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 @@ -1236,6 +1236,14 @@ groups_resp = descope_client.mgmt.group.load_all_groups(
tenant_id="tenant-id",
)

# Load only the groups that came from a specific SSO configuration (each returned group
# carries an "ssoId" field identifying its origin; use "default_ssoid" for the tenant's
# default SSO configuration)
groups_resp = descope_client.mgmt.group.load_all_groups(
tenant_id="tenant-id",
sso_id="sso-config-id",
)

# Load all groups for the given user IDs (can be found in the user's JWT)
groups_resp = descope_client.mgmt.group.load_all_groups_for_members(
tenant_id="tenant-id",
Expand Down
48 changes: 36 additions & 12 deletions descope/management/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@
def load_all_groups(
self,
tenant_id: str,
sso_id: Optional[str] = None,
) -> dict:
"""
Load all groups for a specific tenant id.

Args:
tenant_id (str): Tenant ID to load groups from.
sso_id (str): Optional SSO configuration id (ssoId) to load only groups that came from
that SSO configuration. Use the reserved id "default_ssoid" for the tenant's default
SSO configuration. When omitted, all the tenant's groups are returned.

Return value (dict):
Return dict in the format
Expand All @@ -22,6 +26,7 @@
"id": <group id>,
"display": <display name>,
"source": <"scim" or "jit">,
"ssoId": <sso configuration id>,
"members":[
{
"loginId": <loginId>,
Expand All @@ -36,11 +41,14 @@
Raise:
AuthException: raised if load operation fails
"""
body = {
"tenantId": tenant_id,
}
if sso_id is not None:
body["ssoId"] = sso_id
response = self._http.post(
MgmtV1.group_load_all_path,
body={
"tenantId": tenant_id,
},
body=body,
)
return response.json()

Expand All @@ -49,6 +57,7 @@
tenant_id: str,
user_ids: Optional[List[str]] = None,
login_ids: Optional[List[str]] = None,
sso_id: Optional[str] = None,
) -> dict:
"""
Load all groups for the provided user IDs or login IDs.
Expand All @@ -57,6 +66,9 @@
tenant_id (str): Tenant ID to load groups from.
user_ids (List[str]): Optional List of user IDs, with the format of "U2J5ES9S8TkvCgOvcrkpzUgVTEBM" (example), which can be found on the user's JWT.
login_ids (List[str]): Optional List of login IDs, how the users identify when logging in.
sso_id (str): Optional SSO configuration id (ssoId) to load only groups that came from
that SSO configuration. Use the reserved id "default_ssoid" for the tenant's default
SSO configuration. When omitted, all matching groups are returned.
Comment thread
dorsha marked this conversation as resolved.

Return value (dict):
Return dict in the format
Expand All @@ -65,6 +77,7 @@
"id": <group id>,
"display": <display name>,
"source": <"scim" or "jit">,
"ssoId": <sso configuration id>,
"members":[
{
"loginId": <loginId>,
Expand All @@ -82,27 +95,34 @@
user_ids = [] if user_ids is None else user_ids
login_ids = [] if login_ids is None else login_ids

body = {
"tenantId": tenant_id,
"loginIds": login_ids,
"userIds": user_ids,
}
if sso_id is not None:
body["ssoId"] = sso_id

Check warning on line 104 in descope/management/group.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
response = self._http.post(
MgmtV1.group_load_all_for_member_path,
body={
"tenantId": tenant_id,
"loginIds": login_ids,
"userIds": user_ids,
},
body=body,
)
return response.json()

def load_all_group_members(
self,
tenant_id: str,
group_id: str,
sso_id: Optional[str] = None,
) -> dict:
"""
Load all members of the provided group id.

Args:
tenant_id (str): Tenant ID to load groups from.
group_id (str): Group ID to load members for.
sso_id (str): Optional SSO configuration id (ssoId): return the group only if it came
from that SSO configuration. Use the reserved id "default_ssoid" for the tenant's
default SSO configuration.

Return value (dict):
Return dict in the format
Expand All @@ -111,6 +131,7 @@
"id": <group id>,
"display": <display name>,
"source": <"scim" or "jit">,
"ssoId": <sso configuration id>,
"members":[
{
"loginId": <loginId>,
Expand All @@ -125,11 +146,14 @@
Raise:
AuthException: raised if load operation fails
"""
body = {
"tenantId": tenant_id,
"groupId": group_id,
}
if sso_id is not None:
body["ssoId"] = sso_id

Check warning on line 154 in descope/management/group.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
response = self._http.post(
MgmtV1.group_load_all_group_members_path,
body={
"tenantId": tenant_id,
"groupId": group_id,
},
body=body,
)
return response.json()
48 changes: 36 additions & 12 deletions descope/management/group_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
async def load_all_groups(
self,
tenant_id: str,
sso_id: Optional[str] = None,
) -> dict:
"""
Load all groups for a specific tenant id.

Args:
tenant_id (str): Tenant ID to load groups from.
sso_id (str): Optional SSO configuration id (ssoId) to load only groups that came from
that SSO configuration. Use the reserved id "default_ssoid" for the tenant's default
SSO configuration. When omitted, all the tenant's groups are returned.

Return value (dict):
Return dict in the format
Expand All @@ -26,6 +30,7 @@
"id": <group id>,
"display": <display name>,
"source": <"scim" or "jit">,
"ssoId": <sso configuration id>,
"members":[
{
"loginId": <loginId>,
Expand All @@ -40,11 +45,14 @@
Raise:
AuthException: raised if load operation fails
"""
body = {
"tenantId": tenant_id,
}
if sso_id is not None:
body["ssoId"] = sso_id
response = await self._http.post(
MgmtV1.group_load_all_path,
body={
"tenantId": tenant_id,
},
body=body,
)
return response.json()

Expand All @@ -53,6 +61,7 @@
tenant_id: str,
user_ids: Optional[List[str]] = None,
login_ids: Optional[List[str]] = None,
sso_id: Optional[str] = None,
) -> dict:
"""
Load all groups for the provided user IDs or login IDs.
Expand All @@ -61,6 +70,9 @@
tenant_id (str): Tenant ID to load groups from.
user_ids (List[str]): Optional List of user IDs, with the format of "U2J5ES9S8TkvCgOvcrkpzUgVTEBM" (example), which can be found on the user's JWT.
login_ids (List[str]): Optional List of login IDs, how the users identify when logging in.
sso_id (str): Optional SSO configuration id (ssoId) to load only groups that came from
that SSO configuration. Use the reserved id "default_ssoid" for the tenant's default
SSO configuration. When omitted, all matching groups are returned.

Return value (dict):
Return dict in the format
Expand All @@ -69,6 +81,7 @@
"id": <group id>,
"display": <display name>,
"source": <"scim" or "jit">,
"ssoId": <sso configuration id>,
"members":[
{
"loginId": <loginId>,
Expand All @@ -86,27 +99,34 @@
user_ids = [] if user_ids is None else user_ids
login_ids = [] if login_ids is None else login_ids

body = {
"tenantId": tenant_id,
"loginIds": login_ids,
"userIds": user_ids,
}
if sso_id is not None:
body["ssoId"] = sso_id

Check warning on line 108 in descope/management/group_async.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
response = await self._http.post(
MgmtV1.group_load_all_for_member_path,
body={
"tenantId": tenant_id,
"loginIds": login_ids,
"userIds": user_ids,
},
body=body,
)
return response.json()

async def load_all_group_members(
self,
tenant_id: str,
group_id: str,
sso_id: Optional[str] = None,
) -> dict:
"""
Load all members of the provided group id.

Args:
tenant_id (str): Tenant ID to load groups from.
group_id (str): Group ID to load members for.
sso_id (str): Optional SSO configuration id (ssoId): return the group only if it came
from that SSO configuration. Use the reserved id "default_ssoid" for the tenant's
default SSO configuration.

Return value (dict):
Return dict in the format
Expand All @@ -115,6 +135,7 @@
"id": <group id>,
"display": <display name>,
"source": <"scim" or "jit">,
"ssoId": <sso configuration id>,
"members":[
{
"loginId": <loginId>,
Expand All @@ -129,11 +150,14 @@
Raise:
AuthException: raised if load operation fails
"""
body = {
"tenantId": tenant_id,
"groupId": group_id,
}
if sso_id is not None:
body["ssoId"] = sso_id

Check warning on line 158 in descope/management/group_async.py

View workflow job for this annotation

GitHub Actions / Coverage

This line has no coverage
response = await self._http.post(
MgmtV1.group_load_all_group_members_path,
body={
"tenantId": tenant_id,
"groupId": group_id,
},
body=body,
)
return response.json()
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ dev = [
types = [
# mypy 1.12+ requires Python 3.10+; on 3.9 we stay on the last 1.11.x line.
# mypy is only run in the lint job (Python 3.13) so 3.9 never installs it in CI.
# The `<1.12` upper bound on the 3.9 entry is REQUIRED — without it renovate
# bumps the pin past 1.12 (e.g. to 2.x) and produces an unsatisfiable lock. Keep it.
"mypy>=1.20.1; python_version >= '3.10'",
"mypy==2.3.0; python_version < '3.10'",
"mypy>=1.11.2,<1.12; python_version < '3.10'",
]
tests = [
# pytest 9 requires Python 3.10+; on 3.9 we stay on the last 8.x line.
Expand Down
27 changes: 27 additions & 0 deletions tests/management/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,33 @@ async def test_load_all_groups(self, client_factory):
follow_redirects=False,
)

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

# sso_id scopes the load to one SSO configuration; it is sent only when provided
# (the exact-match json assertion in test_load_all_groups proves it is absent otherwise)
with client.mock_mgmt_post(make_response({})) as mock_post:
assert (
await client.invoke(client.mgmt.group.load_all_groups("someTenantId", sso_id="sso-config-1"))
is not None
)
assert_http_called(
mock_post,
client.mode,
f"{DEFAULT_BASE_URL}{MgmtV1.group_load_all_path}",
headers={
**default_headers,
"Authorization": f"Bearer {PROJECT_ID}:key",
"x-descope-project-id": PROJECT_ID,
},
params=None,
json={
"tenantId": "someTenantId",
"ssoId": "sso-config-1",
},
follow_redirects=False,
)

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

Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading