Makes logistration available at /login and /register as well as /accounts/login/ and /accounts/register/. In addition: - Adds support for redirect URLs in third party auth for combined login/registration page - Adds support for external auth on the combined login/registration page - Removes old login and registration acceptance tests - Adds deprecation warnings to old login and register views - Moves third party auth util to student_account - Adds exception for microsites (theming)
175 lines
7.7 KiB
Python
175 lines
7.7 KiB
Python
"""Helpers for the student app. """
|
|
import time
|
|
from datetime import datetime
|
|
from pytz import UTC
|
|
from django.utils.http import cookie_date
|
|
from django.conf import settings
|
|
from verify_student.models import SoftwareSecurePhotoVerification # pylint: disable=F0401
|
|
from course_modes.models import CourseMode
|
|
from student_account.helpers import auth_pipeline_urls # pylint: disable=unused-import,import-error
|
|
|
|
|
|
def set_logged_in_cookie(request, response):
|
|
"""Set a cookie indicating that the user is logged in.
|
|
|
|
Some installations have an external marketing site configured
|
|
that displays a different UI when the user is logged in
|
|
(e.g. a link to the student dashboard instead of to the login page)
|
|
|
|
Arguments:
|
|
request (HttpRequest): The request to the view, used to calculate
|
|
the cookie's expiration date based on the session expiration date.
|
|
response (HttpResponse): The response on which the cookie will be set.
|
|
|
|
Returns:
|
|
HttpResponse
|
|
|
|
"""
|
|
if request.session.get_expire_at_browser_close():
|
|
max_age = None
|
|
expires = None
|
|
else:
|
|
max_age = request.session.get_expiry_age()
|
|
expires_time = time.time() + max_age
|
|
expires = cookie_date(expires_time)
|
|
|
|
response.set_cookie(
|
|
settings.EDXMKTG_COOKIE_NAME, 'true', max_age=max_age,
|
|
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
|
|
path='/', secure=None, httponly=None,
|
|
)
|
|
|
|
return response
|
|
|
|
|
|
def is_logged_in_cookie_set(request):
|
|
"""Check whether the request has the logged in cookie set. """
|
|
return settings.EDXMKTG_COOKIE_NAME in request.COOKIES
|
|
|
|
|
|
# Enumeration of per-course verification statuses
|
|
# we display on the student dashboard.
|
|
VERIFY_STATUS_NEED_TO_VERIFY = "verify_need_to_verify"
|
|
VERIFY_STATUS_SUBMITTED = "verify_submitted"
|
|
VERIFY_STATUS_APPROVED = "verify_approved"
|
|
VERIFY_STATUS_MISSED_DEADLINE = "verify_missed_deadline"
|
|
|
|
|
|
def check_verify_status_by_course(user, course_enrollment_pairs, all_course_modes):
|
|
"""Determine the per-course verification statuses for a given user.
|
|
|
|
The possible statuses are:
|
|
* VERIFY_STATUS_NEED_TO_VERIFY: The student has not yet submitted photos for verification.
|
|
* VERIFY_STATUS_SUBMITTED: The student has submitted photos for verification,
|
|
but has have not yet been approved.
|
|
* VERIFY_STATUS_APPROVED: The student has been successfully verified.
|
|
* VERIFY_STATUS_MISSED_DEADLINE: The student did not submit photos within the course's deadline.
|
|
|
|
It is is also possible that a course does NOT have a verification status if:
|
|
* The user is not enrolled in a verified mode, meaning that the user didn't pay.
|
|
* The course does not offer a verified mode.
|
|
* The user submitted photos but an error occurred while verifying them.
|
|
* The user submitted photos but the verification was denied.
|
|
|
|
In the last two cases, we rely on messages in the sidebar rather than displaying
|
|
messages for each course.
|
|
|
|
Arguments:
|
|
user (User): The currently logged-in user.
|
|
course_enrollment_pairs (list): The courses the user is enrolled in.
|
|
The list should contain tuples of `(Course, CourseEnrollment)`.
|
|
all_course_modes (list): List of all course modes for the student's enrolled courses,
|
|
including modes that have expired.
|
|
|
|
Returns:
|
|
dict: Mapping of course keys verification status dictionaries.
|
|
If no verification status is applicable to a course, it will not
|
|
be included in the dictionary.
|
|
The dictionaries have these keys:
|
|
* status (str): One of the enumerated status codes.
|
|
* days_until_deadline (int): Number of days until the verification deadline.
|
|
* verification_good_until (str): Date string for the verification expiration date.
|
|
|
|
"""
|
|
status_by_course = {}
|
|
|
|
# Retrieve all verifications for the user, sorted in descending
|
|
# order by submission datetime
|
|
verifications = SoftwareSecurePhotoVerification.objects.filter(user=user)
|
|
|
|
# Check whether the user has an active or pending verification attempt
|
|
# To avoid another database hit, we re-use the queryset we have already retrieved.
|
|
has_active_or_pending = SoftwareSecurePhotoVerification.user_has_valid_or_pending(
|
|
user, queryset=verifications
|
|
)
|
|
|
|
for course, enrollment in course_enrollment_pairs:
|
|
|
|
# Get the verified mode (if any) for this course
|
|
# We pass in the course modes we have already loaded to avoid
|
|
# another database hit, as well as to ensure that expired
|
|
# course modes are included in the search.
|
|
verified_mode = CourseMode.verified_mode_for_course(
|
|
course.id,
|
|
modes=all_course_modes[course.id]
|
|
)
|
|
|
|
# If no verified mode has ever been offered, or the user hasn't enrolled
|
|
# as verified, then the course won't display state related to its
|
|
# verification status.
|
|
if verified_mode is not None and enrollment.mode in CourseMode.VERIFIED_MODES:
|
|
deadline = verified_mode.expiration_datetime
|
|
relevant_verification = SoftwareSecurePhotoVerification.verification_for_datetime(deadline, verifications)
|
|
|
|
# By default, don't show any status related to verification
|
|
status = None
|
|
|
|
# Check whether the user was approved or is awaiting approval
|
|
if relevant_verification is not None:
|
|
if relevant_verification.status == "approved":
|
|
status = VERIFY_STATUS_APPROVED
|
|
elif relevant_verification.status == "submitted":
|
|
status = VERIFY_STATUS_SUBMITTED
|
|
|
|
# If the user didn't submit at all, then tell them they need to verify
|
|
# If the deadline has already passed, then tell them they missed it.
|
|
# If they submitted but something went wrong (error or denied),
|
|
# then don't show any messaging next to the course, since we already
|
|
# show messages related to this on the left sidebar.
|
|
submitted = (
|
|
relevant_verification is not None and
|
|
relevant_verification.status not in ["created", "ready"]
|
|
)
|
|
if status is None and not submitted:
|
|
if deadline is None or deadline > datetime.now(UTC):
|
|
# If a user already has an active or pending verification,
|
|
# but it will expire by the deadline, the we do NOT show the
|
|
# verification message. This is because we don't currently
|
|
# allow users to resubmit an attempt before their current
|
|
# attempt expires.
|
|
if not has_active_or_pending:
|
|
status = VERIFY_STATUS_NEED_TO_VERIFY
|
|
else:
|
|
status = VERIFY_STATUS_MISSED_DEADLINE
|
|
|
|
# Set the status for the course only if we're displaying some kind of message
|
|
# Otherwise, leave the course out of the dictionary.
|
|
if status is not None:
|
|
days_until_deadline = None
|
|
verification_good_until = None
|
|
|
|
now = datetime.now(UTC)
|
|
if deadline is not None and deadline > now:
|
|
days_until_deadline = (deadline - now).days
|
|
|
|
if relevant_verification is not None:
|
|
verification_good_until = relevant_verification.expiration_datetime.strftime("%m/%d/%Y")
|
|
|
|
status_by_course[course.id] = {
|
|
'status': status,
|
|
'days_until_deadline': days_until_deadline,
|
|
'verification_good_until': verification_good_until
|
|
}
|
|
|
|
return status_by_course
|