Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,14 @@ descope_client.mgmt.tenant.update(
custom_attributes={"attribute-name": "value"},
)

# Managing the tenant's settings
# Getting the settings
descope_client.mgmt.tenant.load_settings(id="my-custom-id")

# updating the settings
descope_client.mgmt.tenant.update_settings(id="my-custom-id", self_provisioning_domains=["domain.com"], session_settings_enabled=True, refresh_token_expiration=1, refresh_token_expiration_unit="hours")


# Tenant deletion cannot be undone. Use carefully.
# Pass true to cascade value, in case you want to delete all users/keys associated only with this tenant
descope_client.mgmt.tenant.delete(id="my-custom-id", cascade=False)
Expand Down
12 changes: 11 additions & 1 deletion descope/management/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
from enum import Enum
from typing import List, Optional

class SessionExpirationUnit(Enum):
MINUTES = "minutes"
HOURS = "hours"
DAYS = "days"
WEEKS = "weeks"

class TenantAuthType(Enum):
NONE = "none"
SAML = "saml"
OIDC = "oidc"

class AccessType(Enum):
OFFLINE = "offline"
Expand Down Expand Up @@ -35,6 +45,7 @@ class MgmtV1:
tenant_update_path = "/v1/mgmt/tenant/update"
tenant_delete_path = "/v1/mgmt/tenant/delete"
tenant_load_path = "/v1/mgmt/tenant"
tenant_settings_path = "/v1/mgmt/tenant/settings"
tenant_load_all_path = "/v1/mgmt/tenant/all"
tenant_search_all_path = "/v1/mgmt/tenant/search"

Expand Down Expand Up @@ -292,7 +303,6 @@ def associated_tenants_to_dict(associated_tenants: List[AssociatedTenant]) -> li
)
return associated_tenant_list


class SAMLIDPAttributeMappingInfo:
"""
Represents a SAML IDP attribute mapping object. use this class for mapping Descope attribute
Expand Down
98 changes: 97 additions & 1 deletion descope/management/tenant.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any, List, Optional

from descope._http_base import HTTPBase
from descope.management.common import MgmtV1
from descope.management.common import MgmtV1, TenantAuthType, SessionExpirationUnit


class Tenant(HTTPBase):
Expand Down Expand Up @@ -92,6 +92,73 @@ def update(
),
)

def update_settings(
self,
id: str,
self_provisioning_domains: List[str],
domains: Optional[List[str]] = None,
auth_type: Optional[TenantAuthType] = None,
session_settings_enabled: Optional[bool] = None,
refresh_token_expiration: Optional[int] = None,
refresh_token_expiration_unit: Optional[SessionExpirationUnit] = None,
session_token_expiration: Optional[int] = None,
session_token_expiration_unit: Optional[SessionExpirationUnit] = None,
stepup_token_expiration: Optional[int] = None,
stepup_token_expiration_unit: Optional[SessionExpirationUnit] = None,
enable_inactivity: Optional[bool] = None,
inactivity_time: Optional[int] = None,
inactivity_time_unit: Optional[SessionExpirationUnit] = None,
JITDisabled: Optional[bool] = None
):
"""
Update an existing tenant's session settings.

Args:
id (str): The ID of the tenant to update.
self_provisioning_domains (List[str]): Domains for self-provisioning.
domains (Optional[List[str]]): List of domains associated with the tenant.
auth_type (Optional[TenantAuthType]): Authentication type for the tenant.
session_settings_enabled (Optional[bool]): Whether session settings are enabled.
refresh_token_expiration (Optional[int]): Expiration time for refresh tokens.
refresh_token_expiration_unit (Optional[SessionExiprationUnit]): Unit for refresh token expiration.
session_token_expiration (Optional[int]): Expiration time for session tokens.
session_token_expiration_unit (Optional[SessionExiprationUnit]): Unit for session token expiration.
stepup_token_expiration (Optional[int]): Expiration time for step-up tokens.
stepup_token_expiration_unit (Optional[SessionExiprationUnit]): Unit for step-up token expiration.
enable_inactivity (Optional[bool]): Whether inactivity timeout is enabled.
inactivity_time (Optional[int]): Inactivity timeout duration.
inactivity_time_unit (Optional[SessionExiprationUnit]): Unit for inactivity timeout.
JITDisabled (Optional[bool]): Whether JIT is disabled.

Raise:
AuthException: raised if update operation fails
"""
body: dict[str, Any] = {
"tenantId": id,
"selfProvisioningDomains": self_provisioning_domains,
"domains": domains,
"authType": auth_type,
"enabled": session_settings_enabled,
"refreshTokenExpiration": refresh_token_expiration,
"refreshTokenExpirationUnit": refresh_token_expiration_unit,
"sessionTokenExpiration": session_token_expiration,
"sessionTokenExpirationUnit": session_token_expiration_unit,
"stepupTokenExpiration": stepup_token_expiration,
"stepupTokenExpirationUnit": stepup_token_expiration_unit,
"enableInactivity": enable_inactivity,
"inactivityTime": inactivity_time,
"inactivityTimeUnit": inactivity_time_unit,
"JITDisabled": JITDisabled,
}

body = {k: v for k, v in body.items() if v is not None}

self._http.post(
MgmtV1.tenant_settings_path,
body=body,
params=None
)

def delete(
self,
id: str,
Expand Down Expand Up @@ -134,6 +201,35 @@ def load(
params={"id": id},
)
return response.json()

def load_settings(
self,
id: str,
) -> dict:
"""
Load tenant session settings by id.

Args:
id (str): The ID of the tenant to load session settings for.

Return value (dict):
Return dict in the format
{ "domains":<list[str]>, "selfProvisioningDomains":<list[str]>, "authType":<str>,
"enabled":<bool>, "refreshTokenExpiration":<int>, "refreshTokenExpirationUnit":<str>,
"sessionTokenExpiration":<int>, "sessionTokenExpirationUnit":<str>,
"stepupTokenExpiration":<int>, "stepupTokenExpirationUnit":<str>,
"enableInactivity":<bool>, "inactivityTime":<int>, "inactivityTimeUnit":<str>,
"JITDisabled":<bool> }
Containing the loaded tenant session settings.

Raise:
AuthException: raised if load operation fails
"""
response = self._http.get(
MgmtV1.tenant_settings_path,
params={"id": id},
)
return response.json()

def load_all(
self,
Expand Down
88 changes: 88 additions & 0 deletions tests/management/test_tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,91 @@ def test_search_all(self):
params=None,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_update_settings(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
)

# Test failed flows
with patch("requests.post") as mock_post:
mock_post.return_value.ok = False
self.assertRaises(
AuthException,
client.mgmt.tenant.update_settings,
"valid-id",
{},
)

# Test success flow
with patch("requests.post") as mock_post:
mock_post.return_value.ok = True
self.assertIsNone(
client.mgmt.tenant.update_settings("t1", self_provisioning_domains=["domain1.com"], domains=["domain1.com", "domain2.com"], auth_type="oidc", session_settings_enabled=True)
)
mock_post.assert_called_with(
f"{common.DEFAULT_BASE_URL}{MgmtV1.tenant_settings_path}",
headers={
**common.default_headers,
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
"x-descope-project-id": self.dummy_project_id,
},
json={
"tenantId": "t1",
"selfProvisioningDomains": ["domain1.com"],
"domains": ["domain1.com", "domain2.com"],
"authType": "oidc",
"enabled": True
},
allow_redirects=False,
params=None,
verify=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_load_settings(self):
client = DescopeClient(
self.dummy_project_id,
self.public_key_dict,
False,
self.dummy_management_key,
)

# Test failed flows
with patch("requests.get") as mock_get:
mock_get.return_value.ok = False
self.assertRaises(
AuthException,
client.mgmt.tenant.load_settings,
"valid-id",
)

# Test success flow
with patch("requests.get") as mock_get:
network_resp = mock.Mock()
network_resp.ok = True
network_resp.json.return_value = json.loads(
"""
{"domains": ["domain1.com", "domain2.com"], "authType": "oidc", "sessionSettingsEnabled": true}
"""
)
mock_get.return_value = network_resp
resp = client.mgmt.tenant.load_settings("t1")
self.assertEqual(resp["domains"], ["domain1.com", "domain2.com"])
self.assertEqual(resp["authType"], "oidc")
self.assertEqual(resp["sessionSettingsEnabled"], True)
mock_get.assert_called_with(
f"{common.DEFAULT_BASE_URL}{MgmtV1.tenant_settings_path}",
headers={
**common.default_headers,
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
"x-descope-project-id": self.dummy_project_id,
},
params={"id": "t1"},
allow_redirects=True,
verify=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)
Loading