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 password expiration flag #361

Merged
merged 9 commits 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
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -668,15 +668,19 @@ users_history_resp = descope_client.mgmt.user.history(["user-id-1", "user-id-2"]

#### Set or Expire User Password

You can set or expire a user's password.
Note: When setting a password, it will automatically be set as expired.
The user will not be able log-in using an expired password, and will be required replace it on next login.
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.

```Python
// Set a user's password
descope_client.mgmt.user.setPassword('<login-id>', '<some-password>');

// Or alternatively, expire a user password
# Set a user's temporary password
descope_client.mgmt.user.set_temporary_password('<login-id>', '<some-password>');

# Set a user's password
descope_client.mgmt.user.set_active_password('<login-id>', '<some-password>');

# Or alternatively, expire a user password
descope_client.mgmt.user.expirePassword('<login-id>');
```

Expand Down
4 changes: 3 additions & 1 deletion descope/management/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ class MgmtV1:
user_add_sso_apps = "/v1/mgmt/user/update/ssoapp/add"
user_set_sso_apps = "/v1/mgmt/user/update/ssoapp/set"
user_remove_sso_apps = "/v1/mgmt/user/update/ssoapp/remove"
user_set_password_path = "/v1/mgmt/user/password/set"
user_set_password_path = "/v1/mgmt/user/password/set" # Deprecated
user_set_temporary_password_path = "/v1/mgmt/user/password/set/temporary"
user_set_active_password_path = "/v1/mgmt/user/password/set/active"
user_expire_password_path = "/v1/mgmt/user/password/expire"
user_remove_all_passkeys_path = "/v1/mgmt/user/passkeys/delete"
user_add_tenant_path = "/v1/mgmt/user/update/tenant/add"
Expand Down
66 changes: 64 additions & 2 deletions descope/management/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,27 +1167,89 @@ def remove_tenant_roles(
)
return response.json()

def set_temporary_password(
self,
login_id: str,
password: str,
) -> None:
"""
Set the temporary password for the given login ID.
Note: The password will automatically be set as expired.
The user will not be able to log-in with this password, and will be required to replace it on next login.
See also: expire_password

Args:
login_id (str): The login ID of the user to set the password to.
password (str): The new password to set to the user.

Raise:
AuthException: raised if the operation fails
"""
self._auth.do_post(
MgmtV1.user_set_temporary_password_path,
{
"loginId": login_id,
"password": password,
"setActive": False,
},
pswd=self._auth.management_key,
)
return

def set_active_password(
self,
login_id: str,
password: str,
) -> None:
"""
Set the password for the given login ID.

Args:
login_id (str): The login ID of the user to set the password to.
password (str): The new password to set to the user.

Raise:
AuthException: raised if the operation fails
"""
self._auth.do_post(
MgmtV1.user_set_active_password_path,
{
"loginId": login_id,
"password": password,
"setActive": True,
},
pswd=self._auth.management_key,
)
return

# Deprecated (use set_temporary_password instead)
def set_password(
self,
login_id: str,
password: str,
set_active: Optional[bool] = False,
) -> None:
"""
Set the password for the given login ID.
Note: The password will automatically be set as expired.
Note: The password will automatically be set as expired unless the set_active flag will be set to True,
The user will not be able to log-in with this password, and will be required to replace it on next login.
See also: expire_password

Args:
login_id (str): The login ID of the user to set the password to.
password (str): The new password to set to the user.
set_active (bool): Keep the password active so it will not be expired on next log-in

Raise:
AuthException: raised if the operation fails
"""
self._auth.do_post(
MgmtV1.user_set_password_path,
{"loginId": login_id, "password": password},
{
"loginId": login_id,
"password": password,
"setActive": set_active,
},
pswd=self._auth.management_key,
)
return
Expand Down
75 changes: 75 additions & 0 deletions tests/management/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,80 @@ def test_generate_otp_for_test_user(self):
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_user_set_temporary_password(self):
# Test failed flows
with patch("requests.post") as mock_post:
mock_post.return_value.ok = False
self.assertRaises(
AuthException,
self.client.mgmt.user.set_temporary_password,
"login-id",
"some-password",
)

# Test success flow
with patch("requests.post") as mock_post:
network_resp = mock.Mock()
network_resp.ok = True
mock_post.return_value = network_resp
self.client.mgmt.user.set_temporary_password(
"login-id",
"some-password",
)
mock_post.assert_called_with(
f"{common.DEFAULT_BASE_URL}{MgmtV1.user_set_temporary_password_path}",
headers={
**common.default_headers,
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
},
params=None,
json={
"loginId": "login-id",
"password": "some-password",
"setActive": False,
},
allow_redirects=False,
verify=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_user_set_active_password(self):
# Test failed flows
with patch("requests.post") as mock_post:
mock_post.return_value.ok = False
self.assertRaises(
AuthException,
self.client.mgmt.user.set_active_password,
"login-id",
"some-password",
)

# Test success flow
with patch("requests.post") as mock_post:
network_resp = mock.Mock()
network_resp.ok = True
mock_post.return_value = network_resp
self.client.mgmt.user.set_active_password(
"login-id",
"some-password",
)
mock_post.assert_called_with(
f"{common.DEFAULT_BASE_URL}{MgmtV1.user_set_active_password_path}",
headers={
**common.default_headers,
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
},
params=None,
json={
"loginId": "login-id",
"password": "some-password",
"setActive": True,
},
allow_redirects=False,
verify=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

def test_user_set_password(self):
# Test failed flows
with patch("requests.post") as mock_post:
Expand Down Expand Up @@ -1675,6 +1749,7 @@ def test_user_set_password(self):
json={
"loginId": "login-id",
"password": "some-password",
"setActive": False,
},
allow_redirects=False,
verify=True,
Expand Down
Loading