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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ These sections show how to use the SDK to perform various authentication/authori
10. [Tenant selection](#tenant-selection)
11. [Logging Out](#logging-out)
12. [History](#history)
13. [My Tenants](#my-tenants)

## API Managment Function

Expand Down Expand Up @@ -482,6 +483,19 @@ for user_history in users_history_resp:
# Do something
```

### My Tenants

You can get the current session user tenants.
The request requires a valid refresh token.
And either a boolean to receive the current selected tenant
Or a list of tenant IDs that this user is part of

```python
tenants_resp = descope_client.my_tenants(refresh_token, False, ["tenant_id"])
for tenant in tenants_resp.tenants:
# Do something
```

## Management API

It is very common for some form of management or automation to be required. These can be performed
Expand Down
1 change: 1 addition & 0 deletions descope/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class EndpointsV1:
logout_path = "/v1/auth/logout"
logout_all_path = "/v1/auth/logoutall"
me_path = "/v1/auth/me"
my_tenants_path = "/v1/auth/me/tenants"
history_path = "/v1/auth/me/history"

# accesskey
Expand Down
47 changes: 47 additions & 0 deletions descope/descope_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,53 @@ def me(self, refresh_token: str) -> dict:
)
return response.json()

def my_tenants(
self,
refresh_token: str,
dct: bool = False,
ids: list[str] | None = None,
) -> dict:
"""
Retrieve tenant attributes that user belongs to, one of dct/ids must be populated .

Args:
dct (bool): Get only the selected tenant from jwt
ids (List[str]): Get the list of tenants
refresh_token (str): The refresh token

Return value (dict): returns the tenant requested from the server
(id:str, name:str, customAttributes:dict)

Raise:
AuthException: Exception is raised if session is not authorized or another error occurs
"""
if refresh_token is None:
raise AuthException(
400,
ERROR_TYPE_INVALID_ARGUMENT,
f"signed refresh token {refresh_token} is empty",
)
if dct is True and ids is not None and len(ids) > 0:
raise AuthException(
400,
ERROR_TYPE_INVALID_ARGUMENT,
"Only one of 'dct' or 'ids' should be supplied",
)
if dct is False and (ids is None or len(ids) == 0):
raise AuthException(
400,
ERROR_TYPE_INVALID_ARGUMENT,
"Only one of 'dct' or 'ids' should be supplied",
)

body: dict[str, bool | list[str]] = {"dct": dct}
if ids is not None:
body["ids"] = ids

uri = EndpointsV1.my_tenants_path
response = self._auth.do_post(uri, body, None, refresh_token)
return response.json()

def history(self, refresh_token: str) -> list[dict]:
"""
Retrieve user authentication history for the refresh token
Expand Down
44 changes: 44 additions & 0 deletions tests/test_descope_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,50 @@ def test_me(self):
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_my_tenants(self):
dummy_refresh_token = ""
client = DescopeClient(self.dummy_project_id, self.public_key_dict)

self.assertRaises(AuthException, client.my_tenants, None)
self.assertRaises(AuthException, client.my_tenants, dummy_refresh_token)
self.assertRaises(
AuthException, client.my_tenants, dummy_refresh_token, True, ["a"]
)

# Test failed flow
with patch("requests.post") as mock_get:
mock_get.return_value.ok = False
self.assertRaises(
AuthException, client.my_tenants, dummy_refresh_token, True
)

# Test success flow
with patch("requests.post") as mock_post:
my_mock_response = mock.Mock()
my_mock_response.ok = True
data = json.loads(
"""{"tenants": [{"id": "tenant_id", "name": "tenant_name"}]}"""
)
my_mock_response.json.return_value = data
mock_post.return_value = my_mock_response
tenant_response = client.my_tenants(dummy_refresh_token, False, ["a"])
self.assertIsNotNone(tenant_response)
self.assertEqual(
data["tenants"][0]["name"], tenant_response["tenants"][0]["name"]
)
mock_post.assert_called_with(
f"{common.DEFAULT_BASE_URL}{EndpointsV1.my_tenants_path}",
headers={
**common.default_headers,
"Authorization": f"Bearer {self.dummy_project_id}",
},
json={"dct": False, "ids": ["a"]},
allow_redirects=False,
verify=True,
params=None,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_history(self):
dummy_refresh_token = ""
client = DescopeClient(self.dummy_project_id, self.public_key_dict)
Expand Down