Commit aad6dc43 by ihoover

Merge pull request #417 from edx/ihoover/feature_flag_auto_auth

Ihoover/feature flag auto auth
parents c80a05f4 56883d65
...@@ -62,9 +62,6 @@ MITX_FEATURES = { ...@@ -62,9 +62,6 @@ MITX_FEATURES = {
} }
ENABLE_JASMINE = False ENABLE_JASMINE = False
# needed to use lms student app
GENERATE_RANDOM_USER_CREDENTIALS = False
############################# SET PATH INFORMATION ############################# ############################# SET PATH INFORMATION #############################
PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms
...@@ -108,9 +105,12 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ...@@ -108,9 +105,12 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.static', 'django.core.context_processors.static',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'django.contrib.auth.context_processors.auth', # this is required for admin 'django.contrib.auth.context_processors.auth', # this is required for admin
'django.core.context_processors.csrf', # necessary for csrf protection
) )
# add csrf support unless disabled for load testing
if not MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'):
TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.csrf',) # necessary for csrf protection
LMS_BASE = None LMS_BASE = None
#################### CAPA External Code Evaluation ############################# #################### CAPA External Code Evaluation #############################
...@@ -142,7 +142,6 @@ MIDDLEWARE_CLASSES = ( ...@@ -142,7 +142,6 @@ MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'method_override.middleware.MethodOverrideMiddleware', 'method_override.middleware.MethodOverrideMiddleware',
# Instead of AuthenticationMiddleware, we use a cache-backed version # Instead of AuthenticationMiddleware, we use a cache-backed version
...@@ -158,6 +157,10 @@ MIDDLEWARE_CLASSES = ( ...@@ -158,6 +157,10 @@ MIDDLEWARE_CLASSES = (
'django.middleware.transaction.TransactionMiddleware' 'django.middleware.transaction.TransactionMiddleware'
) )
# add in csrf middleware unless disabled for load testing
if not MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'):
MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ('django.middleware.csrf.CsrfViewMiddleware',)
############################ SIGNAL HANDLERS ################################ ############################ SIGNAL HANDLERS ################################
# This is imported to register the exception signal handling that logs exceptions # This is imported to register the exception signal handling that logs exceptions
import monitoring.exceptions # noqa import monitoring.exceptions # noqa
......
...@@ -149,6 +149,12 @@ if settings.MITX_FEATURES.get('ENABLE_SERVICE_STATUS'): ...@@ -149,6 +149,12 @@ if settings.MITX_FEATURES.get('ENABLE_SERVICE_STATUS'):
urlpatterns += (url(r'^admin/', include(admin.site.urls)),) urlpatterns += (url(r'^admin/', include(admin.site.urls)),)
# enable automatic login
if settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'):
urlpatterns += (
url(r'^auto_auth$', 'student.views.auto_auth'),
)
urlpatterns = patterns(*urlpatterns) urlpatterns = patterns(*urlpatterns)
# Custom error pages # Custom error pages
......
from django.test import TestCase
from django.test.client import Client
from django.contrib.auth.models import User
from util.testing import UrlResetMixin
from mock import patch
from django.core.urlresolvers import reverse, NoReverseMatch
class AutoAuthEnabledTestCase(UrlResetMixin, TestCase):
"""
Tests for the Auto auth view that we have for load testing.
"""
@patch.dict("django.conf.settings.MITX_FEATURES", {"AUTOMATIC_AUTH_FOR_LOAD_TESTING": True})
def setUp(self):
# Patching the settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING']
# value affects the contents of urls.py,
# so we need to call super.setUp() which reloads urls.py (because
# of the UrlResetMixin)
super(AutoAuthEnabledTestCase, self).setUp()
self.url = '/auto_auth'
self.cms_csrf_url = "signup"
self.lms_csrf_url = "signin_user"
self.client = Client()
def test_create_user(self):
"""
Test that user gets created when visiting the page.
"""
self.client.get(self.url)
qset = User.objects.all()
# assert user was created and is active
self.assertEqual(qset.count(), 1)
user = qset[0]
assert user.is_active
@patch('student.views.random.randint')
def test_create_multiple_users(self, randint):
"""
Test to make sure multiple users are created.
"""
randint.return_value = 1
self.client.get(self.url)
randint.return_value = 2
self.client.get(self.url)
qset = User.objects.all()
# make sure that USER_1 and USER_2 were created
self.assertEqual(qset.count(), 2)
@patch.dict("django.conf.settings.MITX_FEATURES", {"MAX_AUTO_AUTH_USERS": 1})
def test_login_already_created_user(self):
"""
Test that when we have reached the limit for automatic users
a subsequent request results in an already existant one being
logged in.
"""
# auto-generate 1 user (the max)
url = '/auto_auth'
self.client.get(url)
# go to the site again
self.client.get(url)
qset = User.objects.all()
# make sure it is the same user
self.assertEqual(qset.count(), 1)
class AutoAuthDisabledTestCase(UrlResetMixin, TestCase):
"""
Test that the page is inaccessible with default settings
"""
@patch.dict("django.conf.settings.MITX_FEATURES", {"AUTOMATIC_AUTH_FOR_LOAD_TESTING": False})
def setUp(self):
# Patching the settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING']
# value affects the contents of urls.py,
# so we need to call super.setUp() which reloads urls.py (because
# of the UrlResetMixin)
super(AutoAuthDisabledTestCase, self).setUp()
self.url = '/auto_auth'
self.client = Client()
def test_auto_auth_disabled(self):
"""
Make sure automatic authentication is disabled.
"""
response = self.client.get(self.url)
self.assertEqual(response.status_code, 404)
def test_csrf_enabled(self):
"""
test that when not load testing, csrf protection is on
"""
cms_csrf_url = "signup"
lms_csrf_url = "signin_user"
self.client = Client(enforce_csrf_checks=True)
try:
csrf_protected_url = reverse(cms_csrf_url)
response = self.client.post(csrf_protected_url)
except NoReverseMatch:
csrf_protected_url = reverse(lms_csrf_url)
response = self.client.post(csrf_protected_url)
self.assertEqual(response.status_code, 403)
...@@ -19,6 +19,7 @@ from django.core.context_processors import csrf ...@@ -19,6 +19,7 @@ from django.core.context_processors import csrf
from django.core.mail import send_mail from django.core.mail import send_mail
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.validators import validate_email, validate_slug, ValidationError from django.core.validators import validate_email, validate_slug, ValidationError
from django.core.exceptions import ObjectDoesNotExist
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, Http404 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, Http404
from django.shortcuts import redirect from django.shortcuts import redirect
...@@ -674,18 +675,20 @@ def create_account(request, post_override=None): ...@@ -674,18 +675,20 @@ def create_account(request, post_override=None):
subject = ''.join(subject.splitlines()) subject = ''.join(subject.splitlines())
message = render_to_string('emails/activation_email.txt', d) message = render_to_string('emails/activation_email.txt', d)
try: # dont send email if we are doing load testing or random user generation for some reason
if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'): if not (settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING')):
dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] try:
message = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) + if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
'-' * 80 + '\n\n' + message) dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL']
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False) message = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
elif not settings.GENERATE_RANDOM_USER_CREDENTIALS: '-' * 80 + '\n\n' + message)
res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False)
except: else:
log.warning('Unable to send activation email to user', exc_info=True) res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
js['value'] = _('Could not send activation e-mail.') except:
return HttpResponse(json.dumps(js)) log.warning('Unable to send activation email to user', exc_info=True)
js['value'] = _('Could not send activation e-mail.')
return HttpResponse(json.dumps(js))
# Immediately after a user creates an account, we log them in. They are only # Immediately after a user creates an account, we log them in. They are only
# logged in until they close the browser. They can't log in again until they click # logged in until they close the browser. They can't log in again until they click
...@@ -902,32 +905,51 @@ def create_exam_registration(request, post_override=None): ...@@ -902,32 +905,51 @@ def create_exam_registration(request, post_override=None):
return HttpResponse(json.dumps(js), mimetype="application/json") return HttpResponse(json.dumps(js), mimetype="application/json")
def get_random_post_override(): def auto_auth(request):
""" """
Return a dictionary suitable for passing to post_vars of _do_create_account or post_override Automatically logs the user in with a generated random credentials
of create_account, with random user info. This view is only accessible when
settings.MITX_SETTINGS['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] is true.
""" """
def id_generator(size=6, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
return {'username': "random_" + id_generator(), def get_dummy_post_data(username, password):
'email': id_generator(size=10, chars=string.ascii_lowercase) + "_dummy_test@mitx.mit.edu", """
'password': id_generator(), Return a dictionary suitable for passing to post_vars of _do_create_account or post_override
'name': (id_generator(size=5, chars=string.ascii_lowercase) + " " + of create_account, with specified username and password.
id_generator(size=7, chars=string.ascii_lowercase)), """
'honor_code': u'true',
'terms_of_service': u'true', }
return {'username': username,
'email': username + "_dummy_test@mitx.mit.edu",
'password': password,
'name': username + " " + username,
'honor_code': u'true',
'terms_of_service': u'true', }
def create_random_account(create_account_function): # generate random user ceredentials from a small name space (determined by settings)
def inner_create_random_account(request): name_base = 'USER_'
return create_account_function(request, post_override=get_random_post_override()) pass_base = 'PASS_'
return inner_create_random_account max_users = settings.MITX_FEATURES.get('MAX_AUTO_AUTH_USERS', 200)
number = random.randint(1, max_users)
# TODO (vshnayder): do we need GENERATE_RANDOM_USER_CREDENTIALS for anything? username = name_base + str(number)
if settings.GENERATE_RANDOM_USER_CREDENTIALS: password = pass_base + str(number)
create_account = create_random_account(create_account)
# if they already are a user, log in
try:
user = User.objects.get(username=username)
user = authenticate(username=username, password=password)
login(request, user)
# else create and activate account info
except ObjectDoesNotExist:
post_override = get_dummy_post_data(username, password)
create_account(request, post_override=post_override)
request.user.is_active = True
request.user.save()
# return empty success
return HttpResponse('')
@ensure_csrf_cookie @ensure_csrf_cookie
......
...@@ -178,6 +178,10 @@ for name, value in ENV_TOKENS.get("CODE_JAIL", {}).items(): ...@@ -178,6 +178,10 @@ for name, value in ENV_TOKENS.get("CODE_JAIL", {}).items():
COURSES_WITH_UNSAFE_CODE = ENV_TOKENS.get("COURSES_WITH_UNSAFE_CODE", []) COURSES_WITH_UNSAFE_CODE = ENV_TOKENS.get("COURSES_WITH_UNSAFE_CODE", [])
# automatic log in for load testing
MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = ENV_TOKENS.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING')
MITX_FEATURES['MAX_AUTO_AUTH_USERS'] = ENV_TOKENS.get('MAX_AUTO_AUTH_USERS')
############################## SECURE AUTH ITEMS ############### ############################## SECURE AUTH ITEMS ###############
# Secret things: passwords, access keys, etc. # Secret things: passwords, access keys, etc.
......
...@@ -37,7 +37,6 @@ PLATFORM_NAME = "edX" ...@@ -37,7 +37,6 @@ PLATFORM_NAME = "edX"
COURSEWARE_ENABLED = True COURSEWARE_ENABLED = True
ENABLE_JASMINE = False ENABLE_JASMINE = False
GENERATE_RANDOM_USER_CREDENTIALS = False
PERFSTATS = False PERFSTATS = False
DISCUSSION_SETTINGS = { DISCUSSION_SETTINGS = {
...@@ -145,6 +144,9 @@ MITX_FEATURES = { ...@@ -145,6 +144,9 @@ MITX_FEATURES = {
# Allow use of the hint managment instructor view. # Allow use of the hint managment instructor view.
'ENABLE_HINTER_INSTRUCTOR_VIEW': False, 'ENABLE_HINTER_INSTRUCTOR_VIEW': False,
# for load testing
'AUTOMATIC_AUTH_FOR_LOAD_TESTING': False,
# Toggle to enable chat availability (configured on a per-course # Toggle to enable chat availability (configured on a per-course
# basis in Studio) # basis in Studio)
'ENABLE_CHAT': False 'ENABLE_CHAT': False
...@@ -218,7 +220,6 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ...@@ -218,7 +220,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
#'django.core.context_processors.i18n', #'django.core.context_processors.i18n',
'django.contrib.auth.context_processors.auth', # this is required for admin 'django.contrib.auth.context_processors.auth', # this is required for admin
'django.core.context_processors.csrf', # necessary for csrf protection
# Added for django-wiki # Added for django-wiki
'django.core.context_processors.media', 'django.core.context_processors.media',
...@@ -231,6 +232,10 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ...@@ -231,6 +232,10 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'mitxmako.shortcuts.marketing_link_context_processor', 'mitxmako.shortcuts.marketing_link_context_processor',
) )
# add csrf support unless disabled for load testing
if not MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'):
TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.csrf',) # necessary for csrf protection
STUDENT_FILEUPLOAD_MAX_SIZE = 4 * 1000 * 1000 # 4 MB STUDENT_FILEUPLOAD_MAX_SIZE = 4 * 1000 * 1000 # 4 MB
MAX_FILEUPLOADS_PER_INPUT = 20 MAX_FILEUPLOADS_PER_INPUT = 20
...@@ -469,7 +474,6 @@ MIDDLEWARE_CLASSES = ( ...@@ -469,7 +474,6 @@ MIDDLEWARE_CLASSES = (
'django_comment_client.middleware.AjaxExceptionMiddleware', 'django_comment_client.middleware.AjaxExceptionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
# Instead of AuthenticationMiddleware, we use a cached backed version # Instead of AuthenticationMiddleware, we use a cached backed version
#'django.contrib.auth.middleware.AuthenticationMiddleware', #'django.contrib.auth.middleware.AuthenticationMiddleware',
...@@ -488,6 +492,10 @@ MIDDLEWARE_CLASSES = ( ...@@ -488,6 +492,10 @@ MIDDLEWARE_CLASSES = (
'codejail.django_integration.ConfigureCodeJailMiddleware', 'codejail.django_integration.ConfigureCodeJailMiddleware',
) )
# add in csrf middleware unless disabled for load testing
if not MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'):
MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ('django.middleware.csrf.CsrfViewMiddleware',)
############################### Pipeline ####################################### ############################### Pipeline #######################################
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage' STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
......
...@@ -441,6 +441,12 @@ if settings.MITX_FEATURES.get('ENABLE_HINTER_INSTRUCTOR_VIEW'): ...@@ -441,6 +441,12 @@ if settings.MITX_FEATURES.get('ENABLE_HINTER_INSTRUCTOR_VIEW'):
'instructor.hint_manager.hint_manager', name="hint_manager"), 'instructor.hint_manager.hint_manager', name="hint_manager"),
) )
# enable automatic login
if settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'):
urlpatterns += (
url(r'^auto_auth$', 'student.views.auto_auth'),
)
urlpatterns = patterns(*urlpatterns) urlpatterns = patterns(*urlpatterns)
if settings.DEBUG: if settings.DEBUG:
......
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