Exemplo n.º 1
0
 def wrapper(*args, **kwargs):
     PraetorianError.require_condition(
         current_rolenames().issubset(set(accepted_rolenames)),
         "This endpoint requires one of the following roles: {}",
         [', '.join(accepted_rolenames)],
     )
     return method(*args, **kwargs)
Exemplo n.º 2
0
    def _validate_user_class(cls, user_class):
        """
        Validates the supplied user_class to make sure that it has the
        class methods necessary to function correctly.

        Requirements:

        - ``lookup`` method. Accepts a string parameter, returns instance
        - ``identify`` method. Accepts an identity parameter, returns instance
        """
        PraetorianError.require_condition(
            getattr(user_class, 'lookup', None) is not None,
            textwrap.dedent("""
                The user_class must have a lookup class method:
                user_class.lookup(<str>) -> <user instance>
            """),
        )
        PraetorianError.require_condition(
            getattr(user_class, 'identify', None) is not None,
            textwrap.dedent("""
                The user_class must have an identify class method:
                user_class.identify(<identity>) -> <user instance>
            """),
        )
        # TODO: Figure out how to check for an identity property
        return user_class
Exemplo n.º 3
0
    def authenticate(self, username, password):
        """
        Verifies that a password matches the stored password for that username.
        If verification passes, the matching user instance is returned
        """
        PraetorianError.require_condition(
            self.user_class is not None,
            "Praetorian must be initialized before this method is available",
        )
        user = self.user_class.lookup(username)
        AuthenticationError.require_condition(
            user is not None and self._verify_password(
                password,
                user.password,
            ),
            'The username and/or password are incorrect',
        )
        """
        If we are set to PRAETORIAN_HASH_AUTOUPDATE then check our hash
            and if needed, update the user.  The developer is responsible
            for using the returned user object and updating the data
            storage endpoint.

        Else, if we are set to PRAETORIAN_HASH_AUTOTEST then check out hash
            and return exception if our hash is using the wrong scheme,
            but don't modify the user.
        """
        if self.hash_autoupdate:
            self.verify_and_update(user=user, password=password)
        elif self.hash_autotest:
            self.verify_and_update(user=user)

        return user
Exemplo n.º 4
0
 def encrypt_password(self, raw_password):
     """
     Encrypts a plaintext password using the stored passlib password context
     """
     PraetorianError.require_condition(
         self.pwd_ctx is not None,
         "Praetorian must be initialized before this method is available",
     )
     return self.pwd_ctx.encrypt(raw_password, scheme=self.hash_scheme)
Exemplo n.º 5
0
 def _verify_password(self, raw_password, hashed_password):
     """
     Verifies that a plaintext password matches the hashed version of that
     password using the stored passlib password context
     """
     PraetorianError.require_condition(
         self.pwd_ctx is not None,
         "Praetorian must be initialized before this method is available",
     )
     return self.pwd_ctx.verify(raw_password, hashed_password)
Exemplo n.º 6
0
 def pack_header_for_user(self, user):
     """
     A method that may only be used for testing that packages a jwt token
     into a header dict for a given user
     """
     PraetorianError.require_condition(
         self.is_testing,
         "Pack header may only be used for testing",
     )
     token = self.encode_jwt_token(user)
     return {self.header_name: self.header_type + ' ' + token}
Exemplo n.º 7
0
def current_guard():
    """
    Fetches the current instance of flask-praetorian that is attached to the
    current flask app
    """
    guard = flask.current_app.extensions.get('praetorian', None)
    PraetorianError.require_condition(
        guard is not None,
        "No current guard found; Praetorian must be initialized first",
    )
    return guard
Exemplo n.º 8
0
def current_user_id():
    """
    This method returns the user id retrieved from jwt token data attached to
    the current flask app's context
    """
    jwt_data = get_jwt_data_from_app_context()
    user_id = jwt_data.get('id')
    PraetorianError.require_condition(
        user_id is not None,
        "Could not fetch an id for the current user",
    )
    return user_id
Exemplo n.º 9
0
def current_token(token_id):
    """
    This method returns a user instance for jwt token data attached to the
    current flask app's context
    """
    #token_id = current_token_id()
    guard = current_guard()
    token = guard.token_store_class.identify(token_id)
    PraetorianError.require_condition(
        token is not None,
        "Could not identify the current token from the current id",
    )
    return token
Exemplo n.º 10
0
 def authenticate(self, username, password):
     """
     Verifies that a password matches the stored password for that username.
     If verification passes, the matching user instance is returned
     """
     PraetorianError.require_condition(
         self.user_class is not None,
         "Praetorian must be initialized before this method is available",
     )
     user = self.user_class.lookup(username)
     if user is None or not self.verify_password(password, user.password):
         return None
     return user
Exemplo n.º 11
0
def current_user():
    """
    This method returns a user instance for jwt token data attached to the
    current flask app's context
    """
    user_id = current_user_id()
    guard = current_guard()
    user = guard.user_class.identify(user_id)
    PraetorianError.require_condition(
        user is not None,
        "Could not identify the current user from the current id",
    )
    return user
Exemplo n.º 12
0
def get_jwt_data_from_app_context():
    """
    Fetches a dict of jwt token data from the top of the flask app's context
    """
    ctx = flask._app_ctx_stack.top
    jwt_data = getattr(ctx, 'jwt_data', None)
    PraetorianError.require_condition(
        jwt_data is not None,
        """
        No jwt_data found in app context.
        Make sure @auth_required decorator is specified *first* for route
        """,
    )
    return jwt_data
Exemplo n.º 13
0
 def hash_password(self, raw_password):
     """
     Hashes a plaintext password using the stored passlib password context
     """
     PraetorianError.require_condition(
         self.pwd_ctx is not None,
         "Praetorian must be initialized before this method is available",
     )
     """
     `scheme` is now set with self.pwd_ctx.update(default=scheme) due
         to the depreciation in upcoming passlib 2.0.
      zillions of warnings suck.
     """
     return self.pwd_ctx.hash(raw_password)
Exemplo n.º 14
0
 def wrapper(*args, **kwargs):
     PraetorianError.require_condition(
         not current_guard().roles_disabled,
         "This feature is not available because roles are disabled",
     )
     role_set = set([str(n) for n in required_rolenames])
     _verify_and_add_jwt()
     try:
         MissingRoleError.require_condition(
             current_rolenames().issuperset(role_set),
             "This endpoint requires all the following roles: "
             "{}".format([", ".join(role_set)]),
         )
         return method(*args, **kwargs)
     finally:
         remove_jwt_data_from_app_context()
Exemplo n.º 15
0
 def validate_reset_token(self, token):
     """
     Validates a password reset request based on the reset token
     that is supplied. Verifies that the token is a reset token
     and that the user can be properly retrieved
     """
     data = self.extract_jwt_token(token, access_type=AccessType.reset)
     user_id = data.get('id')
     PraetorianError.require_condition(
         user_id is not None,
         "Could not fetch an id from the reset token",
     )
     user = self.user_class.identify(user_id)
     PraetorianError.require_condition(
         user is not None,
         "Could not identify the user from the reset token",
     )
     return user
Exemplo n.º 16
0
def current_user():
    """
    This method returns a user instance for jwt token data attached to the
    current flask app's context
    """
    jwt_data = get_jwt_data_from_app_context()
    user_id = jwt_data.get('id')
    PraetorianError.require_condition(
        user_id is not None,
        "Could not fetch an id for the current user",
    )
    guard = current_guard()
    user = guard.user_class.identify(user_id)
    PraetorianError.require_condition(
        user is not None,
        "Could not identify the current user from the current id",
    )
    return user
Exemplo n.º 17
0
 def get_user_from_registration_token(self, token):
     """
     Gets a user based on the registration token that is supplied. Verifies
     that the token is a regisration token and that the user can be properly
     retrieved
     """
     data = self.extract_jwt_token(token, access_type=AccessType.register)
     user_id = data.get("id")
     PraetorianError.require_condition(
         user_id is not None,
         "Could not fetch an id from the registration token",
     )
     user = self.user_class.identify(user_id)
     PraetorianError.require_condition(
         user is not None,
         "Could not identify the user from the registration token",
     )
     return user
Exemplo n.º 18
0
 def authenticate(self, username, password):
     """
     Verifies that a password matches the stored password for that username.
     If verification passes, the matching user instance is returned
     """
     PraetorianError.require_condition(
         self.user_class is not None,
         "Praetorian must be initialized before this method is available",
     )
     user = self.user_class.lookup(username)
     MissingUserError.require_condition(
         user is not None,
         'Could not find the requested user',
     )
     AuthenticationError.require_condition(
         self._verify_password(password, user.password),
         'The password is incorrect',
     )
     return user
Exemplo n.º 19
0
    def refresh_jwt_token(self, token):
        """
        Creates a new token for a user if and only if the old token's access
        permission is expired but its refresh permission is not yet expired.
        The new token's refresh expiration moment is the same as the old
        token's, but the new token's access expiration is refreshed
        """
        moment = datetime.datetime.utcnow()
        # Note: we disable exp verification because we do custom checks here
        data = jwt.decode(
            token,
            self.encode_key,
            algorithms=self.allowed_algorithms,
            options={'verify_exp': False},
        )
        self.validate_jwt_data(data, access_type=AccessType.refresh)

        user = self.user_class.identify(data['id'])
        PraetorianError.require_condition(
            user is not None,
            'Could not find an active user for the token',
        )

        payload_parts = dict(
            iat=moment,
            exp=moment + self.access_lifespan,
            rf_exp=data['rf_exp'],
            jti=data['jti'],
            id=data['id'],
            rls=','.join(user.rolenames),
        )
        return jwt.encode(
            payload_parts,
            self.encode_key,
            self.encode_algorithm,
        ).decode('utf-8')
Exemplo n.º 20
0
    def _validate_token_class(self, token_store_class):
        """
        Validates the supplied user_class to make sure that it has the
        class methods and attributes necessary to function correctly.
        After validating class methods, will attempt to instantiate a dummy
        instance of the user class to test for the requisite attributes

        Requirements:

        - ``lookup`` method. Accepts a string parameter, returns instance
        - ``identify`` method. Accepts an identity parameter, returns instance
        - ``identity`` attribute. Provides unique id for the instance
        - ``rolenames`` attribute. Provides list of roles attached to instance
        - ``password`` attribute. Provides hashed password for instance  # TODO remove password
        """
        PraetorianError.require_condition(
            getattr(token_store_class, 'lookup', None) is not None,
            textwrap.dedent("""
                The user_class must have a lookup class method:
                user_class.lookup(<str>) -> <user instance>
            """),
        )
        PraetorianError.require_condition(
            getattr(token_store_class, 'identify', None) is not None,
            textwrap.dedent("""
                The user_class must have an identify class method:
                user_class.identify(<identity>) -> <user instance>
            """),
        )

        dummy_token_store = None
        try:
            dummy_token_store = token_store_class()
        except Exception:
            flask.current_app.logger.debug(
                "Skipping instance validation because "
                "token cannot be instantiated without arguments")
        if dummy_token_store:
            PraetorianError.require_condition(
                hasattr(dummy_token_store, "identity"),
                textwrap.dedent("""
                    Instances of token_class must have an identity attribute:
                    token_instance.identity -> <unique id for instance>
                """),
            )
            PraetorianError.require_condition(
                self.roles_disabled or hasattr(dummy_token_store, "rolenames"),
                textwrap.dedent("""
                    Instances of token_class must have a rolenames attribute:
                    token_instance.rolenames -> [<role1>, <role2>, ...]
                """),
            )

            # PraetorianError.require_condition(
            #     hasattr(dummy_token, "password"),
            #     textwrap.dedent("""
            #         Instances of user_class must have a password attribute:
            #         user_instance.rolenames -> <hashed password>
            #     """),
            # )

        return dummy_token_store
Exemplo n.º 21
0
    def init_app(
        self,
        app=None,
        user_class=None,
        token_store_class=None,
        is_blacklisted=None,
        encode_jwt_token_hook=None,
        refresh_jwt_token_hook=None,
    ):
        """
        Initializes the Praetorian extension

        :param: app:                    The flask app to bind this
                                        extension to
        :param: user_class:             The class used to interact with
                                        user data
        :param: token_store_class:      The class used to interact with
                                        token store data
        :param: is_blacklisted:         A method that may optionally be
                                        used to check the token against
                                        a blacklist when access or refresh
                                        is requested should take the jti
                                        for the token to check as a single
                                        argument. Returns True if the jti is
                                        blacklisted, False otherwise. By
                                        default, always returns False.
        :param encode_jwt_token_hook:   A method that may optionally be
                                        called right before an encoded jwt
                                        is generated. Should take
                                        payload_parts which contains the
                                        ingredients for the jwt.
        :param refresh_jwt_token_hook:  A method that may optionally be called
                                        right before an encoded jwt is
                                        refreshed. Should take payload_parts
                                        which contains the ingredients for
                                        the jwt.
        """
        PraetorianError.require_condition(
            app.config.get('SECRET_KEY') is not None,
            "There must be a SECRET_KEY app config setting set",
        )

        self.roles_disabled = app.config.get(
            'PRAETORIAN_ROLES_DISABLED',
            DEFAULT_ROLES_DISABLED,
        )

        self.hash_autoupdate = app.config.get(
            'PRAETORIAN_HASH_AUTOUPDATE',
            DEFAULT_HASH_AUTOUPDATE,
        )

        self.hash_autotest = app.config.get(
            'PRAETORIAN_HASH_AUTOTEST',
            DEFAULT_HASH_AUTOTEST,
        )

        self.pwd_ctx = CryptContext(
            schemes=app.config.get(
                'PRAETORIAN_HASH_ALLOWED_SCHEMES',
                DEFAULT_HASH_ALLOWED_SCHEMES,
            ),
            default=app.config.get(
                'PRAETORIAN_HASH_SCHEME',
                DEFAULT_HASH_SCHEME,
            ),
            deprecated=app.config.get(
                'PRAETORIAN_HASH_DEPRECATED_SCHEMES',
                DEFAULT_HASH_DEPRECATED_SCHEMES,
            ),
        )

        valid_schemes = self.pwd_ctx.schemes()
        PraetorianError.require_condition(
            self.hash_scheme in valid_schemes or self.hash_scheme is None,
            "If {} is set, it must be one of the following schemes: {}".format(
                'PRAETORIAN_HASH_SCHEME',
                valid_schemes,
            ),
        )

        self.user_class = self._validate_user_class(user_class)
        #self.token_class = self._validate_token_class(token_class)
        self.token_store_class = token_store_class
        self.is_blacklisted = is_blacklisted or (lambda t: False)
        self.encode_jwt_token_hook = encode_jwt_token_hook
        self.refresh_jwt_token_hook = refresh_jwt_token_hook

        self.encode_key = app.config['SECRET_KEY']
        self.allowed_algorithms = app.config.get(
            'JWT_ALLOWED_ALGORITHMS',
            DEFAULT_JWT_ALLOWED_ALGORITHMS,
        )
        self.encode_algorithm = app.config.get(
            'JWT_ALGORITHM',
            DEFAULT_JWT_ALGORITHM,
        )
        self.access_lifespan = app.config.get(
            'JWT_ACCESS_LIFESPAN',
            DEFAULT_JWT_ACCESS_LIFESPAN,
        )
        self.refresh_lifespan = app.config.get(
            'JWT_REFRESH_LIFESPAN',
            DEFAULT_JWT_REFRESH_LIFESPAN,
        )
        self.reset_lifespan = app.config.get(
            'JWT_RESET_LIFESPAN',
            DEFAULT_JWT_RESET_LIFESPAN,
        )
        self.jwt_places = app.config.get(
            'JWT_PLACES',
            DEFAULT_JWT_PLACES,
        )
        self.cookie_name = app.config.get(
            'JWT_COOKIE_NAME',
            DEFAULT_JWT_COOKIE_NAME,
        )
        self.header_name = app.config.get(
            'JWT_HEADER_NAME',
            DEFAULT_JWT_HEADER_NAME,
        )
        self.header_type = app.config.get(
            'JWT_HEADER_TYPE',
            DEFAULT_JWT_HEADER_TYPE,
        )
        self.user_class_validation_method = app.config.get(
            'USER_CLASS_VALIDATION_METHOD',
            DEFAULT_USER_CLASS_VALIDATION_METHOD,
        )

        self.confirmation_template = app.config.get(
            'PRAETORIAN_CONFIRMATION_TEMPLATE',
            DEFAULT_CONFIRMATION_TEMPLATE,
        )
        self.confirmation_uri = app.config.get('PRAETORIAN_CONFIRMATION_URI', )
        self.confirmation_sender = app.config.get(
            'PRAETORIAN_CONFIRMATION_SENDER', )
        self.confirmation_subject = app.config.get(
            'PRAETORIAN_CONFIRMATION_SUBJECT',
            DEFAULT_CONFIRMATION_SUBJECT,
        )

        self.reset_template = app.config.get(
            'PRAETORIAN_RESET_TEMPLATE',
            DEFAULT_RESET_TEMPLATE,
        )
        self.reset_uri = app.config.get('PRAETORIAN_RESET_URI', )
        self.reset_sender = app.config.get('PRAETORIAN_RESET_SENDER', )
        self.reset_subject = app.config.get(
            'PRAETORIAN_RESET_SUBJECT',
            DEFAULT_RESET_SUBJECT,
        )

        if isinstance(self.access_lifespan, dict):
            self.access_lifespan = pendulum.duration(**self.access_lifespan)
        elif isinstance(self.access_lifespan, str):
            self.access_lifespan = duration_from_string(self.access_lifespan)
        ConfigurationError.require_condition(
            isinstance(self.access_lifespan, datetime.timedelta),
            "access lifespan was not configured",
        )

        if isinstance(self.refresh_lifespan, dict):
            self.refresh_lifespan = pendulum.duration(**self.refresh_lifespan)
        if isinstance(self.refresh_lifespan, str):
            self.refresh_lifespan = duration_from_string(self.refresh_lifespan)
        ConfigurationError.require_condition(
            isinstance(self.refresh_lifespan, datetime.timedelta),
            "refresh lifespan was not configured",
        )

        if not app.config.get('DISABLE_PRAETORIAN_ERROR_HANDLER'):
            app.register_error_handler(
                PraetorianError,
                PraetorianError.build_error_handler(),
            )

        self.is_testing = app.config.get('TESTING', False)

        if not hasattr(app, 'extensions'):
            app.extensions = {}
        app.extensions['praetorian'] = self

        return app
Exemplo n.º 22
0
    def init_app(self, app, user_class, is_blacklisted=None):
        """
        Initializes the Praetorian extension

        :param: app:            The flask app to bind this extension to
        :param: user_class:     The class used to interact with user data
        :param: is_blacklisted: A method that may optionally be used to
                                check the token against a blacklist when
                                access or refresh is requested
                                Should take the jti for the token to check
                                as a single argument. Returns True if
                                the jti is blacklisted, False otherwise.
                                By default, always returns False.
        """
        PraetorianError.require_condition(
            app.config.get('SECRET_KEY') is not None,
            "There must be a SECRET_KEY app config setting set",
        )

        possible_schemes = [
            'argon2',
            'bcrypt',
            'pbkdf2_sha512',
        ]
        self.pwd_ctx = CryptContext(
            default='pbkdf2_sha512',
            schemes=possible_schemes + ['plaintext'],
            deprecated=[],
        )

        self.hash_scheme = app.config.get('PRAETORIAN_HASH_SCHEME')
        valid_schemes = self.pwd_ctx.schemes()
        PraetorianError.require_condition(
            self.hash_scheme in valid_schemes or self.hash_scheme is None,
            "If {} is set, it must be one of the following schemes: {}",
            'PRAETORIAN_HASH_SCHEME',
            valid_schemes,
        )

        self.user_class = self._validate_user_class(user_class)
        self.is_blacklisted = is_blacklisted or (lambda t: False)

        self.encode_key = app.config['SECRET_KEY']
        self.allowed_algorithms = app.config.get(
            'JWT_ALLOWED_ALGORITHMS',
            DEFAULT_JWT_ALLOWED_ALGORITHMS,
        )
        self.encode_algorithm = app.config.get(
            'JWT_ALGORITHM',
            DEFAULT_JWT_ALGORITHM,
        )
        self.access_lifespan = pendulum.Duration(**app.config.get(
            'JWT_ACCESS_LIFESPAN',
            DEFAULT_JWT_ACCESS_LIFESPAN,
        ))
        self.refresh_lifespan = pendulum.Duration(**app.config.get(
            'JWT_REFRESH_LIFESPAN',
            DEFAULT_JWT_REFRESH_LIFESPAN,
        ))
        self.header_name = app.config.get(
            'JWT_HEADER_NAME',
            DEFAULT_JWT_HEADER_NAME,
        )
        self.header_type = app.config.get(
            'JWT_HEADER_TYPE',
            DEFAULT_JWT_HEADER_TYPE,
        )
        self.user_class_validation_method = app.config.get(
            'USER_CLASS_VALIDATION_METHOD',
            DEFAULT_USER_CLASS_VALIDATION_METHOD,
        )

        if not app.config.get('DISABLE_PRAETORIAN_ERROR_HANDLER'):
            app.register_error_handler(
                PraetorianError,
                PraetorianError.build_error_handler(),
            )

        self.is_testing = app.config.get('TESTING', False)

        if not hasattr(app, 'extensions'):
            app.extensions = {}
        app.extensions['praetorian'] = self
Exemplo n.º 23
0
    def send_token_email(
        self,
        email,
        user=None,
        template=None,
        action_sender=None,
        action_uri=None,
        subject=None,
        override_access_lifespan=None,
        custom_token=None,
    ):
        """
        Sends an email to a user, containing a time expiring
            token usable for several actions.  This requires
            your application is initialized with a `mail` extension,
            which supports Flask-Mail's `Message()` object and
            a `send()` method.

        Returns a dict containing the information sent, along with the
            `result` from mail send.
        :param: email:                    The email address to use
                                          (username, id, email, etc)
        :param: user:                     The user object to tie claim to
                                          (username, id, email, etc)
        :param: template:                 HTML Template for confirmation email.
                                          If not provided, a stock entry is
                                          used
        :param: action_sender:            The sender that should be attached
                                          to the confirmation email.
        :param: action_uri:               The uri that should be visited to
                                          complete the token action.
        :param: subject:                  The email subject.
        :param: override_access_lifespan: Overrides the JWT_ACCESS_LIFESPAN
                                          to set an access lifespan for the
                                          registration token.
        :param: custom_token:             The token to be carried as the
                                          email's payload
        """
        notification = {
            "result": None,
            "message": None,
            "user": str(user),
            "email": email,
            "token": custom_token,
            "subject": subject,
            "confirmation_uri": action_uri,  # backwards compatibility
            "action_uri": action_uri,
        }

        PraetorianError.require_condition(
            "mail" in flask.current_app.extensions,
            "Your app must have a mail extension enabled to register by email",
        )

        PraetorianError.require_condition(
            action_sender,
            "A sender is required to send confirmation email",
        )

        PraetorianError.require_condition(
            custom_token,
            "A custom_token is required to send notification email",
        )

        if template is None:
            with open(self.confirmation_template) as fh:
                template = fh.read()

        with PraetorianError.handle_errors("fail sending email"):
            jinja_tmpl = jinja2.Template(template)
            notification["message"] = jinja_tmpl.render(notification).strip()

            msg = Message(
                html=notification["message"],
                sender=action_sender,
                subject=notification["subject"],
                recipients=[notification["email"]],
            )

            flask.current_app.logger.debug("Sending email to {}".format(email))
            notification["result"] = flask.current_app.extensions["mail"].send(
                msg
            )

        return notification
Exemplo n.º 24
0
 def __init__(self, *args):
     PraetorianError.require_condition(len(args) > 0, "BOOM")
Exemplo n.º 25
0
    def send_token_email(self,
                         email,
                         user=None,
                         template=None,
                         action_sender=None,
                         action_uri=None,
                         subject=None,
                         override_access_lifespan=None,
                         custom_token=None,
                         sender='no-reply@praetorian'):
        """
        Sends an email to a user, containing a time expiring
            token usable for several actions.  This requires
            your application is initialized with a `mail` extension,
            which supports Flask-Mail's `Message()` object and
            a `send()` method.

        Returns a dict containing the information sent, along with the
            `result` from mail send.
        :param: email:                    The email address to use
                                          (username, id, email, etc)
        :param: user:                     The user object to tie claim to
                                          (username, id, email, etc)
        :param: template:                 HTML Template for confirmation email.
                                          If not provided, a stock entry is
                                          used
        :param: action_sender:            The sender that should be attached
                                          to the confirmation email.
        :param: action_uri:               The uri that should be visited to
                                          complete the token action.
        :param: subject:                  The email subject.
        :param: override_access_lifespan: Overrides the JWT_ACCESS_LIFESPAN
                                          to set an access lifespan for the
                                          registration token.
        """
        notification = {
            'result': None,
            'message': None,
            'user': str(user),
            'email': email,
            'token': custom_token,
            'subject': subject,
            'confirmation_uri': action_uri,  # backwards compatibility
            'action_uri': action_uri,
        }

        PraetorianError.require_condition(
            action_sender,
            "A sender is required to send confirmation email",
        )

        PraetorianError.require_condition(
            custom_token,
            "A custom_token is required to send notification email",
        )

        if template is None:
            with open(self.confirmation_template) as fh:
                template = fh.read()

        with PraetorianError.handle_errors('fail sending email'):
            flask.current_app.logger.debug(
                "NOTIFICATION: {}".format(notification))

            jinja_tmpl = jinja2.Template(template)
            notification['message'] = jinja_tmpl.render(notification).strip()

            msg = Message(html=notification['message'],
                          sender=action_sender,
                          subject=notification['subject'],
                          recipients=[notification['email']])

            flask.current_app.logger.debug("Sending email to {}".format(email))
            notification['result'] = flask.current_app.extensions['mail'].send(
                msg)

        return notification
Exemplo n.º 26
0
    def send_registration_email(self,
                                email,
                                user=None,
                                template=None,
                                confirmation_sender=None,
                                confirmation_uri=None,
                                subject=None,
                                override_access_lifespan=None):
        """
        Sends a registration email to a new user, containing a time expiring
            token usable for validation.  This requires your application
            is initiliazed with a `mail` extension, which supports
            Flask-Mail's `Message()` object and a `send()` method.

        Returns a dict containing the information sent, along with the
            `result` from mail send.
        :param: user:                     The user object to tie claim to
                                          (username, id, email, etc)
        :param: template:                 HTML Template for confirmation email.
                                          If not provided, a stock entry is
                                          used
        :param: confirmation_sender:      The sender that shoudl be attached
                                          to the confirmation email. Overrides
                                          the PRAETORIAN_CONFIRMRATION_SENDER
                                          config setting
        :param: confirmation_uri:         The uri that should be visited to
                                          complete email registration. Should
                                          usually be a uri to a frontend or
                                          external service that calls a
                                          'finalize' method in the api to
                                          complete registration. Will override
                                          the PRAETORIAN_CONFIRMATION_URI
                                          config setting
        :param: subject:                  The registration email subject.
                                          Will override the
                                          PRAETORIAN_CONFIRMATION_SUBJECT
                                          config setting.
        :param: override_access_lifespan: Overrides the JWT_ACCESS_LIFESPAN
                                          to set an access lifespan for the
                                          registration token.
        """
        if subject is None:
            subject = self.confirmation_subject

        if confirmation_uri is None:
            confirmation_uri = self.confirmation_uri

        notification = {
            'result': None,
            'message': None,
            'user': str(user),
            'email': email,
            'token': None,
            'subject': subject,
            'confirmation_uri': confirmation_uri,
        }

        sender = confirmation_sender or self.confirmation_sender
        PraetorianError.require_condition(
            sender,
            "A confirmation sender is required to send confirmation email",
        )

        if template is None:
            with open(self.confirmation_template) as fh:
                template = fh.read()

        with PraetorianError.handle_errors('fail sending confirmation email'):
            app = flask.current_app
            app.logger.debug("NOTIFICATION: {}".format(notification))
            app.logger.debug(
                "Generating registration token with lifespan: {}".format(
                    override_access_lifespan))
            notification['token'] = self.encode_jwt_token(
                user,
                override_access_lifespan=override_access_lifespan,
                bypass_user_check=True,
                is_registration_token=True,
            )
            jinja_tmpl = jinja2.Template(template)
            notification['message'] = jinja_tmpl.render(notification).strip()

            msg = Message(html=notification['message'],
                          sender=sender,
                          subject=notification['subject'],
                          recipients=[notification['email']])

            app.logger.debug("Sending verification email to {}".format(email))
            notification['result'] = app.extensions['mail'].send(msg)

        return notification