def test_is_rate_limit_exceeded_method(self, db_log_mock, request_counts, ip_address, expected_calls): """ Tests if `BadRequestRateLimiter.is_rate_limit_exceeded` calls Edraak's `db_log_failed_attempt` correctly. At Edraak we've added the `db_log_failed_attempt` to the BadRequestRateLimiter, to show it on the admin panel. """ limiter = BadRequestRateLimiter() request_counts_mock = Mock(values=Mock(return_value=request_counts, )) with patch.object(limiter, 'get_counters', return_value=request_counts_mock): limiter.is_rate_limit_exceeded(FakeRequest(ip_address=ip_address)) self.assertEquals(db_log_mock.call_count, expected_calls, 'Should only be called when if limit exceeded')
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning( "Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = 'shoppingcart/registration_code_receipt.html' if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) context = { 'reg_code_already_redeemed': reg_code_already_redeemed, 'reg_code_is_valid': reg_code_is_valid, 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'registered_for_course': registered_for_course(course, request.user) } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) if reg_code_is_valid and not reg_code_already_redeemed: #now redeem the reg code. RegistrationCodeRedemption.create_invoice_generated_registration_redemption( course_registration, request.user) CourseEnrollment.enroll(request.user, course.id) context = { 'redemption_success': True, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } else: context = { 'reg_code_is_valid': reg_code_is_valid, 'reg_code_already_redeemed': reg_code_already_redeemed, 'redemption_success': False, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } return render_to_response(template_to_render, context)
def password_change_request_handler(request): """Handle password change requests originating from the account page. Uses the Account API to email the user a link to the password reset page. Note: The next step in the password reset process (confirmation) is currently handled by student.views.password_reset_confirm_wrapper, a custom wrapper around Django's password reset confirmation view. Args: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method Example usage: POST /account/password """ limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated() else request.POST.get( 'email') if email: try: request_password_change(email, request.is_secure()) user = user if user.is_authenticated() else User.objects.get( email=email) destroy_oauth_tokens(user) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) except UserAPIInternalError as err: log.exception( 'Error occured during password change for user {email}: {error}' .format(email=email, error=err)) return HttpResponse(_( "Some error occured during password change. Please try again"), status=500) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = 'shoppingcart/registration_code_receipt.html' if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) context = { 'reg_code_already_redeemed': reg_code_already_redeemed, 'reg_code_is_valid': reg_code_is_valid, 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'registered_for_course': registered_for_course(course, request.user) } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) if reg_code_is_valid and not reg_code_already_redeemed: #now redeem the reg code. RegistrationCodeRedemption.create_invoice_generated_registration_redemption(course_registration, request.user) CourseEnrollment.enroll(request.user, course.id) context = { 'redemption_success': True, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } else: context = { 'reg_code_is_valid': reg_code_is_valid, 'reg_code_already_redeemed': reg_code_already_redeemed, 'redemption_success': False, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } return render_to_response(template_to_render, context)
def password_change_request_handler(request): """Handle password change requests originating from the account page. Uses the Account API to email the user a link to the password reset page. Note: The next step in the password reset process (confirmation) is currently handled by student.views.password_reset_confirm_wrapper, a custom wrapper around Django's password reset confirmation view. Args: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method Example usage: POST /account/password """ limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated() else request.POST.get('email') if email: try: request_password_change(email, request.is_secure()) user = user if user.is_authenticated() else User.objects.get(email=email) destroy_oauth_tokens(user) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) except UserAPIInternalError as err: log.exception('Error occured during password change for user {email}: {error}' .format(email=email, error=err)) return HttpResponse(_("Some error occured during password change. Please try again"), status=500) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def account_recovery_request_handler(request): """ Handle account recovery requests. Arguments: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method HttpResponse: 404 if account recovery feature is not enabled Example: POST /account/account_recovery """ if not is_secondary_email_feature_enabled(): raise Http404 limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Account recovery rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = request.POST.get('email') if email: try: # Send an email with a link to direct user towards account recovery. from openedx.core.djangoapps.user_api.accounts.api import request_account_recovery request_account_recovery(email, request.is_secure()) # Check if a user exists with the given secondary email, if so then invalidate the existing oauth tokens. user = user if user.is_authenticated else User.objects.get( id=AccountRecovery.objects.get(secondary_email__iexact=email).user.id ) destroy_oauth_tokens(user) except UserNotFound: AUDIT_LOG.warning( "Account recovery attempt via invalid secondary email '{email}'.".format(email=email) ) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def password_change_request_handler(request): """Handle password change requests originating from the account page. Uses the Account API to email the user a link to the password reset page. Note: The next step in the password reset process (confirmation) is currently handled by student.views.password_reset_confirm_wrapper, a custom wrapper around Django's password reset confirmation view. Args: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter, or if no user with the provided email exists HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method Example usage: POST /account/password """ limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated() else request.POST.get( 'email') if email: try: request_password_change(email, request.get_host(), request.is_secure()) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) return HttpResponseBadRequest( _("No user with the provided email address exists.")) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def password_change_request_handler(request): """Handle password change requests originating from the account page. Uses the Account API to email the user a link to the password reset page. Note: The next step in the password reset process (confirmation) is currently handled by student.views.password_reset_confirm_wrapper, a custom wrapper around Django's password reset confirmation view. Args: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter, or if no user with the provided email exists HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method Example usage: POST /account/password """ limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated() else request.POST.get('email') if email: try: request_password_change(email, request.get_host(), request.is_secure()) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) return HttpResponseBadRequest(_("No user with the provided email address exists.")) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def post(self, request, format=None): data = request.data if 'email' in data: limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() email = data['email'] if len(email) > 0: try: request_password_change(email, request.get_host(), request.is_secure()) user = User.objects.get(email=email) destroy_oauth_tokens(user) return Response( { "message": "You will receive your new password on your email." }, status=status.HTTP_200_OK) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) return Response({"error": "Provide a valid email."}, status=status.HTTP_400_BAD_REQUEST) else: return Response( {"error": "Provide an email to recover your password."}, status=status.HTTP_400_BAD_REQUEST) else: return Response( {"error": "Provide an email to recover your password."}, status=status.HTTP_400_BAD_REQUEST)
def password_reset(request): """ Attempts to send a password reset e-mail. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Rate limit exceeded in password_reset") return HttpResponseForbidden() form = PasswordResetFormNoActive(request.POST) if form.is_valid(): form.save(use_https=request.is_secure(), from_email=configuration_helpers.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL), request=request) # When password change is complete, a "edx.user.settings.changed" event will be emitted. # But because changing the password is multi-step, we also emit an event here so that we can # track where the request was initiated. tracker.emit( SETTING_CHANGE_INITIATED, { "setting": "password", "old": None, "new": None, "user_id": request.user.id, }) destroy_oauth_tokens(request.user) else: # bad user? tick the rate limiter counter AUDIT_LOG.info("Bad password_reset user passed in.") limiter.tick_bad_request_counter(request) return JsonResponse({ 'success': True, 'value': render_to_string('registration/password_reset_done.html', {}), })
def password_reset(request): """ Attempts to send a password reset e-mail. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Rate limit exceeded in password_reset") return HttpResponseForbidden() form = PasswordResetFormNoActive(request.POST) if form.is_valid(): form.save(use_https=request.is_secure(), from_email=configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL), request=request) # When password change is complete, a "edx.user.settings.changed" event will be emitted. # But because changing the password is multi-step, we also emit an event here so that we can # track where the request was initiated. tracker.emit( SETTING_CHANGE_INITIATED, { "setting": "password", "old": None, "new": None, "user_id": request.user.id, } ) destroy_oauth_tokens(request.user) else: # bad user? tick the rate limiter counter AUDIT_LOG.info("Bad password_reset user passed in.") limiter.tick_bad_request_counter(request) return JsonResponse({ 'success': True, 'value': render_to_string('registration/password_reset_done.html', {}), })
def password_change_request_handler(request): """Handle password change requests originating from the account page. Uses the Account API to email the user a link to the password reset page. Note: The next step in the password reset process (confirmation) is currently handled by student.views.password_reset_confirm_wrapper, a custom wrapper around Django's password reset confirmation view. Args: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method Example usage: POST /account/password """ limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated() else request.POST.get('email') if email: try: request_password_change(email, request.is_secure()) user = user if user.is_authenticated() else User.objects.get(email=email) destroy_oauth_tokens(user) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) # If enabled, send an email saying that a password reset was attempted, but that there is # no user associated with the email if configuration_helpers.get_value('ENABLE_PASSWORD_RESET_FAILURE_EMAIL', settings.FEATURES['ENABLE_PASSWORD_RESET_FAILURE_EMAIL']): context = { 'failed': True, 'email_address': email, 'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME), } subject = loader.render_to_string('emails/password_reset_subject.txt', context) subject = ''.join(subject.splitlines()) message = loader.render_to_string('registration/password_reset_email.html', context) from_email = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) try: send_mail(subject, message, from_email, [email], html_message=message) except Exception: # pylint: disable=broad-except log.exception(u'Unable to send password reset failure email notification from "%s"', from_email) except UserAPIInternalError as err: log.exception('Error occured during password change for user {email}: {error}' .format(email=email, error=err)) return HttpResponse(_("Some error occured during password change. Please try again"), status=500) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def post(self, request): response_data = {} # Add some rate limiting here by re-using the RateLimitMixin as a helper class limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): response_data['message'] = _('Rate limit exceeded in api login.') return Response(response_data, status=status.HTTP_403_FORBIDDEN) base_uri = generate_base_uri(request) try: existing_user = User.objects.get(username=request.DATA['username']) except ObjectDoesNotExist: existing_user = None # see if account has been locked out due to excessive login failures if existing_user and LoginFailures.is_feature_enabled(): if LoginFailures.is_user_locked_out(existing_user): response_status = status.HTTP_403_FORBIDDEN response_data['message'] = _('This account has been temporarily locked due to excessive login failures. ' 'Try again later.') return Response(response_data, status=response_status) # see if the user must reset his/her password due to any policy settings if existing_user and PasswordHistory.should_user_reset_password_now(existing_user): response_status = status.HTTP_403_FORBIDDEN response_data['message'] = _( 'Your password has expired due to password policy on this account. ' 'You must reset your password before you can log in again.' ) return Response(response_data, status=response_status) if existing_user: user = authenticate(username=existing_user.username, password=request.DATA['password']) if user is not None: # successful login, clear failed login attempts counters, if applicable if LoginFailures.is_feature_enabled(): LoginFailures.clear_lockout_counter(user) if user.is_active: # # Create a new session directly with the SESSION_ENGINE # We don't call the django.contrib.auth login() method # because it is bound with the HTTP request. # # Since we are a server-to-server API, we shouldn't # be stateful with respect to the HTTP request # and anything that might come with it, as it could # violate our RESTfulness # engine = import_module(settings.SESSION_ENGINE) new_session = engine.SessionStore() new_session.create() # These values are expected to be set in any new session new_session[SESSION_KEY] = user.id new_session[BACKEND_SESSION_KEY] = user.backend new_session.save() response_data['token'] = new_session.session_key response_data['expires'] = new_session.get_expiry_age() user_dto = UserSerializer(user) response_data['user'] = user_dto.data response_data['uri'] = '{}/{}'.format(base_uri, new_session.session_key) response_status = status.HTTP_201_CREATED # generate a CSRF tokens for any web clients that may need to # call into the LMS via Ajax (for example Notifications) response_data['csrftoken'] = RequestContext(request, {}).get('csrf_token') # update the last_login fields in the auth_user table for this user user.last_login = timezone.now() user.save() # add to audit log AUDIT_LOG.info(u"API::User logged in successfully with user-id - {0}".format(user.id)) else: response_status = status.HTTP_403_FORBIDDEN else: limiter.tick_bad_request_counter(request) # tick the failed login counters if the user exists in the database if LoginFailures.is_feature_enabled(): LoginFailures.increment_lockout_counter(existing_user) response_status = status.HTTP_401_UNAUTHORIZED AUDIT_LOG.warn(u"API::User authentication failed with user-id - {0}".format(existing_user.id)) else: AUDIT_LOG.warn(u"API::Failed login attempt with unknown email/username") response_status = status.HTTP_404_NOT_FOUND return Response(response_data, status=response_status)
def update_example_certificate(request): """Callback from the XQueue that updates example certificates. Example certificates are used to verify that certificate generation is configured correctly for a course. Unlike other certificates, example certificates are not associated with a particular user or displayed to students. For this reason, we need a different end-point to update the status of generated example certificates. Arguments: request (HttpRequest) Returns: HttpResponse (200): Status was updated successfully. HttpResponse (400): Invalid parameters. HttpResponse (403): Rate limit exceeded for bad requests. HttpResponse (404): Invalid certificate identifier or access key. """ logger.info(u"Received response for example certificate from XQueue.") rate_limiter = BadRequestRateLimiter() # Check the parameters and rate limits # If these are invalid, return an error response. if rate_limiter.is_rate_limit_exceeded(request): logger.info(u"Bad request rate limit exceeded for update example certificate end-point.") return HttpResponseForbidden("Rate limit exceeded") if 'xqueue_body' not in request.POST: logger.info(u"Missing parameter 'xqueue_body' for update example certificate end-point") rate_limiter.tick_bad_request_counter(request) return JsonResponseBadRequest("Parameter 'xqueue_body' is required.") if 'xqueue_header' not in request.POST: logger.info(u"Missing parameter 'xqueue_header' for update example certificate end-point") rate_limiter.tick_bad_request_counter(request) return JsonResponseBadRequest("Parameter 'xqueue_header' is required.") try: xqueue_body = json.loads(request.POST['xqueue_body']) xqueue_header = json.loads(request.POST['xqueue_header']) except (ValueError, TypeError): logger.info(u"Could not decode params to example certificate end-point as JSON.") rate_limiter.tick_bad_request_counter(request) return JsonResponseBadRequest("Parameters must be JSON-serialized.") # Attempt to retrieve the example certificate record # so we can update the status. try: uuid = xqueue_body.get('username') access_key = xqueue_header.get('lms_key') cert = ExampleCertificate.objects.get(uuid=uuid, access_key=access_key) except ExampleCertificate.DoesNotExist: # If we are unable to retrieve the record, it means the uuid or access key # were not valid. This most likely means that the request is NOT coming # from the XQueue. Return a 404 and increase the bad request counter # to protect against a DDOS attack. logger.info(u"Could not find example certificate with uuid '%s' and access key '%s'", uuid, access_key) rate_limiter.tick_bad_request_counter(request) raise Http404 if 'error' in xqueue_body: # If an error occurs, save the error message so we can fix the issue. error_reason = xqueue_body.get('error_reason') cert.update_status(ExampleCertificate.STATUS_ERROR, error_reason=error_reason) logger.warning( ( u"Error occurred during example certificate generation for uuid '%s'. " u"The error response was '%s'." ), uuid, error_reason ) else: # If the certificate generated successfully, save the download URL # so we can display the example certificate. download_url = xqueue_body.get('url') if download_url is None: rate_limiter.tick_bad_request_counter(request) logger.warning(u"No download URL provided for example certificate with uuid '%s'.", uuid) return JsonResponseBadRequest( "Parameter 'download_url' is required for successfully generated certificates." ) else: cert.update_status(ExampleCertificate.STATUS_SUCCESS, download_url=download_url) logger.info("Successfully updated example certificate with uuid '%s'.", uuid) # Let the XQueue know that we handled the response return JsonResponse({'return_code': 0})
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = 'shoppingcart/registration_code_redemption.html' if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code, request, limiter) course = get_course_by_id(course_registration.course_id, depth=0) # Restrict the user from enrolling based on country access rules embargo_redirect = embargo_api.redirect_if_blocked( course.id, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect is not None: return redirect(embargo_redirect) context = { 'reg_code_already_redeemed': reg_code_already_redeemed, 'reg_code_is_valid': reg_code_is_valid, 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'registered_for_course': not _is_enrollment_code_an_update(course, request.user, course_registration) } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter ) course = get_course_by_id(course_registration.course_id, depth=0) # Restrict the user from enrolling based on country access rules embargo_redirect = embargo_api.redirect_if_blocked( course.id, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect is not None: return redirect(embargo_redirect) context = { 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'reg_code_is_valid': reg_code_is_valid, 'reg_code_already_redeemed': reg_code_already_redeemed, } if reg_code_is_valid and not reg_code_already_redeemed: # remove the course from the cart if it was added there. cart = Order.get_cart_for_user(request.user) try: cart_items = cart.find_item_by_course_id(course_registration.course_id) except ItemNotFoundInCartException: pass else: for cart_item in cart_items: if isinstance(cart_item, PaidCourseRegistration) or isinstance(cart_item, CourseRegCodeItem): # Reload the item directly to prevent select_subclasses() hackery from interfering with # deletion of all objects in the model inheritance hierarchy cart_item = cart_item.__class__.objects.get(id=cart_item.id) cart_item.delete() #now redeem the reg code. redemption = RegistrationCodeRedemption.create_invoice_generated_registration_redemption(course_registration, request.user) try: kwargs = {} if course_registration.mode_slug is not None: if CourseMode.mode_for_course(course.id, course_registration.mode_slug): kwargs['mode'] = course_registration.mode_slug else: raise RedemptionCodeError() redemption.course_enrollment = CourseEnrollment.enroll(request.user, course.id, **kwargs) redemption.save() context['redemption_success'] = True except RedemptionCodeError: context['redeem_code_error'] = True context['redemption_success'] = False except EnrollmentClosedError: context['enrollment_closed'] = True context['redemption_success'] = False except CourseFullError: context['course_full'] = True context['redemption_success'] = False except AlreadyEnrolledError: context['registered_for_course'] = True context['redemption_success'] = False else: context['redemption_success'] = False return render_to_response(template_to_render, context)
def password_change_request_handler(request): """Handle password change requests originating from the account page. Uses the Account API to email the user a link to the password reset page. Note: The next step in the password reset process (confirmation) is currently handled by student.views.password_reset_confirm_wrapper, a custom wrapper around Django's password reset confirmation view. Args: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method Example usage: POST /account/password """ limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated() else request.POST.get('email') if email: try: request_password_change(email, request.is_secure()) user = user if user.is_authenticated() else User.objects.get(email=email) destroy_oauth_tokens(user) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) # If enabled, send an email saying that a password reset was attempted, but that there is # no user associated with the email if configuration_helpers.get_value('ENABLE_PASSWORD_RESET_FAILURE_EMAIL', settings.FEATURES['ENABLE_PASSWORD_RESET_FAILURE_EMAIL']): context = { 'failed': True, 'email_address': email, 'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME), } subject = loader.render_to_string('emails/password_reset_subject.txt', context) subject = ''.join(subject.splitlines()) message = loader.render_to_string('registration/password_reset_email.html', context) from_email = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) try: send_mail(subject, message, from_email, [email]) except Exception: # pylint: disable=broad-except log.exception(u'Unable to send password reset failure email notification from "%s"', from_email) except UserAPIInternalError as err: log.exception('Error occured during password change for user {email}: {error}' .format(email=email, error=err)) return HttpResponse(_("Some error occured during password change. Please try again"), status=500) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def password_change_request_handler(request): """Handle password change requests originating from the account page. Uses the Account API to email the user a link to the password reset page. Note: The next step in the password reset process (confirmation) is currently handled by student.views.password_reset_confirm_wrapper, a custom wrapper around Django's password reset confirmation view. Args: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method Example usage: POST /account/password """ limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated else request.POST.get('email') if email: try: request_password_change(email, request.is_secure()) user = user if user.is_authenticated else User.objects.get(email=email) destroy_oauth_tokens(user) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) # If enabled, send an email saying that a password reset was attempted, but that there is # no user associated with the email if configuration_helpers.get_value('ENABLE_PASSWORD_RESET_FAILURE_EMAIL', settings.FEATURES['ENABLE_PASSWORD_RESET_FAILURE_EMAIL']): site = get_current_site() message_context = get_base_template_context(site) message_context.update({ 'failed': True, 'request': request, # Used by google_analytics_tracking_pixel 'email_address': email, }) msg = PasswordReset().personalize( recipient=Recipient(username='', email_address=email), language=settings.LANGUAGE_CODE, user_context=message_context, ) ace.send(msg) except UserAPIInternalError as err: log.exception('Error occured during password change for user {email}: {error}' .format(email=email, error=err)) return HttpResponse(_("Some error occured during password change. Please try again"), status=500) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def post(self, request): response_data = {} # Add some rate limiting here by re-using the RateLimitMixin as a helper class limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): response_data['message'] = _('Rate limit exceeded in api login.') return Response(response_data, status=status.HTTP_403_FORBIDDEN) base_uri = generate_base_uri(request) try: existing_user = User.objects.get(username=request.DATA['username']) except ObjectDoesNotExist: existing_user = None # see if account has been locked out due to excessive login failures if existing_user and LoginFailures.is_feature_enabled(): if LoginFailures.is_user_locked_out(existing_user): response_status = status.HTTP_403_FORBIDDEN response_data['message'] = _('This account has been temporarily locked due to excessive login failures. ' 'Try again later.') return Response(response_data, status=response_status) # see if the user must reset his/her password due to any policy settings if existing_user and PasswordHistory.should_user_reset_password_now(existing_user): response_status = status.HTTP_403_FORBIDDEN response_data['message'] = _( 'Your password has expired due to password policy on this account. ' 'You must reset your password before you can log in again.' ) return Response(response_data, status=response_status) if existing_user: user = authenticate(username=existing_user.username, password=request.DATA['password']) if user is not None: # successful login, clear failed login attempts counters, if applicable if LoginFailures.is_feature_enabled(): LoginFailures.clear_lockout_counter(user) if user.is_active: login(request, user) response_data['token'] = request.session.session_key response_data['expires'] = request.session.get_expiry_age() user_dto = UserSerializer(user) response_data['user'] = user_dto.data response_data['uri'] = '{}/{}'.format(base_uri, request.session.session_key) response_status = status.HTTP_201_CREATED # add to audit log AUDIT_LOG.info(u"API::User logged in successfully with user-id - {0}".format(user.id)) else: response_status = status.HTTP_403_FORBIDDEN else: limiter.tick_bad_request_counter(request) # tick the failed login counters if the user exists in the database if LoginFailures.is_feature_enabled(): LoginFailures.increment_lockout_counter(existing_user) response_status = status.HTTP_401_UNAUTHORIZED AUDIT_LOG.warn(u"API::User authentication failed with user-id - {0}".format(existing_user.id)) else: AUDIT_LOG.warn(u"API::Failed login attempt with unknown email/username") response_status = status.HTTP_404_NOT_FOUND return Response(response_data, status=response_status)
def login_user(request, session_id=None): """ Create a new session and login the user, or upgrade an existing session """ response_data = {} # Add some rate limiting here by re-using the RateLimitMixin as a helper class limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): response_data['message'] = _('Rate limit exceeded in api login.') return Response(response_data, status=status.HTTP_403_FORBIDDEN) base_uri = generate_base_uri(request) try: existing_user = User.objects.get(username=request.DATA['username']) except ObjectDoesNotExist: existing_user = None # see if account has been locked out due to excessive login failures if existing_user and LoginFailures.is_feature_enabled(): if LoginFailures.is_user_locked_out(existing_user): response_status = status.HTTP_403_FORBIDDEN response_data['message'] = _( 'This account has been temporarily locked due to excessive login failures. ' # pylint: disable=C0301 'Try again later.') return Response(response_data, status=response_status) # see if the user must reset his/her password due to any policy settings if existing_user and PasswordHistory.should_user_reset_password_now( existing_user): response_status = status.HTTP_403_FORBIDDEN response_data['message'] = _( 'Your password has expired due to password policy on this account. ' 'You must reset your password before you can log in again.') return Response(response_data, status=response_status) if existing_user: user = authenticate(username=existing_user.username, password=request.DATA['password']) if user is not None: # successful login, clear failed login attempts counters, if applicable if LoginFailures.is_feature_enabled(): LoginFailures.clear_lockout_counter(user) if user.is_active: # # Create a new session directly with the SESSION_ENGINE # We don't call the django.contrib.auth login() method # because it is bound with the HTTP request. # # Since we are a server-to-server API, we shouldn't # be stateful with respect to the HTTP request # and anything that might come with it, as it could # violate our RESTfulness # engine = import_module(settings.SESSION_ENGINE) if session_id is None: session = engine.SessionStore() session.create() success_status = status.HTTP_201_CREATED else: session = engine.SessionStore(session_id) success_status = status.HTTP_200_OK if SESSION_KEY in session: # Someone is already logged in. The user ID of whoever is logged in # now might be different than the user ID we've been asked to login, # which would be bad. But even if it is the same user, we should not # be asked to login a user who is already logged in. This likely # indicates some sort of programming/validation error and possibly # even a potential security issue - so return 403. return Response({}, status=status.HTTP_403_FORBIDDEN) # These values are expected to be set in any new session session[SESSION_KEY] = user.id session[BACKEND_SESSION_KEY] = user.backend session.save() response_data['token'] = session.session_key response_data['expires'] = session.get_expiry_age() user_dto = UserSerializer(user) response_data['user'] = user_dto.data response_data['uri'] = '{}/{}'.format( base_uri, session.session_key) response_status = success_status # generate a CSRF tokens for any web clients that may need to # call into the LMS via Ajax (for example Notifications) response_data['csrftoken'] = RequestContext( request, {}).get('csrf_token') # update the last_login fields in the auth_user table for this user user.last_login = timezone.now() user.save() # add to audit log AUDIT_LOG.info( u"API::User logged in successfully with user-id - {0}". format(user.id)) # pylint: disable=W1202 else: response_status = status.HTTP_403_FORBIDDEN else: limiter.tick_bad_request_counter(request) # tick the failed login counters if the user exists in the database if LoginFailures.is_feature_enabled(): LoginFailures.increment_lockout_counter(existing_user) response_status = status.HTTP_401_UNAUTHORIZED AUDIT_LOG.warn( u"API::User authentication failed with user-id - {0}". format(existing_user.id)) # pylint: disable=W1202 else: AUDIT_LOG.warn( u"API::Failed login attempt with unknown email/username") response_status = status.HTTP_404_NOT_FOUND return Response(response_data, status=response_status)
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = microsite.get_value("SITE_NAME", settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = "shoppingcart/registration_code_redemption.html" if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter ) course = get_course_by_id(getattr(course_registration, "course_id"), depth=0) context = { "reg_code_already_redeemed": reg_code_already_redeemed, "reg_code_is_valid": reg_code_is_valid, "reg_code": registration_code, "site_name": site_name, "course": course, "registered_for_course": not _is_enrollment_code_an_update(course, request.user, course_registration), } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter ) course = get_course_by_id(getattr(course_registration, "course_id"), depth=0) context = { "reg_code": registration_code, "site_name": site_name, "course": course, "reg_code_is_valid": reg_code_is_valid, "reg_code_already_redeemed": reg_code_already_redeemed, } if reg_code_is_valid and not reg_code_already_redeemed: # remove the course from the cart if it was added there. cart = Order.get_cart_for_user(request.user) try: cart_items = cart.find_item_by_course_id(course_registration.course_id) except ItemNotFoundInCartException: pass else: for cart_item in cart_items: if isinstance(cart_item, PaidCourseRegistration) or isinstance(cart_item, CourseRegCodeItem): cart_item.delete() # now redeem the reg code. redemption = RegistrationCodeRedemption.create_invoice_generated_registration_redemption( course_registration, request.user ) try: kwargs = {} if course_registration.mode_slug is not None: if CourseMode.mode_for_course(course.id, course_registration.mode_slug): kwargs["mode"] = course_registration.mode_slug else: raise RedemptionCodeError() redemption.course_enrollment = CourseEnrollment.enroll(request.user, course.id, **kwargs) redemption.save() context["redemption_success"] = True except RedemptionCodeError: context["redeem_code_error"] = True context["redemption_success"] = False except EnrollmentClosedError: context["enrollment_closed"] = True context["redemption_success"] = False except CourseFullError: context["course_full"] = True context["redemption_success"] = False except AlreadyEnrolledError: context["registered_for_course"] = True context["redemption_success"] = False else: context["redemption_success"] = False return render_to_response(template_to_render, context)
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning( "Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = 'shoppingcart/registration_code_redemption.html' if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) # Restrict the user from enrolling based on country access rules embargo_redirect = embargo_api.redirect_if_blocked( course.id, user=request.user, ip_address=get_ip(request), url=request.path) if embargo_redirect is not None: return redirect(embargo_redirect) context = { 'reg_code_already_redeemed': reg_code_already_redeemed, 'reg_code_is_valid': reg_code_is_valid, 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'registered_for_course': not _is_enrollment_code_an_update(course, request.user, course_registration) } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) # Restrict the user from enrolling based on country access rules embargo_redirect = embargo_api.redirect_if_blocked( course.id, user=request.user, ip_address=get_ip(request), url=request.path) if embargo_redirect is not None: return redirect(embargo_redirect) context = { 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'reg_code_is_valid': reg_code_is_valid, 'reg_code_already_redeemed': reg_code_already_redeemed, } if reg_code_is_valid and not reg_code_already_redeemed: # remove the course from the cart if it was added there. cart = Order.get_cart_for_user(request.user) try: cart_items = cart.find_item_by_course_id( course_registration.course_id) except ItemNotFoundInCartException: pass else: for cart_item in cart_items: if isinstance(cart_item, PaidCourseRegistration) or isinstance( cart_item, CourseRegCodeItem): cart_item.delete() #now redeem the reg code. redemption = RegistrationCodeRedemption.create_invoice_generated_registration_redemption( course_registration, request.user) try: kwargs = {} if course_registration.mode_slug is not None: if CourseMode.mode_for_course( course.id, course_registration.mode_slug): kwargs['mode'] = course_registration.mode_slug else: raise RedemptionCodeError() redemption.course_enrollment = CourseEnrollment.enroll( request.user, course.id, **kwargs) redemption.save() context['redemption_success'] = True except RedemptionCodeError: context['redeem_code_error'] = True context['redemption_success'] = False except EnrollmentClosedError: context['enrollment_closed'] = True context['redemption_success'] = False except CourseFullError: context['course_full'] = True context['redemption_success'] = False except AlreadyEnrolledError: context['registered_for_course'] = True context['redemption_success'] = False else: context['redemption_success'] = False return render_to_response(template_to_render, context)
def password_change_request_handler(request): """Handle password change requests originating from the account page. Uses the Account API to email the user a link to the password reset page. Note: The next step in the password reset process (confirmation) is currently handled by student.views.password_reset_confirm_wrapper, a custom wrapper around Django's password reset confirmation view. Args: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method Example usage: POST /account/password """ limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated else request.POST.get('email') if email: try: request_password_change(email, request.is_secure()) user = user if user.is_authenticated else User.objects.get( email=email) destroy_oauth_tokens(user) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) # If enabled, send an email saying that a password reset was attempted, but that there is # no user associated with the email if configuration_helpers.get_value( 'ENABLE_PASSWORD_RESET_FAILURE_EMAIL', settings.FEATURES['ENABLE_PASSWORD_RESET_FAILURE_EMAIL']): site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update({ 'failed': True, 'request': request, # Used by google_analytics_tracking_pixel 'email_address': email, }) msg = PasswordReset().personalize( recipient=Recipient(username='', email_address=email), language=settings.LANGUAGE_CODE, user_context=message_context, ) ace.send(msg) except UserAPIInternalError as err: log.exception( 'Error occured during password change for user {email}: {error}' .format(email=email, error=err)) return HttpResponse(_( "Some error occured during password change. Please try again"), status=500) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = 'shoppingcart/registration_code_redemption.html' if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) context = { 'reg_code_already_redeemed': reg_code_already_redeemed, 'reg_code_is_valid': reg_code_is_valid, 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'registered_for_course': registered_for_course(course, request.user) } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) if reg_code_is_valid and not reg_code_already_redeemed: # remove the course from the cart if it was added there. cart = Order.get_cart_for_user(request.user) try: cart_items = cart.find_item_by_course_id(course_registration.course_id) except ItemNotFoundInCartException: pass else: for cart_item in cart_items: if isinstance(cart_item, PaidCourseRegistration) or isinstance(cart_item, CourseRegCodeItem): cart_item.delete() #now redeem the reg code. redemption = RegistrationCodeRedemption.create_invoice_generated_registration_redemption(course_registration, request.user) redemption.course_enrollment = CourseEnrollment.enroll(request.user, course.id) redemption.save() context = { 'redemption_success': True, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } else: context = { 'reg_code_is_valid': reg_code_is_valid, 'reg_code_already_redeemed': reg_code_already_redeemed, 'redemption_success': False, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } return render_to_response(template_to_render, context)
def update_example_certificate(request): """Callback from the XQueue that updates example certificates. Example certificates are used to verify that certificate generation is configured correctly for a course. Unlike other certificates, example certificates are not associated with a particular user or displayed to students. For this reason, we need a different end-point to update the status of generated example certificates. Arguments: request (HttpRequest) Returns: HttpResponse (200): Status was updated successfully. HttpResponse (400): Invalid parameters. HttpResponse (403): Rate limit exceeded for bad requests. HttpResponse (404): Invalid certificate identifier or access key. """ logger.info(u"Received response for example certificate from XQueue.") rate_limiter = BadRequestRateLimiter() # Check the parameters and rate limits # If these are invalid, return an error response. if rate_limiter.is_rate_limit_exceeded(request): logger.info( u"Bad request rate limit exceeded for update example certificate end-point." ) return HttpResponseForbidden("Rate limit exceeded") if 'xqueue_body' not in request.POST: logger.info( u"Missing parameter 'xqueue_body' for update example certificate end-point" ) rate_limiter.tick_bad_request_counter(request) return JsonResponseBadRequest("Parameter 'xqueue_body' is required.") if 'xqueue_header' not in request.POST: logger.info( u"Missing parameter 'xqueue_header' for update example certificate end-point" ) rate_limiter.tick_bad_request_counter(request) return JsonResponseBadRequest("Parameter 'xqueue_header' is required.") try: xqueue_body = json.loads(request.POST['xqueue_body']) xqueue_header = json.loads(request.POST['xqueue_header']) except (ValueError, TypeError): logger.info( u"Could not decode params to example certificate end-point as JSON." ) rate_limiter.tick_bad_request_counter(request) return JsonResponseBadRequest("Parameters must be JSON-serialized.") # Attempt to retrieve the example certificate record # so we can update the status. try: uuid = xqueue_body.get('username') access_key = xqueue_header.get('lms_key') cert = ExampleCertificate.objects.get(uuid=uuid, access_key=access_key) except ExampleCertificate.DoesNotExist: # If we are unable to retrieve the record, it means the uuid or access key # were not valid. This most likely means that the request is NOT coming # from the XQueue. Return a 404 and increase the bad request counter # to protect against a DDOS attack. logger.info( u"Could not find example certificate with uuid '%s' and access key '%s'", uuid, access_key) rate_limiter.tick_bad_request_counter(request) raise Http404 if 'error' in xqueue_body: # If an error occurs, save the error message so we can fix the issue. error_reason = xqueue_body.get('error_reason') cert.update_status(ExampleCertificate.STATUS_ERROR, error_reason=error_reason) logger.warning(( u"Error occurred during example certificate generation for uuid '%s'. " u"The error response was '%s'."), uuid, error_reason) else: # If the certificate generated successfully, save the download URL # so we can display the example certificate. download_url = xqueue_body.get('url') if download_url is None: rate_limiter.tick_bad_request_counter(request) logger.warning( u"No download URL provided for example certificate with uuid '%s'.", uuid) return JsonResponseBadRequest( "Parameter 'download_url' is required for successfully generated certificates." ) else: cert.update_status(ExampleCertificate.STATUS_SUCCESS, download_url=download_url) logger.info( "Successfully updated example certificate with uuid '%s'.", uuid) # Let the XQueue know that we handled the response return JsonResponse({'return_code': 0})
def register_code_redemption(request, registration_code): """ This view allows the student to redeem the registration code and enroll in the course. """ # Add some rate limiting here by re-using the RateLimitMixin as a helper class site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning( "Rate limit exceeded in registration code redemption.") return HttpResponseForbidden() template_to_render = 'shoppingcart/registration_code_redemption.html' if request.method == "GET": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) context = { 'reg_code_already_redeemed': reg_code_already_redeemed, 'reg_code_is_valid': reg_code_is_valid, 'reg_code': registration_code, 'site_name': site_name, 'course': course, 'registered_for_course': registered_for_course(course, request.user) } return render_to_response(template_to_render, context) elif request.method == "POST": reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity( registration_code, request, limiter) course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0) if reg_code_is_valid and not reg_code_already_redeemed: # remove the course from the cart if it was added there. cart = Order.get_cart_for_user(request.user) try: cart_items = cart.find_item_by_course_id( course_registration.course_id) except ItemNotFoundInCartException: pass else: for cart_item in cart_items: if isinstance(cart_item, PaidCourseRegistration) or isinstance( cart_item, CourseRegCodeItem): cart_item.delete() #now redeem the reg code. redemption = RegistrationCodeRedemption.create_invoice_generated_registration_redemption( course_registration, request.user) redemption.course_enrollment = CourseEnrollment.enroll( request.user, course.id) redemption.save() context = { 'redemption_success': True, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } else: context = { 'reg_code_is_valid': reg_code_is_valid, 'reg_code_already_redeemed': reg_code_already_redeemed, 'redemption_success': False, 'reg_code': registration_code, 'site_name': site_name, 'course': course, } return render_to_response(template_to_render, context)