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): """ 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() with mock.patch('edxmako.request_context.get_current_request', return_value=request): with mock.patch('django.core.mail.send_mail') as mock_send_mail: student.views.create_account(request) # check that send_mail is called if bypass_activation_email: self.assertFalse(mock_send_mail.called) else: self.assertTrue(mock_send_mail.called)
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 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/', 'HTTP_ACCEPT': "text/html" } 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_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', text_type(course.id)), ('enrollment_action', 'enroll'), ('next', '/testredirect') ] request_kwargs = {'path': '/shib-login/', 'data': dict(params), 'follow': False, 'REMOTE_USER': '******', 'Shib-Identity-Provider': 'https://idp.stanford.edu/', 'HTTP_ACCEPT': "text/html"} 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'], expected_redirect_url('/account/finish_auth?{}'.format(urlencode(params))))
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) request.site = SiteFactory.create() # 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 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 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!"
class ExternalAuthShibTest(ModuleStoreTestCase): """ Tests how login_user() interacts with ExternalAuth, in particular Shib """ 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() @unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set") def test_login_page_redirect(self): """ Tests that when a shib user types their email address into the login page, they get redirected to the shib login. """ response = self.client.post(reverse('login'), { 'email': self.user_w_map.email, 'password': '' }) self.assertEqual(response.status_code, 200) obj = json.loads(response.content) self.assertEqual(obj, { 'success': False, 'redirect': reverse('shib-login'), }) @unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set") def test_login_required_dashboard(self): """ Tests redirects to when @login_required to dashboard, which should always be the normal login, since there is no course context """ response = self.client.get(reverse('dashboard')) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], '/login?next=/dashboard') @unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set") def test_externalauth_login_required_course_context(self): """ Tests the redirects when visiting course-specific URL with @login_required. Should vary by course depending on its enrollment_domain """ target_url = reverse('courseware', args=[text_type(self.course.id)]) noshib_response = self.client.get(target_url, follow=True, HTTP_ACCEPT="text/html") self.assertEqual(noshib_response.redirect_chain[-1], ('/login?next={url}'.format(url=target_url), 302)) self.assertContains(noshib_response, (u"Sign in or Register | {platform_name}".format( platform_name=settings.PLATFORM_NAME))) self.assertEqual(noshib_response.status_code, 200) target_url_shib = reverse('courseware', args=[text_type(self.shib_course.id)]) shib_response = self.client.get( **{ 'path': target_url_shib, 'follow': True, 'REMOTE_USER': self.extauth.external_id, 'Shib-Identity-Provider': 'https://idp.stanford.edu/', 'HTTP_ACCEPT': "text/html" }) # Test that the shib-login redirect page with ?next= and the desired page are part of the redirect chain # The 'courseware' page actually causes a redirect itself, so it's not the end of the chain and we # won't test its contents self.assertEqual( shib_response.redirect_chain[-3], ('/shib-login/?next={url}'.format(url=target_url_shib), 302)) self.assertEqual(shib_response.redirect_chain[-2], (target_url_shib, 302)) self.assertEqual(shib_response.status_code, 200)
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)): # Translators: Domain is an email domain, such as "@gmail.com" msg += _('Email address must end in {domain}').format(domain="@{0}".format(email_domain)) return msg mit_domain = 'ssl:MIT' if ExternalAuthMap.objects.filter(external_id=email, external_domain=mit_domain): msg += _('Failed - email {email_addr} already exists as {external_id}').format( email_addr=email, external_id="external_id" ) 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 {user}, {error}').format( user=user, error="IntegrityError" ) 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 {user} created successfully!').format(user=user) return msg
def _external_login_or_signup(request, external_id, external_domain, credentials, email, fullname, retfun=None): """Generic external auth login or signup""" # pylint: disable=too-many-statements # 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=get_value('email_from_address', 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 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)): # Translators: Domain is an email domain, such as "@gmail.com" msg += _('Email address must end in {domain}').format( domain="@{0}".format(email_domain)) return msg mit_domain = 'ssl:MIT' if ExternalAuthMap.objects.filter(external_id=email, external_domain=mit_domain): msg += _( 'Failed - email {email_addr} already exists as {external_id}' ).format(email_addr=email, external_id="external_id") 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 {user}, {error}').format( user=user, error="IntegrityError") 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 {user} created successfully!').format(user=user) return msg
class ExternalAuthShibTest(ModuleStoreTestCase): """ Tests how login_user() interacts with ExternalAuth, in particular Shib """ 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() @unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set") def test_login_page_redirect(self): """ Tests that when a shib user types their email address into the login page, they get redirected to the shib login. """ response = self.client.post(reverse('login'), {'email': self.user_w_map.email, 'password': ''}) self.assertEqual(response.status_code, 200) obj = json.loads(response.content) self.assertEqual(obj, { 'success': False, 'redirect': reverse('shib-login'), }) @unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set") def test_login_required_dashboard(self): """ Tests redirects to when @login_required to dashboard, which should always be the normal login, since there is no course context """ response = self.client.get(reverse('dashboard')) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], 'http://testserver/login?next=/dashboard') @unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set") def test_externalauth_login_required_course_context(self): """ Tests the redirects when visiting course-specific URL with @login_required. Should vary by course depending on its enrollment_domain """ TARGET_URL = reverse('courseware', args=[self.course.id.to_deprecated_string()]) # pylint: disable=invalid-name noshib_response = self.client.get(TARGET_URL, follow=True, HTTP_ACCEPT="text/html") self.assertEqual(noshib_response.redirect_chain[-1], ('http://testserver/login?next={url}'.format(url=TARGET_URL), 302)) self.assertContains(noshib_response, (u"Sign in or Register | {platform_name}" .format(platform_name=settings.PLATFORM_NAME))) self.assertEqual(noshib_response.status_code, 200) TARGET_URL_SHIB = reverse('courseware', args=[self.shib_course.id.to_deprecated_string()]) # pylint: disable=invalid-name shib_response = self.client.get(**{'path': TARGET_URL_SHIB, 'follow': True, 'REMOTE_USER': self.extauth.external_id, 'Shib-Identity-Provider': 'https://idp.stanford.edu/', 'HTTP_ACCEPT': "text/html"}) # Test that the shib-login redirect page with ?next= and the desired page are part of the redirect chain # The 'courseware' page actually causes a redirect itself, so it's not the end of the chain and we # won't test its contents self.assertEqual(shib_response.redirect_chain[-3], ('http://testserver/shib-login/?next={url}'.format(url=TARGET_URL_SHIB), 302)) self.assertEqual(shib_response.redirect_chain[-2], ('http://testserver{url}'.format(url=TARGET_URL_SHIB), 302)) self.assertEqual(shib_response.status_code, 200)
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 email_exists_or_retired(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!"
class ExternalAuthShibTest(ModuleStoreTestCase): """ Tests how login_user() interacts with ExternalAuth, in particular Shib """ 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() @unittest.skipUnless(settings.FEATURES.get("AUTH_USE_SHIB"), "AUTH_USE_SHIB not set") def test_login_page_redirect(self): """ Tests that when a shib user types their email address into the login page, they get redirected to the shib login. """ response = self.client.post(reverse("login"), {"email": self.user_w_map.email, "password": ""}) self.assertEqual(response.status_code, 200) obj = json.loads(response.content) self.assertEqual(obj, {"success": False, "redirect": reverse("shib-login")}) @unittest.skipUnless(settings.FEATURES.get("AUTH_USE_SHIB"), "AUTH_USE_SHIB not set") def test_login_required_dashboard(self): """ Tests redirects to when @login_required to dashboard, which should always be the normal login, since there is no course context """ response = self.client.get(reverse("dashboard")) self.assertEqual(response.status_code, 302) self.assertEqual(response["Location"], "http://testserver/login?next=/dashboard") @unittest.skipUnless(settings.FEATURES.get("AUTH_USE_SHIB"), "AUTH_USE_SHIB not set") def test_externalauth_login_required_course_context(self): """ Tests the redirects when visiting course-specific URL with @login_required. Should vary by course depending on its enrollment_domain """ TARGET_URL = reverse("courseware", args=[self.course.id.to_deprecated_string()]) # pylint: disable=invalid-name noshib_response = self.client.get(TARGET_URL, follow=True) self.assertEqual( noshib_response.redirect_chain[-1], ("http://testserver/login?next={url}".format(url=TARGET_URL), 302) ) self.assertContains( noshib_response, (u"Sign in or Register | {platform_name}".format(platform_name=settings.PLATFORM_NAME)) ) self.assertEqual(noshib_response.status_code, 200) TARGET_URL_SHIB = reverse( "courseware", args=[self.shib_course.id.to_deprecated_string()] ) # pylint: disable=invalid-name shib_response = self.client.get( **{ "path": TARGET_URL_SHIB, "follow": True, "REMOTE_USER": self.extauth.external_id, "Shib-Identity-Provider": "https://idp.stanford.edu/", } ) # Test that the shib-login redirect page with ?next= and the desired page are part of the redirect chain # The 'courseware' page actually causes a redirect itself, so it's not the end of the chain and we # won't test its contents self.assertEqual( shib_response.redirect_chain[-3], ("http://testserver/shib-login/?next={url}".format(url=TARGET_URL_SHIB), 302), ) self.assertEqual(shib_response.redirect_chain[-2], ("http://testserver{url}".format(url=TARGET_URL_SHIB), 302)) self.assertEqual(shib_response.status_code, 200)
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', enrollment_domain='shib:https://idp.stanford.edu/', user_id=self.test_user_id, ) open_enroll_course = CourseFactory.create( org='MITx', number='999', display_name='Robot Super Course', enrollment_domain='', user_id=self.test_user_id, ) # 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)) 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 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 """ # pylint: disable=too-many-statements 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( 'openedx.core.djangoapps.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, (u"Preferences for {platform_name}".format( platform_name=settings.PLATFORM_NAME))) # no audit logging calls self.assertEquals(len(audit_log_calls), 0)
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', enrollment_domain='shib:https://idp.stanford.edu/', user_id=self.test_user_id, ) open_enroll_course = CourseFactory.create( org='MITx', number='999', display_name='Robot Super Course', enrollment_domain='', user_id=self.test_user_id, ) # 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', data={'enrollment_action': 'enroll', 'course_id': text_type(course.id)} ) 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)) 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 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 """ # pylint: disable=too-many-statements 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('openedx.core.djangoapps.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, (u"Preferences for {platform_name}" .format(platform_name=settings.PLATFORM_NAME))) # no audit logging calls self.assertEquals(len(audit_log_calls), 0)