<commit_before>"""
The module contains internal data representation in SATOSA and general converteras that can be used
for converting from SAML/OAuth/OpenID connect to the internal representation.
"""
import datetime
import hashlib
from enum import Enum


class UserIdHashType(Enum):
    """
    All different user id hash types
    """
    transient = 1
    persistent = 2
    pairwise = 3
    public = 4
    emailaddress = 5
    unspecified = 6

    @classmethod
    def from_string(cls, str):
        try:
            return getattr(cls, str)
        except AttributeError:
            raise ValueError("Unknown hash type '{}'".format(str))


class UserIdHasher(object):
    """
    Class for creating different user id types
    """
    STATE_KEY = "IDHASHER"

    @staticmethod
    def save_state(internal_request, state):
        """
        Saves all necessary information needed by the UserIdHasher

        :type internal_request: satosa.internal_data.InternalRequest

        :param internal_request: The request
        :param state: The current state
        """
        state_data = {
            "hash_type": internal_request.user_id_hash_type.name
        }
        state[UserIdHasher.STATE_KEY] = state_data

    @staticmethod
    def hash_data(salt, value):
        """
        Hashes a value together with a salt.
        :type salt: str
        :type value: str
        :param salt: hash salt
        :param value: value to hash together with the salt
        :return: hash value (SHA512)
        """
        data = '{value}{salt}'.format(value=value, salt=salt).encode()
        hash = hashlib.sha512(data).hexdigest()
        return hash

    @staticmethod
    def hash_type(state):
        state_data = state[UserIdHasher.STATE_KEY]
        hash_type = UserIdHashType.from_string(state_data["hash_type"])
        return hash_type

    @staticmethod
    def hash_id(salt, user_id, requester, state):
        """
        Sets a user id to the internal_response,
        in the format specified by the internal response

        :type salt: str
        :type user_id: str
        :type requester: str
        :type state: satosa.state.State
        :rtype: str

        :param salt: A salt string for the ID hashing
        :param user_id: the user id
        :param user_id_hash_type: Hashing type
        :param state: The current state
        :return: the internal_response containing the hashed user ID
        """
        hash_type_to_format = {
            UserIdHashType.transient:    '{id}{req}{time}',
            UserIdHashType.persistent:   '{id}{req}',
            UserIdHashType.pairwise:     '{id}{req}',
            UserIdHashType.public:       '{id}',
            UserIdHashType.emailaddress: '{id}',
            UserIdHashType.unspecified:  '{id}',
        }

        format_args = {
            'id': user_id,
            'req': requester,
            'time': datetime.datetime.utcnow().timestamp(),
        }

        hash_type = UserIdHasher.hash_type(state)
        try:
            fmt = hash_type_to_format[hash_type]
        except KeyError as e:
            raise ValueError('Unknown hash type: {}'.format(hash_type)) from e
        else:
            user_id = fmt.format(**format_args)

        hasher = (
            (lambda salt, value: value)
            if hash_type in [
                UserIdHashType.emailaddress,
            ]
            else UserIdHasher.hash_data)
        return hasher(salt, user_id)<commit_msg>Do not hash user_id if hash_type is unspecified<commit_after>"""
The module contains internal data representation in SATOSA and general converteras that can be used
for converting from SAML/OAuth/OpenID connect to the internal representation.
"""
import datetime
import hashlib
from enum import Enum


class UserIdHashType(Enum):
    """
    All different user id hash types
    """
    transient = 1
    persistent = 2
    pairwise = 3
    public = 4
    emailaddress = 5
    unspecified = 6

    @classmethod
    def from_string(cls, str):
        try:
            return getattr(cls, str)
        except AttributeError:
            raise ValueError("Unknown hash type '{}'".format(str))


class UserIdHasher(object):
    """
    Class for creating different user id types
    """
    STATE_KEY = "IDHASHER"

    @staticmethod
    def save_state(internal_request, state):
        """
        Saves all necessary information needed by the UserIdHasher

        :type internal_request: satosa.internal_data.InternalRequest

        :param internal_request: The request
        :param state: The current state
        """
        state_data = {
            "hash_type": internal_request.user_id_hash_type.name
        }
        state[UserIdHasher.STATE_KEY] = state_data

    @staticmethod
    def hash_data(salt, value):
        """
        Hashes a value together with a salt.
        :type salt: str
        :type value: str
        :param salt: hash salt
        :param value: value to hash together with the salt
        :return: hash value (SHA512)
        """
        data = '{value}{salt}'.format(value=value, salt=salt).encode()
        hash = hashlib.sha512(data).hexdigest()
        return hash

    @staticmethod
    def hash_type(state):
        state_data = state[UserIdHasher.STATE_KEY]
        hash_type = UserIdHashType.from_string(state_data["hash_type"])
        return hash_type

    @staticmethod
    def hash_id(salt, user_id, requester, state):
        """
        Sets a user id to the internal_response,
        in the format specified by the internal response

        :type salt: str
        :type user_id: str
        :type requester: str
        :type state: satosa.state.State
        :rtype: str

        :param salt: A salt string for the ID hashing
        :param user_id: the user id
        :param user_id_hash_type: Hashing type
        :param state: The current state
        :return: the internal_response containing the hashed user ID
        """
        hash_type_to_format = {
            UserIdHashType.transient:    '{id}{req}{time}',
            UserIdHashType.persistent:   '{id}{req}',
            UserIdHashType.pairwise:     '{id}{req}',
            UserIdHashType.public:       '{id}',
            UserIdHashType.emailaddress: '{id}',
            UserIdHashType.unspecified:  '{id}',
        }

        format_args = {
            'id': user_id,
            'req': requester,
            'time': datetime.datetime.utcnow().timestamp(),
        }

        hash_type = UserIdHasher.hash_type(state)
        try:
            fmt = hash_type_to_format[hash_type]
        except KeyError as e:
            raise ValueError('Unknown hash type: {}'.format(hash_type)) from e
        else:
            user_id = fmt.format(**format_args)

        hasher = (
            (lambda salt, value: value)
            if hash_type in [
                UserIdHashType.emailaddress,
                UserIdHashType.unspecified,
            ]
            else UserIdHasher.hash_data)
        return hasher(salt, user_id)
{'fraction_non_alphanumeric': 0.08748931319464234, 'fraction_numerical': 0.003419777714448561, 'mean_word_length': 2.133482142857143, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2460229', 'n_tokens_mistral': 2008, 'n_tokens_neox': 1958, 'n_words': 561}