[Doc] More documentation on decorator and plugins roles and auth*
This commit is contained in:
parent
a5d3b837cd
commit
8a9776ae0e
|
@ -10,8 +10,8 @@ def get_all():
|
||||||
return Role.query.all()
|
return Role.query.all()
|
||||||
|
|
||||||
|
|
||||||
def get(rid):
|
def get(role_name):
|
||||||
role = Role.query.get(rid).one_or_none()
|
role = Role.query.filter(Role.name == role_name).one_or_none()
|
||||||
if not role:
|
if not role:
|
||||||
raise NotFound
|
raise NotFound
|
||||||
|
|
||||||
|
@ -42,11 +42,11 @@ def create_permissions(permissions):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def create_role(name, permissions=[]):
|
def create_role(name: str, permissions=[]):
|
||||||
role = Role(name=name)
|
role = Role(name=name)
|
||||||
db.session.add(role)
|
db.session.add(role)
|
||||||
set_permissions(role, permissions)
|
set_permissions(role, permissions)
|
||||||
return role.id
|
return role
|
||||||
|
|
||||||
|
|
||||||
def delete(role):
|
def delete(role):
|
||||||
|
|
|
@ -7,6 +7,15 @@ from flaschengeist.controller import sessionController
|
||||||
|
|
||||||
|
|
||||||
def login_required(permission=None):
|
def login_required(permission=None):
|
||||||
|
"""Decorator use to make a route only accessible by logged in users.
|
||||||
|
Sets ``current_session`` into kwargs of wrapped function with session identified by Authorization header.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
permission: Optional permission needed for this route
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Wrapped function with login (and permission) guard
|
||||||
|
"""
|
||||||
def wrap(func):
|
def wrap(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapped_f(*args, **kwargs):
|
def wrapped_f(*args, **kwargs):
|
||||||
|
|
|
@ -28,8 +28,8 @@ def login():
|
||||||
POST-data: {'userid': string, 'password': string}
|
POST-data: {'userid': string, 'password': string}
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A JSON object with `flaschengeist.system.models.user.User` and created
|
A JSON object with `flaschengeist.models.user.User` and created
|
||||||
`flaschengeist.system.models.session.Session` or HTTP error
|
`flaschengeist.models.session.Session` or HTTP error
|
||||||
"""
|
"""
|
||||||
logger.debug("Start log in.")
|
logger.debug("Start log in.")
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
@ -60,7 +60,7 @@ def get_sessions(current_session, **kwargs):
|
||||||
Route: ``/auth`` | Method: ``GET``
|
Route: ``/auth`` | Method: ``GET``
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A JSON array of `flaschengeist.system.models.session.Session` or HTTP error
|
A JSON array of `flaschengeist.models.session.Session` or HTTP error
|
||||||
"""
|
"""
|
||||||
sessions = sessionController.get_users_sessions(current_session._user)
|
sessions = sessionController.get_users_sessions(current_session._user)
|
||||||
return jsonify(sessions)
|
return jsonify(sessions)
|
||||||
|
@ -100,7 +100,7 @@ def get_session(token, current_session, **kwargs):
|
||||||
current_session: Session sent with Authorization Header
|
current_session: Session sent with Authorization Header
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON encoded `flaschengeist.system.models.session.Session` or HTTP error
|
JSON encoded `flaschengeist.models.session.Session` or HTTP error
|
||||||
"""
|
"""
|
||||||
logger.debug("get token {{ {} }}".format(token))
|
logger.debug("get token {{ {} }}".format(token))
|
||||||
session = sessionController.get_session(token, current_session._user)
|
session = sessionController.get_session(token, current_session._user)
|
||||||
|
@ -153,7 +153,7 @@ def get_assocd_user(token, current_session, **kwargs):
|
||||||
current_session: Session sent with Authorization Header
|
current_session: Session sent with Authorization Header
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON encoded `flaschengeist.system.models.user.User` or HTTP error
|
JSON encoded `flaschengeist.models.user.User` or HTTP error
|
||||||
"""
|
"""
|
||||||
logger.debug("get token {{ {} }}".format(token))
|
logger.debug("get token {{ {} }}".format(token))
|
||||||
session = sessionController.get_session(token, current_session._user)
|
session = sessionController.get_session(token, current_session._user)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"""LDAP Authentication Provider Plugin"""
|
||||||
|
|
||||||
import ssl
|
import ssl
|
||||||
from ldap3.utils.hashed import hashed
|
from ldap3.utils.hashed import hashed
|
||||||
from ldap3 import SUBTREE, MODIFY_REPLACE, HASHED_SALTED_MD5
|
from ldap3 import SUBTREE, MODIFY_REPLACE, HASHED_SALTED_MD5
|
||||||
|
|
|
@ -1,36 +1,40 @@
|
||||||
import binascii
|
"""Authentication Provider Plugin
|
||||||
import hashlib
|
Allows simple authentication using Username-Password pair with password saved into
|
||||||
import os
|
Flaschengeist database (as User attribute)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import binascii
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
|
|
||||||
from flaschengeist.plugins import AuthPlugin
|
from flaschengeist.plugins import AuthPlugin
|
||||||
from flaschengeist.models.user import User
|
from flaschengeist.models.user import User
|
||||||
|
|
||||||
|
|
||||||
def _hash_password(password):
|
|
||||||
salt = hashlib.sha256(os.urandom(60)).hexdigest().encode("ascii")
|
|
||||||
pass_hash = hashlib.pbkdf2_hmac("sha3-512", password.encode("utf-8"), salt, 100000)
|
|
||||||
pass_hash = binascii.hexlify(pass_hash)
|
|
||||||
return (salt + pass_hash).decode("ascii")
|
|
||||||
|
|
||||||
|
|
||||||
def _verify_password(stored_password, provided_password):
|
|
||||||
salt = stored_password[:64]
|
|
||||||
stored_password = stored_password[64:]
|
|
||||||
pass_hash = hashlib.pbkdf2_hmac("sha3-512", provided_password.encode("utf-8"), salt.encode("ascii"), 100000)
|
|
||||||
pass_hash = binascii.hexlify(pass_hash).decode("ascii")
|
|
||||||
return pass_hash == stored_password
|
|
||||||
|
|
||||||
|
|
||||||
class AuthPlain(AuthPlugin):
|
class AuthPlain(AuthPlugin):
|
||||||
def login(self, user: User, password: str):
|
def login(self, user: User, password: str):
|
||||||
if user.has_attribute("password"):
|
if user.has_attribute("password"):
|
||||||
return _verify_password(user.get_attribute("password"), password)
|
return AuthPlain._verify_password(user.get_attribute("password"), password)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def modify_user(self, user, password, new_password=None):
|
def modify_user(self, user, password, new_password=None):
|
||||||
if password is not None and not self.login(user, password):
|
if password is not None and not self.login(user, password):
|
||||||
raise BadRequest
|
raise BadRequest
|
||||||
if new_password:
|
if new_password:
|
||||||
user.set_attribute("password", _hash_password(new_password))
|
user.set_attribute("password", AuthPlain._hash_password(new_password))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _hash_password(password):
|
||||||
|
salt = hashlib.sha256(os.urandom(60)).hexdigest().encode("ascii")
|
||||||
|
pass_hash = hashlib.pbkdf2_hmac("sha3-512", password.encode("utf-8"), salt, 100000)
|
||||||
|
pass_hash = binascii.hexlify(pass_hash)
|
||||||
|
return (salt + pass_hash).decode("ascii")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _verify_password(stored_password, provided_password):
|
||||||
|
salt = stored_password[:64]
|
||||||
|
stored_password = stored_password[64:]
|
||||||
|
pass_hash = hashlib.pbkdf2_hmac("sha3-512", provided_password.encode("utf-8"), salt.encode("ascii"), 100000)
|
||||||
|
pass_hash = binascii.hexlify(pass_hash).decode("ascii")
|
||||||
|
return pass_hash == stored_password
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
"""Roles plugin
|
||||||
|
|
||||||
|
Provides routes used to configure roles and permissions of users / roles.
|
||||||
|
"""
|
||||||
|
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from werkzeug.exceptions import NotFound, BadRequest
|
from werkzeug.exceptions import NotFound, BadRequest
|
||||||
|
|
||||||
|
@ -15,57 +20,98 @@ class RolesPlugin(Plugin):
|
||||||
super().__init__(config, roles_bp, permissions=[_permission_edit, _permission_delete])
|
super().__init__(config, roles_bp, permissions=[_permission_edit, _permission_delete])
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
@roles_bp.route("/roles", methods=["GET"])
|
||||||
# Routes #
|
@login_required()
|
||||||
# #
|
def list_roles(current_session):
|
||||||
# /roles POST: register new #
|
"""List all existing roles
|
||||||
# GET: get all roles #
|
|
||||||
# /roles/permissions GET: get all permissions #
|
Route: ``/roles`` | Method: ``GET``
|
||||||
# /roles/<rid> GET: get role with rid #
|
|
||||||
# PUT: modify role / permission #
|
Args:
|
||||||
# DELETE: remove role #
|
current_session: Session sent with Authorization Header
|
||||||
######################################################
|
|
||||||
|
Returns:
|
||||||
|
JSON encodes array of `flaschengeist.models.user.Role`
|
||||||
|
"""
|
||||||
|
roles = roleController.get_all()
|
||||||
|
return jsonify(roles)
|
||||||
|
|
||||||
|
|
||||||
@roles_bp.route("/roles", methods=["POST"])
|
@roles_bp.route("/roles", methods=["POST"])
|
||||||
@login_required(permission=_permission_edit)
|
@login_required(permission=_permission_edit)
|
||||||
def add_role(**kwargs):
|
def create_role(current_session):
|
||||||
|
"""Create new role
|
||||||
|
|
||||||
|
Route: ``/roles`` | Method: ``POST``
|
||||||
|
|
||||||
|
POST-data: ``{name: string, permissions?: string[]}``
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current_session: Session sent with Authorization Header
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTTP-200 or HTTP error
|
||||||
|
"""
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data or "name" not in data:
|
if not data or "name" not in data:
|
||||||
raise BadRequest
|
raise BadRequest
|
||||||
if "permissions" in data:
|
if "permissions" in data:
|
||||||
permissions = data["permissions"]
|
permissions = data["permissions"]
|
||||||
role = roleController.create_role(data["name"], permissions)
|
roleController.create_role(data["name"], permissions)
|
||||||
return jsonify({"ok": "ok", "id": role.id})
|
|
||||||
|
|
||||||
|
|
||||||
@roles_bp.route("/roles", methods=["GET"])
|
|
||||||
@login_required()
|
|
||||||
def list_roles(**kwargs):
|
|
||||||
roles = roleController.get_all()
|
|
||||||
return jsonify(roles)
|
|
||||||
|
|
||||||
|
|
||||||
@roles_bp.route("/roles/permissions", methods=["GET"])
|
@roles_bp.route("/roles/permissions", methods=["GET"])
|
||||||
@login_required()
|
@login_required()
|
||||||
def list_permissions(**kwargs):
|
def list_permissions(current_session):
|
||||||
|
"""List all existing permissions
|
||||||
|
|
||||||
|
Route: ``/roles/permissions`` | Method: ``GET``
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current_session: Session sent with Authorization Header
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON encoded list of `flaschengeist.models.user.Permission`
|
||||||
|
"""
|
||||||
permissions = roleController.get_permissions()
|
permissions = roleController.get_permissions()
|
||||||
return jsonify(permissions)
|
return jsonify(permissions)
|
||||||
|
|
||||||
|
|
||||||
@roles_bp.route("/roles/<rid>", methods=["GET"])
|
@roles_bp.route("/roles/<role_name>", methods=["GET"])
|
||||||
@login_required()
|
@login_required()
|
||||||
def __get_role(rid, **kwargs):
|
def get_role(role_name, current_session):
|
||||||
role = roleController.get(rid)
|
"""Get role by name
|
||||||
if role:
|
|
||||||
return jsonify({"id": role.id, "name": role, "permissions": role.permissions})
|
Route: ``/roles/<role_name>`` | Method: ``GET``
|
||||||
raise NotFound
|
|
||||||
|
Args:
|
||||||
|
role_name: Name of role to retrieve
|
||||||
|
current_session: Session sent with Authorization Header
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON encoded `flaschengeist.models.user.Role` or HTTP error
|
||||||
|
"""
|
||||||
|
role = roleController.get(role_name)
|
||||||
|
return jsonify(role)
|
||||||
|
|
||||||
|
|
||||||
@roles_bp.route("/roles/<rid>", methods=["PUT"])
|
@roles_bp.route("/roles/<role_name>", methods=["PUT"])
|
||||||
@login_required(permission=_permission_edit)
|
@login_required(permission=_permission_edit)
|
||||||
def __edit_role(rid, **kwargs):
|
def edit_role(role_name, current_session):
|
||||||
role = roleController.get(rid)
|
"""Edit role, rename and / or set permissions
|
||||||
|
|
||||||
|
Route: ``/roles/<role_name>`` | Method: ``PUT``
|
||||||
|
|
||||||
|
POST-data: ``{name?: string, permissions?: string[]}``
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role_name: Name of role
|
||||||
|
current_session: Session sent with Authorization Header
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTTP-200 or HTTP error
|
||||||
|
"""
|
||||||
|
role = roleController.get(role_name)
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if "name" in data:
|
if "name" in data:
|
||||||
|
@ -73,13 +119,21 @@ def __edit_role(rid, **kwargs):
|
||||||
if "permissions" in data:
|
if "permissions" in data:
|
||||||
roleController.set_permissions(role, data["permissions"])
|
roleController.set_permissions(role, data["permissions"])
|
||||||
roleController.update_role(role)
|
roleController.update_role(role)
|
||||||
return jsonify({"ok": "ok"})
|
|
||||||
|
|
||||||
|
|
||||||
@roles_bp.route("/roles/<rid>", methods=["DELETE"])
|
@roles_bp.route("/roles/<role_name>", methods=["DELETE"])
|
||||||
@login_required(permission=_permission_delete)
|
@login_required(permission=_permission_delete)
|
||||||
def __delete_role(rid, **kwargs):
|
def delete_role(role_name, current_session):
|
||||||
role = roleController.get(rid)
|
"""Delete role
|
||||||
roleController.delete(role)
|
|
||||||
|
|
||||||
return jsonify({"ok": "ok"})
|
Route: ``/roles/<role_name>`` | Method: ``DELETE``
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role_name: Name of role
|
||||||
|
current_session: Session sent with Authorization Header
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTTP-200 or HTTP error
|
||||||
|
"""
|
||||||
|
role = roleController.get(role_name)
|
||||||
|
roleController.delete(role)
|
||||||
|
|
Loading…
Reference in New Issue