Commit 3732bd16 by Zia Fazal

Merge pull request #22 from edx-solutions/security_enhacements_785

Security enhacements 785
parents 3d65edb4 9292e947
...@@ -8,6 +8,7 @@ from django.contrib.auth import SESSION_KEY, BACKEND_SESSION_KEY, load_backend ...@@ -8,6 +8,7 @@ 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
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.translation import ugettext as _
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view, permission_classes from rest_framework.decorators import api_view, permission_classes
...@@ -15,6 +16,7 @@ from rest_framework.response import Response ...@@ -15,6 +16,7 @@ from rest_framework.response import Response
from api_manager.permissions import ApiKeyHeaderPermission from api_manager.permissions import ApiKeyHeaderPermission
from api_manager.serializers import UserSerializer from api_manager.serializers import UserSerializer
from student.models import LoginFailures
def _generate_base_uri(request): def _generate_base_uri(request):
...@@ -45,9 +47,23 @@ def session_list(request): ...@@ -45,9 +47,23 @@ def session_list(request):
existing_user = User.objects.get(username=request.DATA['username']) existing_user = User.objects.get(username=request.DATA['username'])
except ObjectDoesNotExist: except ObjectDoesNotExist:
existing_user = None 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. '
'Try again later.')
return Response(response_data, status=response_status)
if existing_user: if existing_user:
user = authenticate(username=existing_user.username, password=request.DATA['password']) user = authenticate(username=existing_user.username, password=request.DATA['password'])
if user is not None: 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: if user.is_active:
login(request, user) login(request, user)
response_data['token'] = request.session.session_key response_data['token'] = request.session.session_key
...@@ -59,6 +75,10 @@ def session_list(request): ...@@ -59,6 +75,10 @@ def session_list(request):
else: else:
response_status = status.HTTP_403_FORBIDDEN response_status = status.HTTP_403_FORBIDDEN
else: else:
# 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 response_status = status.HTTP_401_UNAUTHORIZED
else: else:
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
......
...@@ -5,6 +5,9 @@ import logging ...@@ -5,6 +5,9 @@ import logging
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import IntegrityError from django.db import IntegrityError
from django.core.validators import validate_email, validate_slug, ValidationError
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view, permission_classes from rest_framework.decorators import api_view, permission_classes
...@@ -14,8 +17,12 @@ from api_manager.permissions import ApiKeyHeaderPermission ...@@ -14,8 +17,12 @@ from api_manager.permissions import ApiKeyHeaderPermission
from courseware import module_render from courseware import module_render
from courseware.model_data import FieldDataCache from courseware.model_data import FieldDataCache
from courseware.views import get_module_for_descriptor, save_child_position, get_current_child from courseware.views import get_module_for_descriptor, save_child_position, get_current_child
from student.models import CourseEnrollment from student.models import CourseEnrollment, PasswordHistory
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from util.password_policy_validators import (
validate_password_length, validate_password_complexity,
validate_password_dictionary
)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -95,6 +102,32 @@ def user_list(request): ...@@ -95,6 +102,32 @@ def user_list(request):
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', '')
# enforce password complexity as an optional feature
if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False):
try:
validate_password_length(password)
validate_password_complexity(password)
validate_password_dictionary(password)
except ValidationError, err:
status_code = status.HTTP_400_BAD_REQUEST
response_data['message'] = _('Password: ') + '; '.join(err.messages)
return Response(response_data, status=status_code)
try:
validate_email(email)
except ValidationError:
status_code = status.HTTP_400_BAD_REQUEST
response_data['message'] = _('Valid e-mail is required.')
return Response(response_data, status=status_code)
try:
validate_slug(username)
except ValidationError:
status_code = status.HTTP_400_BAD_REQUEST
response_data['message'] = _('Username should only consist of A-Z and 0-9, with no spaces.')
return Response(response_data, status=status_code)
try: try:
user = User.objects.create(email=email, username=username) user = User.objects.create(email=email, username=username)
except IntegrityError: except IntegrityError:
...@@ -105,6 +138,11 @@ def user_list(request): ...@@ -105,6 +138,11 @@ def user_list(request):
user.last_name = last_name user.last_name = last_name
user.save() user.save()
# add this account creation to password history
# NOTE, this will be a NOP unless the feature has been turned on in configuration
password_history_entry = PasswordHistory()
password_history_entry.create(user)
# CDODGE: @TODO: We will have to extend this to look in the CourseEnrollmentAllowed table and # CDODGE: @TODO: We will have to extend this to look in the CourseEnrollmentAllowed table and
# auto-enroll students when they create a new account. Also be sure to remove from # auto-enroll students when they create a new account. Also be sure to remove from
# the CourseEnrollmentAllow table after the auto-registration has taken place # the CourseEnrollmentAllow table after the auto-registration has taken place
......
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