Skip to content

Commit e308136

Browse files
pfvatterottmrmauer
andauthored
New apis april 2026 (#97)
* new apis * new scim apis * fixes --------- Co-authored-by: Matthew Mauer <mrmauer112@gmail.com>
1 parent 0a706d4 commit e308136

10 files changed

Lines changed: 1101 additions & 429 deletions

File tree

propelauth_py/__init__.py

Lines changed: 472 additions & 413 deletions
Large diffs are not rendered by default.

propelauth_py/api/end_user_api_keys.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def _fetch_api_key(auth_hostname, integration_api_key, api_key_id) -> ApiKeyFull
6363
metadata=json_response.get("metadata"),
6464
user_id=json_response.get("user_id"),
6565
org_id=json_response.get("org_id"),
66+
display_name=json_response.get("display_name"),
6667
)
6768

6869

@@ -93,6 +94,7 @@ async def _fetch_api_key_async(httpx_client: httpx.AsyncClient, auth_hostname, i
9394
metadata=json_response.get("metadata"),
9495
user_id=json_response.get("user_id"),
9596
org_id=json_response.get("org_id"),
97+
display_name=json_response.get("display_name"),
9698
)
9799

98100
def _get_paged_api_keys(json_response) -> ApiKeyResultPage:
@@ -104,6 +106,7 @@ def _get_paged_api_keys(json_response) -> ApiKeyResultPage:
104106
metadata=key.get("metadata"),
105107
user_id=key.get("user_id"),
106108
org_id=key.get("org_id"),
109+
display_name=key.get("display_name"),
107110
)
108111
for key in json_response.get("api_keys")
109112
]
@@ -388,7 +391,7 @@ async def _fetch_api_key_usage_async(
388391
# POST #
389392
####################
390393
def _create_api_key(
391-
auth_hostname, integration_api_key, org_id, user_id, expires_at_seconds, metadata
394+
auth_hostname, integration_api_key, org_id, user_id, expires_at_seconds, metadata, display_name
392395
) -> ApiKeyNew:
393396
url = ENDPOINT_URL
394397

@@ -401,6 +404,8 @@ def _create_api_key(
401404
json["expires_at_seconds"] = expires_at_seconds
402405
if metadata:
403406
json["metadata"] = metadata
407+
if display_name:
408+
json["display_name"] = display_name
404409

405410
response = requests.post(
406411
url,
@@ -431,7 +436,8 @@ async def _create_api_key_async(
431436
org_id,
432437
user_id,
433438
expires_at_seconds,
434-
metadata
439+
metadata,
440+
display_name,
435441
) -> ApiKeyNew:
436442

437443
json_body = {}
@@ -443,7 +449,9 @@ async def _create_api_key_async(
443449
json_body["expires_at_seconds"] = expires_at_seconds
444450
if metadata:
445451
json_body["metadata"] = metadata
446-
452+
if display_name:
453+
json_body["display_name"] = display_name
454+
447455
response = await httpx_client.post(
448456
url=ENDPOINT_URL,
449457
json=json_body,
@@ -649,7 +657,7 @@ def _get_api_key_validation(json_response) -> ApiKeyValidation:
649657

650658

651659
def _import_api_key(
652-
auth_hostname, integration_api_key, imported_api_key, org_id, user_id, expires_at_seconds, metadata
660+
auth_hostname, integration_api_key, imported_api_key, org_id, user_id, expires_at_seconds, metadata, display_name,
653661
) -> ImportedApiKeyNew:
654662
url = f"{ENDPOINT_URL}/import"
655663

@@ -664,6 +672,8 @@ def _import_api_key(
664672
json["expires_at_seconds"] = expires_at_seconds
665673
if metadata:
666674
json["metadata"] = metadata
675+
if display_name:
676+
json["display_name"] = display_name
667677

668678
response = requests.post(
669679
url,
@@ -694,7 +704,8 @@ async def _import_api_key_async(
694704
org_id,
695705
user_id,
696706
expires_at_seconds,
697-
metadata
707+
metadata,
708+
display_name
698709
) -> ImportedApiKeyNew:
699710

700711
json_body = {}
@@ -708,6 +719,8 @@ async def _import_api_key_async(
708719
json_body["expires_at_seconds"] = expires_at_seconds
709720
if metadata:
710721
json_body["metadata"] = metadata
722+
if display_name:
723+
json_body["display_name"] = display_name
711724

712725
response = await httpx_client.post(
713726
url=f"{ENDPOINT_URL}/import",

propelauth_py/api/magic_link.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ def _create_magic_link(
3131
expires_in_hours=None,
3232
create_new_user_if_one_doesnt_exist=None,
3333
user_signup_query_parameters=None,
34-
expire_after_first_use=None
34+
expire_after_first_use=None,
35+
requires_interstitial=None,
3536
) -> CreateMagicLinkResponse:
3637
url = ENDPOINT_URL
3738
json = {"email": email}
@@ -46,7 +47,9 @@ def _create_magic_link(
4647
"create_new_user_if_one_doesnt_exist"
4748
] = create_new_user_if_one_doesnt_exist
4849
if expire_after_first_use is not None:
49-
json["expire_after_first_use"] = expire_after_first_use
50+
json["expire_after_first_use"] = expire_after_first_use
51+
if requires_interstitial is not None:
52+
json["requires_interstitial"] = requires_interstitial
5053

5154

5255
response = requests.post(
@@ -79,7 +82,8 @@ async def _create_magic_link_async(
7982
expires_in_hours=None,
8083
create_new_user_if_one_doesnt_exist=None,
8184
user_signup_query_parameters=None,
82-
expire_after_first_use=None
85+
expire_after_first_use=None,
86+
requires_interstitial=None,
8387
) -> CreateMagicLinkResponse:
8488
json_body = {"email": email}
8589
if redirect_to_url is not None:
@@ -92,7 +96,9 @@ async def _create_magic_link_async(
9296
json_body["create_new_user_if_one_doesnt_exist"] = create_new_user_if_one_doesnt_exist
9397
if expire_after_first_use is not None:
9498
json_body["expire_after_first_use"] = expire_after_first_use
95-
99+
if requires_interstitial is not None:
100+
json_body["requires_interstitial"] = requires_interstitial
101+
96102
response = await httpx_client.post(
97103
url=ENDPOINT_URL,
98104
json=json_body,

propelauth_py/api/org.py

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from propelauth_py.api.end_user_api_keys import _validate_api_key, _validate_api_key_async
66
from propelauth_py.types.user import Organization, OrgQueryResponse, Org, PendingInvite, PendingInvitesPage, CreatedOrg, OrgApiKeyValidation
77
from propelauth_py.types.custom_role_mappings import CustomRoleMappings, CustomRoleMapping
8-
from propelauth_py.types.saml_types import SamlIdpMetadata, SpMetadata
8+
from propelauth_py.types.saml_types import SamlIdpMetadata, SpMetadata, SetOidcIdpMetadataRequest, SetGenericOidcMetadataRequest, SetOktaOidcMetadataRequest, SetAzureOidcMetadataRequest
99
from propelauth_py.errors import (
1010
BadRequestException,
1111
EndUserApiKeyException,
@@ -61,7 +61,10 @@ def _fetch_org(auth_hostname, integration_api_key, org_id) -> Optional[Organizat
6161
domain_autojoin=json_response.get('domain_autojoin'),
6262
domain_restrict=json_response.get('domain_restrict'),
6363
custom_role_mapping_name=json_response.get('custom_role_mapping_name'),
64-
legacy_org_id=json_response.get('legacy_org_id')
64+
legacy_org_id=json_response.get('legacy_org_id'),
65+
password_rotation_enabled=json_response.get('password_rotation_enabled'),
66+
password_rotation_history_size=json_response.get('password_rotation_history_size'),
67+
password_rotation_period=json_response.get('password_rotation_period'),
6568
)
6669

6770
async def _fetch_org_async(
@@ -108,7 +111,10 @@ async def _fetch_org_async(
108111
domain_autojoin=json_response.get('domain_autojoin'),
109112
domain_restrict=json_response.get('domain_restrict'),
110113
custom_role_mapping_name=json_response.get('custom_role_mapping_name'),
111-
legacy_org_id=json_response.get('legacy_org_id')
114+
legacy_org_id=json_response.get('legacy_org_id'),
115+
password_rotation_enabled=json_response.get('password_rotation_enabled'),
116+
password_rotation_history_size=json_response.get('password_rotation_history_size'),
117+
password_rotation_period=json_response.get('password_rotation_period'),
112118
)
113119

114120

@@ -155,7 +161,8 @@ def _fetch_org_by_query(
155161
is_saml_configured=key.get('is_saml_configured'),
156162
legacy_org_id=key.get('legacy_org_id'),
157163
metadata=key.get('metadata'),
158-
custom_role_mapping_name=key.get('custom_role_mapping_name')
164+
custom_role_mapping_name=key.get('custom_role_mapping_name'),
165+
created_at=key.get('created_at'),
159166
)
160167
for key in json_response.get('orgs')
161168
]
@@ -220,7 +227,8 @@ async def _fetch_org_by_query_async(
220227
is_saml_configured=key.get('is_saml_configured'),
221228
legacy_org_id=key.get('legacy_org_id'),
222229
metadata=key.get('metadata'),
223-
custom_role_mapping_name=key.get('custom_role_mapping_name')
230+
custom_role_mapping_name=key.get('custom_role_mapping_name'),
231+
created_at=key.get('created_at'),
224232
)
225233
for key in json_response.get('orgs')
226234
]
@@ -1020,6 +1028,92 @@ async def _set_saml_idp_metadata_async(
10201028

10211029
response.raise_for_status()
10221030
return True
1031+
1032+
def _set_oidc_idp_metadata(auth_hostname, integration_api_key, request: SetOidcIdpMetadataRequest) -> bool:
1033+
if not _is_valid_id(request.org_id):
1034+
return False
1035+
1036+
json: dict = {
1037+
"org_id": request.org_id,
1038+
"client_id": request.client_id,
1039+
"client_secret": request.client_secret,
1040+
"uses_pkce": request.uses_pkce,
1041+
"idp_type": request.idp_type,
1042+
}
1043+
1044+
if isinstance(request, SetGenericOidcMetadataRequest):
1045+
json["auth_url"] = request.auth_url
1046+
json["token_url"] = request.token_url
1047+
json["userinfo_url"] = request.userinfo_url
1048+
elif isinstance(request, SetOktaOidcMetadataRequest):
1049+
json["okta_sso_domain"] = request.okta_sso_domain
1050+
elif isinstance(request, SetAzureOidcMetadataRequest):
1051+
json["entra_tenant_id"] = request.entra_tenant_id
1052+
1053+
response = requests.post(
1054+
f"{BASE_ENDPOINT_URL}/oidc_idp_metadata",
1055+
json=json,
1056+
auth=_ApiKeyAuth(integration_api_key),
1057+
headers=_auth_hostname_header(auth_hostname),
1058+
)
1059+
1060+
if response.status_code == 401:
1061+
raise ValueError("integration_api_key is incorrect")
1062+
elif response.status_code == 429:
1063+
raise RateLimitedException(response.text)
1064+
elif response.status_code == 400:
1065+
raise BadRequestException(response.json())
1066+
elif response.status_code == 404:
1067+
return False
1068+
elif not response.ok:
1069+
raise RuntimeError("Unknown error when setting the OIDC IdP metadata for an org's OIDC connection")
1070+
return True
1071+
1072+
async def _set_oidc_idp_metadata_async(
1073+
httpx_client: httpx.AsyncClient,
1074+
auth_hostname,
1075+
integration_api_key,
1076+
request: SetOidcIdpMetadataRequest
1077+
) -> bool:
1078+
if not _is_valid_id(request.org_id):
1079+
return False
1080+
1081+
url = f"{BASE_ENDPOINT_URL}/oidc_idp_metadata"
1082+
1083+
json: dict = {
1084+
"org_id": request.org_id,
1085+
"client_id": request.client_id,
1086+
"client_secret": request.client_secret,
1087+
"uses_pkce": request.uses_pkce,
1088+
"idp_type": request.idp_type,
1089+
}
1090+
1091+
if isinstance(request, SetGenericOidcMetadataRequest):
1092+
json["auth_url"] = request.auth_url
1093+
json["token_url"] = request.token_url
1094+
json["userinfo_url"] = request.userinfo_url
1095+
elif isinstance(request, SetOktaOidcMetadataRequest):
1096+
json["okta_sso_domain"] = request.okta_sso_domain
1097+
elif isinstance(request, SetAzureOidcMetadataRequest):
1098+
json["entra_tenant_id"] = request.entra_tenant_id
1099+
1100+
response = await httpx_client.post(
1101+
url=url,
1102+
json=json,
1103+
headers=_get_async_headers(auth_hostname=auth_hostname, integration_api_key=integration_api_key)
1104+
)
1105+
1106+
if response.status_code == 401:
1107+
raise ValueError("integration_api_key is incorrect")
1108+
elif response.status_code == 429:
1109+
raise RateLimitedException(response.text)
1110+
elif response.status_code == 400:
1111+
raise BadRequestException(response.json())
1112+
elif response.status_code == 404:
1113+
return False
1114+
1115+
response.raise_for_status()
1116+
return True
10231117

10241118
def _saml_go_live(auth_hostname, integration_api_key, org_id) -> bool:
10251119
if not _is_valid_id(org_id):
@@ -1091,6 +1185,9 @@ def _update_org_metadata(
10911185
legacy_org_id=None,
10921186
require_2fa_by=None,
10931187
extra_domains=None,
1188+
password_rotation_enabled=None,
1189+
password_rotation_history_size=None,
1190+
password_rotation_period=None,
10941191
) -> bool:
10951192
if not _is_valid_id(org_id):
10961193
return False
@@ -1117,6 +1214,12 @@ def _update_org_metadata(
11171214
json["require_2fa_by"] = require_2fa_by
11181215
if extra_domains is not None:
11191216
json["extra_domains"] = extra_domains
1217+
if password_rotation_enabled is not None:
1218+
json["password_rotation_enabled"] = password_rotation_enabled
1219+
if password_rotation_history_size is not None:
1220+
json["password_rotation_history_size"] = password_rotation_history_size
1221+
if password_rotation_period is not None:
1222+
json["password_rotation_period"] = password_rotation_period
11201223

11211224
response = requests.put(
11221225
url,
@@ -1153,6 +1256,9 @@ async def _update_org_metadata_async(
11531256
legacy_org_id=None,
11541257
require_2fa_by=None,
11551258
extra_domains=None,
1259+
password_rotation_enabled=None,
1260+
password_rotation_history_size=None,
1261+
password_rotation_period=None,
11561262
) -> bool:
11571263
if not _is_valid_id(org_id):
11581264
return False
@@ -1179,6 +1285,12 @@ async def _update_org_metadata_async(
11791285
json_body["require_2fa_by"] = require_2fa_by
11801286
if extra_domains is not None:
11811287
json_body["extra_domains"] = extra_domains
1288+
if password_rotation_enabled is not None:
1289+
json_body["password_rotation_enabled"] = password_rotation_enabled
1290+
if password_rotation_history_size is not None:
1291+
json_body["password_rotation_history_size"] = password_rotation_history_size
1292+
if password_rotation_period is not None:
1293+
json_body["password_rotation_period"] = password_rotation_period
11821294

11831295
response = await httpx_client.put(
11841296
url=url,

0 commit comments

Comments
 (0)