def _add_honor_code_field(self, form_desc, required=True): """Add an honor code field to a form description. Arguments: form_desc: A form description Keyword Arguments: required (bool): Whether this field is required; defaults to True """ # Separate terms of service and honor code checkboxes if self._is_field_visible("terms_of_service"): terms_text = _(u"Honor Code") # Combine terms of service and honor code checkboxes else: # Translators: This is a legal document users must agree to # in order to register a new account. terms_text = _(u"Terms of Service and Honor Code") terms_link = u"<a href=\"{url}\">{terms_text}</a>".format( url=marketing_link("HONOR"), terms_text=terms_text ) # Translators: "Terms of Service" is a legal document users must agree to # in order to register a new account. label = _(u"I agree to the {platform_name} {terms_of_service}.").format( platform_name=get_themed_value("PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_link ) # Translators: "Terms of Service" is a legal document users must agree to # in order to register a new account. error_msg = _(u"You must agree to the {platform_name} {terms_of_service}.").format( platform_name=get_themed_value("PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_link ) form_desc.add_field( "honor_code", label=label, field_type="checkbox", default=False, required=required, error_messages={ "required": error_msg } )
def __init__(self, *args, **kwargs): super(RegistrationView, self).__init__(*args, **kwargs) # Backwards compatibility: Honor code is required by default, unless # explicitly set to "optional" in Django settings. self._extra_fields_setting = copy.deepcopy( get_themed_value('REGISTRATION_EXTRA_FIELDS')) if not self._extra_fields_setting: self._extra_fields_setting = copy.deepcopy( settings.REGISTRATION_EXTRA_FIELDS) self._extra_fields_setting[ "honor_code"] = self._extra_fields_setting.get( "honor_code", "required") # Check that the setting is configured correctly for field_name in self.EXTRA_FIELDS: if self._extra_fields_setting.get(field_name, "hidden") not in [ "required", "optional", "hidden" ]: msg = u"Setting REGISTRATION_EXTRA_FIELDS values must be either required, optional, or hidden." raise ImproperlyConfigured(msg) # Map field names to the instance method used to add the field to the form self.field_handlers = {} for field_name in self.DEFAULT_FIELDS + self.EXTRA_FIELDS: handler = getattr( self, "_add_{field_name}_field".format(field_name=field_name)) self.field_handlers[field_name] = handler
def get_setting(self, name): """ Get the value of a setting, or raise KeyError """ if name == "ORG_INFO": return json.loads(self.org_info_str) if name == "SP_ENTITY_ID": return self.entity_id if name == "SP_PUBLIC_CERT": if self.public_key: return self.public_key # To allow instances to avoid storing keys in the DB, the key pair can also be set via Django: return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', '') if name == "SP_PRIVATE_KEY": if self.private_key: return self.private_key # To allow instances to avoid storing keys in the DB, the private key can also be set via Django: return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', '') other_config = json.loads(self.other_config_str) if name in ("TECHNICAL_CONTACT", "SUPPORT_CONTACT"): contact = { "givenName": "{} Support".format( get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME)), "emailAddress": settings.TECH_SUPPORT_EMAIL } contact.update(other_config.get(name, {})) return contact return other_config[ name] # SECURITY_CONFIG, SP_EXTRA, or similar extra settings
def get_or_create_root(): """ Returns the root article, or creates it if it doesn't exist. """ try: root = URLPath.root() if not root.article: root.delete() raise NoRootURL return root except NoRootURL: pass starting_content = "\n".join(( _("Welcome to the {platform_name} Wiki").format(platform_name=get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME)), "===", _("Visit a course wiki to add an article."), )) root = URLPath.create_root(title=_("Wiki"), content=starting_content) article = root.article article.group = None article.group_read = True article.group_write = False article.other_read = True article.other_write = False article.save() return root
def get_setting(self, name): """ Get the value of a setting, or raise KeyError """ if name == "ORG_INFO": return json.loads(self.org_info_str) if name == "SP_ENTITY_ID": return self.entity_id if name == "SP_PUBLIC_CERT": if self.public_key: return self.public_key # To allow instances to avoid storing keys in the DB, the key pair can also be set via Django: return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', '') if name == "SP_PRIVATE_KEY": if self.private_key: return self.private_key # To allow instances to avoid storing keys in the DB, the private key can also be set via Django: return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', '') other_config = json.loads(self.other_config_str) if name in ("TECHNICAL_CONTACT", "SUPPORT_CONTACT"): contact = { "givenName": "{} Support".format(get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME)), "emailAddress": settings.TECH_SUPPORT_EMAIL } contact.update(other_config.get(name, {})) return contact return other_config[name] # SECURITY_CONFIG, SP_EXTRA, or similar extra settings
def _send_decision_email(instance): """ Send an email to requesting user with the decision made about their request. """ context = { 'name': instance.user.username, 'api_management_url': urlunsplit( ( 'https' if settings.HTTPS == 'on' else 'http', instance.site.domain, reverse('api_admin:api-status'), '', '', ) ), 'authentication_docs_url': settings.AUTH_DOCUMENTATION_URL, 'api_docs_url': settings.API_DOCUMENTATION_URL, 'support_email_address': settings.API_ACCESS_FROM_EMAIL, 'platform_name': get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME) } message = render_to_string( 'api_admin/api_access_request_email_{status}.txt'.format(status=instance.status), context ) try: send_mail( _('API access request'), message, settings.API_ACCESS_FROM_EMAIL, [instance.user.email], fail_silently=False ) instance.contacted = True except SMTPException: log.exception('Error sending API user notification email for request [%s].', instance.id)
def get_or_create_root(): """ Returns the root article, or creates it if it doesn't exist. """ try: root = URLPath.root() if not root.article: root.delete() raise NoRootURL return root except NoRootURL: pass starting_content = "\n".join(( _("Welcome to the {platform_name} Wiki").format( platform_name=get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME)), "===", _("Visit a course wiki to add an article."), )) root = URLPath.create_root(title=_("Wiki"), content=starting_content) article = root.article article.group = None article.group_read = True article.group_write = False article.other_read = True article.other_write = False article.save() return root
def get(self, request): """Return a description of the password reset form. This decouples clients from the API definition: if the API decides to modify the form, clients won't need to be updated. See `user_api.helpers.FormDescription` for examples of the JSON-encoded form description. Returns: HttpResponse """ form_desc = FormDescription("post", reverse("password_change_request")) # Translators: This label appears above a field on the password reset # form meant to hold the user's email address. email_label = _(u"Email") # Translators: This example email address is used as a placeholder in # a field on the password reset form meant to hold the user's email address. email_placeholder = _(u"*****@*****.**") # Translators: These instructions appear on the password reset form, # immediately below a field meant to hold the user's email address. email_instructions = _(u"The email address you used to register with {platform_name}").format( platform_name=get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME) ) form_desc.add_field( "email", field_type="email", label=email_label, placeholder=email_placeholder, instructions=email_instructions, restrictions={ "min_length": EMAIL_MIN_LENGTH, "max_length": EMAIL_MAX_LENGTH, } ) return HttpResponse(form_desc.to_json(), content_type="application/json")
def get(self, request): """Return a description of the password reset form. This decouples clients from the API definition: if the API decides to modify the form, clients won't need to be updated. See `user_api.helpers.FormDescription` for examples of the JSON-encoded form description. Returns: HttpResponse """ form_desc = FormDescription("post", reverse("password_change_request")) # Translators: This label appears above a field on the password reset # form meant to hold the user's email address. email_label = _(u"Email") # Translators: This example email address is used as a placeholder in # a field on the password reset form meant to hold the user's email address. email_placeholder = _(u"*****@*****.**") # Translators: These instructions appear on the password reset form, # immediately below a field meant to hold the user's email address. email_instructions = _( u"The email address you used to register with {platform_name}" ).format(platform_name=get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME)) form_desc.add_field("email", field_type="email", label=email_label, placeholder=email_placeholder, instructions=email_instructions, restrictions={ "min_length": EMAIL_MIN_LENGTH, "max_length": EMAIL_MAX_LENGTH, }) return HttpResponse(form_desc.to_json(), content_type="application/json")
def _add_goals_field(self, form_desc, required=True): """Add a goals field to a form description. Arguments: form_desc: A form description Keyword Arguments: required (bool): Whether this field is required; defaults to True """ # Translators: This phrase appears above a field on the registration form # meant to hold the user's reasons for registering with edX. goals_label = _(u"Tell us why you're interested in {platform_name}" ).format(platform_name=get_themed_value( "PLATFORM_NAME", settings.PLATFORM_NAME)) form_desc.add_field("goals", label=goals_label, field_type="textarea", required=required)
def __init__(self, *args, **kwargs): super(RegistrationView, self).__init__(*args, **kwargs) # Backwards compatibility: Honor code is required by default, unless # explicitly set to "optional" in Django settings. self._extra_fields_setting = copy.deepcopy(get_themed_value('REGISTRATION_EXTRA_FIELDS')) if not self._extra_fields_setting: self._extra_fields_setting = copy.deepcopy(settings.REGISTRATION_EXTRA_FIELDS) self._extra_fields_setting["honor_code"] = self._extra_fields_setting.get("honor_code", "required") # Check that the setting is configured correctly for field_name in self.EXTRA_FIELDS: if self._extra_fields_setting.get(field_name, "hidden") not in ["required", "optional", "hidden"]: msg = u"Setting REGISTRATION_EXTRA_FIELDS values must be either required, optional, or hidden." raise ImproperlyConfigured(msg) # Map field names to the instance method used to add the field to the form self.field_handlers = {} for field_name in self.DEFAULT_FIELDS + self.EXTRA_FIELDS: handler = getattr(self, "_add_{field_name}_field".format(field_name=field_name)) self.field_handlers[field_name] = handler
def render(self, name, value, attrs=None): final_attrs = self.build_attrs(attrs, type='checkbox', name=name) if self.check_test(value): final_attrs['checked'] = 'checked' if not (value is True or value is False or value is None or value == ''): # Only add the 'value' attribute if a value is non-empty. final_attrs['value'] = force_text(value) # Translators: link_start and link_end are HTML tags for a link to the terms of service. # platform_name is the name of this Open edX installation. label = _('I, and my company, accept the {link_start}{platform_name} API Terms of Service{link_end}.').format( platform_name=get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME), link_start='<a href="{url}" target="_blank">'.format(url=reverse('api_admin:api-tos')), link_end='</a>', ) html = '<input{{}} /> <label class="tos-checkbox-label" for="{id}">{label}</label>'.format( id=final_attrs['id'], label=label ) return format_html(html, flatatt(final_attrs))
def _add_goals_field(self, form_desc, required=True): """Add a goals field to a form description. Arguments: form_desc: A form description Keyword Arguments: required (bool): Whether this field is required; defaults to True """ # Translators: This phrase appears above a field on the registration form # meant to hold the user's reasons for registering with edX. goals_label = _(u"Tell us why you're interested in {platform_name}").format( platform_name=get_themed_value("PLATFORM_NAME", settings.PLATFORM_NAME) ) form_desc.add_field( "goals", label=goals_label, field_type="textarea", required=required )
def _send_decision_email(instance): """ Send an email to requesting user with the decision made about their request. """ context = { 'name': instance.user.username, 'api_management_url': urlunsplit(( 'https' if settings.HTTPS == 'on' else 'http', instance.site.domain, reverse('api_admin:api-status'), '', '', )), 'authentication_docs_url': settings.AUTH_DOCUMENTATION_URL, 'api_docs_url': settings.API_DOCUMENTATION_URL, 'support_email_address': settings.API_ACCESS_FROM_EMAIL, 'platform_name': get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME) } message = render_to_string( 'api_admin/api_access_request_email_{status}.txt'.format( status=instance.status), context) try: send_mail(_('API access request'), message, settings.API_ACCESS_FROM_EMAIL, [instance.user.email], fail_silently=False) instance.contacted = True except SMTPException: log.exception( 'Error sending API user notification email for request [%s].', instance.id)
def render(self, name, value, attrs=None): final_attrs = self.build_attrs(attrs, type='checkbox', name=name) if self.check_test(value): final_attrs['checked'] = 'checked' if not (value is True or value is False or value is None or value == ''): # Only add the 'value' attribute if a value is non-empty. final_attrs['value'] = force_text(value) # Translators: link_start and link_end are HTML tags for a link to the terms of service. # platform_name is the name of this Open edX installation. label = _( 'I, and my company, accept the {link_start}{platform_name} API Terms of Service{link_end}.' ).format( platform_name=get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME), link_start='<a href="{url}" target="_blank">'.format( url=reverse('api_admin:api-tos')), link_end='</a>', ) html = '<input{{}} /> <label class="tos-checkbox-label" for="{id}">{label}</label>'.format( id=final_attrs['id'], label=label) return format_html(html, flatatt(final_attrs))
def account_settings_context(request): """ Context for the account settings page. Args: request: The request object. Returns: dict """ user = request.user year_of_birth_options = [(unicode(year), unicode(year)) for year in UserProfile.VALID_YEARS] try: user_orders = get_user_orders(user) except: # pylint: disable=bare-except log.exception('Error fetching order history from Otto.') # Return empty order list as account settings page expect a list and # it will be broken if exception raised user_orders = [] context = { 'auth': {}, 'duplicate_provider': None, 'fields': { 'country': { 'options': list(countries), }, 'gender': { 'options': [(choice[0], _(choice[1])) for choice in UserProfile.GENDER_CHOICES], # pylint: disable=translation-of-non-string }, 'language': { 'options': released_languages(), }, 'level_of_education': { 'options': [(choice[0], _(choice[1])) for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES], # pylint: disable=translation-of-non-string }, 'password': { 'url': reverse('password_reset'), }, 'year_of_birth': { 'options': year_of_birth_options, }, 'preferred_language': { 'options': all_languages(), }, 'time_zone': { 'options': UserPreference.TIME_ZONE_CHOICES, 'enabled': settings.FEATURES.get('ENABLE_TIME_ZONE_PREFERENCE'), } }, 'platform_name': get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'user_accounts_api_url': reverse("accounts_api", kwargs={'username': user.username}), 'user_preferences_api_url': reverse('preferences_api', kwargs={'username': user.username}), 'disable_courseware_js': True, 'show_program_listing': ProgramsApiConfig.current().show_program_listing, 'order_history': user_orders } if third_party_auth.is_enabled(): # If the account on the third party provider is already connected with another edX account, # we display a message to the user. context['duplicate_provider'] = pipeline.get_duplicate_provider(messages.get_messages(request)) auth_states = pipeline.get_provider_user_states(user) context['auth']['providers'] = [{ 'id': state.provider.provider_id, 'name': state.provider.name, # The name of the provider e.g. Facebook 'connected': state.has_account, # Whether the user's edX account is connected with the provider. # If the user is not connected, they should be directed to this page to authenticate # with the particular provider, as long as the provider supports initiating a login. 'connect_url': pipeline.get_login_url( state.provider.provider_id, pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS, # The url the user should be directed to after the auth process has completed. redirect_url=reverse('account_settings'), ), 'accepts_logins': state.provider.accepts_logins, # If the user is connected, sending a POST request to this url removes the connection # information for this provider from their edX account. 'disconnect_url': pipeline.get_disconnect_url(state.provider.provider_id, state.association_id), } for state in auth_states] return context
def login_and_registration_form(request, initial_mode="login"): """Render the combined login/registration form, defaulting to login This relies on the JS to asynchronously load the actual form from the user_api. Keyword Args: initial_mode (string): Either "login" or "register". """ # Determine the URL to redirect to following login/registration/third_party_auth redirect_to = get_next_url_for_login_page(request) # If we're already logged in, redirect to the dashboard if request.user.is_authenticated(): return redirect(redirect_to) # Retrieve the form descriptions from the user API form_descriptions = _get_form_descriptions(request) # If this is a themed site, revert to the old login/registration pages. # We need to do this for now to support existing themes. # Themed sites can use the new logistration page by setting # 'ENABLE_COMBINED_LOGIN_REGISTRATION' in their theme/microsite # configuration settings. if is_request_in_themed_site() and not get_themed_value('ENABLE_COMBINED_LOGIN_REGISTRATION', False): if initial_mode == "login": return old_login_view(request) elif initial_mode == "register": return old_register_view(request) # Allow external auth to intercept and handle the request ext_auth_response = _external_auth_intercept(request, initial_mode) if ext_auth_response is not None: return ext_auth_response # Our ?next= URL may itself contain a parameter 'tpa_hint=x' that we need to check. # If present, we display a login page focused on third-party auth with that provider. third_party_auth_hint = None if '?' in redirect_to: try: next_args = urlparse.parse_qs(urlparse.urlparse(redirect_to).query) provider_id = next_args['tpa_hint'][0] if third_party_auth.provider.Registry.get(provider_id=provider_id): third_party_auth_hint = provider_id initial_mode = "hinted_login" except (KeyError, ValueError, IndexError): pass # Otherwise, render the combined login/registration page context = { 'data': { 'login_redirect_url': redirect_to, 'initial_mode': initial_mode, 'third_party_auth': _third_party_auth_context(request, redirect_to), 'third_party_auth_hint': third_party_auth_hint or '', 'platform_name': get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME), # Include form descriptions retrieved from the user API. # We could have the JS client make these requests directly, # but we include them in the initial page load to avoid # the additional round-trip to the server. 'login_form_desc': json.loads(form_descriptions['login']), 'registration_form_desc': json.loads(form_descriptions['registration']), 'password_reset_form_desc': json.loads(form_descriptions['password_reset']), }, 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header 'responsive': True, 'allow_iframing': True, 'disable_courseware_js': True, 'disable_footer': True, } return render_to_response('student_account/login_and_register.html', context)
def account_settings_context(request): """ Context for the account settings page. Args: request: The request object. Returns: dict """ user = request.user year_of_birth_options = [(unicode(year), unicode(year)) for year in UserProfile.VALID_YEARS] try: user_orders = get_user_orders(user) except: # pylint: disable=bare-except log.exception("Error fetching order history from Otto.") # Return empty order list as account settings page expect a list and # it will be broken if exception raised user_orders = [] context = { "auth": {}, "duplicate_provider": None, "fields": { "country": {"options": list(countries)}, "gender": { "options": [ (choice[0], _(choice[1])) for choice in UserProfile.GENDER_CHOICES ] # pylint: disable=translation-of-non-string }, "language": {"options": released_languages()}, "level_of_education": { "options": [ (choice[0], _(choice[1])) for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES ] # pylint: disable=translation-of-non-string }, "password": {"url": reverse("password_reset")}, "year_of_birth": {"options": year_of_birth_options}, "preferred_language": {"options": all_languages()}, "time_zone": { "options": TIME_ZONE_CHOICES, "enabled": settings.FEATURES.get("ENABLE_TIME_ZONE_PREFERENCE"), }, }, "platform_name": get_themed_value("PLATFORM_NAME", settings.PLATFORM_NAME), "user_accounts_api_url": reverse("accounts_api", kwargs={"username": user.username}), "user_preferences_api_url": reverse("preferences_api", kwargs={"username": user.username}), "disable_courseware_js": True, "show_program_listing": ProgramsApiConfig.current().show_program_listing, "order_history": user_orders, } if third_party_auth.is_enabled(): # If the account on the third party provider is already connected with another edX account, # we display a message to the user. context["duplicate_provider"] = pipeline.get_duplicate_provider(messages.get_messages(request)) auth_states = pipeline.get_provider_user_states(user) context["auth"]["providers"] = [ { "id": state.provider.provider_id, "name": state.provider.name, # The name of the provider e.g. Facebook "connected": state.has_account, # Whether the user's edX account is connected with the provider. # If the user is not connected, they should be directed to this page to authenticate # with the particular provider, as long as the provider supports initiating a login. "connect_url": pipeline.get_login_url( state.provider.provider_id, pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS, # The url the user should be directed to after the auth process has completed. redirect_url=reverse("account_settings"), ), "accepts_logins": state.provider.accepts_logins, # If the user is connected, sending a POST request to this url removes the connection # information for this provider from their edX account. "disconnect_url": pipeline.get_disconnect_url(state.provider.provider_id, state.association_id), } for state in auth_states ] return context
def _record_feedback_in_zendesk( realname, email, subject, details, tags, additional_info, group_name=None, require_update=False, support_email=None ): """ Create a new user-requested Zendesk ticket. Once created, the ticket will be updated with a private comment containing additional information from the browser and server, such as HTTP headers and user state. Returns a boolean value indicating whether ticket creation was successful, regardless of whether the private comment update succeeded. If `group_name` is provided, attaches the ticket to the matching Zendesk group. If `require_update` is provided, returns False when the update does not succeed. This allows using the private comment to add necessary information which the user will not see in followup emails from support. """ zendesk_api = _ZendeskApi() additional_info_string = ( u"Additional information:\n\n" + u"\n".join(u"%s: %s" % (key, value) for (key, value) in additional_info.items() if value is not None) ) # Tag all issues with LMS to distinguish channel in Zendesk; requested by student support team zendesk_tags = list(tags.values()) + ["LMS"] # Per edX support, we would like to be able to route white label feedback items # via tagging white_label_org = get_themed_value('course_org_filter') if white_label_org: zendesk_tags = zendesk_tags + ["whitelabel_{org}".format(org=white_label_org)] new_ticket = { "ticket": { "requester": {"name": realname, "email": email}, "subject": subject, "comment": {"body": details}, "tags": zendesk_tags } } group = None if group_name is not None: group = zendesk_api.get_group(group_name) if group is not None: new_ticket['ticket']['group_id'] = group['id'] if support_email is not None: # If we do not include the `recipient` key here, Zendesk will default to using its default reply # email address when support agents respond to tickets. By setting the `recipient` key here, # we can ensure that WL site users are responded to via the correct Zendesk support email address. new_ticket['ticket']['recipient'] = support_email try: ticket_id = zendesk_api.create_ticket(new_ticket) if group_name is not None and group is None: # Support uses Zendesk groups to track tickets. In case we # haven't been able to correctly group this ticket, log its ID # so it can be found later. log.warning('Unable to find group named %s for Zendesk ticket with ID %s.', group_name, ticket_id) except zendesk.ZendeskError: log.exception("Error creating Zendesk ticket") return False # Additional information is provided as a private update so the information # is not visible to the user. ticket_update = {"ticket": {"comment": {"public": False, "body": additional_info_string}}} try: zendesk_api.update_ticket(ticket_id, ticket_update) except zendesk.ZendeskError: log.exception("Error updating Zendesk ticket with ID %s.", ticket_id) # The update is not strictly necessary, so do not indicate # failure to the user unless it has been requested with # `require_update`. if require_update: return False return True
def submit_feedback(request): """ Create a new user-requested ticket, currently implemented with Zendesk. If feedback submission is not enabled, any request will raise `Http404`. If any configuration parameter (`ZENDESK_URL`, `ZENDESK_USER`, or `ZENDESK_API_KEY`) is missing, any request will raise an `Exception`. The request must be a POST request specifying `subject` and `details`. If the user is not authenticated, the request must also specify `name` and `email`. If the user is authenticated, the `name` and `email` will be populated from the user's information. If any required parameter is missing, a 400 error will be returned indicating which field is missing and providing an error message. If Zendesk ticket creation fails, 500 error will be returned with no body; if ticket creation succeeds, an empty successful response (200) will be returned. """ if not settings.FEATURES.get('ENABLE_FEEDBACK_SUBMISSION', False): raise Http404() if request.method != "POST": return HttpResponseNotAllowed(["POST"]) if ( not settings.ZENDESK_URL or not settings.ZENDESK_USER or not settings.ZENDESK_API_KEY ): raise Exception("Zendesk enabled but not configured") def build_error_response(status_code, field, err_msg): return HttpResponse(json.dumps({"field": field, "error": err_msg}), status=status_code) additional_info = {} required_fields = ["subject", "details"] if not request.user.is_authenticated(): required_fields += ["name", "email"] required_field_errs = { "subject": "Please provide a subject.", "details": "Please provide details.", "name": "Please provide your name.", "email": "Please provide a valid e-mail.", } for field in required_fields: if field not in request.POST or not request.POST[field]: return build_error_response(400, field, required_field_errs[field]) subject = request.POST["subject"] details = request.POST["details"] tags = dict( [(tag, request.POST[tag]) for tag in ["issue_type", "course_id"] if tag in request.POST] ) if request.user.is_authenticated(): realname = request.user.profile.name email = request.user.email additional_info["username"] = request.user.username else: realname = request.POST["name"] email = request.POST["email"] try: validate_email(email) except ValidationError: return build_error_response(400, "email", required_field_errs["email"]) for header, pretty in [ ("HTTP_REFERER", "Page"), ("HTTP_USER_AGENT", "Browser"), ("REMOTE_ADDR", "Client IP"), ("SERVER_NAME", "Host") ]: additional_info[pretty] = request.META.get(header) success = _record_feedback_in_zendesk( realname, email, subject, details, tags, additional_info, support_email=get_themed_value('email_from_address', settings.DEFAULT_FROM_EMAIL) ) _record_feedback_in_datadog(tags) return HttpResponse(status=(200 if success else 500))
def account_settings_context(request): """ Context for the account settings page. Args: request: The request object. Returns: dict """ user = request.user year_of_birth_options = [(unicode(year), unicode(year)) for year in UserProfile.VALID_YEARS] try: user_orders = get_user_orders(user) except: # pylint: disable=bare-except log.exception('Error fetching order history from Otto.') # Return empty order list as account settings page expect a list and # it will be broken if exception raised user_orders = [] context = { 'auth': {}, 'duplicate_provider': None, 'fields': { 'country': { 'options': list(countries), }, 'gender': { 'options': [(choice[0], _(choice[1])) for choice in UserProfile.GENDER_CHOICES], # pylint: disable=translation-of-non-string }, 'language': { 'options': released_languages(), }, 'level_of_education': { 'options': [(choice[0], _(choice[1])) for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES], # pylint: disable=translation-of-non-string }, 'password': { 'url': reverse('password_reset'), }, 'year_of_birth': { 'options': year_of_birth_options, }, 'preferred_language': { 'options': all_languages(), }, 'time_zone': { 'options': UserPreference.TIME_ZONE_CHOICES, 'enabled': settings.FEATURES.get('ENABLE_TIME_ZONE_PREFERENCE'), } }, 'platform_name': get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'user_accounts_api_url': reverse("accounts_api", kwargs={'username': user.username}), 'user_preferences_api_url': reverse('preferences_api', kwargs={'username': user.username}), 'disable_courseware_js': True, 'show_program_listing': ProgramsApiConfig.current().show_program_listing, 'order_history': user_orders } if third_party_auth.is_enabled(): # If the account on the third party provider is already connected with another edX account, # we display a message to the user. context['duplicate_provider'] = pipeline.get_duplicate_provider( messages.get_messages(request)) auth_states = pipeline.get_provider_user_states(user) context['auth']['providers'] = [ { 'id': state.provider.provider_id, 'name': state.provider.name, # The name of the provider e.g. Facebook 'connected': state. has_account, # Whether the user's edX account is connected with the provider. # If the user is not connected, they should be directed to this page to authenticate # with the particular provider, as long as the provider supports initiating a login. 'connect_url': pipeline.get_login_url( state.provider.provider_id, pipeline.AUTH_ENTRY_ACCOUNT_SETTINGS, # The url the user should be directed to after the auth process has completed. redirect_url=reverse('account_settings'), ), 'accepts_logins': state.provider.accepts_logins, # If the user is connected, sending a POST request to this url removes the connection # information for this provider from their edX account. 'disconnect_url': pipeline.get_disconnect_url(state.provider.provider_id, state.association_id), } for state in auth_states ] return context
def login_and_registration_form(request, initial_mode="login"): """Render the combined login/registration form, defaulting to login This relies on the JS to asynchronously load the actual form from the user_api. Keyword Args: initial_mode (string): Either "login" or "register". """ # Determine the URL to redirect to following login/registration/third_party_auth redirect_to = get_next_url_for_login_page(request) # If we're already logged in, redirect to the dashboard if request.user.is_authenticated(): return redirect(redirect_to) # Retrieve the form descriptions from the user API form_descriptions = _get_form_descriptions(request) # If this is a themed site, revert to the old login/registration pages. # We need to do this for now to support existing themes. # Themed sites can use the new logistration page by setting # 'ENABLE_COMBINED_LOGIN_REGISTRATION' in their theme/microsite # configuration settings. if is_request_in_themed_site() and not get_themed_value( 'ENABLE_COMBINED_LOGIN_REGISTRATION', False): if initial_mode == "login": return old_login_view(request) elif initial_mode == "register": return old_register_view(request) # Allow external auth to intercept and handle the request ext_auth_response = _external_auth_intercept(request, initial_mode) if ext_auth_response is not None: return ext_auth_response # Our ?next= URL may itself contain a parameter 'tpa_hint=x' that we need to check. # If present, we display a login page focused on third-party auth with that provider. third_party_auth_hint = None if '?' in redirect_to: try: next_args = urlparse.parse_qs(urlparse.urlparse(redirect_to).query) provider_id = next_args['tpa_hint'][0] if third_party_auth.provider.Registry.get(provider_id=provider_id): third_party_auth_hint = provider_id initial_mode = "hinted_login" except (KeyError, ValueError, IndexError): pass # Otherwise, render the combined login/registration page context = { 'data': { 'login_redirect_url': redirect_to, 'initial_mode': initial_mode, 'third_party_auth': _third_party_auth_context(request, redirect_to), 'third_party_auth_hint': third_party_auth_hint or '', 'platform_name': get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME), # Include form descriptions retrieved from the user API. # We could have the JS client make these requests directly, # but we include them in the initial page load to avoid # the additional round-trip to the server. 'login_form_desc': json.loads(form_descriptions['login']), 'registration_form_desc': json.loads(form_descriptions['registration']), 'password_reset_form_desc': json.loads(form_descriptions['password_reset']), }, 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header 'responsive': True, 'allow_iframing': True, 'disable_courseware_js': True, 'disable_footer': True, } return render_to_response('student_account/login_and_register.html', context)