示例#1
0
    def save(self, retirement):
        """
        When the form is POSTed we double-check the retirment status
        and perform the necessary steps to cancel the retirement
        request.
        """
        if retirement.current_state.state_name != 'PENDING':
            self.add_error(
                None,
                # Translators: 'current_state' is a string from an enumerated list indicating the learner's retirement
                # state. Example: FORUMS_COMPLETE
                u"Retirement requests can only be cancelled for users in the PENDING state."
                u" Current request state for '{original_username}': {current_state}".format(
                    original_username=retirement.original_username,
                    current_state=retirement.current_state.state_name
                )
            )
            raise ValidationError('Retirement is in the wrong state!')

        # Load the user record using the retired email address -and- change the email address back.
        retirement.user.email = retirement.original_email
        # Reset users password so they can request a password reset and log in again.
        retirement.user.set_password(generate_password(length=25))
        retirement.user.save()

        # Delete the user retirement status record.
        # No need to delete the accompanying "permanent" retirement request record - it gets done via Django signal.
        retirement.delete()
示例#2
0
    def handle(self, *args, **options):
        """
        Execute the command.
        """
        email_address = options['email_address'].lower()

        try:
            # Load the user retirement status.
            retirement_status = UserRetirementStatus.objects.select_related('current_state').select_related('user').get(
                original_email=email_address
            )
        except UserRetirementStatus.DoesNotExist:
            raise CommandError(u"No retirement request with email address '{}' exists.".format(email_address))

        # Check if the user has started the retirement process -or- not.
        if retirement_status.current_state.state_name != 'PENDING':
            raise CommandError(
                u"Retirement requests can only be cancelled for users in the PENDING state."
                u" Current request state for '{}': {}".format(
                    email_address,
                    retirement_status.current_state.state_name
                )
            )

        # Load the user record using the retired email address -and- change the email address back.
        retirement_status.user.email = email_address
        retirement_status.user.set_password(generate_password(length=25))
        retirement_status.user.save()

        # Delete the user retirement status record.
        # No need to delete the accompanying "permanent" retirement request record - it gets done via Django signal.
        retirement_status.delete()

        print(u"Successfully cancelled retirement request for user with email address '{}'.".format(email_address))
示例#3
0
    def test_unusable_password(self):
        """
        Ensure that a user's password is set to an unusable_password.
        """
        user = User.objects.create(username=TEST_USERNAME, email=TEST_EMAIL)
        self.assertEqual([(TEST_USERNAME, TEST_EMAIL)],
                         [(u.username, u.email) for u in User.objects.all()])
        user.set_password(generate_password())
        user.save()

        # Run once without passing --unusable-password and make sure the password is usable
        call_command('manage_user', TEST_USERNAME, TEST_EMAIL)
        user = User.objects.get(username=TEST_USERNAME, email=TEST_EMAIL)
        self.assertTrue(user.has_usable_password())

        # Make sure the user now has an unusable_password
        call_command('manage_user', TEST_USERNAME, TEST_EMAIL,
                     '--unusable-password')
        user = User.objects.get(username=TEST_USERNAME, email=TEST_EMAIL)
        self.assertFalse(user.has_usable_password())

        # check idempotency
        call_command('manage_user', TEST_USERNAME, TEST_EMAIL,
                     '--unusable-password')
        self.assertFalse(user.has_usable_password())
示例#4
0
def register_user_from_mobile_request(request, user_data):
    params = user_data.copy()
    params = _add_extras_in_params(params)
    extra_fields = configuration_helpers.get_value(
        'REGISTRATION_EXTRA_FIELDS',
        getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {})
    )

    params["password"] = generate_password()

    extended_profile_fields = configuration_helpers.get_value('extended_profile_fields', [])

    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)

    (user, profile, registration) = do_create_account(form, custom_form, True)

    new_user = authenticate_new_user(request, user.username, params['password'])

    registration.activate()

    # 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
            LOGGER.exception("Enable discussion notifications failed for user {id}.".format(id=user.id))

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

    create_comments_service_user(user)

    return new_user
    def post(self, request, username_or_email):
        """Allows support staff to disable a user's account."""
        user = get_user_model().objects.get(
            Q(username=username_or_email) | Q(email=username_or_email))
        if user.has_usable_password():
            user.set_unusable_password()
        else:
            user.set_password(generate_password(length=25))
        user.save()

        if user.has_usable_password():
            password_status = _('Usable')
            msg = _('User Enabled Successfully')
        else:
            password_status = _('Unusable')
            msg = _('User Disabled Successfully')
        return JsonResponse({'success_msg': msg, 'status': password_status})
示例#6
0
    def post(self, request, username_or_email):
        """Allows support staff to disable a user's account."""
        user = get_user_model().objects.get(
            Q(username=username_or_email) | Q(email=username_or_email)
        )
        if user.has_usable_password():
            user.set_unusable_password()
        else:
            user.set_password(generate_password(length=25))
        user.save()

        if user.has_usable_password():
            password_status = _('Usable')
            msg = _('User Enabled Successfully')
        else:
            password_status = _('Unusable')
            msg = _('User Disabled Successfully')
        return JsonResponse({'success_msg': msg, 'status': password_status})
    def test_unusable_password(self):
        """
        Ensure that a user's password is set to an unusable_password.
        """
        user = User.objects.create(username=TEST_USERNAME, email=TEST_EMAIL)
        self.assertEqual([(TEST_USERNAME, TEST_EMAIL)], [(u.username, u.email) for u in User.objects.all()])
        user.set_password(generate_password())
        user.save()

        # Run once without passing --unusable-password and make sure the password is usable
        call_command('manage_user', TEST_USERNAME, TEST_EMAIL)
        user = User.objects.get(username=TEST_USERNAME, email=TEST_EMAIL)
        self.assertTrue(user.has_usable_password())

        # Make sure the user now has an unusable_password
        call_command('manage_user', TEST_USERNAME, TEST_EMAIL, '--unusable-password')
        user = User.objects.get(username=TEST_USERNAME, email=TEST_EMAIL)
        self.assertFalse(user.has_usable_password())

        # check idempotency
        call_command('manage_user', TEST_USERNAME, TEST_EMAIL, '--unusable-password')
        self.assertFalse(user.has_usable_password())
示例#8
0
    def create_user(self, uname, name, password=None):
        """ Creates a user (both SSL and regular)"""

        if not uname:
            return _('Must provide username')
        if not name:
            return _('Must provide full name')

        email_domain = getattr(settings, 'SSL_AUTH_EMAIL_DOMAIN', 'MIT.EDU')

        msg = u''
        if settings.FEATURES['AUTH_USE_CERTIFICATES']:
            if '@' not in uname:
                email = '{0}@{1}'.format(uname, email_domain)
            else:
                email = uname
            if not email.endswith('@{0}'.format(email_domain)):
                # Translators: Domain is an email domain, such as "@gmail.com"
                msg += _('Email address must end in {domain}').format(
                    domain="@{0}".format(email_domain))
                return msg
            mit_domain = 'ssl:MIT'
            if ExternalAuthMap.objects.filter(external_id=email,
                                              external_domain=mit_domain):
                msg += _(
                    'Failed - email {email_addr} already exists as {external_id}'
                ).format(email_addr=email, external_id="external_id")
                return msg
            new_password = generate_password()
        else:
            if not password:
                return _('Password must be supplied if not using certificates')

            email = uname

            if '@' not in email:
                msg += _('email address required (not username)')
                return msg
            new_password = password

        user = User(username=uname, email=email, is_active=True)
        user.set_password(new_password)
        try:
            user.save()
        except IntegrityError:
            msg += _('Oops, failed to create user {user}, {error}').format(
                user=user, error="IntegrityError")
            return msg

        reg = Registration()
        reg.register(user)

        profile = UserProfile(user=user)
        profile.name = name
        profile.save()

        if settings.FEATURES['AUTH_USE_CERTIFICATES']:
            credential_string = getattr(
                settings, 'SSL_AUTH_DN_FORMAT_STRING',
                '/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN={0}/emailAddress={1}'
            )
            credentials = credential_string.format(name, email)
            eamap = ExternalAuthMap(
                external_id=email,
                external_email=email,
                external_domain=mit_domain,
                external_name=name,
                internal_password=new_password,
                external_credentials=json.dumps(credentials),
            )
            eamap.user = user
            eamap.dtsignup = timezone.now()
            eamap.save()

        msg += _('User {user} created successfully!').format(user=user)
        return msg
示例#9
0
def auto_auth(request):
    """
    Create or configure a user account, then log in as that user.

    Enabled only when
    settings.FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] is true.

    Accepts the following querystring parameters:
    * `username`, `email`, and `password` for the user account
    * `full_name` for the user profile (the user's full name; defaults to the username)
    * `staff`: Set to "true" to make the user global staff.
    * `course_id`: Enroll the student in the course with `course_id`
    * `roles`: Comma-separated list of roles to grant the student in the course with `course_id`
    * `no_login`: Define this to create the user but not login
    * `redirect`: Set to "true" will redirect to the `redirect_to` value if set, or
        course home page if course_id is defined, otherwise it will redirect to dashboard
    * `redirect_to`: will redirect to to this url
    * `is_active` : make/update account with status provided as 'is_active'
    If username, email, or password are not provided, use
    randomly generated credentials.
    """

    # Generate a unique name to use if none provided
    generated_username = uuid.uuid4().hex[0:30]
    generated_password = generate_password()

    # Use the params from the request, otherwise use these defaults
    username = request.GET.get('username', generated_username)
    password = request.GET.get('password', generated_password)
    email = request.GET.get('email', username + "@example.com")
    full_name = request.GET.get('full_name', username)
    is_staff = str2bool(request.GET.get('staff', False))
    is_superuser = str2bool(request.GET.get('superuser', False))
    course_id = request.GET.get('course_id')
    redirect_to = request.GET.get('redirect_to')
    is_active = str2bool(request.GET.get('is_active', True))

    # Valid modes: audit, credit, honor, no-id-professional, professional, verified
    enrollment_mode = request.GET.get('enrollment_mode', 'honor')

    # Parse roles, stripping whitespace, and filtering out empty strings
    roles = _clean_roles(request.GET.get('roles', '').split(','))
    course_access_roles = _clean_roles(request.GET.get('course_access_roles', '').split(','))

    redirect_when_done = str2bool(request.GET.get('redirect', '')) or redirect_to
    login_when_done = 'no_login' not in request.GET

    restricted = settings.FEATURES.get('RESTRICT_AUTOMATIC_AUTH', True)
    if is_superuser and restricted:
        return HttpResponseForbidden(_('Superuser creation not allowed'))

    form = AccountCreationForm(
        data={
            'username': username,
            'email': email,
            'password': password,
            'name': full_name,
        },
        tos_required=False
    )

    # Attempt to create the account.
    # If successful, this will return a tuple containing
    # the new user object.
    try:
        user, profile, reg = do_create_account(form)
    except (AccountValidationError, ValidationError):
        if restricted:
            return HttpResponseForbidden(_('Account modification not allowed.'))
        # Attempt to retrieve the existing user.
        user = User.objects.get(username=username)
        user.email = email
        user.set_password(password)
        user.is_active = is_active
        user.save()
        profile = UserProfile.objects.get(user=user)
        reg = Registration.objects.get(user=user)
    except PermissionDenied:
        return HttpResponseForbidden(_('Account creation not allowed.'))

    user.is_staff = is_staff
    user.is_superuser = is_superuser
    user.save()

    if is_active:
        reg.activate()
        reg.save()

    # ensure parental consent threshold is met
    year = datetime.date.today().year
    age_limit = settings.PARENTAL_CONSENT_AGE_LIMIT
    profile.year_of_birth = (year - age_limit) - 1
    profile.save()

    create_or_set_user_attribute_created_on_site(user, request.site)

    # Enroll the user in a course
    course_key = None
    if course_id:
        course_key = CourseLocator.from_string(course_id)
        CourseEnrollment.enroll(user, course_key, mode=enrollment_mode)

        # Apply the roles
        for role in roles:
            assign_role(course_key, user, role)

        for role in course_access_roles:
            CourseAccessRole.objects.update_or_create(user=user, course_id=course_key, org=course_key.org, role=role)

    # Log in as the user
    if login_when_done:
        user = authenticate_new_user(request, username, password)
        django_login(request, user)

    create_comments_service_user(user)

    if redirect_when_done:
        if redirect_to:
            # Redirect to page specified by the client
            redirect_url = redirect_to
        elif course_id:
            # Redirect to the course homepage (in LMS) or outline page (in Studio)
            try:
                redirect_url = reverse(course_home_url_name(course_key), kwargs={'course_id': course_id})
            except NoReverseMatch:
                redirect_url = reverse('course_handler', kwargs={'course_key_string': course_id})
        else:
            # Redirect to the learner dashboard (in LMS) or homepage (in Studio)
            try:
                redirect_url = reverse('dashboard')
            except NoReverseMatch:
                redirect_url = reverse('home')

        return redirect(redirect_url)
    else:
        response = JsonResponse({
            'created_status': 'Logged in' if login_when_done else 'Created',
            'username': username,
            'email': email,
            'password': password,
            'user_id': user.id,  # pylint: disable=no-member
            'anonymous_id': anonymous_id_for_user(user, None),
        })
    response.set_cookie('csrftoken', csrf(request)['csrf_token'])
    return response
示例#10
0
def _external_login_or_signup(request,
                              external_id,
                              external_domain,
                              credentials,
                              email,
                              fullname,
                              retfun=None):
    """
    Generic external auth login or signup
    """
    # pylint: disable=too-many-statements
    # see if we have a map from this external_id to an edX username
    eamap_defaults = {
        'external_credentials': json.dumps(credentials),
        'external_email': email,
        'external_name': fullname,
        'internal_password': generate_password()
    }

    # We are not guaranteed to be in a transaction here since some upstream views
    # use non_atomic_requests
    with outer_atomic():
        eamap, created = ExternalAuthMap.objects.get_or_create(
            external_id=external_id,
            external_domain=external_domain,
            defaults=eamap_defaults)

    if created:
        log.debug(u'Created eamap=%s', eamap)
    else:
        log.debug(u'Found eamap=%s', eamap)

    log.info(u"External_Auth login_or_signup for %s : %s : %s : %s",
             external_domain, external_id, email, fullname)
    uses_shibboleth = settings.FEATURES.get(
        'AUTH_USE_SHIB') and external_domain.startswith(
            SHIBBOLETH_DOMAIN_PREFIX)
    uses_certs = settings.FEATURES.get('AUTH_USE_CERTIFICATES')
    internal_user = eamap.user
    if internal_user is None:
        if uses_shibboleth:
            # If we are using shib, try to link accounts
            # For Stanford shib, the email the idp returns is actually under the control of the user.
            # Since the id the idps return is not user-editable, and is of the from "*****@*****.**",
            # use the id to link accounts instead.
            try:
                with outer_atomic():
                    link_user = User.objects.get(email=eamap.external_id)
                    if not ExternalAuthMap.objects.filter(
                            user=link_user).exists():
                        # if there's no pre-existing linked eamap, we link the user
                        eamap.user = link_user
                        eamap.save()
                        internal_user = link_user
                        log.info(u'SHIB: Linking existing account for %s',
                                 eamap.external_id)
                        # now pass through to log in
                    else:
                        # otherwise, there must have been an error, b/c we've already linked a user with these external
                        # creds
                        failure_msg = _(
                            "You have already created an account using "
                            "an external login like WebAuth or Shibboleth. "
                            "Please contact {tech_support_email} for support."
                        ).format(tech_support_email=get_value(
                            'email_from_address',
                            settings.TECH_SUPPORT_EMAIL), )
                        return default_render_failure(request, failure_msg)
            except User.DoesNotExist:
                log.info(u'SHIB: No user for %s yet, doing signup',
                         eamap.external_email)
                return _signup(request, eamap, retfun)
        else:
            log.info(u'No user for %s yet. doing signup', eamap.external_email)
            return _signup(request, eamap, retfun)

    # We trust shib's authentication, so no need to authenticate using the password again
    uname = internal_user.username

    if uses_shibboleth:
        user = internal_user
        # Assuming this 'AUTHENTICATION_BACKENDS' is set in settings, which I think is safe
        if settings.AUTHENTICATION_BACKENDS:
            auth_backend = settings.AUTHENTICATION_BACKENDS[0]
        else:
            auth_backend = 'ratelimitbackend.backends.RateLimitModelBackend'
        user.backend = auth_backend
        if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
            AUDIT_LOG.info(
                u'Linked user.id: {0} logged in via Shibboleth'.format(
                    user.id))
        else:
            AUDIT_LOG.info(
                u'Linked user "{0}" logged in via Shibboleth'.format(
                    user.email))
    elif uses_certs:
        # Certificates are trusted, so just link the user and log the action
        user = internal_user
        user.backend = 'ratelimitbackend.backends.RateLimitModelBackend'
        if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
            AUDIT_LOG.info(
                u'Linked user_id {0} logged in via SSL certificate'.format(
                    user.id))
        else:
            AUDIT_LOG.info(
                u'Linked user "{0}" logged in via SSL certificate'.format(
                    user.email))
    else:
        user = authenticate(username=uname,
                            password=eamap.internal_password,
                            request=request)

    if user is None:
        # we want to log the failure, but don't want to log the password attempted:
        if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
            AUDIT_LOG.warning(u'External Auth Login failed')
        else:
            AUDIT_LOG.warning(
                u'External Auth Login failed for "{0}"'.format(uname))
        return _signup(request, eamap, retfun)

    if not user.is_active:
        if settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
            # if BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH, we trust external auth and activate any users
            # that aren't already active
            user.is_active = True
            user.save()
            if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
                AUDIT_LOG.info(
                    u'Activating user {0} due to external auth'.format(
                        user.id))
            else:
                AUDIT_LOG.info(
                    u'Activating user "{0}" due to external auth'.format(
                        uname))
        else:
            if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
                AUDIT_LOG.warning(
                    u'User {0} is not active after external login'.format(
                        user.id))
            else:
                AUDIT_LOG.warning(
                    u'User "{0}" is not active after external login'.format(
                        uname))
            # TODO: improve error page
            msg = 'Account not yet activated: please look for link in your email'
            return default_render_failure(request, msg)

    login(request, user)
    request.session.set_expiry(0)

    if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
        AUDIT_LOG.info(u"Login success - user.id: {0}".format(user.id))
    else:
        AUDIT_LOG.info(u"Login success - {0} ({1})".format(
            user.username, user.email))
    if retfun is None:
        return redirect('/')
    return retfun()
示例#11
0
    def create_user(self, uname, name, password=None):
        """ Creates a user (both SSL and regular)"""

        if not uname:
            return _('Must provide username')
        if not name:
            return _('Must provide full name')

        email_domain = getattr(settings, 'SSL_AUTH_EMAIL_DOMAIN', 'MIT.EDU')

        msg = u''
        if settings.FEATURES['AUTH_USE_CERTIFICATES']:
            if '@' not in uname:
                email = '{0}@{1}'.format(uname, email_domain)
            else:
                email = uname
            if not email.endswith('@{0}'.format(email_domain)):
                # Translators: Domain is an email domain, such as "@gmail.com"
                msg += _('Email address must end in {domain}').format(domain="@{0}".format(email_domain))
                return msg
            mit_domain = 'ssl:MIT'
            if ExternalAuthMap.objects.filter(external_id=email,
                                              external_domain=mit_domain):
                msg += _('Failed - email {email_addr} already exists as {external_id}').format(
                    email_addr=email,
                    external_id="external_id"
                )
                return msg
            new_password = generate_password()
        else:
            if not password:
                return _('Password must be supplied if not using certificates')

            email = uname

            if '@' not in email:
                msg += _('email address required (not username)')
                return msg
            new_password = password

        user = User(username=uname, email=email, is_active=True)
        user.set_password(new_password)
        try:
            user.save()
        except IntegrityError:
            msg += _('Oops, failed to create user {user}, {error}').format(
                user=user,
                error="IntegrityError"
            )
            return msg

        reg = Registration()
        reg.register(user)

        profile = UserProfile(user=user)
        profile.name = name
        profile.save()

        if settings.FEATURES['AUTH_USE_CERTIFICATES']:
            credential_string = getattr(settings, 'SSL_AUTH_DN_FORMAT_STRING',
                                        '/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN={0}/emailAddress={1}')
            credentials = credential_string.format(name, email)
            eamap = ExternalAuthMap(
                external_id=email,
                external_email=email,
                external_domain=mit_domain,
                external_name=name,
                internal_password=new_password,
                external_credentials=json.dumps(credentials),
            )
            eamap.user = user
            eamap.dtsignup = timezone.now()
            eamap.save()

        msg += _('User {user} created successfully!').format(user=user)
        return msg
示例#12
0
def _external_login_or_signup(request,
                              external_id,
                              external_domain,
                              credentials,
                              email,
                              fullname,
                              retfun=None):
    """
    Generic external auth login or signup
    """
    # pylint: disable=too-many-statements
    # see if we have a map from this external_id to an edX username
    eamap_defaults = {
        'external_credentials': json.dumps(credentials),
        'external_email': email,
        'external_name': fullname,
        'internal_password': generate_password()
    }

    # We are not guaranteed to be in a transaction here since some upstream views
    # use non_atomic_requests
    with outer_atomic():
        eamap, created = ExternalAuthMap.objects.get_or_create(
            external_id=external_id,
            external_domain=external_domain,
            defaults=eamap_defaults
        )

    if created:
        log.debug(u'Created eamap=%s', eamap)
    else:
        log.debug(u'Found eamap=%s', eamap)

    log.info(u"External_Auth login_or_signup for %s : %s : %s : %s", external_domain, external_id, email, fullname)
    uses_shibboleth = settings.FEATURES.get('AUTH_USE_SHIB') and external_domain.startswith(SHIBBOLETH_DOMAIN_PREFIX)
    uses_certs = settings.FEATURES.get('AUTH_USE_CERTIFICATES')
    internal_user = eamap.user
    if internal_user is None:
        if uses_shibboleth:
            # If we are using shib, try to link accounts
            # For Stanford shib, the email the idp returns is actually under the control of the user.
            # Since the id the idps return is not user-editable, and is of the from "*****@*****.**",
            # use the id to link accounts instead.
            try:
                with outer_atomic():
                    link_user = User.objects.get(email=eamap.external_id)
                    if not ExternalAuthMap.objects.filter(user=link_user).exists():
                        # if there's no pre-existing linked eamap, we link the user
                        eamap.user = link_user
                        eamap.save()
                        internal_user = link_user
                        log.info(u'SHIB: Linking existing account for %s', eamap.external_id)
                        # now pass through to log in
                    else:
                        # otherwise, there must have been an error, b/c we've already linked a user with these external
                        # creds
                        failure_msg = _(
                            "You have already created an account using "
                            "an external login like WebAuth or Shibboleth. "
                            "Please contact {tech_support_email} for support."
                        ).format(
                            tech_support_email=get_value('email_from_address', settings.TECH_SUPPORT_EMAIL),
                        )
                        return default_render_failure(request, failure_msg)
            except User.DoesNotExist:
                log.info(u'SHIB: No user for %s yet, doing signup', eamap.external_email)
                return _signup(request, eamap, retfun)
        else:
            log.info(u'No user for %s yet. doing signup', eamap.external_email)
            return _signup(request, eamap, retfun)

    # We trust shib's authentication, so no need to authenticate using the password again
    uname = internal_user.username

    if uses_shibboleth:
        user = internal_user
        # Assuming this 'AUTHENTICATION_BACKENDS' is set in settings, which I think is safe
        if settings.AUTHENTICATION_BACKENDS:
            auth_backend = settings.AUTHENTICATION_BACKENDS[0]
        else:
            auth_backend = 'ratelimitbackend.backends.RateLimitModelBackend'
        user.backend = auth_backend
        if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
            AUDIT_LOG.info(u'Linked user.id: {0} logged in via Shibboleth'.format(user.id))
        else:
            AUDIT_LOG.info(u'Linked user "{0}" logged in via Shibboleth'.format(user.email))
    elif uses_certs:
        # Certificates are trusted, so just link the user and log the action
        user = internal_user
        user.backend = 'ratelimitbackend.backends.RateLimitModelBackend'
        if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
            AUDIT_LOG.info(u'Linked user_id {0} logged in via SSL certificate'.format(user.id))
        else:
            AUDIT_LOG.info(u'Linked user "{0}" logged in via SSL certificate'.format(user.email))
    else:
        user = authenticate(username=uname, password=eamap.internal_password, request=request)

    if user is None:
        # we want to log the failure, but don't want to log the password attempted:
        if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
            AUDIT_LOG.warning(u'External Auth Login failed')
        else:
            AUDIT_LOG.warning(u'External Auth Login failed for "{0}"'.format(uname))
        return _signup(request, eamap, retfun)

    if not user.is_active:
        if settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
            # if BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH, we trust external auth and activate any users
            # that aren't already active
            user.is_active = True
            user.save()
            if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
                AUDIT_LOG.info(u'Activating user {0} due to external auth'.format(user.id))
            else:
                AUDIT_LOG.info(u'Activating user "{0}" due to external auth'.format(uname))
        else:
            if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
                AUDIT_LOG.warning(u'User {0} is not active after external login'.format(user.id))
            else:
                AUDIT_LOG.warning(u'User "{0}" is not active after external login'.format(uname))
            # TODO: improve error page
            msg = 'Account not yet activated: please look for link in your email'
            return default_render_failure(request, msg)

    login(request, user)
    request.session.set_expiry(0)

    if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
        AUDIT_LOG.info(u"Login success - user.id: {0}".format(user.id))
    else:
        AUDIT_LOG.info(u"Login success - {0} ({1})".format(user.username, user.email))
    if retfun is None:
        return redirect('/')
    return retfun()
    def handle(self, username, email, is_remove, is_staff, is_superuser,
               groups, unusable_password, initial_password_hash, *args,
               **options):

        if is_remove:
            return self._handle_remove(username, email)

        old_groups, new_groups = set(), set()
        user, created = get_user_model().objects.get_or_create(
            username=username, defaults={'email': email})

        if created:
            if initial_password_hash:
                if not is_password_usable(initial_password_hash):
                    raise CommandError(
                        'The password hash provided for user {} is invalid.'.
                        format(username))
                user.password = initial_password_hash
            else:
                # Set the password to a random, unknown, but usable password
                # allowing self-service password resetting.  Cases where unusable
                # passwords are required, should be explicit, and will be handled below.
                user.set_password(generate_password(length=25))
            self.stderr.write(_('Created new user: "******"').format(user))
        else:
            # NOTE, we will not update the email address of an existing user.
            self.stderr.write(_('Found existing user: "******"').format(user))
            self._check_email_match(user, email)
            old_groups = set(user.groups.all())

        self._maybe_update(user, 'is_staff', is_staff)
        self._maybe_update(user, 'is_superuser', is_superuser)

        # Set unusable password if specified
        if unusable_password and user.has_usable_password():
            self.stderr.write(
                _('Setting unusable password for user "{}"').format(user))
            user.set_unusable_password()

        # Ensure the user has a profile
        try:
            __ = user.profile
        except UserProfile.DoesNotExist:
            UserProfile.objects.create(user=user)
            self.stderr.write(
                _('Created new profile for user: "******"').format(user))

        # resolve the specified groups
        for group_name in groups or set():

            try:
                group = Group.objects.get(name=group_name)  # pylint: disable=no-member
                new_groups.add(group)
            except Group.DoesNotExist:
                # warn, but move on.
                self.stderr.write(
                    _('Could not find a group named "{}" - skipping.').format(
                        group_name))

        add_groups = new_groups - old_groups
        remove_groups = old_groups - new_groups

        self.stderr.write(
            _('Adding user "{username}" to groups {group_names}').format(
                username=user.username,
                group_names=[g.name for g in add_groups]))
        self.stderr.write(
            _('Removing user "{username}" from groups {group_names}').format(
                username=user.username,
                group_names=[g.name for g in remove_groups]))

        user.groups = new_groups
        user.save()
示例#14
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'))
            ]}
        )

    do_external_auth, eamap = pre_account_creation_external_auth(request, params)

    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'
    ) and (
        not settings.FEATURES.get("AUTH_USE_SHIB") or
        not settings.FEATURES.get("SHIB_DISABLE_TOS") or
        not do_external_auth or
        not eamap.external_domain.startswith(settings.SHIBBOLETH_DOMAIN_PREFIX)
    )

    form = AccountCreationForm(
        data=params,
        extra_fields=extra_fields,
        extended_profile_fields=extended_profile_fields,
        do_third_party_auth=do_external_auth,
        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)

        post_account_creation_external_auth(do_external_auth, eamap, new_user)

    # Check if system is configured to skip activation email for the current user.
    skip_email = _skip_activation_email(
        user, do_external_auth, 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("Enable discussion notifications failed for user {id}.".format(id=user.id))

    dog_stats_api.increment("common.student.account_created")

    _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
示例#15
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'))
            ]}
        )

    do_external_auth, eamap = pre_account_creation_external_auth(request, params)

    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'
    ) and (
        not settings.FEATURES.get("AUTH_USE_SHIB") or
        not settings.FEATURES.get("SHIB_DISABLE_TOS") or
        not do_external_auth or
        not eamap.external_domain.startswith(settings.SHIBBOLETH_DOMAIN_PREFIX)
    )

    form = AccountCreationForm(
        data=params,
        extra_fields=extra_fields,
        extended_profile_fields=extended_profile_fields,
        do_third_party_auth=do_external_auth,
        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)

        post_account_creation_external_auth(do_external_auth, eamap, new_user)

    # Check if system is configured to skip activation email for the current user.
    skip_email = _skip_activation_email(
        user, do_external_auth, 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
示例#16
0
def auto_auth(request):
    """
    Create or configure a user account, then log in as that user.

    Enabled only when
    settings.FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] is true.

    Accepts the following querystring parameters:
    * `username`, `email`, and `password` for the user account
    * `full_name` for the user profile (the user's full name; defaults to the username)
    * `staff`: Set to "true" to make the user global staff.
    * `course_id`: Enroll the student in the course with `course_id`
    * `roles`: Comma-separated list of roles to grant the student in the course with `course_id`
    * `no_login`: Define this to create the user but not login
    * `redirect`: Set to "true" will redirect to the `redirect_to` value if set, or
        course home page if course_id is defined, otherwise it will redirect to dashboard
    * `redirect_to`: will redirect to to this url
    * `is_active` : make/update account with status provided as 'is_active'
    If username, email, or password are not provided, use
    randomly generated credentials.
    """

    # Generate a unique name to use if none provided
    generated_username = uuid.uuid4().hex[0:30]
    generated_password = generate_password()

    # Use the params from the request, otherwise use these defaults
    username = request.GET.get('username', generated_username)
    password = request.GET.get('password', generated_password)
    email = request.GET.get('email', username + "@example.com")
    full_name = request.GET.get('full_name', username)
    is_staff = str2bool(request.GET.get('staff', False))
    is_superuser = str2bool(request.GET.get('superuser', False))
    course_id = request.GET.get('course_id')
    redirect_to = request.GET.get('redirect_to')
    is_active = str2bool(request.GET.get('is_active', True))

    # Valid modes: audit, credit, honor, no-id-professional, professional, verified
    enrollment_mode = request.GET.get('enrollment_mode', 'honor')

    # Parse roles, stripping whitespace, and filtering out empty strings
    roles = _clean_roles(request.GET.get('roles', '').split(','))
    course_access_roles = _clean_roles(request.GET.get('course_access_roles', '').split(','))

    redirect_when_done = str2bool(request.GET.get('redirect', '')) or redirect_to
    login_when_done = 'no_login' not in request.GET

    form = AccountCreationForm(
        data={
            'username': username,
            'email': email,
            'password': password,
            'confirm_password': password,
            'name': full_name,
        },
        tos_required=False
    )

    # Attempt to create the account.
    # If successful, this will return a tuple containing
    # the new user object.
    try:
        user, profile, reg = do_create_account(form)
    except (AccountValidationError, ValidationError):
        # Attempt to retrieve the existing user.
        user = User.objects.get(username=username)
        user.email = email
        user.set_password(password)
        user.is_active = is_active
        user.save()
        profile = UserProfile.objects.get(user=user)
        reg = Registration.objects.get(user=user)
    except PermissionDenied:
        return HttpResponseForbidden(_('Account creation not allowed.'))

    user.is_staff = is_staff
    user.is_superuser = is_superuser
    user.save()

    if is_active:
        reg.activate()
        reg.save()

    # ensure parental consent threshold is met
    year = datetime.date.today().year
    age_limit = settings.PARENTAL_CONSENT_AGE_LIMIT
    profile.year_of_birth = (year - age_limit) - 1
    profile.save()

    create_or_set_user_attribute_created_on_site(user, request.site)

    # Enroll the user in a course
    course_key = None
    if course_id:
        course_key = CourseLocator.from_string(course_id)
        CourseEnrollment.enroll(user, course_key, mode=enrollment_mode)

        # Apply the roles
        for role in roles:
            assign_role(course_key, user, role)

        for role in course_access_roles:
            CourseAccessRole.objects.update_or_create(user=user, course_id=course_key, org=course_key.org, role=role)

    # Log in as the user
    if login_when_done:
        user = authenticate_new_user(request, username, password)
        django_login(request, user)

    create_comments_service_user(user)

    if redirect_when_done:
        if redirect_to:
            # Redirect to page specified by the client
            redirect_url = redirect_to
        elif course_id:
            # Redirect to the course homepage (in LMS) or outline page (in Studio)
            try:
                redirect_url = reverse(course_home_url_name(course_key), kwargs={'course_id': course_id})
            except NoReverseMatch:
                redirect_url = reverse('course_handler', kwargs={'course_key_string': course_id})
        else:
            # Redirect to the learner dashboard (in LMS) or homepage (in Studio)
            try:
                redirect_url = reverse('dashboard')
            except NoReverseMatch:
                redirect_url = reverse('home')

        return redirect(redirect_url)
    else:
        response = JsonResponse({
            'created_status': 'Logged in' if login_when_done else 'Created',
            'username': username,
            'email': email,
            'password': password,
            'user_id': user.id,  # pylint: disable=no-member
            'anonymous_id': anonymous_id_for_user(user, None),
        })
    response.set_cookie('csrftoken', csrf(request)['csrf_token'])
    return response
示例#17
0
    def handle(self, username, email, is_remove, is_staff, is_superuser, groups,
               unusable_password, initial_password_hash, *args, **options):

        if is_remove:
            return self._handle_remove(username, email)

        old_groups, new_groups = set(), set()
        user, created = get_user_model().objects.get_or_create(
            username=username,
            defaults={'email': email}
        )

        if created:
            if initial_password_hash:
                if not is_password_usable(initial_password_hash):
                    raise CommandError('The password hash provided for user {} is invalid.'.format(username))
                user.password = initial_password_hash
            else:
                # Set the password to a random, unknown, but usable password
                # allowing self-service password resetting.  Cases where unusable
                # passwords are required, should be explicit, and will be handled below.
                user.set_password(generate_password(length=25))
            self.stderr.write(_('Created new user: "******"').format(user))
        else:
            # NOTE, we will not update the email address of an existing user.
            self.stderr.write(_('Found existing user: "******"').format(user))
            self._check_email_match(user, email)
            old_groups = set(user.groups.all())

        self._maybe_update(user, 'is_staff', is_staff)
        self._maybe_update(user, 'is_superuser', is_superuser)

        # Set unusable password if specified
        if unusable_password and user.has_usable_password():
            self.stderr.write(_('Setting unusable password for user "{}"').format(user))
            user.set_unusable_password()

        # Ensure the user has a profile
        try:
            __ = user.profile
        except UserProfile.DoesNotExist:
            UserProfile.objects.create(user=user)
            self.stderr.write(_('Created new profile for user: "******"').format(user))

        # resolve the specified groups
        for group_name in groups or set():

            try:
                group = Group.objects.get(name=group_name)  # pylint: disable=no-member
                new_groups.add(group)
            except Group.DoesNotExist:
                # warn, but move on.
                self.stderr.write(_('Could not find a group named "{}" - skipping.').format(group_name))

        add_groups = new_groups - old_groups
        remove_groups = old_groups - new_groups

        self.stderr.write(
            _(
                'Adding user "{username}" to groups {group_names}'
            ).format(
                username=user.username,
                group_names=[g.name for g in add_groups]
            )
        )
        self.stderr.write(
            _(
                'Removing user "{username}" from groups {group_names}'
            ).format(
                username=user.username,
                group_names=[g.name for g in remove_groups]
            )
        )

        user.groups = new_groups
        user.save()