Skip to content

Commit dd9dda6

Browse files
authored
Add set and expire password (#197)
* add set and expire password * readme * fix readme * rename set password arguments * CR fixes * CR fixes * improve readme * improve readme * improve readme
1 parent dd6c639 commit dd9dda6

File tree

4 files changed

+137
-1
lines changed

4 files changed

+137
-1
lines changed

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ tenants = tenants_resp["tenants"]
445445

446446
### Manage Users
447447

448-
You can create, update, delete or load users, as well as search according to filters:
448+
You can create, update, delete or load users, as well as setting new password, expire password and search according to filters:
449449

450450
```Python
451451
# A user must have a login ID, other fields are optional.
@@ -513,6 +513,20 @@ users = users_resp["users"]
513513
# Do something
514514
```
515515

516+
#### Set or Expire User Password
517+
518+
You can set or expire a user's password.
519+
Note: When setting a password, it will automatically be set as expired.
520+
The user will not be able log-in using an expired password, and will be required replace it on next login.
521+
522+
```Python
523+
// Set a user's password
524+
descope_client.mgmt.user.setPassword('<login-id>', '<some-password>');
525+
526+
// Or alternatively, expire a user password
527+
descope_client.mgmt.user.expirePassword('<login-id>');
528+
```
529+
516530
### Manage Access Keys
517531

518532
You can create, update, delete or load access keys, as well as search according to filters:

descope/management/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class MgmtV1:
2323
user_update_custom_attribute_path = "/v1/mgmt/user/update/customAttribute"
2424
user_add_role_path = "/v1/mgmt/user/update/role/add"
2525
user_remove_role_path = "/v1/mgmt/user/update/role/remove"
26+
user_set_password_path = "/v1/mgmt/user/password/set"
27+
user_expire_password_path = "/v1/mgmt/user/password/expire"
2628
user_add_tenant_path = "/v1/mgmt/user/update/tenant/add"
2729
user_remove_tenant_path = "/v1/mgmt/user/update/tenant/remove"
2830
user_generate_otp_for_test_path = "/v1/mgmt/tests/generate/otp"

descope/management/user.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,53 @@ def remove_tenant_roles(
719719
)
720720
return response.json()
721721

722+
def set_password(
723+
self,
724+
login_id: str,
725+
password: str,
726+
) -> None:
727+
"""
728+
Set the password for the given login ID.
729+
Note: The password will automatically be set as expired.
730+
The user will not be able to log-in with this password, and will be required to replace it on next login.
731+
See also: expire_password
732+
733+
Args:
734+
login_id (str): The login ID of the user to set the password to.
735+
password (str): The new password to set to the user.
736+
737+
Raise:
738+
AuthException: raised if the operation fails
739+
"""
740+
self._auth.do_post(
741+
MgmtV1.user_set_password_path,
742+
{"loginId": login_id, "password": password},
743+
pswd=self._auth.management_key,
744+
)
745+
return
746+
747+
def expire_password(
748+
self,
749+
login_id: str,
750+
) -> None:
751+
"""
752+
Expires the password for the given login ID.
753+
Note: user sign-in with an expired password, the user will get an error with code.
754+
Use the `password.send_reset` or `password.replace` methods to reset/replace the password.
755+
756+
Args:
757+
login_id (str): The login ID of the user expire the password to.
758+
759+
Raise:
760+
AuthException: raised if the operation fails
761+
"""
762+
self._auth.do_post(
763+
MgmtV1.user_expire_password_path,
764+
{"loginId": login_id},
765+
pswd=self._auth.management_key,
766+
)
767+
return
768+
722769
def generate_otp_for_test_user(
723770
self,
724771
method: DeliveryMethod,

tests/management/test_user.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,79 @@ def test_generate_otp_for_test_user(self):
10141014
timeout=DEFAULT_TIMEOUT_SECONDS,
10151015
)
10161016

1017+
def test_user_set_password(self):
1018+
# Test failed flows
1019+
with patch("requests.post") as mock_post:
1020+
mock_post.return_value.ok = False
1021+
self.assertRaises(
1022+
AuthException,
1023+
self.client.mgmt.user.set_password,
1024+
"login-id",
1025+
"some-password",
1026+
)
1027+
1028+
# Test success flow
1029+
with patch("requests.post") as mock_post:
1030+
network_resp = mock.Mock()
1031+
network_resp.ok = True
1032+
mock_post.return_value = network_resp
1033+
self.client.mgmt.user.set_password(
1034+
"login-id",
1035+
"some-password",
1036+
)
1037+
mock_post.assert_called_with(
1038+
f"{common.DEFAULT_BASE_URL}{MgmtV1.user_set_password_path}",
1039+
headers={
1040+
**common.default_headers,
1041+
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
1042+
},
1043+
params=None,
1044+
data=json.dumps(
1045+
{
1046+
"loginId": "login-id",
1047+
"password": "some-password",
1048+
}
1049+
),
1050+
allow_redirects=False,
1051+
verify=True,
1052+
timeout=DEFAULT_TIMEOUT_SECONDS,
1053+
)
1054+
1055+
def test_user_expire_password(self):
1056+
# Test failed flows
1057+
with patch("requests.post") as mock_post:
1058+
mock_post.return_value.ok = False
1059+
self.assertRaises(
1060+
AuthException,
1061+
self.client.mgmt.user.expire_password,
1062+
"login-id",
1063+
)
1064+
1065+
# Test success flow
1066+
with patch("requests.post") as mock_post:
1067+
network_resp = mock.Mock()
1068+
network_resp.ok = True
1069+
mock_post.return_value = network_resp
1070+
self.client.mgmt.user.expire_password(
1071+
"login-id",
1072+
)
1073+
mock_post.assert_called_with(
1074+
f"{common.DEFAULT_BASE_URL}{MgmtV1.user_expire_password_path}",
1075+
headers={
1076+
**common.default_headers,
1077+
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
1078+
},
1079+
params=None,
1080+
data=json.dumps(
1081+
{
1082+
"loginId": "login-id",
1083+
}
1084+
),
1085+
allow_redirects=False,
1086+
verify=True,
1087+
timeout=DEFAULT_TIMEOUT_SECONDS,
1088+
)
1089+
10171090
def test_generate_magic_link_for_test_user(self):
10181091
# Test failed flows
10191092
with patch("requests.post") as mock_post:

0 commit comments

Comments
 (0)