Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to impersonate on management sdk #364

Merged
merged 1 commit into from
Feb 21, 2024
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
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,12 @@ These sections show how to use the SDK to perform permission and user management
7. [Query SSO Groups](#query-sso-groups)
8. [Manage Flows](#manage-flows-and-theme)
9. [Manage JWTs](#manage-jwts)
10. [Embedded links](#embedded-links)
11. [Search Audit](#search-audit)
12. [Manage ReBAC Authz](#manage-rebac-authz)
13. [Manage Project](#manage-project)
14. [Manage SSO Applications](#manage-sso-applications)
10. [Impersonate](#impersonate)
12. [Embedded links](#embedded-links)
13. [Search Audit](#search-audit)
14. [Manage ReBAC Authz](#manage-rebac-authz)
15. [Manage Project](#manage-project)
16. [Manage SSO Applications](#manage-sso-applications)

If you wish to run any of our code samples and play with them, check out our [Code Examples](#code-examples) section.

Expand Down Expand Up @@ -668,7 +669,7 @@ users_history_resp = descope_client.mgmt.user.history(["user-id-1", "user-id-2"]

#### Set or Expire User Password

You can set a new active password for a user that they can sign in with.
You can set a new active password for a user that they can sign in with.
You can also set a temporary password that the user will be forced to change on the next login.
For a user that already has an active password, you can expire their current password, effectively requiring them to change it on the next login.

Expand Down Expand Up @@ -1009,6 +1010,20 @@ updated_jwt = descope_client.mgmt.jwt.update_jwt(
)
```

### Impersonate

You can impersonate to another user
The impersonator user must have the `impersonation` permission in order for this request to work.
The response would be a refresh JWT of the impersonated user

```python
refresh_jwt = descope_client.mgmt.jwt.impersonate(
impersonator_id="<Login ID impersonator>",
login_id="<Login ID of impersonated person>",
validate_consent=True
)
```

# Note 1: The generate code/link functions, work only for test users, will not work for regular users.

# Note 2: In case of testing sign-in / sign-up operations with test users, need to make sure to generate the code prior calling the sign-in / sign-up operations.
Expand Down
1 change: 1 addition & 0 deletions descope/management/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class MgmtV1:

# jwt
update_jwt_path = "/v1/mgmt/jwt/update"
impersonate_path = "/v1/mgmt/impersonate"

# permission
permission_create_path = "/v1/mgmt/permission/create"
Expand Down
35 changes: 35 additions & 0 deletions descope/management/jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,38 @@ def update_jwt(self, jwt: str, custom_claims: dict) -> str:
pswd=self._auth.management_key,
)
return response.json().get("jwt", "")

def impersonate(
self, impersonator_id: str, login_id: str, validate_consent: bool
) -> str:
"""
Impersonate to another user

Args:
impersonator_id (str): login id / user id of impersonator, must have "impersonation" permission.
login_id (str): login id of the user whom to which to impersonate to.
validate_consent (bool): Indicate whether to allow impersonation in any case or only if a consent to this operation was granted.

Return value (str): A JWT of the impersonated user

Raise:
AuthException: raised if update failed
"""
if not impersonator_id:
raise AuthException(
400, ERROR_TYPE_INVALID_ARGUMENT, "impersonator_id cannot be empty"
)
if not login_id:
raise AuthException(
400, ERROR_TYPE_INVALID_ARGUMENT, "login_id cannot be empty"
)
response = self._auth.do_post(
MgmtV1.impersonate_path,
{
"loginId": login_id,
"impersonatorId": impersonator_id,
"validateConsent": validate_consent,
},
pswd=self._auth.management_key,
)
return response.json().get("jwt", "")
49 changes: 49 additions & 0 deletions tests/management/test_jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,52 @@ def test_update_jwt(self):
params=None,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_impersonate(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.jwt.impersonate, "imp1", "imp2", False
)

self.assertRaises(
AuthException, client.mgmt.jwt.impersonate, "", "imp2", False
)

self.assertRaises(
AuthException, client.mgmt.jwt.impersonate, "imp1", "", False
)

# Test success flow
with patch("requests.post") as mock_post:
network_resp = mock.Mock()
network_resp.ok = True
network_resp.json.return_value = json.loads("""{"jwt": "response"}""")
mock_post.return_value = network_resp
resp = client.mgmt.jwt.impersonate("imp1", "imp2", True)
self.assertEqual(resp, "response")
expected_uri = f"{common.DEFAULT_BASE_URL}{MgmtV1.impersonate_path}"
mock_post.assert_called_with(
expected_uri,
headers={
**common.default_headers,
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
},
json={
"loginId": "imp2",
"impersonatorId": "imp1",
"validateConsent": True,
},
allow_redirects=False,
verify=True,
params=None,
timeout=DEFAULT_TIMEOUT_SECONDS,
)
Loading