def _change_password(self, user, password): """ Helper method to change password on user and record in the PasswordHistory """ user.set_password(password) user.save() history = PasswordHistory() history.create(user)
def test_pbkdf2_sha256_password_reuse(self): """ Assert against the password reuse policy but using the normal Django PBKDF2 """ user = self._user_factory_with_history() staff = self._user_factory_with_history(is_staff=True) # students need to user at least one different passwords before reuse self.assertFalse( PasswordHistory.is_allowable_password_reuse(user, "test")) self.assertTrue( PasswordHistory.is_allowable_password_reuse(user, "different")) self._change_password(user, "different") self.assertTrue( PasswordHistory.is_allowable_password_reuse(user, "test")) # staff needs to use at least two different passwords before reuse self.assertFalse( PasswordHistory.is_allowable_password_reuse(staff, "test")) self.assertTrue( PasswordHistory.is_allowable_password_reuse(staff, "different")) self._change_password(staff, "different") self.assertFalse( PasswordHistory.is_allowable_password_reuse(staff, "test")) self.assertFalse( PasswordHistory.is_allowable_password_reuse(staff, "different")) self.assertTrue( PasswordHistory.is_allowable_password_reuse(staff, "third")) self._change_password(staff, "third") self.assertTrue( PasswordHistory.is_allowable_password_reuse(staff, "test"))
def test_disabled_too_frequent_password_resets(self): """ Verify properly default behavior when feature is disabled """ student = self._user_factory_with_history() self.assertFalse(PasswordHistory.is_password_reset_too_soon(student))
def test_too_frequent_password_resets(self): """ Assert that a user should not be able to password reset too frequently """ student = self._user_factory_with_history() grandfathered_student = self._user_factory_with_history( set_initial_history=False) self.assertTrue(PasswordHistory.is_password_reset_too_soon(student)) self.assertFalse( PasswordHistory.is_password_reset_too_soon(grandfathered_student)) staff_reset_time = timezone.now() + timedelta(days=100) with freeze_time(staff_reset_time): self.assertFalse( PasswordHistory.is_password_reset_too_soon(student))
def _user_factory_with_history(self, is_staff=False, set_initial_history=True): """ Helper method to generate either an Admin or a User """ if is_staff: user = AdminFactory() else: user = UserFactory() user.date_joined = timezone.now() if set_initial_history: history = PasswordHistory() history.create(user) return user
def _create_user(self, data, errors, response): """Register user and add him to a company.""" user = data.get('user') email = user.get('email') company = data.get('company') # Create the user and their profile. try: # User user = User.objects.create(**user) user.set_password(user.password) user.save() data['user_object'] = user # Profile UserProfile.objects.create(user=user, name='{} {}'.format( user.first_name, user.last_name)) # Notifications if settings.FEATURES.get('ENABLE_DISCUSSION_EMAIL_DIGEST'): enable_notifications(user) # Password History password_history_entry = PasswordHistory() password_history_entry.create(user) except Exception as exc: self._add_error(errors, str(exc.message), _('Registering Participant'), email) else: response['user_id'] = user.id AUDIT_LOG.info("API::New account created with user-id - {}".format( user.id)) # Associate with company. try: company.users.add(user) except Exception as exc: self._add_error(errors, str(exc.message), _('Enrolling Participant in Company'), email) user_organization_updated.send(sender=__name__, user_id=user.id, organization_id=company.id)
def test_no_forced_password_change(self): """ Assert that if we skip configuration, then user will never have to force reset password """ student = self._user_factory_with_history() staff = self._user_factory_with_history(is_staff=True) # also create a user who doesn't have any history grandfathered_student = UserFactory() grandfathered_student.date_joined = timezone.now() self.assertFalse( PasswordHistory.should_user_reset_password_now(student)) self.assertFalse(PasswordHistory.should_user_reset_password_now(staff)) self.assertFalse( PasswordHistory.should_user_reset_password_now( grandfathered_student)) staff_reset_time = timezone.now() + timedelta(days=100) with freeze_time(staff_reset_time): self.assertFalse( PasswordHistory.should_user_reset_password_now(student)) self.assertFalse( PasswordHistory.should_user_reset_password_now( grandfathered_student)) self.assertFalse( PasswordHistory.should_user_reset_password_now(staff))
def test_retirement(self): """ Verify that the user's password history contains no actual passwords after retirement is called. """ user = self._user_factory_with_history() # create multiple rows in the password history table self._change_password(user, "different") self._change_password(user, "differentagain") # ensure the rows were actually created and stored the passwords self.assertTrue( PasswordHistory.objects.filter(user_id=user.id).exists()) for row in PasswordHistory.objects.filter(user_id=user.id): self.assertNotEqual(row.password, "") # retire the user and ensure that the rows are still present, but with no passwords PasswordHistory.retire_user(user.id) self.assertTrue( PasswordHistory.objects.filter(user_id=user.id).exists()) for row in PasswordHistory.objects.filter(user_id=user.id): self.assertEqual(row.password, "")
def login_user(request, session_id=None): """ Create a new session and login the user, or upgrade an existing session """ response_data = {} # Add some rate limiting here by re-using the RateLimitMixin as a helper class limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): response_data['message'] = _('Rate limit exceeded in api login.') return Response(response_data, status=status.HTTP_403_FORBIDDEN) base_uri = generate_base_uri(request) username = request.data.get('username', None) if username is None: return Response({'message': _('username is missing')}, status=status.HTTP_400_BAD_REQUEST) password = request.data.get('password', None) if password is None: return Response({'message': _('password is missing')}, status=status.HTTP_400_BAD_REQUEST) try: existing_user = User.objects.get(username=username) except ObjectDoesNotExist: existing_user = None # see if account has been locked out due to excessive login failures if existing_user and LoginFailures.is_feature_enabled(): if LoginFailures.is_user_locked_out(existing_user): response_status = status.HTTP_403_FORBIDDEN response_data['message'] = _( 'This account has been temporarily locked due to excessive login failures. ' # pylint: disable=C0301 'Try again later.') return Response(response_data, status=response_status) # see if the user must reset his/her password due to any policy settings if existing_user and PasswordHistory.should_user_reset_password_now( existing_user): response_status = status.HTTP_403_FORBIDDEN response_data['message'] = _( 'Your password has expired due to password policy on this account. ' 'You must reset your password before you can log in again.') return Response(response_data, status=response_status) if existing_user: user = authenticate(username=existing_user.username, password=password) if user is not None: # successful login, clear failed login attempts counters, if applicable if LoginFailures.is_feature_enabled(): LoginFailures.clear_lockout_counter(user) if user.is_active: # # Create a new session directly with the SESSION_ENGINE # We don't call the django.contrib.auth login() method # because it is bound with the HTTP request. # # Since we are a server-to-server API, we shouldn't # be stateful with respect to the HTTP request # and anything that might come with it, as it could # violate our RESTfulness # engine = import_module(settings.SESSION_ENGINE) if session_id is None: session = engine.SessionStore() session.create() success_status = status.HTTP_201_CREATED else: session = engine.SessionStore(session_id) success_status = status.HTTP_200_OK if SESSION_KEY in session: # Someone is already logged in. The user ID of whoever is logged in # now might be different than the user ID we've been asked to login, # which would be bad. But even if it is the same user, we should not # be asked to login a user who is already logged in. This likely # indicates some sort of programming/validation error and possibly # even a potential security issue - so return 403. return Response({}, status=status.HTTP_403_FORBIDDEN) # These values are expected to be set in any new session session[SESSION_KEY] = user.id session[BACKEND_SESSION_KEY] = user.backend if hasattr(user, 'get_session_auth_hash'): session_auth_hash = user.get_session_auth_hash() else: session_auth_hash = '' session[HASH_SESSION_KEY] = session_auth_hash session.save() response_data['token'] = session.session_key response_data['expires'] = session.get_expiry_age() user_dto = SimpleUserSerializer(user) response_data['user'] = user_dto.data response_data['uri'] = '{}/{}'.format( base_uri, session.session_key) response_status = success_status # generate a CSRF tokens for any web clients that may need to # call into the LMS via Ajax (for example Notifications) response_data['csrftoken'] = str( csrf(request)['csrf_token']) # update the last_login fields in the auth_user table for this user user.last_login = timezone.now() user.save() # add to audit log AUDIT_LOG.info( "API::User logged in successfully with user-id - {}". format(user.id)) # pylint: disable=W1202 else: response_status = status.HTTP_403_FORBIDDEN else: limiter.tick_request_counter(request) # tick the failed login counters if the user exists in the database if LoginFailures.is_feature_enabled(): LoginFailures.increment_lockout_counter(existing_user) response_status = status.HTTP_401_UNAUTHORIZED AUDIT_LOG.warn( "API::User authentication failed with user-id - {}".format( existing_user.id)) # pylint: disable=W1202 else: AUDIT_LOG.warn( "API::Failed login attempt with unknown email/username") response_status = status.HTTP_404_NOT_FOUND return Response(response_data, status=response_status)
def test_forced_password_change(self): """ Assert when passwords must be reset """ student = self._user_factory_with_history() staff = self._user_factory_with_history(is_staff=True) grandfathered_student = self._user_factory_with_history( set_initial_history=False) self.assertFalse( PasswordHistory.should_user_reset_password_now(student)) self.assertFalse(PasswordHistory.should_user_reset_password_now(staff)) self.assertFalse( PasswordHistory.should_user_reset_password_now( grandfathered_student)) staff_reset_time = timezone.now() + timedelta(days=1) with freeze_time(staff_reset_time): self.assertFalse( PasswordHistory.should_user_reset_password_now(student)) self.assertFalse( PasswordHistory.should_user_reset_password_now( grandfathered_student)) self.assertTrue( PasswordHistory.should_user_reset_password_now(staff)) self._change_password(staff, 'Different') self.assertFalse( PasswordHistory.should_user_reset_password_now(staff)) student_reset_time = timezone.now() + timedelta(days=5) with freeze_time(student_reset_time): self.assertTrue( PasswordHistory.should_user_reset_password_now(student)) self.assertTrue( PasswordHistory.should_user_reset_password_now( grandfathered_student)) self.assertTrue( PasswordHistory.should_user_reset_password_now(staff)) self._change_password(student, 'Different') self.assertFalse( PasswordHistory.should_user_reset_password_now(student)) self._change_password(grandfathered_student, 'Different') self.assertFalse( PasswordHistory.should_user_reset_password_now( grandfathered_student)) self._change_password(staff, 'Different') self.assertFalse( PasswordHistory.should_user_reset_password_now(staff))