def test_enrollment_limit_by_domain(self): """ Tests that the enrollmentDomain setting is properly limiting enrollment to those who have the proper external auth """ # create 2 course, one with limited enrollment one without shib_course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only') shib_course.enrollment_domain = 'shib:https://idp.stanford.edu/' self.store.update_item(shib_course, '**replace_user**') open_enroll_course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') open_enroll_course.enrollment_domain = '' self.store.update_item(open_enroll_course, '**replace_user**') # create 3 kinds of students, external_auth matching shib_course, external_auth not matching, no external auth shib_student = UserFactory.create() shib_student.save() extauth = ExternalAuthMap(external_id='*****@*****.**', external_email='', external_domain='shib:https://idp.stanford.edu/', external_credentials="", user=shib_student) extauth.save() other_ext_student = UserFactory.create() other_ext_student.username = "******" other_ext_student.email = "*****@*****.**" other_ext_student.save() extauth = ExternalAuthMap(external_id='*****@*****.**', external_email='', external_domain='shib:https://other.edu/', external_credentials="", user=other_ext_student) extauth.save() int_student = UserFactory.create() int_student.username = "******" int_student.email = "*****@*****.**" int_student.save() # Tests the two case for courses, limited and not for course in [shib_course, open_enroll_course]: for student in [shib_student, other_ext_student, int_student]: request = self.request_factory.post('/change_enrollment') request.POST.update({'enrollment_action': 'enroll', 'course_id': course.id.to_deprecated_string()}) request.user = student response = change_enrollment(request) # If course is not limited or student has correct shib extauth then enrollment should be allowed if course is open_enroll_course or student is shib_student: self.assertEqual(response.status_code, 200) self.assertTrue(CourseEnrollment.is_enrolled(student, course.id)) # Clean up CourseEnrollment.unenroll(student, course.id) else: self.assertEqual(response.status_code, 400) self.assertFalse(CourseEnrollment.is_enrolled(student, course.id))
def test_shib_login(self): """ Tests that: * shib credentials that match an existing ExternalAuthMap with a linked user logs the user in * shib credentials that match an existing ExternalAuthMap without a linked user and also match the email of an existing user without an existing ExternalAuthMap links the two and log the user in * shib credentials that match an existing ExternalAuthMap without a linked user and also match the email of an existing user that already has an ExternalAuthMap causes an error (403) * shib credentials that do not match an existing ExternalAuthMap causes the registration form to appear """ user_w_map = UserFactory.create(email='*****@*****.**') extauth = ExternalAuthMap( external_id='*****@*****.**', external_email='', external_domain='shib:https://idp.stanford.edu/', external_credentials="", user=user_w_map) user_wo_map = UserFactory.create(email='*****@*****.**') user_w_map.save() user_wo_map.save() extauth.save() idps = ['https://idp.stanford.edu/', 'https://someother.idp.com/'] remote_users = [ '*****@*****.**', '*****@*****.**', 'testuser2@someother_idp.com' ] for idp in idps: for remote_user in remote_users: request = self.request_factory.get('/shib-login') request.session = import_module( settings.SESSION_ENGINE).SessionStore() # empty session request.META.update({ 'Shib-Identity-Provider': idp, 'REMOTE_USER': remote_user, 'mail': remote_user }) request.user = AnonymousUser() response = shib_login(request) if idp == "https://idp.stanford.edu/" and remote_user == '*****@*****.**': self.assertIsInstance(response, HttpResponseRedirect) self.assertEqual(request.user, user_w_map) self.assertEqual(response['Location'], '/') elif idp == "https://idp.stanford.edu/" and remote_user == '*****@*****.**': self.assertIsNotNone( ExternalAuthMap.objects.get(user=user_wo_map)) self.assertIsInstance(response, HttpResponseRedirect) self.assertEqual(request.user, user_wo_map) self.assertEqual(response['Location'], '/') elif idp == "https://someother.idp.com/" and remote_user in \ ['*****@*****.**', '*****@*****.**']: self.assertEqual(response.status_code, 403) self.assertIn( "You have already created an account using an external login", response.content) else: self.assertEqual(response.status_code, 200) self.assertContains(response, "<title>Register for")
def setUp(self): super(ExternalAuthShibTest, self).setUp() self.course = CourseFactory.create( org='Stanford', number='456', display_name='NO SHIB', user_id=self.user.id, ) self.shib_course = CourseFactory.create( org='Stanford', number='123', display_name='Shib Only', enrollment_domain='shib:https://idp.stanford.edu/', user_id=self.user.id, ) self.user_w_map = UserFactory.create(email='*****@*****.**') self.extauth = ExternalAuthMap( external_id='*****@*****.**', external_email='*****@*****.**', external_domain='shib:https://idp.stanford.edu/', external_credentials="", user=self.user_w_map) self.user_w_map.save() self.extauth.save() self.user_wo_map = UserFactory.create(email='*****@*****.**') self.user_wo_map.save()
def base_extauth_bypass_sending_activation_email( self, bypass_activation_email_for_extauth_setting): """ Tests user creation without sending activation email when doing external auth """ request = self.request_factory.post(self.url, self.params) # now indicate we are doing ext_auth by setting 'ExternalAuthMap' in the session. request.session = import_module( settings.SESSION_ENGINE).SessionStore() # empty session extauth = ExternalAuthMap( external_id='*****@*****.**', external_email='*****@*****.**', internal_password=self.params['password'], external_domain='shib:https://idp.stanford.edu/') request.session['ExternalAuthMap'] = extauth request.user = AnonymousUser() mako_middleware_process_request(request) with mock.patch('django.contrib.auth.models.User.email_user' ) as mock_send_mail: student.views.create_account(request) # check that send_mail is called if bypass_activation_email_for_extauth_setting: self.assertFalse(mock_send_mail.called) else: self.assertTrue(mock_send_mail.called)
def test_shib_login_enrollment(self): """ A functionality test that a student with an existing shib login can auto-enroll in a class with GET or POST params. Also tests the direction functionality of the 'next' GET/POST param """ student = UserFactory.create() extauth = ExternalAuthMap( external_id='*****@*****.**', external_email='', external_domain='shib:https://idp.stanford.edu/', external_credentials="", internal_password="******", user=student) student.set_password("password") student.save() extauth.save() course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only') course.enrollment_domain = 'shib:https://idp.stanford.edu/' self.store.update_item(course, '**replace_user**') # use django test client for sessions and url processing # no enrollment before trying self.assertFalse(CourseEnrollment.is_enrolled(student, course.id)) self.client.logout() request_kwargs = { 'path': '/shib-login/', 'data': { 'enrollment_action': 'enroll', 'course_id': course.id, 'next': '/testredirect' }, 'follow': False, 'REMOTE_USER': '******', 'Shib-Identity-Provider': 'https://idp.stanford.edu/' } response = self.client.get(**request_kwargs) # successful login is a redirect to "/" self.assertEqual(response.status_code, 302) self.assertEqual(response['location'], 'http://testserver/testredirect') # now there is enrollment self.assertTrue(CourseEnrollment.is_enrolled(student, course.id)) # Clean up and try again with POST (doesn't happen with real production shib, doing this for test coverage) self.client.logout() CourseEnrollment.unenroll(student, course.id) self.assertFalse(CourseEnrollment.is_enrolled(student, course.id)) response = self.client.post(**request_kwargs) # successful login is a redirect to "/" self.assertEqual(response.status_code, 302) self.assertEqual(response['location'], 'http://testserver/testredirect') # now there is enrollment self.assertTrue(CourseEnrollment.is_enrolled(student, course.id))
def external_login_or_signup(request, external_id, external_domain, credentials, email, fullname, retfun=None): """Generic external auth login or signup""" # see if we have a map from this external_id to an edX username try: eamap = ExternalAuthMap.objects.get(external_id=external_id, external_domain=external_domain) log.debug('Found eamap=%s' % eamap) except ExternalAuthMap.DoesNotExist: # go render form for creating edX user eamap = ExternalAuthMap(external_id=external_id, external_domain=external_domain, external_credentials=json.dumps(credentials)) eamap.external_email = email eamap.external_name = fullname eamap.internal_password = generate_password() log.debug('Created eamap=%s' % eamap) eamap.save() internal_user = eamap.user if internal_user is None: log.debug('No user for %s yet, doing signup' % eamap.external_email) return signup(request, eamap) uname = internal_user.username user = authenticate(username=uname, password=eamap.internal_password) if user is None: log.warning("External Auth Login failed for %s / %s" % (uname, eamap.internal_password)) return signup(request, eamap) if not user.is_active: log.warning("User %s is not active" % (uname)) # TODO: improve error page msg = 'Account not yet activated: please look for link in your email' return default_render_failure(request, msg) login(request, user) request.session.set_expiry(0) student_views.try_change_enrollment(request) log.info("Login success - {0} ({1})".format(user.username, user.email)) if retfun is None: return redirect('/') return retfun()
def test_shib_login_enrollment(self): """ A functionality test that a student with an existing shib login can auto-enroll in a class with GET params """ if not settings.MITX_FEATURES.get('AUTH_USE_SHIB'): return student = UserFactory.create() extauth = ExternalAuthMap( external_id='*****@*****.**', external_email='', external_domain='shib:https://idp.stanford.edu/', external_credentials="", internal_password="******", user=student) student.set_password("password") student.save() extauth.save() course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only') course.enrollment_domain = 'shib:https://idp.stanford.edu/' metadata = own_metadata(course) metadata['enrollment_domain'] = course.enrollment_domain self.store.update_metadata(course.location.url(), metadata) #use django test client for sessions and url processing #no enrollment before trying self.assertEqual( CourseEnrollment.objects.filter(user=student, course_id=course.id).count(), 0) self.client.logout() request_kwargs = { 'path': '/shib-login/', 'data': { 'enrollment_action': 'enroll', 'course_id': course.id }, 'follow': False, 'REMOTE_USER': '******', 'Shib-Identity-Provider': 'https://idp.stanford.edu/' } response = self.client.get(**request_kwargs) #successful login is a redirect to "/" self.assertEqual(response.status_code, 302) self.assertEqual(response['location'], 'http://testserver/') #now there is enrollment self.assertEqual( CourseEnrollment.objects.filter(user=student, course_id=course.id).count(), 1)
def setUp(self): self.store = editable_modulestore() self.course = CourseFactory.create(org='Stanford', number='456', display_name='NO SHIB') self.shib_course = CourseFactory.create(org='Stanford', number='123', display_name='Shib Only') self.shib_course.enrollment_domain = 'shib:https://idp.stanford.edu/' self.store.update_item(self.shib_course, '**replace_user**') self.user_w_map = UserFactory.create(email='*****@*****.**') self.extauth = ExternalAuthMap(external_id='*****@*****.**', external_email='*****@*****.**', external_domain='shib:https://idp.stanford.edu/', external_credentials="", user=self.user_w_map) self.user_w_map.save() self.extauth.save() self.user_wo_map = UserFactory.create(email='*****@*****.**') self.user_wo_map.save()
def test_shib_login_enrollment(self): """ A functionality test that a student with an existing shib login can auto-enroll in a class with GET or POST params. Also tests the direction functionality of the 'next' GET/POST param """ student = UserFactory.create() extauth = ExternalAuthMap( external_id='*****@*****.**', external_email='', external_domain='shib:https://idp.stanford.edu/', external_credentials="", internal_password="******", user=student) student.set_password("password") student.save() extauth.save() course = CourseFactory.create( org='Stanford', number='123', display_name='Shib Only', enrollment_domain='shib:https://idp.stanford.edu/', user_id=self.test_user_id, ) # use django test client for sessions and url processing # no enrollment before trying self.assertFalse(CourseEnrollment.is_enrolled(student, course.id)) self.client.logout() params = [('course_id', course.id.to_deprecated_string()), ('enrollment_action', 'enroll'), ('next', '/testredirect')] request_kwargs = { 'path': '/shib-login/', 'data': dict(params), 'follow': False, 'REMOTE_USER': '******', 'Shib-Identity-Provider': 'https://idp.stanford.edu/' } response = self.client.get(**request_kwargs) # successful login is a redirect to the URL that handles auto-enrollment self.assertEqual(response.status_code, 302) self.assertEqual( response['location'], 'http://testserver/account/finish_auth?{}'.format( urlencode(params)))
def test_authmap_repair(self): """Run authmap check and repair""" self._setstaff_login() Users().create_user('test0', 'test test') # Will raise exception, so no assert needed eamap = ExternalAuthMap.objects.get(external_name='test test') mitu = User.objects.get(username='******') self.assertTrue(check_password(eamap.internal_password, mitu.password)) mitu.set_password('not autogenerated') mitu.save() self.assertFalse(check_password(eamap.internal_password, mitu.password)) # Create really non user AuthMap ExternalAuthMap(external_id='ll', external_domain='ll', external_credentials='{}', external_email='[email protected]', external_name='c', internal_password='').save() response = self.client.post(reverse('sysadmin'), { 'action': 'repair_eamap', }) self.assertIn('{0} test0'.format('Failed in authenticating'), response.content) self.assertIn('fixed password', response.content.decode('utf-8')) self.assertTrue( self.client.login(username='******', password=eamap.internal_password)) # Check for all OK self._setstaff_login() response = self.client.post(reverse('sysadmin'), { 'action': 'repair_eamap', }) self.assertIn('All ok!', response.content.decode('utf-8'))
def test_ext_auth_password_length_too_short(self): """ Tests that even if password policy is enforced, ext_auth registrations aren't subject to it """ self.url_params['password'] = '******' # shouldn't pass validation request = self.request_factory.post(self.url, self.url_params) # now indicate we are doing ext_auth by setting 'ExternalAuthMap' in the session. request.session = import_module(settings.SESSION_ENGINE).SessionStore() # empty session extauth = ExternalAuthMap(external_id='*****@*****.**', external_email='*****@*****.**', internal_password=self.url_params['password'], external_domain='shib:https://idp.stanford.edu/') request.session['ExternalAuthMap'] = extauth request.user = AnonymousUser() with patch('edxmako.request_context.get_current_request', return_value=request): response = create_account(request) self.assertEqual(response.status_code, 200) obj = json.loads(response.content) self.assertTrue(obj['success'])
def test_shib_login(self): """ Tests that: * shib credentials that match an existing ExternalAuthMap with a linked active user logs the user in * shib credentials that match an existing ExternalAuthMap with a linked inactive user shows error page * shib credentials that match an existing ExternalAuthMap without a linked user and also match the email of an existing user without an existing ExternalAuthMap links the two and log the user in * shib credentials that match an existing ExternalAuthMap without a linked user and also match the email of an existing user that already has an ExternalAuthMap causes an error (403) * shib credentials that do not match an existing ExternalAuthMap causes the registration form to appear """ user_w_map = UserFactory.create(email='*****@*****.**') extauth = ExternalAuthMap(external_id='*****@*****.**', external_email='', external_domain='shib:https://idp.stanford.edu/', external_credentials="", user=user_w_map) user_wo_map = UserFactory.create(email='*****@*****.**') user_w_map.save() user_wo_map.save() extauth.save() inactive_user = UserFactory.create(email='*****@*****.**') inactive_user.is_active = False inactive_extauth = ExternalAuthMap(external_id='*****@*****.**', external_email='', external_domain='shib:https://idp.stanford.edu/', external_credentials="", user=inactive_user) inactive_user.save() inactive_extauth.save() idps = ['https://idp.stanford.edu/', 'https://someother.idp.com/'] remote_users = ['*****@*****.**', '*****@*****.**', 'testuser2@someother_idp.com', '*****@*****.**'] for idp in idps: for remote_user in remote_users: request = self.request_factory.get('/shib-login') request.session = import_module(settings.SESSION_ENGINE).SessionStore() # empty session request.META.update({'Shib-Identity-Provider': idp, 'REMOTE_USER': remote_user, 'mail': remote_user}) request.user = AnonymousUser() with patch('external_auth.views.AUDIT_LOG') as mock_audit_log: response = shib_login(request) audit_log_calls = mock_audit_log.method_calls if idp == "https://idp.stanford.edu/" and remote_user == '*****@*****.**': self.assertIsInstance(response, HttpResponseRedirect) self.assertEqual(request.user, user_w_map) self.assertEqual(response['Location'], '/') # verify logging: self.assertEquals(len(audit_log_calls), 2) self._assert_shib_login_is_logged(audit_log_calls[0], remote_user) method_name, args, _kwargs = audit_log_calls[1] self.assertEquals(method_name, 'info') self.assertEquals(len(args), 3) self.assertIn(u'Login success', args[0]) self.assertEquals(remote_user, args[2]) elif idp == "https://idp.stanford.edu/" and remote_user == '*****@*****.**': self.assertEqual(response.status_code, 403) self.assertIn("Account not yet activated: please look for link in your email", response.content) # verify logging: self.assertEquals(len(audit_log_calls), 2) self._assert_shib_login_is_logged(audit_log_calls[0], remote_user) method_name, args, _kwargs = audit_log_calls[1] self.assertEquals(method_name, 'warning') self.assertEquals(len(args), 2) self.assertIn(u'is not active after external login', args[0]) # self.assertEquals(remote_user, args[1]) elif idp == "https://idp.stanford.edu/" and remote_user == '*****@*****.**': self.assertIsNotNone(ExternalAuthMap.objects.get(user=user_wo_map)) self.assertIsInstance(response, HttpResponseRedirect) self.assertEqual(request.user, user_wo_map) self.assertEqual(response['Location'], '/') # verify logging: self.assertEquals(len(audit_log_calls), 2) self._assert_shib_login_is_logged(audit_log_calls[0], remote_user) method_name, args, _kwargs = audit_log_calls[1] self.assertEquals(method_name, 'info') self.assertEquals(len(args), 3) self.assertIn(u'Login success', args[0]) self.assertEquals(remote_user, args[2]) elif idp == "https://someother.idp.com/" and remote_user in \ ['*****@*****.**', '*****@*****.**', '*****@*****.**']: self.assertEqual(response.status_code, 403) self.assertIn("You have already created an account using an external login", response.content) # no audit logging calls self.assertEquals(len(audit_log_calls), 0) else: self.assertEqual(response.status_code, 200) self.assertContains(response, "<title>Register for") # no audit logging calls self.assertEquals(len(audit_log_calls), 0)
def test_shib_login(self): """ Tests that: * shib credentials that match an existing ExternalAuthMap with a linked active user logs the user in * shib credentials that match an existing ExternalAuthMap with a linked inactive user shows error page * shib credentials that match an existing ExternalAuthMap without a linked user and also match the email of an existing user without an existing ExternalAuthMap links the two and log the user in * shib credentials that match an existing ExternalAuthMap without a linked user and also match the email of an existing user that already has an ExternalAuthMap causes an error (403) * shib credentials that do not match an existing ExternalAuthMap causes the registration form to appear """ user_w_map = UserFactory.create(email='*****@*****.**') extauth = ExternalAuthMap( external_id='*****@*****.**', external_email='', external_domain='shib:https://idp.stanford.edu/', external_credentials="", user=user_w_map) user_wo_map = UserFactory.create(email='*****@*****.**') user_w_map.save() user_wo_map.save() extauth.save() inactive_user = UserFactory.create(email='*****@*****.**') inactive_user.is_active = False inactive_extauth = ExternalAuthMap( external_id='*****@*****.**', external_email='', external_domain='shib:https://idp.stanford.edu/', external_credentials="", user=inactive_user) inactive_user.save() inactive_extauth.save() idps = ['https://idp.stanford.edu/', 'https://someother.idp.com/'] remote_users = [ '*****@*****.**', '*****@*****.**', 'testuser2@someother_idp.com', '*****@*****.**' ] for idp in idps: for remote_user in remote_users: self.client.logout() with patch('external_auth.views.AUDIT_LOG') as mock_audit_log: response = self.client.get( reverse('shib-login'), **{ 'Shib-Identity-Provider': idp, 'mail': remote_user, 'REMOTE_USER': remote_user, }) audit_log_calls = mock_audit_log.method_calls if idp == "https://idp.stanford.edu/" and remote_user == '*****@*****.**': self.assertRedirects(response, '/dashboard') self.assertEquals( int(self.client.session['_auth_user_id']), user_w_map.id) # verify logging: self.assertEquals(len(audit_log_calls), 2) self._assert_shib_login_is_logged(audit_log_calls[0], remote_user) method_name, args, _kwargs = audit_log_calls[1] self.assertEquals(method_name, 'info') self.assertEquals(len(args), 1) self.assertIn(u'Login success', args[0]) self.assertIn(remote_user, args[0]) elif idp == "https://idp.stanford.edu/" and remote_user == '*****@*****.**': self.assertEqual(response.status_code, 403) self.assertIn( "Account not yet activated: please look for link in your email", response.content) # verify logging: self.assertEquals(len(audit_log_calls), 2) self._assert_shib_login_is_logged(audit_log_calls[0], remote_user) method_name, args, _kwargs = audit_log_calls[1] self.assertEquals(method_name, 'warning') self.assertEquals(len(args), 1) self.assertIn(u'is not active after external login', args[0]) # self.assertEquals(remote_user, args[1]) elif idp == "https://idp.stanford.edu/" and remote_user == '*****@*****.**': self.assertIsNotNone( ExternalAuthMap.objects.get(user=user_wo_map)) self.assertRedirects(response, '/dashboard') self.assertEquals( int(self.client.session['_auth_user_id']), user_wo_map.id) # verify logging: self.assertEquals(len(audit_log_calls), 2) self._assert_shib_login_is_logged(audit_log_calls[0], remote_user) method_name, args, _kwargs = audit_log_calls[1] self.assertEquals(method_name, 'info') self.assertEquals(len(args), 1) self.assertIn(u'Login success', args[0]) self.assertIn(remote_user, args[0]) elif idp == "https://someother.idp.com/" and remote_user in \ ['*****@*****.**', '*****@*****.**', '*****@*****.**']: self.assertEqual(response.status_code, 403) self.assertIn( "You have already created an account using an external login", response.content) # no audit logging calls self.assertEquals(len(audit_log_calls), 0) else: self.assertEqual(response.status_code, 200) self.assertContains( response, ("Preferences for {platform_name}".format( platform_name=settings.PLATFORM_NAME))) # no audit logging calls self.assertEquals(len(audit_log_calls), 0)
def handle(self, *args, **options): while True: uname = raw_input('username: '******'Create MIT ExternalAuth? [n] ').lower() == 'y': email = '*****@*****.**' % uname if not email.endswith('@MIT.EDU'): print "Failed - email must be @MIT.EDU" sys.exit(-1) mit_domain = 'ssl:MIT' if ExternalAuthMap.objects.filter(external_id=email, external_domain=mit_domain): print "Failed - email %s already exists as external_id" % email sys.exit(-1) make_eamap = True password = GenPasswd(12) # get name from kerberos try: kname = os.popen( "finger %s | grep 'name:'" % email).read().strip().split('name: ')[1].strip() except: kname = '' name = raw_input('Full name: [%s] ' % kname).strip() if name == '': name = kname print "name = %s" % name else: while True: password = getpass() password2 = getpass() if password == password2: break print "Oops, passwords do not match, please retry" while True: email = raw_input('email: ') if User.objects.filter(email=email): print "email %s already taken" % email else: break name = raw_input('Full name: ') user = User(username=uname, email=email, is_active=True) user.set_password(password) try: user.save() except IntegrityError: print "Oops, failed to create user %s, IntegrityError" % user raise r = Registration() r.register(user) up = UserProfile(user=user) up.name = name up.save() if make_eamap: credentials = "/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN=%s/emailAddress=%s" % ( name, email) eamap = ExternalAuthMap( external_id=email, external_email=email, external_domain=mit_domain, external_name=name, internal_password=password, external_credentials=json.dumps(credentials), ) eamap.user = user eamap.dtsignup = datetime.datetime.now(UTC) eamap.save() print "User %s created successfully!" % user if not raw_input( 'Add user %s to any groups? [n] ' % user).lower() == 'y': sys.exit(0) print "Here are the groups available:" groups = [str(g.name) for g in Group.objects.all()] print groups completer = MyCompleter(groups) readline.set_completer(completer.complete) readline.parse_and_bind('tab: complete') while True: gname = raw_input( "Add group (tab to autocomplete, empty line to end): ") if not gname: break if gname not in groups: print "Unknown group %s" % gname continue g = Group.objects.get(name=gname) user.groups.add(g) print "Added %s to group %s" % (user, g) print "Done!"
def create_user(self, uname, name, password=None): """ Creates a user (both SSL and regular)""" if not uname: return _('Must provide username') if not name: return _('Must provide full name') email_domain = getattr(settings, 'SSL_AUTH_EMAIL_DOMAIN', 'MIT.EDU') msg = u'' if settings.FEATURES['AUTH_USE_CERTIFICATES']: if not '@' in uname: email = '{0}@{1}'.format(uname, email_domain) else: email = uname if not email.endswith('@{0}'.format(email_domain)): msg += u'{0} @{1}'.format(_('email must end in'), email_domain) return msg mit_domain = 'ssl:MIT' if ExternalAuthMap.objects.filter(external_id=email, external_domain=mit_domain): msg += _('Failed - email {0} already exists as ' 'external_id').format(email) return msg new_password = generate_password() else: if not password: return _('Password must be supplied if not using certificates') email = uname if not '@' in email: msg += _('email address required (not username)') return msg new_password = password user = User(username=uname, email=email, is_active=True) user.set_password(new_password) try: user.save() except IntegrityError: msg += _('Oops, failed to create user {0}, ' 'IntegrityError').format(user) return msg reg = Registration() reg.register(user) profile = UserProfile(user=user) profile.name = name profile.save() if settings.FEATURES['AUTH_USE_CERTIFICATES']: credential_string = getattr( settings, 'SSL_AUTH_DN_FORMAT_STRING', '/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN={0}/emailAddress={1}' ) credentials = credential_string.format(name, email) eamap = ExternalAuthMap( external_id=email, external_email=email, external_domain=mit_domain, external_name=name, internal_password=new_password, external_credentials=json.dumps(credentials), ) eamap.user = user eamap.dtsignup = timezone.now() eamap.save() msg += _('User {0} created successfully!').format(user) return msg
def _external_login_or_signup(request, external_id, external_domain, credentials, email, fullname, retfun=None): """Generic external auth login or signup""" # see if we have a map from this external_id to an edX username try: eamap = ExternalAuthMap.objects.get(external_id=external_id, external_domain=external_domain) log.debug(u'Found eamap=%s', eamap) except ExternalAuthMap.DoesNotExist: # go render form for creating edX user eamap = ExternalAuthMap(external_id=external_id, external_domain=external_domain, external_credentials=json.dumps(credentials)) eamap.external_email = email eamap.external_name = fullname eamap.internal_password = generate_password() log.debug(u'Created eamap=%s', eamap) eamap.save() log.info(u"External_Auth login_or_signup for %s : %s : %s : %s", external_domain, external_id, email, fullname) uses_shibboleth = settings.FEATURES.get( 'AUTH_USE_SHIB') and external_domain.startswith( SHIBBOLETH_DOMAIN_PREFIX) uses_certs = settings.FEATURES.get('AUTH_USE_CERTIFICATES') internal_user = eamap.user if internal_user is None: if uses_shibboleth: # If we are using shib, try to link accounts # For Stanford shib, the email the idp returns is actually under the control of the user. # Since the id the idps return is not user-editable, and is of the from "*****@*****.**", # use the id to link accounts instead. try: link_user = User.objects.get(email=eamap.external_id) if not ExternalAuthMap.objects.filter(user=link_user).exists(): # if there's no pre-existing linked eamap, we link the user eamap.user = link_user eamap.save() internal_user = link_user log.info(u'SHIB: Linking existing account for %s', eamap.external_id) # now pass through to log in else: # otherwise, there must have been an error, b/c we've already linked a user with these external # creds failure_msg = _( "You have already created an account using " "an external login like WebAuth or Shibboleth. " "Please contact {tech_support_email} for support." ).format(tech_support_email=settings.TECH_SUPPORT_EMAIL, ) return default_render_failure(request, failure_msg) except User.DoesNotExist: log.info(u'SHIB: No user for %s yet, doing signup', eamap.external_email) return _signup(request, eamap, retfun) else: log.info(u'No user for %s yet. doing signup', eamap.external_email) return _signup(request, eamap, retfun) # We trust shib's authentication, so no need to authenticate using the password again uname = internal_user.username if uses_shibboleth: user = internal_user # Assuming this 'AUTHENTICATION_BACKENDS' is set in settings, which I think is safe if settings.AUTHENTICATION_BACKENDS: auth_backend = settings.AUTHENTICATION_BACKENDS[0] else: auth_backend = 'ratelimitbackend.backends.RateLimitModelBackend' user.backend = auth_backend if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.info( u'Linked user.id: {0} logged in via Shibboleth'.format( user.id)) else: AUDIT_LOG.info( u'Linked user "{0}" logged in via Shibboleth'.format( user.email)) elif uses_certs: # Certificates are trusted, so just link the user and log the action user = internal_user user.backend = 'ratelimitbackend.backends.RateLimitModelBackend' if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.info( u'Linked user_id {0} logged in via SSL certificate'.format( user.id)) else: AUDIT_LOG.info( u'Linked user "{0}" logged in via SSL certificate'.format( user.email)) else: user = authenticate(username=uname, password=eamap.internal_password, request=request) if user is None: # we want to log the failure, but don't want to log the password attempted: if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.warning(u'External Auth Login failed') else: AUDIT_LOG.warning( u'External Auth Login failed for "{0}"'.format(uname)) return _signup(request, eamap, retfun) if not user.is_active: if settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'): # if BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH, we trust external auth and activate any users # that aren't already active user.is_active = True user.save() if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.info( u'Activating user {0} due to external auth'.format( user.id)) else: AUDIT_LOG.info( u'Activating user "{0}" due to external auth'.format( uname)) else: if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.warning( u'User {0} is not active after external login'.format( user.id)) else: AUDIT_LOG.warning( u'User "{0}" is not active after external login'.format( uname)) # TODO: improve error page msg = 'Account not yet activated: please look for link in your email' return default_render_failure(request, msg) login(request, user) request.session.set_expiry(0) if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.info(u"Login success - user.id: {0}".format(user.id)) else: AUDIT_LOG.info(u"Login success - {0} ({1})".format( user.username, user.email)) if retfun is None: return redirect('/') return retfun()
def _external_login_or_signup(request, external_id, external_domain, credentials, email, fullname, retfun=None): """Generic external auth login or signup""" logout(request) # see if we have a map from this external_id to an edX username try: eamap = ExternalAuthMap.objects.get(external_id=external_id, external_domain=external_domain) log.debug('Found eamap=%s', eamap) except ExternalAuthMap.DoesNotExist: # go render form for creating edX user eamap = ExternalAuthMap(external_id=external_id, external_domain=external_domain, external_credentials=json.dumps(credentials)) eamap.external_email = email eamap.external_name = fullname eamap.internal_password = generate_password() log.debug('Created eamap=%s', eamap) eamap.save() log.info(u"External_Auth login_or_signup for %s : %s : %s : %s", external_domain, external_id, email, fullname) uses_shibboleth = settings.MITX_FEATURES.get('AUTH_USE_SHIB') and external_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX) internal_user = eamap.user if internal_user is None: if uses_shibboleth: # If we are using shib, try to link accounts # For Stanford shib, the email the idp returns is actually under the control of the user. # Since the id the idps return is not user-editable, and is of the from "*****@*****.**", # use the id to link accounts instead. try: link_user = User.objects.get(email=eamap.external_id) if not ExternalAuthMap.objects.filter(user=link_user).exists(): # if there's no pre-existing linked eamap, we link the user eamap.user = link_user eamap.save() internal_user = link_user log.info('SHIB: Linking existing account for %s', eamap.external_id) # now pass through to log in else: # otherwise, there must have been an error, b/c we've already linked a user with these external # creds failure_msg = _(dedent(""" You have already created an account using an external login like WebAuth or Shibboleth. Please contact %s for support """ % getattr(settings, 'TECH_SUPPORT_EMAIL', '*****@*****.**'))) return default_render_failure(request, failure_msg) except User.DoesNotExist: log.info('SHIB: No user for %s yet, doing signup', eamap.external_email) return _signup(request, eamap) else: log.info('No user for %s yet. doing signup', eamap.external_email) return _signup(request, eamap) # We trust shib's authentication, so no need to authenticate using the password again uname = internal_user.username if uses_shibboleth: user = internal_user # Assuming this 'AUTHENTICATION_BACKENDS' is set in settings, which I think is safe if settings.AUTHENTICATION_BACKENDS: auth_backend = settings.AUTHENTICATION_BACKENDS[0] else: auth_backend = 'django.contrib.auth.backends.ModelBackend' user.backend = auth_backend AUDIT_LOG.info('Linked user "%s" logged in via Shibboleth', user.email) else: user = authenticate(username=uname, password=eamap.internal_password, request=request) if user is None: # we want to log the failure, but don't want to log the password attempted: AUDIT_LOG.warning('External Auth Login failed for "%s"', uname) return _signup(request, eamap) if not user.is_active: AUDIT_LOG.warning('User "%s" is not active after external login', uname) # TODO: improve error page msg = 'Account not yet activated: please look for link in your email' return default_render_failure(request, msg) login(request, user) request.session.set_expiry(0) # Now to try enrollment # Need to special case Shibboleth here because it logs in via a GET. # testing request.method for extra paranoia if uses_shibboleth and request.method == 'GET': enroll_request = _make_shib_enrollment_request(request) student.views.try_change_enrollment(enroll_request) else: student.views.try_change_enrollment(request) AUDIT_LOG.info("Login success - %s (%s)", user.username, user.email) if retfun is None: return redirect('/') return retfun()