Commit af299af1 by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/CLIENT-932: API - Enables editing of User data

parent 1e1c05c4
...@@ -143,7 +143,7 @@ class SessionsApiTests(TestCase): ...@@ -143,7 +143,7 @@ class SessionsApiTests(TestCase):
data = {'username': local_username, 'password': self.test_password} data = {'username': local_username, 'password': self.test_password}
response = self.do_post(self.base_sessions_uri, data) response = self.do_post(self.base_sessions_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
test_uri = self.base_users_uri + str(response.data['user']['id']) test_uri = self.base_sessions_uri + str(response.data['token'])
response = self.do_delete(test_uri) response = self.do_delete(test_uri)
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
response = self.do_get(test_uri) response = self.do_get(test_uri)
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import logging import logging
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login, logout
from django.contrib.auth import SESSION_KEY, BACKEND_SESSION_KEY, load_backend from django.contrib.auth import SESSION_KEY, BACKEND_SESSION_KEY, load_backend
from django.contrib.auth.models import AnonymousUser, User from django.contrib.auth.models import AnonymousUser, User
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
...@@ -151,4 +151,5 @@ class SessionsDetail(APIView): ...@@ -151,4 +151,5 @@ class SessionsDetail(APIView):
user_id = session[SESSION_KEY] user_id = session[SESSION_KEY]
AUDIT_LOG.info(u"API::User session terminated for user-id - {0}".format(user_id)) AUDIT_LOG.info(u"API::User session terminated for user-id - {0}".format(user_id))
session.flush() session.flush()
return Response(response_data, status=status.HTTP_204_NO_CONTENT) logout(request)
\ No newline at end of file return Response(response_data, status=status.HTTP_204_NO_CONTENT)
...@@ -63,7 +63,7 @@ class UserPasswordResetTest(TestCase): ...@@ -63,7 +63,7 @@ class UserPasswordResetTest(TestCase):
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
pass_reset_url, password='Test.Me64@', secure=True pass_reset_url, password='Test.Me64@', secure=True
) )
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 200)
#login successful after reset password #login successful after reset password
response = self._do_post_request(self.session_url, 'test2', 'Test.Me64@', secure=True) response = self._do_post_request(self.session_url, 'test2', 'Test.Me64@', secure=True)
...@@ -88,20 +88,17 @@ class UserPasswordResetTest(TestCase): ...@@ -88,20 +88,17 @@ class UserPasswordResetTest(TestCase):
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
pass_reset_url, password='Test.Me64#', secure=True pass_reset_url, password='Test.Me64#', secure=True
) )
message = 'Password Reset Successful' self._assert_response(response, status=200)
self._assert_response(response, status=201, message=message)
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
pass_reset_url, password='Test.Me64@', secure=True pass_reset_url, password='Test.Me64@', secure=True
) )
message = 'Password Reset Successful' self._assert_response(response, status=200)
self._assert_response(response, status=201, message=message)
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
pass_reset_url, password='Test.Me64^', secure=True pass_reset_url, password='Test.Me64^', secure=True
) )
message = 'Password Reset Successful' self._assert_response(response, status=200)
self._assert_response(response, status=201, message=message)
#now use previously used password #now use previously used password
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
...@@ -116,15 +113,13 @@ class UserPasswordResetTest(TestCase): ...@@ -116,15 +113,13 @@ class UserPasswordResetTest(TestCase):
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
pass_reset_url, password='Test.Me64&', secure=True pass_reset_url, password='Test.Me64&', secure=True
) )
message = 'Password Reset Successful' self._assert_response(response, status=200)
self._assert_response(response, status=201, message=message)
#now use previously used password #now use previously used password
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
pass_reset_url, password='Test.Me64!', secure=True pass_reset_url, password='Test.Me64!', secure=True
) )
message = 'Password Reset Successful' self._assert_response(response, status=200)
self._assert_response(response, status=201, message=message)
@override_settings(ADVANCED_SECURITY_CONFIG={'MIN_TIME_IN_DAYS_BETWEEN_ALLOWED_RESETS': 1}) @override_settings(ADVANCED_SECURITY_CONFIG={'MIN_TIME_IN_DAYS_BETWEEN_ALLOWED_RESETS': 1})
def test_is_password_reset_too_frequent(self): def test_is_password_reset_too_frequent(self):
...@@ -154,8 +149,7 @@ class UserPasswordResetTest(TestCase): ...@@ -154,8 +149,7 @@ class UserPasswordResetTest(TestCase):
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
pass_reset_url, password='NewP@ses34!', secure=True pass_reset_url, password='NewP@ses34!', secure=True
) )
message = 'Password Reset Successful' self._assert_response(response, status=200)
self._assert_response(response, status=201, message=message)
@override_settings(ADVANCED_SECURITY_CONFIG={'MIN_TIME_IN_DAYS_BETWEEN_ALLOWED_RESETS': 0}) @override_settings(ADVANCED_SECURITY_CONFIG={'MIN_TIME_IN_DAYS_BETWEEN_ALLOWED_RESETS': 0})
def test_password_reset_rate_limiting_unblock(self): def test_password_reset_rate_limiting_unblock(self):
...@@ -191,7 +185,7 @@ class UserPasswordResetTest(TestCase): ...@@ -191,7 +185,7 @@ class UserPasswordResetTest(TestCase):
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
pass_reset_url, password='Test.Me64@', secure=True pass_reset_url, password='Test.Me64@', secure=True
) )
self._assert_response(response, status=201) self._assert_response(response, status=200)
def _do_post_request(self, url, username, password, **kwargs): def _do_post_request(self, url, username, password, **kwargs):
""" """
......
...@@ -87,6 +87,15 @@ class UsersApiTests(TestCase): ...@@ -87,6 +87,15 @@ class UsersApiTests(TestCase):
self.assertEqual(response.data['first_name'], self.test_first_name) self.assertEqual(response.data['first_name'], self.test_first_name)
self.assertEqual(response.data['last_name'], self.test_last_name) self.assertEqual(response.data['last_name'], self.test_last_name)
def test_user_list_post_inactive(self):
test_uri = '/api/users'
local_username = self.test_username + str(randint(11, 99))
data = {'email': self.test_email, 'username': local_username, 'password': self.test_password, 'first_name': self.test_first_name, 'last_name': self.test_last_name, 'is_active': False }
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data['is_active'], False)
def test_user_list_post_duplicate(self): def test_user_list_post_duplicate(self):
test_uri = '/api/users' test_uri = '/api/users'
local_username = self.test_username + str(randint(11, 99)) local_username = self.test_username + str(randint(11, 99))
...@@ -112,24 +121,32 @@ class UsersApiTests(TestCase): ...@@ -112,24 +121,32 @@ class UsersApiTests(TestCase):
self.assertEqual(response.data['username'], local_username) self.assertEqual(response.data['username'], local_username)
self.assertEqual(response.data['first_name'], self.test_first_name) self.assertEqual(response.data['first_name'], self.test_first_name)
self.assertEqual(response.data['last_name'], self.test_last_name) self.assertEqual(response.data['last_name'], self.test_last_name)
self.assertEqual(response.data['is_active'], True)
self.assertEqual(len(response.data['resources']), 2) self.assertEqual(len(response.data['resources']), 2)
def test_user_detail_delete(self): def test_user_detail_get_undefined(self):
test_uri = '/api/users/123456789'
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404)
def test_user_detail_post(self):
test_uri = '/api/users' test_uri = '/api/users'
local_username = self.test_username + str(randint(11, 99)) local_username = self.test_username + str(randint(11, 99))
data = {'email': self.test_email, 'username': local_username, 'password': self.test_password, 'first_name': self.test_first_name, 'last_name': self.test_last_name} data = {'email': self.test_email, 'username': local_username, 'password': self.test_password, 'first_name': self.test_first_name, 'last_name': self.test_last_name}
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
test_uri = test_uri + '/' + str(response.data['id']) test_uri = test_uri + '/' + str(response.data['id'])
response = self.do_delete(test_uri) data = {'is_active': False }
self.assertEqual(response.status_code, 204) response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['is_active'], False)
response = self.do_get(test_uri) response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 200)
response = self.do_delete(test_uri) # User no longer exists, should get a 204 all the same self.assertEqual(response.data['is_active'], False)
self.assertEqual(response.status_code, 204)
def test_user_detail_get_undefined(self): def test_user_detail_post_invalid_user(self):
test_uri = '/api/users/123456789' test_uri = '/api/users/123124124'
response = self.do_get(test_uri) data = {'is_active': False }
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
def test_user_groups_list_post(self): def test_user_groups_list_post(self):
......
...@@ -54,6 +54,7 @@ def _serialize_user(response_data, user): ...@@ -54,6 +54,7 @@ def _serialize_user(response_data, user):
response_data['first_name'] = user.first_name response_data['first_name'] = user.first_name
response_data['last_name'] = user.last_name response_data['last_name'] = user.last_name
response_data['id'] = user.id response_data['id'] = user.id
response_data['is_active'] = user.is_active
return response_data return response_data
def _save_module_position(request, user, course_id, course_descriptor, position): def _save_module_position(request, user, course_id, course_descriptor, position):
...@@ -104,6 +105,7 @@ class UsersList(APIView): ...@@ -104,6 +105,7 @@ class UsersList(APIView):
password = request.DATA['password'] password = request.DATA['password']
first_name = request.DATA.get('first_name', '') first_name = request.DATA.get('first_name', '')
last_name = request.DATA.get('last_name', '') last_name = request.DATA.get('last_name', '')
is_active = request.DATA.get('is_active', None)
# enforce password complexity as an optional feature # enforce password complexity as an optional feature
if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False): if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False):
...@@ -138,12 +140,14 @@ class UsersList(APIView): ...@@ -138,12 +140,14 @@ class UsersList(APIView):
user.set_password(password) user.set_password(password)
user.first_name = first_name user.first_name = first_name
user.last_name = last_name user.last_name = last_name
if is_active is not None:
user.is_active = is_active
user.save() user.save()
profile = UserProfile(user=user) profile = UserProfile(user=user)
profile.name = '{} {}'.format(first_name, last_name) profile.name = '{} {}'.format(first_name, last_name)
profile.save() profile.save()
UserPreference.set_preference(user, LANGUAGE_KEY, get_language()) UserPreference.set_preference(user, LANGUAGE_KEY, get_language())
# add this account creation to password history # add this account creation to password history
...@@ -179,7 +183,7 @@ class UsersDetail(APIView): ...@@ -179,7 +183,7 @@ class UsersDetail(APIView):
response_data = {} response_data = {}
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
try: try:
existing_user = User.objects.get(id=user_id, is_active=True) existing_user = User.objects.get(id=user_id)
_serialize_user(response_data, existing_user) _serialize_user(response_data, existing_user)
response_data['uri'] = base_uri response_data['uri'] = base_uri
response_data['resources'] = [] response_data['resources'] = []
...@@ -191,36 +195,37 @@ class UsersDetail(APIView): ...@@ -191,36 +195,37 @@ class UsersDetail(APIView):
except ObjectDoesNotExist: except ObjectDoesNotExist:
return Response(response_data, status=status.HTTP_404_NOT_FOUND) return Response(response_data, status=status.HTTP_404_NOT_FOUND)
def delete(self, request, user_id, format=None): def post(self, request, user_id, format=None):
""" """
DELETE removes/inactivates/etc. an existing user POST provides the ability to update information about an existing user
""" """
response_data = {} response_data = {}
try:
existing_user = User.objects.get(id=user_id, is_active=True)
existing_user.is_active = False
existing_user.save()
except ObjectDoesNotExist:
# It's ok if we don't find a match
pass
return Response(response_data, status=status.HTTP_204_NO_CONTENT)
def post(self, request, user_id, format=None):
response_data = {}
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
response_data['uri'] = _generate_base_uri(request)
# Add some rate limiting here by re-using the RateLimitMixin as a helper class # Add some rate limiting here by re-using the RateLimitMixin as a helper class
limiter = BadRequestRateLimiter() limiter = BadRequestRateLimiter()
if limiter.is_rate_limit_exceeded(request): if limiter.is_rate_limit_exceeded(request):
AUDIT_LOG.warning("API::Rate limit exceeded in password_reset") AUDIT_LOG.warning("API::Rate limit exceeded in password_reset")
response_data['message'] = _('Rate limit exceeded in password_reset.') response_data['message'] = _('Rate limit exceeded in password_reset.')
status_code = status.HTTP_403_FORBIDDEN return Response(response_data, status=status.HTTP_403_FORBIDDEN)
return Response(response_data, status=status_code)
try: try:
existing_user = User.objects.get(id=user_id) existing_user = User.objects.get(id=user_id)
old_password_hash = existing_user.password except ObjectDoesNotExist:
_serialize_user(response_data, existing_user) limiter.tick_bad_request_counter(request)
password = request.DATA['password'] existing_user = None
if existing_user: if existing_user:
is_active = request.DATA.get('is_active', None)
if is_active is not None:
existing_user.is_active = is_active
response_data['is_active'] = existing_user.is_active
existing_user.save()
password = request.DATA.get('password')
if password:
old_password_hash = existing_user.password
_serialize_user(response_data, existing_user)
password = request.DATA['password']
if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False): if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False):
try: try:
validate_password_length(password) validate_password_length(password)
...@@ -270,17 +275,12 @@ class UsersDetail(APIView): ...@@ -270,17 +275,12 @@ class UsersDetail(APIView):
password_history_entry = PasswordHistory() password_history_entry = PasswordHistory()
password_history_entry.create(existing_user) password_history_entry.create(existing_user)
status_code = status.HTTP_201_CREATED status_code = status.HTTP_200_OK
response_data['uri'] = base_uri
response_data['message'] = 'Password Reset Successful'
else: else:
status_code = status.HTTP_404_NOT_FOUND status_code = status.HTTP_404_NOT_FOUND
response_data['message'] = 'User not exist' response_data['message'] = 'User not exist'
except ObjectDoesNotExist:
limiter.tick_bad_request_counter(request)
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
return Response(response_data, status=status_code) return Response(response_data, status=status_code)
...@@ -505,4 +505,4 @@ class UsersCoursesDetail(APIView): ...@@ -505,4 +505,4 @@ class UsersCoursesDetail(APIView):
user = None user = None
if user: if user:
CourseEnrollment.unenroll(user, course_id) CourseEnrollment.unenroll(user, course_id)
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment