Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
utils.py409 linesDownload Raw Back to mfa
1##############################################################################2#3# pgAdmin 4 - PostgreSQL Tools4#5# Copyright (C) 2013 - 2024, The pgAdmin Development Team6# This software is released under the PostgreSQL Licence7#8##############################################################################9"""Multi-factor Authentication (MFA) utility functions"""10 11from collections.abc import Callable12from functools import wraps13 14from flask import url_for, session, request, redirect15from flask_login.utils import login_url16from flask_security import current_user, login_required17 18import config19from pgadmin.model import UserMFA, db20from .registry import MultiFactorAuthRegistry21 22 23class ValidationException(Exception):24    """25    class: ValidationException26    Base class: Exception27 28    An exception class for raising validation issue.29    """30    pass31 32 33def segregate_valid_and_invalid_mfa_methods(34    mfa_supported_methods: list35) -> (list, list):36    """37    Segregate the valid and invalid authentication methods from the given38    methods.39 40    Args:41        mfa_supported_methods (list): List of auth methods42 43    Returns:44        list, list: Set of valid & invalid auth methods45    """46 47    invalid_auth_methods = []48    valid_auth_methods = []49 50    for mfa in mfa_supported_methods:51 52        # Put invalid MFA method in separate list53        if mfa not in MultiFactorAuthRegistry._registry:54            if mfa not in invalid_auth_methods:55                invalid_auth_methods.append(mfa)56            continue57 58        # Exclude the duplicate entries59        if mfa in valid_auth_methods:60            continue61 62        valid_auth_methods.append(mfa)63 64    return valid_auth_methods, invalid_auth_methods65 66 67def mfa_suppored_methods() -> dict:68    """69    Returns the dictionary containing information on all supported methods with70    information about whether they're registered for the current user, or not.71 72    It returns information in this format:73    {74        <auth_method_name>: {75            "mfa": <MFA Auth Object>,76            "registered": True|False77        },78        ...79    }80 81    Returns:82        dict: List of all supported MFA methods with the flag for the83              registered with the current user or not.84    """85    supported_mfa_auth_methods = dict()86 87    for auth_method in config.MFA_SUPPORTED_METHODS:88        registry = MultiFactorAuthRegistry.get(auth_method)89        supported_mfa_auth_methods[registry.name] = {90            "mfa": registry, "registered": False91        }92 93    auths = UserMFA.query.filter_by(user_id=current_user.id).all()94 95    for auth in auths:96        if auth.mfa_auth in supported_mfa_auth_methods:97            supported_mfa_auth_methods[auth.mfa_auth]['registered'] = True98 99    return supported_mfa_auth_methods100 101 102def user_supported_mfa_methods():103    """104    Returns the dict for the authentication methods, registered for the105    current user, among the list of supported.106 107    Returns:108        dict: dict for the auth methods109    """110    auths = UserMFA.query.filter_by(user_id=current_user.id).all()111    res = dict()112    supported_mfa_auth_methods = dict()113 114    if len(auths) > 0:115        for auth_method in config.MFA_SUPPORTED_METHODS:116            registry = MultiFactorAuthRegistry.get(auth_method)117            supported_mfa_auth_methods[registry.name] = registry118 119        for auth in auths:120            if auth.mfa_auth in supported_mfa_auth_methods:121                res[auth.mfa_auth] = \122                    supported_mfa_auth_methods[auth.mfa_auth]123 124    return res125 126 127def is_mfa_session_authenticated() -> bool:128    """129    Checks if this session is authenticated, or not.130 131    Returns:132        bool: Is this session authenticated?133    """134    return session.get('mfa_authenticated', False) is True135 136 137def mfa_enabled(execute_if_enabled, execute_if_disabled) -> None:138    """139    A ternary method to enable calling either of the methods based on the140    configuration for the MFA.141 142    When MFA is enabled and has a valid supported auth methods,143    'execute_if_enabled' method is executed, otherwise -144    'execute_if_disabled' method is executed.145 146    Args:147        execute_if_enabled (Callable[[], None]):  Method to executed when MFA148                                                  is enabled.149        execute_if_disabled (Callable[[], None]): Method to be executed when150                                                  MFA is disabled.151 152    Returns:153        None: Expecting the methods to return None as it will not be consumed.154 155    NOTE: Removed the typing anotation as it was giving errors.156    """157 158    is_server_mode = getattr(config, 'SERVER_MODE', False)159    enabled = getattr(config, "MFA_ENABLED", False)160    supported_methods = getattr(config, "MFA_SUPPORTED_METHODS", [])161 162    if is_server_mode is True and enabled is True and \163            isinstance(supported_methods, list):164        supported_methods, _ = segregate_valid_and_invalid_mfa_methods(165            supported_methods166        )167 168        if len(supported_methods) > 0:169            return execute_if_enabled()170 171    return execute_if_disabled()172 173 174def mfa_user_force_registration_required(register, not_register) -> None:175    """176    A ternary method to cenable calling either of the methods based on the177    condition force registration is required.178 179    When force registration is enabled, and the current user has not registered180    for any of the supported authentication method, then the 'register' method181    is executed, otherwise - 'not_register' method is executed.182 183    Args:184        register (Callable[[], None])    : Method to be executed when for185                                           registration required and user has186                                           not registered for any auth method.187        not_register (Callable[[], None]): Method to be executed otherwise.188 189    Returns:190        None: Expecting the methods to return None as it will not be consumed.191    """192    return register() \193        if getattr(config, "MFA_FORCE_REGISTRATION", False) is True else \194        not_register()195 196 197def mfa_user_registered(registered, not_registered) -> None:198    """199    A ternary method to enable calling either of the methods based on the200    condition - if the user is registed for any of the auth methods.201 202    When current user is registered for any of the supported auth method, then203    the 'registered' method is executed, otherwise - 'not_registered' method is204    executed.205 206    Args:207        registered (Callable[[], None])    : Method to be executed when208                                             registered.209        not_registered (Callable[[], None]): Method to be executed when not210                                             registered211 212    Returns:213        None: Expecting the methods to return None as it will not be consumed.214 215    NOTE: Removed the typing anotation as it was giving errors.216    """217 218    return registered() if len(user_supported_mfa_methods()) > 0 else \219        not_registered()220 221 222def mfa_session_authenticated(authenticated, unauthenticated):223    """224    A ternary method to enable calling either of the methods based on the225    condition - if the user has already authenticated, or not.226 227    When current user is already authenticated, then 'authenticated' method is228    executed, otherwise - 'unauthenticated' method is executed.229 230    Args:231        authenticated (Callable[[], None])  : Method to be executed when232                                              user is authenticated.233        unauthenticated (Callable[[], None]): Method to be executed when the234                                              user is not passed the235                                              authentication.236 237    Returns:238        None: Expecting the methods to return None as it will not be consumed.239 240    NOTE: Removed the typing anotation as it was giving errors.241    """242    return authenticated() if session.get('mfa_authenticated', False) is True \243        else unauthenticated()244 245 246def mfa_required(wrapped):247    """248    A decorator do decide the next course of action when a page is being249    opened, it will open the appropriate page in case the 2FA is not passed.250 251    Function executed252        |253    Check for MFA Enabled? --------+254        |                          |255        | No                       |256        |                          | Yes257    Run the wrapped function [END] |258                                   |259        Is user has registered for at least one MFA method? -+260                    |                                        |261                    | No                                     |262                    |                                        |263        Is force registration required? -+                   |264                    |                    |                   | Yes265                    | No                 |                   |266                    |                    | Yes               |267        Run the wrapped function [END]   |                   |268                                         |                   |269                            Open Registration page [END]     |270                                                              |271                                          Open the authentication page [END]272 273    Args:274        func(Callable[..]): Method to be called if authentcation is passed275    """276 277    def get_next_url():278        next_url = request.url279        registration_url = url_for('mfa.register')280 281        if next_url.startswith(registration_url):282            return url_for('browser.index')283 284        return next_url285 286    def redirect_to_mfa_validate_url():287        return redirect(login_url("mfa.validate", next_url=get_next_url()))288 289    def redirect_to_mfa_registration():290        return redirect(login_url("mfa.register", next_url=get_next_url()))291 292    @wraps(wrapped)293    @login_required294    def inner(*args, **kwargs):295        def execute_func():296            session['mfa_authenticated'] = True297            return wrapped(*args, **kwargs)298 299        def if_else_func(_func, first, second):300            def if_else_func_inner():301                return _func(first, second)302            return if_else_func_inner303 304        return mfa_enabled(305            if_else_func(306                mfa_session_authenticated,307                execute_func,308                if_else_func(309                    mfa_user_registered,310                    redirect_to_mfa_validate_url,311                    if_else_func(312                        mfa_user_force_registration_required,313                        redirect_to_mfa_registration,314                        execute_func315                    )316                )317            ),318            execute_func319        )320 321    return inner322 323 324def is_mfa_enabled() -> bool:325    """326    Returns True if MFA is enabled otherwise False327 328    Returns:329        bool: Is MFA Enabled?330    """331    return mfa_enabled(lambda: True, lambda: False)332 333 334def mfa_delete(auth_name: str) -> bool:335    """336    A utility function to delete the auth method for the current user from the337    configuration database.338 339    Args:340        auth_name (str): Name of the argument341 342    Returns:343        bool: True if auth method was registered for the current user, and344              delete successfully, otherwise - False345    """346    auth = UserMFA.query.filter_by(347        user_id=current_user.id, mfa_auth=auth_name348    )349 350    if int(auth.count()) != 0:351        auth.delete()352        db.session.commit()353 354        return True355 356    return False357 358 359def mfa_add(auth_name: str, options: str) -> None:360    """361    A utility funtion to add/update the auth method in the configuration362    database for the current user with the method specific options.363 364    e.g. email-address for 'email' method, and 'secret' for the 'authenticator'365 366    Args:367        auth_name (str): Name of the auth method368        options (str)  : A data options specific to the auth method369    """370    auth = UserMFA.query.filter_by(371        user_id=current_user.id, mfa_auth=auth_name372    ).first()373 374    if auth is None:375        auth = UserMFA(376            user_id=current_user.id,377            mfa_auth=auth_name,378            options=options379        )380        db.session.add(auth)381 382    # We will override the existing options383    auth.options = options384 385    db.session.commit()386 387 388def fetch_auth_option(auth_name: str) -> (str, bool):389    """390    A utility function to fetch the extra data, stored as options, for the391    given auth method for the current user.392 393    Returns a set as (data, Auth method registered?)394 395    Args:396        auth_name (str): Name of the auth method397 398    Returns:399        (str, bool): (data, has current user registered for the auth method?)400    """401    auth = UserMFA.query.filter_by(402        user_id=current_user.id, mfa_auth=auth_name403    ).first()404 405    if auth is None:406        return None, False407 408    return auth.options, True409 
codekingpro/portable-devtools · Team Ai