Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
login_manager.py544 linesDownload Raw Back to flask_login
1from datetime import datetime2from datetime import timedelta3 4from flask import abort5from flask import current_app6from flask import flash7from flask import g8from flask import has_app_context9from flask import redirect10from flask import request11from flask import session12 13from .config import AUTH_HEADER_NAME14from .config import COOKIE_DURATION15from .config import COOKIE_HTTPONLY16from .config import COOKIE_NAME17from .config import COOKIE_SAMESITE18from .config import COOKIE_SECURE19from .config import ID_ATTRIBUTE20from .config import LOGIN_MESSAGE21from .config import LOGIN_MESSAGE_CATEGORY22from .config import REFRESH_MESSAGE23from .config import REFRESH_MESSAGE_CATEGORY24from .config import SESSION_KEYS25from .config import USE_SESSION_FOR_NEXT26from .mixins import AnonymousUserMixin27from .signals import session_protected28from .signals import user_accessed29from .signals import user_loaded_from_cookie30from .signals import user_loaded_from_request31from .signals import user_needs_refresh32from .signals import user_unauthorized33from .utils import _create_identifier34from .utils import _user_context_processor35from .utils import decode_cookie36from .utils import encode_cookie37from .utils import expand_login_view38from .utils import login_url as make_login_url39from .utils import make_next_param40 41 42class LoginManager:43    """This object is used to hold the settings used for logging in. Instances44    of :class:`LoginManager` are *not* bound to specific apps, so you can45    create one in the main body of your code and then bind it to your46    app in a factory function.47    """48 49    def __init__(self, app=None, add_context_processor=True):50        #: A class or factory function that produces an anonymous user, which51        #: is used when no one is logged in.52        self.anonymous_user = AnonymousUserMixin53 54        #: The name of the view to redirect to when the user needs to log in.55        #: (This can be an absolute URL as well, if your authentication56        #: machinery is external to your application.)57        self.login_view = None58 59        #: Names of views to redirect to when the user needs to log in,60        #: per blueprint. If the key value is set to None the value of61        #: :attr:`login_view` will be used instead.62        self.blueprint_login_views = {}63 64        #: The message to flash when a user is redirected to the login page.65        self.login_message = LOGIN_MESSAGE66 67        #: The message category to flash when a user is redirected to the login68        #: page.69        self.login_message_category = LOGIN_MESSAGE_CATEGORY70 71        #: The name of the view to redirect to when the user needs to72        #: reauthenticate.73        self.refresh_view = None74 75        #: The message to flash when a user is redirected to the 'needs76        #: refresh' page.77        self.needs_refresh_message = REFRESH_MESSAGE78 79        #: The message category to flash when a user is redirected to the80        #: 'needs refresh' page.81        self.needs_refresh_message_category = REFRESH_MESSAGE_CATEGORY82 83        #: The mode to use session protection in. This can be either84        #: ``'basic'`` (the default) or ``'strong'``, or ``None`` to disable85        #: it.86        self.session_protection = "basic"87 88        #: If present, used to translate flash messages ``self.login_message``89        #: and ``self.needs_refresh_message``90        self.localize_callback = None91 92        self.unauthorized_callback = None93 94        self.needs_refresh_callback = None95 96        self.id_attribute = ID_ATTRIBUTE97 98        self._user_callback = None99 100        self._header_callback = None101 102        self._request_callback = None103 104        self._session_identifier_generator = _create_identifier105 106        if app is not None:107            self.init_app(app, add_context_processor)108 109    def setup_app(self, app, add_context_processor=True):  # pragma: no cover110        """111        This method has been deprecated. Please use112        :meth:`LoginManager.init_app` instead.113        """114        import warnings115 116        warnings.warn(117            "'setup_app' is deprecated and will be removed in"118            " Flask-Login 0.7. Use 'init_app' instead.",119            DeprecationWarning,120            stacklevel=2,121        )122        self.init_app(app, add_context_processor)123 124    def init_app(self, app, add_context_processor=True):125        """126        Configures an application. This registers an `after_request` call, and127        attaches this `LoginManager` to it as `app.login_manager`.128 129        :param app: The :class:`flask.Flask` object to configure.130        :type app: :class:`flask.Flask`131        :param add_context_processor: Whether to add a context processor to132            the app that adds a `current_user` variable to the template.133            Defaults to ``True``.134        :type add_context_processor: bool135        """136        app.login_manager = self137        app.after_request(self._update_remember_cookie)138 139        if add_context_processor:140            app.context_processor(_user_context_processor)141 142    def unauthorized(self):143        """144        This is called when the user is required to log in. If you register a145        callback with :meth:`LoginManager.unauthorized_handler`, then it will146        be called. Otherwise, it will take the following actions:147 148            - Flash :attr:`LoginManager.login_message` to the user.149 150            - If the app is using blueprints find the login view for151              the current blueprint using `blueprint_login_views`. If the app152              is not using blueprints or the login view for the current153              blueprint is not specified use the value of `login_view`.154 155            - Redirect the user to the login view. (The page they were156              attempting to access will be passed in the ``next`` query157              string variable, so you can redirect there if present instead158              of the homepage. Alternatively, it will be added to the session159              as ``next`` if USE_SESSION_FOR_NEXT is set.)160 161        If :attr:`LoginManager.login_view` is not defined, then it will simply162        raise a HTTP 401 (Unauthorized) error instead.163 164        This should be returned from a view or before/after_request function,165        otherwise the redirect will have no effect.166        """167        user_unauthorized.send(current_app._get_current_object())168 169        if self.unauthorized_callback:170            return self.unauthorized_callback()171 172        if request.blueprint in self.blueprint_login_views:173            login_view = self.blueprint_login_views[request.blueprint]174        else:175            login_view = self.login_view176 177        if not login_view:178            abort(401)179 180        if self.login_message:181            if self.localize_callback is not None:182                flash(183                    self.localize_callback(self.login_message),184                    category=self.login_message_category,185                )186            else:187                flash(self.login_message, category=self.login_message_category)188 189        config = current_app.config190        if config.get("USE_SESSION_FOR_NEXT", USE_SESSION_FOR_NEXT):191            login_url = expand_login_view(login_view)192            session["_id"] = self._session_identifier_generator()193            session["next"] = make_next_param(login_url, request.url)194            redirect_url = make_login_url(login_view)195        else:196            redirect_url = make_login_url(login_view, next_url=request.url)197 198        return redirect(redirect_url)199 200    def user_loader(self, callback):201        """202        This sets the callback for reloading a user from the session. The203        function you set should take a user ID (a ``str``) and return a204        user object, or ``None`` if the user does not exist.205 206        :param callback: The callback for retrieving a user object.207        :type callback: callable208        """209        self._user_callback = callback210        return self.user_callback211 212    @property213    def user_callback(self):214        """Gets the user_loader callback set by user_loader decorator."""215        return self._user_callback216 217    def request_loader(self, callback):218        """219        This sets the callback for loading a user from a Flask request.220        The function you set should take Flask request object and221        return a user object, or `None` if the user does not exist.222 223        :param callback: The callback for retrieving a user object.224        :type callback: callable225        """226        self._request_callback = callback227        return self.request_callback228 229    @property230    def request_callback(self):231        """Gets the request_loader callback set by request_loader decorator."""232        return self._request_callback233 234    def unauthorized_handler(self, callback):235        """236        This will set the callback for the `unauthorized` method, which among237        other things is used by `login_required`. It takes no arguments, and238        should return a response to be sent to the user instead of their239        normal view.240 241        :param callback: The callback for unauthorized users.242        :type callback: callable243        """244        self.unauthorized_callback = callback245        return callback246 247    def needs_refresh_handler(self, callback):248        """249        This will set the callback for the `needs_refresh` method, which among250        other things is used by `fresh_login_required`. It takes no arguments,251        and should return a response to be sent to the user instead of their252        normal view.253 254        :param callback: The callback for unauthorized users.255        :type callback: callable256        """257        self.needs_refresh_callback = callback258        return callback259 260    def needs_refresh(self):261        """262        This is called when the user is logged in, but they need to be263        reauthenticated because their session is stale. If you register a264        callback with `needs_refresh_handler`, then it will be called.265        Otherwise, it will take the following actions:266 267            - Flash :attr:`LoginManager.needs_refresh_message` to the user.268 269            - Redirect the user to :attr:`LoginManager.refresh_view`. (The page270              they were attempting to access will be passed in the ``next``271              query string variable, so you can redirect there if present272              instead of the homepage.)273 274        If :attr:`LoginManager.refresh_view` is not defined, then it will275        simply raise a HTTP 401 (Unauthorized) error instead.276 277        This should be returned from a view or before/after_request function,278        otherwise the redirect will have no effect.279        """280        user_needs_refresh.send(current_app._get_current_object())281 282        if self.needs_refresh_callback:283            return self.needs_refresh_callback()284 285        if not self.refresh_view:286            abort(401)287 288        if self.needs_refresh_message:289            if self.localize_callback is not None:290                flash(291                    self.localize_callback(self.needs_refresh_message),292                    category=self.needs_refresh_message_category,293                )294            else:295                flash(296                    self.needs_refresh_message,297                    category=self.needs_refresh_message_category,298                )299 300        config = current_app.config301        if config.get("USE_SESSION_FOR_NEXT", USE_SESSION_FOR_NEXT):302            login_url = expand_login_view(self.refresh_view)303            session["_id"] = self._session_identifier_generator()304            session["next"] = make_next_param(login_url, request.url)305            redirect_url = make_login_url(self.refresh_view)306        else:307            login_url = self.refresh_view308            redirect_url = make_login_url(login_url, next_url=request.url)309 310        return redirect(redirect_url)311 312    def header_loader(self, callback):313        """314        This function has been deprecated. Please use315        :meth:`LoginManager.request_loader` instead.316 317        This sets the callback for loading a user from a header value.318        The function you set should take an authentication token and319        return a user object, or `None` if the user does not exist.320 321        :param callback: The callback for retrieving a user object.322        :type callback: callable323        """324        import warnings325 326        warnings.warn(327            "'header_loader' is deprecated and will be removed in"328            " Flask-Login 0.7. Use 'request_loader' instead.",329            DeprecationWarning,330            stacklevel=2,331        )332        self._header_callback = callback333        return callback334 335    def _update_request_context_with_user(self, user=None):336        """Store the given user as ctx.user."""337 338        if user is None:339            user = self.anonymous_user()340 341        g._login_user = user342 343    def _load_user(self):344        """Loads user from session or remember_me cookie as applicable"""345 346        if self._user_callback is None and self._request_callback is None:347            raise Exception(348                "Missing user_loader or request_loader. Refer to "349                "http://flask-login.readthedocs.io/#how-it-works "350                "for more info."351            )352 353        user_accessed.send(current_app._get_current_object())354 355        # Check SESSION_PROTECTION356        if self._session_protection_failed():357            return self._update_request_context_with_user()358 359        user = None360 361        # Load user from Flask Session362        user_id = session.get("_user_id")363        if user_id is not None and self._user_callback is not None:364            user = self._user_callback(user_id)365 366        # Load user from Remember Me Cookie or Request Loader367        if user is None:368            config = current_app.config369            cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)370            header_name = config.get("AUTH_HEADER_NAME", AUTH_HEADER_NAME)371            has_cookie = (372                cookie_name in request.cookies and session.get("_remember") != "clear"373            )374            if has_cookie:375                cookie = request.cookies[cookie_name]376                user = self._load_user_from_remember_cookie(cookie)377            elif self._request_callback:378                user = self._load_user_from_request(request)379            elif header_name in request.headers:380                header = request.headers[header_name]381                user = self._load_user_from_header(header)382 383        return self._update_request_context_with_user(user)384 385    def _session_protection_failed(self):386        sess = session._get_current_object()387        ident = self._session_identifier_generator()388 389        app = current_app._get_current_object()390        mode = app.config.get("SESSION_PROTECTION", self.session_protection)391 392        if not mode or mode not in ["basic", "strong"]:393            return False394 395        # if the sess is empty, it's an anonymous user or just logged out396        # so we can skip this397        if sess and ident != sess.get("_id", None):398            if mode == "basic" or sess.permanent:399                if sess.get("_fresh") is not False:400                    sess["_fresh"] = False401                session_protected.send(app)402                return False403            elif mode == "strong":404                for k in SESSION_KEYS:405                    sess.pop(k, None)406 407                sess["_remember"] = "clear"408                session_protected.send(app)409                return True410 411        return False412 413    def _load_user_from_remember_cookie(self, cookie):414        user_id = decode_cookie(cookie)415        if user_id is not None:416            session["_user_id"] = user_id417            session["_fresh"] = False418            user = None419            if self._user_callback:420                user = self._user_callback(user_id)421            if user is not None:422                app = current_app._get_current_object()423                user_loaded_from_cookie.send(app, user=user)424                return user425        return None426 427    def _load_user_from_header(self, header):428        if self._header_callback:429            user = self._header_callback(header)430            if user is not None:431                app = current_app._get_current_object()432 433                from .signals import _user_loaded_from_header434 435                _user_loaded_from_header.send(app, user=user)436                return user437        return None438 439    def _load_user_from_request(self, request):440        if self._request_callback:441            user = self._request_callback(request)442            if user is not None:443                app = current_app._get_current_object()444                user_loaded_from_request.send(app, user=user)445                return user446        return None447 448    def _update_remember_cookie(self, response):449        # Don't modify the session unless there's something to do.450        if "_remember" not in session and current_app.config.get(451            "REMEMBER_COOKIE_REFRESH_EACH_REQUEST"452        ):453            session["_remember"] = "set"454 455        if "_remember" in session:456            operation = session.pop("_remember", None)457 458            if operation == "set" and "_user_id" in session:459                self._set_cookie(response)460            elif operation == "clear":461                self._clear_cookie(response)462 463        return response464 465    def _set_cookie(self, response):466        # cookie settings467        config = current_app.config468        cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)469        domain = config.get("REMEMBER_COOKIE_DOMAIN")470        path = config.get("REMEMBER_COOKIE_PATH", "/")471 472        secure = config.get("REMEMBER_COOKIE_SECURE", COOKIE_SECURE)473        httponly = config.get("REMEMBER_COOKIE_HTTPONLY", COOKIE_HTTPONLY)474        samesite = config.get("REMEMBER_COOKIE_SAMESITE", COOKIE_SAMESITE)475 476        if "_remember_seconds" in session:477            duration = timedelta(seconds=session["_remember_seconds"])478        else:479            duration = config.get("REMEMBER_COOKIE_DURATION", COOKIE_DURATION)480 481        # prepare data482        data = encode_cookie(str(session["_user_id"]))483 484        if isinstance(duration, int):485            duration = timedelta(seconds=duration)486 487        try:488            expires = datetime.utcnow() + duration489        except TypeError as e:490            raise Exception(491                "REMEMBER_COOKIE_DURATION must be a datetime.timedelta,"492                f" instead got: {duration}"493            ) from e494 495        # actually set it496        response.set_cookie(497            cookie_name,498            value=data,499            expires=expires,500            domain=domain,501            path=path,502            secure=secure,503            httponly=httponly,504            samesite=samesite,505        )506 507    def _clear_cookie(self, response):508        config = current_app.config509        cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)510        domain = config.get("REMEMBER_COOKIE_DOMAIN")511        path = config.get("REMEMBER_COOKIE_PATH", "/")512        response.delete_cookie(cookie_name, domain=domain, path=path)513 514    @property515    def _login_disabled(self):516        """Legacy property, use app.config['LOGIN_DISABLED'] instead."""517        import warnings518 519        warnings.warn(520            "'_login_disabled' is deprecated and will be removed in"521            " Flask-Login 0.7. Use 'LOGIN_DISABLED' in 'app.config'"522            " instead.",523            DeprecationWarning,524            stacklevel=2,525        )526 527        if has_app_context():528            return current_app.config.get("LOGIN_DISABLED", False)529        return False530 531    @_login_disabled.setter532    def _login_disabled(self, newvalue):533        """Legacy property setter, use app.config['LOGIN_DISABLED'] instead."""534        import warnings535 536        warnings.warn(537            "'_login_disabled' is deprecated and will be removed in"538            " Flask-Login 0.7. Use 'LOGIN_DISABLED' in 'app.config'"539            " instead.",540            DeprecationWarning,541            stacklevel=2,542        )543        current_app.config["LOGIN_DISABLED"] = newvalue544 
codekingpro/portable-devtools · Team Ai