Require CAPTCHA for creating new accounts via OAuth
Plugs the remaining hole for bot-based account creation
This commit is contained in:
parent
bae9c593ef
commit
57136eb343
2 changed files with 79 additions and 8 deletions
|
@ -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(),
|
||||
|
|
31
templates/oauthcaptcha.html
Normal file
31
templates/oauthcaptcha.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Confirm · Quay</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block body_content %}
|
||||
<style type="text/css">
|
||||
.captcha-container {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.captcha-container div {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<div class="col-sm-6 col-sm-offset-3">
|
||||
<form name="continueForm" method="post" action="{{ callback_url }}/captcha">
|
||||
<div class="captcha-container" vc-recaptcha ng-model="recaptcha_response" key="'{{ recaptcha_site_key }}'"></div>
|
||||
<input type="hidden" name="recaptcha_response" value="{% raw %}{{ recaptcha_response }}{% endraw %}">
|
||||
<button id="signupButton"
|
||||
class="btn btn-primary btn-block" ng-disabled="continueForm.$invalid" type="submit">
|
||||
Continue
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Reference in a new issue