Example #1
0
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
Example #2
0
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)
Example #3
0
    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)
Example #4
0
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)
Example #5
0
    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,
            )
Example #6
0
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)
Example #7
0
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