Skip to content

Commit 22a582a

Browse files
Add password expiration flag (#361)
* Add password expiration flag * Change password flag name * Change password flag name * split to two different set password functions (temporary and active) * Adjust to new setPassword APIs * improve doc * improve doc * Update README.md --------- Co-authored-by: Gil Shapira <[email protected]>
1 parent 865a453 commit 22a582a

File tree

4 files changed

+152
-9
lines changed

4 files changed

+152
-9
lines changed

README.md

+10-6
Original file line numberDiff line numberDiff line change
@@ -668,15 +668,19 @@ users_history_resp = descope_client.mgmt.user.history(["user-id-1", "user-id-2"]
668668

669669
#### Set or Expire User Password
670670

671-
You can set or expire a user's password.
672-
Note: When setting a password, it will automatically be set as expired.
673-
The user will not be able log-in using an expired password, and will be required replace it on next login.
671+
You can set a new active password for a user that they can sign in with.
672+
You can also set a temporary password that the user will be forced to change on the next login.
673+
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.
674674

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

679-
// Or alternatively, expire a user password
677+
# Set a user's temporary password
678+
descope_client.mgmt.user.set_temporary_password('<login-id>', '<some-password>');
679+
680+
# Set a user's password
681+
descope_client.mgmt.user.set_active_password('<login-id>', '<some-password>');
682+
683+
# Or alternatively, expire a user password
680684
descope_client.mgmt.user.expirePassword('<login-id>');
681685
```
682686

descope/management/common.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ class MgmtV1:
4242
user_add_sso_apps = "/v1/mgmt/user/update/ssoapp/add"
4343
user_set_sso_apps = "/v1/mgmt/user/update/ssoapp/set"
4444
user_remove_sso_apps = "/v1/mgmt/user/update/ssoapp/remove"
45-
user_set_password_path = "/v1/mgmt/user/password/set"
45+
user_set_password_path = "/v1/mgmt/user/password/set" # Deprecated
46+
user_set_temporary_password_path = "/v1/mgmt/user/password/set/temporary"
47+
user_set_active_password_path = "/v1/mgmt/user/password/set/active"
4648
user_expire_password_path = "/v1/mgmt/user/password/expire"
4749
user_remove_all_passkeys_path = "/v1/mgmt/user/passkeys/delete"
4850
user_add_tenant_path = "/v1/mgmt/user/update/tenant/add"

descope/management/user.py

+64-2
Original file line numberDiff line numberDiff line change
@@ -1167,27 +1167,89 @@ def remove_tenant_roles(
11671167
)
11681168
return response.json()
11691169

1170+
def set_temporary_password(
1171+
self,
1172+
login_id: str,
1173+
password: str,
1174+
) -> None:
1175+
"""
1176+
Set the temporary password for the given login ID.
1177+
Note: The password will automatically be set as expired.
1178+
The user will not be able to log-in with this password, and will be required to replace it on next login.
1179+
See also: expire_password
1180+
1181+
Args:
1182+
login_id (str): The login ID of the user to set the password to.
1183+
password (str): The new password to set to the user.
1184+
1185+
Raise:
1186+
AuthException: raised if the operation fails
1187+
"""
1188+
self._auth.do_post(
1189+
MgmtV1.user_set_temporary_password_path,
1190+
{
1191+
"loginId": login_id,
1192+
"password": password,
1193+
"setActive": False,
1194+
},
1195+
pswd=self._auth.management_key,
1196+
)
1197+
return
1198+
1199+
def set_active_password(
1200+
self,
1201+
login_id: str,
1202+
password: str,
1203+
) -> None:
1204+
"""
1205+
Set the password for the given login ID.
1206+
1207+
Args:
1208+
login_id (str): The login ID of the user to set the password to.
1209+
password (str): The new password to set to the user.
1210+
1211+
Raise:
1212+
AuthException: raised if the operation fails
1213+
"""
1214+
self._auth.do_post(
1215+
MgmtV1.user_set_active_password_path,
1216+
{
1217+
"loginId": login_id,
1218+
"password": password,
1219+
"setActive": True,
1220+
},
1221+
pswd=self._auth.management_key,
1222+
)
1223+
return
1224+
1225+
# Deprecated (use set_temporary_password instead)
11701226
def set_password(
11711227
self,
11721228
login_id: str,
11731229
password: str,
1230+
set_active: Optional[bool] = False,
11741231
) -> None:
11751232
"""
11761233
Set the password for the given login ID.
1177-
Note: The password will automatically be set as expired.
1234+
Note: The password will automatically be set as expired unless the set_active flag will be set to True,
11781235
The user will not be able to log-in with this password, and will be required to replace it on next login.
11791236
See also: expire_password
11801237
11811238
Args:
11821239
login_id (str): The login ID of the user to set the password to.
11831240
password (str): The new password to set to the user.
1241+
set_active (bool): Keep the password active so it will not be expired on next log-in
11841242
11851243
Raise:
11861244
AuthException: raised if the operation fails
11871245
"""
11881246
self._auth.do_post(
11891247
MgmtV1.user_set_password_path,
1190-
{"loginId": login_id, "password": password},
1248+
{
1249+
"loginId": login_id,
1250+
"password": password,
1251+
"setActive": set_active,
1252+
},
11911253
pswd=self._auth.management_key,
11921254
)
11931255
return

tests/management/test_user.py

+75
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,80 @@ def test_generate_otp_for_test_user(self):
16451645
timeout=DEFAULT_TIMEOUT_SECONDS,
16461646
)
16471647

1648+
def test_user_set_temporary_password(self):
1649+
# Test failed flows
1650+
with patch("requests.post") as mock_post:
1651+
mock_post.return_value.ok = False
1652+
self.assertRaises(
1653+
AuthException,
1654+
self.client.mgmt.user.set_temporary_password,
1655+
"login-id",
1656+
"some-password",
1657+
)
1658+
1659+
# Test success flow
1660+
with patch("requests.post") as mock_post:
1661+
network_resp = mock.Mock()
1662+
network_resp.ok = True
1663+
mock_post.return_value = network_resp
1664+
self.client.mgmt.user.set_temporary_password(
1665+
"login-id",
1666+
"some-password",
1667+
)
1668+
mock_post.assert_called_with(
1669+
f"{common.DEFAULT_BASE_URL}{MgmtV1.user_set_temporary_password_path}",
1670+
headers={
1671+
**common.default_headers,
1672+
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
1673+
},
1674+
params=None,
1675+
json={
1676+
"loginId": "login-id",
1677+
"password": "some-password",
1678+
"setActive": False,
1679+
},
1680+
allow_redirects=False,
1681+
verify=True,
1682+
timeout=DEFAULT_TIMEOUT_SECONDS,
1683+
)
1684+
1685+
def test_user_set_active_password(self):
1686+
# Test failed flows
1687+
with patch("requests.post") as mock_post:
1688+
mock_post.return_value.ok = False
1689+
self.assertRaises(
1690+
AuthException,
1691+
self.client.mgmt.user.set_active_password,
1692+
"login-id",
1693+
"some-password",
1694+
)
1695+
1696+
# Test success flow
1697+
with patch("requests.post") as mock_post:
1698+
network_resp = mock.Mock()
1699+
network_resp.ok = True
1700+
mock_post.return_value = network_resp
1701+
self.client.mgmt.user.set_active_password(
1702+
"login-id",
1703+
"some-password",
1704+
)
1705+
mock_post.assert_called_with(
1706+
f"{common.DEFAULT_BASE_URL}{MgmtV1.user_set_active_password_path}",
1707+
headers={
1708+
**common.default_headers,
1709+
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
1710+
},
1711+
params=None,
1712+
json={
1713+
"loginId": "login-id",
1714+
"password": "some-password",
1715+
"setActive": True,
1716+
},
1717+
allow_redirects=False,
1718+
verify=True,
1719+
timeout=DEFAULT_TIMEOUT_SECONDS,
1720+
)
1721+
16481722
def test_user_set_password(self):
16491723
# Test failed flows
16501724
with patch("requests.post") as mock_post:
@@ -1675,6 +1749,7 @@ def test_user_set_password(self):
16751749
json={
16761750
"loginId": "login-id",
16771751
"password": "some-password",
1752+
"setActive": False,
16781753
},
16791754
allow_redirects=False,
16801755
verify=True,

0 commit comments

Comments
 (0)