From 57136eb34322593678982d2ac923cf27673fa436 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 8 Aug 2017 15:15:29 -0400 Subject: [PATCH] Require CAPTCHA for creating new accounts via OAuth Plugs the remaining hole for bot-based account creation --- endpoints/oauth/login.py | 56 +++++++++++++++++++++++++++++++------ templates/oauthcaptcha.html | 31 ++++++++++++++++++++ 2 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 templates/oauthcaptcha.html diff --git a/endpoints/oauth/login.py b/endpoints/oauth/login.py index 3c21fde29..0a5e76618 100644 --- a/endpoints/oauth/login.py +++ b/endpoints/oauth/login.py @@ -1,7 +1,9 @@ import logging +import time +import recaptcha2 from collections import namedtuple -from flask import request, redirect, url_for, Blueprint +from flask import request, redirect, url_for, Blueprint, abort, session from peewee import IntegrityError import features @@ -11,8 +13,8 @@ from auth.auth_context import get_authenticated_user from auth.decorators import require_session_login from data import model from endpoints.common import common_login -from endpoints.web import index -from endpoints.csrf import csrf_protect, OAUTH_CSRF_TOKEN_NAME +from endpoints.web import index, render_page_template_with_routedata +from endpoints.csrf import csrf_protect, OAUTH_CSRF_TOKEN_NAME, generate_csrf_token from oauth.login import OAuthLoginException from util.validation import generate_valid_usernames @@ -24,10 +26,12 @@ oauthlogin_csrf_protect = csrf_protect(OAUTH_CSRF_TOKEN_NAME, 'state', all_metho OAuthResult = namedtuple('oauthresult', ['user_obj', 'service_name', 'error_message', - 'register_redirect']) + 'register_redirect', 'requires_verification']) -def _oauthresult(user_obj=None, service_name=None, error_message=None, register_redirect=False): - return OAuthResult(user_obj, service_name, error_message, register_redirect) +def _oauthresult(user_obj=None, service_name=None, error_message=None, register_redirect=False, + requires_verification=False): + return OAuthResult(user_obj, service_name, error_message, register_redirect, + requires_verification) def _get_response(result): if result.error_message is not None: @@ -35,7 +39,8 @@ def _get_response(result): return _perform_login(result.user_obj, result.service_name) -def _conduct_oauth_login(auth_system, login_service, lid, lusername, lemail, metadata=None): +def _conduct_oauth_login(auth_system, login_service, lid, lusername, lemail, metadata=None, + captcha_verified=False): """ Conducts login from the result of an OAuth service's login flow and returns the status of the login, as well as the followup step. """ service_id = login_service.service_id() @@ -85,6 +90,9 @@ def _conduct_oauth_login(auth_system, login_service, lid, lusername, lemail, met error_message = 'User creation is disabled. Please contact your administrator' return _oauthresult(service_name=service_name, error_message=error_message) + if features.RECAPTCHA and not captcha_verified: + return _oauthresult(service_name=service_name, requires_verification=True) + # Try to create the user try: # Generate a valid username. @@ -187,8 +195,17 @@ def _register_service(login_service): 'service_username': lusername, } + # Conduct OAuth login. + captcha_verified = (int(time.time()) - session.get('captcha_verified', 0)) <= 600 + session['captcha_verified'] = 0 + result = _conduct_oauth_login(authentication, login_service, lid, lusername, lemail, - metadata=metadata) + metadata=metadata, captcha_verified=captcha_verified) + if result.requires_verification: + return render_page_template_with_routedata('oauthcaptcha.html', + recaptcha_site_key=app.config['RECAPTCHA_SITE_KEY'], + callback_url=request.base_url) + return _get_response(result) @@ -215,6 +232,29 @@ def _register_service(login_service): return redirect(url_for('web.user_view', path=user_obj.username, tab='external')) + def captcha_func(): + recaptcha_response = request.values.get('recaptcha_response', '') + result = recaptcha2.verify(app.config['RECAPTCHA_SECRET_KEY'], + recaptcha_response, + request.remote_addr) + + if not result['success']: + abort(400) + + # Save that the captcha was verified. + session['captcha_verified'] = int(time.time()) + + # Redirect to the normal OAuth flow again, so that the user can now create an account. + csrf_token = generate_csrf_token(OAUTH_CSRF_TOKEN_NAME) + login_scopes = login_service.get_login_scopes() + auth_url = login_service.get_auth_url(app.config, '', csrf_token, login_scopes) + return redirect(auth_url) + + + oauthlogin.add_url_rule('/%s/callback/captcha' % login_service.service_id(), + '%s_oauth_captcha' % login_service.service_id(), + captcha_func, + methods=['POST']) oauthlogin.add_url_rule('/%s/callback' % login_service.service_id(), '%s_oauth_callback' % login_service.service_id(), diff --git a/templates/oauthcaptcha.html b/templates/oauthcaptcha.html new file mode 100644 index 000000000..47baa6688 --- /dev/null +++ b/templates/oauthcaptcha.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% block title %} + Confirm ยท Quay +{% endblock %} + +{% block body_content %} + + +
+
+
+
+ + +
+
+
+{% endblock %}