acts_as_textcaptcha 2.2.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,25 @@
1
1
  module ActsAsTextcaptcha
2
2
  module TextcaptchaHelper
3
3
 
4
- # generates a spam question and possible answers for a model, answers are stored in session[:possible_answers]
5
- def spamify(model)
6
- session[:possible_answers] = model.generate_spam_question unless model.validate_spam_answer
4
+ # builds html fields for spam question, answer and hidden encrypted spam answers
5
+ def textcaptcha_fields(f, &block)
6
+ model = f.object
7
+ captcha_html = ''
8
+ if model.perform_textcaptcha?
9
+ captcha_html += f.hidden_field(:spam_answers)
10
+ if model.spam_answer
11
+ captcha_html += f.hidden_field(:spam_answer)
12
+ elsif model.spam_question
13
+ captcha_html += capture(&block)
14
+ end
15
+ end
16
+
17
+ # Rails 2 compatability
18
+ if Rails::VERSION::MAJOR < 3
19
+ concat captcha_html, &block.binding
20
+ else
21
+ captcha_html.html_safe
22
+ end
7
23
  end
8
24
  end
9
- end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module ActsAsTextcaptcha
2
- VERSION = "2.2.2"
3
- end
2
+ VERSION = "3.0.0"
3
+ end
@@ -1,23 +1,26 @@
1
+ require 'bcrypt'
2
+
1
3
  namespace :textcaptcha do
2
- desc "Adds a textcaptcha.yml template config file to ./config "
3
- task :generate_config do
4
4
 
5
+ desc "Creates a template config file in config/textcaptcha.yml"
6
+ task :config do
5
7
  src = File.join(File.dirname(__FILE__), '../..', 'config', 'textcaptcha.yml')
6
8
  dest = File.join(Rails.root, 'config', 'textcaptcha.yml')
7
9
  if File.exist?(dest)
8
- puts "\ntextcaptcha config file: #{dest}\n ... already exists. Aborting.\n\n"
10
+ puts "\nOoops, a textcaptcha config file at #{dest} already exists ... aborting.\n\n"
9
11
  else
10
- config_file = ''
12
+ config = ''
13
+ salt = BCrypt::Engine.generate_salt
11
14
  f = File.open(src, 'r')
12
- f.each_line { |line| config_file += line }
13
- config_file.gsub!(/api\_key\:(.+)$/, 'api_key: # get one at http://textcaptcha.com' )
14
- config_file.gsub!(/bcrypt\_salt\:(.+)$/, "bcrypt_salt: #{BCrypt::Engine.generate_salt}" ) if defined?(BCrypt)
15
+ f.each_line { |line| config += line }
16
+ config.gsub!(/RAKE_GENERATED_SALT_PLACEHOLDER/, salt)
17
+ config.gsub!(/ api_key:(.*)# for gem test purposes only$/, " api_key: PASTE_YOUR_TEXTCAPCHA_API_KEY_HERE")
18
+ config.gsub!(/ bcrypt_salt:(.*)# for gem test purposes only$/, " bcrypt_salt: #{salt}")
15
19
 
16
20
  f = File.new(dest, 'w')
17
- f.write(config_file)
21
+ f.write(config)
18
22
  f.close
19
- puts "\ntextcaptcha.yml generated at #{dest} (with a new BCrypt salt).\nNOTE: edit this file and add your textcaptcha api key (from http://textcaptcha.com)"
23
+ puts "\ntextcaptcha.yml generated at #{dest} (with a new BCrypt salt)\nNOTE: edit this file and add your textcaptcha api key, grab one from http://textcaptcha.com/api\n\n"
20
24
  end
21
-
22
25
  end
23
- end
26
+ end
@@ -15,4 +15,4 @@ ActiveRecord::Schema.define(:version => 0) do
15
15
  create_table :notes, :force => true do |t|
16
16
  t.string :name
17
17
  end
18
- end
18
+ end
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)+'./../lib'))
2
+
3
+ ENV['RAILS_ENV'] = 'test'
4
+
5
+ if ENV['COVERAGE']
6
+ require "simplecov"
7
+ SimpleCov.start do
8
+ add_filter '/test/'
9
+ end
10
+ SimpleCov.at_exit do
11
+ SimpleCov.result.format!
12
+ `open ./coverage/index.html` if RUBY_PLATFORM =~ /darwin/
13
+ end
14
+ end
15
+
16
+ require 'minitest/autorun'
17
+ require 'fakeweb'
18
+
19
+ require 'active_record'
20
+ require 'rails'
21
+
22
+ require 'acts_as_textcaptcha'
23
+ require './test/test_models'
24
+
25
+ # load and initialize test db schema
26
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => 'acts_as_textcaptcha.sqlite3.db')
27
+ load(File.dirname(__FILE__) + "/schema.rb")
@@ -0,0 +1,42 @@
1
+ # models for use in tests
2
+
3
+ class Widget < ActiveRecord::Base
4
+ # uses textcaptcha.yml file for configuration
5
+ acts_as_textcaptcha
6
+ end
7
+
8
+ class Comment < ActiveRecord::Base
9
+ # inline options (symbol keys) with api_key only
10
+ acts_as_textcaptcha :api_key => '8u5ixtdnq9csc84cok0owswgo',
11
+ :bcrypt_salt => '$2a$10$j0bmycH.SVfD1b5mpEGPpe'
12
+ end
13
+
14
+ class Review < ActiveRecord::Base
15
+ # inline options with all possible options
16
+ acts_as_textcaptcha 'api_key' => '8u5ixtdnq9csc84cok0owswgo',
17
+ 'bcrypt_salt' => '$2a$10$j0bmycH.SVfD1b5mpEGPpe',
18
+ 'bcrypt_cost' => '3',
19
+ 'questions' => [{ 'question' => 'The green hat is what color?', 'answers' => 'green' }]
20
+ end
21
+
22
+ class Note < ActiveRecord::Base
23
+ # inline options (string keys) with user defined questions only (no textcaptcha service)
24
+ acts_as_textcaptcha 'questions' => [{ 'question' => '1+1', 'answers' => '2,two' }],
25
+ 'bcrypt_salt' => '$2a$10$j0bmycH.SVfD1b5mpEGPpe'
26
+
27
+ # allows toggling perform_textcaptcha on/off (default on)
28
+ attr_accessor :turn_off_captcha
29
+
30
+ def perform_textcaptcha?
31
+ !turn_off_captcha
32
+ end
33
+ end
34
+
35
+ class Contact
36
+ # non active record object (symbol keys), no API used
37
+ include ActiveModel::Validations
38
+ include ActiveModel::Conversion
39
+ extend ActsAsTextcaptcha::Textcaptcha
40
+ acts_as_textcaptcha :questions => [{ :question => 'one+1', :answers => '2,two' }],
41
+ :bcrypt_salt => '$2a$10$j0bmycH.SVfD1b5mpEGPpe'
42
+ end
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.dirname(__FILE__)+'/test_helper')
2
+ require 'action_controller'
3
+ require 'action_view'
4
+
5
+ class NotesController < ActionController::Base; end
6
+
7
+ class Template < ActionView::Base
8
+ def protect_against_forgery?; false; end
9
+ end
10
+
11
+
12
+ describe 'TextcaptchaHelper' do
13
+
14
+ before(:each) do
15
+ @controller = NotesController.new
16
+ @note = Note.new
17
+ @note.textcaptcha
18
+ end
19
+
20
+ def render_template(assigns)
21
+ template = <<-ERB
22
+ <%= form_for(@note, :url => '/') do |f| %>
23
+ <%= textcaptcha_fields(f) do %>
24
+ <div class="field textcaptcha">
25
+ <%= f.label :spam_answer, @note.spam_question %><br/>
26
+ <%= f.text_field :spam_answer, :value => '' %>
27
+ </div>
28
+ <% end %>
29
+ <% end %>
30
+ ERB
31
+
32
+ Template.new([], assigns, @controller).render(:inline => template)
33
+ end
34
+
35
+ it 'should render question and answer fields, with hidden spam_answers field' do
36
+ html = render_template({:note => @note})
37
+
38
+ html.must_match /\<label for\=\"note\_spam\_answer\"\>1\+1\<\/label\>/
39
+ html.must_match /\<input id\=\"note_spam_answers\" name\=\"note\[spam\_answers\]\" type\=\"hidden\" value\=\"(.*)\" \/\>/
40
+ end
41
+
42
+ it 'should render hidden answer and spam_answer fields when question has been answered OK (and not ask question)' do
43
+ @note.spam_answer = 2
44
+ html = render_template({:note => @note})
45
+
46
+ html.wont_match /\<label for\=\"note\_spam\_answer\"\>1\+1\<\/label\>/
47
+ html.must_match /\<input id\=\"note_spam_answers\" name\=\"note\[spam\_answers\]\" type\=\"hidden\" value\=\"(.*)\" \/\>/
48
+ html.must_match /\<input id\=\"note_spam_answer\" name\=\"note\[spam_answer\]\" type\=\"hidden\" value\=\"2\" \/\>/
49
+ end
50
+ end
@@ -0,0 +1,188 @@
1
+ require File.expand_path(File.dirname(__FILE__)+'/test_helper')
2
+
3
+ describe 'Textcaptcha' do
4
+
5
+ describe 'validations' do
6
+
7
+ before(:each) do
8
+ @note = Note.new
9
+ @note.textcaptcha
10
+ end
11
+
12
+ it 'should validate an ActiveRecord object (with multiple correct answers)' do
13
+ @note.spam_question.must_equal('1+1')
14
+ @note.valid?.must_equal false
15
+ @note.errors[:spam_answer].first.must_equal('is incorrect, try another question instead')
16
+
17
+ @note.spam_answer = 'two'
18
+ @note.valid?.must_equal true
19
+ @note.errors[:spam_answer].must_be_empty
20
+
21
+ @note.spam_answer = '2'
22
+ @note.valid?.must_equal true
23
+ @note.errors[:spam_answer].must_be_empty
24
+ end
25
+
26
+ it 'should strip whitespace and downcase spam answer' do
27
+ @note.spam_answer = ' tWo '
28
+ @note.valid?.must_equal true
29
+ @note.errors[:spam_answer].must_be_empty
30
+ end
31
+
32
+ it 'should always be valid when record has been saved' do
33
+ @note.spam_answer = '2'
34
+ @note.save!
35
+ @note.textcaptcha
36
+
37
+ @note.spam_answer = 'wrong answer'
38
+ @note.new_record?.must_equal false
39
+ @note.valid?.must_equal true
40
+ @note.errors[:spam_answer].must_be_empty
41
+ end
42
+
43
+ it 'should always be valid when perform_textcaptcha? is false' do
44
+ @note.turn_off_captcha = true
45
+ @note.valid?.must_equal true
46
+ @note.errors[:spam_answer].must_be_empty
47
+
48
+ @note.save.must_equal true
49
+ end
50
+
51
+ it 'should validate a non ActiveRecord object' do
52
+ @contact = Contact.new
53
+ @contact.textcaptcha
54
+
55
+ @contact.spam_question.must_equal('one+1')
56
+ @contact.spam_answer = 'wrong'
57
+ @contact.valid?.must_equal false
58
+
59
+ @contact.spam_answer = 'two'
60
+ @contact.valid?.must_equal true
61
+ @contact.errors[:spam_answer].must_be_empty
62
+ end
63
+ end
64
+
65
+ describe 'encryption' do
66
+
67
+ before(:each) do
68
+ @note = Note.new
69
+ end
70
+
71
+ it 'should encrypt spam_answers (joined by - seperator) MD5 digested and using BCrypt engine with salt' do
72
+ @note.spam_answers.must_be_nil
73
+ @note.textcaptcha
74
+ encrypted_answers = [2,' TwO '].collect { |answer| BCrypt::Engine.hash_secret(Digest::MD5.hexdigest(answer.to_s.strip.downcase), '$2a$10$j0bmycH.SVfD1b5mpEGPpe', 1) }.join('-')
75
+ @note.spam_answers.must_equal('$2a$10$j0bmycH.SVfD1b5mpEGPpePFe1wBxOn7Brr9lMuLRxv6lg4ZYjJ22-$2a$10$j0bmycH.SVfD1b5mpEGPpe8v5mqqpDaExuS/hZu8Xkq8krYL/T8P.')
76
+ @note.spam_answers.must_equal(encrypted_answers)
77
+ end
78
+
79
+ it 'should raise error if bcyrpt salt is invalid' do
80
+ @note.textcaptcha_config[:bcrypt_salt] = 'bad salt'
81
+ proc { @note.textcaptcha }.must_raise BCrypt::Errors::InvalidSalt
82
+ @note.textcaptcha_config[:bcrypt_salt] ='$2a$10$j0bmycH.SVfD1b5mpEGPpe'
83
+ end
84
+ end
85
+
86
+ describe 'textcaptcha API' do
87
+
88
+ after(:each) do
89
+ FakeWeb.clean_registry
90
+ end
91
+
92
+ it 'should generate spam question from the service' do
93
+ @review = Review.new
94
+
95
+ @review.textcaptcha
96
+ @review.spam_question.wont_be_nil
97
+ @review.spam_question.wont_equal('The green hat is what color?')
98
+
99
+ @review.spam_answers.wont_be_nil
100
+
101
+ @review.valid?.must_equal false
102
+ @review.errors[:spam_answer].first.must_equal('is incorrect, try another question instead')
103
+ end
104
+
105
+ it 'should parse a single answer from XML response' do
106
+ @review = Review.new
107
+ question = 'If tomorrow is Saturday, what day is today?'
108
+ body = "<captcha><question>#{question}</question><answer>f6f7fec07f372b7bd5eb196bbca0f3f4</answer></captcha>"
109
+ FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :body => body)
110
+
111
+ @review.textcaptcha
112
+ @review.spam_question.must_equal(question)
113
+ @review.spam_answers.must_equal('$2a$10$j0bmycH.SVfD1b5mpEGPpecvhlumIBvWXI4HQWk0xa74DebZDx772')
114
+ @review.spam_answers.split('-').length.must_equal(1)
115
+ end
116
+
117
+ it 'should parse multiple answers from XML response' do
118
+ @review = Review.new
119
+ question = 'If tomorrow is Saturday, what day is today?'
120
+ body = "<captcha><question>#{question}</question><answer>1</answer><answer>2</answer><answer>3</answer></captcha>"
121
+ FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :body => body)
122
+
123
+ @review.textcaptcha
124
+ @review.spam_question.must_equal(question)
125
+ @review.spam_answers.split('-').length.must_equal(3)
126
+ end
127
+
128
+ describe 'service is unavailable' do
129
+
130
+ describe 'should fallback to a user defined question' do
131
+
132
+ before(:each) do
133
+ @review = Review.new
134
+ end
135
+
136
+ it 'when errors occur' do
137
+ [SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
138
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, URI::InvalidURIError].each do |error|
139
+ FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :exception => error)
140
+ @review.textcaptcha
141
+ @review.spam_question.must_equal('The green hat is what color?')
142
+ @review.spam_answers.wont_be_nil
143
+ end
144
+ end
145
+
146
+ it 'when response is OK but body cannot be parsed as XML' do
147
+ FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :body => 'here be gibberish')
148
+ @review.textcaptcha
149
+ @review.spam_question.must_equal('The green hat is what color?')
150
+ @review.spam_answers.wont_be_nil
151
+ end
152
+
153
+ it 'when response is OK but empty' do
154
+ FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :body => '')
155
+ @review.textcaptcha
156
+ @review.spam_question.must_equal('The green hat is what color?')
157
+ @review.spam_answers.wont_be_nil
158
+ end
159
+ end
160
+ end
161
+
162
+ it 'should not generate any spam question or answer when no user defined questions set' do
163
+ @comment = Comment.new
164
+
165
+ FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :exception => SocketError)
166
+ @comment.textcaptcha
167
+ @comment.spam_question.must_equal 'ActsAsTextcaptcha >> no API key (or questions) set and/or the textcaptcha service is currently unavailable (answer ok to bypass)'
168
+ @comment.spam_answers.must_equal 'ok'
169
+ end
170
+ end
171
+
172
+ describe 'configuration' do
173
+
174
+ it 'should be configured with inline hash' do
175
+ Review.textcaptcha_config.must_equal({ :api_key => '8u5ixtdnq9csc84cok0owswgo',
176
+ :bcrypt_salt => '$2a$10$j0bmycH.SVfD1b5mpEGPpe',
177
+ :bcrypt_cost => '3',
178
+ :questions => [{ 'question' => 'The green hat is what color?', 'answers' => 'green' }]})
179
+ end
180
+
181
+ it 'should be configured with textcaptcha.yml' do
182
+ Widget.textcaptcha_config[:api_key].must_equal '6eh1co0j12mi2ogcoggkkok4o'
183
+ Widget.textcaptcha_config[:bcrypt_salt].must_equal '$2a$10$qhSefD6gKtmq6M0AzXk4CO'
184
+ Widget.textcaptcha_config[:bcrypt_cost].must_equal 1
185
+ Widget.textcaptcha_config[:questions].length.must_equal 10
186
+ end
187
+ end
188
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_textcaptcha
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.2
4
+ version: 3.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,23 +9,23 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-26 00:00:00.000000000 +01:00
12
+ date: 2011-08-06 00:00:00.000000000 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bcrypt-ruby
17
- requirement: &2152243920 !ruby/object:Gem::Requirement
17
+ requirement: &2156333440 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
21
21
  - !ruby/object:Gem::Version
22
- version: 2.1.2
22
+ version: 2.1.4
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2152243920
25
+ version_requirements: *2156333440
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rails
28
- requirement: &2152243500 !ruby/object:Gem::Requirement
28
+ requirement: &2156333020 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,21 +33,10 @@ dependencies:
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *2152243500
37
- - !ruby/object:Gem::Dependency
38
- name: activerecord
39
- requirement: &2152243040 !ruby/object:Gem::Requirement
40
- none: false
41
- requirements:
42
- - - ! '>='
43
- - !ruby/object:Gem::Version
44
- version: '0'
45
- type: :development
46
- prerelease: false
47
- version_requirements: *2152243040
36
+ version_requirements: *2156333020
48
37
  - !ruby/object:Gem::Dependency
49
38
  name: bundler
50
- requirement: &2152242620 !ruby/object:Gem::Requirement
39
+ requirement: &2156332560 !ruby/object:Gem::Requirement
51
40
  none: false
52
41
  requirements:
53
42
  - - ! '>='
@@ -55,10 +44,10 @@ dependencies:
55
44
  version: '0'
56
45
  type: :development
57
46
  prerelease: false
58
- version_requirements: *2152242620
47
+ version_requirements: *2156332560
59
48
  - !ruby/object:Gem::Dependency
60
- name: rspec
61
- requirement: &2152242200 !ruby/object:Gem::Requirement
49
+ name: simplecov
50
+ requirement: &2156332140 !ruby/object:Gem::Requirement
62
51
  none: false
63
52
  requirements:
64
53
  - - ! '>='
@@ -66,10 +55,10 @@ dependencies:
66
55
  version: '0'
67
56
  type: :development
68
57
  prerelease: false
69
- version_requirements: *2152242200
58
+ version_requirements: *2156332140
70
59
  - !ruby/object:Gem::Dependency
71
- name: rcov
72
- requirement: &2152241780 !ruby/object:Gem::Requirement
60
+ name: rdoc
61
+ requirement: &2156331720 !ruby/object:Gem::Requirement
73
62
  none: false
74
63
  requirements:
75
64
  - - ! '>='
@@ -77,10 +66,10 @@ dependencies:
77
66
  version: '0'
78
67
  type: :development
79
68
  prerelease: false
80
- version_requirements: *2152241780
69
+ version_requirements: *2156331720
81
70
  - !ruby/object:Gem::Dependency
82
- name: rdoc
83
- requirement: &2152241360 !ruby/object:Gem::Requirement
71
+ name: sqlite3
72
+ requirement: &2156331300 !ruby/object:Gem::Requirement
84
73
  none: false
85
74
  requirements:
86
75
  - - ! '>='
@@ -88,10 +77,10 @@ dependencies:
88
77
  version: '0'
89
78
  type: :development
90
79
  prerelease: false
91
- version_requirements: *2152241360
80
+ version_requirements: *2156331300
92
81
  - !ruby/object:Gem::Dependency
93
- name: sqlite3
94
- requirement: &2152240940 !ruby/object:Gem::Requirement
82
+ name: fakeweb
83
+ requirement: &2156330880 !ruby/object:Gem::Requirement
95
84
  none: false
96
85
  requirements:
97
86
  - - ! '>='
@@ -99,13 +88,11 @@ dependencies:
99
88
  version: '0'
100
89
  type: :development
101
90
  prerelease: false
102
- version_requirements: *2152240940
103
- description: ! "Spam protection for your ActiveRecord models using logic questions
104
- and the excellent textcaptcha api. See textcaptcha.com for more details and to get
105
- your api key.\n The logic questions are aimed at a child's age of 7, so can be
106
- solved easily by all but the most cognitively impaired users. As they involve human
107
- logic, such questions cannot be solved by a robot.\n For more reasons on why logic
108
- questions are useful, see here; http://textcaptcha.com/why"
91
+ version_requirements: *2156330880
92
+ description: ! "Simple question/answer based spam protection for your Rails models.\n
93
+ \ You can define your own logic questions and/or fetch questions from the textcaptcha.com
94
+ API.\n The questions involve human logic and are tough for spam bots to crack.\n
95
+ \ For more reasons on why logic questions are a good idea visit; http://textcaptcha.com/why"
109
96
  email:
110
97
  - matt@hiddenloop.com
111
98
  executables: []
@@ -123,17 +110,17 @@ files:
123
110
  - config/textcaptcha.yml
124
111
  - init.rb
125
112
  - lib/acts_as_textcaptcha.rb
113
+ - lib/acts_as_textcaptcha/framework/rails.rb
126
114
  - lib/acts_as_textcaptcha/framework/rails2.rb
127
- - lib/acts_as_textcaptcha/framework/rails3.rb
128
115
  - lib/acts_as_textcaptcha/textcaptcha.rb
129
116
  - lib/acts_as_textcaptcha/textcaptcha_helper.rb
130
117
  - lib/acts_as_textcaptcha/version.rb
131
118
  - lib/tasks/textcaptcha.rake
132
- - spec/acts_as_textcaptcha_spec.rb
133
- - spec/database.yml
134
- - spec/schema.rb
135
- - spec/spec.opts
136
- - spec/spec_helper.rb
119
+ - test/schema.rb
120
+ - test/test_helper.rb
121
+ - test/test_models.rb
122
+ - test/textcaptcha_helper_test.rb
123
+ - test/textcaptcha_test.rb
137
124
  has_rdoc: true
138
125
  homepage: http://github.com/matthutchinson/acts_as_textcaptcha
139
126
  licenses: []
@@ -158,11 +145,6 @@ rubyforge_project:
158
145
  rubygems_version: 1.6.2
159
146
  signing_key:
160
147
  specification_version: 3
161
- summary: Spam protection for your models via logic questions and the excellent textcaptcha.com
162
- api
163
- test_files:
164
- - spec/acts_as_textcaptcha_spec.rb
165
- - spec/database.yml
166
- - spec/schema.rb
167
- - spec/spec.opts
168
- - spec/spec_helper.rb
148
+ summary: Spam protection for your models via logic questions and the textcaptcha.com
149
+ API
150
+ test_files: []