def configure_authenticator(request): """ Google Authenticator configuration view. Only POST requests are allowed. """ ret = {} user = request.browser.user if request.method != "POST": custom_log(request, "cauth: Tried to enter Authenticator configuration view with GET request. Redirecting back. Referer: %s" % request.META.get("HTTP_REFERRER"), level="info") messages.info(request, "You can't access configuration page directly. Please click a link below to configure Authenticator.") return redirect_with_get_params("login_frontend.views.configure", request.GET) ret["back_url"] = redir_to_sso(request).url regen_secret = True otp = request.POST.get("otp_code") if otp: (status, message) = request.browser.user.validate_authenticator_code(otp, request) if status: # Correct OTP. user.strong_configured = True user.strong_authenticator_used = True user.strong_sms_always = False user.strong_skips_available = 0 user.save() custom_log(request, "cauth: Reconfigured Authenticator", level="info") add_user_log(request, "Successfully configured Authenticator", "gear") messages.success(request, "Successfully configured Authenticator") redir = redir_to_sso(request, no_default=True) if redir: return redir_to_sso(request) return redirect_with_get_params("login_frontend.views.configure", request.GET.dict()) else: # Incorrect code. Don't regen secret. custom_log(request, "cauth: Entered invalid OTP during Authenticator configuration", level="info") add_user_log(request, "Entered invalid OTP during Authenticator configuration", "warning") regen_secret = False if not re.match("^[0-9]{5,6}$", otp): ret["is_invalid_otp"] = True ret["invalid_otp"] = message messages.warning(request, "Invalid one-time password. Please scroll down to try again.") if regen_secret: authenticator_secret = user.gen_authenticator() # As new secret was generated and saved, authenticator configuration is no longer valid. # Similarly, strong authentication is no longer configured, because authenticator configuration # was revoked. user.strong_authenticator_used = False user.strong_configured = False user.save() add_user_log(request, "Regenerated Authenticator code", "gear") custom_log(request, "cauth: Regenerated Authenticator code. Set authenticator_used=False, strong_configured=False", level="info") ret["authenticator_secret"] = user.strong_authenticator_secret ret["authenticator_id"] = user.strong_authenticator_id request.browser.authenticator_qr_nonce = create_browser_uuid() ret["authenticator_qr_nonce"] = request.browser.authenticator_qr_nonce request.browser.save() ret["get_params"] = urllib.urlencode(request.GET) response = render_to_response("login_frontend/configure_authenticator.html", ret, context_instance=RequestContext(request)) return response
def secondstepauth(request): """ Determines proper second step authentication method """ assert request.browser is not None, "Second step authentication requested, but browser is None." assert request.browser.user is not None, "Second step authentication requested, but user is not specified." custom_log(request, "2f: Second step authentication requested. Parameters: %s" % request.GET.dict(), level="debug") get_params = request.GET user = request.browser.user # If already authenticated with L_STRONG, redirect back to destination if request.browser.is_authenticated(): custom_log(request, "2f: User is already authenticated. Redirect back to SSO", level="info") return redir_to_sso(request) if not user.strong_configured: # User has not configured any authentication. Go to that pipe. custom_log(request, "2f: Strong authentication is not configured. Go to SMS authentication", level="info") return redirect_with_get_params("login_frontend.authentication_views.authenticate_with_sms", get_params) if user.strong_sms_always: # Strong authentication has been configured, and user has requested to get SMS message. custom_log(request, "2f: User has requested SMS authentication.", level="info") return redirect_with_get_params("login_frontend.authentication_views.authenticate_with_sms", get_params) if user.strong_authenticator_secret: custom_log(request, "2f: Authenticator is properly configured. Redirect.", level="info") return redirect_with_get_params("login_frontend.authentication_views.authenticate_with_authenticator", get_params) custom_log(request, "2f: No proper redirect configured.", level="error") return HttpResponse("Second step auth: no proper redirect configured.")
def configure(request): """ Configuration view for general options. """ user = request.browser.user ret = {} if request.method == "POST": if request.POST.get("always_sms") == "on": add_user_log(request, "Switched to SMS authentication", "info") custom_log(request, "cstrong: Switched to SMS authentication", level="info") user.strong_configured = True user.strong_sms_always = True user.strong_skips_available = 0 user.save() messages.success(request, "Switched to SMS authentication") elif request.POST.get("always_sms") == "off": add_user_log(request, "Switched to Authenticator authentication", "info") custom_log(request, "cstrong: Switched to Authenticator authentication", level="info") # This is only visible when Authenticator is already generated. If it was not generated, # user can click to "Use SMS instead" user.strong_configured = True user.strong_sms_always = False user.strong_skips_available = 0 user.save() messages.success(request, "Default setting changed to Authenticator") elif request.POST.get("location"): action = request.POST.get("location") if action == "share": if not user.location_authorized: user.location_authorized = True custom_log(request, "Enabled location sharing", level="info") add_user_log(request, "Enabled location sharing", "location-arrow") elif action == "off": if user.location_authorized: user.location_authorized = False custom_log(request, "Disabled location sharing", level="info") add_user_log(request, "Disabled location sharing", "location-arrow") messages.success(request, "Location sharing is now disabled") elif action == "error": custom_log(request, "Encountered error with location sharing settings: %s" % request.POST.get("location-error"), level="warn") user.save() return redirect_with_get_params("login_frontend.views.configure", request.GET.dict()) ret["user"] = user ret["get_params"] = urllib.urlencode(request.GET) back_url = redir_to_sso(request, no_default=True) ret["num_sessions"] = Browser.objects.filter(user=user).count() ret["csp_violations"] = CSPReport.objects.filter(username=user.username).count() ret["authenticator_id"] = user.get_authenticator_id() if back_url: ret["back_url"] = back_url.url response = render_to_response("login_frontend/configure.html", ret, context_instance=RequestContext(request)) return response
def secondstepauth(request): """ Determines proper second step authentication method """ assert request.browser is not None, "Second step authentication requested, but browser is None." assert request.browser.user is not None, "Second step authentication requested, but user is not specified." custom_log(request, "2f: Second step authentication requested. Parameters: %s" % request.GET.dict(), level="debug") get_params = request.GET user = request.browser.user # If already authenticated with L_STRONG, redirect back to destination if request.browser.is_authenticated(): custom_log(request, "2f: User is already authenticated. Redirect back to SSO", level="info") return redir_to_sso(request) if not user.strong_configured: # User has not configured any authentication. Go to that pipe. custom_log( request, "2f: Strong authentication is not configured. Go to SMS authentication", level="info") return redirect_with_get_params( "login_frontend.authentication_views.authenticate_with_sms", get_params) if user.strong_sms_always: # Strong authentication has been configured, and user has requested to get SMS message. custom_log(request, "2f: User has requested SMS authentication.", level="info") return redirect_with_get_params( "login_frontend.authentication_views.authenticate_with_sms", get_params) if user.strong_authenticator_secret: custom_log(request, "2f: Authenticator is properly configured. Redirect.", level="info") return redirect_with_get_params( "login_frontend.authentication_views.authenticate_with_authenticator", get_params) custom_log(request, "2f: No proper redirect configured.", level="error") return HttpResponse("Second step auth: no proper redirect configured.")
def authenticate_with_emergency(request): """ TODO: emergency code authentication """ ret = {} get_params = request.GET.dict() codes = request.browser.user.get_emergency_codes() if not codes: ret["no_codes_generated"] = True custom_log(request, "No codes generated. Can't authenticate with emergency codes.", level="info") return render_to_response("login_frontend/no_emergency_available.html", {}, context_instance=RequestContext(request)) if not codes.valid(): ret["no_codes_available"] = True custom_log(request, "No codes available. Can't authenticate with emergency codes.", level="info") return render_to_response("login_frontend/no_emergency_available.html", {}, context_instance=RequestContext(request)) if request.method == 'POST': otp = request.POST.get("otp", "") if otp: # whitespace is not important, but printed passwords include spaces for readability. otp = otp.replace(" ", "") if is_ratelimited(request, True, True, ["POST"], None, "30/30s", [request.user.username], "30s_emergency"): ret["ratelimited"] = True ret["ratelimit_wait_until"] = timezone.now() + datetime.timedelta(seconds=120) custom_log(request, "2f-emergency: ratelimited", level="warn") elif codes.use_code(otp): # Proper code was provided. familiar_device = request.browser.user_is_familiar(request.browser.user, Browser.L_STRONG) emergency_used_notify(request, codes, familiar_device=familiar_device) custom_log(request, "Authenticated with emergency code", level="info") add_user_log(request, "Second-factor authentication with emergency code succeeded.", "lock") request.browser.save_browser = False # emergency codes are only for temporary authentication request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) return redir_to_sso(request) else: if re.match("^[0-9]{5,7}$", otp): custom_log(request, "Tried to enter SMS/authenticator code", level="info") ret["twostep_code"] = True if re.match("^[0-9]{8}$", otp): custom_log(request, "Tried to use Google apps emergency code", level="info") ret["gapps_code"] = True custom_log(request, "Incorrect emergency code", level="info") ret["invalid_otp"] = True ret["generated_at"] = str(codes.generated_at) ret["should_timesync"] = request.browser.should_timesync() ret["get_params"] = urllib.urlencode(get_params) ret["emergency_code_id"] = codes.current_code.code_id return render_to_response("login_frontend/authenticate_with_emergency_code.html", ret, context_instance=RequestContext(request))
def name_your_browser(request): if request.method == 'POST': browser_name = request.POST.get("name", "").strip() if browser_name and browser_name != request.browser.name: if check_browser_name(browser_name): custom_log(request, "Set browser name to '%s'" % browser_name, level="info") add_user_log(request, "Set browser name to '%s'" % browser_name, "info") request.browser.name = browser_name request.browser.save() else: custom_log(request, "Browser name '%s' was rejected." % browser_name, level="info") custom_log(request, "Sending auth_state_changed", level="debug") if request.GET.get("_sc"): request.browser.auth_state_changed() return redir_to_sso(request) ret = {} ret["get_params"] = urllib.urlencode(request.GET) response = render_to_response("login_frontend/name_browser.html", ret, context_instance=RequestContext(request)) return response
def authenticate_with_sms(request): """ Authenticate user with SMS. Accepts Authenticator codes too. """ # If already authenticated with L_STRONG, redirect back to SSO / frontpage if request.browser.is_authenticated(): custom_log(request, "2f-sms: User is already authenticated. Redirect back to SSO service", level="debug") return redir_to_sso(request) custom_log(request, "2f-sms: authenticate_with_sms", level="debug") user = request.browser.user ret = {} if not (user.primary_phone or user.secondary_phone): # Phone numbers are not available. custom_log(request, "2f-sms: No phone number available - unable to authenticate.", level="error") ret["should_timesync"] = request.browser.should_timesync() return render_to_response("login_frontend/no_phone_available.html", ret, context_instance=RequestContext(request)) skips_available = user.strong_skips_available ret["skips_available"] = skips_available ret["return_readable"] = get_return_url(request) if request.method == "POST" and request.POST.get("skip"): if skips_available > 0: user.strong_skips_available -= 1 user.save() add_user_log(request, "Skipped strong authentication: %s left" % user.strong_skips_available, "meh-o") custom_log(request, "2f-sms: Skipped strong authentication: %s left" % user.strong_skips_available) # TODO: determine the levels automatically. request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) request.browser.set_auth_level_valid_until = timezone.now() + datetime.timedelta(hours=12) request.browser.save() request.browser.auth_state_changed() custom_log(request, "2f-sms: Redirecting back to SSO provider", level="debug") return redir_to_sso(request) else: messages.warning(request, "You can't skip strong authentication anymore.") custom_log(request, "2f-sms: Tried to skip strong authentication with no skips available", level="warn") if user.strong_configured: if user.strong_authenticator_secret: ret["can_use_authenticator"] = True if not user.strong_authenticator_used: ret["authenticator_generated"] = True else: custom_log(request, "2f-sms: Strong authentication is not configured yet.", level="debug") # No strong authentication is configured. ret["strong_not_configured"] = True if user.strong_authenticator_secret: ret["authenticator_generated"] = True ret["can_use_authenticator"] = True if user.primary_phone_changed: custom_log(request, "2f-sms: Phone number has changed.", level="debug") # Phone number changed. For security reasons... ret["primary_phone_changed"] = True if request.browser.name: ret["browser_name"] = True if request.method == "POST": custom_log(request, "2f-sms: POST request", level="debug") browser_name = request.POST.get("name") ret["browser_name_value"] = browser_name if request.POST.get("otp"): custom_log(request, "2f-sms: Form is valid", level="debug") otp = request.POST.get("otp") status, message = request.browser.validate_sms(otp) save_browser = False if request.POST.get("my_computer"): save_browser = True if request.browser.save_browser != save_browser: request.browser.save_browser = save_browser request.browser.save() if save_browser: custom_log(request, "2f-sms: Marked browser as remembered", level="info") add_user_log(request, "Marked browser as remembered", "eye") else: custom_log(request, "2f-sms: Marked browser as not remembered", level="info") add_user_log(request, "Marked browser as not remembered", "eye-slash") if not status: # If OTP from SMS did not match, also test for Authenticator OTP. custom_log(request, "2f-sms: OTP from SMS did not match, testing Authenticator", level="info") (status, _) = request.browser.user.validate_authenticator_code(otp, request) if status: if browser_name and browser_name != request.browser.name: request.browser.name = browser_name # Authentication succeeded. custom_log(request, "2f-sms: Second-factor authentication with SMS succeeded") add_user_log(request, "Second-factor authentication with SMS succeeded", "lock") # TODO: determine the levels automatically. request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) if user.primary_phone_changed: user.primary_phone_changed = False user.save() if request.POST.get("timing_data"): custom_log(request, "2f-sms: Saved timing data", level="debug") timing_data = request.POST.get("timing_data") save_timing_data(request, user, timing_data) if not user.strong_configured: # Strong authentication is not configured. Go to configuration view. custom_log(request, "2f-sms: User has not configured strong authentication. Redirect to configuration view", level="info") return redirect_with_get_params("login_frontend.views.configure", request.GET) # Redirect back to SSO service request.browser.auth_state_changed() custom_log(request, "2f-sms: Redirecting back to SSO provider", level="debug") return redir_to_sso(request) else: if message: ret["message"] = message custom_log(request, "2f-sms: SMS OTP login failed: %s" % message, level="warn") add_user_log(request, "SMS OTP login failed: %s" % message, "warning") else: custom_log(request, "2f-sms: Incorrect SMS OTP", level="warn") add_user_log(request, "Incorrect SMS OTP", "warning") ret["authentication_failed"] = True else: messages.warning(request, "Invalid input") else: custom_log(request, "2f-sms: GET request", level="debug") if not request.browser.valid_sms_exists(180) or request.POST.get("regen_sms"): custom_log(request, "2f-sms: Generating a new SMS code", level="info") sms_text = request.browser.generate_sms_text(request=request) for phone in (user.primary_phone, user.secondary_phone): if phone: status = send_sms(phone, sms_text) if not status: messages.warning(request, "Sending SMS to %s failed." % phone) custom_log(request, "2f-sms: Sending SMS to %s failed" % phone, level="warn") add_user_log(request, "Sending SMS to %s failed" % phone) else: custom_log(request, "2f-sms: Sent OTP to %s" % phone) add_user_log(request, "Sent OTP code to %s" % phone, "info") phone_redacted = "%s...%s" % (phone[0:6], phone[-4:]) messages.info(request, mark_safe("Sent SMS to <span class='tooltip-link' title='This is redacted to protect your privacy'>%s</span>" % phone_redacted)) if request.method == "POST": # Redirect to avoid duplicate SMSes on reload. return redirect_with_get_params("login_frontend.authentication_views.authenticate_with_sms", request.GET) ret["sms_valid_until"] = request.browser.sms_code_generated_at + datetime.timedelta(seconds=900) ret["expected_sms_id"] = request.browser.sms_code_id ret["get_params"] = urllib.urlencode(request.GET) ret["my_computer"] = request.browser.save_browser ret["should_timesync"] = request.browser.should_timesync() response = render_to_response("login_frontend/authenticate_with_sms.html", ret, context_instance=RequestContext(request)) return response
def authenticate_with_authenticator(request): """ Authenticates user with Google Authenticator """ custom_log(request, "2f-auth: Requested authentication with Authenticator", level="debug") # If already authenticated with L_STRONG, redirect back to SSO / frontpage if request.browser.is_authenticated(): custom_log(request, "2f-auth: User is already authenticated. Redirect back to SSO", level="info") return redir_to_sso(request) ret = {} user = request.browser.user assert user != None, "Browser is authenticated but no User object exists." ret["return_readable"] = get_return_url(request) skips_available = user.strong_skips_available ret["skips_available"] = skips_available if not user.strong_authenticator_secret: # Authenticator is not configured. Redirect back to secondstep main screen custom_log(request, "2f-auth: Authenticator is not configured, but user accessed Authenticator view. Redirect back to secondstepauth", level="error") messages.warning(request, "You tried to authenticate with Authenticator. However, according to our records, you don't have it configured. Please sign in and go to settings to do that.") return redirect_with_get_params("login_frontend.authentication_views.secondstepauth", request.GET) if not user.strong_authenticator_used: custom_log(request, "2f-auth: Authenticator has not been used. Generated at %s" % user.strong_authenticator_generated_at, level="debug") ret["authenticator_not_used"] = True ret["authenticator_generated"] = user.strong_authenticator_generated_at if request.method == "POST" and request.POST.get("skip"): if skips_available > 0: user.strong_skips_available -= 1 user.save() add_user_log(request, "Skipped strong authentication: %s left" % user.strong_skips_available, "meh-o") custom_log(request, "2f-auth: Skipped strong authentication: %s left" % user.strong_skips_available) # TODO: determine the levels automatically. request.browser.set_auth_level(Browser.L_STRONG_SKIPPED) request.browser.set_auth_state(Browser.S_AUTHENTICATED) request.browser.set_auth_level_valid_until = timezone.now() + datetime.timedelta(hours=12) request.browser.save() custom_log(request, "2f-auth: Redirecting back to SSO provider", level="debug") return redir_to_sso(request) else: messages.warning(request, "You can't skip strong authentication anymore.") custom_log(request, "2f-auth: Tried to skip strong authentication with no skips available", level="warn") if request.browser.name: ret["browser_name"] = True if request.method == "POST" and not request.session.test_cookie_worked(): custom_log(request, "2f-auth: cookies do not work properly", level="warn") ret["enable_cookies"] = True elif request.method == "POST": browser_name = request.POST.get("name") ret["browser_name_value"] = browser_name request.session.delete_test_cookie() custom_log(request, "2f-auth: POST request", level="debug") if request.POST.get("otp"): custom_log(request, "2f-auth: Form is valid", level="debug") otp = request.POST.get("otp") custom_log(request, "2f-auth: Testing OTP code %s at %s" % (otp, time.time()), level="debug") (status, message) = user.validate_authenticator_code(otp, request) save_browser = False if request.POST.get("my_computer"): save_browser = True if request.browser.save_browser != save_browser: request.browser.save_browser = save_browser request.browser.save() if save_browser: custom_log(request, "2f-auth: Marked browser as remembered", level="info") add_user_log(request, "Marked browser as remembered", "eye") else: custom_log(request, "2f-auth: Marked browser as not remembered", level="info") add_user_log(request, "Marked browser as not remembered", "eye-slash") if not status: # If authenticator code did not match, also try latest SMS (if available). custom_log(request, "2f-auth: Authenticator code did not match. Testing SMS", level="info") status, _ = request.browser.validate_sms(otp) if status: if browser_name and browser_name != request.browser.name: request.browser.name = browser_name custom_log(request, "2f-auth: Second-factor authentication with Authenticator succeeded") add_user_log(request, "Second-factor authentication with Authenticator succeeded", "lock") # Mark authenticator configuration as valid. User might have configured # authenticator but aborted without entering validation code. user.strong_authenticator_used = True user.strong_configured = True user.save() if request.POST.get("timing_data"): custom_log(request, "2f-auth: Saving timing data", level="debug") timing_data = request.POST.get("timing_data") save_timing_data(request, user, timing_data) # TODO: determine the levels automatically. request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) request.browser.auth_state_changed() custom_log(request, "2f-auth: Redirecting back to SSO provider", level="debug") return redir_to_sso(request) else: custom_log(request, "2f-auth: Incorrect Authenticator OTP provided: %s" % message, level="warn") add_user_log(request, "Incorrect Authenticator OTP provided: %s" % message, "warning") if not re.match("^[0-9]{5,6}$", otp): ret["is_invalid_otp"] = True ret["invalid_otp"] = message else: custom_log(request, "2f-auth: form was not valid", level="debug") messages.warning(request, "One-time password field is mandatory.") else: custom_log(request, "2f-auth: GET request", level="debug") ret["user"] = user ret["authenticator_id"] = user.get_authenticator_id() ret["get_params"] = urllib.urlencode(request.GET) ret["my_computer"] = request.browser.save_browser ret["should_timesync"] = request.browser.should_timesync() request.session.set_test_cookie() response = render_to_response("login_frontend/authenticate_with_authenticator.html", ret, context_instance=RequestContext(request)) return response
def authenticate_with_password(request): """ Authenticate with username and password """ ret = {} cookies = [] browser = None ret["return_readable"] = get_return_url(request) if request.browser is None: # No Browser object is initialized. Create one. custom_log(request, "1f: No browser object exists. Create a new one. Cookies: %s" % request.COOKIES, level="debug") browser = Browser(bid=create_browser_uuid(), bid_public=create_browser_uuid(), bid_session=create_browser_uuid(), ua=request.META.get("HTTP_USER_AGENT")) browser.save() cookies.extend(browser.get_cookies()) else: custom_log(request, "1f: Browser object exists", level="debug") browser = request.browser if browser.get_auth_state() == Browser.S_REQUEST_STRONG: # User is already in strong authentication. Redirect them there. custom_log(request, "1f: State: REQUEST_STRONG. Redirecting user", level="debug") return redirect_with_get_params("login_frontend.authentication_views.secondstepauth", request.GET) if browser.is_authenticated(): # User is already authenticated. Redirect back to SSO service. custom_log(request, "1f: User is already authenticated. Redirect back to SSO service.", level="debug") return redir_to_sso(request) if browser: if browser.forced_sign_out: custom_log(request, "1f: Browser was remotely signed out.", level="debug") ret["forced_sign_out"] = True if browser.get_auth_state() == Browser.S_REQUEST_BASIC_ONLY: ret["basic_only"] = True if not browser.user: custom_log(request, "1f: S_REQUEST_BASIC_ONLY was requested, but browser.user does not exist", level="warn") messages.warning(request, "Invalid request was encountered. Please sign in again.") return redirect_with_get_params("login_frontend.views.indexview", request.GET.dict()) custom_log(request, "1f: S_REQUEST_BASIC_ONLY requested", level="debug") if request.method == 'POST': custom_log(request, "1f: POST request", level="debug") username = request.POST.get("username") if browser.get_auth_state() == Browser.S_REQUEST_BASIC_ONLY: # Only basic authentication was requested. Take username from session. username = browser.user.username password = request.POST.get("password") if username and password: custom_log(request, "1f: Both username and password exists. username=%s" % username, level="debug") auth = LdapLogin(username, password) auth_status = auth.login() if username != auth.username: custom_log(request, "1f: mapped username %s to %s" % (username, auth.username), level="debug") username = auth.username # mapped from aliases (email address -> username) save_browser = False if request.POST.get("my_computer"): save_browser = True if browser.save_browser != save_browser: browser.save_browser = save_browser browser.save() if save_browser: custom_log(request, "1f: Marked browser as remembered", level="info") add_user_log(request, "Marked browser as remembered", "eye") else: custom_log(request, "1f: Marked browser as not remembered", level="info") add_user_log(request, "Marked browser as not remembered", "eye-slash") if auth_status == True: # User signed in, so there's no reason to keep forced_sign_out anymore. browser.forced_sign_out = False browser_name = dcache.get("browser-name-for-%s-%s" % (browser.bid_public, username)) if browser_name: # This user named this browser before signing out. Restore that name. browser.name = browser_name if browser.user is None: custom_log(request, "1f: browser.user is None: %s" % username, level="debug") (user, _) = User.objects.get_or_create(username=username) user.user_tokens = json.dumps(auth.get_auth_tokens()) custom_log(request, "User tokens: %s" % user.user_tokens, level="info") user.save() browser.user = user # Delete cached counter dcache.delete("num_sessions-%s" % username) request.browser = browser custom_log(request, "1f: Successfully logged in using username and password") add_user_log(request, "Successfully logged in using username and password", "sign-in") if request.POST.get("timing_data"): custom_log(request, "1f: saving timing data", level="debug") timing_data = request.POST.get("timing_data") save_timing_data(request, browser.user, timing_data) if browser.user.emulate_legacy: custom_log(request, "1f: Emulating legacy SSO", level="info") # This is a special case for emulating legacy system: # - no two-factor authentication # - all logins expire in 12 hours browser.set_auth_level(Browser.L_STRONG_SKIPPED) browser.set_auth_state(Browser.S_AUTHENTICATED) custom_log(request, "1f: Redirecting back to SSO service", level="info") return redir_to_sso(request) # TODO: no further authentication is necessarily needed. Determine these automatically. if browser.get_auth_state() == Browser.S_REQUEST_BASIC_ONLY: # Only basic authentication is required. custom_log(request, "1f: only basic authentication was required. Upgrade directly to L_STRONG and S_AUTHENTICATED") browser.set_auth_level(Browser.L_STRONG) browser.set_auth_state(Browser.S_AUTHENTICATED) else: # Continue to strong authentication custom_log(request, "1f: set L_BASIC and S_REQUEST_STRONG") browser.set_auth_level(Browser.L_BASIC) browser.set_auth_state(Browser.S_REQUEST_STRONG) return redirect_with_get_params("login_frontend.authentication_views.secondstepauth", request.GET) else: if auth_status == "invalid_credentials": ret["authentication_failed"] = True if re.match("^[0-9]{5,6}$", password): ret["is_otp"] = True custom_log(request, "1f: Authentication failed. Invalid credentials", level="warn") add_user_log(request, "Authentication failed. Invalid credentials", "warning") elif auth_status == "server_down": messages.warning(request, "Unable to connect user directory (LDAP). Could not proceed with authentication. Please try again later, and/or contact IT team.") custom_log(request, "1f: LDAP server is down.", level="error") else: ret["message"] = auth_status custom_log(request, "1f: Authentication failed: %s" % auth_status, level="warn") add_user_log(request, "Authentication failed: %s" % auth_status, "warning") else: custom_log(request, "1f: Either username or password is missing.", level="warn") messages.warning(request, "Please enter both username and password.") else: custom_log(request, "1f: GET request", level="debug") if browser: ret["my_computer"] = browser.save_browser # Keep GET query parameters in form posts. ret["get_params"] = urllib.urlencode(request.GET) custom_log(request, "1f: Query parameters: %s" % ret["get_params"], level="debug") response = render_to_response("login_frontend/authenticate_with_password.html", ret, context_instance=RequestContext(request)) for cookie_name, cookie in cookies: custom_log(request, "Setting cookie %s=%s" % (cookie_name, cookie)) response.set_cookie(cookie_name, **cookie) return response
def configure_yubikey(request): ret = {} ret["get_params"] = urllib.urlencode(request.GET) user = request.browser.user if request.method != "POST": custom_log(request, "cyubi: Tried to enter Yubikey configuration view with GET request. Redirecting back. Referer: %s" % request.META.get("HTTP_REFERRER"), level="info") messages.info(request, "You can't access configuration page directly. Please click a link below to configure Yubikey.") return redirect_with_get_params("login_frontend.views.configure", request.GET) ret["back_url"] = redir_to_sso(request).url if request.POST.get("revoke-yubikey"): if user.yubikey: user.yubikey = None user.save() messages.success(request, "Your Yubikey is now revoked.") custom_log(request, "cyubi: Revoked Yubikey", level="info") else: messages.success(request, "You don't have Yubikey configured. No changes were made.") otp = request.POST.get("otp") if otp: # validate OTP. try: validator = YubikeyValidate(otp) except BadOtpException: custom_log(request, "cyubi: Entered incorrect Yubikey OTP", level="warn") messages.warning(request, "Invalid Yubikey OTP. This code should come from your Yubikey. It is not your password or Authenticator/SMS OTP.") return render_to_response("login_frontend/configure_yubikey.html", ret, context_instance=RequestContext(request)) # Unknown Yubikey try: yubikey = Yubikey.objects.get(public_uid=validator.public_uid) except Yubikey.DoesNotExist: custom_log(request, "cyubi: Unknown Yubikey. public_uid=%s" % validator.public_uid, level="warn") messages.warning(request, "Unknown Yubikey. Please contact IT team to get your Yubikey reconfigured.") return render_to_response("login_frontend/configure_yubikey.html", ret, context_instance=RequestContext(request)) # Invalid OTP validation_failed = False try: (internalcounter, timestamp) = validator.validate(validator.public_uid, yubikey.internal_uid, yubikey.secret, yubikey.last_id, yubikey.last_timestamp) except IncorrectOtpException: validation_failed = True custom_log(request, "cyubi: Yubikey private UID does not match.", level="warn") messages.warning(request, "Internal error: your Yubikey is configured incorrectly. Please contact IT team.") except BadOtpException: validation_failed = True custom_log(request, "cyubi: Yubikey CRC check failed.", level="warn") messages.warning(request, "Internal error: integrity check for your Yubikey failed. Please contact IT team.") except ReplayedOtpException: validation_failed = True custom_log(request, "cyubi: replay detected.", level="warn") messages.warning(request, "Do not store Yubikey values. It seems your code was replayed.") except DelayedOtpException: validation_failed = True custom_log(request, "cyubi: delayed code detected.", level="warn") messages.warning(request, "Do not store Yubikey values. It seems you entered old code.") if yubikey.compromised: messages.warning(request, "This key is marked as compromised. Please bring the key to IT team for reconfiguration. You can not use this key before it is reconfigured.") validation_failed = True if validation_failed: return render_to_response("login_frontend/configure_yubikey.html", ret, context_instance=RequestContext(request)) # If no replay/delays were detected, always save Yubikey values to prevent any replay attacks. yubikey.last_id = internalcounter yubikey.last_timestamp = timestamp yubikey.save() associated_users = yubikey.user_set.all() if len(associated_users) == 0: user.yubikey = yubikey user.save() custom_log(request, "cyubi: Configured Yubikey with public_uid=%s" % yubikey.public_uid, level="info") add_user_log(request, "Successfully configured Yubikey", "gear") messages.success(request, "Your Yubikey is now configured. You can use it on your next login - just use it on the Authenticator/SMS form.") return redirect_with_get_params("login_frontend.views.configure", request.GET.dict()) associated_user = associated_users[0] # ForeignKey -> only a single user if associated_user == user: custom_log(request, "cyubi: user tried to reconfigure their current key", level="info") messages.info(request, "You already have this key configured. No changes were made.") return redirect_with_get_params("login_frontend.views.configure", request.GET.dict()) yubikey.compromised = True yubikey.save() associated_user.yubikey = None associated_user.save() custom_log(request, "cyubi: user tried to configure key that belonged to %s. Marked as compromised." % associated_user.username, level="error") messages.warning(request, "This key belonged to another user. It is now marked as compromised. Please bring the key to IT team for reconfiguration.") return render_to_response("login_frontend/configure_yubikey.html", ret, context_instance=RequestContext(request))
def configure(request): """ Configuration view for general options. """ user = request.browser.user ret = {} get_params = request.GET.dict() ret["user"] = user ret["get_params"] = urllib.urlencode(request.GET) ret["csp_violations"] = dcache.get("csp-has-reports-for-%s" % user.username) ret["authenticator_id"] = user.get_authenticator_id() emergency_codes = user.get_emergency_codes() ret["emergency_codes"] = emergency_codes ret["user_aliases"] = user.get_aliases() ret["yubikey"] = user.yubikey locations = UserLocation.objects.filter(user=user).count() if locations > 0: ret["locations_shared"] = locations back_url = redir_to_sso(request, no_default=True) if back_url: ret["back_url"] = back_url.url if request.method == "POST": if request.POST.get("otp_code"): otp = request.POST.get("otp_code") (status, message) = request.browser.user.validate_authenticator_code(otp, request) if status: # Correct OTP. user.strong_configured = True user.strong_authenticator_used = True user.strong_skips_available = 0 user.save() custom_log(request, "configure: validated Authenticator code", level="info") add_user_log(request, "Successfully validated Authenticator configuration", "gear") messages.success(request, "Successfully validated Authenticator configuration") else: if message: messages.warning(request, message) if request.POST.get("always_sms") == "on": add_user_log(request, "Switched to SMS authentication", "info") custom_log(request, "cstrong: Switched to SMS authentication", level="info") user.strong_configured = True user.strong_sms_always = True user.strong_skips_available = 0 user.save() messages.success(request, "Switched to SMS authentication") elif request.POST.get("always_sms") == "off": add_user_log(request, "Switched to Authenticator authentication", "info") custom_log(request, "cstrong: Switched to Authenticator authentication", level="info") # This is only visible when Authenticator is already generated. If it was not generated, # user can click to "Use SMS instead" user.strong_configured = True user.strong_sms_always = False user.strong_skips_available = 0 user.save() messages.success(request, "Default setting changed to Authenticator") elif request.POST.get("location"): action = request.POST.get("location") if action == "share": if not user.location_authorized: user.location_authorized = True custom_log(request, "Enabled location sharing", level="info") add_user_log(request, "Enabled location sharing", "location-arrow") elif action == "off": if user.location_authorized: user.location_authorized = False custom_log(request, "Disabled location sharing", level="info") add_user_log(request, "Disabled location sharing", "location-arrow") messages.success(request, "Location sharing is now disabled") elif action == "error": custom_log(request, "Encountered error with location sharing settings: %s" % request.POST.get("location-error"), level="warn") user.save() elif request.POST.get("generate_emergency"): (emergency_codes, _) = EmergencyCodes.objects.get_or_create(user=request.browser.user) old_existed = False if emergency_codes.valid(): # Old codes existed. custom_log(request, "Overwrote old emergency codes", level="info") old_existed = True pass emergency_codes.generate_codes(3) custom_log(request, "Created new emergency codes", level="info") add_user_log(request, "Generated new emergency codes", "fire-extinguisher") dl_uuid = create_browser_uuid() dcache.set("emergency-nonce-for-%s" % request.browser.bid_public, dl_uuid, 300) ret["dl_uuid"] = dl_uuid ret["code_valid_until"] = timezone.now() + datetime.timedelta(seconds=300) return render_to_response("login_frontend/emergency_codes_created.html", ret, context_instance=RequestContext(request)) return redirect_with_get_params("login_frontend.views.configure", get_params) response = render_to_response("login_frontend/configure.html", ret, context_instance=RequestContext(request)) return response
def authenticate_with_sms(request): """ Authenticate user with SMS. Accepts Authenticator codes too. """ # If already authenticated with L_STRONG, redirect back to SSO / frontpage if request.browser.is_authenticated(): custom_log( request, "2f-sms: User is already authenticated. Redirect back to SSO service", level="debug") return redir_to_sso(request) custom_log(request, "2f-sms: authenticate_with_sms", level="debug") user = request.browser.user ret = {} if not (user.primary_phone or user.secondary_phone): # Phone numbers are not available. custom_log( request, "2f-sms: No phone number available - unable to authenticate.", level="error") ret["should_timesync"] = request.browser.should_timesync() return render_to_response("login_frontend/no_phone_available.html", ret, context_instance=RequestContext(request)) skips_available = user.strong_skips_available ret["skips_available"] = skips_available ret["return_readable"] = get_return_url(request) if request.method == "POST" and request.POST.get("skip"): if skips_available > 0: user.strong_skips_available -= 1 user.save() add_user_log( request, "Skipped strong authentication: %s left" % user.strong_skips_available, "meh-o") custom_log( request, "2f-sms: Skipped strong authentication: %s left" % user.strong_skips_available) # TODO: determine the levels automatically. request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) request.browser.set_auth_level_valid_until = timezone.now( ) + datetime.timedelta(hours=12) request.browser.save() request.browser.auth_state_changed() custom_log(request, "2f-sms: Redirecting back to SSO provider", level="debug") return redir_to_sso(request) else: messages.warning(request, "You can't skip strong authentication anymore.") custom_log( request, "2f-sms: Tried to skip strong authentication with no skips available", level="warn") if user.strong_configured: if user.strong_authenticator_secret: ret["can_use_authenticator"] = True if not user.strong_authenticator_used: ret["authenticator_generated"] = True else: custom_log(request, "2f-sms: Strong authentication is not configured yet.", level="debug") # No strong authentication is configured. ret["strong_not_configured"] = True if user.strong_authenticator_secret: ret["authenticator_generated"] = True ret["can_use_authenticator"] = True if user.primary_phone_changed: custom_log(request, "2f-sms: Phone number has changed.", level="debug") # Phone number changed. For security reasons... ret["primary_phone_changed"] = True if request.browser.name: ret["browser_name"] = True if request.method == "POST": custom_log(request, "2f-sms: POST request", level="debug") browser_name = request.POST.get("name") ret["browser_name_value"] = browser_name if request.POST.get("otp"): custom_log(request, "2f-sms: Form is valid", level="debug") otp = request.POST.get("otp") status, message = request.browser.validate_sms(otp) save_browser = False if request.POST.get("my_computer"): save_browser = True if request.browser.save_browser != save_browser: request.browser.save_browser = save_browser request.browser.save() if save_browser: custom_log(request, "2f-sms: Marked browser as remembered", level="info") add_user_log(request, "Marked browser as remembered", "eye") else: custom_log(request, "2f-sms: Marked browser as not remembered", level="info") add_user_log(request, "Marked browser as not remembered", "eye-slash") if not status: # If OTP from SMS did not match, also test for Authenticator OTP. custom_log( request, "2f-sms: OTP from SMS did not match, testing Authenticator", level="info") (status, _) = request.browser.user.validate_authenticator_code( otp, request) if status: if browser_name and browser_name != request.browser.name: request.browser.name = browser_name # Authentication succeeded. custom_log( request, "2f-sms: Second-factor authentication with SMS succeeded") add_user_log( request, "Second-factor authentication with SMS succeeded", "lock") # TODO: determine the levels automatically. request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) if user.primary_phone_changed: user.primary_phone_changed = False user.save() if request.POST.get("timing_data"): custom_log(request, "2f-sms: Saved timing data", level="debug") timing_data = request.POST.get("timing_data") save_timing_data(request, user, timing_data) if not user.strong_configured: # Strong authentication is not configured. Go to configuration view. custom_log( request, "2f-sms: User has not configured strong authentication. Redirect to configuration view", level="info") return redirect_with_get_params( "login_frontend.views.configure", request.GET) # Redirect back to SSO service request.browser.auth_state_changed() custom_log(request, "2f-sms: Redirecting back to SSO provider", level="debug") return redir_to_sso(request) else: if message: ret["message"] = message custom_log(request, "2f-sms: SMS OTP login failed: %s" % message, level="warn") add_user_log(request, "SMS OTP login failed: %s" % message, "warning") else: custom_log(request, "2f-sms: Incorrect SMS OTP", level="warn") add_user_log(request, "Incorrect SMS OTP", "warning") ret["authentication_failed"] = True else: messages.warning(request, "Invalid input") else: custom_log(request, "2f-sms: GET request", level="debug") if not request.browser.valid_sms_exists(180) or request.POST.get( "regen_sms"): custom_log(request, "2f-sms: Generating a new SMS code", level="info") sms_text = request.browser.generate_sms_text(request=request) for phone in (user.primary_phone, user.secondary_phone): if phone: status = send_sms(phone, sms_text) if not status: messages.warning(request, "Sending SMS to %s failed." % phone) custom_log(request, "2f-sms: Sending SMS to %s failed" % phone, level="warn") add_user_log(request, "Sending SMS to %s failed" % phone) else: custom_log(request, "2f-sms: Sent OTP to %s" % phone) add_user_log(request, "Sent OTP code to %s" % phone, "info") phone_redacted = "%s...%s" % (phone[0:6], phone[-4:]) messages.info( request, mark_safe( "Sent SMS to <span class='tooltip-link' title='This is redacted to protect your privacy'>%s</span>" % phone_redacted)) if request.method == "POST": # Redirect to avoid duplicate SMSes on reload. return redirect_with_get_params( "login_frontend.authentication_views.authenticate_with_sms", request.GET) ret["sms_valid_until"] = request.browser.sms_code_generated_at + datetime.timedelta( seconds=900) ret["expected_sms_id"] = request.browser.sms_code_id ret["get_params"] = urllib.urlencode(request.GET) ret["my_computer"] = request.browser.save_browser ret["should_timesync"] = request.browser.should_timesync() response = render_to_response("login_frontend/authenticate_with_sms.html", ret, context_instance=RequestContext(request)) return response
def authenticate_with_authenticator(request): """ Authenticates user with Google Authenticator """ custom_log(request, "2f-auth: Requested authentication with Authenticator", level="debug") # If already authenticated with L_STRONG, redirect back to SSO / frontpage if request.browser.is_authenticated(): custom_log( request, "2f-auth: User is already authenticated. Redirect back to SSO", level="info") return redir_to_sso(request) ret = {} user = request.browser.user assert user != None, "Browser is authenticated but no User object exists." ret["return_readable"] = get_return_url(request) skips_available = user.strong_skips_available ret["skips_available"] = skips_available if not user.strong_authenticator_secret: # Authenticator is not configured. Redirect back to secondstep main screen custom_log( request, "2f-auth: Authenticator is not configured, but user accessed Authenticator view. Redirect back to secondstepauth", level="error") messages.warning( request, "You tried to authenticate with Authenticator. However, according to our records, you don't have it configured. Please sign in and go to settings to do that." ) return redirect_with_get_params( "login_frontend.authentication_views.secondstepauth", request.GET) if not user.strong_authenticator_used: custom_log( request, "2f-auth: Authenticator has not been used. Generated at %s" % user.strong_authenticator_generated_at, level="debug") ret["authenticator_not_used"] = True ret["authenticator_generated"] = user.strong_authenticator_generated_at if request.method == "POST" and request.POST.get("skip"): if skips_available > 0: user.strong_skips_available -= 1 user.save() add_user_log( request, "Skipped strong authentication: %s left" % user.strong_skips_available, "meh-o") custom_log( request, "2f-auth: Skipped strong authentication: %s left" % user.strong_skips_available) # TODO: determine the levels automatically. request.browser.set_auth_level(Browser.L_STRONG_SKIPPED) request.browser.set_auth_state(Browser.S_AUTHENTICATED) request.browser.set_auth_level_valid_until = timezone.now( ) + datetime.timedelta(hours=12) request.browser.save() custom_log(request, "2f-auth: Redirecting back to SSO provider", level="debug") return redir_to_sso(request) else: messages.warning(request, "You can't skip strong authentication anymore.") custom_log( request, "2f-auth: Tried to skip strong authentication with no skips available", level="warn") if request.browser.name: ret["browser_name"] = True if request.method == "POST" and not request.session.test_cookie_worked(): custom_log(request, "2f-auth: cookies do not work properly", level="warn") ret["enable_cookies"] = True elif request.method == "POST": browser_name = request.POST.get("name") ret["browser_name_value"] = browser_name request.session.delete_test_cookie() custom_log(request, "2f-auth: POST request", level="debug") if request.POST.get("otp"): custom_log(request, "2f-auth: Form is valid", level="debug") otp = request.POST.get("otp") custom_log(request, "2f-auth: Testing OTP code %s at %s" % (otp, time.time()), level="debug") (status, message) = user.validate_authenticator_code(otp, request) save_browser = False if request.POST.get("my_computer"): save_browser = True if request.browser.save_browser != save_browser: request.browser.save_browser = save_browser request.browser.save() if save_browser: custom_log(request, "2f-auth: Marked browser as remembered", level="info") add_user_log(request, "Marked browser as remembered", "eye") else: custom_log(request, "2f-auth: Marked browser as not remembered", level="info") add_user_log(request, "Marked browser as not remembered", "eye-slash") if not status: # If authenticator code did not match, also try latest SMS (if available). custom_log( request, "2f-auth: Authenticator code did not match. Testing SMS", level="info") status, _ = request.browser.validate_sms(otp) if status: if browser_name and browser_name != request.browser.name: request.browser.name = browser_name custom_log( request, "2f-auth: Second-factor authentication with Authenticator succeeded" ) add_user_log( request, "Second-factor authentication with Authenticator succeeded", "lock") # Mark authenticator configuration as valid. User might have configured # authenticator but aborted without entering validation code. user.strong_authenticator_used = True user.strong_configured = True user.save() if request.POST.get("timing_data"): custom_log(request, "2f-auth: Saving timing data", level="debug") timing_data = request.POST.get("timing_data") save_timing_data(request, user, timing_data) # TODO: determine the levels automatically. request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) request.browser.auth_state_changed() custom_log(request, "2f-auth: Redirecting back to SSO provider", level="debug") return redir_to_sso(request) else: custom_log( request, "2f-auth: Incorrect Authenticator OTP provided: %s" % message, level="warn") add_user_log( request, "Incorrect Authenticator OTP provided: %s" % message, "warning") if not re.match("^[0-9]{5,6}$", otp): ret["is_invalid_otp"] = True ret["invalid_otp"] = message else: custom_log(request, "2f-auth: form was not valid", level="debug") messages.warning(request, "One-time password field is mandatory.") else: custom_log(request, "2f-auth: GET request", level="debug") ret["user"] = user ret["authenticator_id"] = user.get_authenticator_id() ret["get_params"] = urllib.urlencode(request.GET) ret["my_computer"] = request.browser.save_browser ret["should_timesync"] = request.browser.should_timesync() request.session.set_test_cookie() response = render_to_response( "login_frontend/authenticate_with_authenticator.html", ret, context_instance=RequestContext(request)) return response
def authenticate_with_password(request): """ Authenticate with username and password """ ret = {} cookies = [] browser = None ret["return_readable"] = get_return_url(request) if request.browser is None: # No Browser object is initialized. Create one. custom_log( request, "1f: No browser object exists. Create a new one. Cookies: %s" % request.COOKIES, level="debug") browser = Browser(bid=create_browser_uuid(), bid_public=create_browser_uuid(), bid_session=create_browser_uuid(), ua=request.META.get("HTTP_USER_AGENT")) browser.save() cookies.extend(browser.get_cookies()) else: custom_log(request, "1f: Browser object exists", level="debug") browser = request.browser if browser.get_auth_state() == Browser.S_REQUEST_STRONG: # User is already in strong authentication. Redirect them there. custom_log(request, "1f: State: REQUEST_STRONG. Redirecting user", level="debug") return redirect_with_get_params( "login_frontend.authentication_views.secondstepauth", request.GET) if browser.is_authenticated(): # User is already authenticated. Redirect back to SSO service. custom_log( request, "1f: User is already authenticated. Redirect back to SSO service.", level="debug") return redir_to_sso(request) if browser: if browser.forced_sign_out: custom_log(request, "1f: Browser was remotely signed out.", level="debug") ret["forced_sign_out"] = True if browser.get_auth_state() == Browser.S_REQUEST_BASIC_ONLY: ret["basic_only"] = True if not browser.user: custom_log( request, "1f: S_REQUEST_BASIC_ONLY was requested, but browser.user does not exist", level="warn") messages.warning( request, "Invalid request was encountered. Please sign in again.") return redirect_with_get_params( "login_frontend.views.indexview", request.GET.dict()) custom_log(request, "1f: S_REQUEST_BASIC_ONLY requested", level="debug") if request.method == 'POST': custom_log(request, "1f: POST request", level="debug") username = request.POST.get("username") if browser.get_auth_state() == Browser.S_REQUEST_BASIC_ONLY: # Only basic authentication was requested. Take username from session. username = browser.user.username password = request.POST.get("password") if username and password: custom_log(request, "1f: Both username and password exists. username=%s" % username, level="debug") auth = LdapLogin(username, password) auth_status = auth.login() if username != auth.username: custom_log(request, "1f: mapped username %s to %s" % (username, auth.username), level="debug") username = auth.username # mapped from aliases (email address -> username) save_browser = False if request.POST.get("my_computer"): save_browser = True if browser.save_browser != save_browser: browser.save_browser = save_browser browser.save() if save_browser: custom_log(request, "1f: Marked browser as remembered", level="info") add_user_log(request, "Marked browser as remembered", "eye") else: custom_log(request, "1f: Marked browser as not remembered", level="info") add_user_log(request, "Marked browser as not remembered", "eye-slash") if auth_status == True: # User signed in, so there's no reason to keep forced_sign_out anymore. browser.forced_sign_out = False browser_name = dcache.get("browser-name-for-%s-%s" % (browser.bid_public, username)) if browser_name: # This user named this browser before signing out. Restore that name. browser.name = browser_name if browser.user is None: custom_log(request, "1f: browser.user is None: %s" % username, level="debug") (user, _) = User.objects.get_or_create(username=username) user.user_tokens = json.dumps(auth.get_auth_tokens()) custom_log(request, "User tokens: %s" % user.user_tokens, level="info") user.save() browser.user = user # Delete cached counter dcache.delete("num_sessions-%s" % username) request.browser = browser custom_log( request, "1f: Successfully logged in using username and password") add_user_log( request, "Successfully logged in using username and password", "sign-in") if request.POST.get("timing_data"): custom_log(request, "1f: saving timing data", level="debug") timing_data = request.POST.get("timing_data") save_timing_data(request, browser.user, timing_data) if browser.user.emulate_legacy: custom_log(request, "1f: Emulating legacy SSO", level="info") # This is a special case for emulating legacy system: # - no two-factor authentication # - all logins expire in 12 hours browser.set_auth_level(Browser.L_STRONG_SKIPPED) browser.set_auth_state(Browser.S_AUTHENTICATED) custom_log(request, "1f: Redirecting back to SSO service", level="info") return redir_to_sso(request) # TODO: no further authentication is necessarily needed. Determine these automatically. if browser.get_auth_state() == Browser.S_REQUEST_BASIC_ONLY: # Only basic authentication is required. custom_log( request, "1f: only basic authentication was required. Upgrade directly to L_STRONG and S_AUTHENTICATED" ) browser.set_auth_level(Browser.L_STRONG) browser.set_auth_state(Browser.S_AUTHENTICATED) else: # Continue to strong authentication custom_log(request, "1f: set L_BASIC and S_REQUEST_STRONG") browser.set_auth_level(Browser.L_BASIC) browser.set_auth_state(Browser.S_REQUEST_STRONG) return redirect_with_get_params( "login_frontend.authentication_views.secondstepauth", request.GET) else: if auth_status == "invalid_credentials": ret["authentication_failed"] = True if re.match("^[0-9]{5,6}$", password): ret["is_otp"] = True custom_log( request, "1f: Authentication failed. Invalid credentials", level="warn") add_user_log(request, "Authentication failed. Invalid credentials", "warning") elif auth_status == "server_down": messages.warning( request, "Unable to connect user directory (LDAP). Could not proceed with authentication. Please try again later, and/or contact IT team." ) custom_log(request, "1f: LDAP server is down.", level="error") else: ret["message"] = auth_status custom_log(request, "1f: Authentication failed: %s" % auth_status, level="warn") add_user_log(request, "Authentication failed: %s" % auth_status, "warning") else: custom_log(request, "1f: Either username or password is missing.", level="warn") messages.warning(request, "Please enter both username and password.") else: custom_log(request, "1f: GET request", level="debug") if browser: ret["my_computer"] = browser.save_browser # Keep GET query parameters in form posts. ret["get_params"] = urllib.urlencode(request.GET) custom_log(request, "1f: Query parameters: %s" % ret["get_params"], level="debug") response = render_to_response( "login_frontend/authenticate_with_password.html", ret, context_instance=RequestContext(request)) for cookie_name, cookie in cookies: custom_log(request, "Setting cookie %s=%s" % (cookie_name, cookie)) response.set_cookie(cookie_name, **cookie) return response
def configure_authenticator(request): """ Google Authenticator configuration view. Only POST requests are allowed. """ ret = {} user = request.browser.user if request.method != "POST": custom_log( request, "cauth: Tried to enter Authenticator configuration view with GET request. Redirecting back. Referer: %s" % request.META.get("HTTP_REFERRER"), level="info") messages.info( request, "You can't access configuration page directly. Please click a link below to configure Authenticator." ) return redirect_with_get_params("login_frontend.views.configure", request.GET) ret["back_url"] = redir_to_sso(request).url regen_secret = True otp = request.POST.get("otp_code") if otp: (status, message) = request.browser.user.validate_authenticator_code( otp, request) if status: # Correct OTP. user.strong_configured = True user.strong_authenticator_used = True user.strong_sms_always = False user.strong_skips_available = 0 user.save() custom_log(request, "cauth: Reconfigured Authenticator", level="info") add_user_log(request, "Successfully configured Authenticator", "gear") messages.success(request, "Successfully configured Authenticator") redir = redir_to_sso(request, no_default=True) if redir: return redir_to_sso(request) return redirect_with_get_params("login_frontend.views.configure", request.GET.dict()) else: # Incorrect code. Don't regen secret. custom_log( request, "cauth: Entered invalid OTP during Authenticator configuration", level="info") add_user_log( request, "Entered invalid OTP during Authenticator configuration", "warning") regen_secret = False if not re.match("^[0-9]{5,6}$", otp): ret["is_invalid_otp"] = True ret["invalid_otp"] = message messages.warning( request, "Invalid one-time password. Please scroll down to try again.") if regen_secret: authenticator_secret = user.gen_authenticator() # As new secret was generated and saved, authenticator configuration is no longer valid. # Similarly, strong authentication is no longer configured, because authenticator configuration # was revoked. user.strong_authenticator_used = False user.strong_configured = False user.save() add_user_log(request, "Regenerated Authenticator code", "gear") custom_log( request, "cauth: Regenerated Authenticator code. Set authenticator_used=False, strong_configured=False", level="info") ret["authenticator_secret"] = user.strong_authenticator_secret ret["authenticator_id"] = user.strong_authenticator_id request.browser.authenticator_qr_nonce = create_browser_uuid() ret["authenticator_qr_nonce"] = request.browser.authenticator_qr_nonce request.browser.save() ret["get_params"] = urllib.urlencode(request.GET) response = render_to_response( "login_frontend/configure_authenticator.html", ret, context_instance=RequestContext(request)) return response
def configure(request): """ Configuration view for general options. """ user = request.browser.user ret = {} if request.method == "POST": if request.POST.get("always_sms") == "on": add_user_log(request, "Switched to SMS authentication", "info") custom_log(request, "cstrong: Switched to SMS authentication", level="info") user.strong_configured = True user.strong_sms_always = True user.strong_skips_available = 0 user.save() messages.success(request, "Switched to SMS authentication") elif request.POST.get("always_sms") == "off": add_user_log(request, "Switched to Authenticator authentication", "info") custom_log(request, "cstrong: Switched to Authenticator authentication", level="info") # This is only visible when Authenticator is already generated. If it was not generated, # user can click to "Use SMS instead" user.strong_configured = True user.strong_sms_always = False user.strong_skips_available = 0 user.save() messages.success(request, "Default setting changed to Authenticator") elif request.POST.get("location"): action = request.POST.get("location") if action == "share": if not user.location_authorized: user.location_authorized = True custom_log(request, "Enabled location sharing", level="info") add_user_log(request, "Enabled location sharing", "location-arrow") elif action == "off": if user.location_authorized: user.location_authorized = False custom_log(request, "Disabled location sharing", level="info") add_user_log(request, "Disabled location sharing", "location-arrow") messages.success(request, "Location sharing is now disabled") elif action == "error": custom_log( request, "Encountered error with location sharing settings: %s" % request.POST.get("location-error"), level="warn") user.save() return redirect_with_get_params("login_frontend.views.configure", request.GET.dict()) ret["user"] = user ret["get_params"] = urllib.urlencode(request.GET) back_url = redir_to_sso(request, no_default=True) ret["num_sessions"] = Browser.objects.filter(user=user).count() ret["csp_violations"] = CSPReport.objects.filter( username=user.username).count() ret["authenticator_id"] = user.get_authenticator_id() if back_url: ret["back_url"] = back_url.url response = render_to_response("login_frontend/configure.html", ret, context_instance=RequestContext(request)) return response