Skip to content

Commit f943f46

Browse files
authored
Management (#84)
Add management functionality to the python SDK. With this addition it is possible to manage users and tenants via API.
1 parent a554a39 commit f943f46

File tree

15 files changed

+885
-3
lines changed

15 files changed

+885
-3
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,6 @@ dmypy.json
129129
.pyre/
130130

131131
.vscode/
132+
133+
# Mac OS
134+
.DS_Store

descope/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@
88
)
99
from descope.descope_client import DescopeClient
1010
from descope.exceptions import AuthException
11+
from descope.management.sso_settings import RoleMapping
12+
from descope.management.user import UserTenants

descope/authmethod/otp.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ def sign_up_or_in(self, method: DeliveryMethod, identifier: str) -> None:
7979
"""
8080
Sign_up_or_in lets you handle both sign up and sign in with a single call. Sign-up_or_in will first determine if
8181
identifier is a new or existing end user. If identifier is new, a new end user user will be created and then
82-
authenticated using the OTP DeliveryMethod specififed. If identifier exists, the end user will be authenticated
83-
using the OTP DelieryMethod specified.
82+
authenticated using the OTP DeliveryMethod specified. If identifier exists, the end user will be authenticated
83+
using the OTP DeliveryMethod specified.
8484
8585
Args:
8686
method (DeliveryMethod): The method to use for delivering the OTP verification code, for example phone or email
@@ -100,7 +100,7 @@ def sign_up_or_in(self, method: DeliveryMethod, identifier: str) -> None:
100100

101101
def verify_code(self, method: DeliveryMethod, identifier: str, code: str) -> dict:
102102
"""
103-
Verify the valdity of an OTP code entered by an end user during sign_in or sign_up.
103+
Verify the validity of an OTP code entered by an end user during sign_in or sign_up.
104104
(This function is not needed if you are using the sign_up_or_in function.
105105
106106
Args:

descope/descope_client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from descope.authmethod.webauthn import WebauthN # noqa: F401
1212
from descope.common import SESSION_TOKEN_NAME, EndpointsV1
1313
from descope.exceptions import ERROR_TYPE_INVALID_ARGUMENT, AuthException
14+
from descope.mgmt import MGMT # noqa: F401
1415

1516

1617
class DescopeClient:
@@ -24,13 +25,18 @@ def __init__(
2425
):
2526
auth = Auth(project_id, public_key, skip_verify)
2627
self._auth = auth
28+
self._mgmt = MGMT(auth)
2729
self._magiclink = MagicLink(auth)
2830
self._oauth = OAuth(auth)
2931
self._saml = SAML(auth)
3032
self._otp = OTP(auth)
3133
self._totp = TOTP(auth)
3234
self._webauthn = WebauthN(auth)
3335

36+
@property
37+
def mgmt(self):
38+
return self._mgmt
39+
3440
@property
3541
def magiclink(self):
3642
return self._magiclink

descope/management/common.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class MgmtV1:
2+
# tenant
3+
tenantCreatePath = "/v1/mgmt/tenant/create"
4+
tenantUpdatePath = "/v1/mgmt/tenant/update"
5+
tenantDeletePath = "/v1/mgmt/tenant/delete"
6+
7+
# user
8+
userCreatePath = "/v1/mgmt/user/create"
9+
userUpdatePath = "/v1/mgmt/user/update"
10+
userDeletePath = "/v1/mgmt/user/delete"
11+
12+
# sso
13+
ssoConfigurePath = "/v1/mgmt/sso/settings"
14+
ssoMetadataPath = "/v1/mgmt/sso/metadata"
15+
ssoRoleMappingPath = "/v1/mgmt/sso/roles"

descope/management/sso_settings.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
from typing import List
2+
3+
from descope.auth import Auth
4+
from descope.management.common import MgmtV1
5+
6+
7+
class RoleMapping:
8+
def __init__(self, groups: List[str], role_name: str):
9+
self.groups = groups
10+
self.role_name = role_name
11+
12+
13+
class SSOSettings:
14+
_auth: Auth
15+
16+
def __init__(self, auth: Auth):
17+
self._auth = auth
18+
19+
def configure(
20+
self,
21+
mgmt_key: str,
22+
tenant_id: str,
23+
idp_url: str,
24+
entity_id: str,
25+
idp_cert: str,
26+
redirect_url: str = None,
27+
) -> None:
28+
"""
29+
Configure SSO setting for a tenant manually. Alternatively, `configure_via_metadata` can be used instead.
30+
31+
Args:
32+
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
33+
tenant_id (str): The tenant ID to be configured
34+
idp_url (str): The URL for the identity provider.
35+
entity_id (str): The entity ID (in the IDP).
36+
idp_cert (str): The certificate provided by the IDP.
37+
redirect_url (str): An Optional Redirect URL after successful authentication.
38+
39+
Raise:
40+
AuthException: raised if configuration operation fails
41+
"""
42+
self._auth.do_post(
43+
MgmtV1.ssoConfigurePath,
44+
SSOSettings._compose_configure_body(
45+
tenant_id, idp_url, entity_id, idp_cert, redirect_url
46+
),
47+
pswd=mgmt_key,
48+
)
49+
50+
def configure_via_metadata(
51+
self,
52+
mgmt_key: str,
53+
tenant_id: str,
54+
idp_metadata_url: str,
55+
):
56+
"""
57+
Configure SSO setting for am IDP metadata URL. Alternatively, `configure` can be used instead.
58+
59+
Args:
60+
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
61+
tenant_id (str): The tenant ID to be configured
62+
enabled (bool): Is SSO enabled
63+
idp_metadata_url (str): The URL to fetch SSO settings from.
64+
65+
Raise:
66+
AuthException: raised if configuration operation fails
67+
"""
68+
self._auth.do_post(
69+
MgmtV1.ssoMetadataPath,
70+
SSOSettings._compose_metadata_body(tenant_id, idp_metadata_url),
71+
pswd=mgmt_key,
72+
)
73+
74+
def map_roles(
75+
self,
76+
mgmt_key: str,
77+
tenant_id: str,
78+
role_mappings: List[RoleMapping],
79+
):
80+
"""
81+
Configure SSO role mapping from the IDP groups to the Descope roles.
82+
83+
Args:
84+
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
85+
tenant_id (str): The tenant ID to be configured
86+
role_mappings (List[RoleMapping]): A mapping between IDP groups and Descope roles.
87+
88+
Raise:
89+
AuthException: raised if configuration operation fails
90+
"""
91+
self._auth.do_post(
92+
MgmtV1.ssoRoleMappingPath,
93+
SSOSettings._compose_role_mapping_body(tenant_id, role_mappings),
94+
pswd=mgmt_key,
95+
)
96+
97+
@staticmethod
98+
def _compose_configure_body(
99+
tenant_id: str,
100+
idp_url: str,
101+
entity_id: str,
102+
idp_cert: str,
103+
redirect_url: str = None,
104+
) -> dict:
105+
return {
106+
"tenantId": tenant_id,
107+
"idpURL": idp_url,
108+
"entityId": entity_id,
109+
"idpCert": idp_cert,
110+
"redirectURL": redirect_url,
111+
}
112+
113+
@staticmethod
114+
def _compose_metadata_body(
115+
tenant_id: str,
116+
idp_metadata_url: str,
117+
) -> dict:
118+
return {
119+
"tenantId": tenant_id,
120+
"idpMetadataURL": idp_metadata_url,
121+
}
122+
123+
@staticmethod
124+
def _compose_role_mapping_body(
125+
tenant_id: str,
126+
role_mapping: List[RoleMapping],
127+
) -> dict:
128+
return {
129+
"tenantId": tenant_id,
130+
"roleMapping": SSOSettings._role_mapping_to_dict(role_mapping),
131+
}
132+
133+
@staticmethod
134+
def _role_mapping_to_dict(role_mapping: List[RoleMapping]) -> list:
135+
role_mapping_list = []
136+
for mapping in role_mapping:
137+
role_mapping_list.append(
138+
{
139+
"groups": mapping.groups,
140+
"roleName": mapping.role_name,
141+
}
142+
)
143+
return role_mapping_list

descope/management/tenant.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from typing import List
2+
3+
from descope.auth import Auth
4+
from descope.management.common import MgmtV1
5+
6+
7+
class Tenant:
8+
_auth: Auth
9+
10+
def __init__(self, auth: Auth):
11+
self._auth = auth
12+
13+
def create(
14+
self,
15+
mgmt_key: str,
16+
name: str,
17+
id: str = None,
18+
self_provisioning_domains: List[str] = [],
19+
) -> dict:
20+
"""
21+
Create a new tenant with the given name. Tenant IDs are provisioned automatically, but can be provided
22+
explicitly if needed. Both the name and ID must be unique per project.
23+
24+
Args:
25+
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
26+
name (str): The tenant's name
27+
id (str): Optional tenant ID.
28+
self_provisioning_domains (List[str]): An optional list of domain that are associated with this tenant.
29+
Users authenticating from these domains will be associated with this tenant.
30+
31+
Raise:
32+
AuthException: raised if creation operation fails
33+
"""
34+
uri = MgmtV1.tenantCreatePath
35+
response = self._auth.do_post(
36+
uri,
37+
Tenant._compose_create_update_body(name, id, self_provisioning_domains),
38+
pswd=mgmt_key,
39+
)
40+
return response.json()
41+
42+
def update(
43+
self,
44+
mgmt_key: str,
45+
id: str,
46+
name: str,
47+
self_provisioning_domains: List[str] = [],
48+
):
49+
"""
50+
Update an existing tenant with the given name and domains. IMPORTANT: All parameters are used as overrides
51+
to the existing tenant. Empty fields will override populated fields. Use carefully.
52+
53+
Args:
54+
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
55+
id (str): The ID of the tenant to update.
56+
name (str): Updated tenant name
57+
self_provisioning_domains (List[str]): An optional list of domain that are associated with this tenant.
58+
Users authenticating from these domains will be associated with this tenant.
59+
60+
Raise:
61+
AuthException: raised if creation operation fails
62+
"""
63+
uri = MgmtV1.tenantUpdatePath
64+
self._auth.do_post(
65+
uri,
66+
Tenant._compose_create_update_body(name, id, self_provisioning_domains),
67+
pswd=mgmt_key,
68+
)
69+
70+
def delete(
71+
self,
72+
mgmt_key: str,
73+
id: str,
74+
):
75+
"""
76+
Delete an existing tenant. IMPORTANT: This action is irreversible. Use carefully.
77+
78+
Args:
79+
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
80+
id (str): The ID of the tenant that's to be deleted.
81+
82+
Raise:
83+
AuthException: raised if creation operation fails
84+
"""
85+
uri = MgmtV1.tenantDeletePath
86+
self._auth.do_post(uri, {"id": id}, pswd=mgmt_key)
87+
88+
@staticmethod
89+
def _compose_create_update_body(
90+
name: str, id: str, self_provisioning_domains: List[str]
91+
) -> dict:
92+
return {
93+
"name": name,
94+
"id": id,
95+
"selfProvisioningDomains": self_provisioning_domains,
96+
}

0 commit comments

Comments
 (0)