def login_refresh(request): # lint-amnesty, pylint: disable=missing-function-docstring if not request.user.is_authenticated or request.user.is_anonymous: return JsonResponse('Unauthorized', status=401) try: return get_response_with_refreshed_jwt_cookies(request, request.user) except AuthFailedError as error: log.exception(error.get_response()) return JsonResponse(error.get_response(), status=400)
def get(self, request, username_or_email): try: user = User.objects.get( Q(username=username_or_email) | Q(email=username_or_email)) except User.DoesNotExist: return JsonResponse([]) user_social_auths = UserSocialAuth.objects.filter(user=user) sso_records = serialize_sso_records(user_social_auths) return JsonResponse(sso_records)
def get(self, request, username_or_email): # lint-amnesty, pylint: disable=missing-function-docstring try: user = User.objects.get( Q(username=username_or_email) | Q(email=username_or_email)) except User.DoesNotExist: return JsonResponse([]) user_social_auths = UserSocialAuth.objects.filter(user=user) sso_records = serialize_sso_records(user_social_auths) return JsonResponse(sso_records)
def login_refresh(request): if not request.user.is_authenticated or request.user.is_anonymous: return JsonResponse('Unauthorized', status=401) try: return get_response_with_refreshed_jwt_cookies(request, request.user) except AuthFailedError as error: log.exception(error.get_response()) return JsonResponse(error.get_response(), status=400)
def transcript_upload_handler(request): """ View to upload a transcript file. Arguments: request: A WSGI request object Transcript file, edx video id and transcript language are required. Transcript file should be in SRT(SubRip) format. Returns - A 400 if any of the validation fails - A 200 if transcript has been uploaded successfully """ error = validate_transcript_upload_data(data=request.POST, files=request.FILES) if error: response = JsonResponse({'error': error}, status=400) else: edx_video_id = request.POST['edx_video_id'] language_code = request.POST['language_code'] new_language_code = request.POST['new_language_code'] transcript_file = request.FILES['file'] try: # Convert SRT transcript into an SJSON format # and upload it to S3. sjson_subs = Transcript.convert( content=transcript_file.read().decode('utf-8'), input_format=Transcript.SRT, output_format=Transcript.SJSON).encode() create_or_update_video_transcript( video_id=edx_video_id, language_code=language_code, metadata={ 'provider': TranscriptProvider.CUSTOM, 'file_format': Transcript.SJSON, 'language_code': new_language_code }, file_data=ContentFile(sjson_subs), ) response = JsonResponse(status=201) except (TranscriptsGenerationException, UnicodeDecodeError): response = JsonResponse( { 'error': _(u'There is a problem with this transcript file. Try to upload a different file.' ) }, status=400) return response
def get(self, request, number): """ HTTP handler. """ # If the account activation requirement is disabled for this installation, override the # anonymous user object attached to the request with the actual user object (if it exists) if not request.user.is_authenticated and is_account_activation_requirement_disabled(): try: request.user = User.objects.get(id=request.session._session_cache['_auth_user_id']) # lint-amnesty, pylint: disable=protected-access except User.DoesNotExist: return JsonResponse(status=403) try: order = ecommerce_api_client(request.user).orders(number).get() return JsonResponse(order) except exceptions.HttpNotFoundError: return JsonResponse(status=404)
def _update_pagination_context(request): """ Updates session with posted value """ error_msg = _(u'A non zero positive integer is expected') try: videos_per_page = int(request.POST.get('value')) if videos_per_page <= 0: return JsonResponse({'error': error_msg}, status=500) except ValueError: return JsonResponse({'error': error_msg}, status=500) request.session['VIDEOS_PER_PAGE'] = videos_per_page return JsonResponse()
def get(self, request, username_or_email): """ Returns details for the given user, along with information about its username and joining date. """ try: user = get_user_model().objects.get( Q(username=username_or_email) | Q(email=username_or_email) ) data = AccountUserSerializer(user, context={'request': request}).data data['status'] = _('Usable') if user.has_usable_password() else _('Unusable') return JsonResponse(data) except get_user_model().DoesNotExist: return JsonResponse([])
def get(self, request, *_args, **kwargs): """ HTTP handler. """ try: api_url = urljoin(f"{get_ecommerce_api_base_url()}/", f"baskets/{kwargs['basket_id']}/order/") response = get_ecommerce_api_client(request.user).get(api_url) response.raise_for_status() return JsonResponse(response.json()) except HTTPError as err: if err.response.status_code == 404: return JsonResponse(status=404) raise
def password_reset(request): """ Attempts to send a password reset e-mail. """ user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated else request.POST.get('email') AUDIT_LOG.info("Password reset initiated for email %s.", email) if getattr(request, 'limited', False): AUDIT_LOG.warning("Password reset rate limit exceeded for email %s.", email) return JsonResponse( { 'success': False, 'value': _("Your previous request is in progress, please try again in a few moments." ) }, status=403) 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.") return JsonResponse({ 'success': True, 'value': render_to_string('registration/password_reset_done.html', {}), })
def reorder_tabs_handler(course_item, request): """ Helper function for handling reorder of tabs request """ # Tabs are identified by tab_id or locators. # The locators are used to identify static tabs since they are xmodules. # Although all tabs have tab_ids, newly created static tabs do not know # their tab_ids since the xmodule editor uses only locators to identify new objects. requested_tab_id_locators = request.json['tabs'] # original tab list in original order old_tab_list = course_item.tabs # create a new list in the new order new_tab_list = [] for tab_id_locator in requested_tab_id_locators: tab = get_tab_by_tab_id_locator(old_tab_list, tab_id_locator) if tab is None: return JsonResponse( { "error": u"Tab with id_locator '{0}' does not exist.".format( tab_id_locator) }, status=400) new_tab_list.append(tab) # the old_tab_list may contain additional tabs that were not rendered in the UI because of # global or course settings. so add those to the end of the list. non_displayed_tabs = set(old_tab_list) - set(new_tab_list) new_tab_list.extend(non_displayed_tabs) # validate the tabs to make sure everything is Ok (e.g., did the client try to reorder unmovable tabs?) try: CourseTabList.validate_tabs(new_tab_list) except InvalidTabsException as exception: return JsonResponse( { "error": u"New list of tabs is not valid: {0}.".format(str(exception)) }, status=400) # persist the new order of the tabs course_item.tabs = new_tab_list modulestore().update_item(course_item, request.user.id) return JsonResponse()
def disable_account_ajax(request): """ Ajax call to change user standing. Endpoint of the form in manage_user_standing.html """ if not request.user.is_staff: raise Http404 username = request.POST.get('username') context = {} if username is None or username.strip() == '': context['message'] = _('Please enter a username') return JsonResponse(context, status=400) account_action = request.POST.get('account_action') if account_action is None: context['message'] = _('Please choose an option') return JsonResponse(context, status=400) username = username.strip() try: user = User.objects.get(username=username) except User.DoesNotExist: context['message'] = _("User with username {} does not exist").format( username) return JsonResponse(context, status=400) else: user_account, _success = UserStanding.objects.get_or_create( user=user, defaults={'changed_by': request.user}, ) if account_action == 'disable': user_account.account_status = UserStanding.ACCOUNT_DISABLED context['message'] = _( "Successfully disabled {}'s account").format(username) log.info(u"%s disabled %s's account", request.user, username) elif account_action == 'reenable': user_account.account_status = UserStanding.ACCOUNT_ENABLED context['message'] = _( "Successfully reenabled {}'s account").format(username) log.info(u"%s reenabled %s's account", request.user, username) else: context['message'] = _("Unexpected account status") return JsonResponse(context, status=400) user_account.changed_by = request.user user_account.standing_last_changed_at = datetime.datetime.now(UTC) user_account.save() return JsonResponse(context)
def transcript_credentials_handler(request, course_key_string): """ JSON view handler to update the transcript organization credentials. Arguments: request: WSGI request object course_key_string: A course identifier to extract the org. Returns: - A 200 response if credentials are valid and successfully updated in edx-video-pipeline. - A 404 response if transcript feature is not enabled for this course. - A 400 if credentials do not pass validations, hence not updated in edx-video-pipeline. """ course_key = CourseKey.from_string(course_key_string) if not VideoTranscriptEnabledFlag.feature_enabled(course_key): return HttpResponseNotFound() provider = request.json.pop('provider') error_message, validated_credentials = validate_transcript_credentials( provider=provider, **request.json) if error_message: response = JsonResponse({'error': error_message}, status=400) else: # Send the validated credentials to edx-video-pipeline and video-encode-manager credentials_payload = dict(validated_credentials, org=course_key.org, provider=provider) error_response, is_updated = update_3rd_party_transcription_service_credentials( **credentials_payload) # Send appropriate response based on whether credentials were updated or not. if is_updated: # Cache credentials state in edx-val. update_transcript_credentials_state_for_org(org=course_key.org, provider=provider, exists=is_updated) response = JsonResponse(status=200) else: # Error response would contain error types and the following # error type is received from edx-video-pipeline whenever we've # got invalid credentials for a provider. Its kept this way because # edx-video-pipeline doesn't support i18n translations yet. error_type = error_response.get('error_type') if error_type == TranscriptionProviderErrorType.INVALID_CREDENTIALS: error_message = _('The information you entered is incorrect.') response = JsonResponse({'error': error_message}, status=400) return response
def certificate_activation_handler(request, course_key_string): """ A handler for Certificate Activation/Deactivation POST json: is_active. update the activation state of certificate """ course_key = CourseKey.from_string(course_key_string) store = modulestore() try: course = _get_course_and_check_access(course_key, request.user) except PermissionDenied: msg = _('PermissionDenied: Failed in authenticating {user}').format(user=request.user) return JsonResponse({"error": msg}, status=403) data = json.loads(request.body.decode('utf8')) is_active = data.get('is_active', False) certificates = CertificateManager.get_certificates(course) # for certificate activation/deactivation, we are assuming one certificate in certificates collection. for certificate in certificates: certificate['is_active'] = is_active break store.update_item(course, request.user.id) cert_event_type = 'activated' if is_active else 'deactivated' CertificateManager.track_event(cert_event_type, { 'course_id': str(course.id), }) return HttpResponse(status=200)
def post(self, request, username_or_email): """Allows support staff to disable a user's account.""" user = get_user_model().objects.get( Q(username=username_or_email) | Q(email=username_or_email) ) comment = request.data.get("comment") if user.has_usable_password(): user.set_unusable_password() UserPasswordToggleHistory.objects.create( user=user, comment=comment, created_by=request.user, disabled=True ) else: user.set_password(generate_password(length=25)) UserPasswordToggleHistory.objects.create( user=user, comment=comment, created_by=request.user, disabled=False ) user.save() if user.has_usable_password(): password_status = _('Usable') msg = _('User Enabled Successfully') else: password_status = _('Unusable') msg = _('User Disabled Successfully') return JsonResponse({'success_msg': msg, 'status': password_status})
def cohorting_settings(request, course_key_string): """ The handler for verified track cohorting requests. This will raise 404 if user is not staff. Returns a JSON representation of whether or not the course has verified track cohorting enabled. The "verified_cohort_name" field will only be present if "enabled" is True. Example: >>> example = { >>> "enabled": True, >>> "verified_cohort_name" : "Micromasters" >>> } """ course_key = CourseKey.from_string(course_key_string) get_course_with_access(request.user, 'staff', course_key) settings = {} verified_track_cohort_enabled = VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled( course_key) settings['enabled'] = verified_track_cohort_enabled if verified_track_cohort_enabled: settings[ 'verified_cohort_name'] = VerifiedTrackCohortedCourse.verified_cohort_name_for_course( course_key) return JsonResponse(settings)
def get(self, request, course_id): """ Returns the duration config information if FBE is enabled. If FBE is not enabled, empty dict is returned. * Example Request: - GET /support/feature_based_enrollment_details/<course_id> * Example Response: { "course_id": <course_id>, "course_name": "FBE course", "gating_config": { "enabled": true, "enabled_as_of": "2030-01-01 00:00:00+00:00", "reason": "Site" }, "duration_config": { "enabled": true, "enabled_as_of": "2030-01-01 00:00:00+00:00", "reason": "Site" } } """ return JsonResponse(get_course_duration_info(course_id))
def render_response(self): """ A short method to render_to_response that renders response. """ if self.request.is_ajax(): return JsonResponse(self.context) return render_to_response(self.template, self.context)
def test_get_sailthru_list_map_no_list(self, mock_sailthru_client): """Test when no list returned from sailthru""" mock_sailthru_client.api_get.return_value = SailthruResponse( JsonResponse({'lists': []})) self.assertEqual( _get_list_from_email_marketing_provider(mock_sailthru_client), {}) mock_sailthru_client.api_get.assert_called_with("list", {})
def test_dict(self): obj = {"foo": "bar"} resp = JsonResponse(obj) compare = json.loads(resp.content.decode('utf-8')) self.assertEqual(obj, compare) self.assertEqual(resp.status_code, 200) self.assertEqual(resp["content-type"], "application/json")
def test_set_status_arg(self): obj = {"error": "resource not found"} resp = JsonResponse(obj, 404) compare = json.loads(resp.content.decode('utf-8')) self.assertEqual(obj, compare) self.assertEqual(resp.status_code, 404) self.assertEqual(resp["content-type"], "application/json")
def _list_libraries(request): """ List all accessible libraries, after applying filters in the request Query params: org - The organization used to filter libraries text_search - The string used to filter libraries by searching in title, id or org """ org = request.GET.get('org', '') text_search = request.GET.get('text_search', '').lower() if org: libraries = modulestore().get_libraries(org=org) else: libraries = modulestore().get_libraries() lib_info = [ { "display_name": lib.display_name, "library_key": str(lib.location.library_key), } for lib in libraries if ( ( text_search in lib.display_name.lower() or text_search in lib.location.library_key.org.lower() or text_search in lib.location.library_key.library.lower() ) and has_studio_read_access(request.user, lib.location.library_key) ) ] return JsonResponse(lib_info)
def test_set_status_arg(self): obj = {"error": "resource not found"} resp = JsonResponse(obj, 404) compare = json.loads(resp.content.decode('utf-8')) assert obj == compare assert resp.status_code == 404 assert resp['content-type'] == 'application/json'
def test_dict(self): obj = {"foo": "bar"} resp = JsonResponse(obj) compare = json.loads(resp.content.decode('utf-8')) assert obj == compare assert resp.status_code == 200 assert resp['content-type'] == 'application/json'
def post(self, request, username_or_email): """Allows support staff to alter a user's enrollment.""" try: user = User.objects.get( Q(username=username_or_email) | Q(email=username_or_email)) course_id = request.data['course_id'] course_key = CourseKey.from_string(course_id) old_mode = request.data['old_mode'] new_mode = request.data['new_mode'] reason = request.data['reason'] enrollment = CourseEnrollment.objects.get(user=user, course_id=course_key) if enrollment.mode != old_mode: return HttpResponseBadRequest( 'User {username} is not enrolled with mode {old_mode}.'. format(username=user.username, old_mode=old_mode)) except KeyError as err: return HttpResponseBadRequest('The field {} is required.'.format( str(err))) except InvalidKeyError: return HttpResponseBadRequest('Could not parse course key.') except (CourseEnrollment.DoesNotExist, User.DoesNotExist): return HttpResponseBadRequest( 'Could not find enrollment for user {username} in course {course}.' .format(username=username_or_email, course=str(course_key))) try: # Wrapped in a transaction so that we can be sure the # ManualEnrollmentAudit record is always created correctly. with transaction.atomic(): update_enrollment(user.username, course_id, mode=new_mode, include_expired=True) manual_enrollment = ManualEnrollmentAudit.create_manual_enrollment_audit( request.user, enrollment.user.email, ENROLLED_TO_ENROLLED, reason=reason, enrollment=enrollment) if new_mode == CourseMode.CREDIT_MODE: provider_ids = get_credit_provider_attribute_values( course_key, 'id') credit_provider_attr = { 'namespace': 'credit', 'name': 'provider_id', 'value': provider_ids[0], } CourseEnrollmentAttribute.add_enrollment_attr( enrollment=enrollment, data_list=[credit_provider_attr]) entitlement = CourseEntitlement.get_fulfillable_entitlement_for_user_course_run( user=user, course_run_key=course_id) if entitlement is not None and entitlement.mode == new_mode: entitlement.set_enrollment( CourseEnrollment.get_enrollment(user, course_id)) return JsonResponse( ManualEnrollmentSerializer( instance=manual_enrollment).data) except CourseModeNotFoundError as err: return HttpResponseBadRequest(str(err))
def edxnotes_visibility(request, course_id): """ Handle ajax call from "Show notes" checkbox. """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, "load", course_key) field_data_cache = FieldDataCache([course], course_key, request.user) course_module = get_module_for_descriptor(request.user, request, course, field_data_cache, course_key, course=course) if not is_feature_enabled(course, request.user): raise Http404 try: visibility = json.loads(request.body.decode('utf8'))["visibility"] course_module.edxnotes_visibility = visibility course_module.save() return JsonResponse(status=200) except (ValueError, KeyError): log.warning( u"Could not decode request body as JSON and find a boolean visibility field: '%s'", request.body) return JsonResponseBadRequest()
def create_order(request): """ This endpoint is named 'create_order' for backward compatibility, but its actual use is to add a single product to the user's cart and request immediate checkout. """ course_id = request.POST['course_id'] course_id = CourseKey.from_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) contribution = request.POST.get("contribution", donation_for_course.get(six.text_type(course_id), 0)) try: amount = decimal.Decimal(contribution).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) current_mode = None sku = request.POST.get('sku', None) if sku: try: current_mode = CourseMode.objects.get(sku=sku) except CourseMode.DoesNotExist: log.exception(u'Failed to find CourseMode with SKU [%s].', sku) if not current_mode: # Check if there are more than 1 paid(mode with min_price>0 e.g verified/professional/no-id-professional) modes # for course exist then choose the first one paid_modes = CourseMode.paid_modes_for_course(course_id) if paid_modes: if len(paid_modes) > 1: log.warning(u"Multiple paid course modes found for course '%s' for create order request", course_id) current_mode = paid_modes[0] # Make sure this course has a paid mode if not current_mode: log.warning(u"Create order requested for course '%s' without a paid mode.", course_id) return HttpResponseBadRequest(_("This course doesn't support paid certificates")) if CourseMode.is_professional_mode(current_mode): amount = current_mode.min_price if amount < current_mode.min_price: return HttpResponseBadRequest(_("No selected price or selected price is below minimum.")) # if request.POST doesn't contain 'processor' then the service's default payment processor will be used. payment_data = checkout_with_ecommerce_service( request.user, course_id, current_mode, request.POST.get('processor') ) if 'processor' not in request.POST: # (XCOM-214) To be removed after release. # the absence of this key in the POST payload indicates that the request was initiated from # a stale js client, which expects a response containing only the 'payment_form_data' part of # the payment data result. payment_data = payment_data['payment_form_data'] return JsonResponse(payment_data)
def _assets_json(request, course_key): ''' Display an editable asset library. Supports start (0-based index into the list of assets) and max query parameters. ''' request_options = _parse_request_to_dictionary(request) filter_parameters = {} if request_options['requested_asset_type']: filters_are_invalid_error = _get_error_if_invalid_parameters(request_options['requested_asset_type']) if filters_are_invalid_error is not None: return filters_are_invalid_error filter_parameters.update(_get_content_type_filter_for_mongo(request_options['requested_asset_type'])) if request_options['requested_text_search']: filter_parameters.update(_get_displayname_search_filter_for_mongo(request_options['requested_text_search'])) sort_type_and_direction = _get_sort_type_and_direction(request_options) requested_page_size = request_options['requested_page_size'] current_page = _get_current_page(request_options['requested_page']) first_asset_to_display_index = _get_first_asset_index(current_page, requested_page_size) query_options = { 'current_page': current_page, 'page_size': requested_page_size, 'sort': sort_type_and_direction, 'filter_params': filter_parameters } assets, total_count = _get_assets_for_page(course_key, query_options) if request_options['requested_page'] > 0 and first_asset_to_display_index >= total_count and total_count > 0: # lint-amnesty, pylint: disable=chained-comparison _update_options_to_requery_final_page(query_options, total_count) current_page = query_options['current_page'] first_asset_to_display_index = _get_first_asset_index(current_page, requested_page_size) assets, total_count = _get_assets_for_page(course_key, query_options) last_asset_to_display_index = first_asset_to_display_index + len(assets) assets_in_json_format = _get_assets_in_json_format(assets, course_key) response_payload = { 'start': first_asset_to_display_index, 'end': last_asset_to_display_index, 'page': current_page, 'pageSize': requested_page_size, 'totalCount': total_count, 'assets': assets_in_json_format, 'sort': request_options['requested_sort'], 'direction': request_options['requested_sort_direction'], 'assetTypes': _get_requested_file_types_from_requested_filter(request_options['requested_asset_type']), 'textSearch': request_options['requested_text_search'], } return JsonResponse(response_payload)
def upload_transcript(request): """ Upload a transcript file Arguments: request: A WSGI request object Transcript file in SRT format """ edx_video_id = request.POST['edx_video_id'] language_code = request.POST['language_code'] new_language_code = request.POST['new_language_code'] transcript_file = request.FILES['file'] try: # Convert SRT transcript into an SJSON format # and upload it to S3. sjson_subs = Transcript.convert( content=transcript_file.read().decode('utf-8'), input_format=Transcript.SRT, output_format=Transcript.SJSON).encode() create_or_update_video_transcript( video_id=edx_video_id, language_code=language_code, metadata={ 'provider': TranscriptProvider.CUSTOM, 'file_format': Transcript.SJSON, 'language_code': new_language_code }, file_data=ContentFile(sjson_subs), ) response = JsonResponse(status=201) except (TranscriptsGenerationException, UnicodeDecodeError): LOGGER.error( "Unable to update transcript on edX video %s for language %s", edx_video_id, new_language_code) response = JsonResponse( { 'error': _('There is a problem with this transcript file. Try to upload a different file.' ) }, status=400) finally: LOGGER.info("Updated transcript on edX video %s for language %s", edx_video_id, new_language_code) return response
def test_update_user_error_nonretryable(self, mock_sailthru, mock_log_error, mock_retry): """ Ensure that non-retryable error is not retried """ mock_sailthru.return_value = SailthruResponse(JsonResponse({'error': 1, 'errormsg': 'Got an error'})) update_user.delay({}, self.user.email) self.assertTrue(mock_log_error.called) self.assertFalse(mock_retry.called)