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 internal_login(request): """ Internal login using Django authentication framework """ custom_log(request, "Internal login requested. back_url=%s" % request.GET.get("next"), level="debug") params = request.GET.dict() params["_sso"] = "internal" ret = {} browser = request.browser if browser is None: custom_log(request, "Browser is not set. Redirect to first step authentication") return redirect_with_get_params("login_frontend.authentication_views.firststepauth", params) if request.GET.get("next") is None: # No back url is defined. Go to front page. custom_log(request, "No back URL is defined. Redirect to the front page", level="debug") return HttpResponseRedirect(reverse("login_frontend.views.indexview")) # TODO: static auth level if browser.get_auth_level() >= Browser.L_STRONG: back_url = request.GET.get("next") custom_log(request, "User is authenticated with strong authentication. Redirecting back to %s" % back_url, level="info") (user, _) = DjangoUser.objects.get_or_create(username=browser.user.username, defaults={"email": browser.user.email, "is_staff": False, "is_active": True, "is_superuser": False, "last_login": datetime.datetime.now(), "date_joined": datetime.datetime.now()}) user.backend = 'django.contrib.auth.backends.ModelBackend' # Horrible hack. django_login(request, user) return HttpResponseRedirect(back_url) custom_log(request, "More authentication is required. Redirect to first step authentication", level="debug") return redirect_with_get_params("login_frontend.authentication_views.firststepauth", params)
def internal_login(request): """ Internal login using Django authentication framework """ custom_log(request, "Internal login requested. back_url=%s" % request.GET.get("next"), level="debug") params = request.GET.dict() params["_sso"] = "internal" ret = {} browser = request.browser if browser is None: custom_log( request, "Browser is not set. Redirect to first step authentication") return redirect_with_get_params( "login_frontend.authentication_views.firststepauth", params) if request.GET.get("next") is None: # No back url is defined. Go to front page. custom_log(request, "No back URL is defined. Redirect to the front page", level="debug") return HttpResponseRedirect(reverse("login_frontend.views.indexview")) # TODO: static auth level if browser.get_auth_level() >= Browser.L_STRONG: back_url = request.GET.get("next") custom_log( request, "User is authenticated with strong authentication. Redirecting back to %s" % back_url, level="info") (user, _) = DjangoUser.objects.get_or_create(username=browser.user.username, defaults={ "email": browser.user.email, "is_staff": False, "is_active": True, "is_superuser": False, "last_login": datetime.datetime.now(), "date_joined": datetime.datetime.now() }) user.backend = 'django.contrib.auth.backends.ModelBackend' # Horrible hack. django_login(request, user) return HttpResponseRedirect(back_url) custom_log( request, "More authentication is required. Redirect to first step authentication", level="debug") return redirect_with_get_params( "login_frontend.authentication_views.firststepauth", params)
def main_redir(request): """ Hack to enable backward compatibility with pubtkt. If "back" parameter is specified, forward to pubtkt provider. Otherwise, go to index page """ if request.GET.get("back") != None: custom_log(request, "Redirecting to pubtkt provider from main: %s" % request.GET.dict(), level="debug") return redirect_with_get_params("login_frontend.providers.pubtkt", request.GET) return redirect_with_get_params("login_frontend.views.indexview", request.GET)
def indexview(request): """ Index page: user is redirected here if no real destination is available. """ # TODO: "valid until" ret = {} user = request.browser.user browser = request.browser if request.method == "POST": if request.POST.get("my_computer"): save_browser = False if request.POST.get("my_computer") == "on": save_browser = True if browser.save_browser != save_browser: browser.save_browser = save_browser browser.save() if save_browser: custom_log(request, "Marked browser as remembered", level="info") add_user_log(request, "Marked browser as remembered", "eye") messages.info(request, "You're now remembered on this browser") else: custom_log(request, "Marked browser as not remembered", level="info") add_user_log(request, "Marked browser as not remembered", "eye-slash") messages.info(request, "You're no longer remembered on this browser") return redirect_with_get_params("login_frontend.views.indexview", request.GET.dict()) ret["username"] = user.username ret["user"] = user ret["get_params"] = urllib.urlencode(request.GET) ret["user_services"] = UserService.objects.filter(user=user).order_by("-access_count")[0:5] if user.password_expires: if user.password_expires > timezone.now(): diff = user.password_expires - timezone.now() if diff < datetime.timedelta(days=14): custom_log(request, "Password is expiring in %s. Show warning" % diff, level="info") ret["password_expiring"] = user.password_expires else: custom_log(request, "Password has expired. Sign user out.", level="warn") browser.logout() return redirect_with_get_params("login_frontend.views.indexview", request.GET) auth_level = request.browser.get_auth_level() if user.emulate_legacy: ret["auth_level"] = "emulate_legacy" ret["session_expire"] = browser.auth_level_valid_until elif auth_level == Browser.L_STRONG: ret["auth_level"] = "strong" elif auth_level == Browser.L_STRONG_SKIPPED: ret["auth_level"] = "strong_skipped" elif auth_level == Browser.L_BASIC: ret["auth_level"] = "basic" ret["remembered"] = browser.save_browser ret["should_timesync"] = browser.should_timesync() response = render_to_response("login_frontend/indexview.html", ret, context_instance=RequestContext(request)) return response
def inner(request, *args, **kwargs): required_level = main_kwargs.get("required_level", Browser.L_STRONG) get_params = request.GET.dict() if get_params.get("_sso") is None: # Avoid redirect loops if current_step not in ("firststepauth", "secondstepauth", "authenticate_with_sms", "authenticate_with_password", "authenticate_with_authenticator", "authenticate_with_emergency"): get_params["_sso"] = "internal" included_get_params = {} for p in INCLUDE_PARAMS_NEXT: if p in get_params: included_get_params[p] = get_params[p] del get_params[p] if len(included_get_params) > 0: get_params["next"] = "%s?%s" % (request.path, urllib.urlencode(included_get_params)) else: get_params["next"] = request.path custom_log(request, "Automatically adding internal SSO. next=%s" % get_params["next"], level="debug") browser = request.browser if browser is None: current_level = Browser.L_UNAUTH else: current_level = int(browser.get_auth_level()) admin_allowed = True if main_kwargs.get("admin_only", False): if not (browser and browser.user and browser.user.is_admin): admin_allowed = False if current_level >= required_level: if not admin_allowed: custom_log(request, "User have no access to admin_only resource %s" % request.path, level="warn") raise PermissionDenied # Authentication level is already satisfied # Execute requested method. return inner_func(request, *args, **kwargs) # Authentication level is not satisfied. Determine correct step for next page. if browser is None: # User is not authenticated. Go to first step. custom_log(request, "Browser object does not exist. Go to first step authentication", level="debug") return redir_view("firststepauth", redirect_with_get_params('login_frontend.authentication_views.firststepauth', get_params)) if browser.get_auth_state() == Browser.S_REQUEST_STRONG: # Login is still valid. Go to second step authentication custom_log(request, "Second step authentication requested.", level="debug") return redir_view("secondstepauth", redirect_with_get_params("login_frontend.authentication_views.secondstepauth", get_params)) # Requested authentication level is not satisfied, and user is not proceeding to the second step. # Start from the beginning. custom_log(request, "Requested authentication level is not satisfied. Start from the first step authentication", level="debug") return redir_view("firststepauth", redirect_with_get_params("login_frontend.authentication_views.firststepauth", get_params))
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 logout(request): """ Forwards SAML 2.0 logout URL to logout page. """ custom_log(request, "Redirecting to logout page from GET logout", level="debug") return redirect_with_get_params("login_frontend.views.logoutview", request.GET)
def indexview(request): """ Index page: user is redirected here if no real destination is available. """ # TODO: "valid until" ret = {} if request.method == "POST": if request.POST.get("my_computer"): save_browser = False if request.POST.get("my_computer") == "on": 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, "Marked browser as remembered", level="info") add_user_log(request, "Marked browser as remembered", "eye") messages.info(request, "You're now remembered on this browser") else: custom_log(request, "Marked browser as not remembered", level="info") add_user_log(request, "Marked browser as not remembered", "eye-slash") messages.info( request, "You're no longer remembered on this browser") return redirect_with_get_params( "login_frontend.views.indexview", request.GET.dict()) ret["username"] = request.browser.user.username ret["user"] = request.browser.user ret["get_params"] = urllib.urlencode(request.GET) ret["user_services"] = UserService.objects.filter( user=request.browser.user).order_by("-access_count")[0:5] auth_level = request.browser.get_auth_level() if request.browser.user.emulate_legacy: ret["auth_level"] = "emulate_legacy" ret["session_expire"] = request.browser.auth_level_valid_until elif auth_level == Browser.L_STRONG: ret["auth_level"] = "strong" elif auth_level == Browser.L_STRONG_SKIPPED: ret["auth_level"] = "strong_skipped" elif auth_level == Browser.L_BASIC: ret["auth_level"] = "basic" ret["remembered"] = request.browser.save_browser ret["should_timesync"] = request.browser.should_timesync() response = render_to_response("login_frontend/indexview.html", ret, context_instance=RequestContext(request)) return response
def logoutview(request): """ Handles logout as well as possible. Only POST requests with valid CSRF token are accepted. In case of a GET request, page with logout button is shown. """ if request.method == 'POST' and hasattr(request, "browser") and request.browser and request.browser.user: ret_dict = request.GET.dict() ret_dict["logout"] = "on" logins = BrowserLogin.objects.filter(user=request.browser.user, browser=request.browser).filter(can_logout=False).filter(signed_out=False).filter(Q(expires_at__gte=timezone.now()) | Q(expires_at=None)) active_sessions = [] for login in logins: active_sessions.append({"sso_provider": login.sso_provider, "remote_service": login.remote_service, "expires_at": login.expires_at, "expires_session": login.expires_session, "auth_timestamp": login.auth_timestamp}) add_user_log(request, "Signed out", "sign-out") custom_log(request, "Signed out") logout_keys = ["username", "authenticated", "authentication_level", "login_time", "relogin_time"] for keyname in logout_keys: try: del request.session[keyname] except KeyError: pass request.browser.logout(request) django_auth.logout(request) request.session["active_sessions"] = active_sessions request.session["logout"] = True return redirect_with_get_params("login_frontend.authentication_views.logoutview", ret_dict) else: ret = {} if request.GET.get("logout") == "on": ret["signed_out"] = True active_sessions = request.session.get("active_sessions", []) if len(active_sessions) > 0: custom_log(request, "logout: active sessions: %s" % active_sessions, level="info") ret["active_sessions"] = active_sessions try: del request.session["active_sessions"] except KeyError: pass get_params = request.GET.dict() try: del get_params["logout"] except KeyError: pass ret["get_params"] = urllib.urlencode(get_params) if request.browser is None: ret["not_logged_in"] = True elif request.browser.get_auth_level() < Browser.L_BASIC: ret["not_logged_in"] = True if request.browser: ret["should_timesync"] = request.browser.should_timesync() return render_to_response("login_frontend/logout.html", ret, context_instance=RequestContext(request))
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 slo_logout(request): """ Receives a SAML 2.0 LogoutRequest from a Service Provider, logs out the user and returns a standard logged-out page. """ if "SAMLRequest" not in request.POST: custom_log(request, "Invalid request to logout page: missing SAMLRequest", level="warn") return render_to_response('saml2idp/error.html', {"missing_fields": True}, context_instance=RequestContext(request)) custom_log(request, "Redirecting to logout page from POST logout", level="debug") return redirect_with_get_params("login_frontend.views.logoutview", request.GET)
def login_begin(request, *args, **kwargs): """ Receives a SAML 2.0 AuthnRequest from a Service Provider and stores it in the session prior to enforcing login. """ if request.method == 'POST': source = request.POST else: source = request.GET if not ('SAMLRequest' in source and 'RelayState' in source): custom_log(request, "Invalid request: missing SAMLRequest or RelayState", level="info") return render_to_response('saml2idp/error.html', {"missing_fields": True}, context_instance=RequestContext(request)) # Store these values now, because Django's login cycle won't preserve them. saml_id = str(uuid.uuid4()) try: parsed = urlparse.urlparse(source['RelayState']) query = urlparse.parse_qs(parsed.query) if "continue" in query: parsed_continue = urlparse.urlparse(query["continue"][0]) return_url = None if parsed_continue.hostname == "www.google.com" and parsed_continue.path.startswith("/calendar/"): return_url = "Google Calendar" elif parsed_continue.hostname: host = parsed_continue.hostname if host == "mail.google.com": return_url = "gmail" elif host == "docs.google.com" or host == "drive.google.com": return_url = "Drive" elif host == "groups.google.com": return_url = "Google Groups" elif host == "plus.google.com": return_url = "Google Plus" if return_url: r.setex("saml-return-%s" % saml_id, return_url, 3600 * 12) except: pass r.setex("saml-SAMLRequest-%s" % saml_id, source['SAMLRequest'], 3600 * 12) r.setex("saml-RelayState-%s" % saml_id, source['RelayState'], 3600 * 12) custom_log(request, "Storing SAMLRequest=%s and RelayState=%s with saml_id=%s" % (source['SAMLRequest'], source['RelayState'], saml_id), level="debug") return redirect_with_get_params("saml2idp.views.login_process", {"saml_id": saml_id})
def logoutview(request): """ Handles logout as well as possible. Only POST requests with valid CSRF token are accepted. In case of a GET request, page with logout button is shown. """ if request.method == 'POST' and hasattr( request, "browser") and request.browser and request.browser.user: ret_dict = request.GET.dict() ret_dict["logout"] = "on" logins = BrowserLogin.objects.filter( user=request.browser.user, browser=request.browser).filter( can_logout=False).filter(signed_out=False).filter( Q(expires_at__gte=timezone.now()) | Q(expires_at=None)) active_sessions = [] for login in logins: active_sessions.append({ "sso_provider": login.sso_provider, "remote_service": login.remote_service, "expires_at": login.expires_at, "expires_session": login.expires_session, "auth_timestamp": login.auth_timestamp }) add_user_log(request, "Signed out", "sign-out") custom_log(request, "Signed out") logout_keys = [ "username", "authenticated", "authentication_level", "login_time", "relogin_time" ] for keyname in logout_keys: try: del request.session[keyname] except KeyError: pass request.browser.logout(request) django_auth.logout(request) request.session["active_sessions"] = active_sessions request.session["logout"] = True return redirect_with_get_params( "login_frontend.authentication_views.logoutview", ret_dict) else: ret = {} if request.GET.get("logout") == "on": ret["signed_out"] = True active_sessions = request.session.get("active_sessions", []) if len(active_sessions) > 0: custom_log(request, "logout: active sessions: %s" % active_sessions, level="info") ret["active_sessions"] = active_sessions try: del request.session["active_sessions"] except KeyError: pass get_params = request.GET.dict() try: del get_params["logout"] except KeyError: pass ret["get_params"] = urllib.urlencode(get_params) if request.browser is None: ret["not_logged_in"] = True elif request.browser.get_auth_level() < Browser.L_BASIC: ret["not_logged_in"] = True if request.browser: ret["should_timesync"] = request.browser.should_timesync() return render_to_response("login_frontend/logout.html", ret, context_instance=RequestContext(request))
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(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_url(request, **kwargs): """ Authenticates user with URL sent via SMS """ def sid_cleanup(sid): keys = ["urlauth-%s-%s" % (k, sid) for k in ("params", "user", "bid")] dcache.delete(keys) template_name = "login_frontend/authenticate_with_url.html" sid = kwargs.get("sid") ret = {} if not hasattr(request, "browser") or not request.browser or not request.browser.user: custom_log(request, "2f-url: No browser object / no signed-in user", level="warn") ret["invalid_request"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) if not dcache.get("urlauth-params-%s" % sid): custom_log(request, "2f-url: sid does not exist, or it expired.", level="warn") ret["invalid_sid"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) username = dcache.get("urlauth-user-%s" % sid) if username != request.browser.user.username: custom_log(request, "2f-url: Tried to access SID that belongs to another user.", level="warn") ret["invalid_request"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) bid_public = dcache.get("urlauth-bid-%s" % sid) if bid_public != request.browser.bid_public: custom_log(request, "2f-url: Tried to access SID with wrong browser. Probably the phone opens SMS links to different browser, or it was actually another phone.", level="warn") ret["wrong_browser"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) get_params = dcache.get("urlauth-params-%s" % sid) try: get_params_dict = json.loads(get_params) except (ValueError, EOFError): custom_log(request, "2f-url: Invalid get_params json from cache", level="warn") ret["invalid_request"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) if request.browser.is_authenticated(): custom_log(request, "2f-url: User is already signed in. Redirect to secondstepauth: %s" % get_params, level="info") sid_cleanup(sid) request.browser.revoke_sms() return redirect_with_get_params("login_frontend.authentication_views.secondstepauth", get_params_dict) if not request.browser.get_auth_level() >= Browser.L_BASIC or not request.browser.get_auth_state() == Browser.S_REQUEST_STRONG: custom_log(request, "2f-url: Browser is in wrong authentication state", level="warn") ret["invalid_auth_state"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) # Everything is fine: # - sid is valid # - browser matches # - user is authenticated # set authentication state and redirect through secondstepauth. # TODO: determine these automatically request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) sid_cleanup(sid) request.browser.revoke_sms() custom_log(request, "2f-url: Successfully authenticated with URL. Redirecting to secondstepauth", level="info") add_user_log(request, "Successfully authenticated with URL.", "lock") return redirect_with_get_params("login_frontend.authentication_views.secondstepauth", get_params_dict)
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
# Store these values now, because Django's login cycle won't preserve them. saml_id = str(uuid.uuid4()) return_url = None try: return_url = parse_google_saml(source["RelayState"]) except Exception, e: log.error("URL parsing exception %s" % e) if return_url: dcache.set("saml-return-%s" % saml_id, return_url, 3600 * 12) dcache.set("saml-SAMLRequest-%s" % saml_id, source['SAMLRequest'], 3600 * 12) dcache.set("saml-RelayState-%s" % saml_id, source['RelayState'], 3600 * 12) custom_log(request, "Storing SAMLRequest=%s and RelayState=%s with saml_id=%s" % (source['SAMLRequest'], source['RelayState'], saml_id), level="debug") return redirect_with_get_params("saml2idp.views.login_process", {"saml_id": saml_id}) @protect_view("saml.login_init", required_level=Browser.L_STRONG) def login_init(request, resource, **kwargs): """ Initiates an IdP-initiated link to a simple SP resource/target URL. """ sp_config = metadata.get_config_for_resource(resource) proc_path = sp_config['processor'] custom_log(request, "login_init: proc_path=%s" % proc_path, level="debug") proc = registry.get_processor(proc_path) try: linkdict = dict(metadata.get_links(sp_config)) pattern = linkdict[resource] except KeyError: raise ImproperlyConfigured('Cannot find link resource in SAML2IDP_REMOTE setting: "%s"' % resource)
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 firststepauth(request): """ Redirects user to appropriate first factor authentication. Currently only username/password query """ return redirect_with_get_params( "login_frontend.authentication_views.authenticate_with_password", request.GET)
def sessions(request): """ Shows sessions to the user. """ user = request.browser.user ret = {} if request.method == "POST": if request.POST.get("logout"): bid_public = request.POST.get("logout") if bid_public == "all": # Log out all sessions custom_log(request, "sessions: user requested signing out all sessions", level="info") bid_public = [ obj.bid_public for obj in Browser.objects.filter(user=user).exclude( bid_public=request.browser.bid_public) ] else: bid_public = [bid_public] custom_log(request, "sessions: signing out sessions: %s" % bid_public, level="debug") self_logout = False for bid in bid_public: try: browser_logout = Browser.objects.get(bid_public=bid) if browser_logout.user != user: custom_log( request, "sessions: Tried to sign out browser that belongs to another user: %s" % bid, level="warn") ret["message"] = "That browser belongs to another user." else: if browser_logout == request.browser: custom_log(request, "sessions: signing out current browser", level="info") self_logout = True browser_identification = browser_logout.get_readable_ua( ) if browser_logout.name: browser_identification = "%s (%s)" % ( browser_logout.name, browser_identification) request_browser_identification = request.browser.get_readable_ua( ) if request.browser.name: request_browser_identification = "%s (%s)" % ( request.browser.name, request_browser_identification) browser_logout.logout() browser_logout.forced_sign_out = True browser_logout.save() custom_log(request, "sessions: Signed out browser %s" % browser_logout.bid_public, level="info") add_user_log( request, "Signed out browser %s" % browser_identification, "sign-out") if not self_logout: add_user_log(request, "Signed out from browser %s" % request_browser_identification, "sign-out", bid_public=browser_logout.bid_public) messages.success( request, "Signed out browser %s" % browser_identification) except Browser.DoesNotExist: ret["message"] = "Invalid browser" if self_logout: get_params = request.GET.dict() get_params["logout"] = "on" return redirect_with_get_params( "login_frontend.views.logoutview", get_params) elif request.POST.get("action") == "rename": try: abrowser = Browser.objects.get( bid_public=request.POST.get("bid_public")) if abrowser.user != request.browser.user: raise Browser.DoesNotExist except Browser.DoesNotExist: messages.warning( request, "Invalid browser. Your changes were not saved") return redirect_with_get_params( "login_frontend.views.sessions", request.GET) val = request.POST.get("name") abrowser.name = val abrowser.save() if val: messages.success(request, "Browser was renamed as '%s'" % val) else: messages.success(request, "Browser name was removed") return redirect_with_get_params("login_frontend.views.sessions", request.GET) browsers = Browser.objects.filter(user=user) sessions = [] for browser in browsers: session = BrowserUsers.objects.get(user=user, browser=browser) details = {"session": session, "browser": browser} if browser == request.browser: details["this_session"] = True details["geo"] = get_geoip_string(session.remote_ip) details["icons"] = browser.get_ua_icons() try: details["p0f"] = BrowserP0f.objects.filter( browser=browser).latest() except BrowserP0f.DoesNotExist: pass try: details["timesync"] = BrowserTime.objects.filter( browser=browser).latest() except BrowserTime.DoesNotExist: pass logins = BrowserLogin.objects.filter( user=user, browser=browser).filter(can_logout=False).filter( signed_out=False).filter( Q(expires_at__gte=timezone.now()) | Q(expires_at=None)) details["logins"] = logins cache_keys = [("last_known_location", "last-known-location-%s"), ("last_known_location_from", "last-known-location-from-%s"), ("last_known_location_timestamp", "last-known-location-timestamp-%s")] for tk, k in cache_keys: r_k = k % browser.bid_public val = dcache.get(r_k) if val: if tk == "last_known_location_timestamp": val = datetime.datetime.fromtimestamp(float(val)) details[tk] = val sessions.append(details) try: sessions.sort(key=lambda item: item.get("session").last_seen, reverse=True) except Exception, e: # In certain cases, session.last_seen is None. custom_log(request, "Unable to sort sessions: %s" % e, level="error")
def pubtkt(request): """ pubtkt login """ def is_valid_back_url(back_url): """ Returns true if back_url should be okay """ if not back_url: return valid_domains = settings.PUBTKT_ALLOWED_DOMAINS parsed_url = urlparse(back_url) if parsed_url.scheme != "https": return "wrong_protocol" if parsed_url.hostname: for domain in valid_domains: if parsed_url.hostname.endswith(domain): break else: return "invalid_domain" else: return "no_hostname" return True custom_log(request, "pubtkt provider initialized. Cookies: %s" % request.COOKIES) ret = {} cookies = [] params = request.GET.dict() params["_sso"] = "pubtkt" ret["get_params"] = urllib.urlencode(params) browser = request.browser if browser is None: custom_log(request, "pubtkt: Browser is not set. Redirect to first step authentication") return redirect_with_get_params("login_frontend.authentication_views.firststepauth", params) show_error_page = False back_url = request.GET.get("back") custom_log(request, "Requested back_url=%s" % back_url, level="info") back_url_status = is_valid_back_url(back_url) if "unauth" in request.GET: ret["unauth"] = True ret["back_url"] = back_url show_error_page = True custom_log(request, "pubtkt: User is not authorized to access %s" % back_url, level="info") elif back_url is None: # No back url is defined. Show error page. show_error_page = True ret["back_url_not_defined"] = True custom_log(request, "pubtkt: back url is not defined", level="info") elif back_url_status != True: show_error_page = True ret["invalid_back_url"] = True ret["invalid_back_url_reason"] = back_url_status ret["back_url"] = back_url custom_log(request, "pubtkt: back url is invalid: %s" % back_url_status, level="info") if show_error_page: return render_to_response("login_frontend/pubtkt_error.html", ret, context_instance=RequestContext(request)) # TODO: static auth level if browser.get_auth_level() >= Browser.L_STRONG: # TODO: ticket expiration time expiration_in_seconds = 3600 * 9 valid_until = int(time.time() + expiration_in_seconds) tokens = json.loads(browser.user.user_tokens) ticket = auth_pubtkt.create_ticket(privkey, browser.user.username, valid_until, tokens=tokens) cookies.append(("auth_pubtkt", {"value": urllib.quote(ticket), "secure": True, "httponly": True, "domain": ".futurice.com"})) ret["back_url"] = back_url invalid_extensions = (".jpg", ".png", ".js", ".json") for extension in invalid_extensions: if back_url.endswith(extension): break else: (obj, created) = UserService.objects.get_or_create(user=browser.user, service_url=back_url, defaults={"access_count": 1}) if not created: obj.access_count += 1 obj.save() response = render_to_response("login_frontend/html_redirect.html", ret, context_instance=RequestContext(request)) # Add/update BrowserLogin d_valid_until = timezone.now() + datetime.timedelta(seconds=expiration_in_seconds) try: (browser_login, _) = BrowserLogin.objects.get_or_create(user=browser.user, browser=browser, sso_provider="pubtkt", signed_out=False, defaults={"auth_timestamp": timezone.now(), "expires_at": d_valid_until, "remote_service": back_url}) browser_login.auth_timestamp = timezone.now() browser_login.expires_at = d_valid_until browser_login.save() except: pass add_user_log(request, "Granted pubtkt access (%s)" % back_url, "share-square-o") # Set cookies for cookie_name, cookie in cookies: custom_log(request, "pubtkt: Setting cookie: %s=%s" % (cookie_name, cookie), level="debug") response.set_cookie(cookie_name, **cookie) custom_log(request, "pubtkt: redirecting back to %s with html redirect", level="info") return response custom_log(request, "pubtkt: additional authentication is required. Redirect to first step authentication") return redirect_with_get_params("login_frontend.authentication_views.firststepauth", params)
def sessions(request): """ Shows sessions to the user. """ user = request.browser.user ret = {} if request.method == "POST": if request.POST.get("logout"): bid_public = request.POST.get("logout") if bid_public == "all": # Log out all sessions custom_log(request, "sessions: user requested signing out all sessions", level="info") bid_public = [obj.bid_public for obj in Browser.objects.filter(user=user).exclude(bid_public=request.browser.bid_public)] else: bid_public = [bid_public] custom_log(request, "sessions: signing out sessions: %s" % bid_public, level="debug") self_logout = False for bid in bid_public: try: browser_logout = Browser.objects.get(bid_public=bid) if browser_logout.user != user: custom_log(request, "sessions: Tried to sign out browser that belongs to another user: %s" % bid, level="warn") ret["message"] = "That browser belongs to another user." else: if browser_logout == request.browser: custom_log(request, "sessions: signing out current browser", level="info") self_logout = True browser_identification = browser_logout.get_readable_ua() if browser_logout.name: browser_identification = "%s (%s)" % (browser_logout.name, browser_identification) request_browser_identification = request.browser.get_readable_ua() if request.browser.name: request_browser_identification = "%s (%s)" % (request.browser.name, request_browser_identification) browser_logout.logout() browser_logout.forced_sign_out = True browser_logout.save() custom_log(request, "sessions: Signed out browser %s" % browser_logout.bid_public, level="info") add_user_log(request, "Signed out browser %s" % browser_identification, "sign-out") if not self_logout: add_user_log(request, "Signed out from browser %s" % request_browser_identification, "sign-out", bid_public=browser_logout.bid_public) messages.success(request, "Signed out browser %s" % browser_identification) except Browser.DoesNotExist: ret["message"] = "Invalid browser" if self_logout: get_params = request.GET.dict() get_params["logout"] = "on" return redirect_with_get_params("login_frontend.views.logoutview", get_params) elif request.POST.get("action") == "rename": try: abrowser = Browser.objects.get(bid_public=request.POST.get("bid_public")) if abrowser.user != request.browser.user: raise Browser.DoesNotExist except Browser.DoesNotExist: messages.warning(request, "Invalid browser. Your changes were not saved") return redirect_with_get_params("login_frontend.views.sessions", request.GET) val = request.POST.get("name") abrowser.name = val abrowser.save() if val: messages.success(request, "Browser was renamed as '%s'" % val) else: messages.success(request, "Browser name was removed") return redirect_with_get_params("login_frontend.views.sessions", request.GET) browsers = Browser.objects.filter(user=user) sessions = [] for browser in browsers: session = BrowserUsers.objects.get(user=user, browser=browser) details = {"session": session, "browser": browser} if browser == request.browser: details["this_session"] = True details["geo"] = get_geoip_string(session.remote_ip) details["icons"] = browser.get_ua_icons() try: details["p0f"] = BrowserP0f.objects.filter(browser=browser).latest() except BrowserP0f.DoesNotExist: pass try: details["timesync"] = BrowserTime.objects.filter(browser=browser).latest() except BrowserTime.DoesNotExist: pass logins = BrowserLogin.objects.filter(user=user, browser=browser).filter(can_logout=False).filter(signed_out=False).filter(Q(expires_at__gte=timezone.now()) | Q(expires_at=None)).filter(expires_at__lte=timezone.now()+datetime.timedelta(days=30)) details["logins"] = logins cache_keys = [("last_known_location", "last-known-location-%s"), ("last_known_location_from", "last-known-location-from-%s"), ("last_known_location_timestamp", "last-known-location-timestamp-%s")] for tk, k in cache_keys: r_k = k % browser.bid_public val = bcache.get(r_k) if val: if tk == "last_known_location_timestamp": val = datetime.datetime.fromtimestamp(float(val)) details[tk] = val sessions.append(details) try: sessions.sort(key=lambda item:item.get("session").last_seen, reverse=True) except Exception, e: # In certain cases, session.last_seen is None. custom_log(request, "Unable to sort sessions: %s" % e, level="error")
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_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 pubtkt(request): """ pubtkt login """ def is_valid_back_url(back_url): """ Returns true if back_url should be okay """ if not back_url: return valid_domains = settings.PUBTKT_ALLOWED_DOMAINS parsed_url = urlparse(back_url) if parsed_url.scheme != "https": return "wrong_protocol" if parsed_url.hostname: for domain in valid_domains: if parsed_url.hostname.endswith(domain): break else: return "invalid_domain" else: return "no_hostname" return True custom_log(request, "pubtkt provider initialized. Cookies: %s" % request.COOKIES) ret = {} cookies = [] params = request.GET.dict() params["_sso"] = "pubtkt" ret["get_params"] = urllib.urlencode(params) browser = request.browser if browser is None: custom_log( request, "pubtkt: Browser is not set. Redirect to first step authentication" ) return redirect_with_get_params( "login_frontend.authentication_views.firststepauth", params) show_error_page = False back_url = request.GET.get("back") custom_log(request, "Requested back_url=%s" % back_url, level="info") back_url_status = is_valid_back_url(back_url) if "unauth" in request.GET: ret["unauth"] = True ret["back_url"] = back_url show_error_page = True custom_log(request, "pubtkt: User is not authorized to access %s" % back_url, level="info") elif back_url is None: # No back url is defined. Show error page. show_error_page = True ret["back_url_not_defined"] = True custom_log(request, "pubtkt: back url is not defined", level="info") elif back_url_status != True: show_error_page = True ret["invalid_back_url"] = True ret["invalid_back_url_reason"] = back_url_status ret["back_url"] = back_url custom_log(request, "pubtkt: back url is invalid: %s" % back_url_status, level="info") if show_error_page: return render_to_response("login_frontend/pubtkt_error.html", ret, context_instance=RequestContext(request)) # TODO: static auth level if browser.get_auth_level() >= Browser.L_STRONG: # TODO: ticket expiration time expiration_in_seconds = 3600 * 9 valid_until = int(time.time() + expiration_in_seconds) tokens = json.loads(browser.user.user_tokens) ticket = auth_pubtkt.create_ticket(privkey, browser.user.username, valid_until, tokens=tokens) cookies.append(("auth_pubtkt", { "value": urllib.quote(ticket), "secure": True, "httponly": True, "domain": ".futurice.com" })) ret["back_url"] = back_url invalid_extensions = (".jpg", ".png", ".js", ".json") for extension in invalid_extensions: if back_url.endswith(extension): break else: (obj, created) = UserService.objects.get_or_create( user=browser.user, service_url=back_url, defaults={"access_count": 1}) if not created: obj.access_count += 1 obj.save() response = render_to_response("login_frontend/html_redirect.html", ret, context_instance=RequestContext(request)) # Add/update BrowserLogin d_valid_until = timezone.now() + datetime.timedelta( seconds=expiration_in_seconds) try: (browser_login, _) = BrowserLogin.objects.get_or_create(user=browser.user, browser=browser, sso_provider="pubtkt", signed_out=False, defaults={ "auth_timestamp": timezone.now(), "expires_at": d_valid_until, "remote_service": back_url }) browser_login.auth_timestamp = timezone.now() browser_login.expires_at = d_valid_until browser_login.save() except: pass add_user_log(request, "Granted pubtkt access (%s)" % back_url, "share-square-o") # Set cookies for cookie_name, cookie in cookies: custom_log(request, "pubtkt: Setting cookie: %s=%s" % (cookie_name, cookie), level="debug") response.set_cookie(cookie_name, **cookie) custom_log(request, "pubtkt: redirecting back to %s with html redirect", level="info") return response custom_log( request, "pubtkt: additional authentication is required. Redirect to first step authentication" ) return redirect_with_get_params( "login_frontend.authentication_views.firststepauth", params)
def firststepauth(request): """ Redirects user to appropriate first factor authentication. Currently only username/password query """ return redirect_with_get_params("login_frontend.authentication_views.authenticate_with_password", request.GET)
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 authenticate_with_url(request, **kwargs): """ Authenticates user with URL sent via SMS """ def sid_cleanup(sid): keys = ["urlauth-%s-%s" % (k, sid) for k in ("params", "user", "bid")] dcache.delete(keys) template_name = "login_frontend/authenticate_with_url.html" sid = kwargs.get("sid") ret = {} if not hasattr( request, "browser") or not request.browser or not request.browser.user: custom_log(request, "2f-url: No browser object / no signed-in user", level="warn") ret["invalid_request"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) if not dcache.get("urlauth-params-%s" % sid): custom_log(request, "2f-url: sid does not exist, or it expired.", level="warn") ret["invalid_sid"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) username = dcache.get("urlauth-user-%s" % sid) if username != request.browser.user.username: custom_log(request, "2f-url: Tried to access SID that belongs to another user.", level="warn") ret["invalid_request"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) bid_public = dcache.get("urlauth-bid-%s" % sid) if bid_public != request.browser.bid_public: custom_log( request, "2f-url: Tried to access SID with wrong browser. Probably the phone opens SMS links to different browser, or it was actually another phone.", level="warn") ret["wrong_browser"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) get_params = dcache.get("urlauth-params-%s" % sid) try: get_params_dict = json.loads(get_params) except (ValueError, EOFError): custom_log(request, "2f-url: Invalid get_params json from cache", level="warn") ret["invalid_request"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) if request.browser.is_authenticated(): custom_log( request, "2f-url: User is already signed in. Redirect to secondstepauth: %s" % get_params, level="info") sid_cleanup(sid) request.browser.revoke_sms() return redirect_with_get_params( "login_frontend.authentication_views.secondstepauth", get_params_dict) if not request.browser.get_auth_level( ) >= Browser.L_BASIC or not request.browser.get_auth_state( ) == Browser.S_REQUEST_STRONG: custom_log(request, "2f-url: Browser is in wrong authentication state", level="warn") ret["invalid_auth_state"] = True return render_to_response(template_name, ret, context_instance=RequestContext(request)) # Everything is fine: # - sid is valid # - browser matches # - user is authenticated # set authentication state and redirect through secondstepauth. # TODO: determine these automatically request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) sid_cleanup(sid) request.browser.revoke_sms() custom_log( request, "2f-url: Successfully authenticated with URL. Redirecting to secondstepauth", level="info") add_user_log(request, "Successfully authenticated with URL.", "lock") return redirect_with_get_params( "login_frontend.authentication_views.secondstepauth", get_params_dict)
def inner(request, *args, **kwargs): required_level = main_kwargs.get("required_level", Browser.L_STRONG) get_params = request.GET.dict() if get_params.get("_sso") is None: # Avoid redirect loops if current_step not in ("firststepauth", "secondstepauth", "authenticate_with_sms", "authenticate_with_password", "authenticate_with_authenticator"): get_params["_sso"] = "internal" get_params["next"] = request.path custom_log(request, "Automatically adding internal SSO. next=%s" % get_params["next"], level="debug") browser = request.browser if browser is None: current_level = Browser.L_UNAUTH else: current_level = int(browser.get_auth_level()) admin_allowed = True if main_kwargs.get("admin_only", False): if not (browser and browser.user and browser.user.is_admin): admin_allowed = False if current_level >= required_level: if not admin_allowed: custom_log( request, "User have no access to admin_only resource %s" % request.path, level="warn") raise PermissionDenied # Authentication level is already satisfied # Execute requested method. return inner_func(request, *args, **kwargs) # Authentication level is not satisfied. Determine correct step for next page. if browser is None: # User is not authenticated. Go to first step. custom_log( request, "Browser object does not exist. Go to first step authentication", level="debug") return redir_view( "firststepauth", redirect_with_get_params( 'login_frontend.authentication_views.firststepauth', get_params)) if browser.get_auth_state() == Browser.S_REQUEST_STRONG: # Login is still valid. Go to second step authentication custom_log(request, "Second step authentication requested.", level="debug") return redir_view( "secondstepauth", redirect_with_get_params( "login_frontend.authentication_views.secondstepauth", get_params)) # Requested authentication level is not satisfied, and user is not proceeding to the second step. # Start from the beginning. custom_log( request, "Requested authentication level is not satisfied. Start from the first step authentication", level="debug") return redir_view( "firststepauth", redirect_with_get_params( "login_frontend.authentication_views.firststepauth", get_params))
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_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_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
return_url = None try: return_url = parse_google_saml(source["RelayState"]) except Exception, e: log.error("URL parsing exception %s" % e) if return_url: dcache.set("saml-return-%s" % saml_id, return_url, 3600 * 12) dcache.set("saml-SAMLRequest-%s" % saml_id, source['SAMLRequest'], 3600 * 12) dcache.set("saml-RelayState-%s" % saml_id, source['RelayState'], 3600 * 12) custom_log(request, "Storing SAMLRequest=%s and RelayState=%s with saml_id=%s" % (source['SAMLRequest'], source['RelayState'], saml_id), level="debug") return redirect_with_get_params("saml2idp.views.login_process", {"saml_id": saml_id}) @login_required def login_init(request, resource, **kwargs): """ Initiates an IdP-initiated link to a simple SP resource/target URL. """ sp_config = metadata.get_config_for_resource(resource) proc_path = sp_config['processor'] custom_log(request, "login_init: proc_path=%s" % proc_path, level="debug") proc = registry.get_processor(proc_path) try: linkdict = dict(metadata.get_links(sp_config)) pattern = linkdict[resource] except KeyError: