openlinktoken 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openlinktoken/__init__.py +15 -0
- openlinktoken/attributes/__init__.py +17 -0
- openlinktoken/attributes/attribute.py +26 -0
- openlinktoken/attributes/attribute_expression.py +232 -0
- openlinktoken/attributes/attribute_loader.py +48 -0
- openlinktoken/attributes/base_attribute.py +31 -0
- openlinktoken/attributes/combined_attribute.py +69 -0
- openlinktoken/attributes/general/__init__.py +17 -0
- openlinktoken/attributes/general/date_attribute.py +117 -0
- openlinktoken/attributes/general/decimal_attribute.py +86 -0
- openlinktoken/attributes/general/integer_attribute.py +78 -0
- openlinktoken/attributes/general/record_id_attribute.py +41 -0
- openlinktoken/attributes/general/string_attribute.py +55 -0
- openlinktoken/attributes/general/year_attribute.py +78 -0
- openlinktoken/attributes/person/__init__.py +25 -0
- openlinktoken/attributes/person/age_attribute.py +41 -0
- openlinktoken/attributes/person/birth_date_attribute.py +40 -0
- openlinktoken/attributes/person/birth_year_attribute.py +39 -0
- openlinktoken/attributes/person/canadian_postal_code_attribute.py +165 -0
- openlinktoken/attributes/person/first_name_attribute.py +112 -0
- openlinktoken/attributes/person/last_name_attribute.py +146 -0
- openlinktoken/attributes/person/postal_code_attribute.py +42 -0
- openlinktoken/attributes/person/sex_attribute.py +48 -0
- openlinktoken/attributes/person/social_security_number_attribute.py +119 -0
- openlinktoken/attributes/person/us_postal_code_attribute.py +104 -0
- openlinktoken/attributes/serializable_attribute.py +7 -0
- openlinktoken/attributes/utilities/__init__.py +7 -0
- openlinktoken/attributes/utilities/attribute_utilities.py +224 -0
- openlinktoken/attributes/validation/__init__.py +23 -0
- openlinktoken/attributes/validation/age_range_validator.py +41 -0
- openlinktoken/attributes/validation/attribute_validator.py +22 -0
- openlinktoken/attributes/validation/date_range_validator.py +111 -0
- openlinktoken/attributes/validation/not_in_validator.py +48 -0
- openlinktoken/attributes/validation/not_null_or_empty_validator.py +21 -0
- openlinktoken/attributes/validation/not_starts_with_validator.py +48 -0
- openlinktoken/attributes/validation/regex_validator.py +44 -0
- openlinktoken/attributes/validation/serializable_attribute_validator.py +26 -0
- openlinktoken/attributes/validation/year_range_validator.py +48 -0
- openlinktoken/ec_key_utils.py +207 -0
- openlinktoken/exchange_config.py +305 -0
- openlinktoken/exchange_jwe.py +107 -0
- openlinktoken/metadata.py +104 -0
- openlinktoken/tokens/__init__.py +19 -0
- openlinktoken/tokens/base_token_definition.py +48 -0
- openlinktoken/tokens/definitions/t1_token.py +39 -0
- openlinktoken/tokens/definitions/t2_token.py +39 -0
- openlinktoken/tokens/definitions/t3_token.py +39 -0
- openlinktoken/tokens/definitions/t4_token.py +37 -0
- openlinktoken/tokens/definitions/t5_token.py +37 -0
- openlinktoken/tokens/token.py +38 -0
- openlinktoken/tokens/token_definition.py +46 -0
- openlinktoken/tokens/token_generation_exception.py +16 -0
- openlinktoken/tokens/token_generator.py +201 -0
- openlinktoken/tokens/token_generator_result.py +33 -0
- openlinktoken/tokens/token_registry.py +46 -0
- openlinktoken/tokens/tokenizer/__init__.py +11 -0
- openlinktoken/tokens/tokenizer/passthrough_tokenizer.py +66 -0
- openlinktoken/tokens/tokenizer/sha256_tokenizer.py +72 -0
- openlinktoken/tokens/tokenizer/tokenizer.py +31 -0
- openlinktoken/tokentransformer/__init__.py +43 -0
- openlinktoken/tokentransformer/decrypt_token_transformer.py +83 -0
- openlinktoken/tokentransformer/encrypt_token_transformer.py +85 -0
- openlinktoken/tokentransformer/encryption_constants.py +23 -0
- openlinktoken/tokentransformer/hash_token_transformer.py +88 -0
- openlinktoken/tokentransformer/jwe_match_token_formatter.py +128 -0
- openlinktoken/tokentransformer/match_token_constants.py +57 -0
- openlinktoken/tokentransformer/no_operation_token_transformer.py +24 -0
- openlinktoken/tokentransformer/token_transformer.py +22 -0
- openlinktoken-2.0.0.dist-info/METADATA +21 -0
- openlinktoken-2.0.0.dist-info/RECORD +72 -0
- openlinktoken-2.0.0.dist-info/WHEEL +5 -0
- openlinktoken-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Metadata:
|
|
9
|
+
"""Handles metadata generation and management for Open Link Token."""
|
|
10
|
+
|
|
11
|
+
# Metadata keys
|
|
12
|
+
PLATFORM = "Platform"
|
|
13
|
+
PYTHON_VERSION = "PythonVersion"
|
|
14
|
+
VERSION = "Version"
|
|
15
|
+
OUTPUT_FORMAT = "OutputFormat"
|
|
16
|
+
ENCRYPTION_SECRET_HASH = "EncryptionSecretHash"
|
|
17
|
+
HASHING_SECRET_HASH = "HashingSecretHash"
|
|
18
|
+
BLANK_TOKENS_BY_RULE = "BlankTokensByRule"
|
|
19
|
+
|
|
20
|
+
# Metadata values
|
|
21
|
+
PLATFORM_PYTHON = "Python"
|
|
22
|
+
METADATA_FILE_EXTENSION = ".metadata.json"
|
|
23
|
+
SYSTEM_PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
24
|
+
|
|
25
|
+
DEFAULT_VERSION = "2.0.0"
|
|
26
|
+
|
|
27
|
+
# Output format values
|
|
28
|
+
OUTPUT_FORMAT_JSON = "JSON"
|
|
29
|
+
OUTPUT_FORMAT_CSV = "CSV"
|
|
30
|
+
OUTPUT_FORMAT_PARQUET = "Parquet"
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
"""Initialize the metadata object."""
|
|
34
|
+
self.metadata_map: Dict[str, Any] = {}
|
|
35
|
+
|
|
36
|
+
def initialize(self) -> Dict[str, Any]:
|
|
37
|
+
"""
|
|
38
|
+
Initialize metadata with system information only.
|
|
39
|
+
Secret hashes must be set separately using add_hashed_secret().
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
The initialized metadata map
|
|
43
|
+
"""
|
|
44
|
+
self.metadata_map.clear()
|
|
45
|
+
self.metadata_map[self.PYTHON_VERSION] = self.SYSTEM_PYTHON_VERSION
|
|
46
|
+
self.metadata_map[self.PLATFORM] = self.PLATFORM_PYTHON
|
|
47
|
+
self.metadata_map[self.VERSION] = self.DEFAULT_VERSION
|
|
48
|
+
|
|
49
|
+
return self.metadata_map
|
|
50
|
+
|
|
51
|
+
def add_hashed_secret(self, secret_key: str, secret_to_hash: str | bytes | None) -> Dict[str, Any]:
|
|
52
|
+
"""
|
|
53
|
+
Set the secret and add its hash to the metadata.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
secret_key: The key to use for the hashed secret in metadata
|
|
57
|
+
secret_to_hash: The secret to hash
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The metadata map for method chaining
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(secret_to_hash, bytes):
|
|
63
|
+
if secret_to_hash:
|
|
64
|
+
self.metadata_map[secret_key] = self.calculate_secure_hash(secret_to_hash)
|
|
65
|
+
elif secret_to_hash and secret_to_hash.strip():
|
|
66
|
+
self.metadata_map[secret_key] = self.calculate_secure_hash(secret_to_hash)
|
|
67
|
+
return self.metadata_map
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def calculate_secure_hash(input_str: str | bytes | None) -> str | None:
|
|
71
|
+
"""
|
|
72
|
+
Calculate a secure SHA-256 hash of the given input.
|
|
73
|
+
The hash is returned as a hexadecimal string, or ``None`` for empty input.
|
|
74
|
+
|
|
75
|
+
Accepts both ``str`` (encoded as UTF-8) and raw ``bytes``.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
input_str: The input string or raw bytes to hash.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The SHA-256 hash as a hexadecimal string, or ``None`` when the input is empty.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
HashCalculationException: If there's an error calculating the hash.
|
|
85
|
+
"""
|
|
86
|
+
if not input_str:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
hash_input = input_str if isinstance(input_str, bytes) else input_str.encode("utf-8")
|
|
91
|
+
hash_object = hashlib.sha256(hash_input)
|
|
92
|
+
hex_string = hash_object.hexdigest()
|
|
93
|
+
return hex_string
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
raise HashCalculationException("Error calculating SHA-256 hash", e)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class HashCalculationException(Exception):
|
|
100
|
+
"""Custom exception for hash calculation errors."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, message: str, cause: Exception = None):
|
|
103
|
+
super().__init__(message)
|
|
104
|
+
self.cause = cause
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Token definitions and generation utilities for Open Link Token."""
|
|
2
|
+
|
|
3
|
+
from .base_token_definition import BaseTokenDefinition
|
|
4
|
+
from .token import Token
|
|
5
|
+
from .token_definition import TokenDefinition
|
|
6
|
+
from .token_generation_exception import TokenGenerationException
|
|
7
|
+
from .token_generator import TokenGenerator
|
|
8
|
+
from .token_generator_result import TokenGeneratorResult
|
|
9
|
+
from .token_registry import TokenRegistry
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Token",
|
|
13
|
+
"TokenDefinition",
|
|
14
|
+
"BaseTokenDefinition",
|
|
15
|
+
"TokenGenerator",
|
|
16
|
+
"TokenRegistry",
|
|
17
|
+
"TokenGeneratorResult",
|
|
18
|
+
"TokenGenerationException",
|
|
19
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import List, Set
|
|
5
|
+
|
|
6
|
+
from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseTokenDefinition(ABC):
|
|
10
|
+
"""
|
|
11
|
+
A generic interface for the token definition.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def get_version(self) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Get the version of the token definition.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
The token definition version.
|
|
21
|
+
"""
|
|
22
|
+
... # pragma: no cover
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_token_identifiers(self) -> Set[str]:
|
|
26
|
+
"""
|
|
27
|
+
Get all token identifiers. For example, a set of { T1, T2, T3, T4, T5 }.
|
|
28
|
+
|
|
29
|
+
The token identifiers are also called rule identifiers because every token is
|
|
30
|
+
generated from rule definition.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
A set of token identifiers.
|
|
34
|
+
"""
|
|
35
|
+
... # pragma: no cover
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def get_token_definition(self, token_id: str) -> List[AttributeExpression]:
|
|
39
|
+
"""
|
|
40
|
+
Get the token definition for a given token identifier.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
token_id: The token/rule identifier.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
A list of token/rule definition.
|
|
47
|
+
"""
|
|
48
|
+
... # pragma: no cover
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
6
|
+
from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
|
|
7
|
+
from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
|
|
8
|
+
from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
|
|
9
|
+
from openlinktoken.attributes.person.sex_attribute import SexAttribute
|
|
10
|
+
from openlinktoken.tokens.token import Token
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class T1Token(Token):
|
|
14
|
+
"""
|
|
15
|
+
Represents the token definition for token T1.
|
|
16
|
+
|
|
17
|
+
It is a collection of attribute expressions that are concatenated together
|
|
18
|
+
to get the token signature. The token signature is as follows:
|
|
19
|
+
U(last-name)|U(first-name-1)|U(gender)|birth-date
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
ID = "T1"
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""Initialize the T1 token definition."""
|
|
26
|
+
self._definition = [
|
|
27
|
+
AttributeExpression(LastNameAttribute, "T|U"),
|
|
28
|
+
AttributeExpression(FirstNameAttribute, "T|S(0,1)|U"),
|
|
29
|
+
AttributeExpression(SexAttribute, "T|U"),
|
|
30
|
+
AttributeExpression(BirthDateAttribute, "T|D"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
def get_identifier(self) -> str:
|
|
34
|
+
"""Get the unique identifier for the token."""
|
|
35
|
+
return self.ID
|
|
36
|
+
|
|
37
|
+
def get_definition(self) -> List[AttributeExpression]:
|
|
38
|
+
"""Get the list of attribute expressions that define the token."""
|
|
39
|
+
return self._definition
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
6
|
+
from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
|
|
7
|
+
from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
|
|
8
|
+
from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
|
|
9
|
+
from openlinktoken.attributes.person.postal_code_attribute import PostalCodeAttribute
|
|
10
|
+
from openlinktoken.tokens.token import Token
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class T2Token(Token):
|
|
14
|
+
"""
|
|
15
|
+
Represents the token definition for token T2.
|
|
16
|
+
|
|
17
|
+
It is a collection of attribute expressions that are concatenated together
|
|
18
|
+
to get the token signature. The token signature is as follows:
|
|
19
|
+
U(last-name)|U(first-name)|birth-date|postal-code-3
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
ID = "T2"
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""Initialize the T2 token definition."""
|
|
26
|
+
self._definition = [
|
|
27
|
+
AttributeExpression(LastNameAttribute, "T|U"),
|
|
28
|
+
AttributeExpression(FirstNameAttribute, "T|U"),
|
|
29
|
+
AttributeExpression(BirthDateAttribute, "T|D"),
|
|
30
|
+
AttributeExpression(PostalCodeAttribute, "T|S(0,3)|U"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
def get_identifier(self) -> str:
|
|
34
|
+
"""Get the unique identifier for the token."""
|
|
35
|
+
return self.ID
|
|
36
|
+
|
|
37
|
+
def get_definition(self) -> List[AttributeExpression]:
|
|
38
|
+
"""Get the list of attribute expressions that define the token."""
|
|
39
|
+
return self._definition
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
6
|
+
from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
|
|
7
|
+
from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
|
|
8
|
+
from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
|
|
9
|
+
from openlinktoken.attributes.person.sex_attribute import SexAttribute
|
|
10
|
+
from openlinktoken.tokens.token import Token
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class T3Token(Token):
|
|
14
|
+
"""
|
|
15
|
+
Represents the token definition for token T3.
|
|
16
|
+
|
|
17
|
+
It is a collection of attribute expressions that are concatenated together
|
|
18
|
+
to get the token signature. The token signature is as follows:
|
|
19
|
+
U(last-name)|U(first-name)|U(gender)|birth-date
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
ID = "T3"
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""Initialize the T3 token definition."""
|
|
26
|
+
self._definition = [
|
|
27
|
+
AttributeExpression(LastNameAttribute, "T|U"),
|
|
28
|
+
AttributeExpression(FirstNameAttribute, "T|U"),
|
|
29
|
+
AttributeExpression(SexAttribute, "T|U"),
|
|
30
|
+
AttributeExpression(BirthDateAttribute, "T|D"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
def get_identifier(self) -> str:
|
|
34
|
+
"""Get the unique identifier for the token."""
|
|
35
|
+
return self.ID
|
|
36
|
+
|
|
37
|
+
def get_definition(self) -> List[AttributeExpression]:
|
|
38
|
+
"""Get the list of attribute expressions that define the token."""
|
|
39
|
+
return self._definition
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
6
|
+
from openlinktoken.attributes.person.birth_date_attribute import BirthDateAttribute
|
|
7
|
+
from openlinktoken.attributes.person.sex_attribute import SexAttribute
|
|
8
|
+
from openlinktoken.attributes.person.social_security_number_attribute import SocialSecurityNumberAttribute
|
|
9
|
+
from openlinktoken.tokens.token import Token
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class T4Token(Token):
|
|
13
|
+
"""
|
|
14
|
+
Represents the token definition for token T4.
|
|
15
|
+
|
|
16
|
+
It is a collection of attribute expressions that are concatenated together
|
|
17
|
+
to get the token signature. The token signature is as follows:
|
|
18
|
+
social-security-number|U(gender)|birth-date
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
ID = "T4"
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
"""Initialize the T4 token definition."""
|
|
25
|
+
self._definition = [
|
|
26
|
+
AttributeExpression(SocialSecurityNumberAttribute, "T|M(\\d+)"),
|
|
27
|
+
AttributeExpression(SexAttribute, "T|U"),
|
|
28
|
+
AttributeExpression(BirthDateAttribute, "T|D"),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
def get_identifier(self) -> str:
|
|
32
|
+
"""Get the unique identifier for the token."""
|
|
33
|
+
return self.ID
|
|
34
|
+
|
|
35
|
+
def get_definition(self) -> List[AttributeExpression]:
|
|
36
|
+
"""Get the list of attribute expressions that define the token."""
|
|
37
|
+
return self._definition
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
6
|
+
from openlinktoken.attributes.person.first_name_attribute import FirstNameAttribute
|
|
7
|
+
from openlinktoken.attributes.person.last_name_attribute import LastNameAttribute
|
|
8
|
+
from openlinktoken.attributes.person.sex_attribute import SexAttribute
|
|
9
|
+
from openlinktoken.tokens.token import Token
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class T5Token(Token):
|
|
13
|
+
"""
|
|
14
|
+
Represents the token definition for token T5.
|
|
15
|
+
|
|
16
|
+
It is a collection of attribute expressions that are concatenated together
|
|
17
|
+
to get the token signature. The token signature is as follows:
|
|
18
|
+
U(last-name)|U(first-name-3)|U(gender)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
ID = "T5"
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
"""Initialize the T5 token definition."""
|
|
25
|
+
self._definition = [
|
|
26
|
+
AttributeExpression(LastNameAttribute, "T|U"),
|
|
27
|
+
AttributeExpression(FirstNameAttribute, "T|S(0,3)|U"),
|
|
28
|
+
AttributeExpression(SexAttribute, "T|U"),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
def get_identifier(self) -> str:
|
|
32
|
+
"""Get the unique identifier for the token."""
|
|
33
|
+
return self.ID
|
|
34
|
+
|
|
35
|
+
def get_definition(self) -> List[AttributeExpression]:
|
|
36
|
+
"""Get the list of attribute expressions that define the token."""
|
|
37
|
+
return self._definition
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Token(ABC):
|
|
10
|
+
"""
|
|
11
|
+
A token is a collection of attribute expressions that are concatenated
|
|
12
|
+
together to get the token signature and a unique identifier.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Represents the value for tokens with invalid or missing data.
|
|
16
|
+
# This constant is a placeholder for cases where a token is either
|
|
17
|
+
# not provided or contains invalid information.
|
|
18
|
+
BLANK = "0000000000000000000000000000000000000000000000000000000000000000"
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def get_identifier(self) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Get the unique identifier for the token.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The unique identifier for the token.
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def get_definition(self) -> List[AttributeExpression]:
|
|
32
|
+
"""
|
|
33
|
+
Get the list of attribute expressions that define the token.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The list of attribute expressions that define the token.
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Set
|
|
4
|
+
|
|
5
|
+
from openlinktoken.attributes.attribute_expression import AttributeExpression
|
|
6
|
+
from openlinktoken.tokens.base_token_definition import BaseTokenDefinition
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TokenDefinition(BaseTokenDefinition):
|
|
10
|
+
"""
|
|
11
|
+
Encapsulates the token definitions.
|
|
12
|
+
|
|
13
|
+
The tokens are generated using some token generation rules. This class
|
|
14
|
+
encapsulates the definition of those rules. Together, they are commonly
|
|
15
|
+
referred to as token definitions or rule definitions.
|
|
16
|
+
|
|
17
|
+
Each token/rule definition is a collection of AttributeExpression that are
|
|
18
|
+
concatenated together to get the token signature.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""
|
|
23
|
+
Initializes the token definitions.
|
|
24
|
+
Loads all token definitions using TokenRegistry.
|
|
25
|
+
"""
|
|
26
|
+
from openlinktoken.tokens.token_registry import TokenRegistry
|
|
27
|
+
|
|
28
|
+
self.definitions: Dict[str, List[AttributeExpression]] = TokenRegistry.load_all_tokens()
|
|
29
|
+
|
|
30
|
+
def get_version(self) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Get the version of the token definition.
|
|
33
|
+
"""
|
|
34
|
+
return "2.0"
|
|
35
|
+
|
|
36
|
+
def get_token_identifiers(self) -> Set[str]:
|
|
37
|
+
"""
|
|
38
|
+
Get all token identifiers.
|
|
39
|
+
"""
|
|
40
|
+
return set(self.definitions.keys())
|
|
41
|
+
|
|
42
|
+
def get_token_definition(self, token_id: str) -> List[AttributeExpression]:
|
|
43
|
+
"""
|
|
44
|
+
Get the token definition for a given token identifier.
|
|
45
|
+
"""
|
|
46
|
+
return self.definitions.get(token_id)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TokenGenerationException(Exception):
|
|
5
|
+
"""Exception raised when token generation fails."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, message: str, cause: Exception = None):
|
|
8
|
+
"""
|
|
9
|
+
Initialize the exception.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
message: The error message.
|
|
13
|
+
cause: The underlying cause of the exception.
|
|
14
|
+
"""
|
|
15
|
+
super().__init__(message)
|
|
16
|
+
self.cause = cause
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Dict, List, Optional, Set, Type
|
|
5
|
+
|
|
6
|
+
from openlinktoken.attributes.attribute import Attribute
|
|
7
|
+
from openlinktoken.attributes.attribute_loader import AttributeLoader
|
|
8
|
+
from openlinktoken.tokens.base_token_definition import BaseTokenDefinition
|
|
9
|
+
from openlinktoken.tokens.token import Token
|
|
10
|
+
from openlinktoken.tokens.token_generation_exception import TokenGenerationException
|
|
11
|
+
from openlinktoken.tokens.token_generator_result import TokenGeneratorResult
|
|
12
|
+
from openlinktoken.tokens.tokenizer.sha256_tokenizer import SHA256Tokenizer
|
|
13
|
+
from openlinktoken.tokens.tokenizer.tokenizer import Tokenizer
|
|
14
|
+
from openlinktoken.tokentransformer.token_transformer import TokenTransformer
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TokenGenerator:
|
|
20
|
+
"""Generates both the token signature and the token itself."""
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_transformers(
|
|
24
|
+
cls, token_definition: BaseTokenDefinition, token_transformer_list: List[TokenTransformer]
|
|
25
|
+
) -> "TokenGenerator":
|
|
26
|
+
"""
|
|
27
|
+
Convenience constructor that creates a TokenGenerator with SHA256Tokenizer.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
token_definition: The token definition.
|
|
31
|
+
token_transformer_list: A list of token transformers.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
A TokenGenerator instance with SHA256Tokenizer.
|
|
35
|
+
"""
|
|
36
|
+
return cls(token_definition, SHA256Tokenizer(token_transformer_list))
|
|
37
|
+
|
|
38
|
+
def __init__(self, token_definition: BaseTokenDefinition, tokenizer: Tokenizer):
|
|
39
|
+
"""
|
|
40
|
+
Initialize the token generator with an explicit tokenizer.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
token_definition: The token definition.
|
|
44
|
+
tokenizer: Tokenizer implementation. Use PassthroughTokenizer for plain mode.
|
|
45
|
+
"""
|
|
46
|
+
self.token_definition = token_definition
|
|
47
|
+
self.attribute_instance_map: Dict[Type[Attribute], Attribute] = {}
|
|
48
|
+
|
|
49
|
+
# Load attributes
|
|
50
|
+
for attribute in AttributeLoader.load():
|
|
51
|
+
self.attribute_instance_map[type(attribute)] = attribute
|
|
52
|
+
|
|
53
|
+
self.tokenizer = tokenizer
|
|
54
|
+
|
|
55
|
+
def _get_token_signature(
|
|
56
|
+
self, token_id: str, person_attributes: Dict[Type[Attribute], str], result: TokenGeneratorResult
|
|
57
|
+
) -> Optional[str]:
|
|
58
|
+
"""
|
|
59
|
+
Get the token signature for a given token identifier.
|
|
60
|
+
|
|
61
|
+
Populates the invalid_attributes list in the result object with the attributes
|
|
62
|
+
that are invalid.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
token_id: The token identifier.
|
|
66
|
+
person_attributes: The person attributes map.
|
|
67
|
+
result: The token generator result.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The token signature using the token definition for the given token identifier.
|
|
71
|
+
"""
|
|
72
|
+
definition = self.token_definition.get_token_definition(token_id)
|
|
73
|
+
|
|
74
|
+
if person_attributes is None:
|
|
75
|
+
raise ValueError("Person attributes cannot be null.")
|
|
76
|
+
|
|
77
|
+
values = []
|
|
78
|
+
|
|
79
|
+
for attribute_expression in definition:
|
|
80
|
+
attribute_class = attribute_expression.attribute_class
|
|
81
|
+
|
|
82
|
+
if attribute_class not in person_attributes:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
attribute = self.attribute_instance_map.get(attribute_class)
|
|
86
|
+
if attribute is None:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
attribute_value = person_attributes[attribute_class]
|
|
90
|
+
|
|
91
|
+
if not attribute.validate(attribute_value):
|
|
92
|
+
result.invalid_attributes.add(attribute.get_name())
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
attribute_value = attribute.normalize(attribute_value)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
attribute_value = attribute_expression.get_effective_value(attribute_value)
|
|
99
|
+
values.append(attribute_value)
|
|
100
|
+
except ValueError as e:
|
|
101
|
+
logger.error(str(e))
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
# Filter out None and blank values, then join with '|'
|
|
105
|
+
filtered_values = [v for v in values if v is not None and v.strip() != ""]
|
|
106
|
+
return "|".join(filtered_values)
|
|
107
|
+
|
|
108
|
+
def get_all_token_signatures(self, person_attributes: Dict[Type[Attribute], str]) -> Dict[str, str]:
|
|
109
|
+
"""
|
|
110
|
+
Get the token signatures for all token/rule identifiers.
|
|
111
|
+
|
|
112
|
+
This is mostly a debug/logging/test method.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
person_attributes: The person attributes map.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
A map of token/rule identifier to the token signature.
|
|
119
|
+
"""
|
|
120
|
+
signatures = {}
|
|
121
|
+
|
|
122
|
+
for token_id in self.token_definition.get_token_identifiers():
|
|
123
|
+
try:
|
|
124
|
+
signature = self._get_token_signature(token_id, person_attributes, TokenGeneratorResult())
|
|
125
|
+
if signature is not None:
|
|
126
|
+
signatures[token_id] = signature
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.error(f"Error generating token signature for token id: {token_id}", exc_info=e)
|
|
129
|
+
|
|
130
|
+
return signatures
|
|
131
|
+
|
|
132
|
+
def _get_token(
|
|
133
|
+
self, token_id: str, person_attributes: Dict[Type[Attribute], str], result: TokenGeneratorResult
|
|
134
|
+
) -> Optional[str]:
|
|
135
|
+
"""
|
|
136
|
+
Get token for a given token identifier.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
token_id: The token identifier.
|
|
140
|
+
person_attributes: The person attributes map.
|
|
141
|
+
result: The token generator result.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
The token using the token definition for the given token identifier.
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
TokenGenerationException: In case of failure to generate the token.
|
|
148
|
+
"""
|
|
149
|
+
signature = self._get_token_signature(token_id, person_attributes, result)
|
|
150
|
+
logger.debug(f"Token signature for token id {token_id}: {signature}")
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
token = self.tokenizer.tokenize(signature)
|
|
154
|
+
# Track blank tokens by rule
|
|
155
|
+
if Token.BLANK == token:
|
|
156
|
+
result.blank_tokens_by_rule.add(token_id)
|
|
157
|
+
return token
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"Error generating token for token id: {token_id}", exc_info=e)
|
|
160
|
+
raise TokenGenerationException("Error generating token", e)
|
|
161
|
+
|
|
162
|
+
def get_all_tokens(self, person_attributes: Dict[Type[Attribute], str]) -> TokenGeneratorResult:
|
|
163
|
+
"""
|
|
164
|
+
Get the tokens for all token/rule identifiers.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
person_attributes: The person attributes map.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
A TokenGeneratorResult object containing the tokens and invalid attributes.
|
|
171
|
+
"""
|
|
172
|
+
result = TokenGeneratorResult()
|
|
173
|
+
|
|
174
|
+
for token_id in self.token_definition.get_token_identifiers():
|
|
175
|
+
try:
|
|
176
|
+
token = self._get_token(token_id, person_attributes, result)
|
|
177
|
+
if token is not None:
|
|
178
|
+
result.tokens[token_id] = token
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(f"Error generating token for token id: {token_id}", exc_info=e)
|
|
181
|
+
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
def get_invalid_person_attributes(self, person_attributes: Dict[Type[Attribute], str]) -> Set[str]:
|
|
185
|
+
"""
|
|
186
|
+
Get invalid person attribute names.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
person_attributes: The person attributes map.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
A set of invalid person attribute names.
|
|
193
|
+
"""
|
|
194
|
+
response = set()
|
|
195
|
+
|
|
196
|
+
for attribute_class, value in person_attributes.items():
|
|
197
|
+
attribute = self.attribute_instance_map.get(attribute_class)
|
|
198
|
+
if attribute and not attribute.validate(value):
|
|
199
|
+
response.add(attribute.get_name())
|
|
200
|
+
|
|
201
|
+
return response
|