예제 #1
0
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.")
예제 #3
0
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)
예제 #4
0
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)
예제 #5
0
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)
예제 #6
0
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
예제 #7
0
        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))
예제 #8
0
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)
예제 #9
0
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.")
예제 #10
0
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)
예제 #11
0
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))
예제 #13
0
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
예제 #14
0
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)
예제 #15
0
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})
예제 #16
0
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)
예제 #17
0
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)
예제 #18
0
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
예제 #20
0
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
예제 #23
0
    # 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)
예제 #24
0
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
예제 #25
0
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)
예제 #26
0
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")
예제 #27
0
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)
예제 #28
0
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")
예제 #29
0
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
예제 #30
0
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))
예제 #31
0
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)
예제 #33
0
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
예제 #34
0
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)
예제 #35
0
        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
예제 #37
0
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
예제 #38
0
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
예제 #39
0
    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: