Commit 920f13a937341eb52a0b26d4924cf399fc994b52

Authored by Aurélio A. Heckert
1 parent 05dbd36f

Force minimum signup time to block bots. Also add captcha for fast submitters

ActionItem2556
app/controllers/public/account_controller.rb
@@ -56,8 +56,8 @@ class AccountController < ApplicationController @@ -56,8 +56,8 @@ class AccountController < ApplicationController
56 end 56 end
57 57
58 def signup_time 58 def signup_time
59 - set_signup_time_for_now  
60 - render :text => {:ok=>true}.to_json 59 + key = set_signup_start_time_for_now
  60 + render :text => { :ok=>true, :key=>key }.to_json
61 end 61 end
62 62
63 # action to register an user to the application 63 # action to register an user to the application
@@ -83,12 +83,12 @@ class AccountController < ApplicationController @@ -83,12 +83,12 @@ class AccountController < ApplicationController
83 @person.environment = @user.environment 83 @person.environment = @user.environment
84 if request.post? 84 if request.post?
85 if may_be_a_bot 85 if may_be_a_bot
86 - set_signup_time_for_now 86 + set_signup_start_time_for_now
87 @block_bot = true 87 @block_bot = true
88 session[:may_be_a_bot] = true 88 session[:may_be_a_bot] = true
89 else 89 else
90 if session[:may_be_a_bot] 90 if session[:may_be_a_bot]
91 - return false unless verify_recaptcha :model=>@user, :message=>_('bota o recaptcha manuel!') 91 + return false unless verify_recaptcha :model=>@user, :message=>_('Captcha (the human test)')
92 end 92 end
93 @user.signup! 93 @user.signup!
94 owner_role = Role.find_by_name('owner') 94 owner_role = Role.find_by_name('owner')
@@ -112,6 +112,7 @@ class AccountController < ApplicationController @@ -112,6 +112,7 @@ class AccountController < ApplicationController
112 @person.errors.delete(:user_id) 112 @person.errors.delete(:user_id)
113 render :action => 'signup' 113 render :action => 'signup'
114 end 114 end
  115 + clear_signup_start_time
115 end 116 end
116 117
117 # action to perform logout from the application 118 # action to perform logout from the application
@@ -287,13 +288,33 @@ class AccountController < ApplicationController @@ -287,13 +288,33 @@ class AccountController < ApplicationController
287 @cannot_redirect = true 288 @cannot_redirect = true
288 end 289 end
289 290
290 - def set_signup_time_for_now  
291 - session[:signup_time] = Time.now 291 + def set_signup_start_time_for_now
  292 + key = 'signup_start_time_' + rand.to_s.split('.')[1]
  293 + Rails.cache.write key, Time.now
  294 + key
  295 + end
  296 +
  297 + def get_signup_start_time
  298 + Rails.cache.read params[:signup_time_key]
  299 + end
  300 +
  301 + def clear_signup_start_time
  302 + Rails.cache.delete params[:signup_time_key]
292 end 303 end
293 304
294 def may_be_a_bot 305 def may_be_a_bot
295 - return true if session[:signup_time].nil?  
296 - session[:signup_time] > ( Time.now - 15.seconds ) 306 + # No minimum signup delay, no bot test.
  307 + return false if environment.min_signup_delay == 0
  308 +
  309 + # answering captcha, may be human!
  310 + return false if params[:recaptcha_response_field]
  311 +
  312 + # never set signup_time, hi wget!
  313 + signup_start_time = get_signup_start_time
  314 + return true if signup_start_time.nil?
  315 +
  316 + # so fast, so bot.
  317 + signup_start_time > ( Time.now - environment.min_signup_delay.seconds )
297 end 318 end
298 319
299 def check_answer 320 def check_answer
app/models/environment.rb
@@ -233,6 +233,7 @@ class Environment < ActiveRecord::Base @@ -233,6 +233,7 @@ class Environment < ActiveRecord::Base
233 settings[:message_for_member_invitation] || InviteMember.mail_template 233 settings[:message_for_member_invitation] || InviteMember.mail_template
234 end 234 end
235 235
  236 + settings_items :min_signup_delay, :type => Integer, :default => 25 #seconds
236 settings_items :activation_blocked_text, :type => String 237 settings_items :activation_blocked_text, :type => String
237 settings_items :message_for_disabled_enterprise, :type => String, 238 settings_items :message_for_disabled_enterprise, :type => String,
238 :default => _('This enterprise needs to be enabled.') 239 :default => _('This enterprise needs to be enabled.')
app/views/account/_signup_form.rhtml
1 -<script type="text/javascript">  
2 - jQuery.post("<%= url_for :controller=>'account', :action=>'signup_time' %>");  
3 -</script>  
4 -  
5 <% if @block_bot %> 1 <% if @block_bot %>
6 - <div class="bot-notice">  
7 - <%=_('<strong>How Fast!</strong> Looks like you are a bot.')%> 2 + <div id="bot-notice">
  3 + <strong><%=_('How Fast!')%></strong>
  4 + <p><%=_('Looks like you are a robot. Please, prove that you are human.')%></p>
8 </div> 5 </div>
9 <% end %> 6 <% end %>
10 7
@@ -14,6 +11,18 @@ @@ -14,6 +11,18 @@
14 11
15 <% labelled_form_for :user, @user, :html => { :multipart => true, :id => 'signup-form' } do |f| %> 12 <% labelled_form_for :user, @user, :html => { :multipart => true, :id => 'signup-form' } do |f| %>
16 13
  14 +<input type="hidden" id="signup_time_key" name="signup_time_key" />
  15 +<script type="text/javascript">
  16 + jQuery.ajax({
  17 + type: "POST",
  18 + url: "<%= url_for :controller=>'account', :action=>'signup_time' %>",
  19 + dataType: 'json',
  20 + success: function(data) {
  21 + if (data.ok) jQuery('#signup_time_key').val(data.key);
  22 + }
  23 + });
  24 +</script>
  25 +
17 <%= hidden_field_tag :invitation_code, @invitation_code %> 26 <%= hidden_field_tag :invitation_code, @invitation_code %>
18 27
19 <div id='signup-form-header'> 28 <div id='signup-form-header'>
public/stylesheets/application.css
@@ -5636,6 +5636,16 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img { @@ -5636,6 +5636,16 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img {
5636 5636
5637 /* Signup interface {{{ */ 5637 /* Signup interface {{{ */
5638 5638
  5639 +#bot-notice {
  5640 + border: 3px solid #000;
  5641 + background: #FE0;
  5642 + padding: 5px 10px;
  5643 + font-size: 150%;
  5644 +}
  5645 +#bot-notice p {
  5646 + margin: 0px;
  5647 +}
  5648 +
5639 #url-check { 5649 #url-check {
5640 margin: 0 0 -5px 0; 5650 margin: 0 0 -5px 0;
5641 width: 100%; 5651 width: 100%;
test/integration/signup_test.rb
@@ -7,8 +7,40 @@ class SignupTest &lt; ActionController::IntegrationTest @@ -7,8 +7,40 @@ class SignupTest &lt; ActionController::IntegrationTest
7 ActionController::Integration::Session.any_instance.stubs(:https?).returns(true) 7 ActionController::Integration::Session.any_instance.stubs(:https?).returns(true)
8 end 8 end
9 9
  10 + # helper
  11 + def registering_with_bot_test(min_signup_delay, sleep_secs)
  12 + env = Environment.default
  13 + env.min_signup_delay = min_signup_delay
  14 + env.save!
  15 + get '/account/signup'
  16 + assert_response :success
  17 + get '/account/signup_time'
  18 + assert_response :success
  19 + data = ActiveSupport::JSON.decode response.body
  20 + sleep sleep_secs
  21 + post '/account/signup', :user => { :login => 'someone', :password => 'test', :password_confirmation => 'test', :email => 'someone@example.com' }, :signup_time_key => data['key']
  22 + assert_response :success
  23 + end
  24 +
  25 + def test_signup_form_submition_must_be_blocked_for_fast_bots
  26 + count = User.count
  27 + registering_with_bot_test 5, 1
  28 + assert_template 'signup'
  29 + assert_equal count, User.count
  30 + assert_match /you are a robot/, response.body
  31 + end
  32 +
  33 + def test_signup_form_submition_must_not_block_after_min_signup_delay
  34 + count = User.count
  35 + registering_with_bot_test 1, 2
  36 + assert_equal count+1, User.count
  37 + end
  38 +
10 def test_should_require_acceptance_of_terms_for_signup 39 def test_should_require_acceptance_of_terms_for_signup
11 - Environment.default.update_attributes(:terms_of_use => 'You agree to not be annoying.') 40 + env = Environment.default
  41 + env.update_attributes(:terms_of_use => 'You agree to not be annoying.')
  42 + env.min_signup_delay = 0
  43 + env.save!
12 44
13 count = User.count 45 count = User.count
14 mail_count = ActionMailer::Base.deliveries.count 46 mail_count = ActionMailer::Base.deliveries.count