def _is_ratelimited(request): """Ratelimiting helper for kbforum threads and replies. They are ratelimited together with the same key. """ return ( is_ratelimited(request, increment=True, rate='4/m', ip=False, keys=user_or_ip('kbforum-post-min')) or is_ratelimited(request, increment=True, rate='50/d', ip=False, keys=user_or_ip('kbforum-post-day')))
def _wrapped(request, *args, **kw): request.limited = getattr(request, 'limited', False) if skip_if is None or not skip_if(request): ratelimited = is_ratelimited(request=request, increment=False, ip=ip, method=method, field=field, rate=rate, keys=keys) if ratelimited and block: raise Ratelimited() return_val, success = fn(request, *args, **kw) if success: is_ratelimited(request=request, increment=True, ip=ip, method=method, field=field, rate=rate, keys=keys) return return_val
def new_message(request, template): """Send a new private message.""" to = request.GET.get('to') if to: try: User.objects.get(username=to) except User.DoesNotExist: contrib_messages.add_message( request, contrib_messages.ERROR, _('Invalid username provided. Enter a new username below.')) return HttpResponseRedirect(reverse('messages.new')) form = MessageForm(request.POST or None, initial={'to': to}) if (request.method == 'POST' and form.is_valid() and not is_ratelimited(request, increment=True, rate='50/d', ip=False, keys=user_or_ip('private-message-day'))): send_message(form.cleaned_data['to'], form.cleaned_data['message'], request.user) if form.cleaned_data['in_reply_to']: irt = form.cleaned_data['in_reply_to'] try: m = InboxMessage.objects.get(pk=irt, to=request.user) m.update(replied=True) except InboxMessage.DoesNotExist: pass contrib_messages.add_message(request, contrib_messages.SUCCESS, _('Your message was sent!')) return HttpResponseRedirect(reverse('messages.inbox')) return render(request, template, {'form': form})
def do_increment(request): return is_ratelimited(request, increment=True, ip=False, method=None, keys=[get_keys], rate='1/m')
def new_message(request, template): """Send a new private message.""" to = request.GET.get("to") if to: try: User.objects.get(username=to) except User.DoesNotExist: contrib_messages.add_message( request, contrib_messages.ERROR, _("Invalid username provided. Enter a new username below.") ) return HttpResponseRedirect(reverse("messages.new")) form = MessageForm(request.POST or None, initial={"to": to}) if ( request.method == "POST" and form.is_valid() and not is_ratelimited(request, increment=True, rate="50/d", ip=False, keys=user_or_ip("private-message-day")) ): send_message(form.cleaned_data["to"], form.cleaned_data["message"], request.user) if form.cleaned_data["in_reply_to"]: irt = form.cleaned_data["in_reply_to"] try: m = InboxMessage.objects.get(pk=irt, to=request.user) m.update(replied=True) except InboxMessage.DoesNotExist: pass contrib_messages.add_message(request, contrib_messages.SUCCESS, _("Your message was sent!")) return HttpResponseRedirect(reverse("messages.inbox")) return render(request, template, {"form": form})
def new_thread(request, forum_slug): """Start a new thread.""" forum = get_object_or_404(Forum, slug=forum_slug) user = request.user if not forum.allows_posting_by(user): if forum.allows_viewing_by(user): raise PermissionDenied else: raise Http404 if request.method == 'GET': form = NewThreadForm() return render(request, 'forums/new_thread.html', { 'form': form, 'forum': forum }) form = NewThreadForm(request.POST) post_preview = None if form.is_valid(): if 'preview' in request.POST: thread = Thread(creator=request.user, title=form.cleaned_data['title']) post_preview = Post(thread=thread, author=request.user, content=form.cleaned_data['content']) post_preview.author_post_count = \ post_preview.author.post_set.count() elif (_skip_post_ratelimit(request) or not is_ratelimited(request, increment=True, rate='5/d', ip=False, keys=user_or_ip('forum-post'))): thread = forum.thread_set.create(creator=request.user, title=form.cleaned_data['title']) thread.save() statsd.incr('forums.thread') post = thread.new_post(author=request.user, content=form.cleaned_data['content']) post.save() NewThreadEvent(post).fire(exclude=post.author) # Add notification automatically if needed. if Setting.get_for_user(request.user, 'forums_watch_new_thread'): NewPostEvent.notify(request.user, thread) url = reverse('forums.posts', args=[forum_slug, thread.id]) return HttpResponseRedirect(urlparams(url, last=post.id)) return render(request, 'forums/new_thread.html', { 'form': form, 'forum': forum, 'post_preview': post_preview })
def _wrapped(request, *args, **kwargs): already_limited = getattr(request, 'limited', False) ratelimited = is_ratelimited( request=request, increment=True, ip=False, method=['POST'], field=None, rate=rate, keys=keyfun) if not already_limited and ratelimited: statsd.incr('throttled.' + rulename) return fn(request, *args, **kwargs)
def allow_request(self, request, view): ratelimited = is_ratelimited( request=request, increment=True, ip=False, method=self.methods, field=None, rate=self.rate, keys=self.keyfun) if ratelimited: # Failed rate-limiting, so this request is not allowed. statsd.incr('throttled.' + self.rulename) return self.throttle_failure() # Did not trigger rate-limiting, so this request is allowed. return self.throttle_success()
def _wrapped(request, *args, **kw): request.limited = getattr(request, 'limited', False) if skip_if is None or not skip_if(request): ratelimited = is_ratelimited(request=request, increment=True, ip=ip, method=method, field=field, rate=rate, keys=keys) if ratelimited and block: raise Ratelimited() return fn(request, *args, **kw)
def _wrapped(request, *args, **kwargs): already_limited = getattr(request, 'limited', False) ratelimited = is_ratelimited(request=request, increment=True, ip=False, method=['POST'], field=None, rate=rate, keys=keyfun) if not already_limited and ratelimited: statsd.incr('throttled.' + rulename) return fn(request, *args, **kwargs)
def authenticate_with_emergency(request): """ TODO: emergency code authentication """ ret = {} get_params = request.GET.dict() codes = request.browser.user.get_emergency_codes() if not codes: ret["no_codes_generated"] = True custom_log(request, "No codes generated. Can't authenticate with emergency codes.", level="info") return render_to_response("login_frontend/no_emergency_available.html", {}, context_instance=RequestContext(request)) if not codes.valid(): ret["no_codes_available"] = True custom_log(request, "No codes available. Can't authenticate with emergency codes.", level="info") return render_to_response("login_frontend/no_emergency_available.html", {}, context_instance=RequestContext(request)) if request.method == 'POST': otp = request.POST.get("otp", "") if otp: # whitespace is not important, but printed passwords include spaces for readability. otp = otp.replace(" ", "") if is_ratelimited(request, True, True, ["POST"], None, "30/30s", [request.user.username], "30s_emergency"): ret["ratelimited"] = True ret["ratelimit_wait_until"] = timezone.now() + datetime.timedelta(seconds=120) custom_log(request, "2f-emergency: ratelimited", level="warn") elif codes.use_code(otp): # Proper code was provided. familiar_device = request.browser.user_is_familiar(request.browser.user, Browser.L_STRONG) emergency_used_notify(request, codes, familiar_device=familiar_device) custom_log(request, "Authenticated with emergency code", level="info") add_user_log(request, "Second-factor authentication with emergency code succeeded.", "lock") request.browser.save_browser = False # emergency codes are only for temporary authentication request.browser.set_auth_level(Browser.L_STRONG) request.browser.set_auth_state(Browser.S_AUTHENTICATED) return redir_to_sso(request) else: if re.match("^[0-9]{5,7}$", otp): custom_log(request, "Tried to enter SMS/authenticator code", level="info") ret["twostep_code"] = True if re.match("^[0-9]{8}$", otp): custom_log(request, "Tried to use Google apps emergency code", level="info") ret["gapps_code"] = True custom_log(request, "Incorrect emergency code", level="info") ret["invalid_otp"] = True ret["generated_at"] = str(codes.generated_at) ret["should_timesync"] = request.browser.should_timesync() ret["get_params"] = urllib.urlencode(get_params) ret["emergency_code_id"] = codes.current_code.code_id return render_to_response("login_frontend/authenticate_with_emergency_code.html", ret, context_instance=RequestContext(request))
def new_thread(request, forum_slug): """Start a new thread.""" forum = get_object_or_404(Forum, slug=forum_slug) user = request.user if not forum.allows_posting_by(user): if forum.allows_viewing_by(user): raise PermissionDenied else: raise Http404 if request.method == 'GET': form = NewThreadForm() return render(request, 'forums/new_thread.html', { 'form': form, 'forum': forum}) form = NewThreadForm(request.POST) post_preview = None if form.is_valid(): if 'preview' in request.POST: thread = Thread(creator=request.user, title=form.cleaned_data['title']) post_preview = Post(thread=thread, author=request.user, content=form.cleaned_data['content']) post_preview.author_post_count = \ post_preview.author.post_set.count() elif (_skip_post_ratelimit(request) or not is_ratelimited(request, increment=True, rate='5/d', ip=False, keys=user_or_ip('forum-post'))): thread = forum.thread_set.create(creator=request.user, title=form.cleaned_data['title']) thread.save() statsd.incr('forums.thread') post = thread.new_post(author=request.user, content=form.cleaned_data['content']) post.save() NewThreadEvent(post).fire(exclude=post.author) # Add notification automatically if needed. if Setting.get_for_user(request.user, 'forums_watch_new_thread'): NewPostEvent.notify(request.user, thread) url = reverse('forums.posts', args=[forum_slug, thread.id]) return HttpResponseRedirect(urlparams(url, last=post.id)) return render(request, 'forums/new_thread.html', { 'form': form, 'forum': forum, 'post_preview': post_preview})
def reply(request, forum_slug, thread_id): """Reply to a thread.""" forum = get_object_or_404(Forum, slug=forum_slug) user = request.user if not forum.allows_posting_by(user): if forum.allows_viewing_by(user): raise PermissionDenied else: raise Http404 form = ReplyForm(request.POST) post_preview = None if form.is_valid(): thread = get_object_or_404(Thread, pk=thread_id, forum=forum) if not thread.is_locked: reply_ = form.save(commit=False) reply_.thread = thread reply_.author = request.user if 'preview' in request.POST: post_preview = reply_ post_preview.author_post_count = \ reply_.author.post_set.count() elif (_skip_post_ratelimit(request) or not is_ratelimited(request, increment=True, rate='15/d', ip=False, keys=user_or_ip('forum-post'))): reply_.save() statsd.incr('forums.reply') # Subscribe the user to the thread. if Setting.get_for_user(request.user, 'forums_watch_after_reply'): NewPostEvent.notify(request.user, thread) # Send notifications to thread/forum watchers. NewPostEvent(reply_).fire(exclude=reply_.author) return HttpResponseRedirect(thread.get_last_post_url()) return posts(request, forum_slug, thread_id, form, post_preview, is_reply=True)
def allow_request(self, request, view): ratelimited = is_ratelimited(request=request, increment=True, ip=False, method=self.methods, field=None, rate=self.rate, keys=self.keyfun) if ratelimited: # Failed rate-limiting, so this request is not allowed. statsd.incr('throttled.' + self.rulename) return self.throttle_failure() # Did not trigger rate-limiting, so this request is allowed. return self.throttle_success()
def reply(request, forum_slug, thread_id): """Reply to a thread.""" forum = get_object_or_404(Forum, slug=forum_slug) user = request.user if not forum.allows_posting_by(user): if forum.allows_viewing_by(user): raise PermissionDenied else: raise Http404 form = ReplyForm(request.POST) post_preview = None if form.is_valid(): thread = get_object_or_404(Thread, pk=thread_id, forum=forum) if not thread.is_locked: reply_ = form.save(commit=False) reply_.thread = thread reply_.author = request.user if 'preview' in request.POST: post_preview = reply_ post_preview.author_post_count = \ reply_.author.post_set.count() elif (_skip_post_ratelimit(request) or not is_ratelimited(request, increment=True, rate='5/d', ip=False, keys=user_or_ip('forum-post'))): reply_.save() statsd.incr('forums.reply') # Subscribe the user to the thread. if Setting.get_for_user(request.user, 'forums_watch_after_reply'): NewPostEvent.notify(request.user, thread) # Send notifications to thread/forum watchers. NewPostEvent(reply_).fire(exclude=reply_.author) return HttpResponseRedirect(thread.get_last_post_url()) return posts(request, forum_slug, thread_id, form, post_preview, is_reply=True)
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) if is_ratelimited(request, True, True, ["POST"], None, "10/30s", [request.user.username], "30s_url"): ret["ratelimited"] = True ret["ratelimit_wait_until"] = timezone.now() + datetime.timedelta(seconds=120) custom_log(request, "2f-url: ratelimited") return render_to_response(template_name, ret, context_instance=RequestContext(request)) 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_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." 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 emergency_codes = user.get_emergency_codes() if emergency_codes and emergency_codes.valid(): ret["can_use_emergency"] = True 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 is_ratelimited(request, True, False, ["POST"], None, "30/30s", [request.user.username], "30s_2f"): ret["ratelimited"] = True ret["ratelimit_wait_until"] = timezone.now() + datetime.timedelta(seconds=120) custom_log(request, "2f-auth: ratelimited", level="warn") elif request.POST.get("otp"): custom_log(request, "2f-auth: Form is valid", level="debug") otp = request.POST.get("otp") otp = otp.replace(" ", "") # spaces doesn't matter 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 not status: # If neither SMS or Authenticator matched, test Yubikey. custom_log(request, "2f-auth: OTP from SMS/Authenticator did not match. Try Yubikey.", level="info") (status, yubikey_message) = validate_yubikey(user, otp) if yubikey_message: message = yubikey_message if status: request.browser.twostep_last_entered_at = timezone.now() if browser_name and browser_name != request.browser.name: request.browser.name = browser_name new_device_notify(request, "authenticator") 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) if request.browser.name: 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: Browser name is not set. Redirect to naming view", level="debug") # Don't send auth_state_changed(), as it would redirect all browser windows to name form get_params = request.GET.dict() get_params["_sc"] = "on" return redirect_with_get_params("login_frontend.views.name_your_browser", get_params) else: custom_log(request, "2f-auth: Incorrect Authenticator OTP provided: %s" % message, level="warn") add_user_log(request, "Incorrect Authenticator OTP provided: %s" % message, "warning") if not re.match("^[0-9]{5,6}$", otp): ret["is_invalid_otp"] = True ret["invalid_otp"] = message else: custom_log(request, "2f-auth: form was not valid", level="debug") messages.warning(request, "One-time password field is mandatory.") else: custom_log(request, "2f-auth: GET request", level="debug") ret["user"] = user ret["authenticator_id"] = user.get_authenticator_id() ret["get_params"] = urllib.urlencode(request.GET) ret["my_computer"] = request.browser.save_browser ret["should_timesync"] = request.browser.should_timesync() request.session.set_test_cookie() response = render_to_response("login_frontend/authenticate_with_authenticator.html", ret, context_instance=RequestContext(request)) return response
def authenticate_with_sms(request): """ Authenticate user with SMS. Accepts Authenticator codes too. """ # If already authenticated with L_STRONG, redirect back to SSO / frontpage if request.browser.is_authenticated(): custom_log(request, "2f-sms: User is already authenticated. Redirect back to SSO service", level="debug") return redir_to_sso(request) custom_log(request, "2f-sms: authenticate_with_sms", level="debug") user = request.browser.user ret = {} if not (user.primary_phone or user.secondary_phone): # Phone numbers are not available. custom_log(request, "2f-sms: No phone number available - unable to authenticate.", level="error") ret["should_timesync"] = request.browser.should_timesync() return render_to_response("login_frontend/no_phone_available.html", ret, context_instance=RequestContext(request)) skips_available = user.strong_skips_available ret["skips_available"] = skips_available 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") emergency_codes = user.get_emergency_codes() if emergency_codes and emergency_codes.valid(): ret["can_use_emergency"] = True 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 is_ratelimited(request, True, True, ["POST"], None, "60/15m", [request.user.username], "30s_sms"): ret["ratelimited"] = True ret["ratelimit_wait_until"] = timezone.now() + datetime.timedelta(seconds=900) custom_log(request, "2f-sms: ratelimited", level="warn") elif request.POST.get("otp"): custom_log(request, "2f-sms: Form is valid", level="debug") otp = request.POST.get("otp") otp = otp.replace(" ", "") # spaces doesn't matter 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 not status: # If neither SMS or Authenticator matched, test Yubikey. custom_log(request, "2f-sms: OTP from SMS/Authenticator did not match. Try Yubikey.", level="info") (status, yubikey_message) = validate_yubikey(user, otp) if yubikey_message: message = yubikey_message if status: request.browser.twostep_last_entered_at = timezone.now() if browser_name and browser_name != request.browser.name: request.browser.name = browser_name new_device_notify(request, "sms") # 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 if request.browser.name: request.browser.auth_state_changed() custom_log(request, "2f-sms: Redirecting back to SSO provider", level="debug") return redir_to_sso(request) else: custom_log(request, "2f-sms: Browser name is not set. Redirect to naming view", level="debug") # Don't send auth_state_changed(), as it would redirect all browser windows to name form get_params = request.GET.dict() get_params["_sc"] = "on" return redirect_with_get_params("login_frontend.views.name_your_browser", get_params) 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