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.
@@ -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,2 @@
1
+ class RelyingPartyClientSdkException(Exception):
2
+ pass
@@ -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.