Source code for fplaneserver.lib.user

"""
This module contains a user model, the database setup and the Flask-login hooks
for storing usernames and passwords on the server
"""
# deal with time stamps in the database
import peewee

from flask import current_app, g, jsonify
from flask_login import UserMixin
# from werkzeug.security import check_password_hash

dbProxy = peewee.Proxy()


# ============ Database setup ============ #
[docs]def connect_db(): """Connects to the database specified in the USER_DATABASE configuration field. Returns ------- :py:class:`~peewee.SqliteDatabase` """ return peewee.SqliteDatabase(current_app.config['USER_DATABASE'])
[docs]def get_db(): """Opens a new database connection if there is none yet for the current application context. Returns ------- :py:class:`~peewee.SqliteDatabase` """ if not hasattr(g, 'sqlite_db'): g.sqlite_db = connect_db() dbProxy.initialize(g.sqlite_db) dbProxy.connect() return g.sqlite_db
[docs]def init_db(app): """Initialize the database. Parameters ---------- app: Flask object application """ with app.app_context(): db = get_db() User.create_table(True) db.commit()
[docs]def close_db(): """Closes the database again at the end of the request.""" if hasattr(g, 'sqlite_db'): g.sqlite_db.close()
# ============ User setup ============#
[docs]def handle_user_error(error): '''Handler for errors, jsonifies the error as a dictionary and sets the response status code from the error status code. Parameters ---------- error : :py:class:`~fplaneserver.user.UserException` The user exception Returns ------- :py:class:`flask.Response` ''' response = jsonify(error.to_dict()) response.status_code = error.status_code return response
[docs]class UserException(Exception): '''Overloaded from Exception for handling user login errors. Attributes ---------- message : str The error message of the exception status_code : int The status_code to be sent with response. Parameters ---------- message : str The error message of the exception status_code : int, optional The status_code to be sent with response. Default is 400 ''' status_code = 400 def __init__(self, message, status_code=None): super().__init__(self) self.message = message if status_code is not None: self.status_code = status_code
[docs] def to_dict(self): '''Create a dictionart containing the message. This dictionary can be turned into a json and sent with the response Returns ------- dict ''' return {'message': self.message}
[docs]class NoUserException(UserException): '''Overloaded from :py:class:`~fplaneserver.user.UserException` with a default status code 404 ''' status_code = 401
[docs]class InvalidUserException(UserException): '''Overloaded from :py:class:`~fplaneserver.user.UserException` with a default status code 403 ''' status_code = 403
[docs]class User(peewee.Model, UserMixin): """ User object that wraps around a sqlite 3 database containing a table with the registered users. The user apikey should be created using ``binascii.hexlify(os.urandom(24))`` Attributes ---------- id : int The user id username : str The username firstname : str First name of the user lastname : str Last name of the user email : str E-mail address of the user password : str The users password `Currently unused` apikey : str The key used for login. """ id = peewee.AutoField() username = peewee.TextField(unique=True) firstname = peewee.TextField() lastname = peewee.TextField() email = peewee.TextField() password = peewee.TextField() # Generate this with binascii.hexlify(os.urandom(24))? apikey = peewee.TextField(unique=True) def __init__(self, **kwargs): super(User, self).__init__(**kwargs) self.authenticated = False
[docs] def is_active(self): '''Return True if the user is active. (Always True)''' return True
[docs] def get_id(self): ''' Return the id of the user''' return self.id
[docs] def is_authenticated(self): '''Return True if the user authenticated successfully''' return self.authenticated
[docs] def is_anonymous(self): '''Return True if the user is anonymous. (Always False)''' return False
class Meta: database = dbProxy
# ============ Flask-login necessary hooks ============#
[docs]def load_user_from_request(request): '''Interface hook for flask. For a given request object, try to load the user from the Authorization header. Return None, if the user cannot be found, or if the Authorization format is wrong. The request object must contain an Authorization header of the form "FPS_API APIKEY=<user apikey>" Parameters ---------- request : :py:class:`flask.Request` The request object. ''' api_string = request.headers.get('Authorization') if api_string: try: api_method, key = api_string.split() api_key = key.split('=')[1] except ValueError: # Wrong authorization format g.logger.info('Wrong authorization format', extra=g.extra) return None if api_method == 'FPS_API': try: u = User.get(User.apikey == api_key) return u except peewee.DoesNotExist: # No user found return None return None