fast_submission_protection 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +13 -0
- data/MIT-LICENSE +25 -0
- data/README.md +76 -0
- data/Rakefile +33 -0
- data/app/views/fast_submission_protection/error.html +26 -0
- data/lib/fast_submission_protection/controller.rb +113 -0
- data/lib/fast_submission_protection/engine.rb +8 -0
- data/lib/fast_submission_protection/submission_timer.rb +30 -0
- data/lib/fast_submission_protection/version.rb +3 -0
- data/lib/fast_submission_protection.rb +4 -0
- data/spec/controllers/controller_spec.rb +150 -0
- data/spec/spec_helper.rb +10 -0
- metadata +89 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
= 0.1.0
|
2
|
+
* Renamed to fast_submission_protection
|
3
|
+
* Raise an error to handle fast submission
|
4
|
+
* better design for starting and finishing submission from within different controllers
|
5
|
+
|
6
|
+
= 0.0.3
|
7
|
+
* Docfix
|
8
|
+
|
9
|
+
= 0.0.2
|
10
|
+
* Adds #reject_fast_create which has some minimal default behaviour, encourages users to override
|
11
|
+
|
12
|
+
= 0.0.1
|
13
|
+
* Initial Release
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright 2012 Ian White
|
2
|
+
|
3
|
+
This plugin was developed by Ian White (http://github.com/ianwhite)
|
4
|
+
and Nicholas Rutherford (http://github.com/nruth) while working at
|
5
|
+
Distinctive Doors (http://distinctivedoors.co.uk) who have kindly
|
6
|
+
agreed to release this under the MIT-LICENSE.
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
9
|
+
a copy of this software and associated documentation files (the
|
10
|
+
"Software"), to deal in the Software without restriction, including
|
11
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
12
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
13
|
+
permit persons to whom the Software is furnished to do so, subject to
|
14
|
+
the following conditions:
|
15
|
+
|
16
|
+
The above copyright notice and this permission notice shall be
|
17
|
+
included in all copies or substantial portions of the Software.
|
18
|
+
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
20
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
21
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
22
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
23
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
24
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
25
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# FastSubmissionProtection
|
2
|
+
|
3
|
+
*This is experimental and the API is currently subject to sudden and massive change!*
|
4
|
+
|
5
|
+
[](http://travis-ci.org/i2w/timed_spam_rejection)
|
6
|
+
|
7
|
+
ActionController plugin that facilitates rejecting spam based on how long the form submission took.
|
8
|
+
|
9
|
+
This plugin was developed by [Ian White](http://github.com/ianwhite) and [Nicholas Rutherford](http://github.com/nruth) while working at [Distinctive Doors](http://distinctivedoors.co.uk) who have kindly agreed to release this under the MIT-LICENSE.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
In your Gemfile:
|
14
|
+
|
15
|
+
gem 'fast_submission_protection'
|
16
|
+
|
17
|
+
## Example Usage
|
18
|
+
|
19
|
+
class FeedbackController < ApplicationController
|
20
|
+
protect_from_fast_submission # default delay is 5 seconds, protects create from fast submission
|
21
|
+
end
|
22
|
+
|
23
|
+
class CommentsController < ApplicationController
|
24
|
+
# protects a Comment#update from happening too quickly, and rescues with custom behaviour
|
25
|
+
protect_from_fast_submission :delay => 10, :start => [:edit, :update], :finish => [:update], :rescue => false
|
26
|
+
|
27
|
+
rescue_from FastSubmissionProtection::SubmissionTooFastError, :with => lambda {|c| c.redirect_to :edit, :alert => 'Don't comment in anger!' }
|
28
|
+
end
|
29
|
+
|
30
|
+
See `FastSubmissionProtection::Controller#protect_from_fast_submission` for more details.
|
31
|
+
|
32
|
+
## Filters
|
33
|
+
|
34
|
+
You can start and finish the timed submission in different controllers, just set up the filters manually:
|
35
|
+
|
36
|
+
class WelcomeController < ApplicationController
|
37
|
+
before_filter FastSubmissionProtection::StartFilter.new('abused_form'), :only => :feedback_form
|
38
|
+
end
|
39
|
+
|
40
|
+
class FeedbackController < ApplicationController
|
41
|
+
before_filter FastSubmissionProtection::FinishFilter.new('abused_form'), :only => :feedback
|
42
|
+
end
|
43
|
+
|
44
|
+
## Instance methods
|
45
|
+
|
46
|
+
You can start and finish at any point within a controller
|
47
|
+
|
48
|
+
start_timed_submission 'abused_form'
|
49
|
+
finish_timed_submission 'abused_form', 20 # raises FastSubmissionProtection::SubmissionTooFastError if the above line was < 20 seconds ago
|
50
|
+
|
51
|
+
Other methods, like reset timer, and clear timer are available on the timer object
|
52
|
+
|
53
|
+
submission_timer('abused_form') # => a FastSubmissionProtection::SubmissionTimer
|
54
|
+
|
55
|
+
## Rescue
|
56
|
+
|
57
|
+
if you include FastSubmissionProtection::Rescue, the error is rescued with an error page with HTTP status 420 (enhance your calm).
|
58
|
+
This is included by default when you specify `protect_from_fast_submission`.
|
59
|
+
|
60
|
+
The default error page resides in 'views/fast_submission_protection/error'. Simply add this page to your views directory to use a custom page.
|
61
|
+
|
62
|
+
Another option is to do something like put a message in the flash, and re-render the new page. Simply rescue_from FastSubmissionProtection::SubmissionTimer.
|
63
|
+
|
64
|
+
## Development
|
65
|
+
|
66
|
+
Grab the project, use the last known good set of deps, and run the specs:
|
67
|
+
|
68
|
+
git clone http://github.com/i2w/fast_submission_protection
|
69
|
+
cd fast_submission_protection
|
70
|
+
cp Gemfile.lock.development Gemfile.lock
|
71
|
+
bundle
|
72
|
+
rake
|
73
|
+
|
74
|
+
## License
|
75
|
+
|
76
|
+
This project uses the MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'TimedSpamRejection'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.md', 'CHANGELOG', 'MIT-LICENSE')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
Bundler::GemHelper.install_tasks
|
24
|
+
|
25
|
+
require 'rspec/core/rake_task'
|
26
|
+
|
27
|
+
RSpec::Core::RakeTask.new(:spec)
|
28
|
+
|
29
|
+
desc "Run the specs with simplecov"
|
30
|
+
task :simplecov => [:simplecov_env, :spec]
|
31
|
+
task :simplecov_env do ENV['SIMPLECOV'] = '1' end
|
32
|
+
|
33
|
+
task :default => :spec
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Enhance your calm (420)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<div class="dialog">
|
21
|
+
<h1>The request you made was too fast.</h1>
|
22
|
+
<p>Click your browser's back button to return to the previous page, wait 5 seconds, and try the request again.</p>
|
23
|
+
<p>This is an anti-spam measure.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module FastSubmissionProtection
|
2
|
+
module Controller
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# protects a create action from fast_submission
|
7
|
+
#
|
8
|
+
# This checks the time taken between the form being rendered (by new, or failed create), and
|
9
|
+
# the form being posted to the create action. If it is less than the specified time, an
|
10
|
+
# error is raised, which is rescued with a basic 420 error page (enhance your calm) which
|
11
|
+
# invites the user to click their back button, wait 5 seconds, and try again.
|
12
|
+
#
|
13
|
+
# Options:
|
14
|
+
# * :name: The name of the submission (default "#{controller_name}_create")
|
15
|
+
# * :delay: The time to wait (default 5 seconds)
|
16
|
+
# * :start: List of actions when the timer should be started (default [:new, :create])
|
17
|
+
# * :finish: List of actions when the timer should be finished (default [:create])
|
18
|
+
# * :rescue: Rescue SubmissionTooFast error with a 420.html error page (default true)
|
19
|
+
#
|
20
|
+
# If your submission starts in one controller and finishes in another, you can start the
|
21
|
+
# timer wherever you like, as follows
|
22
|
+
#
|
23
|
+
# # At the class level, ie specifying a filter where the submission ends
|
24
|
+
# before_filter FastSubmissionProtection::FinishFilter.new('abused_form_post'), :only => [:create]
|
25
|
+
# # and where the submission starts
|
26
|
+
# before_filter FastSubmissionProtection::StartFilter.new('abused_form_post'), :only => [:new]
|
27
|
+
#
|
28
|
+
# # At the instance level, wherever you want, perhaps in an action
|
29
|
+
# start_timed_submission('often_abused_form_post') # to start
|
30
|
+
#
|
31
|
+
# # later, somewhere else
|
32
|
+
# finish_timed_submission('often_abused_form_post') # to finsih, raises SubmissionTooFastError if too fast
|
33
|
+
def protect_from_fast_submission options = {}
|
34
|
+
delay = options[:delay]
|
35
|
+
start = options[:start] || [:new, :create]
|
36
|
+
finish = options[:finish] || [:create]
|
37
|
+
name = options[:name] || "#{controller_name}_#{Array(finish).join('_')}"
|
38
|
+
|
39
|
+
include Rescue unless options[:rescue] == false || self < Rescue
|
40
|
+
|
41
|
+
before_filter FinishFilter.new(name, delay), :only => finish
|
42
|
+
before_filter StartFilter.new(name), :only => start
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
included do
|
47
|
+
hide_action :start_timed_submission, :finish_timed_submission
|
48
|
+
end
|
49
|
+
|
50
|
+
def start_timed_submission name
|
51
|
+
submission_timer(name).start
|
52
|
+
end
|
53
|
+
|
54
|
+
def finish_timed_submission name, delay = nil
|
55
|
+
if protect_from_fast_submission?
|
56
|
+
timer = submission_timer(name, delay)
|
57
|
+
if timer.too_fast?
|
58
|
+
logger.warn "WARNING: timed submission too fast" if logger
|
59
|
+
timer.restart
|
60
|
+
raise SubmissionTooFastError.new(name, delay)
|
61
|
+
else
|
62
|
+
timer.clear
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
def submission_timer name, delay = nil
|
69
|
+
SubmissionTimer.new timed_submission_storage, name, delay
|
70
|
+
end
|
71
|
+
|
72
|
+
def protect_from_fast_submission?
|
73
|
+
request.post? || request.put?
|
74
|
+
end
|
75
|
+
|
76
|
+
def timed_submission_storage
|
77
|
+
session[:_fsp] ||= {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class StartFilter < Struct.new(:name)
|
82
|
+
def filter controller
|
83
|
+
controller.start_timed_submission name
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class FinishFilter < Struct.new(:name, :delay)
|
88
|
+
def filter controller
|
89
|
+
controller.finish_timed_submission name, delay
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class FastSubmissionProtection::SubmissionTooFastError < RuntimeError
|
94
|
+
def initialize name, delay
|
95
|
+
@name, @delay = name, delay
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_reader :name, :delay
|
99
|
+
end
|
100
|
+
|
101
|
+
module Rescue
|
102
|
+
extend ActiveSupport::Concern
|
103
|
+
|
104
|
+
included do
|
105
|
+
rescue_from FastSubmissionProtection::SubmissionTooFastError, :with => :render_fast_submission_protection_error
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
def render_fast_submission_protection_error
|
110
|
+
render :template => 'fast_submission_protection/error', :layout => false, :status => 420
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module FastSubmissionProtection
|
2
|
+
class SubmissionTimer
|
3
|
+
class_attribute :delay, :clock
|
4
|
+
self.delay = 5
|
5
|
+
self.clock = Time
|
6
|
+
|
7
|
+
def initialize storage, key, delay = nil, clock = nil
|
8
|
+
@storage, @key = storage, key
|
9
|
+
@delay = delay || self.class.delay
|
10
|
+
@clock = clock || self.class.clock
|
11
|
+
end
|
12
|
+
|
13
|
+
def too_fast?
|
14
|
+
started = @storage[@key]
|
15
|
+
!started || (started + @delay > @clock.now)
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
@storage[@key] ||= @clock.now
|
20
|
+
end
|
21
|
+
|
22
|
+
def restart
|
23
|
+
@storage[@key] = @clock.now
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear
|
27
|
+
@storage.delete @key
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module FastSubmissionProtection
|
4
|
+
class Application < Rails::Application
|
5
|
+
config.i18n.default_locale = :en
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class ApplicationController < ActionController::Base
|
10
|
+
include Rails.application.routes.url_helpers
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'A controller with protect_from_fast_submission' do
|
14
|
+
controller do
|
15
|
+
protect_from_fast_submission :name => 'test', :rescue => false
|
16
|
+
|
17
|
+
def new
|
18
|
+
render_new
|
19
|
+
end
|
20
|
+
|
21
|
+
def create
|
22
|
+
if params[:create_failed]
|
23
|
+
render_new
|
24
|
+
else
|
25
|
+
created
|
26
|
+
render_create
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def index
|
31
|
+
render :nothing => true
|
32
|
+
end
|
33
|
+
|
34
|
+
def render_new
|
35
|
+
render :nothing => true
|
36
|
+
end
|
37
|
+
|
38
|
+
def render_create
|
39
|
+
render :nothing => true
|
40
|
+
end
|
41
|
+
|
42
|
+
def created
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'post :create' do
|
47
|
+
subject { do_post }
|
48
|
+
|
49
|
+
def do_post *args
|
50
|
+
post :create, *args
|
51
|
+
end
|
52
|
+
|
53
|
+
# The only thing we're stubbing in this integration spec is the time
|
54
|
+
let(:clock) { double :now => Time.now }
|
55
|
+
before do FastSubmissionProtection::SubmissionTimer.clock = clock end
|
56
|
+
|
57
|
+
def seconds_pass amount
|
58
|
+
now = clock.now + amount.seconds
|
59
|
+
clock.stub(:now).and_return now
|
60
|
+
end
|
61
|
+
|
62
|
+
shared_examples_for 'a spammy form posting' do
|
63
|
+
it do expect{ subject }.to raise_error FastSubmissionProtection::SubmissionTooFastError end
|
64
|
+
|
65
|
+
it 'should not execute create' do
|
66
|
+
controller.should_not_receive(:created)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
shared_examples_for 'a non spammy form posting' do
|
71
|
+
it 'should execute :create' do
|
72
|
+
controller.should_receive(:created)
|
73
|
+
subject
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when get :new is not the previous action' do
|
78
|
+
it_should_behave_like 'a spammy form posting'
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'after get :new' do
|
82
|
+
before do
|
83
|
+
get :new
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'when enough time has passed' do
|
87
|
+
before do seconds_pass(6) end
|
88
|
+
|
89
|
+
it_should_behave_like 'a non spammy form posting'
|
90
|
+
|
91
|
+
context 'but the create fails for another reason, and new is rendered' do
|
92
|
+
before do
|
93
|
+
do_post :create_failed => true
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'and enough time passes' do
|
97
|
+
before do seconds_pass(6) end
|
98
|
+
|
99
|
+
it_should_behave_like 'a non spammy form posting'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'and another action is requested in the meantime' do
|
105
|
+
before do
|
106
|
+
get :index
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'and enough time has passed' do
|
110
|
+
before do seconds_pass(6) end
|
111
|
+
|
112
|
+
it_should_behave_like 'a non spammy form posting'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'when not enough time has passed' do
|
117
|
+
before do seconds_pass(4) end
|
118
|
+
|
119
|
+
it_should_behave_like 'a spammy form posting'
|
120
|
+
|
121
|
+
context 'and not enough time passes again' do
|
122
|
+
before do
|
123
|
+
do_post rescue FastSubmissionProtection::SubmissionTooFastError
|
124
|
+
seconds_pass(4)
|
125
|
+
end
|
126
|
+
|
127
|
+
it_should_behave_like 'a spammy form posting'
|
128
|
+
|
129
|
+
context 'but then enough time passes' do
|
130
|
+
before do
|
131
|
+
do_post rescue FastSubmissionProtection::SubmissionTooFastError
|
132
|
+
seconds_pass(6)
|
133
|
+
end
|
134
|
+
|
135
|
+
it_should_behave_like 'a non spammy form posting'
|
136
|
+
|
137
|
+
context 'but then I post again immediately' do
|
138
|
+
before do
|
139
|
+
do_post rescue FastSubmissionProtection::SubmissionTooFastError
|
140
|
+
seconds_pass(1)
|
141
|
+
end
|
142
|
+
|
143
|
+
it_should_behave_like 'a spammy form posting'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fast_submission_protection
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ian White
|
9
|
+
- Nicholas Rutherford
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-04-03 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rails
|
17
|
+
requirement: &70112464848300 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *70112464848300
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rspec-rails
|
28
|
+
requirement: &70112464847440 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *70112464847440
|
37
|
+
description: Reject form submissions based on the time taken to submit them. Version
|
38
|
+
0.1.0
|
39
|
+
email:
|
40
|
+
- ian@i2wdev.com
|
41
|
+
executables: []
|
42
|
+
extensions: []
|
43
|
+
extra_rdoc_files: []
|
44
|
+
files:
|
45
|
+
- app/views/fast_submission_protection/error.html
|
46
|
+
- lib/fast_submission_protection/controller.rb
|
47
|
+
- lib/fast_submission_protection/engine.rb
|
48
|
+
- lib/fast_submission_protection/submission_timer.rb
|
49
|
+
- lib/fast_submission_protection/version.rb
|
50
|
+
- lib/fast_submission_protection.rb
|
51
|
+
- MIT-LICENSE
|
52
|
+
- Rakefile
|
53
|
+
- README.md
|
54
|
+
- CHANGELOG
|
55
|
+
- spec/controllers/controller_spec.rb
|
56
|
+
- spec/spec_helper.rb
|
57
|
+
homepage: http://github.com/i2w/timed_spam_rejection
|
58
|
+
licenses: []
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
hash: 3131108100916053125
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
hash: 3131108100916053125
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.8.10
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Reject form submissions based on the time taken to submit them
|
87
|
+
test_files:
|
88
|
+
- spec/controllers/controller_spec.rb
|
89
|
+
- spec/spec_helper.rb
|