Esempio n. 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.")
Esempio n. 3
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
Esempio n. 4
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.")
def authenticate_with_emergency(request):
    """ TODO: emergency code authentication """
    ret = {}
    get_params = request.GET.dict()
    codes = request.browser.user.get_emergency_codes()
    if not codes:
        ret["no_codes_generated"] = True
        custom_log(request, "No codes generated. Can't authenticate with emergency codes.", level="info")
        return render_to_response("login_frontend/no_emergency_available.html", {}, context_instance=RequestContext(request))
    if not codes.valid():
        ret["no_codes_available"] = True
        custom_log(request, "No codes available. Can't authenticate with emergency codes.", level="info")
        return render_to_response("login_frontend/no_emergency_available.html", {}, context_instance=RequestContext(request))

    if request.method == 'POST':
        otp = request.POST.get("otp", "")
        if otp:
            # whitespace is not important, but printed passwords include spaces for readability.
            otp = otp.replace(" ", "")
        if is_ratelimited(request, True, True, ["POST"], None, "30/30s", [request.user.username], "30s_emergency"):
            ret["ratelimited"] = True
            ret["ratelimit_wait_until"] = timezone.now() + datetime.timedelta(seconds=120)
            custom_log(request, "2f-emergency: ratelimited", level="warn")
        elif codes.use_code(otp):
            # Proper code was provided.
            familiar_device = request.browser.user_is_familiar(request.browser.user, Browser.L_STRONG)
            emergency_used_notify(request, codes, familiar_device=familiar_device)
            custom_log(request, "Authenticated with emergency code", level="info")
            add_user_log(request, "Second-factor authentication with emergency code succeeded.", "lock")
            request.browser.save_browser = False # emergency codes are only for temporary authentication
            request.browser.set_auth_level(Browser.L_STRONG)
            request.browser.set_auth_state(Browser.S_AUTHENTICATED)
            return redir_to_sso(request)
        else:
            if re.match("^[0-9]{5,7}$", otp):
                custom_log(request, "Tried to enter SMS/authenticator code", level="info")
                ret["twostep_code"] = True
            if re.match("^[0-9]{8}$", otp):
                custom_log(request, "Tried to use Google apps emergency code", level="info")
                ret["gapps_code"] = True
            custom_log(request, "Incorrect emergency code", level="info")
            ret["invalid_otp"] = True

    ret["generated_at"] = str(codes.generated_at)
    ret["should_timesync"] = request.browser.should_timesync()
    ret["get_params"] = urllib.urlencode(get_params)
    ret["emergency_code_id"] = codes.current_code.code_id
    return render_to_response("login_frontend/authenticate_with_emergency_code.html", ret, context_instance=RequestContext(request))
Esempio n. 6
0
def name_your_browser(request):
    if request.method == 'POST':
        browser_name = request.POST.get("name", "").strip()

        if browser_name and browser_name != request.browser.name:
            if check_browser_name(browser_name):
                custom_log(request, "Set browser name to '%s'" % browser_name, level="info")
                add_user_log(request, "Set browser name to '%s'" % browser_name, "info")
                request.browser.name = browser_name
                request.browser.save()
            else:
                custom_log(request, "Browser name '%s' was rejected." % browser_name, level="info")
        custom_log(request, "Sending auth_state_changed", level="debug")
        if request.GET.get("_sc"):
            request.browser.auth_state_changed()
        return redir_to_sso(request)
    ret = {}
    ret["get_params"] = urllib.urlencode(request.GET)
    response = render_to_response("login_frontend/name_browser.html", ret, context_instance=RequestContext(request))
    return response
def authenticate_with_sms(request):
    """ Authenticate user with SMS.
    Accepts Authenticator codes too.
    """
    # If already authenticated with L_STRONG, redirect back to SSO / frontpage
    if request.browser.is_authenticated():
        custom_log(request, "2f-sms: User is already authenticated. Redirect back to SSO service", level="debug")
        return redir_to_sso(request)

    custom_log(request, "2f-sms: authenticate_with_sms", level="debug")

    user = request.browser.user
    ret = {}
    if not (user.primary_phone or user.secondary_phone):
        # Phone numbers are not available.
        custom_log(request, "2f-sms: No phone number available - unable to authenticate.", level="error")
        ret["should_timesync"] = request.browser.should_timesync()
        return render_to_response("login_frontend/no_phone_available.html", ret, context_instance=RequestContext(request))

    skips_available = user.strong_skips_available
    ret["skips_available"] = skips_available
    ret["return_readable"] = get_return_url(request)

    if request.method == "POST" and request.POST.get("skip"):
        if skips_available > 0:
            user.strong_skips_available -= 1
            user.save()
            add_user_log(request, "Skipped strong authentication: %s left" % user.strong_skips_available, "meh-o")
            custom_log(request, "2f-sms: Skipped strong authentication: %s left" % user.strong_skips_available)
            # TODO: determine the levels automatically.
            request.browser.set_auth_level(Browser.L_STRONG)
            request.browser.set_auth_state(Browser.S_AUTHENTICATED)
            request.browser.set_auth_level_valid_until = timezone.now() + datetime.timedelta(hours=12)
            request.browser.save()
            request.browser.auth_state_changed()
            custom_log(request, "2f-sms: Redirecting back to SSO provider", level="debug")
            return redir_to_sso(request)
        else:
            messages.warning(request, "You can't skip strong authentication anymore.")
            custom_log(request, "2f-sms: Tried to skip strong authentication with no skips available", level="warn")

    if user.strong_configured:
        if user.strong_authenticator_secret:
            ret["can_use_authenticator"] = True
            if not user.strong_authenticator_used:
                ret["authenticator_generated"] = True
    else:
        custom_log(request, "2f-sms: Strong authentication is not configured yet.", level="debug")
        # No strong authentication is configured.
        ret["strong_not_configured"] = True
        if user.strong_authenticator_secret:
            ret["authenticator_generated"] = True
            ret["can_use_authenticator"] = True


    if user.primary_phone_changed:
        custom_log(request, "2f-sms: Phone number has changed.", level="debug")
        # Phone number changed. For security reasons...
        ret["primary_phone_changed"] = True

    if request.browser.name:
        ret["browser_name"] = True

    if request.method == "POST":
        custom_log(request, "2f-sms: POST request", level="debug")
        browser_name = request.POST.get("name")
        ret["browser_name_value"] = browser_name
        if request.POST.get("otp"):
            custom_log(request, "2f-sms: Form is valid", level="debug")
            otp = request.POST.get("otp")
            status, message = request.browser.validate_sms(otp)

            save_browser = False
            if request.POST.get("my_computer"):
                save_browser = True
            if request.browser.save_browser != save_browser:
                request.browser.save_browser = save_browser
                request.browser.save()
                if save_browser:
                    custom_log(request, "2f-sms: Marked browser as remembered", level="info")
                    add_user_log(request, "Marked browser as remembered", "eye")
                else:
                    custom_log(request, "2f-sms: Marked browser as not remembered", level="info")
                    add_user_log(request, "Marked browser as not remembered", "eye-slash")

            if not status:
                # If OTP from SMS did not match, also test for Authenticator OTP.
                custom_log(request, "2f-sms: OTP from SMS did not match, testing Authenticator", level="info")
                (status, _) = request.browser.user.validate_authenticator_code(otp, request)

            if status:
                if browser_name and browser_name != request.browser.name:
                    request.browser.name = browser_name

                # Authentication succeeded.
                custom_log(request, "2f-sms: Second-factor authentication with SMS succeeded")
                add_user_log(request, "Second-factor authentication with SMS succeeded", "lock")
                # TODO: determine the levels automatically.
                request.browser.set_auth_level(Browser.L_STRONG)
                request.browser.set_auth_state(Browser.S_AUTHENTICATED)
                if user.primary_phone_changed:
                    user.primary_phone_changed = False
                    user.save()

                if request.POST.get("timing_data"):
                    custom_log(request, "2f-sms: Saved timing data", level="debug")
                    timing_data = request.POST.get("timing_data")
                    save_timing_data(request, user, timing_data)


                if not user.strong_configured:
                    # Strong authentication is not configured. Go to configuration view.
                    custom_log(request, "2f-sms: User has not configured strong authentication. Redirect to configuration view", level="info")
                    return redirect_with_get_params("login_frontend.views.configure", request.GET)
                # Redirect back to SSO service
                request.browser.auth_state_changed()
                custom_log(request, "2f-sms: Redirecting back to SSO provider", level="debug")
                return redir_to_sso(request)
            else:
                if message:
                    ret["message"] = message
                    custom_log(request, "2f-sms: SMS OTP login failed: %s" % message, level="warn")
                    add_user_log(request, "SMS OTP login failed: %s" % message, "warning")
                else:
                    custom_log(request, "2f-sms: Incorrect SMS OTP", level="warn")
                    add_user_log(request, "Incorrect SMS OTP", "warning")
                    ret["authentication_failed"] = True
        else:
            messages.warning(request, "Invalid input")
    else:
        custom_log(request, "2f-sms: GET request", level="debug")

    if not request.browser.valid_sms_exists(180) or request.POST.get("regen_sms"):
        custom_log(request, "2f-sms: Generating a new SMS code", level="info")
        sms_text = request.browser.generate_sms_text(request=request)
        for phone in (user.primary_phone, user.secondary_phone):
            if phone:
                status = send_sms(phone, sms_text)
                if not status:
                    messages.warning(request, "Sending SMS to %s failed." % phone)
                    custom_log(request, "2f-sms: Sending SMS to %s failed" % phone, level="warn")
                    add_user_log(request, "Sending SMS to %s failed" % phone)
                else:
                    custom_log(request, "2f-sms: Sent OTP to %s" % phone)
                    add_user_log(request, "Sent OTP code to %s" % phone, "info")
                    phone_redacted = "%s...%s" % (phone[0:6], phone[-4:])
                    messages.info(request, mark_safe("Sent SMS to <span class='tooltip-link' title='This is redacted to protect your privacy'>%s</span>" % phone_redacted))
        if request.method == "POST":
            # Redirect to avoid duplicate SMSes on reload.
            return redirect_with_get_params("login_frontend.authentication_views.authenticate_with_sms", request.GET)

    ret["sms_valid_until"] = request.browser.sms_code_generated_at + datetime.timedelta(seconds=900)
    ret["expected_sms_id"] = request.browser.sms_code_id
    ret["get_params"] = urllib.urlencode(request.GET)
    ret["my_computer"] = request.browser.save_browser
    ret["should_timesync"] = request.browser.should_timesync()

    response = render_to_response("login_frontend/authenticate_with_sms.html", ret, context_instance=RequestContext(request))
    return response
def authenticate_with_authenticator(request):
    """ Authenticates user with Google Authenticator """

    custom_log(request, "2f-auth: Requested authentication with Authenticator", level="debug")

    # If already authenticated with L_STRONG, redirect back to SSO / frontpage
    if request.browser.is_authenticated():
        custom_log(request, "2f-auth: User is already authenticated. Redirect back to SSO", level="info")
        return redir_to_sso(request)

    ret = {}
    user = request.browser.user
    assert user != None, "Browser is authenticated but no User object exists."
    ret["return_readable"] = get_return_url(request)

    skips_available = user.strong_skips_available
    ret["skips_available"] = skips_available

    if not user.strong_authenticator_secret:
        # Authenticator is not configured. Redirect back to secondstep main screen
        custom_log(request, "2f-auth: Authenticator is not configured, but user accessed Authenticator view. Redirect back to secondstepauth", level="error")
        messages.warning(request, "You tried to authenticate with Authenticator. However, according to our records, you don't have it configured. Please sign in and go to settings to do that.")
        return redirect_with_get_params("login_frontend.authentication_views.secondstepauth", request.GET)

    if not user.strong_authenticator_used:
        custom_log(request, "2f-auth: Authenticator has not been used. Generated at %s" % user.strong_authenticator_generated_at, level="debug")
        ret["authenticator_not_used"] = True
        ret["authenticator_generated"] = user.strong_authenticator_generated_at

    if request.method == "POST" and request.POST.get("skip"):
        if skips_available > 0:
            user.strong_skips_available -= 1
            user.save()
            add_user_log(request, "Skipped strong authentication: %s left" % user.strong_skips_available, "meh-o")
            custom_log(request, "2f-auth: Skipped strong authentication: %s left" % user.strong_skips_available)
            # TODO: determine the levels automatically.
            request.browser.set_auth_level(Browser.L_STRONG_SKIPPED)
            request.browser.set_auth_state(Browser.S_AUTHENTICATED)
            request.browser.set_auth_level_valid_until = timezone.now() + datetime.timedelta(hours=12)
            request.browser.save()
            custom_log(request, "2f-auth: Redirecting back to SSO provider", level="debug")
            return redir_to_sso(request)
        else:
            messages.warning(request, "You can't skip strong authentication anymore.")
            custom_log(request, "2f-auth: Tried to skip strong authentication with no skips available", level="warn")

    if request.browser.name:
        ret["browser_name"] = True

    if request.method == "POST" and not request.session.test_cookie_worked():
        custom_log(request, "2f-auth: cookies do not work properly", level="warn")
        ret["enable_cookies"] = True

    elif request.method == "POST":
        browser_name = request.POST.get("name")
        ret["browser_name_value"] = browser_name
        request.session.delete_test_cookie()

        custom_log(request, "2f-auth: POST request", level="debug")
        if request.POST.get("otp"):
            custom_log(request, "2f-auth: Form is valid", level="debug")
            otp = request.POST.get("otp")
            custom_log(request, "2f-auth: Testing OTP code %s at %s" % (otp, time.time()), level="debug")
            (status, message) = user.validate_authenticator_code(otp, request)

            save_browser = False
            if request.POST.get("my_computer"):
                save_browser = True
            if request.browser.save_browser != save_browser:
                request.browser.save_browser = save_browser
                request.browser.save()
                if save_browser:
                    custom_log(request, "2f-auth: Marked browser as remembered", level="info")
                    add_user_log(request, "Marked browser as remembered", "eye")
                else:
                    custom_log(request, "2f-auth: Marked browser as not remembered", level="info")
                    add_user_log(request, "Marked browser as not remembered", "eye-slash")

            if not status:
                # If authenticator code did not match, also try latest SMS (if available).
                custom_log(request, "2f-auth: Authenticator code did not match. Testing SMS", level="info")
                status, _ = request.browser.validate_sms(otp)
            if status:
                if browser_name and browser_name != request.browser.name:
                    request.browser.name = browser_name

                custom_log(request, "2f-auth: Second-factor authentication with Authenticator succeeded")
                add_user_log(request, "Second-factor authentication with Authenticator succeeded", "lock")
                # Mark authenticator configuration as valid. User might have configured
                # authenticator but aborted without entering validation code.
                user.strong_authenticator_used = True
                user.strong_configured = True
                user.save()

                if request.POST.get("timing_data"):
                    custom_log(request, "2f-auth: Saving timing data", level="debug")
                    timing_data = request.POST.get("timing_data")
                    save_timing_data(request, user, timing_data)


                # TODO: determine the levels automatically.
                request.browser.set_auth_level(Browser.L_STRONG)
                request.browser.set_auth_state(Browser.S_AUTHENTICATED)
                request.browser.auth_state_changed()
                custom_log(request, "2f-auth: Redirecting back to SSO provider", level="debug")
                return redir_to_sso(request)
            else:
                custom_log(request, "2f-auth: Incorrect Authenticator OTP provided: %s" % message, level="warn")
                add_user_log(request, "Incorrect Authenticator OTP provided: %s" % message, "warning")
                if not re.match("^[0-9]{5,6}$", otp):
                    ret["is_invalid_otp"] = True
                ret["invalid_otp"] = message
        else:
            custom_log(request, "2f-auth: form was not valid", level="debug")
            messages.warning(request, "One-time password field is mandatory.")
    else:
        custom_log(request, "2f-auth: GET request", level="debug")

    ret["user"] = user
    ret["authenticator_id"] = user.get_authenticator_id()
    ret["get_params"] = urllib.urlencode(request.GET)
    ret["my_computer"] = request.browser.save_browser
    ret["should_timesync"] = request.browser.should_timesync()
    request.session.set_test_cookie()

    response = render_to_response("login_frontend/authenticate_with_authenticator.html", ret, context_instance=RequestContext(request))
    return response
def authenticate_with_password(request):
    """ Authenticate with username and password """

    ret = {}
    cookies = []
    browser = None

    ret["return_readable"] = get_return_url(request)

    if request.browser is None:
        # No Browser object is initialized. Create one.
        custom_log(request, "1f: No browser object exists. Create a new one. Cookies: %s" % request.COOKIES, level="debug")
        browser = Browser(bid=create_browser_uuid(), bid_public=create_browser_uuid(), bid_session=create_browser_uuid(), ua=request.META.get("HTTP_USER_AGENT"))
        browser.save()
        cookies.extend(browser.get_cookies())
    else:
        custom_log(request, "1f: Browser object exists", level="debug")
        browser = request.browser
        if browser.get_auth_state() == Browser.S_REQUEST_STRONG:
            # User is already in strong authentication. Redirect them there.
            custom_log(request, "1f: State: REQUEST_STRONG. Redirecting user", level="debug")
            return redirect_with_get_params("login_frontend.authentication_views.secondstepauth", request.GET)
        if browser.is_authenticated():
            # User is already authenticated. Redirect back to SSO service.
            custom_log(request, "1f: User is already authenticated. Redirect back to SSO service.", level="debug")
            return redir_to_sso(request)

    if browser:
        if browser.forced_sign_out:
            custom_log(request, "1f: Browser was remotely signed out.", level="debug")
            ret["forced_sign_out"] = True

        if browser.get_auth_state() == Browser.S_REQUEST_BASIC_ONLY:
            ret["basic_only"] = True
            if not browser.user:
                custom_log(request, "1f: S_REQUEST_BASIC_ONLY was requested, but browser.user does not exist", level="warn")
                messages.warning(request, "Invalid request was encountered. Please sign in again.")
                return redirect_with_get_params("login_frontend.views.indexview", request.GET.dict())
            custom_log(request, "1f: S_REQUEST_BASIC_ONLY requested", level="debug")

    if request.method == 'POST':
        custom_log(request, "1f: POST request", level="debug")
        username = request.POST.get("username")
        if browser.get_auth_state() == Browser.S_REQUEST_BASIC_ONLY:
            # Only basic authentication was requested. Take username from session.
            username = browser.user.username
        password = request.POST.get("password")

        if username and password:
            custom_log(request, "1f: Both username and password exists. username=%s" % username, level="debug")
            auth = LdapLogin(username, password)
            auth_status = auth.login()
            if username != auth.username:
                custom_log(request, "1f: mapped username %s to %s" % (username, auth.username), level="debug")
            username = auth.username # mapped from aliases (email address -> username)

            save_browser = False
            if request.POST.get("my_computer"):
                save_browser = True
            if browser.save_browser != save_browser:
                browser.save_browser = save_browser
                browser.save()
                if save_browser:
                    custom_log(request, "1f: Marked browser as remembered", level="info")
                    add_user_log(request, "Marked browser as remembered", "eye")
                else:
                    custom_log(request, "1f: Marked browser as not remembered", level="info")
                    add_user_log(request, "Marked browser as not remembered", "eye-slash")

            if auth_status == True:
                # User signed in, so there's no reason to keep forced_sign_out anymore.
                browser.forced_sign_out = False
                browser_name = dcache.get("browser-name-for-%s-%s" % (browser.bid_public, username))
                if browser_name:
                    # This user named this browser before signing out. Restore that name.
                    browser.name = browser_name

                if browser.user is None:
                    custom_log(request, "1f: browser.user is None: %s" % username, level="debug")
                    (user, _) = User.objects.get_or_create(username=username)
                    user.user_tokens = json.dumps(auth.get_auth_tokens())
                    custom_log(request, "User tokens: %s" % user.user_tokens, level="info")
                    user.save()
                    browser.user = user
                    # Delete cached counter
                    dcache.delete("num_sessions-%s" % username)

                request.browser = browser

                custom_log(request, "1f: Successfully logged in using username and password")
                add_user_log(request, "Successfully logged in using username and password", "sign-in")

                if request.POST.get("timing_data"):
                    custom_log(request, "1f: saving timing data", level="debug")
                    timing_data = request.POST.get("timing_data")
                    save_timing_data(request, browser.user, timing_data)

                if browser.user.emulate_legacy:
                    custom_log(request, "1f: Emulating legacy SSO", level="info")
                    # This is a special case for emulating legacy system:
                    # - no two-factor authentication
                    # - all logins expire in 12 hours
                    browser.set_auth_level(Browser.L_STRONG_SKIPPED)
                    browser.set_auth_state(Browser.S_AUTHENTICATED)
                    custom_log(request, "1f: Redirecting back to SSO service", level="info")
                    return redir_to_sso(request)

                # TODO: no further authentication is necessarily needed. Determine these automatically.
                if browser.get_auth_state() == Browser.S_REQUEST_BASIC_ONLY:
                    # Only basic authentication is required.
                    custom_log(request, "1f: only basic authentication was required. Upgrade directly to L_STRONG and S_AUTHENTICATED")
                    browser.set_auth_level(Browser.L_STRONG)
                    browser.set_auth_state(Browser.S_AUTHENTICATED)
                else:
                    # Continue to strong authentication
                    custom_log(request, "1f: set L_BASIC and S_REQUEST_STRONG")
                    browser.set_auth_level(Browser.L_BASIC)
                    browser.set_auth_state(Browser.S_REQUEST_STRONG)

                return redirect_with_get_params("login_frontend.authentication_views.secondstepauth", request.GET)
            else:
                if auth_status == "invalid_credentials":
                    ret["authentication_failed"] = True
                    if re.match("^[0-9]{5,6}$", password):
                        ret["is_otp"] = True
                    custom_log(request, "1f: Authentication failed. Invalid credentials", level="warn")
                    add_user_log(request, "Authentication failed. Invalid credentials", "warning")
                elif auth_status == "server_down":
                    messages.warning(request, "Unable to connect user directory (LDAP). Could not proceed with authentication. Please try again later, and/or contact IT team.")
                    custom_log(request, "1f: LDAP server is down.", level="error")
                else:
                    ret["message"] = auth_status
                    custom_log(request, "1f: Authentication failed: %s" % auth_status, level="warn")
                    add_user_log(request, "Authentication failed: %s" % auth_status, "warning")
        else:
            custom_log(request, "1f: Either username or password is missing.", level="warn")
            messages.warning(request, "Please enter both username and password.")
    else:
        custom_log(request, "1f: GET request", level="debug")
    if browser:
        ret["my_computer"] = browser.save_browser

    # Keep GET query parameters in form posts.
    ret["get_params"] = urllib.urlencode(request.GET)
    custom_log(request, "1f: Query parameters: %s" % ret["get_params"], level="debug")
    response = render_to_response("login_frontend/authenticate_with_password.html", ret, context_instance=RequestContext(request))
    for cookie_name, cookie in cookies:
        custom_log(request, "Setting cookie %s=%s" % (cookie_name, cookie))
        response.set_cookie(cookie_name, **cookie)
    return response
Esempio n. 10
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))
Esempio n. 11
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
Esempio n. 12
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
Esempio n. 13
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
Esempio n. 14
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
Esempio n. 15
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
Esempio n. 16
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