From de017bd970dbf2a673473f6280d56f1c48bdbb03 Mon Sep 17 00:00:00 2001 From: adriant Date: Fri, 18 Dec 2015 11:24:07 +1300 Subject: [PATCH] Adding admin only endpoint to force password resets. * Different email templates for each type of password reset. * For now the force reset will be mainly for new users, hence "initial_password" template. Change-Id: Ie0fecacfed7d767727bd2729fca888a45467a43d --- conf/conf.yaml | 14 ++++++ stacktask/api/v1/openstack.py | 22 ++++++++- .../api/v1/templates/initial_password.txt | 6 +++ stacktask/api/v1/templates/password_reset.txt | 7 +++ stacktask/api/v1/tests/test_api_openstack.py | 46 +++++++++++++++++++ stacktask/api/v1/urls.py | 5 +- stacktask/test_settings.py | 21 ++++++++- 7 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 stacktask/api/v1/templates/initial_password.txt create mode 100644 stacktask/api/v1/templates/password_reset.txt diff --git a/conf/conf.yaml b/conf/conf.yaml index f949508..13d96ac 100644 --- a/conf/conf.yaml +++ b/conf/conf.yaml @@ -109,6 +109,20 @@ TASK_SETTINGS: handle_duplicates: cancel emails: initial: null + token: + subject: Password Reset for OpenStack + reply: no-reply@example.com + template: password_reset.txt + html_template: password_reset.txt + force_password: + handle_duplicates: cancel + emails: + initial: null + token: + subject: Setup Your OpenStack Password + reply: no-reply@example.com + template: initial_password.txt + html_template: initial_password.txt edit_user: emails: initial: null diff --git a/stacktask/api/v1/openstack.py b/stacktask/api/v1/openstack.py index 00b91b7..758cc57 100644 --- a/stacktask/api/v1/openstack.py +++ b/stacktask/api/v1/openstack.py @@ -246,7 +246,7 @@ class RoleList(tasks.TaskView): return Response({'roles': managable_roles}) -class ResetPassword(tasks.ResetPassword): +class UserResetPassword(tasks.ResetPassword): """ The openstack forgot password endpoint. --- @@ -258,3 +258,23 @@ class ResetPassword(tasks.ResetPassword): This returns a 404. """ return Response(status=404) + + +class UserSetPassword(tasks.ResetPassword): + """ + The openstack endpoint to force a password reset. + --- + """ + + task_type = "force_password" + + def get(self, request): + """ + The ForcePassword endpoint does not support GET. + This returns a 404. + """ + return Response(status=404) + + @utils.admin + def post(self, request, format=None): + return super(UserSetPassword, self).post(request) diff --git a/stacktask/api/v1/templates/initial_password.txt b/stacktask/api/v1/templates/initial_password.txt new file mode 100644 index 0000000..33ae6c9 --- /dev/null +++ b/stacktask/api/v1/templates/initial_password.txt @@ -0,0 +1,6 @@ +Hello, + +You can setup your initial OpenStack user password by following this link: +{{ tokenurl }}{{ token }}/ + +Once setup you will have access to the web dashboard. diff --git a/stacktask/api/v1/templates/password_reset.txt b/stacktask/api/v1/templates/password_reset.txt new file mode 100644 index 0000000..24d2eec --- /dev/null +++ b/stacktask/api/v1/templates/password_reset.txt @@ -0,0 +1,7 @@ +Hello, + +Your password reset link is: +{{ tokenurl }}{{ token }}/ + +If you did not request this reset ignore this email. + diff --git a/stacktask/api/v1/tests/test_api_openstack.py b/stacktask/api/v1/tests/test_api_openstack.py index b9aaeaa..570ecc2 100644 --- a/stacktask/api/v1/tests/test_api_openstack.py +++ b/stacktask/api/v1/tests/test_api_openstack.py @@ -83,6 +83,7 @@ class OpenstackAPITests(APITestCase): 'user_id': "test_user_id", 'authenticated': True } + data = {'email': "test@example.com", 'roles': ["Member"], 'project_id': 'test_project_id'} response = self.client.post(url, data, format='json', headers=headers) @@ -104,3 +105,48 @@ class OpenstackAPITests(APITestCase): response = self.client.get(url, headers=headers) self.assertEqual(response.status_code, status.HTTP_200_OK) + + @mock.patch( + 'stacktask.actions.models.user_store.IdentityManager', FakeManager) + def test_force_reset_password(self): + """ + Ensure the force password endpoint works as expected, + and only for admin. + + Should also check if template can be rendered. + """ + + user = mock.Mock() + user.id = 'user_id' + user.username = "test@example.com" + user.email = "test@example.com" + user.password = "test_password" + + setup_temp_cache({}, {user.username: user}) + + headers = { + 'project_name': "test_project", + 'project_id': "test_project_id", + 'roles': "Member", + 'username': "test@example.com", + 'user_id': "test_user_id", + 'authenticated': True + } + + url = "/v1/openstack/users/password-set" + data = {'email': "test@example.com"} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + response = self.client.post(url, data, format='json', headers=headers) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + headers["roles"] = "admin,Member" + response = self.client.post(url, data, format='json', headers=headers) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data, None) + + new_token = Token.objects.all()[0] + url = "/v1/tokens/" + new_token.token + data = {'password': 'new_test_password'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(user.password, 'new_test_password') diff --git a/stacktask/api/v1/urls.py b/stacktask/api/v1/urls.py index 8e09b69..2b3f781 100644 --- a/stacktask/api/v1/urls.py +++ b/stacktask/api/v1/urls.py @@ -31,9 +31,12 @@ urlpatterns = [ openstack.UserRoles.as_view()), url(r'^openstack/users/(?P\w+)/?$', openstack.UserDetail.as_view()), + url(r'^openstack/users/password-reset?$', + openstack.UserResetPassword.as_view()), + url(r'^openstack/users/password-set?$', + openstack.UserSetPassword.as_view()), url(r'^openstack/users/?$', openstack.UserList.as_view()), url(r'^openstack/roles/?$', openstack.RoleList.as_view()), - url(r'^openstack/forgotpassword/?$', openstack.ResetPassword.as_view()), ] if settings.SHOW_ACTION_ENDPOINTS: diff --git a/stacktask/test_settings.py b/stacktask/test_settings.py index 7d4fa38..7151e42 100644 --- a/stacktask/test_settings.py +++ b/stacktask/test_settings.py @@ -108,7 +108,26 @@ TASK_SETTINGS = { ] }, 'reset_password': { - 'handle_duplicates': 'cancel' + 'handle_duplicates': 'cancel', + 'emails': { + 'token': { + 'reply': 'no-reply@example.com', + 'html_template': 'password_reset.txt', + 'template': 'password_reset.txt', + 'subject': 'Password Reset for OpenStack' + } + } + }, + 'force_password': { + 'handle_duplicates': 'cancel', + 'emails': { + 'token': { + 'reply': 'no-reply@example.com', + 'html_template': 'initial_password.txt', + 'template': 'initial_password.txt', + 'subject': 'Setup Your OpenStack Password' + } + } } }