rp_python_sdk 0.3.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.
- rp_python_sdk/__init__.py +0 -0
- rp_python_sdk/endpoints/get_participants.py +98 -0
- rp_python_sdk/endpoints/pushed_authorisation_request.py +204 -0
- rp_python_sdk/endpoints/retrieve_tokens.py +254 -0
- rp_python_sdk/endpoints/user_info.py +44 -0
- rp_python_sdk/endpoints/util/fapi.py +30 -0
- rp_python_sdk/filters/participant_filters.py +99 -0
- rp_python_sdk/model.py +137 -0
- rp_python_sdk/relying_party_client_sdk.py +40 -0
- rp_python_sdk/relying_party_client_sdk_exception.py +2 -0
- rp_python_sdk/sdk_config.py +72 -0
- rp_python_sdk/setup_logger.py +12 -0
- rp_python_sdk-0.3.0.dist-info/LICENSE +21 -0
- rp_python_sdk-0.3.0.dist-info/METADATA +414 -0
- rp_python_sdk-0.3.0.dist-info/RECORD +16 -0
- rp_python_sdk-0.3.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from rp_python_sdk.model import Participant
|
|
4
|
+
from rp_python_sdk.sdk_config import CertificationFilter
|
|
5
|
+
from datetime import datetime, date
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def remove_out_of_date_certifications(participants: List[Participant], reference_date: date) -> List[Participant]:
|
|
9
|
+
for participant in participants:
|
|
10
|
+
for auth_server in participant.authorisation_servers:
|
|
11
|
+
filtered = [certification for certification in auth_server.authorisation_server_certifications if
|
|
12
|
+
to_date(certification.certification_start_date) < reference_date < to_date(certification.certification_expiration_date)]
|
|
13
|
+
auth_server.authorisation_server_certifications = filtered
|
|
14
|
+
|
|
15
|
+
return participants
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def remove_unofficial_certifications(participants: List[Participant]) -> List[Participant]:
|
|
19
|
+
for participant in participants:
|
|
20
|
+
for auth_server in participant.authorisation_servers:
|
|
21
|
+
filtered = [certification for certification in auth_server.authorisation_server_certifications
|
|
22
|
+
if certification.status == "Certified"]
|
|
23
|
+
|
|
24
|
+
auth_server.authorisation_server_certifications = filtered
|
|
25
|
+
|
|
26
|
+
return participants
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def remove_inactive_auth_servers(participants: List[Participant]) -> List[Participant]:
|
|
30
|
+
for participant in participants:
|
|
31
|
+
filtered = [auth_server for auth_server in participant.authorisation_servers if any(
|
|
32
|
+
certification.profile_type == "Redirect" and certification.profile_variant == "FAPI2 Adv. OP w/Private Key, PAR"
|
|
33
|
+
for certification in auth_server.authorisation_server_certifications)]
|
|
34
|
+
|
|
35
|
+
participant.authorisation_servers = filtered
|
|
36
|
+
|
|
37
|
+
return participants
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def remove_participants_without_auth_servers(participants: List[Participant]) -> List[Participant]:
|
|
41
|
+
return [participant for participant in participants if participant.authorisation_servers]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def filter_auth_servers_for_supported_claims(participants: List[Participant], claims: List[str]) -> List[Participant]:
|
|
45
|
+
name_claims = ["name", "given_name", "middle_name", "family_name"]
|
|
46
|
+
has_name_claims = any(claim in name_claims for claim in claims)
|
|
47
|
+
|
|
48
|
+
formatted_claims = ["name"] + [claim for claim in claims if
|
|
49
|
+
claim not in name_claims] if has_name_claims else claims
|
|
50
|
+
|
|
51
|
+
for participant in participants:
|
|
52
|
+
filtered = [auth_server for auth_server in participant.authorisation_servers
|
|
53
|
+
if set([certification.profile_variant for certification in
|
|
54
|
+
auth_server.authorisation_server_certifications
|
|
55
|
+
if certification.profile_type == "ConnectID Claims"]).issuperset(formatted_claims)]
|
|
56
|
+
|
|
57
|
+
participant.authorisation_servers = filtered
|
|
58
|
+
|
|
59
|
+
return participants
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def filter_for_required_certifications(participants: List[Participant],
|
|
63
|
+
certifications: List[CertificationFilter]) -> List[Participant]:
|
|
64
|
+
for certification in certifications:
|
|
65
|
+
for participant in participants:
|
|
66
|
+
filtered = [auth_server for auth_server in participant.authorisation_servers if any(
|
|
67
|
+
server_certification.profile_type == certification.profile_type and server_certification.profile_variant == certification.profile_variant
|
|
68
|
+
for server_certification in auth_server.authorisation_server_certifications)]
|
|
69
|
+
|
|
70
|
+
participant.authorisation_servers = filtered
|
|
71
|
+
|
|
72
|
+
return participants
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def remove_fallback_identity_service_provider(participants: List[Participant]) -> List[Participant]:
|
|
76
|
+
for participant in participants:
|
|
77
|
+
filtered = [auth_server for auth_server in participant.authorisation_servers if not any(
|
|
78
|
+
certification.profile_type == "ConnectID" and certification.profile_variant == "Fallback Identity Service Provider"
|
|
79
|
+
for certification in auth_server.authorisation_server_certifications)]
|
|
80
|
+
participant.authorisation_servers = filtered
|
|
81
|
+
|
|
82
|
+
return participants
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def filter_for_fallback_identity_service_providers(participants: List[Participant]) -> List[Participant]:
|
|
86
|
+
for participant in participants:
|
|
87
|
+
filtered = [auth_server for auth_server in participant.authorisation_servers if any(
|
|
88
|
+
certification.profile_type == "ConnectID" and certification.profile_variant == "Fallback Identity Service Provider"
|
|
89
|
+
for certification in auth_server.authorisation_server_certifications)]
|
|
90
|
+
|
|
91
|
+
participant.authorisation_servers = filtered
|
|
92
|
+
|
|
93
|
+
return participants
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def to_date(date_string: str):
|
|
97
|
+
date_format = "%d/%m/%Y"
|
|
98
|
+
return datetime.strptime(date_string, date_format).date()
|
|
99
|
+
|
rp_python_sdk/model.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from dataclass_wizard import JSONWizard
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Participant(JSONWizard):
|
|
10
|
+
organisation_id: str
|
|
11
|
+
status: str
|
|
12
|
+
organisation_name: str
|
|
13
|
+
created_on: str
|
|
14
|
+
legal_entity_name: str
|
|
15
|
+
country_of_registration: str
|
|
16
|
+
company_register: str
|
|
17
|
+
registration_number: str
|
|
18
|
+
registration_id: str
|
|
19
|
+
registered_name: str
|
|
20
|
+
address_line1: str
|
|
21
|
+
address_line2: str
|
|
22
|
+
city: str
|
|
23
|
+
postcode: str
|
|
24
|
+
country: str
|
|
25
|
+
parent_organisation_reference: str
|
|
26
|
+
authorisation_servers: List['AuthorisationServer'] # Use quotes for forward references
|
|
27
|
+
org_domain_claims: List['OrgDomainClaim']
|
|
28
|
+
org_domain_role_claims: List['OrgDomainRoleClaim']
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class AuthorisationServerCertification(JSONWizard):
|
|
33
|
+
certification_start_date: str # date
|
|
34
|
+
certification_expiration_date: str # date
|
|
35
|
+
certification_id: str
|
|
36
|
+
authorisation_server_id: str
|
|
37
|
+
status: str
|
|
38
|
+
profile_variant: str
|
|
39
|
+
profile_type: str
|
|
40
|
+
profile_version: str
|
|
41
|
+
certification_uri: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class OrgDomainClaim(JSONWizard):
|
|
46
|
+
authorisation_domain_name: str
|
|
47
|
+
authority_name: str
|
|
48
|
+
registration_id: str
|
|
49
|
+
status: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class AuthorisationServer(JSONWizard):
|
|
54
|
+
authorisation_server_id: str
|
|
55
|
+
api_resources: List[str]
|
|
56
|
+
authorisation_server_certifications: List[AuthorisationServerCertification]
|
|
57
|
+
customer_friendly_description: str
|
|
58
|
+
customer_friendly_logo_uri: str
|
|
59
|
+
customer_friendly_name: str
|
|
60
|
+
developer_portal_uri: str
|
|
61
|
+
terms_of_service_uri: str
|
|
62
|
+
open_id_discovery_document: str
|
|
63
|
+
issuer: str
|
|
64
|
+
payload_signing_cert_location_uri: str
|
|
65
|
+
parent_authorisation_server_id: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class OrgDomainRoleClaim(JSONWizard):
|
|
70
|
+
status: str
|
|
71
|
+
authorisation_domain: str
|
|
72
|
+
role: str
|
|
73
|
+
registration_id: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class CallbackBody:
|
|
78
|
+
code: str
|
|
79
|
+
iss: str
|
|
80
|
+
state: str
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class PARResponse:
|
|
85
|
+
auth_url: str
|
|
86
|
+
code_verifier: str
|
|
87
|
+
state: str
|
|
88
|
+
nonce: str
|
|
89
|
+
x_fapi_interaction_id: str
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class TokenSet:
|
|
94
|
+
token_input: 'TokenInput'
|
|
95
|
+
claims: Dict[str, Any]
|
|
96
|
+
x_fapi_interaction_id: str
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class TokenInput(JSONWizard):
|
|
101
|
+
access_token: str
|
|
102
|
+
scope: str
|
|
103
|
+
id_token: str
|
|
104
|
+
token_type: str
|
|
105
|
+
expires_in: Optional[int] = None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class MtlsEndpointAliases(JSONWizard):
|
|
110
|
+
token_endpoint: Optional[str] = None
|
|
111
|
+
pushed_authorization_request_endpoint: Optional[str] = None
|
|
112
|
+
authorization_endpoint: Optional[str] = None
|
|
113
|
+
userinfo_endpoint: Optional[str] = None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class IssuerMetadata(JSONWizard):
|
|
118
|
+
issuer: str
|
|
119
|
+
jwks_uri: str
|
|
120
|
+
id_token_signing_alg_values_supported: List[str]
|
|
121
|
+
token_endpoint: str
|
|
122
|
+
pushed_authorization_request_endpoint: str
|
|
123
|
+
authorization_endpoint: str
|
|
124
|
+
userinfo_endpoint: str
|
|
125
|
+
mtls_endpoint_aliases: MtlsEndpointAliases
|
|
126
|
+
|
|
127
|
+
def get_preferred_token_endpoint(self) -> str:
|
|
128
|
+
return self.mtls_endpoint_aliases.token_endpoint if self.mtls_endpoint_aliases.token_endpoint else self.token_endpoint
|
|
129
|
+
|
|
130
|
+
def get_preferred_pushed_authorization_request_endpoint(self) -> str:
|
|
131
|
+
return self.mtls_endpoint_aliases.pushed_authorization_request_endpoint if self.mtls_endpoint_aliases.pushed_authorization_request_endpoint else self.pushed_authorization_request_endpoint
|
|
132
|
+
|
|
133
|
+
def get_preferred_authorization_endpoint(self) -> str:
|
|
134
|
+
return self.mtls_endpoint_aliases.authorization_endpoint if self.mtls_endpoint_aliases.authorization_endpoint else self.authorization_endpoint
|
|
135
|
+
|
|
136
|
+
def get_preferred_userinfo_endpoint(self) -> str:
|
|
137
|
+
return self.mtls_endpoint_aliases.userinfo_endpoint if self.mtls_endpoint_aliases.userinfo_endpoint else self.userinfo_endpoint
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from rp_python_sdk.endpoints.get_participants import get_participants, \
|
|
2
|
+
retrieve_fallback_provider_participants
|
|
3
|
+
from rp_python_sdk.endpoints.pushed_authorisation_request import send_pushed_authorisation_request
|
|
4
|
+
from rp_python_sdk.endpoints.retrieve_tokens import retrieve_tokens
|
|
5
|
+
|
|
6
|
+
from rp_python_sdk.model import Participant, PARResponse, CallbackBody, TokenSet
|
|
7
|
+
from rp_python_sdk.sdk_config import SdkConfig
|
|
8
|
+
from rp_python_sdk.setup_logger import logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RelyingPartyClientSdk:
|
|
12
|
+
|
|
13
|
+
def __init__(self, config: SdkConfig):
|
|
14
|
+
self.config = config
|
|
15
|
+
|
|
16
|
+
if config.custom_config.enable_auto_compliance_verification:
|
|
17
|
+
logger.info(f"Auto Compliance Verification mode enabled, printing config information: {self.config}")
|
|
18
|
+
|
|
19
|
+
def get_participants(self) -> list[Participant]:
|
|
20
|
+
return get_participants(self.config)
|
|
21
|
+
|
|
22
|
+
def get_fallback_provider_participants(self) -> list[Participant]:
|
|
23
|
+
return retrieve_fallback_provider_participants(self.config)
|
|
24
|
+
|
|
25
|
+
def send_pushed_authorisation_request(self, authorisation_server_id: str,
|
|
26
|
+
essential_claims: set[str] | None = None,
|
|
27
|
+
voluntary_claims: set[str] | None = None,
|
|
28
|
+
purpose: str | None = None) -> PARResponse:
|
|
29
|
+
# Converting the claims to a set just in case a non-set (e.g. list) was passed in.
|
|
30
|
+
checked_essential_claims = set(essential_claims) if essential_claims else set()
|
|
31
|
+
checked_voluntary_claims = set(voluntary_claims) if voluntary_claims else set()
|
|
32
|
+
checked_purpose = purpose if purpose else self.config.custom_config.purpose
|
|
33
|
+
|
|
34
|
+
return send_pushed_authorisation_request(self.config, authorisation_server_id, checked_essential_claims,
|
|
35
|
+
checked_voluntary_claims, checked_purpose)
|
|
36
|
+
|
|
37
|
+
def retrieve_tokens(self, authorisation_server_id: str, callback_body: CallbackBody, original_code_verifier: str,
|
|
38
|
+
original_state: str, nonce: str) -> TokenSet:
|
|
39
|
+
return retrieve_tokens(self.config, authorisation_server_id, callback_body, original_code_verifier,
|
|
40
|
+
original_state, nonce)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, List
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
from rp_python_sdk.setup_logger import logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def assert_not_blank(value: str, parameter_name: str) -> None:
|
|
9
|
+
if not value.strip():
|
|
10
|
+
raise ValueError(f"{parameter_name} can't be blank")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def assert_not_null(value: Any, parameter_name: str) -> None:
|
|
14
|
+
if value is None:
|
|
15
|
+
raise ValueError(f"{parameter_name} can't be null")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class CustomConfig:
|
|
20
|
+
enable_auto_compliance_verification: bool = False
|
|
21
|
+
purpose: str = "verifying your identity"
|
|
22
|
+
include_uncertified_participants: bool = False
|
|
23
|
+
required_claims: List[str] = field(default_factory=list)
|
|
24
|
+
required_participant_certifications: List['CertificationFilter'] = field(default_factory=list)
|
|
25
|
+
timeout_in_seconds: int = 10
|
|
26
|
+
|
|
27
|
+
def __post_init__(self) -> None:
|
|
28
|
+
if self.include_uncertified_participants:
|
|
29
|
+
logger.info("Identity provider list will not be filtered as includeUncertifiedParticipants=true")
|
|
30
|
+
else:
|
|
31
|
+
if self.required_claims:
|
|
32
|
+
logger.info(
|
|
33
|
+
f"Identity provider list will be filtered for participants that support the following claims: {self.required_claims}")
|
|
34
|
+
if self.required_participant_certifications:
|
|
35
|
+
logger.info(
|
|
36
|
+
f"Identity provider list will be filtered for participants that support the following certifications: {self.required_participant_certifications}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class SdkConfig:
|
|
41
|
+
signing_kid: str
|
|
42
|
+
transport_key: str
|
|
43
|
+
transport_pem: str
|
|
44
|
+
signing_key: str
|
|
45
|
+
signing_pem: str
|
|
46
|
+
ca_pem: str
|
|
47
|
+
application_redirect_uri: str
|
|
48
|
+
registry_participants_uri: str
|
|
49
|
+
client_id: str
|
|
50
|
+
custom_config: CustomConfig # TODO: fix this = field(default_factory=lambda: CustomConfig())
|
|
51
|
+
|
|
52
|
+
def __post_init__(self) -> None:
|
|
53
|
+
assert_not_blank(self.signing_kid, "Signing kid")
|
|
54
|
+
assert_not_blank(self.transport_key, "Transport key")
|
|
55
|
+
assert_not_blank(self.transport_pem, "Transport pem")
|
|
56
|
+
assert_not_blank(self.signing_key, "Signing key")
|
|
57
|
+
assert_not_blank(self.signing_pem, "Signing pem")
|
|
58
|
+
assert_not_blank(self.ca_pem, "CA pem")
|
|
59
|
+
|
|
60
|
+
# For URI validation, we're using urlparse
|
|
61
|
+
for uri_attr in ['application_redirect_uri', 'registry_participants_uri', 'client_id']:
|
|
62
|
+
uri_value = getattr(self, uri_attr)
|
|
63
|
+
assert_not_null(urlparse(uri_value).scheme, uri_attr) # Simple check to ensure it's a valid URI
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class CertificationFilter:
|
|
68
|
+
profile_variant: str
|
|
69
|
+
profile_type: str
|
|
70
|
+
|
|
71
|
+
def __str__(self) -> str:
|
|
72
|
+
return f"CertificationFilter{{profileVariant='{self.profile_variant}', profileType='{self.profile_type}'}}"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
5
|
+
logger = logging.getLogger('relying_party_client_sdk')
|
|
6
|
+
|
|
7
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
8
|
+
handler.setLevel(logging.DEBUG)
|
|
9
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
10
|
+
handler.setFormatter(formatter)
|
|
11
|
+
|
|
12
|
+
logger.addHandler(handler)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 markgreene74
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|