[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