Exemplo n.º 1
0
    def test_regenerate_user_is_not_enrolled(self):
        # Unenroll the user
        CourseEnrollment.unenroll(self.student, self.CERT_COURSE_KEY)

        # Can no longer regenerate certificates for the user
        response = self._regenerate(course_key=self.CERT_COURSE_KEY, username=self.STUDENT_USERNAME)
        self.assertEqual(response.status_code, 400)
Exemplo n.º 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))
Exemplo n.º 3
0
    def test_refund_cert_callback_before_expiration(self):
        # If the expiration date has not yet passed on a verified mode, the user can be refunded
        many_days = datetime.timedelta(days=60)

        course = CourseFactory.create()
        self.course_key = course.id
        course_mode = CourseMode(course_id=self.course_key,
                                 mode_slug="verified",
                                 mode_display_name="verified cert",
                                 min_price=self.cost,
                                 expiration_datetime=(datetime.datetime.now(pytz.utc) + many_days))
        course_mode.save()

        # need to prevent analytics errors from appearing in stderr
        with patch('sys.stderr', sys.stdout.write):
            CourseEnrollment.enroll(self.user, self.course_key, 'verified')
            cart = Order.get_cart_for_user(user=self.user)
            CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified')
            cart.purchase()
            CourseEnrollment.unenroll(self.user, self.course_key)

        target_certs = CertificateItem.objects.filter(course_id=self.course_key, user_id=self.user, status='refunded', mode='verified')
        self.assertTrue(target_certs[0])
        self.assertTrue(target_certs[0].refund_requested_time)
        self.assertEquals(target_certs[0].order.status, 'refunded')
        self._assert_refund_tracked()
Exemplo n.º 4
0
    def test_refund_cert_callback_before_expiration(self):
        # If the expiration date has not yet passed on a verified mode, the user can be refunded
        many_days = datetime.timedelta(days=60)

        course = CourseFactory.create(org="refund_before_expiration", number="test", display_name="one")
        course_key = course.id
        course_mode = CourseMode(
            course_id=course_key,
            mode_slug="verified",
            mode_display_name="verified cert",
            min_price=self.cost,
            expiration_datetime=(datetime.datetime.now(pytz.utc) + many_days),
        )
        course_mode.save()

        CourseEnrollment.enroll(self.user, course_key, "verified")
        cart = Order.get_cart_for_user(user=self.user)
        CertificateItem.add_to_order(cart, course_key, self.cost, "verified")
        cart.purchase()

        CourseEnrollment.unenroll(self.user, course_key)
        target_certs = CertificateItem.objects.filter(
            course_id=course_key, user_id=self.user, status="refunded", mode="verified"
        )
        self.assertTrue(target_certs[0])
        self.assertTrue(target_certs[0].refund_requested_time)
        self.assertEquals(target_certs[0].order.status, "refunded")
Exemplo n.º 5
0
    def test_unenrolled_from_some_courses(self):
        # Enroll in several courses in the org
        self._create_courses_and_enrollments(
            (self.TEST_ORG, True),
            (self.TEST_ORG, True),
            (self.TEST_ORG, True),
            ("org_alias", True)
        )

        # Set a preference for the aliased course
        self._set_opt_in_pref(self.user, "org_alias", False)

        # Unenroll from the aliased course
        CourseEnrollment.unenroll(self.user, self.courses[3].id, skip_refund=True)

        # Expect that the preference still applies,
        # and all the enrollments should appear in the list
        output = self._run_command(self.TEST_ORG, other_names=["org_alias"])
        self._assert_output(
            output,
            (self.user, self.courses[0].id, False),
            (self.user, self.courses[1].id, False),
            (self.user, self.courses[2].id, False),
            (self.user, self.courses[3].id, False)
        )
Exemplo n.º 6
0
    def perform_destroy(self, instance):
        """
        This method is an override and is called by the DELETE method
        """
        save_model = False
        if instance.expired_at is None:
            instance.expired_at = timezone.now()
            log.info('Set expired_at to [%s] for course entitlement [%s]', instance.expired_at, instance.uuid)
            save_model = True

        if instance.enrollment_course_run is not None:
            CourseEnrollment.unenroll(
                user=instance.user,
                course_id=instance.enrollment_course_run.course_id,
                skip_refund=True
            )
            enrollment = instance.enrollment_course_run
            instance.enrollment_course_run = None
            save_model = True
            log.info(
                'Unenrolled user [%s] from course run [%s] as part of revocation of course entitlement [%s]',
                instance.user.username,
                enrollment.course_id,
                instance.uuid
            )
        if save_model:
            instance.save()
Exemplo n.º 7
0
    def test_choose_mode_audit_enroll_on_post(self):
        audit_mode = 'audit'
        # Create the course modes
        for mode in (audit_mode, 'verified'):
            min_price = 0 if mode in [audit_mode] else 1
            CourseModeFactory.create(mode_slug=mode, course_id=self.course.id, min_price=min_price)

        # Assert learner is not enrolled in Audit track pre-POST
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        self.assertIsNone(mode)
        self.assertIsNone(is_active)

        # Choose the audit mode (POST request)
        choose_track_url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
        self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[audit_mode])

        # Assert learner is enrolled in Audit track post-POST
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        self.assertEqual(mode, audit_mode)
        self.assertTrue(is_active)

        # Unenroll learner from Audit track and confirm the enrollment record is now 'inactive'
        CourseEnrollment.unenroll(self.user, self.course.id)
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        self.assertEqual(mode, audit_mode)
        self.assertFalse(is_active)

        # Choose the audit mode again
        self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[audit_mode])

        # Assert learner is again enrolled in Audit track post-POST-POST
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        self.assertEqual(mode, audit_mode)
        self.assertTrue(is_active)
Exemplo n.º 8
0
    def test_unenroll_entitlement_with_audit_course_enrollment(self, mock_refund, mock_get_course_uuid):
        """
        Test that entitlement is not refunded if un-enroll is called on audit course un-enroll.
        """
        self.enrollment.mode = CourseMode.AUDIT
        self.enrollment.user = self.user
        self.enrollment.save()
        entitlement = CourseEntitlementFactory.create(user=self.user)
        mock_get_course_uuid.return_value = entitlement.course_uuid
        CourseEnrollment.unenroll(self.user, self.course.id)

        assert not mock_refund.called
        entitlement.refresh_from_db()
        assert entitlement.expired_at is None

        self.enrollment.mode = CourseMode.VERIFIED
        self.enrollment.is_active = True
        self.enrollment.save()
        entitlement.enrollment_course_run = self.enrollment
        entitlement.save()
        CourseEnrollment.unenroll(self.user, self.course.id)

        assert mock_refund.called
        entitlement.refresh_from_db()
        assert entitlement.expired_at < now()
Exemplo n.º 9
0
    def handle(self, *args, **options):
        csv_path = options['csv_path']
        with open(csv_path) as csvfile:
            reader = unicodecsv.DictReader(csvfile)
            for row in reader:
                username = row['username']
                email = row['email']
                course_key = row['course_id']
                try:
                    user = User.objects.get(Q(username=username) | Q(email=email))
                except ObjectDoesNotExist:
                    user = None
                    msg = 'User with username {} or email {} does not exist'.format(username, email)
                    logger.warning(msg)

                try:
                    course_id = CourseKey.from_string(course_key)
                except InvalidKeyError:
                    course_id = None
                    msg = 'Invalid course id {course_id}, skipping un-enrollement for {username}, {email}'.format(**row)
                    logger.warning(msg)

                if user and course_id:
                    enrollment = CourseEnrollment.get_enrollment(user, course_id)
                    if not enrollment:
                        msg = 'Enrollment for the user {} in course {} does not exist!'.format(username, course_key)
                        logger.info(msg)
                    else:
                        try:
                            CourseEnrollment.unenroll(user, course_id, skip_refund=True)
                        except Exception as err:
                            msg = 'Error un-enrolling User {} from course {}: '.format(username, course_key, err)
                            logger.error(msg, exc_info=True)
Exemplo n.º 10
0
    def test_certificate_exception_user_not_enrolled_error(self):
        """
        Test certificates exception addition api endpoint returns failure when called with
        username/email that is not enrolled in the given course.
        """
        # Un-enroll student from the course
        CourseEnrollment.unenroll(self.user, self.course.id)
        response = self.client.post(
            self.url,
            data=json.dumps(self.certificate_exception),
            content_type='application/json'
        )

        # Assert 400 status code in response
        self.assertEqual(response.status_code, 400)
        res_json = json.loads(response.content)

        # Assert Request not successful
        self.assertFalse(res_json['success'])

        # Assert Error Message
        self.assertEqual(
            res_json['message'],
            "{user} is not enrolled in this course. Please check your spelling and retry.".format(
                user=self.certificate_exception['user_name']
            )
        )
Exemplo n.º 11
0
    def handle(self, *args, **options):
        source = options["source_course"]
        dest = options["dest_course"]

        source_students = User.objects.filter(courseenrollment__course_id=source)

        for user in source_students:
            print("Moving {}.".format(user.username))
            # Find the old enrollment.
            enrollment = CourseEnrollment.objects.get(user=user, course_id=source)

            # Move the Student between the classes.
            mode = enrollment.mode
            old_is_active = enrollment.is_active
            CourseEnrollment.unenroll(user, source)
            new_enrollment = CourseEnrollment.enroll(user, dest, mode=mode)

            # Unenroll from the new coures if the user had unenrolled
            # form the old course.
            if not old_is_active:
                new_enrollment.update_enrollment(is_active=False)

            if mode == "verified":
                try:
                    certificate_item = CertificateItem.objects.get(course_id=source, course_enrollment=enrollment)
                except CertificateItem.DoesNotExist:
                    print("No certificate for {}".format(user))
                    continue

                certificate_item.course_id = dest
                certificate_item.course_enrollment = new_enrollment
                certificate_item.save()
Exemplo n.º 12
0
    def test_enrollment(self):
        user = User.objects.create_user("joe", "*****@*****.**", "password")
        course_id = "edX/Test101/2013"
        course_id_partial = "edX/Test101"

        # Test basic enrollment
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))
        self.assertFalse(CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))
        CourseEnrollment.enroll(user, course_id)
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
        self.assertTrue(CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))

        # Enrolling them again should be harmless
        CourseEnrollment.enroll(user, course_id)
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
        self.assertTrue(CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))

        # Now unenroll the user
        CourseEnrollment.unenroll(user, course_id)
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))
        self.assertFalse(CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))

        # Unenrolling them again should also be harmless
        CourseEnrollment.unenroll(user, course_id)
        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))
        self.assertFalse(CourseEnrollment.is_enrolled_by_partial(user, course_id_partial))

        # The enrollment record should still exist, just be inactive
        enrollment_record = CourseEnrollment.objects.get(user=user, course_id=course_id)
        self.assertFalse(enrollment_record.is_active)
Exemplo n.º 13
0
    def test_refund_cert_callback_before_expiration_email(self):
        """ Test that refund emails are being sent correctly. """
        course = CourseFactory.create()
        course_key = course.id
        many_days = datetime.timedelta(days=60)

        course_mode = CourseMode(course_id=course_key,
                                 mode_slug="verified",
                                 mode_display_name="verified cert",
                                 min_price=self.cost,
                                 expiration_datetime=datetime.datetime.now(pytz.utc) + many_days)
        course_mode.save()

        CourseEnrollment.enroll(self.user, course_key, 'verified')
        cart = Order.get_cart_for_user(user=self.user)
        CertificateItem.add_to_order(cart, course_key, self.cost, 'verified')
        cart.purchase()

        mail.outbox = []
        with patch('shoppingcart.models.log.error') as mock_error_logger:
            CourseEnrollment.unenroll(self.user, course_key)
            self.assertFalse(mock_error_logger.called)
            self.assertEquals(len(mail.outbox), 1)
            self.assertEquals('[Refund] User-Requested Refund', mail.outbox[0].subject)
            self.assertEquals(settings.PAYMENT_SUPPORT_EMAIL, mail.outbox[0].from_email)
            self.assertIn('has requested a refund on Order', mail.outbox[0].body)
Exemplo n.º 14
0
    def test_cea_enrolls_only_one_user(self):
        """
        Tests that a CourseEnrollmentAllowed can be used by just one user.
        If the user changes e-mail and then a second user tries to enroll with the same accepted e-mail,
        the second enrollment should fail.
        However, the original user can reuse the CEA many times.
        """

        cea = CourseEnrollmentAllowedFactory(
            email='*****@*****.**',
            course_id=self.course.id,
            auto_enroll=False,
        )
        # Still unlinked
        self.assertIsNone(cea.user)

        user1 = UserFactory.create(username="******", email="*****@*****.**", password="******")
        user2 = UserFactory.create(username="******", email="*****@*****.**", password="******")

        self.assertFalse(
            CourseEnrollment.objects.filter(course_id=self.course.id, user=user1).exists()
        )

        user1.email = '*****@*****.**'
        user1.save()

        CourseEnrollment.enroll(user1, self.course.id, check_access=True)

        self.assertTrue(
            CourseEnrollment.objects.filter(course_id=self.course.id, user=user1).exists()
        )

        # The CEA is now linked
        cea.refresh_from_db()
        self.assertEqual(cea.user, user1)

        # user2 wants to enroll too, (ab)using the same allowed e-mail, but cannot
        user1.email = '*****@*****.**'
        user1.save()
        user2.email = '*****@*****.**'
        user2.save()
        with self.assertRaises(EnrollmentClosedError):
            CourseEnrollment.enroll(user2, self.course.id, check_access=True)

        # CEA still linked to user1. Also after unenrolling
        cea.refresh_from_db()
        self.assertEqual(cea.user, user1)

        CourseEnrollment.unenroll(user1, self.course.id)

        cea.refresh_from_db()
        self.assertEqual(cea.user, user1)

        # Enroll user1 again. Because it's the original owner of the CEA, the enrollment is allowed
        CourseEnrollment.enroll(user1, self.course.id, check_access=True)

        # Still same
        cea.refresh_from_db()
        self.assertEqual(cea.user, user1)
Exemplo n.º 15
0
 def _course_unenroll(self,username,course_id):
     try:
         user = User.objects.get(username=username)
         course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
         CourseEnrollment.unenroll(user, course_key)
         return True
     except:
         return False
Exemplo n.º 16
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))
Exemplo n.º 17
0
    def setUp(self):
        super(PermissionTests, self).setUp()
        self.user = UserFactory()

        self.course_id = CourseLocator('MITx', '000', 'Perm_course')
        CourseModeFactory(mode_slug='verified', course_id=self.course_id)
        CourseModeFactory(mode_slug='masters', course_id=self.course_id)
        CourseModeFactory(mode_slug='professional', course_id=self.course_id)
        CourseEnrollment.unenroll(self.user, self.course_id)
Exemplo n.º 18
0
 def test_existing_inactive_enrollment(self):
     """
     If the user has an inactive enrollment for the course, the view should behave as if the
     user has no enrollment.
     """
     # Create an inactive enrollment
     CourseEnrollment.enroll(self.user, self.course.id)
     CourseEnrollment.unenroll(self.user, self.course.id, True)
     self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
     self.assertIsNotNone(get_enrollment(self.user.username, unicode(self.course.id)))
Exemplo n.º 19
0
    def test_refund_cert_callback_no_expiration(self):
        # When there is no expiration date on a verified mode, the user can always get a refund
        CourseEnrollment.enroll(self.user, self.course_id, 'verified')
        cart = Order.get_cart_for_user(user=self.user)
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified')
        cart.purchase()

        CourseEnrollment.unenroll(self.user, self.course_id)
        target_certs = CertificateItem.objects.filter(course_id=self.course_id, user_id=self.user, status='refunded', mode='verified')
        self.assertTrue(target_certs[0])
Exemplo n.º 20
0
    def test_unenrolled_from_all_courses(self, opt_in_pref):
        # Enroll in the course and set a preference
        self._create_courses_and_enrollments((self.TEST_ORG, True))
        self._set_opt_in_pref(self.user, self.TEST_ORG, opt_in_pref)

        # Unenroll from the course
        CourseEnrollment.unenroll(self.user, self.courses[0].id, skip_refund=True)

        # Enrollments should still appear in the outpu
        output = self._run_command(self.TEST_ORG)
        self._assert_output(output, (self.user, self.courses[0].id, opt_in_pref))
Exemplo n.º 21
0
def cm_unenroll_user(request):
    """
    This is a hard unenroll as opposed to the regular soft unenroll that edx uses internally.
    """
    response_format = request.REQUEST.get('format','html')
    if response_format == 'json' or 'application/json' in request.META.get('HTTP/ACCEPT', 'application/json'):
        if request.method == 'POST':
            if validate_token(request.body, request) is False:
                log.warn("Unauthorized access made by course: %s, user: %s", request.json.get('email'), request.json.get('course_id'))
                return HttpResponse('Unauthorized', status=401)
            if 'email' not in request.json or 'course_id' not in request.json:
                log.error("Incomplete request.")
                return HttpResponse(content=json.dumps({'errors':'Missing params'}), \
                        content_type = 'application/json', status=400)

            try:
                log.info("Unenrolling user: %s from course: %s" % (request.json.get('email'), request.json.get('course_id')))
                user = User.objects.get(email=request.json.get('email'))
                request.user = user
            except User.DoesNotExist:
                log.info("Unknown user : %s from course %s", request.json.get('email'), request.json.get('course_id'))
                return HttpResponse(content=json.dumps({'errors': 'Unknown user'}), status=500, content_type='application/json')
            course_id = request.json.get('course_id')

            #locator = loc_mapper().translate_location(course_id, CourseDescriptor.id_to_location(course_id))

            role = request.json.get('role')
            if role == 'staff':
                try:
                    try_remove_instructor(request, get_key_from_course_id(course_id), user)
                    log.info("staff unenrolled: %s", str(user))
                except CannotOrphanCourse as oops:
                    log.warn("last course admin removal attempted: %s", str(user))
                    return JsonResponse(oops.msg, 400)
            else:
                log.info("student unenroll: %s", str(user))
                try:
                    CourseEnrollment.unenroll(user, get_key_from_course_id(request.json.get('course_id')))
                    status_code = 200
                except DatabaseError:
                    # this is just for tests. This error will never be hit in production
                    # should probably consider moving cm_plugin out of common too.
                    log.error("Unenroll failed for user: %s from course: %s", request.json.get('email'), request.json.get('course_id'))
                    pass

            status_code = 200
            content = {'success':'ok'}

            log.info("Unenrolled user: %s from course: %s", request.json.get('email'), request.json.get('course_id'))
            return HttpResponse(content=json.dumps(content), status=status_code, content_type='application/json')
        else:
            return HttpResponse(content=json.dumps({}), status=404, content_type='application/json')
    else:
        return HttpResponse(content=json.dumps({}), status=404, content_type='application/json')
Exemplo n.º 22
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))
Exemplo n.º 23
0
    def test_existing_inactive_enrollment(self):
        """
        If the user has an inactive enrollment for the course, the view should behave as if the
        user has no enrollment.
        """
        # Create an inactive enrollment
        CourseEnrollment.enroll(self.user, self.course.id)
        CourseEnrollment.unenroll(self.user, self.course.id, True)
        self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
        self.assertIsNotNone(get_enrollment(self.user.username, unicode(self.course.id)))

        with mock_create_basket():
            self._test_successful_ecommerce_api_call(False)
Exemplo n.º 24
0
    def test_refund_cert_callback_no_expiration(self):
        # When there is no expiration date on a verified mode, the user can always get a refund
        CourseEnrollment.enroll(self.user, self.course_key, "verified")
        cart = Order.get_cart_for_user(user=self.user)
        CertificateItem.add_to_order(cart, self.course_key, self.cost, "verified")
        cart.purchase()

        CourseEnrollment.unenroll(self.user, self.course_key)
        target_certs = CertificateItem.objects.filter(
            course_id=self.course_key, user_id=self.user, status="refunded", mode="verified"
        )
        self.assertTrue(target_certs[0])
        self.assertTrue(target_certs[0].refund_requested_time)
        self.assertEquals(target_certs[0].order.status, "refunded")
Exemplo n.º 25
0
    def test_enrollment_non_existent_user(self):
        # Testing enrollment of newly unsaved user (i.e. no database entry)
        user = User(username="******", email="*****@*****.**")
        course_id = "edX/Test101/2013"

        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))

        # Unenroll does nothing
        CourseEnrollment.unenroll(user, course_id)

        # Implicit save() happens on new User object when enrolling, so this
        # should still work
        CourseEnrollment.enroll(user, course_id)
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
Exemplo n.º 26
0
    def handle(self, *args, **options):
        source_key = SlashSeparatedCourseKey.from_deprecated_string(options['source_course'])
        dest_key = SlashSeparatedCourseKey.from_deprecated_string(options['dest_course'])

        source_students = User.objects.filter(
            courseenrollment__course_id=source_key
        )

        for user in source_students:
            if CourseEnrollment.is_enrolled(user, dest_key):
                # Un Enroll from source course but don't mess
                # with the enrollment in the destination course.
                CourseEnrollment.unenroll(user, source_key)
                print("Unenrolled {} from {}".format(user.username, source_key.to_deprecated_string()))
                msg = "Skipping {}, already enrolled in destination course {}"
                print(msg.format(user.username, dest_key.to_deprecated_string()))
                continue

            print("Moving {}.".format(user.username))
            # Find the old enrollment.
            enrollment = CourseEnrollment.objects.get(
                user=user,
                course_id=source_key
            )

            # Move the Student between the classes.
            mode = enrollment.mode
            old_is_active = enrollment.is_active
            CourseEnrollment.unenroll(user, source_key)
            new_enrollment = CourseEnrollment.enroll(user, dest_key, mode=mode)

            # Unenroll from the new coures if the user had unenrolled
            # form the old course.
            if not old_is_active:
                new_enrollment.update_enrollment(is_active=False)

            if mode == 'verified':
                try:
                    certificate_item = CertificateItem.objects.get(
                        course_id=source_key,
                        course_enrollment=enrollment
                    )
                except CertificateItem.DoesNotExist:
                    print("No certificate for {}".format(user))
                    continue

                certificate_item.course_id = dest_key
                certificate_item.course_enrollment = new_enrollment
                certificate_item.save()
Exemplo n.º 27
0
    def handle(self, *args, **options):
        source_key = CourseKey.from_string(options.get('source_course', ''))
        dest_keys = []
        for course_key in options.get('dest_course_list', '').split(','):
            dest_keys.append(CourseKey.from_string(course_key))

        if not source_key or not dest_keys:
            raise TransferStudentError(u"Must have a source course and destination course specified.")

        tc_option = options.get('transfer_certificates', '')
        transfer_certificates = ('true' == tc_option.lower()) if tc_option else False
        if transfer_certificates and len(dest_keys) != 1:
            raise TransferStudentError(u"Cannot transfer certificate items from one course to many.")

        source_students = User.objects.filter(
            courseenrollment__course_id=source_key
        )

        for user in source_students:
            with transaction.atomic():
                print "Moving {}.".format(user.username)
                # Find the old enrollment.
                enrollment = CourseEnrollment.objects.get(
                    user=user,
                    course_id=source_key
                )

                # Move the Student between the classes.
                mode = enrollment.mode
                old_is_active = enrollment.is_active
                CourseEnrollment.unenroll(user, source_key, skip_refund=True)
                print u"Unenrolled {} from {}".format(user.username, unicode(source_key))

                for dest_key in dest_keys:
                    if CourseEnrollment.is_enrolled(user, dest_key):
                        # Un Enroll from source course but don't mess
                        # with the enrollment in the destination course.
                        msg = u"Skipping {}, already enrolled in destination course {}"
                        print msg.format(user.username, unicode(dest_key))
                    else:
                        new_enrollment = CourseEnrollment.enroll(user, dest_key, mode=mode)

                        # Un-enroll from the new course if the user had un-enrolled
                        # form the old course.
                        if not old_is_active:
                            new_enrollment.update_enrollment(is_active=False, skip_refund=True)

                        if transfer_certificates:
                            self._transfer_certificate_item(source_key, enrollment, user, dest_keys, new_enrollment)
Exemplo n.º 28
0
    def test_refund_cert_callback_no_expiration(self):
        # When there is no expiration date on a verified mode, the user can always get a refund

        # need to prevent analytics errors from appearing in stderr
        with patch('sys.stderr', sys.stdout.write):
            CourseEnrollment.enroll(self.user, self.course_key, 'verified')
            cart = Order.get_cart_for_user(user=self.user)
            CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified')
            cart.purchase()
            CourseEnrollment.unenroll(self.user, self.course_key)

        target_certs = CertificateItem.objects.filter(course_id=self.course_key, user_id=self.user, status='refunded', mode='verified')
        self.assertTrue(target_certs[0])
        self.assertTrue(target_certs[0].refund_requested_time)
        self.assertEquals(target_certs[0].order.status, 'refunded')
        self._assert_refund_tracked()
Exemplo n.º 29
0
    def test_enrollment_non_existent_user(self):
        # Testing enrollment of newly unsaved user (i.e. no database entry)
        user = User(username="******", email="*****@*****.**")
        course_id = SlashSeparatedCourseKey("edX", "Test101", "2013")

        self.assertFalse(CourseEnrollment.is_enrolled(user, course_id))

        # Unenroll does nothing
        CourseEnrollment.unenroll(user, course_id)
        self.assert_no_events_were_emitted()

        # Implicit save() happens on new User object when enrolling, so this
        # should still work
        CourseEnrollment.enroll(user, course_id)
        self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
        self.assert_enrollment_event_was_emitted(user, course_id)
Exemplo n.º 30
0
    def test_get_course_list(self):
        """
        Test getting courses
        """
        course_location = self.store.make_course_key('Org1', 'Course1', 'Run1')
        self._create_course_with_access_groups(course_location)

        # get dashboard
        courses_list = list(get_course_enrollment_pairs(self.student, None, []))
        self.assertEqual(len(courses_list), 1)
        self.assertEqual(courses_list[0][0].id, course_location)

        CourseEnrollment.unenroll(self.student, course_location)
        # get dashboard
        courses_list = list(get_course_enrollment_pairs(self.student, None, []))
        self.assertEqual(len(courses_list), 0)
Exemplo n.º 31
0
 def test_unenrolled_student(self):
     CourseEnrollment.unenroll(self.student, self.course.id)
     self.assert_raises_permission_denied()
Exemplo n.º 32
0
 def test_unenrolled_staff(self):
     CourseEnrollment.unenroll(self.staff, self.course.id)
     self.initial = {'requesting_user': self.staff}
     self.form_data['username'] = self.staff.username
     self.get_form(expected_valid=True)
Exemplo n.º 33
0
 def test_unenrolled_student_by_staff(self):
     CourseEnrollment.unenroll(self.student, self.course.id)
     self.initial = {'requesting_user': self.staff}
     self.get_form(expected_valid=True)
Exemplo n.º 34
0
 def test_course_info_unenrolled(self):
     self._set_up_course(False, False, False)
     course_id = self.courses[0].id
     CourseEnrollment.unenroll(self.user, course_id)
     result = self._get_detail()
     self.assertNotIn(six.text_type(course_id), result["course_info"])
Exemplo n.º 35
0
def populate_user(user, authentication_response):

    attr = authentication_response.find(CAS + 'authenticationSuccess/' + CAS +
                                        'attributes',
                                        namespaces=NSMAP)

    if attr is not None:

        staff_flag = attr.find(CAS + 'is_staff', NSMAP)
        if staff_flag is not None:
            user.is_staff = (staff_flag.text or '').upper() == 'TRUE'

        superuser_flag = attr.find(CAS + 'is_superuser', NSMAP)
        if superuser_flag is not None:
            user.is_superuser = (superuser_flag.text or '').upper() == 'TRUE'

        active_flag = attr.find(CAS + 'is_active', NSMAP)
        if active_flag is not None:
            user.is_active = (active_flag.text or '').upper() == 'TRUE'

        # Limiting by maximum lengths.
        # Max length of firstname/lastname is 30.
        # Max length of a email is 75.

        first_name = attr.find(CAS + 'givenName', NSMAP)
        if first_name is not None:
            user.first_name = (first_name.text or '')[0:30]

        last_name = attr.find(CAS + 'sn', NSMAP)
        if last_name is not None:
            user.last_name = (last_name.text or '')[0:30]

        email = attr.find(CAS + 'email', NSMAP)
        if email is not None:
            user.email = (email.text or '')[0:75]

        # Here we handle things that go into UserProfile instead.

        # This is a dirty hack and you shouldn't do that.
        # However, I don't think it's going to work when imported outside of the function body.

        from student.models import UserProfile

        # Make the user's password unusable. But only if they don't have an unusable password already,
        # to prevent SessionAuthenticationMiddleware from logging them out because their password changed.
        if user.has_usable_password():
            user.set_unusable_password()
        user.save()

        # If the user doesn't yet have a profile, it means it's a new one and we need to create it a profile.
        # but we need to save the user first.
        user_profile, created = UserProfile.objects.get_or_create(
            user=user, defaults={'name': user.username})

        # There should be more variables, but let's settle on the actual model first.
        full_name = attr.find(CAS + 'fullName', NSMAP)
        if full_name is not None:
            user_profile.name = full_name.text or ''

        user_profile.save()

        # Now the really fun bit. Signing the user up for courses given.
        coursetag = attr.find(CAS + 'courses', NSMAP)

        from student.models import CourseEnrollment
        from opaque_keys.edx.locator import CourseLocator
        from opaque_keys import InvalidKeyError
        from xmodule.modulestore.django import modulestore
        from xmodule.modulestore.exceptions import ItemNotFoundError

        if coursetag is not None:
            try:
                courses = json.loads(coursetag.text)
                assert isinstance(courses, list)
            except (ValueError, AssertionError):
                # We failed to parse the tag and get a list, so we leave.
                log.error("Course list failed to parse.")
                return

            # We got a list. Compare it to existing enrollments.
            existing_enrollments = CourseEnrollment.objects.filter(
                user=user, is_active=True).values_list('course_id', flat=True)

            for course in courses:
                if course and not course in existing_enrollments:
                    try:
                        locator = CourseLocator.from_string(course)
                    except (InvalidKeyError, AttributeError) as e:
                        log.error(
                            "Invalid course identifier {}".format(course))
                        continue
                    try:
                        course = modulestore().get_course(locator)
                    except ItemNotFoundError:
                        log.error("Course {} does not exist.".format(course))
                        continue
                    CourseEnrollment.enroll(user, locator)
            # Now we need to unsub the user from courses for which they are not enrolled.
            for course in existing_enrollments:
                if not course in courses:
                    try:
                        locator = CourseLocator.from_string(course)
                    except (InvalidKeyError, AttributeError) as e:
                        log.error(
                            "Invalid course identifier {} in existing enrollments."
                            .format(course))
                        continue
                    CourseEnrollment.unenroll(user, locator)

        # Now implement CourseEnrollmentAllowed objects, because otherwise they will only ever fire when
        # users click a link in the registration email -- which can never happen here.
        # Considering the new setup, I doubt this will ever be useful.
        if created:
            from student.models import CourseEnrollmentAllowed
            for cea in CourseEnrollmentAllowed.objects.filter(
                    email=user.email, auto_enroll=True):
                CourseEnrollment.enroll(user, cea.course_id)

        # Now, deal with course administration packets.
        course_admin_tag = attr.find(CAS + 'course_administration_update',
                                     NSMAP)

        if course_admin_tag is not None:
            try:
                courses = json.loads(course_admin_tag.text)
                assert isinstance(courses, dict)
            except (ValueError, AssertionError):
                # We failed to parse the tag, so we leave.
                log.error(
                    "Could not parse course administration block: <<{}>>".
                    format(course_admin_tag.text))
                return

            from instructor.access import list_with_level, allow_access, revoke_access
            from django_comment_common.models import Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA
            from django.contrib.auth.models import User

            for course_id, admin_block in courses.iteritems():
                try:
                    locator = CourseLocator.from_string(course_id)
                except (InvalidKeyError, AttributeError) as e:
                    log.error("Invalid course identifier {}".format(course_id))
                    continue
                try:
                    course = modulestore().get_course(locator)
                except ItemNotFoundError:
                    log.error("Course {} does not exist.".format(course_id))
                    continue

                if not course:
                    continue

                # Course roles are relatively easy.
                for block_name, role in [('admin', 'instructor'),
                                         ('staff', 'staff'), ('beta', 'beta')]:
                    role_list = admin_block.get(block_name, [])
                    existing = list_with_level(course, role)

                    for username in role_list:
                        try:
                            user = User.objects.get(username=username)
                        except User.DoesNotExist:
                            continue
                        if not user in existing:
                            allow_access(course, user, role)
                            try:
                                CourseEnrollment.enroll(user, locator)
                            except:
                                pass
                    for user in existing:
                        if not user.username in role_list:
                            revoke_access(course, user, role)

                # Forum roles, considerably different.

                for block_name, rolename in [
                    ('forum_admin', FORUM_ROLE_ADMINISTRATOR),
                    ('forum_moderator', FORUM_ROLE_MODERATOR),
                    ('forum_assistant', FORUM_ROLE_COMMUNITY_TA)
                ]:
                    role_list = admin_block.get(block_name, [])
                    try:
                        role = Role.objects.get(course_id=locator,
                                                name=rolename)
                    except Role.DoesNotExist:
                        continue
                    existing = role.users.all()

                    for user in existing:
                        if not user.username in role_list:
                            role.users.remove(user)
                    for username in role_list:
                        try:
                            user = User.objects.get(username=username)
                        except User.DoesNotExist:
                            continue
                        if not user in existing:
                            role.users.add(user)
                            try:
                                CourseEnrollment.enroll(user, locator)
                            except:
                                pass

    pass
Exemplo n.º 36
0
 def test_not_enrolled(self):
     CourseEnrollment.unenroll(self.user, self.course_key)
     self.verify_response(403)
Exemplo n.º 37
0
def _do_unenroll_students(course_key, students, email_students=False):
    """
    Do the actual work of un-enrolling multiple students, presented as a string
    of emails separated by commas or returns
    `course_key` is id of course (a `str`)
    `students` is string of student emails separated by commas or returns (a `str`)
    `email_students` is user input preference (a `boolean`)
    """

    old_students, __ = get_and_clean_student_list(students)
    status = dict([x, 'unprocessed'] for x in old_students)

    stripped_site_name = microsite.get_value(
        'SITE_NAME',
        settings.SITE_NAME
    )
    if email_students:
        course = modulestore().get_course(course_key)
        # Composition of email
        data = {
            'site_name': stripped_site_name,
            'course': course
        }

    for student in old_students:

        isok = False
        cea = CourseEnrollmentAllowed.objects.filter(course_id=course_key, email=student)
        # Will be 0 or 1 records as there is a unique key on email + course_id
        if cea:
            cea[0].delete()
            status[student] = "un-enrolled"
            isok = True

        try:
            user = User.objects.get(email=student)
        except User.DoesNotExist:

            if isok and email_students:
                # User was allowed to join but had not signed up yet
                data['email_address'] = student
                data['message'] = 'allowed_unenroll'
                send_mail_ret = send_mail_to_student(student, data)
                status[student] += (', email sent' if send_mail_ret else '')

            continue

        # Will be 0 or 1 records as there is a unique key on user + course_id
        if CourseEnrollment.is_enrolled(user, course_key):
            try:
                CourseEnrollment.unenroll(user, course_key)
                status[student] = "un-enrolled"
                if email_students:
                    # User was enrolled
                    data['email_address'] = student
                    data['full_name'] = user.profile.name
                    data['message'] = 'enrolled_unenroll'
                    send_mail_ret = send_mail_to_student(student, data)
                    status[student] += (', email sent' if send_mail_ret else '')

            except Exception:  # pylint: disable=broad-except
                if not isok:
                    status[student] = "Error!  Failed to un-enroll"

    datatable = {'header': ['StudentEmail', 'action']}
    datatable['data'] = [[x, status[x]] for x in sorted(status)]
    datatable['title'] = _('Un-enrollment of students')

    return dict(datatable=datatable)
Exemplo n.º 38
0
    def setUp(self):
        super(ReportTypeTests, self).setUp()

        # Need to make a *lot* of users for this one
        self.first_verified_user = UserFactory.create(profile__name="John Doe")
        self.second_verified_user = UserFactory.create(profile__name="Jane Deer")
        self.first_audit_user = UserFactory.create(profile__name="Joe Miller")
        self.second_audit_user = UserFactory.create(profile__name="Simon Blackquill")
        self.third_audit_user = UserFactory.create(profile__name="Super Mario")
        self.honor_user = UserFactory.create(profile__name="Princess Peach")
        self.first_refund_user = UserFactory.create(profile__name="King Bowsér")
        self.second_refund_user = UserFactory.create(profile__name="Súsan Smith")

        # Two are verified, three are audit, one honor

        self.cost = 40
        self.course = CourseFactory.create(org='MITx', number='999', display_name=u'Robot Super Course')
        self.course_key = self.course.id
        settings.COURSE_LISTINGS['default'] = [self.course_key.to_deprecated_string()]
        course_mode = CourseMode(course_id=self.course_key,
                                 mode_slug="honor",
                                 mode_display_name="honor cert",
                                 min_price=self.cost)
        course_mode.save()

        course_mode2 = CourseMode(course_id=self.course_key,
                                  mode_slug="verified",
                                  mode_display_name="verified cert",
                                  min_price=self.cost)
        course_mode2.save()

        # User 1 & 2 will be verified
        self.cart1 = Order.get_cart_for_user(self.first_verified_user)
        CertificateItem.add_to_order(self.cart1, self.course_key, self.cost, 'verified')
        self.cart1.purchase()

        self.cart2 = Order.get_cart_for_user(self.second_verified_user)
        CertificateItem.add_to_order(self.cart2, self.course_key, self.cost, 'verified')
        self.cart2.purchase()

        # Users 3, 4, and 5 are audit
        CourseEnrollment.enroll(self.first_audit_user, self.course_key, "audit")
        CourseEnrollment.enroll(self.second_audit_user, self.course_key, "audit")
        CourseEnrollment.enroll(self.third_audit_user, self.course_key, "audit")

        # User 6 is honor
        CourseEnrollment.enroll(self.honor_user, self.course_key, "honor")

        self.now = datetime.datetime.now(pytz.UTC)

        # Users 7 & 8 are refunds
        self.cart = Order.get_cart_for_user(self.first_refund_user)
        CertificateItem.add_to_order(self.cart, self.course_key, self.cost, 'verified')
        self.cart.purchase()
        CourseEnrollment.unenroll(self.first_refund_user, self.course_key)

        self.cart = Order.get_cart_for_user(self.second_refund_user)
        CertificateItem.add_to_order(self.cart, self.course_key, self.cost, 'verified')
        self.cart.purchase(self.second_refund_user, self.course_key)
        CourseEnrollment.unenroll(self.second_refund_user, self.course_key)

        self.test_time = datetime.datetime.now(pytz.UTC)

        first_refund = CertificateItem.objects.get(id=3)
        first_refund.fulfilled_time = self.test_time
        first_refund.refund_requested_time = self.test_time
        first_refund.save()

        second_refund = CertificateItem.objects.get(id=4)
        second_refund.fulfilled_time = self.test_time
        second_refund.refund_requested_time = self.test_time
        second_refund.save()

        self.CORRECT_REFUND_REPORT_CSV = dedent("""
            Order Number,Customer Name,Date of Original Transaction,Date of Refund,Amount of Refund,Service Fees (if any)
            3,King Bowsér,{time_str},{time_str},40,0
            4,Súsan Smith,{time_str},{time_str},40,0
            """.format(time_str=str(self.test_time)))

        self.CORRECT_CERT_STATUS_CSV = dedent("""
            University,Course,Course Announce Date,Course Start Date,Course Registration Close Date,Course Registration Period,Total Enrolled,Audit Enrollment,Honor Code Enrollment,Verified Enrollment,Gross Revenue,Gross Revenue over the Minimum,Number of Verified Students Contributing More than the Minimum,Number of Refunds,Dollars Refunded
            MITx,999 Robot Super Course,,,,,6,3,1,2,80.00,0.00,0,2,80.00
            """.format(time_str=str(self.test_time)))

        self.CORRECT_UNI_REVENUE_SHARE_CSV = dedent("""
            University,Course,Number of Transactions,Total Payments Collected,Service Fees (if any),Number of Successful Refunds,Total Amount of Refunds
            MITx,999 Robot Super Course,6,80.00,0.00,2,80.00
            """.format(time_str=str(self.test_time)))
Exemplo n.º 39
0
 def tearDown(self):
     CourseEnrollment.unenroll(self.user, self.course.id)
     super(CourseExpirationTestCase, self).tearDown()
Exemplo n.º 40
0
def change_enrollment(request, check_access=True):
    """
    Modify the enrollment status for the logged-in user.

    TODO: This is lms specific and does not belong in common code.

    The request parameter must be a POST request (other methods return 405)
    that specifies course_id and enrollment_action parameters. If course_id or
    enrollment_action is not specified, if course_id is not valid, if
    enrollment_action is something other than "enroll" or "unenroll", if
    enrollment_action is "enroll" and enrollment is closed for the course, or
    if enrollment_action is "unenroll" and the user is not enrolled in the
    course, a 400 error will be returned. If the user is not logged in, 403
    will be returned; it is important that only this case return 403 so the
    front end can redirect the user to a registration or login page when this
    happens. This function should only be called from an AJAX request, so
    the error messages in the responses should never actually be user-visible.

    Args:
        request (`Request`): The Django request object

    Keyword Args:
        check_access (boolean): If True, we check that an accessible course actually
            exists for the given course_key before we enroll the student.
            The default is set to False to avoid breaking legacy code or
            code with non-standard flows (ex. beta tester invitations), but
            for any standard enrollment flow you probably want this to be True.

    Returns:
        Response

    """
    # Get the user
    user = request.user

    # Ensure the user is authenticated
    if not user.is_authenticated:
        return HttpResponseForbidden()

    # Ensure we received a course_id
    action = request.POST.get("enrollment_action")
    if 'course_id' not in request.POST:
        return HttpResponseBadRequest(_("Course id not specified"))

    try:
        course_id = CourseKey.from_string(request.POST.get("course_id"))
    except InvalidKeyError:
        log.warning(
            u"User %s tried to %s with invalid course id: %s",
            user.username,
            action,
            request.POST.get("course_id"),
        )
        return HttpResponseBadRequest(_("Invalid course id"))

    # Allow us to monitor performance of this transaction on a per-course basis since we often roll-out features
    # on a per-course basis.
    monitoring_utils.set_custom_metric('course_id', text_type(course_id))

    if action == "enroll":
        # Make sure the course exists
        # We don't do this check on unenroll, or a bad course id can't be unenrolled from
        if not modulestore().has_course(course_id):
            log.warning(u"User %s tried to enroll in non-existent course %s",
                        user.username, course_id)
            return HttpResponseBadRequest(_("Course id is invalid"))

        # Record the user's email opt-in preference
        if settings.FEATURES.get('ENABLE_MKTG_EMAIL_OPT_IN'):
            _update_email_opt_in(request, course_id.org)

        available_modes = CourseMode.modes_for_course_dict(course_id)

        # Check whether the user is blocked from enrolling in this course
        # This can occur if the user's IP is on a global blacklist
        # or if the user is enrolling in a country in which the course
        # is not available.
        redirect_url = embargo_api.redirect_if_blocked(
            course_id, user=user, ip_address=get_ip(request), url=request.path)
        if redirect_url:
            return HttpResponse(redirect_url)

        if CourseEntitlement.check_for_existing_entitlement_and_enroll(
                user=user, course_run_key=course_id):
            return HttpResponse(
                reverse('courseware', args=[unicode(course_id)]))

        # Check that auto enrollment is allowed for this course
        # (= the course is NOT behind a paywall)
        if CourseMode.can_auto_enroll(course_id):
            # Enroll the user using the default mode (audit)
            # We're assuming that users of the course enrollment table
            # will NOT try to look up the course enrollment model
            # by its slug.  If they do, it's possible (based on the state of the database)
            # for no such model to exist, even though we've set the enrollment type
            # to "audit".
            try:
                enroll_mode = CourseMode.auto_enroll_mode(
                    course_id, available_modes)
                if enroll_mode:
                    CourseEnrollment.enroll(user,
                                            course_id,
                                            check_access=check_access,
                                            mode=enroll_mode)
            except Exception:  # pylint: disable=broad-except
                return HttpResponseBadRequest(_("Could not enroll"))

        # If we have more than one course mode or professional ed is enabled,
        # then send the user to the choose your track page.
        # (In the case of no-id-professional/professional ed, this will redirect to a page that
        # funnels users directly into the verification / payment flow)
        if CourseMode.has_verified_mode(
                available_modes) or CourseMode.has_professional_mode(
                    available_modes):
            return HttpResponse(
                reverse("course_modes_choose",
                        kwargs={'course_id': text_type(course_id)}))

        # Otherwise, there is only one mode available (the default)
        return HttpResponse()
    elif action == "unenroll":
        enrollment = CourseEnrollment.get_enrollment(user, course_id)
        if not enrollment:
            return HttpResponseBadRequest(
                _("You are not enrolled in this course"))

        certificate_info = cert_info(user, enrollment.course_overview)
        if certificate_info.get('status') in DISABLE_UNENROLL_CERT_STATES:
            return HttpResponseBadRequest(
                _("Your certificate prevents you from unenrolling from this course"
                  ))

        CourseEnrollment.unenroll(user, course_id)
        REFUND_ORDER.send(sender=None, course_enrollment=enrollment)
        return HttpResponse()
    else:
        return HttpResponseBadRequest(_("Enrollment action is invalid"))
Exemplo n.º 41
0
 def unenroll(self, course_id=None):
     """Unenroll test user in test course."""
     CourseEnrollment.unenroll(self.user, course_id or self.course.id)
Exemplo n.º 42
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
                })
                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))
Exemplo n.º 43
0
 def test_refund_cert_no_cert_exists(self):
     # If there is no paid certificate, the refund callback should return nothing
     CourseEnrollment.enroll(self.user, self.course_id, 'verified')
     ret_val = CourseEnrollment.unenroll(self.user, self.course_id)
     self.assertFalse(ret_val)
Exemplo n.º 44
0
def mobile_change_enrollment(request):
    """
    Modify the enrollment status for the logged-in user.

    The request parameter must be a POST request (other methods return 405)
    that specifies course_id and enrollment_action parameters. If course_id or
    enrollment_action is not specified, if course_id is not valid, if
    enrollment_action is something other than "enroll" or "unenroll", if
    enrollment_action is "enroll" and enrollment is closed for the course, or
    if enrollment_action is "unenroll" and the user is not enrolled in the
    course, a 400 error will be returned. If the user is not logged in, 403
    will be returned; it is important that only this case return 403 so the
    front end can redirect the user to a registration or login page when this
    happens. This function should only be called from an AJAX request or
    as a post-login/registration helper, so the error messages in the responses
    should never actually be user-visible.
    """
    user = request.user

    action = request.POST.get("enrollment_action")
    course_id = request.POST.get("course_id")
    if course_id is None:
        return HttpResponseBadRequest(_("Course id not specified"))

    if not user.is_authenticated():
        return HttpResponseForbidden()

    if action == "enroll":
        # Make sure the course exists
        # We don't do this check on unenroll, or a bad course id can't be unenrolled from
        try:
            course = course_from_id(course_id)
        except ItemNotFoundError:
            log.warning("User {0} tried to enroll in non-existent course {1}"
                        .format(user.username, course_id))
            return HttpResponseBadRequest(_("Course id is invalid"))

        if not has_access(user, course, 'enroll'):
            return HttpResponseBadRequest(_("Enrollment is closed"))

        # see if we have already filled up all allowed enrollments
        is_course_full = CourseEnrollment.is_course_full(course)

        if is_course_full:
            return HttpResponseBadRequest(_("Course is full"))

        # If this course is available in multiple modes, redirect them to a page
        # where they can choose which mode they want.
        available_modes = CourseMode.modes_for_course(course_id)
        if len(available_modes) > 1:
            return HttpResponse(
                reverse("course_modes_choose", kwargs={'course_id': course_id})
            )

        current_mode = available_modes[0]

        course_id_dict = Location.parse_course_id(course_id)
        dog_stats_api.increment(
            "common.student.enrollment",
            tags=[u"org:{org}".format(**course_id_dict),
                  u"course:{course}".format(**course_id_dict),
                  u"run:{name}".format(**course_id_dict)]
        )

        CourseEnrollment.enroll(user, course.id, mode=current_mode.slug)

        return HttpResponse('about')

    elif action == "add_to_cart":
        # Pass the request handling to shoppingcart.views
        # The view in shoppingcart.views performs error handling and logs different errors.  But this elif clause
        # is only used in the "auto-add after user reg/login" case, i.e. it's always wrapped in try_change_enrollment.
        # This means there's no good way to display error messages to the user.  So we log the errors and send
        # the user to the shopping cart page always, where they can reasonably discern the status of their cart,
        # whether things got added, etc

        shoppingcart.views.add_course_to_cart(request, course_id)
        return HttpResponse(
            reverse("shoppingcart.views.show_cart")
        )

    elif action == "unenroll":
        if not CourseEnrollment.is_enrolled(user, course_id):
            return HttpResponseBadRequest(_("You are not enrolled in this course"))
        CourseEnrollment.unenroll(user, course_id)
        course_id_dict = Location.parse_course_id(course_id)
        dog_stats_api.increment(
            "common.student.unenrollment",
            tags=[u"org:{org}".format(**course_id_dict),
                  u"course:{course}".format(**course_id_dict),
                  u"run:{name}".format(**course_id_dict)]
        )
        return HttpResponse()
    else:
        return HttpResponseBadRequest(_("Enrollment action is invalid"))
    def test_cea_enrolls_only_one_user(self):
        """
        Tests that a CourseEnrollmentAllowed can be used by just one user.
        If the user changes e-mail and then a second user tries to enroll with the same accepted e-mail,
        the second enrollment should fail.
        However, the original user can reuse the CEA many times.
        """

        cea = CourseEnrollmentAllowedFactory(
            email='*****@*****.**',
            course_id=self.course.id,
            auto_enroll=False,
        )
        # Still unlinked
        self.assertIsNone(cea.user)

        user1 = UserFactory.create(username="******",
                                   email="*****@*****.**",
                                   password="******")
        user2 = UserFactory.create(username="******",
                                   email="*****@*****.**",
                                   password="******")

        self.assertFalse(
            CourseEnrollment.objects.filter(course_id=self.course.id,
                                            user=user1).exists())

        user1.email = '*****@*****.**'
        user1.save()

        CourseEnrollment.enroll(user1, self.course.id, check_access=True)

        self.assertTrue(
            CourseEnrollment.objects.filter(course_id=self.course.id,
                                            user=user1).exists())

        # The CEA is now linked
        cea.refresh_from_db()
        self.assertEqual(cea.user, user1)

        # user2 wants to enroll too, (ab)using the same allowed e-mail, but cannot
        user1.email = '*****@*****.**'
        user1.save()
        user2.email = '*****@*****.**'
        user2.save()
        with self.assertRaises(EnrollmentClosedError):
            CourseEnrollment.enroll(user2, self.course.id, check_access=True)

        # CEA still linked to user1. Also after unenrolling
        cea.refresh_from_db()
        self.assertEqual(cea.user, user1)

        CourseEnrollment.unenroll(user1, self.course.id)

        cea.refresh_from_db()
        self.assertEqual(cea.user, user1)

        # Enroll user1 again. Because it's the original owner of the CEA, the enrollment is allowed
        CourseEnrollment.enroll(user1, self.course.id, check_access=True)

        # Still same
        cea.refresh_from_db()
        self.assertEqual(cea.user, user1)
Exemplo n.º 46
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))
Exemplo n.º 47
0
 def _unenroll_entitlement(self, entitlement, course_run_key, user):
     """
     Internal method to handle the details of Unenrolling a User in a Course Run.
     """
     CourseEnrollment.unenroll(user, course_run_key, skip_refund=True)
     entitlement.set_enrollment(None)