Пример #1
0
    def test_staff_access_country_block(self, staff_role_cls):
        # Add a country to the blacklist
        CountryAccessRule.objects.create(
            rule_type=CountryAccessRule.BLACKLIST_RULE,
            restricted_course=self.restricted_course,
            country=Country.objects.get(country='US')
        )

        # Appear to make a request from an IP in the blocked country
        with self._mock_geoip('US'):
            result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')

        # Expect that the user is blocked, because the user isn't staff
        self.assertFalse(result, msg="User should not have access because the user isn't staff.")

        # Instantiate the role, configuring it for this course or org
        if issubclass(staff_role_cls, CourseRole):
            staff_role = staff_role_cls(self.course.id)
        elif issubclass(staff_role_cls, OrgRole):
            staff_role = staff_role_cls(self.course.id.org)
        else:
            staff_role = staff_role_cls()

        # Add the user to the role
        staff_role.add_users(self.user)

        # Now the user should have access
        with self._mock_geoip('US'):
            result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')

        self.assertTrue(result, msg="User should have access because the user is staff.")
Пример #2
0
    def test_staff_access_country_block(self, staff_role_cls):
        # Add a country to the blacklist
        CountryAccessRule.objects.create(
            rule_type=CountryAccessRule.BLACKLIST_RULE,
            restricted_course=self.restricted_course,
            country=Country.objects.get(country='US')
        )

        # Appear to make a request from an IP in the blocked country
        with self._mock_geoip('US'):
            result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')

        # Expect that the user is blocked, because the user isn't staff
        self.assertFalse(result, msg="User should not have access because the user isn't staff.")

        # Instantiate the role, configuring it for this course or org
        if issubclass(staff_role_cls, CourseRole):
            staff_role = staff_role_cls(self.course.id)
        elif issubclass(staff_role_cls, OrgRole):
            staff_role = staff_role_cls(self.course.id.org)
        else:
            staff_role = staff_role_cls()

        # Add the user to the role
        staff_role.add_users(self.user)

        # Now the user should have access
        with self._mock_geoip('US'):
            result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')

        self.assertTrue(result, msg="User should have access because the user is staff.")
Пример #3
0
    def test_caching_no_restricted_courses(self):
        RestrictedCourse.objects.all().delete()
        cache.clear()

        with self.assertNumQueries(1):
            embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')

        with self.assertNumQueries(0):
            embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')
Пример #4
0
    def test_caching_no_restricted_courses(self):
        RestrictedCourse.objects.all().delete()
        cache.clear()

        with self.assertNumQueries(1):
            embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')

        with self.assertNumQueries(0):
            embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')
Пример #5
0
    def test_caching(self):
        # Test the scenario that will go through every check
        # (restricted course, but pass all the checks)
        # This is the worst case, so it will hit all of the
        # caching code.
        with self.assertNumQueries(3):
            embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')

        with self.assertNumQueries(0):
            embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')
Пример #6
0
    def test_course_not_restricted(self):
        # No restricted course model for this course key,
        # so all access checks should be skipped.
        unrestricted_course = CourseFactory.create()
        with self.assertNumQueries(1):
            embargo_api.check_course_access(unrestricted_course.id, user=self.user, ip_address='0.0.0.0')

        # The second check should require no database queries
        with self.assertNumQueries(0):
            embargo_api.check_course_access(unrestricted_course.id, user=self.user, ip_address='0.0.0.0')
Пример #7
0
    def test_course_not_restricted(self):
        # No restricted course model for this course key,
        # so all access checks should be skipped.
        unrestricted_course = CourseFactory.create()
        with self.assertNumQueries(1):
            embargo_api.check_course_access(unrestricted_course.id, user=self.user, ip_address='0.0.0.0')

        # The second check should require no database queries
        with self.assertNumQueries(0):
            embargo_api.check_course_access(unrestricted_course.id, user=self.user, ip_address='0.0.0.0')
Пример #8
0
    def test_caching(self):
        # Test the scenario that will go through every check
        # (restricted course, but pass all the checks)
        # This is the worst case, so it will hit all of the
        # caching code.
        with self.assertNumQueries(3):
            embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')

        with self.assertNumQueries(0):
            embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')
Пример #9
0
    def test_country_access_rules(self, ip_country, profile_country, blacklist,
                                  whitelist, allow_access):
        # Configure the access rules
        for whitelist_country in whitelist:
            CountryAccessRule.objects.create(
                rule_type=CountryAccessRule.WHITELIST_RULE,
                restricted_course=self.restricted_course,
                country=Country.objects.get(country=whitelist_country))

        for blacklist_country in blacklist:
            CountryAccessRule.objects.create(
                rule_type=CountryAccessRule.BLACKLIST_RULE,
                restricted_course=self.restricted_course,
                country=Country.objects.get(country=blacklist_country))

        # Configure the user's profile country
        if profile_country is not None:
            self.user.profile.country = profile_country
            self.user.profile.save()

        # Appear to make a request from an IP in a particular country
        with self._mock_geoip(ip_country):
            # Call the API.  Note that the IP address we pass in doesn't
            # matter, since we're injecting a mock for geo-location
            result = embargo_api.check_course_access(self.course.id,
                                                     user=self.user,
                                                     ip_address='0.0.0.0')

        # Verify that the access rules were applied correctly
        self.assertEqual(result, allow_access)
Пример #10
0
 def test_country_access_fallback_to_continent_code(self):
     # Simulate PyGeoIP falling back to a continent code
     # instead of a country code.  In this case, we should
     # allow the user access.
     with self._mock_geoip('EU'):
         result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')
         self.assertTrue(result)
Пример #11
0
 def test_country_access_fallback_to_continent_code(self):
     # Simulate PyGeoIP falling back to a continent code
     # instead of a country code.  In this case, we should
     # allow the user access.
     with self._mock_geoip('EU'):
         result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')
         self.assertTrue(result)
Пример #12
0
    def test_country_access_rules(self, ip_country, profile_country, blacklist, whitelist, allow_access):
        # Configure the access rules
        for whitelist_country in whitelist:
            CountryAccessRule.objects.create(
                rule_type=CountryAccessRule.WHITELIST_RULE,
                restricted_course=self.restricted_course,
                country=Country.objects.get(country=whitelist_country)
            )

        for blacklist_country in blacklist:
            CountryAccessRule.objects.create(
                rule_type=CountryAccessRule.BLACKLIST_RULE,
                restricted_course=self.restricted_course,
                country=Country.objects.get(country=blacklist_country)
            )

        # Configure the user's profile country
        if profile_country is not None:
            self.user.profile.country = profile_country
            self.user.profile.save()

        # Appear to make a request from an IP in a particular country
        with mock.patch.object(pygeoip.GeoIP, 'country_code_by_addr') as mock_ip:
            mock_ip.return_value = ip_country

            # Call the API.  Note that the IP address we pass in doesn't
            # matter, since we're injecting a mock for geo-location
            result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')

        # Verify that the access rules were applied correctly
        self.assertEqual(result, allow_access)
Пример #13
0
 def test_ip_v6(self):
     # Test the scenario that will go through every check
     # (restricted course, but pass all the checks)
     result = embargo_api.check_course_access(
         self.course.id,
         user=self.user,
         ip_address='FE80::0202:B3FF:FE1E:8329')
     self.assertTrue(result)
Пример #14
0
    def test_no_user_has_access(self):
        CountryAccessRule.objects.create(
            rule_type=CountryAccessRule.BLACKLIST_RULE,
            restricted_course=self.restricted_course,
            country=Country.objects.get(country='US')
        )

        # The user is set to None, because the user has not been authenticated.
        result = embargo_api.check_course_access(self.course.id, ip_address='0.0.0.0')
        self.assertTrue(result)
Пример #15
0
    def test_no_user_has_access(self):
        CountryAccessRule.objects.create(
            rule_type=CountryAccessRule.BLACKLIST_RULE,
            restricted_course=self.restricted_course,
            country=Country.objects.get(country='US'))

        # The user is set to None, because the user has not been authenticated.
        result = embargo_api.check_course_access(self.course.id,
                                                 ip_address='0.0.0.0')
        self.assertTrue(result)
Пример #16
0
    def test_no_user_blocked(self):
        CountryAccessRule.objects.create(
            rule_type=CountryAccessRule.BLACKLIST_RULE,
            restricted_course=self.restricted_course,
            country=Country.objects.get(country='US')
        )

        with mock.patch.object(pygeoip.GeoIP, 'country_code_by_addr') as mock_ip:
            mock_ip.return_value = 'US'

            # The user is set to None, because the user has not been authenticated.
            result = embargo_api.check_course_access(self.course.id, ip_address='0.0.0.0')
            self.assertFalse(result)
Пример #17
0
    def test_no_user_blocked(self):
        CountryAccessRule.objects.create(
            rule_type=CountryAccessRule.BLACKLIST_RULE,
            restricted_course=self.restricted_course,
            country=Country.objects.get(country='US')
        )

        with mock.patch.object(pygeoip.GeoIP, 'country_code_by_addr') as mock_ip:
            mock_ip.return_value = 'US'

            # The user is set to None, because the user has not been authenticated.
            result = embargo_api.check_course_access(self.course.id, ip_address='0.0.0.0')
            self.assertFalse(result)
Пример #18
0
    def test_profile_country_db_null(self):
        # Django country fields treat NULL values inconsistently.
        # When saving a profile with country set to None, Django saves an empty string to the database.
        # However, when the country field loads a NULL value from the database, it sets
        # `country.code` to `None`.  This caused a bug in which country values created by
        # the original South schema migration -- which defaulted to NULL -- caused a runtime
        # exception when the embargo middleware treated the value as a string.
        # In order to simulate this behavior, we can't simply set `profile.country = None`.
        # (because when we save it, it will set the database field to an empty string instead of NULL)
        query = "UPDATE auth_userprofile SET country = NULL WHERE id = %s"
        connection.cursor().execute(query, [str(self.user.profile.id)])

        # Verify that we can check the user's access without error
        result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')
        self.assertTrue(result)
Пример #19
0
    def test_profile_country_db_null(self):
        # Django country fields treat NULL values inconsistently.
        # When saving a profile with country set to None, Django saves an empty string to the database.
        # However, when the country field loads a NULL value from the database, it sets
        # `country.code` to `None`.  This caused a bug in which country values created by
        # the original South schema migration -- which defaulted to NULL -- caused a runtime
        # exception when the embargo middleware treated the value as a string.
        # In order to simulate this behavior, we can't simply set `profile.country = None`.
        # (because when we save it, it will set the database field to an empty string instead of NULL)
        query = "UPDATE auth_userprofile SET country = NULL WHERE id = %s"
        connection.cursor().execute(query, [str(self.user.profile.id)])

        # Verify that we can check the user's access without error
        result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='0.0.0.0')
        self.assertTrue(result)
Пример #20
0
def change_enrollment(strategy, auth_entry=None, user=None, *args, **kwargs):
    """Enroll a user in a course.

    If a user entered the authentication flow when trying to enroll
    in a course, then attempt to enroll the user.
    We will try to do this if the pipeline was started with the
    querystring param `enroll_course_id`.

    In the following cases, we can't enroll the user:
        * The course does not have an honor mode.
        * The course has an honor mode with a minimum price.
        * The course is not yet open for enrollment.
        * The course does not exist.

    If we can't enroll the user now, then skip this step.
    For paid courses, users will be redirected to the payment flow
    upon completion of the authentication pipeline
    (configured using the ?next parameter to the third party auth login url).

    Keyword Arguments:
        auth_entry: The entry mode into the pipeline.
        user (User): The user being authenticated.
    """
    # We skip enrollment if the user entered the flow from the "link account"
    # button on the account settings page.  At this point, either:
    #
    # 1) The user already had a linked account when they started the enrollment flow,
    # in which case they would have been enrolled during the normal authentication process.
    #
    # 2) The user did NOT have a linked account, in which case they would have
    # needed to go through the login/register page.  Since we preserve the querystring
    # args when sending users to this page, successfully authenticating through this page
    # would also enroll the student in the course.
    enroll_course_id = strategy.session_get('enroll_course_id')
    if enroll_course_id and auth_entry != AUTH_ENTRY_ACCOUNT_SETTINGS:
        course_id = CourseKey.from_string(enroll_course_id)
        modes = CourseMode.modes_for_course_dict(course_id)

        # If the email opt in parameter is found, set the preference.
        email_opt_in = strategy.session_get(AUTH_EMAIL_OPT_IN_KEY)
        if email_opt_in:
            opt_in = email_opt_in.lower() == 'true'
            update_email_opt_in(user, course_id.org, opt_in)

        # Check whether we're blocked from enrolling by a
        # country access rule.
        # Note: We skip checking the user's profile setting
        # for country here because the "redirect URL" pointing
        # to the blocked message page is set when the user
        # *enters* the pipeline, at which point they're
        # not authenticated.  If they end up being blocked
        # from the courseware, it's better to let them
        # enroll and then show the message when they
        # enter the course than to skip enrollment
        # altogether.
        is_blocked = not embargo_api.check_course_access(
            course_id, ip_address=get_ip(strategy.request),
            url=strategy.request.path
        )
        if is_blocked:
            # If we're blocked, skip enrollment.
            # A redirect URL should have been set so the user
            # ends up on the embargo page when enrollment completes.
            pass

        elif CourseMode.can_auto_enroll(course_id, modes_dict=modes):
            try:
                CourseEnrollment.enroll(user, course_id, check_access=True)
            except CourseEnrollmentException:
                pass
            except Exception as ex:
                logger.exception(ex)

        # Handle white-label courses as a special case
        # If a course is white-label, we should add it to the shopping cart.
        elif CourseMode.is_white_label(course_id, modes_dict=modes):
            try:
                cart = Order.get_cart_for_user(user)
                PaidCourseRegistration.add_to_order(cart, course_id)
            except (
                CourseDoesNotExistException,
                ItemAlreadyInCartException,
                AlreadyEnrolledInCourseException,
            ):
                pass
            # It's more important to complete login than to
            # ensure that the course was added to the shopping cart.
            # Log errors, but don't stop the authentication pipeline.
            except Exception as ex:  # pylint: disable=broad-except
                logger.exception(ex)
Пример #21
0
 def test_ip_v6(self):
     # Test the scenario that will go through every check
     # (restricted course, but pass all the checks)
     result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='FE80::0202:B3FF:FE1E:8329')
     self.assertTrue(result)
Пример #22
0
def change_enrollment(strategy, auth_entry=None, user=None, *args, **kwargs):
    """Enroll a user in a course.

    If a user entered the authentication flow when trying to enroll
    in a course, then attempt to enroll the user.
    We will try to do this if the pipeline was started with the
    querystring param `enroll_course_id`.

    In the following cases, we can't enroll the user:
        * The course does not have an honor mode.
        * The course has an honor mode with a minimum price.
        * The course is not yet open for enrollment.
        * The course does not exist.

    If we can't enroll the user now, then skip this step.
    For paid courses, users will be redirected to the payment flow
    upon completion of the authentication pipeline
    (configured using the ?next parameter to the third party auth login url).

    Keyword Arguments:
        auth_entry: The entry mode into the pipeline.
        user (User): The user being authenticated.
    """
    # We skip enrollment if the user entered the flow from the "link account"
    # button on the student dashboard.  At this point, either:
    #
    # 1) The user already had a linked account when they started the enrollment flow,
    # in which case they would have been enrolled during the normal authentication process.
    #
    # 2) The user did NOT have a linked account, in which case they would have
    # needed to go through the login/register page.  Since we preserve the querystring
    # args when sending users to this page, successfully authenticating through this page
    # would also enroll the student in the course.
    enroll_course_id = strategy.session_get('enroll_course_id')
    if enroll_course_id and auth_entry != AUTH_ENTRY_DASHBOARD:
        course_id = CourseKey.from_string(enroll_course_id)
        modes = CourseMode.modes_for_course_dict(course_id)

        # If the email opt in parameter is found, set the preference.
        email_opt_in = strategy.session_get(AUTH_EMAIL_OPT_IN_KEY)
        if email_opt_in:
            opt_in = email_opt_in.lower() == 'true'
            update_email_opt_in(user, course_id.org, opt_in)

        # Check whether we're blocked from enrolling by a
        # country access rule.
        # Note: We skip checking the user's profile setting
        # for country here because the "redirect URL" pointing
        # to the blocked message page is set when the user
        # *enters* the pipeline, at which point they're
        # not authenticated.  If they end up being blocked
        # from the courseware, it's better to let them
        # enroll and then show the message when they
        # enter the course than to skip enrollment
        # altogether.
        is_blocked = not embargo_api.check_course_access(
            course_id,
            ip_address=get_ip(strategy.request),
            url=strategy.request.path)
        if is_blocked:
            # If we're blocked, skip enrollment.
            # A redirect URL should have been set so the user
            # ends up on the embargo page when enrollment completes.
            pass

        elif CourseMode.can_auto_enroll(course_id, modes_dict=modes):
            try:
                CourseEnrollment.enroll(user, course_id, check_access=True)
            except CourseEnrollmentException:
                pass
            except Exception as ex:
                logger.exception(ex)

        # Handle white-label courses as a special case
        # If a course is white-label, we should add it to the shopping cart.
        elif CourseMode.is_white_label(course_id, modes_dict=modes):
            try:
                cart = Order.get_cart_for_user(user)
                PaidCourseRegistration.add_to_order(cart, course_id)
            except (
                    CourseDoesNotExistException,
                    ItemAlreadyInCartException,
                    AlreadyEnrolledInCourseException,
            ):
                pass
            # It's more important to complete login than to
            # ensure that the course was added to the shopping cart.
            # Log errors, but don't stop the authentication pipeline.
            except Exception as ex:  # pylint: disable=broad-except
                logger.exception(ex)