Exemplo n.º 1
0
 def __init__(self):
     """
     :type realms: tuple
     """
     self._realms = None
     self._event_bus = None
     self.serialization_manager = SerializationManager(format='json')
Exemplo n.º 2
0
    def create_manager(self, yosai, settings, session_attributes):
        """
        Order of execution matters.  The sac must be set before the cache_handler is
        instantiated so that the cache_handler's serialization manager instance
        registers the sac.
        """
        mgr_settings = SecurityManagerSettings(settings)
        attributes = mgr_settings.attributes

        realms = self._init_realms(settings, attributes['realms'])

        session_attributes = self._init_session_attributes(
            session_attributes, attributes)

        serialization_manager =\
            SerializationManager(session_attributes,
                                 serializer_scheme=attributes['serializer'])

        # the cache_handler doesn't initialize a cache_realm until it gets
        # a serialization manager, which is assigned within the SecurityManager
        cache_handler = self._init_cache_handler(settings,
                                                 attributes['cache_handler'],
                                                 serialization_manager)

        manager = mgr_settings.security_manager(
            yosai,
            settings,
            realms=realms,
            cache_handler=cache_handler,
            serialization_manager=serialization_manager)

        return manager
Exemplo n.º 3
0
def test_sm_init_unrecognized_format():
    """
    unit tested:  __init__

    test case:
    an unrecognized serialization format raises an exception
    """
    with pytest.raises(InvalidSerializationFormatException):
        SerializationManager(format='protobufferoni')
Exemplo n.º 4
0
Arquivo: mgt.py Projeto: hoatle/yosai
    def __init__(self):

        # new to yosai.core.
        self.serialization_manager = SerializationManager()

        self.encryption_cipher_key = None
        self.decryption_cipher_key = None

        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!i!!!!!!!!
        # !!!
        # !!!                    HEY  YOU!
        # !!! Generate your own cipher key using the instructions above and
        # !!! update your yosai.core.settings file to include it.  The code below
        # !!! references this key.  Yosai does not include a default key.
        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        # as default, the encryption key == decryption key:
        default_key = mgt_settings.default_cipher_key

        self.set_cipher_key(encrypt_key=default_key, decrypt_key=default_key)
Exemplo n.º 5
0
    def __init__(self):

        # new to yosai.core.
        self.serialization_manager = SerializationManager()

        self.encryption_cipher_key = None
        self.decryption_cipher_key = None

        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!i!!!!!!!!
        # !!!
        # !!!                    HEY  YOU!
        # !!! Generate your own cipher key using the instructions above and
        # !!! update your yosai.core.settings file to include it.  The code below
        # !!! references this key.  Yosai does not include a default key.
        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        # as default, the encryption key == decryption key:
        default_key = mgt_settings.default_cipher_key

        self.set_cipher_key(encrypt_key=default_key, decrypt_key=default_key)
Exemplo n.º 6
0
class AbstractRememberMeManager(mgt_abcs.RememberMeManager):
    """
    Abstract implementation of the ``RememberMeManager`` interface that handles
    serialization and encryption of the remembered user identity.

    The remembered identity storage location and details are left to
    subclasses.

    Default encryption key
    -----------------------
    This implementation uses the Fernet API from PyCA's cryptography for
    symmetric encryption. As per the documentation, Fernet uses AES in CBC mode
    with a 128-bit key for encryption and uses PKCS7 padding:
        https://cryptography.io/en/stable/fernet/

    It also uses a default, generated symmetric key to both encrypt and decrypt
    data.  As AES is a symmetric cipher, the same key is used to both encrypt
    and decrypt data, BUT NOTE:

    Because Yosai is an open-source project, if anyone knew that you were
    using Yosai's default key, they could download/view the source, and with
    enough effort, reconstruct the key and decode encrypted data at will.

    Of course, this key is only really used to encrypt the remembered
    ``IdentifierCollection``, which is typically a user id or username.  So if you
    do not consider that sensitive information, and you think the default key
    still makes things 'sufficiently difficult', then you can ignore this
    issue.

    However, if you do feel this constitutes sensitive information, it is
    recommended that you provide your own key and set it via the cipher_key
    property attribute to a key known only to your application,
    guaranteeing that no third party can decrypt your data.

    You can generate your own key by importing fernet and calling its
    generate_key method:
       >>> from cryptography.fernet import Fernet
       >>> key = Fernet.generate_key()

    your key will be a byte string that looks like this:
        b'cghiiLzTI6CUFCO5Hhh-5RVKzHTQFZM2QSZxxgaC6Wo='

        copy and paste YOUR newly generated byte string, excluding the
        bytestring notation, into its respective place in /conf/yosai.core.settings.json
        following this format:
            DEFAULT_CIPHER_KEY = "cghiiLzTI6CUFCO5Hhh-5RVKzHTQFZM2QSZxxgaC6Wo="
    """

    def __init__(self):

        # new to yosai.core.
        self.serialization_manager = SerializationManager()

        self.encryption_cipher_key = None
        self.decryption_cipher_key = None

        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!i!!!!!!!!
        # !!!
        # !!!                    HEY  YOU!
        # !!! Generate your own cipher key using the instructions above and
        # !!! update your yosai.core.settings file to include it.  The code below
        # !!! references this key.  Yosai does not include a default key.
        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        # as default, the encryption key == decryption key:
        default_key = mgt_settings.default_cipher_key

        self.set_cipher_key(encrypt_key=default_key, decrypt_key=default_key)

    def set_cipher_key(self, encrypt_key, decrypt_key):
        """
        :param cipher_key: the private key used to encrypt and decrypt
        :type cipher_key: a string
        """
        self.encryption_cipher_key = bytes(encrypt_key, 'utf-8')
        self.decryption_cipher_key = bytes(decrypt_key, 'utf-8')

    @abstractmethod
    def forget_identity(self, subject):
        """
        Forgets (removes) any remembered identity data for the specified
        Subject instance.

        :param subject: the subject instance for which identity data should be
                        forgotten from the underlying persistence mechanism
        """
        pass

    def is_remember_me(self, authc_token):
        """
        Determines whether remember me services should be performed for the
        specified token.

        :param authc_token: the authentication token submitted during the
                            successful authentication attempt
        :returns: True if remember me services should be performed as a
                  result of the successful authentication attempt
        """
        # Yosai uses a more implicit check:
        try:
            return authc_token.is_remember_me
        except AttributeError:
            return False

    def on_successful_login(self, subject, authc_token, account):
        """
        Reacts to the successful login attempt by first always
        forgetting any previously stored identity.  Then if the authc_token
        is a ``RememberMe`` type of token, the associated identity
        will be remembered for later retrieval during a new user session.

        :param subject: the subject whose identifying attributes are being
                        remembered
        :param authc_token:  the token that resulted in a successful
                             authentication attempt
        :param account: account that contains the authentication info resulting
                        from the successful authentication attempt
        """
        # always clear any previous identity:
        self.forget_identity(subject)

        # now save the new identity:
        if (self.is_remember_me(authc_token)):
            self.remember_identity(subject, authc_token, account)
        else:
            if logger.getEffectiveLevel() <= logging.DEBUG:
                msg = ("AuthenticationToken did not indicate that RememberMe is "
                       "requested.  RememberMe functionality will not be executed "
                       "for corresponding account.")
                logger.debug(msg)

    # yosai.core.omits authc_token argument as its for an edge case
    def remember_identity(self, subject, identifiers=None, account=None):
        """
        Yosai consolidates rememberIdentity, an overloaded method in java,
        to a method that will use an identifier-else-account logic.

        Remembers a subject-unique identity for retrieval later.  This
        implementation first resolves the exact identifying attributes to
        remember.  It then remembers these identifying attributes by calling
            remember_identity(Subject, IdentifierCollection)

        :param subject:  the subject for which the identifying attributes are
                         being remembered
        :param identifiers: the identifying attributes to remember for retrieval
                            later on
        :param account: the account containing authentication info resulting
                         from the successful authentication attempt
        """
        if not identifiers:  # then account must not be None
            try:
                identifiers = self.get_identity_to_remember(subject, account)
            except AttributeError:
                msg = "Neither account nor identifier arguments passed"
                raise InvalidArgumentException(msg)

        serialized = self.convert_identifiers_to_bytes(identifiers)
        self.remember_serialized_identity(subject, serialized)

    def get_identity_to_remember(self, subject, account):
        """
        Returns the account's identifier and ignores the subject argument

        :param subject: the subject whose identifiers are remembered
        :param account: the account resulting from the successful authentication attempt
        :returns: the IdentifierCollection to remember
        """
        return account.account_id

    def convert_identifiers_to_bytes(self, identifiers):
        """
        Encryption requires a binary type as input, so this method converts
        the identifier collection object to one.

        :type identifiers: a serializable IdentifierCollection object
        :returns: a bytestring
        """

        # serializes to bytes by default:
        return self.serialization_manager.serialize(identifiers)

    @abstractmethod
    def remember_serialized_identity(subject, serialized):
        """
        Persists the identity bytes to a persistent store for retrieval
        later via the get_remembered_serialized_identity(SubjectContext)
        method.

        :param subject: the Subject for whom the identity is being serialized
        :param serialized: the serialized bytes to be persisted.
        """
        pass

    def get_remembered_identifiers(self, subject_context):
        identifiers = None
        try:
            serialized = self.get_remembered_serialized_identity(subject_context)
            if serialized:
                identifiers = self.convert_bytes_to_identifier(identifiers,
                                                               subject_context)
        except Exception as ex:
            identifiers = \
                self.on_remembered_identifiers_failure(ex, subject_context)

        return identifiers

    @abstractmethod
    def get_remembered_serialized_identity(subject_context):
        """
        Based on the given subject context data, retrieves the previously
        persisted serialized identity, or None if there is no available data.
        The context map is usually populated by a ``SubjectBuilder``
        implementation.  See the ``SubjectFactory`` class constants for Yosai's
        known map keys.

        :param subject_context: the contextual data, usually provided by a
                                SubjectBuilder implementation, that
                                is being used to construct a Subject instance.

        :returns: the previously persisted serialized identity, or None if
                  no such data can be acquired for the Subject
        """
        pass

    def convert_bytes_to_identifiers(self, serialized, subject_context):
        """
        If a cipher_service is available, it will be used to first decrypt the
        serialized message.  Then, the bytes are deserialized and returned.

        :param serialized:      the bytes to decrypt and then deserialize
        :param subject_context: the contextual data, usually provided by a
                                SubjectBuilder implementation, that is being
                                used to construct a Subject instance
        :returns: the de-serialized identifier
        """

        # unlike Shiro, Yosai assumes that the message is encrypted:
        decrypted = self.decrypt(serialized)

        return self.serialization_manager.deserialize(decrypted)

    def on_remembered_identifiers_failure(self, exc, subject_context):
        """
        Called when an exception is thrown while trying to retrieve identifier.
        The default implementation logs a debug message and forgets ('unremembers')
        the problem identity by calling forget_identity(subject_context) and
        then immediately re-raises the exception to allow the calling
        component to react accordingly.

        This method implementation never returns an object - it always rethrows,
        but can be overridden by subclasses for custom handling behavior.

        This most commonly would be called when an encryption key is updated
        and old identifier are retrieved that have been encrypted with the
        previous key.

        :param exc: the exception that was thrown
        :param subject_context: the contextual data, usually provided by a
                                SubjectBuilder implementation, that is being
                                used to construct a Subject instance
        :raises:  the original Exception passed is propagated in all cases
        """
        if logger.getEffectiveLevel() <= logging.DEBUG:
            msg = ("There was a failure while trying to retrieve remembered "
                   "identifier.  This could be due to a configuration problem or "
                   "corrupted identifier.  This could also be due to a recently "
                   "changed encryption key.  The remembered identity will be "
                   "forgotten and not used for this request. ", exc)
            logger.debug(msg)

        self.forget_identity(subject_context)

        # propagate - security manager implementation will handle and warn
        # appropriately:
        raise exc

    def encrypt(self, serialized):
        """
        Encrypts the serialized message using Fernet

        :param serialized: the serialized object to encrypt
        :type serialized: bytes
        :returns: an encrypted bytes returned by Fernet
        """

        fernet = Fernet(self.encryption_cipher_key)
        return fernet.encrypt(serialized)

    def decrypt(self, encrypted):
        """
        decrypts the encrypted message using Fernet

        :param encrypted: the encrypted message
        :returns: the decrypted, serialized identifier collection
        """
        fernet = Fernet(self.decryption_cipher_key)
        return fernet.decrypt(encrypted)

    def on_failed_login(self, subject, authc_token, ae):
        """
        Reacts to a failed login by immediately forgetting any previously
        remembered identity.  This is an additional security feature to prevent
        any remenant identity data from being retained in case the
        authentication attempt is not being executed by the expected user.

        :param subject: the subject which executed the failed login attempt
        :param authc_token:   the authentication token resulting in a failed
                              login attempt - ignored by this implementation
        :param ae:  the exception thrown as a result of the failed login
                    attempt - ignored by this implementation
        """
        self.forget_identity(subject)

    def on_logout(self, subject):
        """
        Reacts to a subject logging out of the application and immediately
        forgets any previously stored identity and returns.

        :param subject: the subject logging out
        """
        self.forget_identity(subject)
Exemplo n.º 7
0
def serialization_manager(session_attributes):
    return SerializationManager(session_attributes)
Exemplo n.º 8
0
 def __init__(self, settings, session_attributes):
     super().__init__(settings)
     self.serialization_manager = SerializationManager(session_attributes)
Exemplo n.º 9
0
def serialization_manager():
    return SerializationManager()  # defaults to msgpack
Exemplo n.º 10
0
 def __init__(self):
     super().__init__()
     self.serialization_manager = SerializationManager()
Exemplo n.º 11
0
Arquivo: mgt.py Projeto: hoatle/yosai
class AbstractRememberMeManager(mgt_abcs.RememberMeManager):
    """
    Abstract implementation of the ``RememberMeManager`` interface that handles
    serialization and encryption of the remembered user identity.

    The remembered identity storage location and details are left to
    subclasses.

    Default encryption key
    -----------------------
    This implementation uses the Fernet API from PyCA's cryptography for
    symmetric encryption. As per the documentation, Fernet uses AES in CBC mode
    with a 128-bit key for encryption and uses PKCS7 padding:
        https://cryptography.io/en/stable/fernet/

    It also uses a default, generated symmetric key to both encrypt and decrypt
    data.  As AES is a symmetric cipher, the same key is used to both encrypt
    and decrypt data, BUT NOTE:

    Because Yosai is an open-source project, if anyone knew that you were
    using Yosai's default key, they could download/view the source, and with
    enough effort, reconstruct the key and decode encrypted data at will.

    Of course, this key is only really used to encrypt the remembered
    ``IdentifierCollection``, which is typically a user id or username.  So if you
    do not consider that sensitive information, and you think the default key
    still makes things 'sufficiently difficult', then you can ignore this
    issue.

    However, if you do feel this constitutes sensitive information, it is
    recommended that you provide your own key and set it via the cipher_key
    property attribute to a key known only to your application,
    guaranteeing that no third party can decrypt your data.

    You can generate your own key by importing fernet and calling its
    generate_key method:
       >>> from cryptography.fernet import Fernet
       >>> key = Fernet.generate_key()

    your key will be a byte string that looks like this:
        b'cghiiLzTI6CUFCO5Hhh-5RVKzHTQFZM2QSZxxgaC6Wo='

        copy and paste YOUR newly generated byte string, excluding the
        bytestring notation, into its respective place in /conf/yosai.core.settings.json
        following this format:
            DEFAULT_CIPHER_KEY = "cghiiLzTI6CUFCO5Hhh-5RVKzHTQFZM2QSZxxgaC6Wo="
    """

    def __init__(self):

        # new to yosai.core.
        self.serialization_manager = SerializationManager()

        self.encryption_cipher_key = None
        self.decryption_cipher_key = None

        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!i!!!!!!!!
        # !!!
        # !!!                    HEY  YOU!
        # !!! Generate your own cipher key using the instructions above and
        # !!! update your yosai.core.settings file to include it.  The code below
        # !!! references this key.  Yosai does not include a default key.
        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        # as default, the encryption key == decryption key:
        default_key = mgt_settings.default_cipher_key

        self.set_cipher_key(encrypt_key=default_key, decrypt_key=default_key)

    def set_cipher_key(self, encrypt_key, decrypt_key):
        """
        :param cipher_key: the private key used to encrypt and decrypt
        :type cipher_key: a string
        """
        self.encryption_cipher_key = bytes(encrypt_key, 'utf-8')
        self.decryption_cipher_key = bytes(decrypt_key, 'utf-8')

    @abstractmethod
    def forget_identity(self, subject):
        """
        Forgets (removes) any remembered identity data for the specified
        Subject instance.

        :param subject: the subject instance for which identity data should be
                        forgotten from the underlying persistence mechanism
        """
        pass

    def is_remember_me(self, authc_token):
        """
        Determines whether remember me services should be performed for the
        specified token.

        :param authc_token: the authentication token submitted during the
                            successful authentication attempt
        :returns: True if remember me services should be performed as a
                  result of the successful authentication attempt
        """
        # Yosai uses a more implicit check:
        try:
            return authc_token.is_remember_me
        except AttributeError:
            return False

    def on_successful_login(self, subject, authc_token, account):
        """
        Reacts to the successful login attempt by first always
        forgetting any previously stored identity.  Then if the authc_token
        is a ``RememberMe`` type of token, the associated identity
        will be remembered for later retrieval during a new user session.

        :param subject: the subject whose identifying attributes are being
                        remembered
        :param authc_token:  the token that resulted in a successful
                             authentication attempt
        :param account: account that contains the authentication info resulting
                        from the successful authentication attempt
        """
        # always clear any previous identity:
        self.forget_identity(subject)

        # now save the new identity:
        if (self.is_remember_me(authc_token)):
            self.remember_identity(subject, authc_token, account)
        else:
            if logger.getEffectiveLevel() <= logging.DEBUG:
                msg = ("AuthenticationToken did not indicate that RememberMe is "
                       "requested.  RememberMe functionality will not be executed "
                       "for corresponding account.")
                logger.debug(msg)

    # yosai.core.omits authc_token argument as its for an edge case
    def remember_identity(self, subject, identifiers=None, account=None):
        """
        Yosai consolidates rememberIdentity, an overloaded method in java,
        to a method that will use an identifier-else-account logic.

        Remembers a subject-unique identity for retrieval later.  This
        implementation first resolves the exact identifying attributes to
        remember.  It then remembers these identifying attributes by calling
            remember_identity(Subject, IdentifierCollection)

        :param subject:  the subject for which the identifying attributes are
                         being remembered
        :param identifiers: the identifying attributes to remember for retrieval
                            later on
        :param account: the account containing authentication info resulting
                         from the successful authentication attempt
        """
        if not identifiers:  # then account must not be None
            try:
                identifiers = self.get_identity_to_remember(subject, account)
            except AttributeError:
                msg = "Neither account nor identifier arguments passed"
                raise InvalidArgumentException(msg)

        serialized = self.convert_identifiers_to_bytes(identifiers)
        self.remember_serialized_identity(subject, serialized)

    def get_identity_to_remember(self, subject, account):
        """
        Returns the account's identifier and ignores the subject argument

        :param subject: the subject whose identifiers are remembered
        :param account: the account resulting from the successful authentication attempt
        :returns: the IdentifierCollection to remember
        """
        return account.account_id

    def convert_identifiers_to_bytes(self, identifiers):
        """
        Encryption requires a binary type as input, so this method converts
        the identifier collection object to one.

        :type identifiers: a serializable IdentifierCollection object
        :returns: a bytestring
        """

        # serializes to bytes by default:
        return self.serialization_manager.serialize(identifiers)

    @abstractmethod
    def remember_serialized_identity(subject, serialized):
        """
        Persists the identity bytes to a persistent store for retrieval
        later via the get_remembered_serialized_identity(SubjectContext)
        method.

        :param subject: the Subject for whom the identity is being serialized
        :param serialized: the serialized bytes to be persisted.
        """
        pass

    def get_remembered_identifiers(self, subject_context):
        identifiers = None
        try:
            serialized = self.get_remembered_serialized_identity(subject_context)
            if serialized:
                identifiers = self.convert_bytes_to_identifier(identifiers,
                                                               subject_context)
        except Exception as ex:
            identifiers = \
                self.on_remembered_identifiers_failure(ex, subject_context)

        return identifiers

    @abstractmethod
    def get_remembered_serialized_identity(subject_context):
        """
        Based on the given subject context data, retrieves the previously
        persisted serialized identity, or None if there is no available data.
        The context map is usually populated by a ``SubjectBuilder``
        implementation.  See the ``SubjectFactory`` class constants for Yosai's
        known map keys.

        :param subject_context: the contextual data, usually provided by a
                                SubjectBuilder implementation, that
                                is being used to construct a Subject instance.

        :returns: the previously persisted serialized identity, or None if
                  no such data can be acquired for the Subject
        """
        pass

    def convert_bytes_to_identifiers(self, serialized, subject_context):
        """
        If a cipher_service is available, it will be used to first decrypt the
        serialized message.  Then, the bytes are deserialized and returned.

        :param serialized:      the bytes to decrypt and then deserialize
        :param subject_context: the contextual data, usually provided by a
                                SubjectBuilder implementation, that is being
                                used to construct a Subject instance
        :returns: the de-serialized identifier
        """

        # unlike Shiro, Yosai assumes that the message is encrypted:
        decrypted = self.decrypt(serialized)

        return self.serialization_manager.deserialize(decrypted)

    def on_remembered_identifiers_failure(self, exc, subject_context):
        """
        Called when an exception is thrown while trying to retrieve identifier.
        The default implementation logs a debug message and forgets ('unremembers')
        the problem identity by calling forget_identity(subject_context) and
        then immediately re-raises the exception to allow the calling
        component to react accordingly.

        This method implementation never returns an object - it always rethrows,
        but can be overridden by subclasses for custom handling behavior.

        This most commonly would be called when an encryption key is updated
        and old identifier are retrieved that have been encrypted with the
        previous key.

        :param exc: the exception that was thrown
        :param subject_context: the contextual data, usually provided by a
                                SubjectBuilder implementation, that is being
                                used to construct a Subject instance
        :raises:  the original Exception passed is propagated in all cases
        """
        if logger.getEffectiveLevel() <= logging.DEBUG:
            msg = ("There was a failure while trying to retrieve remembered "
                   "identifier.  This could be due to a configuration problem or "
                   "corrupted identifier.  This could also be due to a recently "
                   "changed encryption key.  The remembered identity will be "
                   "forgotten and not used for this request. ", exc)
            logger.debug(msg)

        self.forget_identity(subject_context)

        # propagate - security manager implementation will handle and warn
        # appropriately:
        raise exc

    def encrypt(self, serialized):
        """
        Encrypts the serialized message using Fernet

        :param serialized: the serialized object to encrypt
        :type serialized: bytes
        :returns: an encrypted bytes returned by Fernet
        """

        fernet = Fernet(self.encryption_cipher_key)
        return fernet.encrypt(serialized)

    def decrypt(self, encrypted):
        """
        decrypts the encrypted message using Fernet

        :param encrypted: the encrypted message
        :returns: the decrypted, serialized identifier collection
        """
        fernet = Fernet(self.decryption_cipher_key)
        return fernet.decrypt(encrypted)

    def on_failed_login(self, subject, authc_token, ae):
        """
        Reacts to a failed login by immediately forgetting any previously
        remembered identity.  This is an additional security feature to prevent
        any remenant identity data from being retained in case the
        authentication attempt is not being executed by the expected user.

        :param subject: the subject which executed the failed login attempt
        :param authc_token:   the authentication token resulting in a failed
                              login attempt - ignored by this implementation
        :param ae:  the exception thrown as a result of the failed login
                    attempt - ignored by this implementation
        """
        self.forget_identity(subject)

    def on_logout(self, subject):
        """
        Reacts to a subject logging out of the application and immediately
        forgets any previously stored identity and returns.

        :param subject: the subject logging out
        """
        self.forget_identity(subject)