Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #163: Some fixes in documentation and add auth example #166

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ First we need to create eve-side authentication:
register_views(app)
app.run()

Next step is the `User` SQLAlchemy model:
Next step is the `User` And `Role` SQLAlchemy model:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "And" -> "and"


.. code-block:: python

Expand All @@ -92,19 +92,31 @@ Next step is the `User` SQLAlchemy model:
as Serializer
from itsdangerous import SignatureExpired, BadSignature

from sqlalchemy.orm import validates
from sqlalchemy.orm import validates, relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey, Integer, String, Table

Base = declarative_base()
SECRET_KEY = 'this-is-my-super-secret-key'

association_table = Table(
'association', Base.metadata,
Column('role_id', Integer, ForeignKey('roles.id')),
Column('user_login', String(80), ForeignKey('users.login')),
)

class Role(Base):
__tablename__ = 'roles'

id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(80))

class User(Base):
__tablename__ = 'users'

login = Column(String, primary_key=True)
password = Column(String)
roles = relationship("Role", backref="users")
login = Column(String(120), primary_key=True)
password = Column(String(120))
roles = relationship('Role', secondary=association_table, backref='users')

def generate_auth_token(self, expiration=24*60*60):
"""Generates token for given expiration
Expand Down Expand Up @@ -134,21 +146,19 @@ Next step is the `User` SQLAlchemy model:
return len(allowed_roles) > 0

def generate_salt(self):
return ''.join(random.sample(string.letters, 12))
return SECRET_KEY

def encrypt(self, password):
"""Encrypt password using hashlib and current salt.
"""
return str(hashlib.sha1(password + str(self.salt))\
.hexdigest())
return str(hashlib.sha1((password + str(self.generate_salt())).encode('utf8')).hexdigest())

@validates('password')
def _set_password(self, key, value):
"""Using SQLAlchemy validation makes sure each
time password is changed it will get encrypted
before flushing to db.
"""
self.salt = self.generate_salt()
return self.encrypt(value)

def check_password(self, password):
Expand Down Expand Up @@ -201,6 +211,7 @@ And finally a flask login view:
return jsonify({'token': token.decode('ascii')})
raise Unauthorized('Wrong username and/or password.')

For more code see: `examples/auth`_

Start Eve
---------
Expand Down Expand Up @@ -395,3 +406,4 @@ referring each other.
.. _`Eve Authentication`: http://python-eve.org/authentication.html#token-based-authentication
.. _`Eve Embedded Resource Serialization`: http://python-eve.org/features.html#embedded-resource-serialization
.. _`Eve Projections`: http://python-eve.org/features.html#projections
.. _`examples/auth`: https://github.com/pyeve/eve-sqlalchemy/tree/master/examples
Empty file added examples/auth/__init__.py
Empty file.
35 changes: 35 additions & 0 deletions examples/auth/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pymysql
from eve import Eve
from eve.auth import TokenAuth
from eve_sqlalchemy import SQL
from eve_sqlalchemy.validation import ValidatorSQL
from models import Base, Role, User
from views import register_views

pymysql.install_as_MySQLdb()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to use pymysql here? The other examples use a in-memory SQLite database. Would that be sufficient here, too?



class TokenAuth(TokenAuth):
def check_auth(self, token, allowed_roles, resource, method):
login = User.verify_auth_token(token)
if login and allowed_roles:
user = app.data.driver.session.query(User).get(login)
return user.isAuthorized(allowed_roles)
else:
return False


if __name__ == '__main__':
app = Eve(auth=TokenAuth, validator=ValidatorSQL, data=SQL)
db = app.data.driver
Base.metadata.bind = db.engine
db.Model = Base
db.create_all()
if not db.session.query(User).count():
r = Role(name='admin')
db.session.add(r)
db.session.commit()
db.session.add(User(login='aa', password='bb', roles=[r]))
db.session.commit()
register_views(app)
app.run(debug=True)
96 changes: 96 additions & 0 deletions examples/auth/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import hashlib

from itsdangerous import (
BadSignature, SignatureExpired,
TimedJSONWebSignatureSerializer as Serializer,
)
from sqlalchemy import (
Column, DateTime, ForeignKey, Integer, String, Table, func,
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import column_property, relationship, validates

Base = declarative_base()

SECRET_KEY = 'this-is-my-super-sercret-key'


class CommonColumns(Base):
__abstract__ = True
_created = Column(DateTime, default=func.now())
_updated = Column(DateTime, default=func.now())
_etag = Column(String(40))


class People(CommonColumns):
__tablename__ = 'people'
id = Column(Integer, primary_key=True, autoincrement=True)
firstname = Column(String(80))
lastname = Column(String(120))
fullname = column_property(firstname + " " + lastname)


class Invoices(CommonColumns):
__tablename__ = 'invoices'
id = Column(Integer, primary_key=True, autoincrement=True)
numner = Column(Integer)
people_id = Column(Integer, ForeignKey('people.id'))
people = relationship(People, uselist=False)


association_table = Table(
'association', Base.metadata,
Column('role_id', Integer, ForeignKey('roles.id')),
Column('user_login', String(80), ForeignKey('users.login')),
)


class Role(Base):
__tablename__ = 'roles'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(80))


class User(Base):
__tablename__ = 'users'
login = Column(String(120), primary_key=True)
password = Column(String(120))
roles = relationship('Role', secondary=association_table, backref='users')

def generate_auth_token(self, expiration=24 * 60 * 60):
s = Serializer(SECRET_KEY, expires_in=expiration)
return s.dumps({'login': self.login})

@staticmethod
def verify_auth_token(token):
s = Serializer(SECRET_KEY)
try:
data = s.loads(token)
print(data)
except SignatureExpired:
return None
except BadSignature:
return None
return data['login']

def isAuthorized(self, role_names):
allowed_roles = set(
[r.name for r in self.roles]).intersection(set(role_names))
return len(allowed_roles) > 0

def generate_salt(self):
return SECRET_KEY

def encrypt(self, password):
return str(hashlib.sha1(
(password + str(self.generate_salt())).encode('utf8')
).hexdigest())

@validates('password')
def _set_password(self, key, value):
return self.encrypt(value)

def check_password(self, password):
if not self.password:
return False
return self.encrypt(password) == self.password
15 changes: 15 additions & 0 deletions examples/auth/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Cerberus==0.9.2
Eve==0.6.4
Eve-SQLAlchemy==0.5.0
Events==0.2.2
Flask==0.10.1
Flask-PyMongo==0.5.1
Flask-SQLAlchemy==2.3.2
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==0.23
pymongo==3.6.0
PyMySQL==0.7.11
simplejson==3.13.2
SQLAlchemy==1.1.15
Werkzeug==0.13
21 changes: 21 additions & 0 deletions examples/auth/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from eve_sqlalchemy.config import DomainConfig, ResourceConfig
from models import Invoices, People

DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite://'
SQLALCHEMY_TARCK_MODIFICATIONS = False
RESOURCE_METHODS = ['GET', 'POST']

ALLOWED_ROLES = ['admin']

DOMAIN = DomainConfig({
'people': ResourceConfig(People),
'invoice': ResourceConfig(Invoices)
}).render()

DOMAIN['people'].update({
'item_title': 'person',
'cache_control': 'max-age=10,must-revalidate',
'cache_expires': 10,
'resource_methods': ['GET', 'POST', 'DELETE']
})
19 changes: 19 additions & 0 deletions examples/auth/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from flask import jsonify, request
from models import User
from werkzeug.exceptions import Unauthorized


def register_views(app):
@app.route('/login', methods=['POST'])
def login(**kwargs):
data = request.get_json()
login = data.get('username')
password = data.get('password')
if not login or not password:
raise Unauthorized('Wrong username and/or password.')
else:
user = app.data.driver.session.query(User).get(login)
if user and user.check_password(password):
token = user.generate_auth_token()
return jsonify({'token': token.decode('ascii')})
raise Unauthorized('Wrong username and/or password.')