From a49e00c8af2679c99542b7a4d2de9fbafab4976f Mon Sep 17 00:00:00 2001 From: Alexis Lahouze Date: Wed, 9 Dec 2015 22:52:50 +0100 Subject: [PATCH] Rewrote REST auth management. --- accountant/api/views/users.py | 117 +++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 9 deletions(-) diff --git a/accountant/api/views/users.py b/accountant/api/views/users.py index 9ecf21a..29b2d56 100644 --- a/accountant/api/views/users.py +++ b/accountant/api/views/users.py @@ -14,17 +14,116 @@ You should have received a copy of the GNU Affero General Public License along with Accountant. If not, see . """ -#from accountant import session_scope +# vim: set tw=80 ts=4 sw=4 sts=4: +import arrow -from . import auth -#from .. import api +from functools import wraps + +from flask import request, g +from flask.ext.restful import Resource, fields, reqparse, marshal_with, marshal_with_field + +from sqlalchemy.orm.exc import NoResultFound + +from werkzeug.exceptions import BadRequest + +from accountant import app + +from .. import api_api + +from ..fields import Object + +from ..models.users import User -@auth.verify_password -def verify_password(username, password): +def load_user_from_token(token): + return User.verify_auth_token(token) - if username == 'titi' and password == 'toto': - return True - # Update principal identity - return False +def load_user_from_auth(auth): + token = auth.replace('Bearer ', '', 1) + return load_user_from_token(token) + + +def load_user_from_request(): + # No token found, trying to authenticate using request data. + try: + kwargs = parser.parse_args() + + try: + user = User.query().filter( + User.email == kwargs['email'] + ).one() + + if user and user.verify_password(kwargs['password']): + return user + + except NoResultFound: + pass + except BadRequest: + pass + + return None + + +def authenticate(): + return {'error': 'Please login before executing this request.'}, 401 + + +def requires_auth(f): + @wraps(f) + def wrapped(*args, **kwargs): + if 'Authorization' in request.headers: + auth = request.headers['Authorization'] + user = load_user_from_auth(auth) + else: + user = load_user_from_request() + + if user: + g.user = user + return f(*args, **kwargs) + + return authenticate() + return wrapped + + +token_resource_fields = { + 'token': fields.String, + 'expiration': fields.DateTime(dt_format='iso8601'), + 'token_type': fields.String +} + +user_resource_fields = { + 'id': fields.Integer(default=None), + 'email': fields.String, + 'active': fields.Boolean +} + +parser = reqparse.RequestParser() +parser.add_argument('email', type=str, required=True) +parser.add_argument('password', type=str, required=True) + + +class LoginResource(Resource): + @requires_auth + @marshal_with(token_resource_fields) + def post(self): + user = g.user + + token = user.generate_auth_token() + expiration_time = arrow.now().replace( + seconds=app.config['SESSION_TTL'] + ) + + return { + 'token': token, + 'expiration': expiration_time.datetime, + 'token_type': 'Bearer' + }, 200 + + @requires_auth + @marshal_with_field(Object(user_resource_fields)) + def get(self): + return g.user + + +api_api.add_resource(LoginResource, "/users/login")