From 1f6a0af9c1ddf25cceea7dc837bfb3c276b71a34 Mon Sep 17 00:00:00 2001 From: Derek Higgins Date: Fri, 23 Nov 2012 09:17:24 +0000 Subject: [PATCH] Add command to allow users to change their own password Fixes Bug 1082539 The Equivalent of doing curl -X PATCH http://localhost:5000/v2.0/OS-KSCRUD/users/ \ -H "Content-type: application/json" \ -H "X_Auth_Token: " \ -d '{"user": {"password": "ABCD", "original_password": "DCBA"}}' Change-Id: Ia1a907c5fd138c4252196145b361f43671047a1a --- keystoneclient/base.py | 8 +++++--- keystoneclient/client.py | 11 +++++++++-- keystoneclient/v2_0/shell.py | 33 +++++++++++++++++++++++++++++++++ keystoneclient/v2_0/users.py | 12 ++++++++++++ tests/v2_0/test_users.py | 18 ++++++++++++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/keystoneclient/base.py b/keystoneclient/base.py index be8551f52..13a8bf1ed 100644 --- a/keystoneclient/base.py +++ b/keystoneclient/base.py @@ -93,15 +93,17 @@ class Manager(object): def _delete(self, url): resp, body = self.api.delete(url) - def _update(self, url, body=None, response_key=None, method="PUT"): + def _update(self, url, body=None, response_key=None, method="PUT", + management=True): methods = {"PUT": self.api.put, "POST": self.api.post, "PATCH": self.api.patch} try: if body is not None: - resp, body = methods[method](url, body=body) + resp, body = methods[method](url, body=body, + management=management) else: - resp, body = methods[method](url) + resp, body = methods[method](url, management=management) except KeyError: raise exceptions.ClientException("Invalid update method: %s" % method) diff --git a/keystoneclient/client.py b/keystoneclient/client.py index c15cdc157..de86258e9 100644 --- a/keystoneclient/client.py +++ b/keystoneclient/client.py @@ -187,14 +187,21 @@ class HTTPClient(httplib2.Http): concatenating self.management_url and url and passing in method and any associated kwargs. """ - if self.management_url is None: + is_management = kwargs.pop('management', True) + + if is_management and self.management_url is None: raise exceptions.AuthorizationFailure( 'Current authorization does not have a known management url') + + url_to_use = self.auth_url + if is_management: + url_to_use = self.management_url + kwargs.setdefault('headers', {}) if self.auth_token: kwargs['headers']['X-Auth-Token'] = self.auth_token - resp, body = self.request(self.management_url + url, method, + resp, body = self.request(url_to_use + url, method, **kwargs) return resp, body diff --git a/keystoneclient/v2_0/shell.py b/keystoneclient/v2_0/shell.py index a976134da..d13041d10 100755 --- a/keystoneclient/v2_0/shell.py +++ b/keystoneclient/v2_0/shell.py @@ -16,6 +16,7 @@ # under the License. import argparse +import getpass from keystoneclient.v2_0 import client from keystoneclient import utils @@ -108,6 +109,38 @@ def do_user_password_update(kc, args): kc.users.update_password(args.id, args.passwd) +@utils.arg('--current-password', metavar='', + dest='currentpasswd', required=False, help='Current password, ' + 'Defaults to the password as set by --os-password or ' + 'OS_PASSWORD') +@utils.arg('--new-password ', metavar='', dest='newpasswd', + required=False, help='Desired new password') +def do_password_update(kc, args): + """Update own password""" + + # we are prompting for these passwords if they are not passed in + # this gives users the option not to have their password + # appear in bash history etc.. + currentpasswd = args.os_password + if args.currentpasswd is not None: + currentpasswd = args.currentpasswd + if currentpasswd is None: + currentpasswd = getpass.getpass('Current Password: ') + + newpasswd = args.newpasswd + while newpasswd is None: + passwd1 = getpass.getpass('New Password: ') + passwd2 = getpass.getpass('Repeat New Password: ') + if passwd1 == passwd2: + newpasswd = passwd1 + + kc.users.update_own_password(currentpasswd, newpasswd) + + if args.os_password != newpasswd: + print "You should update the password you are using to authenticate "\ + "to match your new password" + + @utils.arg('id', metavar='', help='User ID to delete') def do_user_delete(kc, args): """Delete user""" diff --git a/keystoneclient/v2_0/users.py b/keystoneclient/v2_0/users.py index b334f96a8..97dc72f73 100644 --- a/keystoneclient/v2_0/users.py +++ b/keystoneclient/v2_0/users.py @@ -71,6 +71,18 @@ class UserManager(base.ManagerWithFind): return self._update("/users/%s/OS-KSADM/password" % base.getid(user), params, "user") + def update_own_password(self, origpasswd, passwd): + """ + Update password + """ + params = {"user": {"password": passwd, + "original_password": origpasswd}} + + return self._update("/OS-KSCRUD/users/%s" % self.api.user_id, params, + response_key="access", + method="PATCH", + management=False) + def update_tenant(self, user, tenant): """ Update default tenant. diff --git a/tests/v2_0/test_users.py b/tests/v2_0/test_users.py index 702aed78a..5dca0bec1 100644 --- a/tests/v2_0/test_users.py +++ b/tests/v2_0/test_users.py @@ -220,3 +220,21 @@ class UserTests(utils.TestCase): user = self.client.users.update_password(2, 'swordfish') user = self.client.users.update_tenant(2, 1) user = self.client.users.update_enabled(2, False) + + def test_update_own_password(self): + req_1 = {'user': {'password': 'ABCD', 'original_password': 'DCBA'}} + + resp_1 = {'access': {}} + resp_1 = httplib2.Response({'status': 200, 'body': json.dumps(resp_1)}) + + httplib2.Http.request(urlparse.urljoin(self.TEST_URL, + 'v2.0/OS-KSCRUD/users/123'), + 'PATCH', + body=json.dumps(req_1), + headers=self.TEST_POST_HEADERS) \ + .AndReturn((resp_1, resp_1['body'])) + + self.mox.ReplayAll() + + self.client.user_id = '123' + self.client.users.update_own_password('DCBA', 'ABCD')