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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,12 @@ descope_client.mgmt.tenant.update(
custom_attributes={"attribute-name": "value"},
)

# Patch only updates the fields you pass in - everything else on the tenant is left untouched.
descope_client.mgmt.tenant.patch_tenant(
id="my-custom-id",
disabled=True,
)

# Managing the tenant's settings
# Getting the settings
descope_client.mgmt.tenant.load_settings(id="my-custom-id")
Expand Down
31 changes: 31 additions & 0 deletions descope/management/_tenant_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,34 @@ def _compose_create_update_body(
if federated_app_ids is not None:
body["federatedAppIds"] = federated_app_ids
return body

@staticmethod
def _compose_patch_body(
id: str,
name: Optional[str] = None,
self_provisioning_domains: Optional[List[str]] = None,
custom_attributes: Optional[dict] = None,
disabled: Optional[bool] = None,
enforce_sso: Optional[bool] = None,
enforce_sso_exclusions: Optional[List[str]] = None,
federated_app_ids: Optional[List[str]] = None,
role_inheritance: Optional[str] = None,
) -> dict:
body: dict[str, Any] = {"id": id}
if name is not None:
body["name"] = name
if self_provisioning_domains is not None:
body["selfProvisioningDomains"] = self_provisioning_domains
if custom_attributes is not None:
body["customAttributes"] = custom_attributes
if disabled is not None:
body["disabled"] = disabled
if enforce_sso is not None:
body["enforceSSO"] = enforce_sso
if enforce_sso_exclusions is not None:
body["enforceSSOExclusions"] = enforce_sso_exclusions
if federated_app_ids is not None:
body["federatedAppIds"] = federated_app_ids
if role_inheritance is not None:
body["roleInheritance"] = role_inheritance
return body
1 change: 1 addition & 0 deletions descope/management/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class MgmtV1:
# tenant
tenant_create_path = "/v1/mgmt/tenant/create"
tenant_update_path = "/v1/mgmt/tenant/update"
tenant_patch_path = "/v1/mgmt/tenant/patch"
tenant_delete_path = "/v1/mgmt/tenant/delete"
tenant_load_path = "/v1/mgmt/tenant"
tenant_settings_path = "/v1/mgmt/tenant/settings"
Expand Down
45 changes: 45 additions & 0 deletions descope/management/tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,51 @@ def update(
),
)

def patch_tenant(
self,
id: str,
name: Optional[str] = None,
self_provisioning_domains: Optional[List[str]] = None,
custom_attributes: Optional[dict] = None,
disabled: Optional[bool] = None,
enforce_sso: Optional[bool] = None,
enforce_sso_exclusions: Optional[List[str]] = None,
federated_app_ids: Optional[List[str]] = None,
role_inheritance: Optional[str] = None,
) -> None:
"""
Patch an existing tenant. Only the given fields will be updated; omitted fields are left unchanged.

Args:
id (str): The ID of the tenant to patch.
name (str): Optional updated tenant name.
self_provisioning_domains (List[str]): Optional list of domains associated with this tenant.
Users authenticating from these domains will be associated with this tenant.
custom_attributes (dict): Optional, set the different custom attributes values of the keys that were previously configured in Descope console app
disabled (bool): Optional, login to the tenant will be disabled
enforce_sso (bool): Optional, login to the tenant is possible only using the configured sso
enforce_sso_exclusions (List[str]): Optional, list of user IDs excluded from SSO enforcement
federated_app_ids (List[str]): Optional, list of federated application IDs
role_inheritance (str): Optional, role inheritance setting for the tenant

Raise:
AuthException: raised if patch operation fails
"""
self._http.patch(
MgmtV1.tenant_patch_path,
body=TenantBase._compose_patch_body(
id,
name,
self_provisioning_domains,
custom_attributes,
disabled,
enforce_sso,
enforce_sso_exclusions,
federated_app_ids,
role_inheritance,
),
)

def update_settings(
self,
id: str,
Expand Down
45 changes: 45 additions & 0 deletions descope/management/tenant_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,51 @@ async def update(
),
)

async def patch_tenant(
self,
id: str,
name: Optional[str] = None,
self_provisioning_domains: Optional[List[str]] = None,
custom_attributes: Optional[dict] = None,
disabled: Optional[bool] = None,
enforce_sso: Optional[bool] = None,
enforce_sso_exclusions: Optional[List[str]] = None,
federated_app_ids: Optional[List[str]] = None,
role_inheritance: Optional[str] = None,
) -> None:
"""
Patch an existing tenant. Only the given fields will be updated; omitted fields are left unchanged.

Args:
id (str): The ID of the tenant to patch.
name (str): Optional updated tenant name.
self_provisioning_domains (List[str]): Optional list of domains associated with this tenant.
Users authenticating from these domains will be associated with this tenant.
custom_attributes (dict): Optional, set the different custom attributes values of the keys that were previously configured in Descope console app
disabled (bool): Optional, login to the tenant will be disabled
enforce_sso (bool): Optional, login to the tenant is possible only using the configured sso
enforce_sso_exclusions (List[str]): Optional, list of user IDs excluded from SSO enforcement
federated_app_ids (List[str]): Optional, list of federated application IDs
role_inheritance (str): Optional, role inheritance setting for the tenant

Raise:
AuthException: raised if patch operation fails
"""
await self._http.patch(
MgmtV1.tenant_patch_path,
body=TenantBase._compose_patch_body(
id,
name,
self_provisioning_domains,
custom_attributes,
disabled,
enforce_sso,
enforce_sso_exclusions,
federated_app_ids,
role_inheritance,
),
)

async def update_settings(
self,
id: str,
Expand Down
58 changes: 58 additions & 0 deletions tests/management/test_tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,64 @@ async def test_update(self, client_factory):
follow_redirects=False,
)

async def test_patch_tenant(self, client_factory):
client = client_factory.make(PROJECT_ID, PUBLIC_KEY_DICT, False, "key")

# Test failed flow
with client.mock_mgmt_patch(make_response(status=500)) as _:
with pytest.raises(AuthException):
await client.invoke(client.mgmt.tenant.patch_tenant("valid-id"))

# Test success flow with id only
with client.mock_mgmt_patch(make_response()) as mock_patch:
result = await client.invoke(client.mgmt.tenant.patch_tenant("t1"))
assert result is None
assert_http_called(
mock_patch,
client.mode,
f"{DEFAULT_BASE_URL}{MgmtV1.tenant_patch_path}",
headers=MGMT_HEADERS,
params=None,
json={"id": "t1"},
follow_redirects=False,
)

# Test success flow with all fields
with client.mock_mgmt_patch(make_response()) as mock_patch:
result = await client.invoke(
client.mgmt.tenant.patch_tenant(
"t1",
name="new-name",
self_provisioning_domains=["domain.com"],
custom_attributes={"k1": "v1"},
disabled=True,
enforce_sso=True,
enforce_sso_exclusions=["user1", "user2"],
federated_app_ids=["app1", "app2"],
role_inheritance="tenant2",
)
)
assert result is None
assert_http_called(
mock_patch,
client.mode,
f"{DEFAULT_BASE_URL}{MgmtV1.tenant_patch_path}",
headers=MGMT_HEADERS,
params=None,
json={
"id": "t1",
"name": "new-name",
"selfProvisioningDomains": ["domain.com"],
"customAttributes": {"k1": "v1"},
"disabled": True,
"enforceSSO": True,
"enforceSSOExclusions": ["user1", "user2"],
"federatedAppIds": ["app1", "app2"],
"roleInheritance": "tenant2",
},
follow_redirects=False,
)

async def test_delete(self, client_factory):
client = client_factory.make(PROJECT_ID, PUBLIC_KEY_DICT, False, "key")

Expand Down
Loading