Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Management #84

Merged
merged 10 commits into from
Nov 24, 2022
Merged
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,6 @@ dmypy.json
.pyre/

.vscode/

# Mac OS
.DS_Store
2 changes: 2 additions & 0 deletions descope/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
)
from descope.descope_client import DescopeClient
from descope.exceptions import AuthException
from descope.management.sso_settings import RoleMapping
from descope.management.user import UserTenants
6 changes: 3 additions & 3 deletions descope/authmethod/otp.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ def sign_up_or_in(self, method: DeliveryMethod, identifier: str) -> None:
"""
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
identifier is a new or existing end user. If identifier is new, a new end user user will be created and then
authenticated using the OTP DeliveryMethod specififed. If identifier exists, the end user will be authenticated
using the OTP DelieryMethod specified.
authenticated using the OTP DeliveryMethod specified. If identifier exists, the end user will be authenticated
using the OTP DeliveryMethod specified.

Args:
method (DeliveryMethod): The method to use for delivering the OTP verification code, for example phone or email
Expand All @@ -100,7 +100,7 @@ def sign_up_or_in(self, method: DeliveryMethod, identifier: str) -> None:

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

Args:
Expand Down
6 changes: 6 additions & 0 deletions descope/descope_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from descope.authmethod.webauthn import WebauthN # noqa: F401
from descope.common import SESSION_TOKEN_NAME, EndpointsV1
from descope.exceptions import ERROR_TYPE_INVALID_ARGUMENT, AuthException
from descope.mgmt import MGMT # noqa: F401


class DescopeClient:
Expand All @@ -24,13 +25,18 @@ def __init__(
):
auth = Auth(project_id, public_key, skip_verify)
self._auth = auth
self._mgmt = MGMT(auth)
self._magiclink = MagicLink(auth)
self._oauth = OAuth(auth)
self._saml = SAML(auth)
self._otp = OTP(auth)
self._totp = TOTP(auth)
self._webauthn = WebauthN(auth)

@property
def mgmt(self):
return self._mgmt

@property
def magiclink(self):
return self._magiclink
Expand Down
15 changes: 15 additions & 0 deletions descope/management/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class MgmtV1:
itaihanski marked this conversation as resolved.
Show resolved Hide resolved
# tenant
tenantCreatePath = "/v1/mgmt/tenant/create"
tenantUpdatePath = "/v1/mgmt/tenant/update"
tenantDeletePath = "/v1/mgmt/tenant/delete"

# user
userCreatePath = "/v1/mgmt/user/create"
userUpdatePath = "/v1/mgmt/user/update"
userDeletePath = "/v1/mgmt/user/delete"

# sso
ssoConfigurePath = "/v1/mgmt/sso/settings"
ssoMetadataPath = "/v1/mgmt/sso/metadata"
ssoRoleMappingPath = "/v1/mgmt/sso/roles"
143 changes: 143 additions & 0 deletions descope/management/sso_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from typing import List

from descope.auth import Auth
from descope.management.common import MgmtV1


class RoleMapping:
def __init__(self, groups: List[str], role_name: str):
self.groups = groups
self.role_name = role_name


class SSOSettings:
_auth: Auth

def __init__(self, auth: Auth):
self._auth = auth

def configure(
itaihanski marked this conversation as resolved.
Show resolved Hide resolved
self,
mgmt_key: str,
tenant_id: str,
idp_url: str,
entity_id: str,
idp_cert: str,
redirect_url: str = None,
) -> None:
"""
Configure SSO setting for a tenant manually. Alternatively, `configure_via_metadata` can be used instead.

Args:
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
tenant_id (str): The tenant ID to be configured
idp_url (str): The URL for the identity provider.
entity_id (str): The entity ID (in the IDP).
idp_cert (str): The certificate provided by the IDP.
redirect_url (str): An Optional Redirect URL after successful authentication.

Raise:
AuthException: raised if configuration operation fails
"""
self._auth.do_post(
MgmtV1.ssoConfigurePath,
SSOSettings._compose_configure_body(
tenant_id, idp_url, entity_id, idp_cert, redirect_url
),
pswd=mgmt_key,
)

def configure_via_metadata(
self,
mgmt_key: str,
tenant_id: str,
idp_metadata_url: str,
):
"""
Configure SSO setting for am IDP metadata URL. Alternatively, `configure` can be used instead.

Args:
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
tenant_id (str): The tenant ID to be configured
enabled (bool): Is SSO enabled
idp_metadata_url (str): The URL to fetch SSO settings from.

Raise:
AuthException: raised if configuration operation fails
"""
self._auth.do_post(
MgmtV1.ssoMetadataPath,
SSOSettings._compose_metadata_body(tenant_id, idp_metadata_url),
pswd=mgmt_key,
)

def map_roles(
self,
mgmt_key: str,
tenant_id: str,
role_mappings: List[RoleMapping],
):
"""
Configure SSO role mapping from the IDP groups to the Descope roles.

Args:
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
tenant_id (str): The tenant ID to be configured
role_mappings (List[RoleMapping]): A mapping between IDP groups and Descope roles.

Raise:
AuthException: raised if configuration operation fails
"""
self._auth.do_post(
MgmtV1.ssoRoleMappingPath,
SSOSettings._compose_role_mapping_body(tenant_id, role_mappings),
pswd=mgmt_key,
)

@staticmethod
def _compose_configure_body(
tenant_id: str,
idp_url: str,
entity_id: str,
idp_cert: str,
redirect_url: str = None,
) -> dict:
return {
"tenantId": tenant_id,
"idpURL": idp_url,
"entityId": entity_id,
"idpCert": idp_cert,
"redirectURL": redirect_url,
}

@staticmethod
def _compose_metadata_body(
tenant_id: str,
idp_metadata_url: str,
) -> dict:
return {
"tenantId": tenant_id,
"idpMetadataURL": idp_metadata_url,
}

@staticmethod
def _compose_role_mapping_body(
tenant_id: str,
role_mapping: List[RoleMapping],
) -> dict:
return {
"tenantId": tenant_id,
"roleMapping": SSOSettings._role_mapping_to_dict(role_mapping),
}

@staticmethod
def _role_mapping_to_dict(role_mapping: List[RoleMapping]) -> list:
role_mapping_list = []
for mapping in role_mapping:
role_mapping_list.append(
{
"groups": mapping.groups,
"roleName": mapping.role_name,
}
)
return role_mapping_list
96 changes: 96 additions & 0 deletions descope/management/tenant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from typing import List

from descope.auth import Auth
from descope.management.common import MgmtV1


class Tenant:
_auth: Auth

def __init__(self, auth: Auth):
self._auth = auth

def create(
self,
mgmt_key: str,
name: str,
id: str = None,
self_provisioning_domains: List[str] = [],
) -> dict:
"""
Create a new tenant with the given name. Tenant IDs are provisioned automatically, but can be provided
explicitly if needed. Both the name and ID must be unique per project.

Args:
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
name (str): The tenant's name
id (str): Optional tenant ID.
self_provisioning_domains (List[str]): An optional list of domain that are associated with this tenant.
Users authenticating from these domains will be associated with this tenant.

Raise:
AuthException: raised if creation operation fails
"""
uri = MgmtV1.tenantCreatePath
response = self._auth.do_post(
uri,
Tenant._compose_create_update_body(name, id, self_provisioning_domains),
pswd=mgmt_key,
)
return response.json()

def update(
self,
mgmt_key: str,
id: str,
name: str,
self_provisioning_domains: List[str] = [],
):
"""
Update an existing tenant with the given name and domains. IMPORTANT: All parameters are used as overrides
itaihanski marked this conversation as resolved.
Show resolved Hide resolved
to the existing tenant. Empty fields will override populated fields. Use carefully.

Args:
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
id (str): The ID of the tenant to update.
name (str): Updated tenant name
self_provisioning_domains (List[str]): An optional list of domain that are associated with this tenant.
Users authenticating from these domains will be associated with this tenant.

Raise:
AuthException: raised if creation operation fails
"""
uri = MgmtV1.tenantUpdatePath
self._auth.do_post(
uri,
Tenant._compose_create_update_body(name, id, self_provisioning_domains),
pswd=mgmt_key,
)

def delete(
self,
mgmt_key: str,
id: str,
):
"""
Delete an existing tenant. IMPORTANT: This action is irreversible. Use carefully.

Args:
mgmt_key (str): A management key generated in the Descope console. All management functions require it.
id (str): The ID of the tenant that's to be deleted.

Raise:
AuthException: raised if creation operation fails
"""
uri = MgmtV1.tenantDeletePath
self._auth.do_post(uri, {"id": id}, pswd=mgmt_key)

@staticmethod
def _compose_create_update_body(
name: str, id: str, self_provisioning_domains: List[str]
) -> dict:
return {
"name": name,
"id": id,
"selfProvisioningDomains": self_provisioning_domains,
itaihanski marked this conversation as resolved.
Show resolved Hide resolved
}
Loading