def get_saml_idp_choices(): """ Get a list of the available SAMLIdentityProvider subclasses that can be used to process SAML requests, for use in the Django administration form. """ choices = ( (STANDARD_SAML_PROVIDER_KEY, 'Standard SAML provider'), (SAP_SUCCESSFACTORS_SAML_KEY, 'SAP SuccessFactors provider'), ) add_extended_saml_idp_choices = run_extension_point( 'NAU_ADD_SAML_IDP_CHOICES', choices=choices) return add_extended_saml_idp_choices or choices
def get_saml_idp_class(idp_identifier_string): """ Given a string ID indicating the type of identity provider in use during a given request, return the SAMLIdentityProvider subclass able to handle requests for that type of identity provider. """ choices = { STANDARD_SAML_PROVIDER_KEY: EdXSAMLIdentityProvider, SAP_SUCCESSFACTORS_SAML_KEY: SapSuccessFactorsIdentityProvider, } add_extended_saml_idp_class_choices = run_extension_point( 'NAU_ADD_SAML_IDP_CLASSES', choices=choices, idp_identifier_string=idp_identifier_string) choices = add_extended_saml_idp_class_choices or choices if idp_identifier_string not in choices: log.error( u'[THIRD_PARTY_AUTH] Invalid EdXSAMLIdentityProvider subclass--' u'using EdXSAMLIdentityProvider base class. Provider: {provider}'. format(provider=idp_identifier_string)) return choices.get(idp_identifier_string, EdXSAMLIdentityProvider)
def to_representation(self, user): """ Overwrite to_native to handle custom logic since we are serializing three models as one here :param user: User object :return: Dict serialized account """ try: user_profile = user.profile except ObjectDoesNotExist: user_profile = None LOGGER.warning(u"user profile for the user [%s] does not exist", user.username) try: account_recovery = user.account_recovery except ObjectDoesNotExist: account_recovery = None accomplishments_shared = badges_enabled() data = { "username": user.username, "url": self.context.get('request').build_absolute_uri( reverse('accounts_api', kwargs={'username': user.username})), "email": user.email, # For backwards compatibility: Tables created after the upgrade to Django 1.8 will save microseconds. # However, mobile apps are not expecting microsecond in the serialized value. If we set it to zero the # DRF JSONEncoder will not include it in the serialized value. # https://docs.djangoproject.com/en/1.8/ref/databases/#fractional-seconds-support-for-time-and-datetime-fields "date_joined": user.date_joined.replace(microsecond=0), "is_active": user.is_active, "bio": None, "country": None, "state": None, "profile_image": None, "language_proficiencies": None, "name": None, "gender": None, "goals": None, "year_of_birth": None, "level_of_education": None, "mailing_address": None, "requires_parental_consent": None, "accomplishments_shared": accomplishments_shared, "account_privacy": self.configuration.get('default_visibility'), "social_links": None, "extended_profile_fields": None, "phone_number": None, } if user_profile: data.update({ "bio": AccountLegacyProfileSerializer.convert_empty_to_None( user_profile.bio), "country": AccountLegacyProfileSerializer.convert_empty_to_None( user_profile.country.code), "state": AccountLegacyProfileSerializer.convert_empty_to_None( user_profile.state), "profile_image": AccountLegacyProfileSerializer.get_profile_image( user_profile, user, self.context.get('request')), "language_proficiencies": LanguageProficiencySerializer( user_profile.language_proficiencies.all().order_by('code'), many=True).data, "name": user_profile.name, "gender": AccountLegacyProfileSerializer.convert_empty_to_None( user_profile.gender), "goals": user_profile.goals, "year_of_birth": user_profile.year_of_birth, "level_of_education": AccountLegacyProfileSerializer.convert_empty_to_None( user_profile.level_of_education), "mailing_address": user_profile.mailing_address, "requires_parental_consent": user_profile.requires_parental_consent(), "account_privacy": get_profile_visibility(user_profile, user, self.configuration), "social_links": SocialLinkSerializer( user_profile.social_links.all().order_by('platform'), many=True).data, "extended_profile": get_extended_profile(user_profile), "phone_number": user_profile.phone_number, }) if is_secondary_email_feature_enabled(): data.update({ "secondary_email": account_recovery.secondary_email if account_recovery else None, "secondary_email_enabled": True, }) # Append/Override the existing data values with plugin defined values run_extension_point( 'NAU_STUDENT_SERIALIZER_CONTEXT_EXTENSION', data=data, user=user, ) if self.custom_fields: fields = self.custom_fields elif user_profile: fields = _visible_fields(user_profile, user, self.configuration) else: fields = self.configuration.get('public_fields') return self._filter_fields(fields, data)
def update_account_settings(requesting_user, update, username=None): """Update user account information. Note: It is up to the caller of this method to enforce the contract that this method is only called with the user who made the request. Arguments: requesting_user (User): The user requesting to modify account information. Only the user with username 'username' has permissions to modify account information. update (dict): The updated account field values. username (str): Optional username specifying which account should be updated. If not specified, `requesting_user.username` is assumed. Raises: errors.UserNotFound: no user with username `username` exists (or `requesting_user.username` if `username` is not specified) errors.UserNotAuthorized: the requesting_user does not have access to change the account associated with `username` errors.AccountValidationError: the update was not attempted because validation errors were found with the supplied update errors.AccountUpdateError: the update could not be completed. Note that if multiple fields are updated at the same time, some parts of the update may have been successful, even if an errors.AccountUpdateError is returned; in particular, the user account (not including e-mail address) may have successfully been updated, but then the e-mail change request, which is processed last, may throw an error. errors.UserAPIInternalError: the operation failed due to an unexpected error. """ # Get user if username is None: username = requesting_user.username if requesting_user.username != username: raise errors.UserNotAuthorized() user, user_profile = _get_user_and_profile(username) # Validate fields to update field_errors = {} _validate_read_only_fields(user, update, field_errors) user_serializer = AccountUserSerializer(user, data=update) legacy_profile_serializer = AccountLegacyProfileSerializer(user_profile, data=update) for serializer in user_serializer, legacy_profile_serializer: add_serializer_errors(serializer, update, field_errors) _validate_email_change(user, update, field_errors) _validate_secondary_email(user, update, field_errors) old_name = _validate_name_change(user_profile, update, field_errors) old_language_proficiencies = _get_old_language_proficiencies_if_updating( user_profile, update) if field_errors: raise errors.AccountValidationError(field_errors) # Save requested changes try: for serializer in user_serializer, legacy_profile_serializer: serializer.save() _update_preferences_if_needed(update, requesting_user, user) _notify_language_proficiencies_update_if_needed( update, user, user_profile, old_language_proficiencies) _store_old_name_if_needed(old_name, user_profile, requesting_user) _update_extended_profile_if_needed(update, user_profile) _update_state_if_needed(update, user_profile) # Allow a plugin to save the updated values run_extension_point( 'NAU_STUDENT_ACCOUNT_PARTIAL_UPDATE', update=update, user=user, ) except PreferenceValidationError as err: raise AccountValidationError(err.preference_errors) except (AccountUpdateError, AccountValidationError) as err: raise err except Exception as err: raise AccountUpdateError( u"Error thrown when saving account updates: '{}'".format( text_type(err))) _send_email_change_requests_if_needed(update, user)
def _apply_third_party_auth_overrides(self, request, form_desc): """Modify the registration form if the user has authenticated with a third-party provider. If a user has successfully authenticated with a third-party provider, but does not yet have an account with EdX, we want to fill in the registration form with any info that we get from the provider. This will also hide the password field, since we assign users a default (random) password on the assumption that they will be using third-party auth to log in. Arguments: request (HttpRequest): The request for the registration form, used to determine if the user has successfully authenticated with a third-party provider. form_desc (FormDescription): The registration form description """ # pylint: disable=too-many-nested-blocks if third_party_auth.is_enabled(): running_pipeline = third_party_auth.pipeline.get(request) if running_pipeline: current_provider = third_party_auth.provider.Registry.get_from_pipeline( running_pipeline) if current_provider: # Override username / email / full name field_overrides = current_provider.get_register_form_data( running_pipeline.get('kwargs')) # When the TPA Provider is configured to skip the registration form and we are in an # enterprise context, we need to hide all fields except for terms of service and # ensure that the user explicitly checks that field. # pylint: disable=consider-using-ternary hide_registration_fields_except_tos = ( (current_provider.skip_registration_form and enterprise_customer_for_request(request)) or current_provider.sync_learner_profile_data) for field_name in self.DEFAULT_FIELDS + self.EXTRA_FIELDS: if field_name in field_overrides: form_desc.override_field_properties( field_name, default=field_overrides[field_name]) if (field_name not in ['terms_of_service', 'honor_code'] and field_overrides[field_name] and hide_registration_fields_except_tos): form_desc.override_field_properties( field_name, field_type="hidden", label="", instructions="", ) # Hide the password field form_desc.override_field_properties("password", default="", field_type="hidden", required=False, label="", instructions="", restrictions={}) # used to identify that request is running third party social auth form_desc.add_field( "social_auth_provider", field_type="hidden", label="", default=current_provider.name if current_provider.name else "Third Party", required=False, ) run_extension_point( 'NAU_APPLY_SAML_OVERRIDES', request=request, form_desc=form_desc, extra_settings=self._extra_fields_setting, )
def render_html_view(request, course_id, certificate=None): """ This public view generates an HTML representation of the specified user and course If a certificate is not available, we display a "Sorry!" screen instead """ user = certificate.user if certificate else request.user user_id = user.id preview_mode = request.GET.get('preview', None) platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME) configuration = CertificateHtmlViewConfiguration.get_config() # Kick the user back to the "Invalid" screen if the feature is disabled globally if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False): return _render_invalid_certificate(request, course_id, platform_name, configuration) # Load the course and user objects try: course_key = CourseKey.from_string(course_id) course = get_course_by_id(course_key) # For any course or user exceptions, kick the user back to the "Invalid" screen except (InvalidKeyError, Http404) as exception: error_str = (u"Invalid cert: error finding course %s " u"Specific error: %s") log.info(error_str, course_id, str(exception)) return _render_invalid_certificate(request, course_id, platform_name, configuration) # Kick the user back to the "Invalid" screen if the feature is disabled for the course if not course.cert_html_view_enabled: log.info( u"Invalid cert: HTML certificates disabled for %s. User id: %d", course_id, user_id, ) return _render_invalid_certificate(request, course_id, platform_name, configuration) # Load user's certificate user_certificate = _get_user_certificate(request, user, course_key, course, preview_mode) if not user_certificate: log.info( u"Invalid cert: User %d does not have eligible cert for %s.", user_id, course_id, ) return _render_invalid_certificate(request, course_id, platform_name, configuration) # Get the active certificate configuration for this course # If we do not have an active certificate, we'll need to send the user to the "Invalid" screen # Passing in the 'preview' parameter, if specified, will return a configuration, if defined active_configuration = get_active_web_certificate(course, preview_mode) if active_configuration is None: log.info( u"Invalid cert: course %s does not have an active configuration. User id: %d", course_id, user_id, ) return _render_invalid_certificate(request, course_id, platform_name, configuration) # Get data from Discovery service that will be necessary for rendering this Certificate. catalog_data = _get_catalog_data_for_course(course_key) # Determine whether to use the standard or custom template to render the certificate. custom_template = None custom_template_language = None if settings.FEATURES.get('CUSTOM_CERTIFICATE_TEMPLATES_ENABLED', False): log.info(u"Custom certificate for course %s", course_id) custom_template, custom_template_language = _get_custom_template_and_language( course.id, user_certificate.mode, catalog_data.pop('content_language', None)) # Determine the language that should be used to render the certificate. # For the standard certificate template, use the user language. For custom templates, use # the language associated with the template. user_language = translation.get_language() certificate_language = custom_template_language if custom_template else user_language log.info(u"certificate language is: %s for the course: %s", certificate_language, course_key) # Generate the certificate context in the correct language, then render the template. with translation.override(certificate_language): context = {'user_language': user_language} _update_context_with_basic_info(context, course_id, platform_name, configuration) context['certificate_data'] = active_configuration # Append/Override the existing view context values with any mode-specific ConfigurationModel values context.update(configuration.get(user_certificate.mode, {})) # Append organization info _update_organization_context(context, course) # Append course info _update_course_context(request, context, course, course_key, platform_name) # Append course run info from discovery context.update(catalog_data) # Append user info _update_context_with_user_info(context, user, user_certificate) # Append social sharing info _update_social_context(request, context, course, user, user_certificate, platform_name) # Append/Override the existing view context values with certificate specific values _update_certificate_context(context, course, user_certificate, platform_name) # Append badge info _update_badge_context(context, course, user) # Add certificate header/footer data to current context context.update( get_certificate_header_context(is_secure=request.is_secure())) context.update(get_certificate_footer_context()) # Append/Override the existing view context values with plugin defined values run_extension_point( 'NAU_CERTIFICATE_CONTEXT_EXTENSION', context=context, request=request, course=course, user=user, user_certificate=user_certificate, configuration=configuration, certificate_language=certificate_language, ) # Append/Override the existing view context values with any course-specific static values from Advanced Settings context.update(course.cert_html_view_overrides) # Track certificate view events _track_certificate_events(request, context, course, user, user_certificate) # Render the certificate return _render_valid_certificate(request, context, custom_template)
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 = [(six.text_type(year), six.text_type(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 = [] beta_language = {} dark_lang_config = DarkLangConfig.current() if dark_lang_config.enable_beta_languages: user_preferences = get_user_preferences(user) pref_language = user_preferences.get('pref-lang') if pref_language in dark_lang_config.beta_languages_list: beta_language['code'] = pref_language beta_language['name'] = settings.LANGUAGE_DICT.get(pref_language) context = { 'auth': {}, 'duplicate_provider': None, 'nav_hidden': True, 'fields': { 'country': { 'options': list(countries), }, 'gender': { 'options': [(choice[0], _(choice[1])) for choice in UserProfile.GENDER_CHOICES], }, 'language': { 'options': released_languages(), }, 'level_of_education': { 'options': [(choice[0], _(choice[1])) for choice in UserProfile.LEVEL_OF_EDUCATION_CHOICES], }, 'password': { 'url': reverse('password_reset'), }, 'year_of_birth': { 'options': year_of_birth_options, }, 'preferred_language': { 'options': all_languages(), }, 'time_zone': { 'options': TIME_ZONE_CHOICES, } }, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'password_reset_support_link': configuration_helpers.get_value('PASSWORD_RESET_SUPPORT_LINK', settings.PASSWORD_RESET_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK, '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.is_enabled(), 'show_dashboard_tabs': True, 'order_history': user_orders, 'disable_order_history_tab': should_redirect_to_order_history_microfrontend(), 'enable_account_deletion': configuration_helpers.get_value( 'ENABLE_ACCOUNT_DELETION', settings.FEATURES.get('ENABLE_ACCOUNT_DELETION', False)), 'extended_profile_fields': _get_extended_profile_fields(), 'beta_language': beta_language, } enterprise_customer = enterprise_customer_for_request(request) update_account_settings_context_for_enterprise(context, enterprise_customer, user) 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), # We only want to include providers if they are either currently available to be logged # in with, or if the user is already authenticated with them. } for state in auth_states if state.provider.display_for_login or state.has_account ] # Append/Override the existing view context values with plugin defined values run_extension_point( 'NAU_STUDENT_ACCOUNT_CONTEXT_EXTENSION', context=context, request=request, user=user, ) return context