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