Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,19 @@ descope_client.mgmt.tenant.update(
custom_attributes={"attribute-name": "value"},
)

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

# updating the session settings
tenant_settings = {
session_settings_enabled = True,
self_provisioning_domains = ["ackme.com"],

}
descope_client.mgmt.tenant.update_settings(id="my-custom-id", self_provisioning_domain=["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 SessionExiprationUnit(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
85 changes: 84 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, SessionExiprationUnit


class Tenant(HTTPBase):
Expand Down Expand Up @@ -92,6 +92,60 @@ 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[SessionExiprationUnit] = None,
session_token_expiration: Optional[int] = None,
session_token_expiration_unit: Optional[SessionExiprationUnit] = None,
stepup_token_expiration: Optional[int] = None,
stepup_token_expiration_unit: Optional[SessionExiprationUnit] = None,
enable_inactivity: Optional[bool] = None,
inactivity_time: Optional[int] = None,
inactivity_time_unit: Optional[SessionExiprationUnit] = None,
JITDisabled: Optional[bool] = None
):
"""
Update an existing tenant's session settings.

Args:
id (str): The ID of the tenant to update.
session_settings (dict): The session settings to set for the tenant.

Raise:
AuthException: raised if creation 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 +188,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