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")
Example #2
0
    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()
        request_kwargs = {
            "path": "/shib-login/",
            "data": {
                "enrollment_action": "enroll",
                "course_id": course.id.to_deprecated_string(),
                "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))
Example #3
0
 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()
Example #4
0
    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))
Example #5
0
    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()
        request_kwargs = {'path': '/shib-login/',
                          'data': {'enrollment_action': 'enroll', 'course_id': course.id.to_deprecated_string(), '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 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)
Example #7
0
    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")
Example #8
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')
        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 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)
Example #10
0
 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()
Example #11
0
 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()
Example #12
0
    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)))
Example #13
0
    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)))
Example #14
0
    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)))
Example #15
0
    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)
Example #16
0
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()
Example #17
0
 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()
Example #18
0
 def setUp(self):
     self.store = 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()
Example #19
0
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()
Example #20
0
    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'))
Example #21
0
    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'])
Example #22
0
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()

    log.info(u"External_Auth login_or_signup for %s : %s : %s : %s", external_domain, external_id, email, fullname)
    internal_user = eamap.user
    if internal_user is None:
        if settings.MITX_FEATURES.get('AUTH_USE_SHIB'):
            # if we are using shib, try to link accounts using email
            try:
                link_user = User.objects.get(email=eamap.external_email)
                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_email)
                    # 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
    if settings.MITX_FEATURES.get('AUTH_USE_SHIB'):
        uname = internal_user.username
        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
        log.info('SHIB: Logging in linked user %s', user.email)
    else:
        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)

    # Now to try enrollment
    # Need to special case Shibboleth here because it logs in via a GET.
    # testing request.method for extra paranoia
    if settings.MITX_FEATURES.get('AUTH_USE_SHIB') and 'shib:' in external_domain 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)
    log.info("Login success - %s (%s)", user.username, user.email)
    if retfun is None:
        return redirect('/')
    return retfun()
Example #23
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)
Example #24
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:
                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)
Example #25
0
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,
                            ("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")
        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})
                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))
Example #27
0
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()

    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("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, retfun)
        else:
            log.info("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 = "django.contrib.auth.backends.ModelBackend"
        user.backend = auth_backend
        if settings.FEATURES["SQUELCH_PII_IN_LOGS"]:
            AUDIT_LOG.info("Linked user.id: {0} logged in via Shibboleth".format(user.id))
        else:
            AUDIT_LOG.info('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 = "django.contrib.auth.backends.ModelBackend"
        if settings.FEATURES["SQUELCH_PII_IN_LOGS"]:
            AUDIT_LOG.info("Linked user_id {0} logged in via SSL certificate".format(user.id))
        else:
            AUDIT_LOG.info('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("External Auth Login failed")
        else:
            AUDIT_LOG.warning('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("Activating user {0} due to external auth".format(user.id))
            else:
                AUDIT_LOG.info('Activating user "{0}" due to external auth'.format(uname))
        else:
            if settings.FEATURES["SQUELCH_PII_IN_LOGS"]:
                AUDIT_LOG.warning("User {0} is not active after external login".format(user.id))
            else:
                AUDIT_LOG.warning('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)

    # 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)
    if settings.FEATURES["SQUELCH_PII_IN_LOGS"]:
        AUDIT_LOG.info("Login success - user.id: {0}".format(user.id))
    else:
        AUDIT_LOG.info("Login success - {0} ({1})".format(user.username, user.email))
    if retfun is None:
        return redirect("/")
    return retfun()
Example #28
0
class ExternalAuthShibTest(ModuleStoreTestCase):
    """
    Tests how login_user() interacts with ExternalAuth, in particular Shib
    """
    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/'
        metadata = own_metadata(self.shib_course)
        metadata['enrollment_domain'] = self.shib_course.enrollment_domain
        self.store.update_metadata(self.shib_course.location.url(), metadata)
        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.MITX_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)
        self.assertEqual(response.content, json.dumps({'success': False, 'redirect': reverse('shib-login')}))

    @unittest.skipUnless(settings.MITX_FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
    def test__get_course_enrollment_domain(self):
        """
        Tests the _get_course_enrollment_domain utility function
        """
        self.assertIsNone(_get_course_enrollment_domain("I/DONT/EXIST"))
        self.assertIsNone(_get_course_enrollment_domain(self.course.id))
        self.assertEqual(self.shib_course.enrollment_domain, _get_course_enrollment_domain(self.shib_course.id))

    @unittest.skipUnless(settings.MITX_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/accounts/login?next=/dashboard')

    @unittest.skipUnless(settings.MITX_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])            # pylint: disable=C0103
        noshib_response = self.client.get(TARGET_URL, follow=True)
        self.assertEqual(noshib_response.redirect_chain[-1],
                         ('http://testserver/accounts/login?next={url}'.format(url=TARGET_URL), 302))
        self.assertContains(noshib_response, ("<title>Log into your {platform_name} Account</title>"
                                              .format(platform_name=settings.PLATFORM_NAME)))
        self.assertEqual(noshib_response.status_code, 200)

        TARGET_URL_SHIB = reverse('courseware', args=[self.shib_course.id])  # pylint: disable=C0103
        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)
Example #29
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 not gname 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!"
Example #30
0
    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
Example #31
0
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__get_course_enrollment_domain(self):
        """
        Tests the _get_course_enrollment_domain utility function
        """
        self.assertIsNone(_get_course_enrollment_domain(SlashSeparatedCourseKey("I", "DONT", "EXIST")))
        self.assertIsNone(_get_course_enrollment_domain(self.course.id))
        self.assertEqual(self.shib_course.enrollment_domain, _get_course_enrollment_domain(self.shib_course.id))

    @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/accounts/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=C0103
        noshib_response = self.client.get(TARGET_URL, follow=True)
        self.assertEqual(noshib_response.redirect_chain[-1],
                         ('http://testserver/accounts/login?next={url}'.format(url=TARGET_URL), 302))
        self.assertContains(noshib_response, ("Log into your {platform_name} Account | {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=C0103
        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)
Example #32
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!"
Example #33
0
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()
Example #34
0
class ExternalAuthShibTest(ModuleStoreTestCase):
    """
    Tests how login_user() interacts with ExternalAuth, in particular Shib
    """

    def setUp(self):
        self.store = 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()

    @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__get_course_enrollment_domain(self):
        """
        Tests the _get_course_enrollment_domain utility function
        """
        self.assertIsNone(_get_course_enrollment_domain(SlashSeparatedCourseKey("I", "DONT", "EXIST")))
        self.assertIsNone(_get_course_enrollment_domain(self.course.id))
        self.assertEqual(self.shib_course.enrollment_domain, _get_course_enrollment_domain(self.shib_course.id))

    @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/accounts/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=C0103
        noshib_response = self.client.get(TARGET_URL, follow=True)
        self.assertEqual(
            noshib_response.redirect_chain[-1],
            ("http://testserver/accounts/login?next={url}".format(url=TARGET_URL), 302),
        )
        self.assertContains(
            noshib_response,
            ("Log into your {platform_name} Account | {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=C0103
        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)
Example #35
0
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()
Example #36
0
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()

    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)
    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, retfun)
        else:
            log.info('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 = '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, retfun)

    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()
Example #37
0
    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
Example #38
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:
                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()

                mako_middleware_process_request(request)
                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'], '/dashboard')
                    # 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.assertIsInstance(response, HttpResponseRedirect)
                    self.assertEqual(request.user, user_wo_map)
                    self.assertEqual(response['Location'], '/dashboard')
                    # 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)
Example #39
0
    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
Example #40
0
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()
Example #41
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:
                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>Preferences for {platform_name}</title>".format(platform_name=settings.PLATFORM_NAME)),
                    )
                    # no audit logging calls
                    self.assertEquals(len(audit_log_calls), 0)