Exemple #1
0
def _generate_not_activated_message(user):
    """
    Generates the message displayed on the sign-in screen when a learner attempts to access the
    system with an inactive account.
    """

    support_url = configuration_helpers.get_value(
        'SUPPORT_SITE_LINK',
        settings.SUPPORT_SITE_LINK
    )

    platform_name = configuration_helpers.get_value(
        'PLATFORM_NAME',
        settings.PLATFORM_NAME
    )
    not_activated_message = Text(_(
        u'In order to sign in, you need to activate your account.{blank_lines}'
        u'We just sent an activation link to {email_strong}. If '
        u'you do not receive an email, check your spam folders or '
        u'{link_start}contact {platform_name} Support{link_end}.'
    )).format(
        platform_name=platform_name,
        blank_lines=HTML('<br/><br/>'),
        email_strong=HTML('<strong>{email}</strong>').format(email=user.email),
        link_start=HTML(u'<a href="{support_url}">').format(
            support_url=support_url,
        ),
        link_end=HTML("</a>"),
    )

    return not_activated_message
Exemple #2
0
def view_student_survey(user, survey_name, course=None, redirect_url=None, is_required=False, skip_redirect_url=None):
    """
    Shared utility method to render a survey form
    NOTE: This method is shared between the Survey and Courseware Djangoapps
    """

    redirect_url = redirect_url if redirect_url else reverse('dashboard')
    dashboard_redirect_url = reverse('dashboard')
    skip_redirect_url = skip_redirect_url if skip_redirect_url else dashboard_redirect_url

    survey = SurveyForm.get(survey_name, throw_if_not_found=False)
    if not survey:
        return HttpResponseRedirect(redirect_url)

    # the result set from get_answers, has an outer key with the user_id
    # just remove that outer key to make the JSON payload simplier
    existing_answers = survey.get_answers(user=user).get(user.id, {})

    platform_name = configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)

    context = {
        'existing_data_json': json.dumps(existing_answers),
        'postback_url': reverse('submit_answers', args=[survey_name]),
        'redirect_url': redirect_url,
        'skip_redirect_url': skip_redirect_url,
        'dashboard_redirect_url': dashboard_redirect_url,
        'survey_form': survey.form,
        'is_required': is_required,
        'mail_to_link': configuration_helpers.get_value('email_from_address', settings.CONTACT_EMAIL),
        'platform_name': platform_name,
        'course': course,
    }

    return render_to_response("survey/survey.html", context)
    def get(self, request):
        if not configuration_helpers.get_value('CONTACT_US_PAGE', True):
            raise Http404

        context = {
            'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
            'support_email': configuration_helpers.get_value('CONTACT_EMAIL', settings.CONTACT_EMAIL),
            'custom_fields': settings.ZENDESK_CUSTOM_FIELDS
        }

        # Tag all issues with LMS to distinguish channel which received the request
        tags = ['LMS']

        # Per edX support, we would like to be able to route feedback items by site via tagging
        current_site_name = configuration_helpers.get_value("SITE_NAME")
        if current_site_name:
            current_site_name = current_site_name.replace(".", "_")
            tags.append("site_name_{site}".format(site=current_site_name))

        if request.user.is_authenticated:
            context['course_id'] = request.session.get('course_id', '')
            context['user_enrollments'] = CourseEnrollment.enrollments_for_user_with_overviews_preload(request.user)
            enterprise_learner_data = enterprise_api.get_enterprise_learner_data(user=request.user)
            if enterprise_learner_data:
                tags.append('enterprise_learner')

        context['tags'] = tags

        return render_to_response("support/contact_us.html", context)
Exemple #4
0
def _generate_not_activated_message(user):
    """
    Generates the message displayed on the sign-in screen when a learner attempts to access the
    system with an inactive account.
    """

    support_url = configuration_helpers.get_value(
        'SUPPORT_SITE_LINK',
        settings.SUPPORT_SITE_LINK
    )

    platform_name = configuration_helpers.get_value(
        'PLATFORM_NAME',
        settings.PLATFORM_NAME
    )

    not_activated_msg_template = _('In order to sign in, you need to activate your account.<br /><br />'
                                   'We just sent an activation link to <strong>{email}</strong>.  If '
                                   'you do not receive an email, check your spam folders or '
                                   '<a href="{support_url}">contact {platform} Support</a>.')

    not_activated_message = not_activated_msg_template.format(
        email=user.email,
        support_url=support_url,
        platform=platform_name
    )

    return not_activated_message
    def get(self, request):
        context = {
            'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
            'zendesk_api_host': settings.ZENDESK_URL,
            'access_token': 'DUMMY_ACCESS_TOKEN',  # LEARNER-3450
            'custom_fields': settings.ZENDESK_CUSTOM_FIELDS
        }

        # Tag all issues with LMS to distinguish channel in Zendesk; requested by student support team
        zendesk_tags = ['LMS']

        # Per edX support, we would like to be able to route feedback items by site via tagging
        current_site_name = configuration_helpers.get_value("SITE_NAME")
        if current_site_name:
            current_site_name = current_site_name.replace(".", "_")
            zendesk_tags.append("site_name_{site}".format(site=current_site_name))

        if request.user.is_authenticated():
            context['user_enrollments'] = CourseEnrollment.enrollments_for_user(request.user)
            enterprise_learner_data = enterprise_api.get_enterprise_learner_data(site=request.site, user=request.user)
            if enterprise_learner_data:
                zendesk_tags.append('enterprise_learner')

        context['zendesk_tags'] = zendesk_tags

        return render_to_response("support/contact_us.html", context)
Exemple #6
0
def show_cart(request):
    """
    This view shows cart items.
    """
    cart = Order.get_cart_for_user(request.user)
    is_any_course_expired, expired_cart_items, expired_cart_item_names, valid_cart_item_tuples = \
        verify_for_closed_enrollment(request.user, cart)
    site_name = configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME)

    if is_any_course_expired:
        for expired_item in expired_cart_items:
            Order.remove_cart_item_from_order(expired_item, request.user)
        cart.update_order_type()

    callback_url = request.build_absolute_uri(
        reverse("shoppingcart.views.postpay_callback")
    )
    form_html = render_purchase_form_html(cart, callback_url=callback_url)
    context = {
        'order': cart,
        'shoppingcart_items': valid_cart_item_tuples,
        'amount': cart.total_cost,
        'is_course_enrollment_closed': is_any_course_expired,
        'expired_course_names': expired_cart_item_names,
        'site_name': site_name,
        'form_html': form_html,
        'currency_symbol': settings.PAID_COURSE_REGISTRATION_CURRENCY[1],
        'currency': settings.PAID_COURSE_REGISTRATION_CURRENCY[0],
        'enable_bulk_purchase': configuration_helpers.get_value('ENABLE_SHOPPING_CART_BULK_PURCHASE', True)
    }
    return render_to_response("shoppingcart/shopping_cart.html", context)
Exemple #7
0
    def __init__(self):

        # Backwards compatibility: Honor code is required by default, unless
        # explicitly set to "optional" in Django settings.
        self._extra_fields_setting = copy.deepcopy(configuration_helpers.get_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 = {}
        valid_fields = self.DEFAULT_FIELDS + self.EXTRA_FIELDS
        for field_name in valid_fields:
            handler = getattr(self, "_add_{field_name}_field".format(field_name=field_name))
            self.field_handlers[field_name] = handler

        field_order = configuration_helpers.get_value('REGISTRATION_FIELD_ORDER')
        if not field_order:
            field_order = settings.REGISTRATION_FIELD_ORDER or valid_fields

        # Check that all of the valid_fields are in the field order and vice versa, if not set to the default order
        if set(valid_fields) != set(field_order):
            field_order = valid_fields

        self.field_order = field_order
Exemple #8
0
 def save(
         self,
         subject_template_name='registration/password_reset_subject.txt',
         email_template_name='registration/password_reset_email.html',
         use_https=False,
         token_generator=default_token_generator,
         from_email=configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL),
         request=None
 ):
     """
     Generates a one-use only link for resetting password and sends to the
     user.
     """
     # This import is here because we are copying and modifying the .save from Django 1.4.5's
     # django.contrib.auth.forms.PasswordResetForm directly, which has this import in this place.
     from django.core.mail import send_mail
     for user in self.users_cache:
         site_name = configuration_helpers.get_value(
             'SITE_NAME',
             settings.SITE_NAME
         )
         context = {
             'email': user.email,
             'site_name': site_name,
             'uid': int_to_base36(user.id),
             'user': user,
             'token': token_generator.make_token(user),
             'protocol': 'https' if use_https else 'http',
             'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
         }
         subject = loader.render_to_string(subject_template_name, context)
         # Email subject *must not* contain newlines
         subject = subject.replace('\n', '')
         email = loader.render_to_string(email_template_name, context)
         send_mail(subject, email, from_email, [user.email])
Exemple #9
0
def marketing_link(name):
    """Returns the correct URL for a link to the marketing site
    depending on if the marketing site is enabled

    Since the marketing site is enabled by a setting, we have two
    possible URLs for certain links. This function is to decides
    which URL should be provided.
    """
    # link_map maps URLs from the marketing site to the old equivalent on
    # the Django site
    link_map = settings.MKTG_URL_LINK_MAP
    enable_mktg_site = configuration_helpers.get_value(
        "ENABLE_MKTG_SITE", settings.FEATURES.get("ENABLE_MKTG_SITE", False)
    )
    marketing_urls = configuration_helpers.get_value("MKTG_URLS", settings.MKTG_URLS)

    if enable_mktg_site and name in marketing_urls:
        # special case for when we only want the root marketing URL
        if name == "ROOT":
            return marketing_urls.get("ROOT")
        # Using urljoin here allows us to enable a marketing site and set
        # a site ROOT, but still specify absolute URLs for other marketing
        # URLs in the MKTG_URLS setting
        # e.g. urljoin('http://marketing.com', 'http://open-edx.org/about') >>> 'http://open-edx.org/about'
        return urljoin(marketing_urls.get("ROOT"), marketing_urls.get(name))
    # only link to the old pages when the marketing site isn't on
    elif not enable_mktg_site and name in link_map:
        # don't try to reverse disabled marketing links
        if link_map[name] is not None:
            return reverse(link_map[name])
    else:
        log.debug("Cannot find corresponding link for name: %s", name)
        return "#"
Exemple #10
0
    def save(self,  # pylint: disable=arguments-differ
             use_https=False,
             token_generator=default_token_generator,
             request=None,
             **_kwargs):
        """
        Generates a one-use only link for resetting password and sends to the
        user.
        """
        for user in self.users_cache:
            site = get_current_site()
            message_context = get_base_template_context(site)

            message_context.update({
                'request': request,  # Used by google_analytics_tracking_pixel
                # TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes
                'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
                'reset_link': '{protocol}://{site}{link}'.format(
                    protocol='https' if use_https else 'http',
                    site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
                    link=reverse('password_reset_confirm', kwargs={
                        'uidb36': int_to_base36(user.id),
                        'token': token_generator.make_token(user),
                    }),
                )
            })

            msg = PasswordReset().personalize(
                recipient=Recipient(user.username, user.email),
                language=get_user_preference(user, LANGUAGE_KEY),
                user_context=message_context,
            )
            ace.send(msg)
Exemple #11
0
def compose_and_send_activation_email(user, profile, user_registration=None):
    """
    Construct all the required params and send the activation email
    through celery task

    Arguments:
        user: current logged-in user
        profile: profile object of the current logged-in user
        user_registration: registration of the current logged-in user
    """
    dest_addr = user.email
    if user_registration is None:
        user_registration = Registration.objects.get(user=user)
    context = generate_activation_email_context(user, user_registration)
    subject = render_to_string('emails/activation_email_subject.txt', context)
    # Email subject *must not* contain newlines
    subject = ''.join(subject.splitlines())
    message_for_activation = render_to_string('emails/activation_email.txt', context)
    from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
    from_address = configuration_helpers.get_value('ACTIVATION_EMAIL_FROM_ADDRESS', from_address)
    if settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
        dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL']
        message_for_activation = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
                                  '-' * 80 + '\n\n' + message_for_activation)
    send_activation_email.delay(subject, message_for_activation, from_address, dest_addr)
Exemple #12
0
def send_account_recovery_email_for_user(user, request, email=None):
    """
    Send out a account recovery email for the given user.

    Arguments:
        user (User): Django User object
        request (HttpRequest): Django request object
        email (str): Send email to this address.
    """
    site = get_current_site()
    message_context = get_base_template_context(site)
    message_context.update({
        'request': request,  # Used by google_analytics_tracking_pixel
        'email': email,
        'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
        'reset_link': '{protocol}://{site}{link}?is_account_recovery=true'.format(
            protocol='https' if request.is_secure() else 'http',
            site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
            link=reverse('password_reset_confirm', kwargs={
                'uidb36': int_to_base36(user.id),
                'token': default_token_generator.make_token(user),
            }),
        )
    })

    msg = AccountRecoveryMessage().personalize(
        recipient=Recipient(user.username, email),
        language=get_user_preference(user, LANGUAGE_KEY),
        user_context=message_context,
    )
    ace.send(msg)
Exemple #13
0
def send_password_reset_email_for_user(user, request, preferred_email=None):
    """
    Send out a password reset email for the given user.

    Arguments:
        user (User): Django User object
        request (HttpRequest): Django request object
        preferred_email (str): Send email to this address if present, otherwise fallback to user's email address.
    """
    site = get_current_site()
    message_context = get_base_template_context(site)
    message_context.update({
        'request': request,  # Used by google_analytics_tracking_pixel
        # TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes
        'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
        'reset_link': '{protocol}://{site}{link}'.format(
            protocol='https' if request.is_secure() else 'http',
            site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
            link=reverse('password_reset_confirm', kwargs={
                'uidb36': int_to_base36(user.id),
                'token': default_token_generator.make_token(user),
            }),
        )
    })

    msg = PasswordReset().personalize(
        recipient=Recipient(user.username, preferred_email or user.email),
        language=get_user_preference(user, LANGUAGE_KEY),
        user_context=message_context,
    )
    ace.send(msg)
Exemple #14
0
def render(request, template):
    """
    This view function renders the template sent without checking that it
    exists. Do not expose template as a regex part of the url. The user should
    not be able to ender any arbitray template name. The correct usage would be:

    url(r'^jobs$', 'static_template_view.views.render', {'template': 'jobs.html'}, name="jobs")
    """

    # Guess content type from file extension
    content_type, __ = mimetypes.guess_type(template)

    try:
        context = {}
        # This is necessary for the dialog presented with the TOS in /register
        if template == 'honor.html':
            context['allow_iframing'] = True
        # Format Examples: static_template_about_header
        configuration_base = 'static_template_' + template.replace('.html', '').replace('-', '_')
        page_header = configuration_helpers.get_value(configuration_base + '_header')
        page_content = configuration_helpers.get_value(configuration_base + '_content')
        if page_header:
            context['page_header'] = mark_safe(page_header)
        if page_content:
            context['page_content'] = mark_safe(page_content)
        result = render_to_response('static_templates/' + template, context, content_type=content_type)
        return result
    except TopLevelLookupException:
        raise Http404
    except TemplateDoesNotExist:
        raise Http404
Exemple #15
0
def get_id_token(user):
    """
    Return a JWT for `user`, suitable for use with the course discovery service.

    Arguments:
        user (User): User for whom to generate the JWT.

    Returns:
        str: The JWT.
    """
    try:
        # Service users may not have user profiles.
        full_name = UserProfile.objects.get(user=user).name
    except UserProfile.DoesNotExist:
        full_name = None

    now = datetime.datetime.utcnow()
    expires_in = getattr(settings, 'OAUTH_ID_TOKEN_EXPIRATION', 30)

    payload = {
        'preferred_username': user.username,
        'name': full_name,
        'email': user.email,
        'administrator': user.is_staff,
        'iss': configuration_helpers.get_value('OAUTH_OIDC_ISSUER', settings.OAUTH_OIDC_ISSUER),
        'exp': now + datetime.timedelta(seconds=expires_in),
        'iat': now,
        'aud': configuration_helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_AUDIENCE'],
        'sub': anonymous_id_for_user(user, None),
    }
    secret_key = configuration_helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_SECRET_KEY']

    return jwt.encode(payload, secret_key).decode('utf-8')
Exemple #16
0
    def get_setting(self, name):
        """ Get the value of a setting, or raise KeyError """
        default_saml_contact = {
            # Default contact information to put into the SAML metadata that gets generated by python-saml.
            "givenName": _("{platform_name} Support").format(
                platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
            ),
            "emailAddress": configuration_helpers.get_value('TECH_SUPPORT_EMAIL', settings.TECH_SUPPORT_EMAIL),
        }

        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 = {
            # These defaults can be overriden by self.other_config_str
            "GET_ALL_EXTRA_DATA": True,  # Save all attribute values the IdP sends into the UserSocialAuth table
            "TECHNICAL_CONTACT": default_saml_contact,
            "SUPPORT_CONTACT": default_saml_contact,
        }
        other_config.update(json.loads(self.other_config_str))
        return other_config[name]  # SECURITY_CONFIG, SP_EXTRA, or similar extra settings
    def test_get_value(self):
        """
        Test that get_value returns correct value for any given key.
        """
        # Make sure entry is saved and retrieved correctly
        self.assertEqual(configuration_helpers.get_value("university"), test_config['university'])
        self.assertEqual(configuration_helpers.get_value("platform_name"), test_config['platform_name'])
        self.assertEqual(configuration_helpers.get_value("SITE_NAME"), test_config['SITE_NAME'])
        self.assertEqual(configuration_helpers.get_value("course_org_filter"), test_config['course_org_filter'])
        self.assertEqual(configuration_helpers.get_value("css_overrides_file"), test_config['css_overrides_file'])
        self.assertEqual(configuration_helpers.get_value("ENABLE_MKTG_SITE"), test_config['ENABLE_MKTG_SITE'])
        self.assertEqual(configuration_helpers.get_value("favicon_path"), test_config['favicon_path'])
        self.assertEqual(
            configuration_helpers.get_value("ENABLE_THIRD_PARTY_AUTH"),
            test_config['ENABLE_THIRD_PARTY_AUTH'],
        )
        self.assertEqual(
            configuration_helpers.get_value("course_about_show_social_links"),
            test_config['course_about_show_social_links'],
        )

        # Test that the default value is returned if the value for the given key is not found in the configuration
        self.assertEqual(
            configuration_helpers.get_value("non_existent_name", "dummy-default-value"),
            "dummy-default-value",
        )
Exemple #18
0
def header_language_selector_is_enabled():
    """Return true if the header language selector has been enabled via settings or site-specific configuration."""
    setting = get_value('SHOW_HEADER_LANGUAGE_SELECTOR', settings.FEATURES.get('SHOW_HEADER_LANGUAGE_SELECTOR', False))

    # The SHOW_LANGUAGE_SELECTOR setting is deprecated, but might still be in use on some installations.
    deprecated_setting = get_value('SHOW_LANGUAGE_SELECTOR', settings.FEATURES.get('SHOW_LANGUAGE_SELECTOR', False))

    return setting or deprecated_setting
def _get_extended_profile_fields():
    """Retrieve the extended profile fields from site configuration to be shown on the
       Account Settings page

    Returns:
        A list of dicts. Each dict corresponds to a single field. The keys per field are:
            "field_name"  : name of the field stored in user_profile.meta
            "field_label" : The label of the field.
            "field_type"  : TextField or ListField
            "field_options": a list of tuples for options in the dropdown in case of ListField
    """

    extended_profile_fields = []
    fields_already_showing = ['username', 'name', 'email', 'pref-lang', 'country', 'time_zone', 'level_of_education',
                              'gender', 'year_of_birth', 'language_proficiencies', 'social_links']

    field_labels_map = {
        "first_name": _(u"First Name"),
        "last_name": _(u"Last Name"),
        "city": _(u"City"),
        "state": _(u"State/Province/Region"),
        "company": _(u"Company"),
        "title": _(u"Title"),
        "job_title": _(u"Job Title"),
        "mailing_address": _(u"Mailing address"),
        "goals": _(u"Tell us why you're interested in {platform_name}").format(
            platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME)
        ),
        "profession": _(u"Profession"),
        "specialty": _(u"Specialty")
    }

    extended_profile_field_names = configuration_helpers.get_value('extended_profile_fields', [])
    for field_to_exclude in fields_already_showing:
        if field_to_exclude in extended_profile_field_names:
            extended_profile_field_names.remove(field_to_exclude)

    extended_profile_field_options = configuration_helpers.get_value('EXTRA_FIELD_OPTIONS', [])
    extended_profile_field_option_tuples = {}
    for field in extended_profile_field_options.keys():
        field_options = extended_profile_field_options[field]
        extended_profile_field_option_tuples[field] = [(option.lower(), option) for option in field_options]

    for field in extended_profile_field_names:
        field_dict = {
            "field_name": field,
            "field_label": field_labels_map.get(field, field),
        }

        field_options = extended_profile_field_option_tuples.get(field)
        if field_options:
            field_dict["field_type"] = "ListField"
            field_dict["field_options"] = field_options
        else:
            field_dict["field_type"] = "TextField"
        extended_profile_fields.append(field_dict)

    return extended_profile_fields
Exemple #20
0
 def public_records_url(self):
     """
     Publicly-accessible Records URL root.
     """
     # Not every site wants the Learner Records feature, so we allow opting out.
     if not helpers.get_value('ENABLE_LEARNER_RECORDS', True):
         return None
     root = helpers.get_value('CREDENTIALS_PUBLIC_SERVICE_URL', settings.CREDENTIALS_PUBLIC_SERVICE_URL)
     return urljoin(root, '/records/')
Exemple #21
0
def is_commerce_service_configured():
    """
    Return a Boolean indicating whether or not configuration is present to use
    the external commerce service.
    """
    ecommerce_api_url = configuration_helpers.get_value("ECOMMERCE_API_URL", settings.ECOMMERCE_API_URL)
    ecommerce_api_signing_key = configuration_helpers.get_value(
        "ECOMMERCE_API_SIGNING_KEY", settings.ECOMMERCE_API_SIGNING_KEY,
    )
    return bool(ecommerce_api_url and ecommerce_api_signing_key)
Exemple #22
0
def checkout_receipt(request):
    """ Receipt view. """

    page_title = _('Receipt')
    is_payment_complete = True
    payment_support_email = configuration_helpers.get_value('payment_support_email', settings.PAYMENT_SUPPORT_EMAIL)
    payment_support_link = '<a href=\"mailto:{email}\">{email}</a>'.format(email=payment_support_email)

    is_cybersource = all(k in request.POST for k in ('signed_field_names', 'decision', 'reason_code'))
    if is_cybersource and request.POST['decision'] != 'ACCEPT':
        # Cybersource may redirect users to this view if it couldn't recover
        # from an error while capturing payment info.
        is_payment_complete = False
        page_title = _('Payment Failed')
        reason_code = request.POST['reason_code']
        # if the problem was with the info submitted by the user, we present more detailed messages.
        if is_user_payment_error(reason_code):
            error_summary = _("There was a problem with this transaction. You have not been charged.")
            error_text = _(
                "Make sure your information is correct, or try again with a different card or another form of payment."
            )
        else:
            error_summary = _("A system error occurred while processing your payment. You have not been charged.")
            error_text = _("Please wait a few minutes and then try again.")
        for_help_text = _("For help, contact {payment_support_link}.").format(payment_support_link=payment_support_link)
    else:
        # if anything goes wrong rendering the receipt, it indicates a problem fetching order data.
        error_summary = _("An error occurred while creating your receipt.")
        error_text = None  # nothing particularly helpful to say if this happens.
        for_help_text = _(
            "If your course does not appear on your dashboard, contact {payment_support_link}."
        ).format(payment_support_link=payment_support_link)

    commerce_configuration = CommerceConfiguration.current()
    # user order cache should be cleared when a new order is placed
    # so user can see new order in their order history.
    if is_payment_complete and commerce_configuration.enabled and commerce_configuration.is_cache_enabled:
        cache_key = commerce_configuration.CACHE_KEY + '.' + str(request.user.id)
        cache.delete(cache_key)

    context = {
        'page_title': page_title,
        'is_payment_complete': is_payment_complete,
        'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
        'verified': SoftwareSecurePhotoVerification.verification_valid_or_pending(request.user).exists(),
        'error_summary': error_summary,
        'error_text': error_text,
        'for_help_text': for_help_text,
        'payment_support_email': payment_support_email,
        'username': request.user.username,
        'nav_hidden': True,
        'is_request_in_themed_site': is_request_in_themed_site()
    }
    return render_to_response('commerce/checkout_receipt.html', context)
Exemple #23
0
def ecommerce_api_client(user):
    """ Returns an E-Commerce API client setup with authentication for the specified user. """
    jwt_auth = configuration_helpers.get_value("JWT_AUTH", settings.JWT_AUTH)
    return EdxRestApiClient(
        configuration_helpers.get_value("ECOMMERCE_API_URL", settings.ECOMMERCE_API_URL),
        configuration_helpers.get_value("ECOMMERCE_API_SIGNING_KEY", settings.ECOMMERCE_API_SIGNING_KEY),
        user.username,
        user.profile.name if hasattr(user, 'profile') else None,
        user.email,
        tracking_context=create_tracking_context(user),
        issuer=jwt_auth['JWT_ISSUER'],
        expires_in=jwt_auth['JWT_EXPIRATION']
    )
Exemple #24
0
def is_marketing_link_set(name):
    """
    Returns a boolean if a given named marketing link is configured.
    """

    enable_mktg_site = configuration_helpers.get_value(
        "ENABLE_MKTG_SITE", settings.FEATURES.get("ENABLE_MKTG_SITE", False)
    )
    marketing_urls = configuration_helpers.get_value("MKTG_URLS", settings.MKTG_URLS)

    if enable_mktg_site:
        return name in marketing_urls
    else:
        return name in settings.MKTG_URL_LINK_MAP
Exemple #25
0
    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_label = _(u"Honor Code")
            terms_link = marketing_link("HONOR")
            terms_text = _(u"Review the 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_label = _(u"Terms of Service and Honor Code")
            terms_link = marketing_link("HONOR")
            terms_text = _(u"Review the Terms of Service and Honor Code")

        # 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=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME),
            terms_of_service=terms_label
        )

        # 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=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME),
            terms_of_service=terms_label
        )

        form_desc.add_field(
            "honor_code",
            label=label,
            field_type="checkbox",
            default=False,
            required=required,
            error_messages={
                "required": error_msg
            },
            supplementalLink=terms_link,
            supplementalText=terms_text
        )
Exemple #26
0
def get_visible_courses(org=None, filter_=None):
    """
    Return the set of CourseOverviews that should be visible in this branded
    instance.

    Arguments:
        org (string): Optional parameter that allows case-insensitive
            filtering by organization.
        filter_ (dict): Optional parameter that allows custom filtering by
            fields on the course.
    """
    current_site_org = configuration_helpers.get_value('course_org_filter')

    if org and current_site_org:
        # Return an empty result if the org passed by the caller does not match the designated site org.
        courses = CourseOverview.get_all_courses(
            org=org,
            filter_=filter_,
        ) if org == current_site_org else []
    else:
        # We only make it to this point if one of org or current_site_org is defined.
        # If both org and current_site_org were defined, the code would have fallen into the
        # first branch of the conditional above, wherein an equality check is performed.
        target_org = org or current_site_org
        courses = CourseOverview.get_all_courses(org=target_org, filter_=filter_)

    courses = sorted(courses, key=lambda course: course.number)

    # Filtering can stop here.
    if current_site_org:
        return courses

    # See if we have filtered course listings in this domain
    filtered_visible_ids = None

    # this is legacy format, which also handle dev case, which should not filter
    subdomain = configuration_helpers.get_value('subdomain', 'default')
    if hasattr(settings, 'COURSE_LISTINGS') and subdomain in settings.COURSE_LISTINGS and not settings.DEBUG:
        filtered_visible_ids = frozenset(
            [SlashSeparatedCourseKey.from_deprecated_string(c) for c in settings.COURSE_LISTINGS[subdomain]]
        )

    if filtered_visible_ids:
        return [course for course in courses if course.id in filtered_visible_ids]
    else:
        # Filter out any courses based on current org, to avoid leaking these.
        orgs = configuration_helpers.get_all_orgs()
        return [course for course in courses if course.location.org not in orgs]
Exemple #27
0
def update_account_settings_context_for_enterprise(context, enterprise_customer):
    """
    Take processed context for account settings page and update it taking enterprise customer into account.

     Arguments:
         context (dict): Context for account settings page.
         enterprise_customer (dict): data for enterprise customer

    """
    enterprise_context = {
        'enterprise_name': None,
        'sync_learner_profile_data': False,
        'edx_support_url': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
        'enterprise_readonly_account_fields': {
            'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS
        }
    }

    if enterprise_customer:
        enterprise_context['enterprise_name'] = enterprise_customer['name']
        identity_provider = third_party_auth.provider.Registry.get(
            provider_id=enterprise_customer['identity_provider'],
        )
        if identity_provider:
            enterprise_context['sync_learner_profile_data'] = identity_provider.sync_learner_profile_data

    context.update(enterprise_context)
Exemple #28
0
def signin_user(request):
    """Deprecated. To be replaced by :class:`student_account.views.login_and_registration_form`."""
    external_auth_response = external_auth_login(request)
    if external_auth_response is not None:
        return external_auth_response
    # Determine the URL to redirect to following login:
    redirect_to = get_next_url_for_login_page(request)
    if request.user.is_authenticated:
        return redirect(redirect_to)

    third_party_auth_error = None
    for msg in messages.get_messages(request):
        if msg.extra_tags.split()[0] == "social-auth":
            # msg may or may not be translated. Try translating [again] in case we are able to:
            third_party_auth_error = _(text_type(msg))  # pylint: disable=translation-of-non-string
            break

    context = {
        'login_redirect_url': redirect_to,  # This gets added to the query string of the "Sign In" button in the header
        # Bool injected into JS to submit form if we're inside a running third-
        # party auth pipeline; distinct from the actual instance of the running
        # pipeline, if any.
        'pipeline_running': 'true' if pipeline.running(request) else 'false',
        'pipeline_url': auth_pipeline_urls(pipeline.AUTH_ENTRY_LOGIN, redirect_url=redirect_to),
        'platform_name': configuration_helpers.get_value(
            'platform_name',
            settings.PLATFORM_NAME
        ),
        'third_party_auth_error': third_party_auth_error
    }

    return render_to_response('login.html', context)
Exemple #29
0
def get_feedback_form_context(request):
    """
    Extract the submitted form fields to be used as a context for
    feedback submission.
    """
    context = {}

    context["subject"] = request.POST["subject"]
    context["details"] = request.POST["details"]
    context["tags"] = dict(
        [(tag, request.POST[tag]) for tag in ["issue_type", "course_id"] if request.POST.get(tag)]
    )

    context["additional_info"] = {}

    if request.user.is_authenticated():
        context["realname"] = request.user.profile.name
        context["email"] = request.user.email
        context["additional_info"]["username"] = request.user.username
    else:
        context["realname"] = request.POST["name"]
        context["email"] = request.POST["email"]

    for header, pretty in [("HTTP_REFERER", "Page"), ("HTTP_USER_AGENT", "Browser"), ("REMOTE_ADDR", "Client IP"),
                           ("SERVER_NAME", "Host")]:
        context["additional_info"][pretty] = request.META.get(header)

    context["support_email"] = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)

    return context
Exemple #30
0
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': configuration_helpers.get_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)
Exemple #31
0
    def __init__(self, items_data, item_id, date, is_invoice, total_cost,
                 payment_received, balance):
        """
        Accepts the following positional arguments

        items_data - A list having the following items for each row.
            item_description - String
            quantity - Integer
            list_price - float
            discount - float
            item_total - float
        id - String
        date - datetime
        is_invoice - boolean - True (for invoice) or False (for Receipt)
        total_cost - float
        payment_received - float
        balance - float
        """

        # From settings
        self.currency = settings.PAID_COURSE_REGISTRATION_CURRENCY[1]
        self.logo_path = configuration_helpers.get_value(
            "PDF_RECEIPT_LOGO_PATH", settings.PDF_RECEIPT_LOGO_PATH)
        self.cobrand_logo_path = configuration_helpers.get_value(
            "PDF_RECEIPT_COBRAND_LOGO_PATH",
            settings.PDF_RECEIPT_COBRAND_LOGO_PATH)
        self.tax_label = configuration_helpers.get_value(
            "PDF_RECEIPT_TAX_ID_LABEL", settings.PDF_RECEIPT_TAX_ID_LABEL)
        self.tax_id = configuration_helpers.get_value(
            "PDF_RECEIPT_TAX_ID", settings.PDF_RECEIPT_TAX_ID)
        self.footer_text = configuration_helpers.get_value(
            "PDF_RECEIPT_FOOTER_TEXT", settings.PDF_RECEIPT_FOOTER_TEXT)
        self.disclaimer_text = configuration_helpers.get_value(
            "PDF_RECEIPT_DISCLAIMER_TEXT",
            settings.PDF_RECEIPT_DISCLAIMER_TEXT,
        )
        self.billing_address_text = configuration_helpers.get_value(
            "PDF_RECEIPT_BILLING_ADDRESS",
            settings.PDF_RECEIPT_BILLING_ADDRESS)
        self.terms_conditions_text = configuration_helpers.get_value(
            "PDF_RECEIPT_TERMS_AND_CONDITIONS",
            settings.PDF_RECEIPT_TERMS_AND_CONDITIONS)
        self.brand_logo_height = configuration_helpers.get_value(
            "PDF_RECEIPT_LOGO_HEIGHT_MM",
            settings.PDF_RECEIPT_LOGO_HEIGHT_MM) * mm
        self.cobrand_logo_height = configuration_helpers.get_value(
            "PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM",
            settings.PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM) * mm

        # From Context
        self.items_data = items_data
        self.item_id = item_id
        self.date = ModuleI18nService().strftime(date, 'SHORT_DATE')
        self.is_invoice = is_invoice
        self.total_cost = '{currency}{amount:.2f}'.format(
            currency=self.currency, amount=total_cost)
        self.payment_received = '{currency}{amount:.2f}'.format(
            currency=self.currency, amount=payment_received)
        self.balance = '{currency}{amount:.2f}'.format(currency=self.currency,
                                                       amount=balance)

        # initialize the pdf variables
        self.margin = 15 * mm
        self.page_width = letter[0]
        self.page_height = letter[1]
        self.min_clearance = 3 * mm
        self.second_page_available_height = ''
        self.second_page_start_y_pos = ''
        self.first_page_available_height = ''
        self.pdf = None
Exemple #32
0
def activate_account(request, key):
    """
    When link in activation e-mail is clicked
    """
    # If request is in Studio call the appropriate view
    if theming_helpers.get_project_root_name().lower() == 'cms':
        monitoring_utils.set_custom_attribute('student_activate_account', 'cms')
        return activate_account_studio(request, key)

    # TODO: Use custom attribute to determine if there are any `activate_account` calls for cms in Production.
    # If not, the templates wouldn't be needed for cms, but we still need a way to activate for cms tests.
    monitoring_utils.set_custom_attribute('student_activate_account', 'lms')
    activation_message_type = None
    try:
        registration = Registration.objects.get(activation_key=key)
    except (Registration.DoesNotExist, Registration.MultipleObjectsReturned):
        activation_message_type = 'error'
        messages.error(
            request,
            HTML(_(
                '{html_start}Your account could not be activated{html_end}'
                'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.'
            )).format(
                support_url=configuration_helpers.get_value(
                    'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK
                ) or settings.SUPPORT_SITE_LINK,
                html_start=HTML('<p class="message-title">'),
                html_end=HTML('</p>'),
            ),
            extra_tags='account-activation aa-icon'
        )
    else:
        if registration.user.is_active:
            activation_message_type = 'info'
            messages.info(
                request,
                HTML(_('{html_start}This account has already been activated.{html_end}')).format(
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                ),
                extra_tags='account-activation aa-icon',
            )
        else:
            registration.activate()
            # Success message for logged in users.
            message = _('{html_start}Success{html_end} You have activated your account.')

            tracker.emit(
                USER_ACCOUNT_ACTIVATED,
                {
                    "user_id": registration.user.id,
                    "activation_timestamp": registration.activation_timestamp
                }
            )

            if not request.user.is_authenticated:
                # Success message for logged out users
                message = _(
                    '{html_start}Success! You have activated your account.{html_end}'
                    'You will now receive email updates and alerts from us related to'
                    ' the courses you are enrolled in. Sign In to continue.'
                )

            # Add message for later use.
            activation_message_type = 'success'
            messages.success(
                request,
                HTML(message).format(
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                ),
                extra_tags='account-activation aa-icon',
            )

    if should_redirect_to_authn_microfrontend() and not request.user.is_authenticated:
        url_path = f'/login?account_activation_status={activation_message_type}'
        return redirect(settings.AUTHN_MICROFRONTEND_URL + url_path)

    return redirect('dashboard')
Exemple #33
0
def do_email_change_request(user, new_email, activation_key=None, secondary_email_change_request=False):
    """
    Given a new email for a user, does some basic verification of the new address and sends an activation message
    to the new address. If any issues are encountered with verification or sending the message, a ValueError will
    be thrown.
    """
    # if activation_key is not passing as an argument, generate a random key
    if not activation_key:
        activation_key = uuid.uuid4().hex

    confirm_link = reverse('confirm_email_change', kwargs={'key': activation_key, })

    if secondary_email_change_request:
        PendingSecondaryEmailChange.objects.update_or_create(
            user=user,
            defaults={
                'new_secondary_email': new_email,
                'activation_key': activation_key,
            }
        )
        confirm_link = reverse('activate_secondary_email', kwargs={'key': activation_key})
    else:
        PendingEmailChange.objects.update_or_create(
            user=user,
            defaults={
                'new_email': new_email,
                'activation_key': activation_key,
            }
        )

    use_https = theming_helpers.get_current_request().is_secure()

    site = Site.objects.get_current()
    message_context = get_base_template_context(site)
    message_context.update({
        'old_email': user.email,
        'new_email': new_email,
        'confirm_link': '{protocol}://{site}{link}'.format(
            protocol='https' if use_https else 'http',
            site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
            link=confirm_link,
        ),
    })

    if secondary_email_change_request:
        msg = RecoveryEmailCreate().personalize(
            recipient=Recipient(user.id, new_email),
            language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
            user_context=message_context,
        )
    else:
        msg = EmailChange().personalize(
            recipient=Recipient(user.id, new_email),
            language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
            user_context=message_context,
        )

    try:
        ace.send(msg)
    except Exception:
        from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
        log.error('Unable to send email activation link to user from "%s"', from_address, exc_info=True)
        raise ValueError(_('Unable to send email activation link. Please try again later.'))  # lint-amnesty, pylint: disable=raise-missing-from

    if not secondary_email_change_request:
        # When the email address change is complete, a "edx.user.settings.changed" event will be emitted.
        # But because changing the email address 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": "email",
                "old": message_context['old_email'],
                "new": message_context['new_email'],
                "user_id": user.id,
            }
        )
Exemple #34
0
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)

    # 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]
            tpa_hint_provider = third_party_auth.provider.Registry.get(
                provider_id=provider_id)
            if tpa_hint_provider:
                if tpa_hint_provider.skip_hinted_login_dialog:
                    # Forward the user directly to the provider's login URL when the provider is configured
                    # to skip the dialog.
                    if initial_mode == "register":
                        auth_entry = pipeline.AUTH_ENTRY_REGISTER
                    else:
                        auth_entry = pipeline.AUTH_ENTRY_LOGIN
                    return redirect(
                        pipeline.get_login_url(provider_id,
                                               auth_entry,
                                               redirect_url=redirect_to))
                third_party_auth_hint = provider_id
                initial_mode = "hinted_login"
        except (KeyError, ValueError, IndexError) as ex:
            log.exception("Unknown tpa_hint provider: %s", ex)

    # 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
    # configuration settings.
    if is_request_in_themed_site() and not configuration_helpers.get_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

    # Account activation message
    account_activation_messages = [{
        'message': message.message,
        'tags': message.tags
    } for message in messages.get_messages(request)
                                   if 'account-activation' in message.tags]

    # 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':
            third_party_auth_hint or '',
            'platform_name':
            configuration_helpers.get_value('PLATFORM_NAME',
                                            settings.PLATFORM_NAME),
            'support_link':
            configuration_helpers.get_value('SUPPORT_SITE_LINK',
                                            settings.SUPPORT_SITE_LINK),
            'password_reset_support_link':
            configuration_helpers.get_value(
                'PASSWORD_RESET_SUPPORT_LINK',
                settings.PASSWORD_RESET_SUPPORT_LINK)
            or settings.SUPPORT_SITE_LINK,
            'account_activation_messages':
            account_activation_messages,

            # 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']),
            'account_creation_allowed':
            configuration_helpers.get_value(
                'ALLOW_PUBLIC_ACCOUNT_CREATION',
                settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True))
        },
        '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,
        'combined_login_and_register':
        True,
        'disable_footer':
        not configuration_helpers.get_value(
            'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER',
            settings.FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER']),
    }

    enterprise_customer = enterprise_customer_for_request(request)
    update_logistration_context_for_enterprise(request, context,
                                               enterprise_customer)

    response = render_to_response('student_account/login_and_register.html',
                                  context)
    handle_enterprise_cookies_for_logistration(request, response, context)

    return response
Exemple #35
0
def send_mail_to_student(student, param_dict, language=None):
    """
    Construct the email using templates and then send it.
    `student` is the student's email address (a `str`),

    `param_dict` is a `dict` with keys
    [
        `site_name`: name given to edX instance (a `str`)
        `registration_url`: url for registration (a `str`)
        `display_name` : display name of a course (a `str`)
        `course_id`: id of course (a `str`)
        `auto_enroll`: user input option (a `str`)
        `course_url`: url of course (a `str`)
        `email_address`: email of student (a `str`)
        `full_name`: student full name (a `str`)
        `message`: type of email to send and template to use (a `str`)
        `is_shib_course`: (a `boolean`)
    ]

    `language` is the language used to render the email. If None the language
    of the currently-logged in user (that is, the user sending the email) will
    be used.

    Returns a boolean indicating whether the email was sent successfully.
    """

    # add some helpers and microconfig subsitutions
    if 'display_name' in param_dict:
        param_dict['course_name'] = param_dict['display_name']

    param_dict['site_name'] = configuration_helpers.get_value(
        'SITE_NAME', param_dict['site_name'])

    subject = None
    message = None

    # see if there is an activation email template definition available as configuration,
    # if so, then render that
    message_type = param_dict['message']

    email_template_dict = {
        'allowed_enroll': ('emails/enroll_email_allowedsubject.txt',
                           'emails/enroll_email_allowedmessage.txt'),
        'enrolled_enroll': ('emails/enroll_email_enrolledsubject.txt',
                            'emails/enroll_email_enrolledmessage.txt'),
        'allowed_unenroll': ('emails/unenroll_email_subject.txt',
                             'emails/unenroll_email_allowedmessage.txt'),
        'enrolled_unenroll': ('emails/unenroll_email_subject.txt',
                              'emails/unenroll_email_enrolledmessage.txt'),
        'add_beta_tester': ('emails/add_beta_tester_email_subject.txt',
                            'emails/add_beta_tester_email_message.txt'),
        'remove_beta_tester': ('emails/remove_beta_tester_email_subject.txt',
                               'emails/remove_beta_tester_email_message.txt'),
        'account_creation_and_enrollment':
        ('emails/enroll_email_enrolledsubject.txt',
         'emails/account_creation_and_enroll_emailMessage.txt'),
    }

    subject_template, message_template = email_template_dict.get(
        message_type, (None, None))
    if subject_template is not None and message_template is not None:
        subject, message = render_message_to_string(subject_template,
                                                    message_template,
                                                    param_dict,
                                                    language=language)

    if subject and message:
        # Remove leading and trailing whitespace from body
        message = message.strip()

        # Email subject *must not* contain newlines
        subject = ''.join(subject.splitlines())
        from_address = configuration_helpers.get_value(
            'email_from_address', settings.DEFAULT_FROM_EMAIL)

        send_mail(subject,
                  message,
                  from_address, [student],
                  fail_silently=False)
Exemple #36
0
def student_dashboard(request):
    """
    Provides the LMS dashboard view

    TODO: This is lms specific and does not belong in common code.

    Arguments:
        request: The request object.

    Returns:
        The dashboard response.

    """
    user = request.user
    if not UserProfile.objects.filter(user=user).exists():
        return redirect(reverse('account_settings'))

    platform_name = configuration_helpers.get_value("platform_name",
                                                    settings.PLATFORM_NAME)

    enable_verified_certificates = configuration_helpers.get_value(
        'ENABLE_VERIFIED_CERTIFICATES',
        settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES'))
    display_course_modes_on_dashboard = configuration_helpers.get_value(
        'DISPLAY_COURSE_MODES_ON_DASHBOARD',
        settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True))
    activation_email_support_link = configuration_helpers.get_value(
        'ACTIVATION_EMAIL_SUPPORT_LINK',
        settings.ACTIVATION_EMAIL_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK
    hide_dashboard_courses_until_activated = configuration_helpers.get_value(
        'HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED',
        settings.FEATURES.get('HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED', False))
    empty_dashboard_message = configuration_helpers.get_value(
        'EMPTY_DASHBOARD_MESSAGE', None)

    # Get the org whitelist or the org blacklist for the current site
    site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site(
    )
    course_enrollments = list(
        get_course_enrollments(user, site_org_whitelist, site_org_blacklist))

    # Get the entitlements for the user and a mapping to all available sessions for that entitlement
    # If an entitlement has no available sessions, pass through a mock course overview object
    (course_entitlements, course_entitlement_available_sessions,
     unfulfilled_entitlement_pseudo_sessions
     ) = get_filtered_course_entitlements(user, site_org_whitelist,
                                          site_org_blacklist)

    # Record how many courses there are so that we can get a better
    # understanding of usage patterns on prod.
    monitoring_utils.accumulate('num_courses', len(course_enrollments))

    # Sort the enrollment pairs by the enrollment date
    course_enrollments.sort(key=lambda x: x.created, reverse=True)

    # Retrieve the course modes for each course
    enrolled_course_ids = [
        enrollment.course_id for enrollment in course_enrollments
    ]
    __, unexpired_course_modes = CourseMode.all_and_unexpired_modes_for_courses(
        enrolled_course_ids)
    course_modes_by_course = {
        course_id: {mode.slug: mode
                    for mode in modes}
        for course_id, modes in iteritems(unexpired_course_modes)
    }

    # Check to see if the student has recently enrolled in a course.
    # If so, display a notification message confirming the enrollment.
    enrollment_message = _create_recent_enrollment_message(
        course_enrollments, course_modes_by_course)
    course_optouts = Optout.objects.filter(user=user).values_list('course_id',
                                                                  flat=True)

    # Display activation message
    activate_account_message = ''
    if not user.is_active:
        activate_account_message = Text(
            _("Check your {email_start}{email}{email_end} inbox for an account activation link from {platform_name}. "
              "If you need help, contact {link_start}{platform_name} Support{link_end}."
              )
        ).format(
            platform_name=platform_name,
            email_start=HTML("<strong>"),
            email_end=HTML("</strong>"),
            email=user.email,
            link_start=HTML(
                "<a target='_blank' href='{activation_email_support_link}'>").
            format(
                activation_email_support_link=activation_email_support_link, ),
            link_end=HTML("</a>"),
        )

    enterprise_message = get_dashboard_consent_notification(
        request, user, course_enrollments)

    recovery_email_message = recovery_email_activation_message = None
    if is_secondary_email_feature_enabled_for_user(user=user):
        try:
            account_recovery_obj = AccountRecovery.objects.get(user=user)
        except AccountRecovery.DoesNotExist:
            recovery_email_message = Text(
                _("Add a recovery email to retain access when single-sign on is not available. "
                  "Go to {link_start}your Account Settings{link_end}.")
            ).format(link_start=HTML(
                "<a target='_blank' href='{account_setting_page}'>").format(
                    account_setting_page=reverse('account_settings'), ),
                     link_end=HTML("</a>"))
        else:
            if not account_recovery_obj.is_active:
                recovery_email_activation_message = Text(
                    _("Recovery email is not activated yet. "
                      "Kindly visit your email and follow the instructions to activate it."
                      ))

    # Disable lookup of Enterprise consent_required_course due to ENT-727
    # Will re-enable after fixing WL-1315
    consent_required_courses = set()
    enterprise_customer_name = None

    # Account activation message
    account_activation_messages = [
        message for message in messages.get_messages(request)
        if 'account-activation' in message.tags
    ]

    # Global staff can see what courses encountered an error on their dashboard
    staff_access = False
    errored_courses = {}
    if has_access(user, 'staff', 'global'):
        # Show any courses that encountered an error on load
        staff_access = True
        errored_courses = modulestore().get_errored_courses()

    show_courseware_links_for = {
        enrollment.course_id: has_access(request.user, 'load',
                                         enrollment.course_overview)
        for enrollment in course_enrollments
    }

    # Find programs associated with course runs being displayed. This information
    # is passed in the template context to allow rendering of program-related
    # information on the dashboard.
    meter = ProgramProgressMeter(request.site,
                                 user,
                                 enrollments=course_enrollments)
    ecommerce_service = EcommerceService()
    inverted_programs = meter.invert_programs()

    urls, programs_data = {}, {}
    bundles_on_dashboard_flag = WaffleFlag(
        WaffleFlagNamespace(name=u'student.experiments'),
        u'bundles_on_dashboard')

    # TODO: Delete this code and the relevant HTML code after testing LEARNER-3072 is complete
    if bundles_on_dashboard_flag.is_enabled(
    ) and inverted_programs and inverted_programs.items():
        if len(course_enrollments) < 4:
            for program in inverted_programs.values():
                try:
                    program_uuid = program[0]['uuid']
                    program_data = get_programs(request.site,
                                                uuid=program_uuid)
                    program_data = ProgramDataExtender(program_data,
                                                       request.user).extend()
                    skus = program_data.get('skus')
                    checkout_page_url = ecommerce_service.get_checkout_page_url(
                        *skus)
                    program_data[
                        'completeProgramURL'] = checkout_page_url + '&bundle=' + program_data.get(
                            'uuid')
                    programs_data[program_uuid] = program_data
                except:  # pylint: disable=bare-except
                    pass

    # Construct a dictionary of course mode information
    # used to render the course list.  We re-use the course modes dict
    # we loaded earlier to avoid hitting the database.
    course_mode_info = {
        enrollment.course_id: complete_course_mode_info(
            enrollment.course_id,
            enrollment,
            modes=course_modes_by_course[enrollment.course_id])
        for enrollment in course_enrollments
    }

    # Determine the per-course verification status
    # This is a dictionary in which the keys are course locators
    # and the values are one of:
    #
    # VERIFY_STATUS_NEED_TO_VERIFY
    # VERIFY_STATUS_SUBMITTED
    # VERIFY_STATUS_APPROVED
    # VERIFY_STATUS_MISSED_DEADLINE
    #
    # Each of which correspond to a particular message to display
    # next to the course on the dashboard.
    #
    # If a course is not included in this dictionary,
    # there is no verification messaging to display.
    verify_status_by_course = check_verify_status_by_course(
        user, course_enrollments)
    cert_statuses = {
        enrollment.course_id: cert_info(request.user,
                                        enrollment.course_overview)
        for enrollment in course_enrollments
    }

    # only show email settings for Mongo course and when bulk email is turned on
    show_email_settings_for = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if (BulkEmailFlag.feature_enabled(enrollment.course_id)))

    # Verification Attempts
    # Used to generate the "you must reverify for course x" banner
    verification_status = IDVerificationService.user_status(user)
    verification_errors = get_verification_error_reasons_for_display(
        verification_status['error'])

    # Gets data for midcourse reverifications, if any are necessary or have failed
    statuses = ["approved", "denied", "pending", "must_reverify"]
    reverifications = reverification_info(statuses)

    block_courses = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if is_course_blocked(
            request,
            CourseRegistrationCode.objects.filter(
                course_id=enrollment.course_id,
                registrationcoderedemption__redeemed_by=request.user),
            enrollment.course_id))

    enrolled_courses_either_paid = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.is_paid_course())

    # If there are *any* denied reverifications that have not been toggled off,
    # we'll display the banner
    denied_banner = any(item.display for item in reverifications["denied"])

    # Populate the Order History for the side-bar.
    order_history_list = order_history(user,
                                       course_org_filter=site_org_whitelist,
                                       org_filter_out_set=site_org_blacklist)

    # get list of courses having pre-requisites yet to be completed
    courses_having_prerequisites = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.course_overview.pre_requisite_courses)
    courses_requirements_not_met = get_pre_requisite_courses_not_completed(
        user, courses_having_prerequisites)

    if 'notlive' in request.GET:
        redirect_message = _(
            "The course you are looking for does not start until {date}."
        ).format(date=request.GET['notlive'])
    elif 'course_closed' in request.GET:
        redirect_message = _(
            "The course you are looking for is closed for enrollment as of {date}."
        ).format(date=request.GET['course_closed'])
    elif 'access_response_error' in request.GET:
        # This can be populated in a generalized way with fields from access response errors
        redirect_message = request.GET['access_response_error']
    else:
        redirect_message = ''

    valid_verification_statuses = [
        'approved', 'must_reverify', 'pending', 'expired'
    ]
    display_sidebar_on_dashboard = (
        len(order_history_list)
        or (verification_status['status'] in valid_verification_statuses
            and verification_status['should_display']))

    # Filter out any course enrollment course cards that are associated with fulfilled entitlements
    for entitlement in [
            e for e in course_entitlements
            if e.enrollment_course_run is not None
    ]:
        course_enrollments = [
            enr for enr in course_enrollments
            if entitlement.enrollment_course_run.course_id != enr.course_id
        ]

    context = {
        'urls':
        urls,
        'programs_data':
        programs_data,
        'enterprise_message':
        enterprise_message,
        'consent_required_courses':
        consent_required_courses,
        'enterprise_customer_name':
        enterprise_customer_name,
        'enrollment_message':
        enrollment_message,
        'redirect_message':
        redirect_message,
        'account_activation_messages':
        account_activation_messages,
        'activate_account_message':
        activate_account_message,
        'course_enrollments':
        course_enrollments,
        'course_entitlements':
        course_entitlements,
        'course_entitlement_available_sessions':
        course_entitlement_available_sessions,
        'unfulfilled_entitlement_pseudo_sessions':
        unfulfilled_entitlement_pseudo_sessions,
        'course_optouts':
        course_optouts,
        'staff_access':
        staff_access,
        'errored_courses':
        errored_courses,
        'show_courseware_links_for':
        show_courseware_links_for,
        'all_course_modes':
        course_mode_info,
        'cert_statuses':
        cert_statuses,
        'credit_statuses':
        _credit_statuses(user, course_enrollments),
        'show_email_settings_for':
        show_email_settings_for,
        'reverifications':
        reverifications,
        'verification_display':
        verification_status['should_display'],
        'verification_status':
        verification_status['status'],
        'verification_status_by_course':
        verify_status_by_course,
        'verification_errors':
        verification_errors,
        'block_courses':
        block_courses,
        'denied_banner':
        denied_banner,
        'billing_email':
        settings.PAYMENT_SUPPORT_EMAIL,
        'user':
        user,
        'logout_url':
        reverse('logout'),
        'platform_name':
        platform_name,
        'enrolled_courses_either_paid':
        enrolled_courses_either_paid,
        'provider_states': [],
        'order_history_list':
        order_history_list,
        'courses_requirements_not_met':
        courses_requirements_not_met,
        'nav_hidden':
        True,
        'inverted_programs':
        inverted_programs,
        'show_program_listing':
        ProgramsApiConfig.is_enabled(),
        'show_journal_listing':
        journals_enabled(),  # TODO: Dashboard Plugin required
        'show_dashboard_tabs':
        True,
        'disable_courseware_js':
        True,
        'display_course_modes_on_dashboard':
        enable_verified_certificates and display_course_modes_on_dashboard,
        'display_sidebar_on_dashboard':
        display_sidebar_on_dashboard,
        'display_sidebar_account_activation_message':
        not (user.is_active or hide_dashboard_courses_until_activated),
        'display_dashboard_courses':
        (user.is_active or not hide_dashboard_courses_until_activated),
        'empty_dashboard_message':
        empty_dashboard_message,
        'recovery_email_message':
        recovery_email_message,
        'recovery_email_activation_message':
        recovery_email_activation_message,
    }

    if ecommerce_service.is_enabled(request.user):
        context.update({
            'use_ecommerce_payment_flow':
            True,
            'ecommerce_payment_page':
            ecommerce_service.payment_page_url(),
        })

    # Gather urls for course card resume buttons.
    resume_button_urls = ['' for entitlement in course_entitlements]
    for url in _get_urls_for_resume_buttons(user, course_enrollments):
        resume_button_urls.append(url)

    # eliteu membership
    if settings.FEATURES.get('ENABLE_MEMBERSHIP_INTEGRATION', False):
        from membership.models import VIPCourseEnrollment, VIPInfo
        vip_info = VIPInfo.objects.filter(user=user).order_by('-id').first()

        vces = VIPCourseEnrollment.objects.filter(user=user, is_active=True)
        vip_course_enrollment_ids = [v.course_id.html_id() for v in vces]

        context.update({
            'is_vip': VIPInfo.is_vip(user),
            'vip_expired_at': vip_info and vip_info.expired_at or None,
            'vip_purchase_url': reverse('membership_card'),
            'vip_course_enrollment_ids': vip_course_enrollment_ids,
            'display_sidebar_on_dashboard': True
        })

    # There must be enough urls for dashboard.html. Template creates course
    # cards for "enrollments + entitlements".
    context.update({'resume_button_urls': resume_button_urls})

    response = render_to_response('dashboard.html', context)
    set_logged_in_cookies(request, response, user)
    return response
Exemple #37
0
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 = configuration_helpers.get_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
Exemple #38
0
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
    # configuration settings.
    if is_request_in_themed_site() and not configuration_helpers.get_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':
            configuration_helpers.get_value('PLATFORM_NAME',
                                            settings.PLATFORM_NAME),
            'support_link':
            configuration_helpers.get_value('SUPPORT_SITE_LINK',
                                            settings.SUPPORT_SITE_LINK),

            # 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':
        not configuration_helpers.get_value(
            'ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER',
            settings.FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION_FOOTER']),
    }

    return render_to_response('student_account/login_and_register.html',
                              context)
Exemple #39
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 = [(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,
        'nav_hidden':
        True,
        '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,
            }
        },
        'platform_name':
        configuration_helpers.get_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),
                # 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
        ]

    return context
Exemple #40
0
    'packages': ('openassessment', ),
}

urlpatterns += (url(r'^openassessment/fileupload/',
                    include('openassessment.fileupload.urls')), )

# sysadmin dashboard, to see what courses are loaded, to delete & load courses
if settings.FEATURES["ENABLE_SYSADMIN_DASHBOARD"]:
    urlpatterns += (url(r'^sysadmin/', include('dashboard.sysadmin_urls')), )

urlpatterns += (url(
    r'^support/',
    include('support.urls', app_name="support", namespace='support')), )

# Favicon
favicon_path = configuration_helpers.get_value('favicon_path',
                                               settings.FAVICON_PATH)  # pylint: disable=invalid-name
urlpatterns += (url(
    r'^favicon\.ico$',
    RedirectView.as_view(url=settings.STATIC_URL + favicon_path,
                         permanent=True)), )

# Multicourse wiki (Note: wiki urls must be above the courseware ones because of
# the custom tab catch-all)
if settings.WIKI_ENABLED:
    from wiki.urls import get_pattern as wiki_pattern
    from django_notify.urls import get_pattern as notify_pattern

    urlpatterns += (
        # First we include views from course_wiki that we use to override the default views.
        # They come first in the urlpatterns so they get resolved first
        url('^wiki/create-root/$',
Exemple #41
0
def create_account(username, password, email):
    """Create a new user account.

    This will implicitly create an empty profile for the user.

    WARNING: This function does NOT yet implement all the features
    in `student/views.py`.  Until it does, please use this method
    ONLY for tests of the account API, not in production code.
    In particular, these are currently missing:

    * 3rd party auth
    * External auth (shibboleth)
    * Complex password policies (ENFORCE_PASSWORD_POLICY)

    In addition, we assume that some functionality is handled
    at higher layers:

    * Analytics events
    * Activation email
    * Terms of service / honor code checking
    * Recording demographic info (use profile API)
    * Auto-enrollment in courses (if invited via instructor dash)

    Args:
        username (unicode): The username for the new account.
        password (unicode): The user's password.
        email (unicode): The email address associated with the account.

    Returns:
        unicode: an activation key for the account.

    Raises:
        errors.AccountUserAlreadyExists
        errors.AccountUsernameInvalid
        errors.AccountEmailInvalid
        errors.AccountPasswordInvalid
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
    ):
        return HttpResponseForbidden(_("Account creation not allowed."))

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)

    # Validate the username, password, and email
    # This will raise an exception if any of these are not in a valid format.
    _validate_username(username)
    _validate_password(password, username)
    _validate_email(email)

    # Create the user account, setting them to "inactive" until they activate their account.
    user = User(username=username, email=email, is_active=False)
    user.set_password(password)

    try:
        user.save()
    except IntegrityError:
        raise errors.AccountUserAlreadyExists

    # Create a registration to track the activation process
    # This implicitly saves the registration.
    registration = Registration()
    registration.register(user)

    # Create an empty user profile with default values
    UserProfile(user=user).save()

    # Return the activation key, which the caller should send to the user
    return registration.activation_key
Exemple #42
0
 def public_api_url(self):
     """
     Publicly-accessible API URL root.
     """
     root = helpers.get_value('CREDENTIALS_PUBLIC_SERVICE_URL', settings.CREDENTIALS_PUBLIC_SERVICE_URL)
     return urljoin(root, '/api/{}/'.format(API_VERSION))
Exemple #43
0
 def internal_api_url(self):
     """
     Internally-accessible API URL root.
     """
     root = helpers.get_value('CREDENTIALS_INTERNAL_SERVICE_URL', settings.CREDENTIALS_INTERNAL_SERVICE_URL)
     return urljoin(root, '/api/{}/'.format(API_VERSION))
Exemple #44
0
    def get(self, request):
        """Return a description of the login 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("user_api_login_session"))

        # Translators: This label appears above a field on the login 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 login form meant to hold the user's email address.
        email_placeholder = _(u"*****@*****.**")

        # Translators: These instructions appear on the login form, immediately
        # below a field meant to hold the user's email address.
        email_instructions = _(
            "The email address you used to register with {platform_name}"
        ).format(platform_name=configuration_helpers.get_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": accounts.EMAIL_MIN_LENGTH,
                                "max_length": accounts.EMAIL_MAX_LENGTH,
                            })

        # Translators: This label appears above a field on the login form
        # meant to hold the user's password.
        password_label = _(u"Password")

        form_desc.add_field("password",
                            label=password_label,
                            field_type="password",
                            restrictions={
                                "min_length": accounts.PASSWORD_MIN_LENGTH,
                                "max_length": accounts.PASSWORD_MAX_LENGTH,
                            })

        form_desc.add_field(
            "remember",
            field_type="checkbox",
            label=_("Remember me"),
            default=False,
            required=False,
        )

        return HttpResponse(form_desc.to_json(),
                            content_type="application/json")
Exemple #45
0
def submit_feedback(request):
    """
    Create a Zendesk ticket or if not available, send an email with the
    feedback form fields.

    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"])

    def build_error_response(status_code, field, err_msg):
        return HttpResponse(json.dumps({"field": field, "error": err_msg}), status=status_code)

    required_fields = ["subject", "details"]

    if not UserProfile.has_registered(request.user):
        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])

    if not UserProfile.has_registered(request.user):
        try:
            validate_email(request.POST["email"])
        except ValidationError:
            return build_error_response(400, "email", required_field_errs["email"])

    success = False
    context = get_feedback_form_context(request)
    support_backend = configuration_helpers.get_value('CONTACT_FORM_SUBMISSION_BACKEND', SUPPORT_BACKEND_ZENDESK)

    if support_backend == SUPPORT_BACKEND_EMAIL:
        try:
            send_mail(
                subject=render_to_string('emails/contact_us_feedback_email_subject.txt', context),
                message=render_to_string('emails/contact_us_feedback_email_body.txt', context),
                from_email=context["support_email"],
                recipient_list=[context["support_email"]],
                fail_silently=False
            )
            success = True
        except SMTPException:
            log.exception('Error sending feedback to contact_us email address.')
            success = False

    else:
        if not settings.ZENDESK_URL or not settings.ZENDESK_USER or not settings.ZENDESK_API_KEY:
            raise Exception("Zendesk enabled but not configured")

        success = _record_feedback_in_zendesk(
            context["realname"],
            context["email"],
            context["subject"],
            context["details"],
            context["tags"],
            context["additional_info"],
            support_email=context["support_email"]
        )

    _record_feedback_in_datadog(context["tags"])

    return HttpResponse(status=(200 if success else 500))
Exemple #46
0
    def get(
        self, request, course_id,
        always_show_payment=False,
        current_step=None,
        message=FIRST_TIME_VERIFY_MSG
    ):
        """
        Render the payment and verification flow.

        Arguments:
            request (HttpRequest): The request object.
            course_id (unicode): The ID of the course the user is trying
                to enroll in.

        Keyword Arguments:
            always_show_payment (bool): If True, show the payment steps
                even if the user has already paid.  This is useful
                for users returning to the flow after paying.
            current_step (string): The current step in the flow.
            message (string): The messaging to display.

        Returns:
            HttpResponse

        Raises:
            Http404: The course does not exist or does not
                have a verified mode.

        """
        # Parse the course key
        # The URL regex should guarantee that the key format is valid.
        course_key = CourseKey.from_string(course_id)
        course = modulestore().get_course(course_key)

        # Verify that the course exists
        if course is None:
            log.warn(u"Could not find course with ID %s.", course_id)
            raise Http404

        # Check whether the user has access to this course
        # based on country access rules.
        redirect_url = embargo_api.redirect_if_blocked(
            course_key,
            user=request.user,
            ip_address=get_ip(request),
            url=request.path
        )
        if redirect_url:
            return redirect(redirect_url)

        # If the verification deadline has passed
        # then show the user a message that he/she can't verify.
        #
        # We're making the assumptions (enforced in Django admin) that:
        #
        # 1) Only verified modes have verification deadlines.
        #
        # 2) If set, verification deadlines are always AFTER upgrade deadlines, because why would you
        #   let someone upgrade into a verified track if they can't complete verification?
        #
        verification_deadline = VerificationDeadline.deadline_for_course(course.id)
        response = self._response_if_deadline_passed(course, self.VERIFICATION_DEADLINE, verification_deadline)
        if response is not None:
            log.info(u"Verification deadline for '%s' has passed.", course.id)
            return response

        # Retrieve the relevant course mode for the payment/verification flow.
        #
        # WARNING: this is technical debt!  A much better way to do this would be to
        # separate out the payment flow and use the product SKU to figure out what
        # the user is trying to purchase.
        #
        # Nonetheless, for the time being we continue to make the really ugly assumption
        # that at some point there was a paid course mode we can query for the price.
        relevant_course_mode = self._get_paid_mode(course_key)

        # If we can find a relevant course mode, then log that we're entering the flow
        # Otherwise, this course does not support payment/verification, so respond with a 404.
        if relevant_course_mode is not None:
            if CourseMode.is_verified_mode(relevant_course_mode):
                log.info(
                    u"Entering payment and verification flow for user '%s', course '%s', with current step '%s'.",
                    request.user.id, course_id, current_step
                )
            else:
                log.info(
                    u"Entering payment flow for user '%s', course '%s', with current step '%s'",
                    request.user.id, course_id, current_step
                )
        else:
            # Otherwise, there has never been a verified/paid mode,
            # so return a page not found response.
            log.warn(
                u"No paid/verified course mode found for course '%s' for verification/payment flow request",
                course_id
            )
            raise Http404

        # If the user is trying to *pay* and the upgrade deadline has passed,
        # then they shouldn't be able to enter the flow.
        #
        # NOTE: This should match the availability dates used by the E-Commerce service
        # to determine whether a user can purchase a product.  The idea is that if the service
        # won't fulfill the order, we shouldn't even let the user get into the payment flow.
        #
        user_is_trying_to_pay = message in [self.FIRST_TIME_VERIFY_MSG, self.UPGRADE_MSG]
        if user_is_trying_to_pay:
            upgrade_deadline = relevant_course_mode.expiration_datetime
            response = self._response_if_deadline_passed(course, self.UPGRADE_DEADLINE, upgrade_deadline)
            if response is not None:
                log.info(u"Upgrade deadline for '%s' has passed.", course.id)
                return response

        # Check whether the user has verified, paid, and enrolled.
        # A user is considered "paid" if he or she has an enrollment
        # with a paid course mode (such as "verified").
        # For this reason, every paid user is enrolled, but not
        # every enrolled user is paid.
        # If the course mode is not verified(i.e only paid) then already_verified is always True
        already_verified = (
            self._check_already_verified(request.user)
            if CourseMode.is_verified_mode(relevant_course_mode)
            else True
        )
        already_paid, is_enrolled = self._check_enrollment(request.user, course_key)

        # Redirect the user to a more appropriate page if the
        # messaging won't make sense based on the user's
        # enrollment / payment / verification status.
        sku_to_use = relevant_course_mode.sku
        purchase_workflow = request.GET.get('purchase_workflow', 'single')
        if purchase_workflow == 'bulk' and relevant_course_mode.bulk_sku:
            sku_to_use = relevant_course_mode.bulk_sku
        redirect_response = self._redirect_if_necessary(
            message,
            already_verified,
            already_paid,
            is_enrolled,
            course_key,
            user_is_trying_to_pay,
            request.user,
            sku_to_use
        )
        if redirect_response is not None:
            return redirect_response

        display_steps = self._display_steps(
            always_show_payment,
            already_verified,
            already_paid,
            relevant_course_mode
        )

        # Override the actual value if account activation has been disabled
        # Also see the reference to this parameter in context dictionary further down
        user_is_active = self._get_user_active_status(request.user)
        requirements = self._requirements(display_steps, user_is_active)

        if current_step is None:
            current_step = display_steps[0]['name']

        # Allow the caller to skip the first page
        # This is useful if we want the user to be able to
        # use the "back" button to return to the previous step.
        # This parameter should only work for known skip-able steps
        if request.GET.get('skip-first-step') and current_step in self.SKIP_STEPS:
            display_step_names = [step['name'] for step in display_steps]
            current_step_idx = display_step_names.index(current_step)
            if (current_step_idx + 1) < len(display_steps):
                current_step = display_steps[current_step_idx + 1]['name']

        courseware_url = ""
        if not course.start or course.start < datetime.datetime.today().replace(tzinfo=UTC):
            courseware_url = reverse(
                'course_root',
                kwargs={'course_id': unicode(course_key)}
            )

        full_name = (
            request.user.profile.name
            if request.user.profile.name
            else ""
        )

        # If the user set a contribution amount on another page,
        # use that amount to pre-fill the price selection form.
        contribution_amount = request.session.get(
            'donation_for_course', {}
        ).get(unicode(course_key), '')

        # Remember whether the user is upgrading
        # so we can fire an analytics event upon payment.
        request.session['attempting_upgrade'] = (message == self.UPGRADE_MSG)

        # Determine the photo verification status
        verification_good_until = self._verification_valid_until(request.user)

        # get available payment processors
        if relevant_course_mode.sku:
            # transaction will be conducted via ecommerce service
            processors = ecommerce_api_client(request.user).payment.processors.get()
        else:
            # transaction will be conducted using legacy shopping cart
            processors = [settings.CC_PROCESSOR_NAME]

        # Render the top-level page
        context = {
            'contribution_amount': contribution_amount,
            'course': course,
            'course_key': unicode(course_key),
            'checkpoint_location': request.GET.get('checkpoint'),
            'course_mode': relevant_course_mode,
            'courseware_url': courseware_url,
            'current_step': current_step,
            'disable_courseware_js': True,
            'display_steps': display_steps,
            'is_active': json.dumps(user_is_active),
            'user_email': request.user.email,
            'message_key': message,
            'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
            'processors': processors,
            'requirements': requirements,
            'user_full_name': full_name,
            'verification_deadline': verification_deadline or "",
            'already_verified': already_verified,
            'verification_good_until': verification_good_until,
            'capture_sound': staticfiles_storage.url("audio/camera_capture.wav"),
            'nav_hidden': True,
            'is_ab_testing': 'begin-flow' in request.path,
        }

        return render_to_response("verify_student/pay_and_verify.html", context)
Exemple #47
0
    def tma_users_registered(self):
        context = {}
        enrolled_to_current_course = False
        user_email = self.request.POST['user_email']

        if User.objects.filter(email=user_email).exists():
            user = User.objects.get(email=user_email)
            #Get preprofile info
            userprofile = UserProfile.objects.get(user=user)
            try:
                custom_field = json.loads(user.profile.custom_field)
                first_name = custom_field['first_name']
                last_name = custom_field['last_name']
            except:
                custom_field = {}
                last_name = 'Undefined'
                first_name = 'Undefined'

            #Get courses enrollments
            microsite_courses = get_courses(
                user=user,
                org=configuration_helpers.get_value('course_org_filter'))

            user_ms_course_list = {}
            for course in microsite_courses:
                if CourseEnrollment.objects.filter(
                        user=user, course_id=course.id).exists():
                    user_ms_course_list[str(course.id)] = {
                        'course_name':
                        course.display_name_with_default,
                        'course_grades':
                        '/courses/' + str(course.id) + '/progress/' +
                        str(user.id),
                        'opened_enrollments':
                        is_enrollment_opened(course),
                        'opened_course':
                        is_course_opened(course),
                        'on_invitation':
                        course.invitation_only,
                    }

            #Get user grade for this course
            course_key = SlashSeparatedCourseKey.from_deprecated_string(
                self.course_id)
            current_course = get_course_by_id(course_key)
            grade = CourseGradeFactory().create(user, current_course)

            #User dates
            if user.last_login is not None:
                last_login = user.date_joined.strftime("%d-%b-%Y %H:%M:%S")
            else:
                last_login = _('User has not logged in yet')

            context = {
                'email': str(user_email),
                'id': str(user.id),
                'inscription':
                str(user.date_joined.strftime("%d-%b-%Y %H:%M:%S")),
                'last_login': last_login,
                'first_name': first_name,
                'last_name': last_name,
                'user_ms_course_list': user_ms_course_list,
                'custom_field': custom_field,
                'grade': grade.grade_value['percent'],
                'passed': grade.grade_value['grade'],
                'active': user.is_active,
                'login_failure': LoginFailures.is_user_locked_out(user),
            }

            #Check if user is registered to this course
            course_key = SlashSeparatedCourseKey.from_deprecated_string(
                self.course_id)
            if CourseEnrollment.objects.filter(user=user,
                                               course_id=course_key,
                                               is_active=1).exists():
                current_course_grades = '/courses/' + str(
                    self.course_id) + '/progress/' + str(user.id)
                context['enrolled_to_current_course'] = True
                context['current_course_grades'] = current_course_grades
            else:
                context['enrolled_to_current_course'] = False

        else:
            context = {
                'error':
                'Le participant n\'a pas de compte sur nos plateformes.'
            }

        return context
def instructor_dashboard_2(request, course_id):  # lint-amnesty, pylint: disable=too-many-statements
    """ Display the instructor dashboard for a course. """
    try:
        course_key = CourseKey.from_string(course_id)
    except InvalidKeyError:
        log.error(
            "Unable to find course with course key %s while loading the Instructor Dashboard.",
            course_id)
        return HttpResponseServerError()

    course = get_course_by_id(course_key, depth=0)

    access = {
        'admin':
        request.user.is_staff,
        'instructor':
        bool(has_access(request.user, 'instructor', course)),
        'finance_admin':
        CourseFinanceAdminRole(course_key).has_user(request.user),
        'sales_admin':
        CourseSalesAdminRole(course_key).has_user(request.user),
        'staff':
        bool(has_access(request.user, 'staff', course)),
        'forum_admin':
        has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR),
        'data_researcher':
        request.user.has_perm(permissions.CAN_RESEARCH, course_key),
    }

    if not request.user.has_perm(permissions.VIEW_DASHBOARD, course_key):
        raise Http404()

    is_white_label = CourseMode.is_white_label(course_key)  # lint-amnesty, pylint: disable=unused-variable

    reports_enabled = configuration_helpers.get_value('SHOW_ECOMMERCE_REPORTS',
                                                      False)  # lint-amnesty, pylint: disable=unused-variable

    sections = []
    if access['staff']:
        sections.extend([
            _section_course_info(course, access),
            _section_membership(course, access),
            _section_cohort_management(course, access),
            _section_discussions_management(course, access),
            _section_student_admin(course, access),
        ])
    if access['data_researcher']:
        sections.append(_section_data_download(course, access))

    analytics_dashboard_message = None
    if show_analytics_dashboard_message(course_key) and (access['staff'] or
                                                         access['instructor']):
        # Construct a URL to the external analytics dashboard
        analytics_dashboard_url = '{}/courses/{}'.format(
            settings.ANALYTICS_DASHBOARD_URL, str(course_key))
        link_start = HTML("<a href=\"{}\" rel=\"noopener\" target=\"_blank\">"
                          ).format(analytics_dashboard_url)
        analytics_dashboard_message = _(
            "To gain insights into student enrollment and participation {link_start}"
            "visit {analytics_dashboard_name}, our new course analytics product{link_end}."
        )
        analytics_dashboard_message = Text(analytics_dashboard_message).format(
            link_start=link_start,
            link_end=HTML("</a>"),
            analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME)

        # Temporarily show the "Analytics" section until we have a better way of linking to Insights
        sections.append(_section_analytics(course, access))

    # Check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
    course_mode_has_price = False  # lint-amnesty, pylint: disable=unused-variable
    paid_modes = CourseMode.paid_modes_for_course(course_key)
    if len(paid_modes) == 1:
        course_mode_has_price = True
    elif len(paid_modes) > 1:
        log.error(
            "Course %s has %s course modes with payment options. Course must only have "
            "one paid course mode to enable eCommerce options.",
            str(course_key), len(paid_modes))

    if access['instructor'] and is_enabled_for_course(course_key):
        sections.insert(3, _section_extensions(course))

    # Gate access to course email by feature flag & by course-specific authorization
    if is_bulk_email_feature_enabled(course_key) and (access['staff']
                                                      or access['instructor']):
        sections.append(_section_send_email(course, access))

    # Gate access to Special Exam tab depending if either timed exams or proctored exams
    # are enabled in the course

    user_has_access = any([
        request.user.is_staff,
        CourseStaffRole(course_key).has_user(request.user),
        CourseInstructorRole(course_key).has_user(request.user)
    ])
    course_has_special_exams = course.enable_proctored_exams or course.enable_timed_exams
    can_see_special_exams = course_has_special_exams and user_has_access and settings.FEATURES.get(
        'ENABLE_SPECIAL_EXAMS', False)

    if can_see_special_exams:
        sections.append(_section_special_exams(course, access))
    # Certificates panel
    # This is used to generate example certificates
    # and enable self-generated certificates for a course.
    # Note: This is hidden for all CCXs
    certs_enabled = CertificateGenerationConfiguration.current(
    ).enabled and not hasattr(course_key, 'ccx')
    if certs_enabled and access['admin']:
        sections.append(_section_certificates(course))

    openassessment_blocks = modulestore().get_items(
        course_key, qualifiers={'category': 'openassessment'})
    # filter out orphaned openassessment blocks
    openassessment_blocks = [
        block for block in openassessment_blocks if block.parent is not None
    ]
    if len(openassessment_blocks) > 0 and access['staff']:
        sections.append(
            _section_open_response_assessment(request, course,
                                              openassessment_blocks, access))

    disable_buttons = not CourseEnrollment.objects.is_small_course(course_key)

    certificate_white_list = CertificateWhitelist.get_certificate_white_list(
        course_key)
    generate_certificate_exceptions_url = reverse(
        'generate_certificate_exceptions',
        kwargs={
            'course_id': str(course_key),
            'generate_for': ''
        })
    generate_bulk_certificate_exceptions_url = reverse(
        'generate_bulk_certificate_exceptions',
        kwargs={'course_id': str(course_key)})
    certificate_exception_view_url = reverse(
        'certificate_exception_view', kwargs={'course_id': str(course_key)})

    certificate_invalidation_view_url = reverse(
        'certificate_invalidation_view', kwargs={'course_id': str(course_key)})

    certificate_invalidations = CertificateInvalidation.get_certificate_invalidations(
        course_key)

    context = {
        'course':
        course,
        'studio_url':
        get_studio_url(course, 'course'),
        'sections':
        sections,
        'disable_buttons':
        disable_buttons,
        'analytics_dashboard_message':
        analytics_dashboard_message,
        'certificate_white_list':
        certificate_white_list,
        'certificate_invalidations':
        certificate_invalidations,
        'generate_certificate_exceptions_url':
        generate_certificate_exceptions_url,
        'generate_bulk_certificate_exceptions_url':
        generate_bulk_certificate_exceptions_url,
        'certificate_exception_view_url':
        certificate_exception_view_url,
        'certificate_invalidation_view_url':
        certificate_invalidation_view_url,
        'xqa_server':
        settings.FEATURES.get('XQA_SERVER', "http://your_xqa_server.com"),
    }

    return render_to_response(
        'instructor/instructor_dashboard_2/instructor_dashboard_2.html',
        context)
    def get_enrollment_info(self, user, course_id):
        """
        Returns the User Enrollment information.
        """
        course = get_course_by_id(course_id, depth=0)
        is_course_staff = bool(has_access(user, 'staff', course))
        manual_enrollment_reason = 'N/A'

        # check the user enrollment role
        if user.is_staff:
            platform_name = configuration_helpers.get_value(
                'platform_name', settings.PLATFORM_NAME)
            enrollment_role = _('{platform_name} Staff').format(
                platform_name=platform_name)
        elif is_course_staff:
            enrollment_role = _('Course Staff')
        else:
            enrollment_role = _('Student')

        course_enrollment = CourseEnrollment.get_enrollment(
            user=user, course_key=course_id)

        if is_course_staff:
            enrollment_source = _('Staff')
        else:
            # get the registration_code_redemption object if exists
            registration_code_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment(
                course_enrollment)
            # get the paid_course registration item if exists
            paid_course_reg_item = PaidCourseRegistration.get_course_item_for_user_enrollment(
                user=user,
                course_id=course_id,
                course_enrollment=course_enrollment)

            # from where the user get here
            if registration_code_redemption is not None:
                enrollment_source = _('Used Registration Code')
            elif paid_course_reg_item is not None:
                enrollment_source = _('Credit Card - Individual')
            else:
                manual_enrollment = ManualEnrollmentAudit.get_manual_enrollment(
                    course_enrollment)
                if manual_enrollment is not None:
                    enrollment_source = _(
                        'manually enrolled by username: {username}').format(
                            username=manual_enrollment.enrolled_by.username)

                    manual_enrollment_reason = manual_enrollment.reason
                else:
                    enrollment_source = _('Manually Enrolled')

        enrollment_date = course_enrollment.created.strftime("%B %d, %Y")
        currently_enrolled = course_enrollment.is_active

        course_enrollment_data = collections.OrderedDict()
        course_enrollment_data['Enrollment Date'] = enrollment_date
        course_enrollment_data['Currently Enrolled'] = currently_enrolled
        course_enrollment_data['Enrollment Source'] = enrollment_source
        course_enrollment_data[
            'Manual (Un)Enrollment Reason'] = manual_enrollment_reason
        course_enrollment_data['Enrollment Role'] = enrollment_role
        return course_enrollment_data
Exemple #50
0
def do_create_account(form, custom_form=None):
    """
    Given cleaned post variables, create the User and UserProfile objects, as well as the
    registration for this user.

    Returns a tuple (User, UserProfile, Registration).

    Note: this function is also used for creating test users.
    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
    ):
        raise PermissionDenied()

    errors = {}
    errors.update(form.errors)
    if custom_form:
        errors.update(custom_form.errors)

    if errors:
        raise ValidationError(errors)

    proposed_username = form.cleaned_data["username"]
    user = User(
        username=proposed_username,
        email=form.cleaned_data["email"],
        is_active=False
    )
    user.set_password(form.cleaned_data["password"])
    registration = Registration()

    # TODO: Rearrange so that if part of the process fails, the whole process fails.
    # Right now, we can have e.g. no registration e-mail sent out and a zombie account
    try:
        with transaction.atomic():
            user.save()
            if custom_form:
                custom_model = custom_form.save(commit=False)
                custom_model.user = user
                custom_model.save()
    except IntegrityError:
        # Figure out the cause of the integrity error
        # TODO duplicate email is already handled by form.errors above as a ValidationError.
        # The checks for duplicate email/username should occur in the same place with an
        # AccountValidationError and a consistent user message returned (i.e. both should
        # return "It looks like {username} belongs to an existing account. Try again with a
        # different username.")
        if User.objects.filter(username=user.username):
            raise AccountValidationError(
                USERNAME_EXISTS_MSG_FMT.format(username=proposed_username),
                field="username"
            )
        elif email_exists_or_retired(user.email):
            raise AccountValidationError(
                _("An account with the Email '{email}' already exists.").format(email=user.email),
                field="email"
            )
        else:
            raise

    # add this account creation to password history
    # NOTE, this will be a NOP unless the feature has been turned on in configuration
    password_history_entry = PasswordHistory()
    password_history_entry.create(user)

    registration.register(user)

    profile_fields = [
        "name", "level_of_education", "gender", "mailing_address", "city", "country", "goals",
        "year_of_birth"
    ]
    profile = UserProfile(
        user=user,
        **{key: form.cleaned_data.get(key) for key in profile_fields}
    )
    extended_profile = form.cleaned_extended_profile
    if extended_profile:
        profile.meta = json.dumps(extended_profile)
    try:
        profile.save()
    except Exception:
        log.exception("UserProfile creation failed for user {id}.".format(id=user.id))
        raise

    return user, profile, registration
def third_party_auth_context(request, redirect_to, tpa_hint=None):
    """
    Context for third party auth providers and the currently running pipeline.

    Arguments:
        request (HttpRequest): The request, used to determine if a pipeline
            is currently running.
        redirect_to: The URL to send the user to following successful
            authentication.
        tpa_hint (string): An override flag that will return a matching provider
            as long as its configuration has been enabled

    Returns:
        dict

    """
    context = {
        "currentProvider":
        None,
        "platformName":
        configuration_helpers.get_value('PLATFORM_NAME',
                                        settings.PLATFORM_NAME),
        "providers": [],
        "secondaryProviders": [],
        "finishAuthUrl":
        None,
        "errorMessage":
        None,
        "registerFormSubmitButtonText":
        _("Create Account"),
        "syncLearnerProfileData":
        False,
        "pipeline_user_details": {}
    }

    if third_party_auth.is_enabled():
        for enabled in third_party_auth.provider.Registry.displayed_for_login(
                tpa_hint=tpa_hint):
            info = {
                "id":
                enabled.provider_id,
                "name":
                enabled.name,
                "iconClass":
                enabled.icon_class or None,
                "iconImage":
                enabled.icon_image.url if enabled.icon_image else None,
                "skipHintedLogin":
                enabled.skip_hinted_login_dialog,
                "loginUrl":
                pipeline.get_login_url(
                    enabled.provider_id,
                    pipeline.AUTH_ENTRY_LOGIN,
                    redirect_url=redirect_to,
                ),
                "registerUrl":
                pipeline.get_login_url(
                    enabled.provider_id,
                    pipeline.AUTH_ENTRY_REGISTER,
                    redirect_url=redirect_to,
                ),
            }
            context["providers" if not enabled.
                    secondary else "secondaryProviders"].append(info)

        running_pipeline = pipeline.get(request)
        if running_pipeline is not None:
            current_provider = third_party_auth.provider.Registry.get_from_pipeline(
                running_pipeline)
            user_details = running_pipeline['kwargs']['details']
            if user_details:
                context['pipeline_user_details'] = user_details

            if current_provider is not None:
                context["currentProvider"] = current_provider.name
                context["finishAuthUrl"] = pipeline.get_complete_url(
                    current_provider.backend_name)
                context[
                    "syncLearnerProfileData"] = current_provider.sync_learner_profile_data

                if current_provider.skip_registration_form:
                    # As a reliable way of "skipping" the registration form, we just submit it automatically
                    context["autoSubmitRegForm"] = True

        # Check for any error messages we may want to display:
        for msg in messages.get_messages(request):
            if msg.extra_tags.split()[0] == "social-auth":
                # msg may or may not be translated. Try translating [again] in case we are able to:
                context["errorMessage"] = _(six.text_type(msg))  # pylint: disable=E7610
                break

    return context
Exemple #52
0
def should_redirect_to_profile_microfrontend():
    return (configuration_helpers.get_value('ENABLE_PROFILE_MICROFRONTEND')
            and REDIRECT_TO_PROFILE_MICROFRONTEND.is_enabled())
Exemple #53
0
def instructor_dashboard_2(request, course_id):
    """ Display the instructor dashboard for a course. """
    try:
        course_key = CourseKey.from_string(course_id)
    except InvalidKeyError:
        log.error(
            u"Unable to find course with course key %s while loading the Instructor Dashboard.",
            course_id)
        return HttpResponseServerError()

    course = get_course_by_id(course_key, depth=0)

    access = {
        'admin':
        request.user.is_staff,
        'instructor':
        bool(has_access(request.user, 'instructor', course)),
        'finance_admin':
        CourseFinanceAdminRole(course_key).has_user(request.user),
        'sales_admin':
        CourseSalesAdminRole(course_key).has_user(request.user),
        'staff':
        bool(has_access(request.user, 'staff', course)),
        'forum_admin':
        has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR),
    }

    if not access['staff']:
        raise Http404()

    is_white_label = CourseMode.is_white_label(course_key)

    reports_enabled = configuration_helpers.get_value('SHOW_ECOMMERCE_REPORTS',
                                                      False)

    sections = [
        _section_course_info(course, access),
        _section_membership(course, access, is_white_label),
        _section_cohort_management(course, access),
        _section_discussions_management(course, access),
        _section_student_admin(course, access),
        _section_data_download(course, access),
    ]

    analytics_dashboard_message = None
    if show_analytics_dashboard_message(course_key):
        # Construct a URL to the external analytics dashboard
        analytics_dashboard_url = '{0}/courses/{1}'.format(
            settings.ANALYTICS_DASHBOARD_URL, unicode(course_key))
        link_start = HTML("<a href=\"{}\" target=\"_blank\">").format(
            analytics_dashboard_url)
        analytics_dashboard_message = _(
            "To gain insights into student enrollment and participation {link_start}"
            "visit {analytics_dashboard_name}, our new course analytics product{link_end}."
        )
        analytics_dashboard_message = Text(analytics_dashboard_message).format(
            link_start=link_start,
            link_end=HTML("</a>"),
            analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME)

        # Temporarily show the "Analytics" section until we have a better way of linking to Insights
        sections.append(_section_analytics(course, access))

    # Check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
    course_mode_has_price = False
    paid_modes = CourseMode.paid_modes_for_course(course_key)
    if len(paid_modes) == 1:
        course_mode_has_price = True
    elif len(paid_modes) > 1:
        log.error(
            u"Course %s has %s course modes with payment options. Course must only have "
            u"one paid course mode to enable eCommerce options.",
            unicode(course_key), len(paid_modes))

    if settings.FEATURES.get('INDIVIDUAL_DUE_DATES') and access['instructor']:
        sections.insert(3, _section_extensions(course))

    # Gate access to course email by feature flag & by course-specific authorization
    if BulkEmailFlag.feature_enabled(course_key):
        sections.append(_section_send_email(course, access))

    # Gate access to Metrics tab by featue flag and staff authorization
    if settings.FEATURES['CLASS_DASHBOARD'] and access['staff']:
        sections.append(_section_metrics(course, access))

    # Gate access to Ecommerce tab
    if course_mode_has_price and (access['finance_admin']
                                  or access['sales_admin']):
        sections.append(
            _section_e_commerce(course, access, paid_modes[0], is_white_label,
                                reports_enabled))

    # Gate access to Special Exam tab depending if either timed exams or proctored exams
    # are enabled in the course

    # NOTE: For now, if we only have procotred exams enabled, then only platform Staff
    # (user.is_staff) will be able to view the special exams tab. This may
    # change in the future
    can_see_special_exams = (
        ((course.enable_proctored_exams and request.user.is_staff)
         or course.enable_timed_exams)
        and settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False))
    if can_see_special_exams:
        sections.append(_section_special_exams(course, access))

    # Certificates panel
    # This is used to generate example certificates
    # and enable self-generated certificates for a course.
    # Note: This is hidden for all CCXs
    certs_enabled = CertificateGenerationConfiguration.current(
    ).enabled and not hasattr(course_key, 'ccx')
    if certs_enabled and access['admin']:
        sections.append(_section_certificates(course))

    openassessment_blocks = modulestore().get_items(
        course_key, qualifiers={'category': 'openassessment'})
    # filter out orphaned openassessment blocks
    openassessment_blocks = [
        block for block in openassessment_blocks if block.parent is not None
    ]
    if len(openassessment_blocks) > 0:
        sections.append(
            _section_open_response_assessment(request, course,
                                              openassessment_blocks, access))

    disable_buttons = not _is_small_course(course_key)

    certificate_white_list = CertificateWhitelist.get_certificate_white_list(
        course_key)
    generate_certificate_exceptions_url = reverse(  # pylint: disable=invalid-name
        'generate_certificate_exceptions',
        kwargs={
            'course_id': unicode(course_key),
            'generate_for': ''
        })
    generate_bulk_certificate_exceptions_url = reverse(  # pylint: disable=invalid-name
        'generate_bulk_certificate_exceptions',
        kwargs={'course_id': unicode(course_key)})
    certificate_exception_view_url = reverse(
        'certificate_exception_view',
        kwargs={'course_id': unicode(course_key)})

    certificate_invalidation_view_url = reverse(  # pylint: disable=invalid-name
        'certificate_invalidation_view',
        kwargs={'course_id': unicode(course_key)})

    certificate_invalidations = CertificateInvalidation.get_certificate_invalidations(
        course_key)

    context = {
        'course': course,
        'studio_url': get_studio_url(course, 'course'),
        'sections': sections,
        'disable_buttons': disable_buttons,
        'analytics_dashboard_message': analytics_dashboard_message,
        'certificate_white_list': certificate_white_list,
        'certificate_invalidations': certificate_invalidations,
        'generate_certificate_exceptions_url':
        generate_certificate_exceptions_url,
        'generate_bulk_certificate_exceptions_url':
        generate_bulk_certificate_exceptions_url,
        'certificate_exception_view_url': certificate_exception_view_url,
        'certificate_invalidation_view_url': certificate_invalidation_view_url,
    }

    return render_to_response(
        'instructor/instructor_dashboard_2/instructor_dashboard_2.html',
        context)
Exemple #54
0
def confirm_email_change(request, key):
    """
    User requested a new e-mail. This is called when the activation
    link is clicked. We confirm with the old e-mail, and update
    """
    with transaction.atomic():
        try:
            pec = PendingEmailChange.objects.get(activation_key=key)
        except PendingEmailChange.DoesNotExist:
            response = render_to_response("invalid_email_key.html", {})
            transaction.set_rollback(True)
            return response

        user = pec.user
        address_context = {
            'old_email': user.email,
            'new_email': pec.new_email
        }

        if len(User.objects.filter(email=pec.new_email)) != 0:
            response = render_to_response("email_exists.html", {})
            transaction.set_rollback(True)
            return response

        use_https = request.is_secure()
        if settings.FEATURES['ENABLE_MKTG_SITE']:
            contact_link = marketing_link('CONTACT')
        else:
            contact_link = '{protocol}://{site}{link}'.format(
                protocol='https' if use_https else 'http',
                site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
                link=reverse('contact'),
            )

        site = Site.objects.get_current()
        message_context = get_base_template_context(site)
        message_context.update({
            'old_email': user.email,
            'new_email': pec.new_email,
            'contact_link': contact_link,
            'from_address': configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL),
        })

        msg = EmailChangeConfirmation().personalize(
            recipient=Recipient(user.id, user.email),
            language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
            user_context=message_context,
        )

        u_prof = UserProfile.objects.get(user=user)
        meta = u_prof.get_meta()
        if 'old_emails' not in meta:
            meta['old_emails'] = []
        meta['old_emails'].append([user.email, datetime.datetime.now(UTC).isoformat()])
        u_prof.set_meta(meta)
        u_prof.save()
        # Send it to the old email...
        try:
            ace.send(msg)
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to old address', exc_info=True)
            response = render_to_response("email_change_failed.html", {'email': user.email})
            transaction.set_rollback(True)
            return response

        user.email = pec.new_email
        user.save()
        pec.delete()
        # And send it to the new email...
        msg.recipient = Recipient(user.id, pec.new_email)
        try:
            ace.send(msg)
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to new address', exc_info=True)
            response = render_to_response("email_change_failed.html", {'email': pec.new_email})
            transaction.set_rollback(True)
            return response

        response = render_to_response("email_change_successful.html", address_context)
        return response
Exemple #55
0
def _get_extended_profile_fields():
    """Retrieve the extended profile fields from site configuration to be shown on the
       Account Settings page

    Returns:
        A list of dicts. Each dict corresponds to a single field. The keys per field are:
            "field_name"  : name of the field stored in user_profile.meta
            "field_label" : The label of the field.
            "field_type"  : TextField or ListField
            "field_options": a list of tuples for options in the dropdown in case of ListField
    """

    extended_profile_fields = []
    fields_already_showing = [
        'username', 'name', 'email', 'pref-lang', 'country', 'time_zone',
        'level_of_education', 'gender', 'year_of_birth',
        'language_proficiencies', 'social_links'
    ]

    field_labels_map = {
        "first_name":
        _(u"First Name"),
        "last_name":
        _(u"Last Name"),
        "city":
        _(u"City"),
        "state":
        _(u"State/Province/Region"),
        "company":
        _(u"Company"),
        "title":
        _(u"Title"),
        "job_title":
        _(u"Job Title"),
        "mailing_address":
        _(u"Mailing address"),
        "goals":
        _(u"Tell us why you're interested in {platform_name}").format(
            platform_name=configuration_helpers.get_value(
                "PLATFORM_NAME", settings.PLATFORM_NAME)),
        "profession":
        _(u"Profession"),
        "specialty":
        _(u"Specialty")
    }

    extended_profile_field_names = configuration_helpers.get_value(
        'extended_profile_fields', [])
    for field_to_exclude in fields_already_showing:
        if field_to_exclude in extended_profile_field_names:
            extended_profile_field_names.remove(field_to_exclude)

    extended_profile_field_options = configuration_helpers.get_value(
        'EXTRA_FIELD_OPTIONS', [])
    extended_profile_field_option_tuples = {}
    for field in extended_profile_field_options.keys():
        field_options = extended_profile_field_options[field]
        extended_profile_field_option_tuples[field] = [
            (option.lower(), option) for option in field_options
        ]

    for field in extended_profile_field_names:
        field_dict = {
            "field_name": field,
            "field_label": field_labels_map.get(field, field),
        }

        field_options = extended_profile_field_option_tuples.get(field)
        if field_options:
            field_dict["field_type"] = "ListField"
            field_dict["field_options"] = field_options
        else:
            field_dict["field_type"] = "TextField"
        extended_profile_fields.append(field_dict)

    return extended_profile_fields
Exemple #56
0
def get_next_url_for_login_page(request, include_host=False):
    """
    Determine the URL to redirect to following login/registration/third_party_auth

    The user is currently on a login or registration page.
    If 'course_id' is set, or other POST_AUTH_PARAMS, we will need to send the user to the
    /account/finish_auth/ view following login, which will take care of auto-enrollment in
    the specified course.

    Otherwise, we go to the ?next= query param or the configured custom
    redirection url (the default behaviour is to go to /dashboard).

    If THIRD_PARTY_AUTH_HINT is set, then `tpa_hint=<hint>` is added as a query parameter.

    This works with both GET and POST requests.
    """
    request_params = request.GET if request.method == 'GET' else request.POST
    redirect_to = _get_redirect_to(
        request_host=request.get_host(),
        request_headers=request.META,
        request_params=request_params,
        request_is_https=request.is_secure(),
    )
    root_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL)
    if not redirect_to:
        if settings.ROOT_URLCONF == 'lms.urls':
            login_redirect_url = configuration_helpers.get_value('DEFAULT_REDIRECT_AFTER_LOGIN')

            if login_redirect_url:
                try:
                    redirect_to = reverse(login_redirect_url)
                except NoReverseMatch:
                    log.warning(
                        'Default redirect after login doesn\'t exist: %(login_redirect_url)r. '
                        'Check the value set on DEFAULT_REDIRECT_AFTER_LOGIN configuration variable.',
                        {"login_redirect_url": login_redirect_url}
                    )

            # If redirect url isn't set, reverse to dashboard
            if not redirect_to:
                # Tries reversing the LMS dashboard if the url doesn't exist
                redirect_to = reverse('dashboard')

        elif settings.ROOT_URLCONF == 'cms.urls':
            redirect_to = reverse('home')
            scheme = "https" if settings.HTTPS == "on" else "http"
            root_url = f'{scheme}://{settings.CMS_BASE}'

    if any(param in request_params for param in POST_AUTH_PARAMS):
        # Before we redirect to next/dashboard, we need to handle auto-enrollment:
        params = [(param, request_params[param]) for param in POST_AUTH_PARAMS if param in request_params]

        params.append(('next', redirect_to))  # After auto-enrollment, user will be sent to payment page or to this URL
        redirect_to = '{}?{}'.format(reverse('finish_auth'), urllib.parse.urlencode(params))
        # Note: if we are resuming a third party auth pipeline, then the next URL will already
        # be saved in the session as part of the pipeline state. That URL will take priority
        # over this one.

    # Append a tpa_hint query parameter, if one is configured
    tpa_hint = configuration_helpers.get_value(
        "THIRD_PARTY_AUTH_HINT",
        settings.FEATURES.get("THIRD_PARTY_AUTH_HINT", '')
    )
    if tpa_hint:
        # Don't add tpa_hint if we're already in the TPA pipeline (prevent infinite loop),
        # and don't overwrite any existing tpa_hint params (allow tpa_hint override).
        running_pipeline = third_party_auth.pipeline.get(request)
        (scheme, netloc, path, query, fragment) = list(urllib.parse.urlsplit(redirect_to))
        if not running_pipeline and 'tpa_hint' not in query:
            params = urllib.parse.parse_qs(query)
            params['tpa_hint'] = [tpa_hint]
            query = urllib.parse.urlencode(params, doseq=True)
            redirect_to = urllib.parse.urlunsplit((scheme, netloc, path, query, fragment))

    if include_host:
        return redirect_to, root_url

    return redirect_to
Exemple #57
0
def create_account_with_params(request, params):
    """
    Given a request and a dict of parameters (which may or may not have come
    from the request), create an account for the requesting user, including
    creating a comments service user object and sending an activation email.
    This also takes external/third-party auth into account, updates that as
    necessary, and authenticates the user for the request's session.

    Does not return anything.

    Raises AccountValidationError if an account with the username or email
    specified by params already exists, or ValidationError if any of the given
    parameters is invalid for any other reason.

    Issues with this code:
    * It is non-transactional except where explicitly wrapped in atomic to
      alleviate deadlocks and improve performance. This means failures at
      different places in registration can leave users in inconsistent
      states.
    * Third-party auth passwords are not verified. There is a comment that
      they are unused, but it would be helpful to have a sanity check that
      they are sane.
    * The user-facing text is rather unfriendly (e.g. "Username must be a
      minimum of two characters long" rather than "Please use a username of
      at least two characters").
    * Duplicate email raises a ValidationError (rather than the expected
      AccountValidationError). Duplicate username returns an inconsistent
      user message (i.e. "An account with the Public Username '{username}'
      already exists." rather than "It looks like {username} belongs to an
      existing account. Try again with a different username.") The two checks
      occur at different places in the code; as a result, registering with
      both a duplicate username and email raises only a ValidationError for
      email only.
    """
    # Copy params so we can modify it; we can't just do dict(params) because if
    # params is request.POST, that results in a dict containing lists of values
    params = dict(params.items())

    # allow to define custom set of required/optional/hidden fields via configuration
    extra_fields = configuration_helpers.get_value(
        'REGISTRATION_EXTRA_FIELDS',
        getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {})
    )
    # registration via third party (Google, Facebook) using mobile application
    # doesn't use social auth pipeline (no redirect uri(s) etc involved).
    # In this case all related info (required for account linking)
    # is sent in params.
    # `third_party_auth_credentials_in_api` essentially means 'request
    # is made from mobile application'
    third_party_auth_credentials_in_api = 'provider' in params
    is_third_party_auth_enabled = third_party_auth.is_enabled()

    if is_third_party_auth_enabled and (pipeline.running(request) or third_party_auth_credentials_in_api):
        params["password"] = generate_password()

    # in case user is registering via third party (Google, Facebook) and pipeline has expired, show appropriate
    # error message
    if is_third_party_auth_enabled and ('social_auth_provider' in params and not pipeline.running(request)):
        raise ValidationError(
            {'session_expired': [
                _(u"Registration using {provider} has timed out.").format(
                    provider=params.get('social_auth_provider'))
            ]}
        )

    extended_profile_fields = configuration_helpers.get_value('extended_profile_fields', [])
    # Can't have terms of service for certain SHIB users, like at Stanford
    registration_fields = getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {})
    tos_required = (
        registration_fields.get('terms_of_service') != 'hidden' or
        registration_fields.get('honor_code') != 'hidden'
    )

    form = AccountCreationForm(
        data=params,
        extra_fields=extra_fields,
        extended_profile_fields=extended_profile_fields,
        do_third_party_auth=False,
        tos_required=tos_required,
    )
    custom_form = get_registration_extension_form(data=params)

    # Perform operations within a transaction that are critical to account creation
    with outer_atomic(read_committed=True):
        # first, create the account
        (user, profile, registration) = do_create_account(form, custom_form)

        third_party_provider, running_pipeline = _link_user_to_third_party_provider(
            is_third_party_auth_enabled, third_party_auth_credentials_in_api, user, request, params,
        )

        new_user = authenticate_new_user(request, user.username, params['password'])
        django_login(request, new_user)
        request.session.set_expiry(0)

    # Check if system is configured to skip activation email for the current user.
    skip_email = _skip_activation_email(
        user, running_pipeline, third_party_provider,
    )

    if skip_email:
        registration.activate()
    else:
        compose_and_send_activation_email(user, profile, registration)

    # Perform operations that are non-critical parts of account creation
    create_or_set_user_attribute_created_on_site(user, request.site)

    preferences_api.set_user_preference(user, LANGUAGE_KEY, get_language())

    if settings.FEATURES.get('ENABLE_DISCUSSION_EMAIL_DIGEST'):
        try:
            enable_notifications(user)
        except Exception:  # pylint: disable=broad-except
            log.exception(u"Enable discussion notifications failed for user {id}.".format(id=user.id))

    _track_user_registration(user, profile, params, third_party_provider)

    # Announce registration
    REGISTER_USER.send(sender=None, user=user, registration=registration)

    create_comments_service_user(user)

    try:
        _record_registration_attributions(request, new_user)
    # Don't prevent a user from registering due to attribution errors.
    except Exception:   # pylint: disable=broad-except
        log.exception('Error while attributing cookies to user registration.')

    # TODO: there is no error checking here to see that the user actually logged in successfully,
    # and is not yet an active user.
    if new_user is not None:
        AUDIT_LOG.info(u"Login success on new account creation - {0}".format(new_user.username))

    return new_user
Exemple #58
0
def activate_account(request, key):
    """
    When link in activation e-mail is clicked
    """
    # If request is in Studio call the appropriate view
    if theming_helpers.get_project_root_name().lower() == 'cms':
        monitoring_utils.set_custom_attribute('student_activate_account',
                                              'cms')
        return activate_account_studio(request, key)

    # TODO: Use custom attribute to determine if there are any `activate_account` calls for cms in Production.
    # If not, the templates wouldn't be needed for cms, but we still need a way to activate for cms tests.
    monitoring_utils.set_custom_attribute('student_activate_account', 'lms')
    activation_message_type = None

    invalid_message = HTML(
        _('{html_start}Your account could not be activated{html_end}'
          'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.'
          )).format(
              support_url=configuration_helpers.get_value(
                  'ACTIVATION_EMAIL_SUPPORT_LINK',
                  settings.ACTIVATION_EMAIL_SUPPORT_LINK)
              or settings.SUPPORT_SITE_LINK,
              html_start=HTML('<p class="message-title">'),
              html_end=HTML('</p>'),
          )

    try:
        registration = Registration.objects.get(activation_key=key)
    except (Registration.DoesNotExist, Registration.MultipleObjectsReturned):
        activation_message_type = 'error'
        messages.error(request,
                       invalid_message,
                       extra_tags='account-activation aa-icon')
    else:
        if request.user.is_authenticated and request.user.id != registration.user.id:
            activation_message_type = 'error'
            messages.error(request,
                           invalid_message,
                           extra_tags='account-activation aa-icon')
        elif registration.user.is_active:
            activation_message_type = 'info'
            messages.info(
                request,
                HTML(
                    _('{html_start}This account has already been activated.{html_end}'
                      )).format(
                          html_start=HTML('<p class="message-title">'),
                          html_end=HTML('</p>'),
                      ),
                extra_tags='account-activation aa-icon',
            )
        else:
            registration.activate()
            # Success message for logged in users.
            message = _(
                '{html_start}Success{html_end} You have activated your account.'
            )

            tracker.emit(
                USER_ACCOUNT_ACTIVATED, {
                    "user_id": registration.user.id,
                    "activation_timestamp": registration.activation_timestamp
                })

            if not request.user.is_authenticated:
                # Success message for logged out users
                message = _(
                    '{html_start}Success! You have activated your account.{html_end}'
                    'You will now receive email updates and alerts from us related to'
                    ' the courses you are enrolled in. Sign In to continue.')

            # Add message for later use.
            activation_message_type = 'success'
            messages.success(
                request,
                HTML(message).format(
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                ),
                extra_tags='account-activation aa-icon',
            )

    # If a safe `next` parameter is provided in the request
    # and it's not the same as the dashboard, redirect there.
    # The `get_next_url_for_login_page()` function will only return a safe redirect URL.
    # If the provided `next` URL is not safe, that function will fill `redirect_to`
    # with a value of `reverse('dashboard')`.
    redirect_url = None
    if request.GET.get('next'):
        redirect_to, root_login_url = get_next_url_for_login_page(
            request, include_host=True)

        # Don't automatically redirect authenticated users to the redirect_url
        # if the `next` value is either:
        # 1. "/dashboard" or
        # 2. "https://{LMS_ROOT_URL}/dashboard" (which we might provide as a value from the AuthN MFE)
        if redirect_to not in (root_login_url + reverse('dashboard'),
                               reverse('dashboard')):
            redirect_url = get_redirect_url_with_host(root_login_url,
                                                      redirect_to)

    if should_redirect_to_authn_microfrontend(
    ) and not request.user.is_authenticated:
        params = {'account_activation_status': activation_message_type}
        if redirect_url:
            params['next'] = redirect_url
        url_path = '/login?{}'.format(urllib.parse.urlencode(params))
        return redirect(settings.AUTHN_MICROFRONTEND_URL + url_path)

    return redirect(redirect_url) if redirect_url and is_enterprise_learner(
        request.user) else redirect('dashboard')
Exemple #59
0
def marketing_link(name):
    """Returns the correct URL for a link to the marketing site
    depending on if the marketing site is enabled

    Since the marketing site is enabled by a setting, we have two
    possible URLs for certain links. This function is to decides
    which URL should be provided.
    """
    # link_map maps URLs from the marketing site to the old equivalent on
    # the Django site
    link_map = settings.MKTG_URL_LINK_MAP
    enable_mktg_site = configuration_helpers.get_value(
        'ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False))
    marketing_urls = configuration_helpers.get_value('MKTG_URLS',
                                                     settings.MKTG_URLS)
    marketing_url_overrides = configuration_helpers.get_value(
        'MKTG_URL_OVERRIDES', settings.MKTG_URL_OVERRIDES)

    if name in marketing_url_overrides:
        validate = URLValidator()
        url = marketing_url_overrides.get(name)
        try:
            validate(url)
            return url
        except ValidationError as err:
            log.debug("Invalid link set for link %s: %s", name, err)
            return '#'

    if enable_mktg_site and name in marketing_urls:
        # special case for when we only want the root marketing URL
        if name == 'ROOT':
            return marketing_urls.get('ROOT')
        # special case for new enterprise marketing url with custom tracking query params
        if name == 'ENTERPRISE':
            enterprise_url = marketing_urls.get(name)
            # if url is not relative, then return it without joining to root
            if not enterprise_url.startswith('/'):
                return enterprise_url
        # Using urljoin here allows us to enable a marketing site and set
        # a site ROOT, but still specify absolute URLs for other marketing
        # URLs in the MKTG_URLS setting
        # e.g. urljoin('https://marketing.com', 'https://open-edx.org/about') >>> 'https://open-edx.org/about'
        return urljoin(marketing_urls.get('ROOT'), marketing_urls.get(name))
    # only link to the old pages when the marketing site isn't on
    elif not enable_mktg_site and name in link_map:
        # don't try to reverse disabled marketing links
        if link_map[name] is not None:
            host_name = get_current_request_hostname()
            if link_map[name].startswith('http'):
                return link_map[name]
            else:
                try:
                    return reverse(link_map[name])
                except NoReverseMatch:
                    log.debug(u"Cannot find corresponding link for name: %s",
                              name)
                    set_custom_attribute('unresolved_marketing_link', name)
                    return '#'
    else:
        log.debug(u"Cannot find corresponding link for name: %s", name)
        return '#'
Exemple #60
0
 def ecommerce_url_root(self):
     """ Retrieve Ecommerce service public url root. """
     return configuration_helpers.get_value('ECOMMERCE_PUBLIC_URL_ROOT', settings.ECOMMERCE_PUBLIC_URL_ROOT)