mwhuss-recaptcha 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +4 -0
- data/LICENSE +19 -0
- data/Manifest +12 -0
- data/README.rdoc +53 -0
- data/Rakefile +40 -0
- data/init.rb +3 -0
- data/lib/recaptcha.rb +3 -0
- data/lib/recaptcha/recaptcha.rb +84 -0
- data/recaptcha.gemspec +32 -0
- data/tasks/recaptcha_tasks.rake +4 -0
- data/test/recaptcha_test.rb +38 -0
- data/test/verify_recaptcha_test.rb +88 -0
- metadata +75 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2007 Jason L Perry
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/Manifest
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
= ReCAPTCHA
|
2
|
+
|
3
|
+
Author:: Jason L Perry (http://ambethia.com)
|
4
|
+
|
5
|
+
Copyright:: Copyright (c) 2007 Jason L Perry
|
6
|
+
|
7
|
+
License:: MIT
|
8
|
+
|
9
|
+
RDOC:: http://ambethia.com/recaptcha/
|
10
|
+
|
11
|
+
Git:: http://github.com/ambethia/recaptcha/tree/master
|
12
|
+
|
13
|
+
Bugs:: http://ambethia.lighthouseapp.com/projects/11072-recaptcha/overview
|
14
|
+
|
15
|
+
|
16
|
+
This plugin adds helpers for the ReCAPTCHA API (http://recaptcha.net/). In your views you can use
|
17
|
+
the +recaptcha_tags+ method to embed the needed javascript, and you can validate in your controllers
|
18
|
+
with +verify_recaptcha+.
|
19
|
+
|
20
|
+
You'll want to add your public and private API keys in the environment variables RECAPTCHA_PUBLIC_KEY
|
21
|
+
and RECAPTCHA_PRIVATE_KEY, respectively. You could also specify them in config/environment.rb if you
|
22
|
+
are so inclined. Exceptions will be raised if you call these methods and the keys can't be found.
|
23
|
+
|
24
|
+
== +recaptcha_tags+
|
25
|
+
|
26
|
+
Some of the options available:
|
27
|
+
|
28
|
+
<tt>:ssl</tt>:: Uses secure http for captcha widget (default +false+)
|
29
|
+
<tt>:noscript</tt>:: Include <noscript> content (default +true+)
|
30
|
+
<tt>:display</tt>:: Takes a hash containing the +theme+ and +tabindex+ options per the API. (default +nil+)
|
31
|
+
<tt>:public_key</tt>:: Your public API key, takes precedence over the ENV variable (default +nil+).
|
32
|
+
<tt>:error</tt>:: Override the error in +session[:recaptcha_error]+ (default +nil+).
|
33
|
+
|
34
|
+
== +verify_recaptcha+
|
35
|
+
|
36
|
+
This method returns +true+ or +false+ after processing the parameters from the ReCAPTCHA widget. Why
|
37
|
+
isn't this a model validation? Because that violates MVC. Use can use it like this, or how ever you
|
38
|
+
like. Passing in the ActiveRecord object is optional, if you do--and the captcha fails to verify--an
|
39
|
+
error will be added to the object for you to use.
|
40
|
+
|
41
|
+
respond_to do |format|
|
42
|
+
if verify_recaptcha(@post) && @post.save
|
43
|
+
# ...
|
44
|
+
else
|
45
|
+
# ...
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
== TODO
|
50
|
+
* Remove Rails/ActionController dependencies
|
51
|
+
* Framework agnostic
|
52
|
+
* Add some helpers to use in before_filter and what not
|
53
|
+
* Better documentation
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'echoe'
|
5
|
+
|
6
|
+
Echoe.new('recaptcha', '0.1.0') do |p|
|
7
|
+
p.description = "This plugin adds helpers for the ReCAPTCHA API "
|
8
|
+
p.url = "http://github.com/ambethia/recaptcha/tree/master"
|
9
|
+
p.author = "Jason L. Perry"
|
10
|
+
p.email = "jasper@ambethia.com"
|
11
|
+
p.ignore_pattern = ["pkg/**/*"]
|
12
|
+
p.development_dependencies = []
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
desc 'Default: run unit tests.'
|
17
|
+
task :default => :test
|
18
|
+
|
19
|
+
desc 'Test the recaptcha plugin.'
|
20
|
+
Rake::TestTask.new(:test) do |t|
|
21
|
+
t.libs << 'lib'
|
22
|
+
t.pattern = 'test/**/*_test.rb'
|
23
|
+
t.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
ALLISON = "/Library/Ruby/Gems/1.8/gems/allison-2.0.3/lib/allison.rb"
|
27
|
+
|
28
|
+
Rake::RDocTask.new do |rd|
|
29
|
+
rd.main = "README.rdoc"
|
30
|
+
rd.rdoc_files.include "README.rdoc", "LICENSE", "lib/**/*.rb"
|
31
|
+
rd.title = "ReCAPTCHA"
|
32
|
+
rd.options << '-N' # line numbers
|
33
|
+
rd.options << '-S' # inline source
|
34
|
+
rd.template = ALLISON if File.exist?(ALLISON)
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Upload the rdoc to ambethia.com"
|
38
|
+
task "publish" do
|
39
|
+
sh "scp -r html/* ambethia.com:~/www/ambethia.com/recaptcha"
|
40
|
+
end
|
data/init.rb
ADDED
data/lib/recaptcha.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# ReCAPTCHA
|
2
|
+
module Ambethia
|
3
|
+
module ReCaptcha
|
4
|
+
RECAPTCHA_API_SERVER = 'http://api.recaptcha.net';
|
5
|
+
RECAPTCHA_API_SECURE_SERVER = 'https://api-secure.recaptcha.net';
|
6
|
+
RECAPTCHA_VERIFY_SERVER = 'api-verify.recaptcha.net';
|
7
|
+
|
8
|
+
SKIP_VERIFY_ENV = ['test']
|
9
|
+
|
10
|
+
module Helper
|
11
|
+
# Your public API can be specified in the +options+ hash or preferably the environment
|
12
|
+
# variable +RECAPTCHA_PUBLIC_KEY+.
|
13
|
+
def recaptcha_tags(options = {})
|
14
|
+
# Default options
|
15
|
+
key = options[:public_key] ||= ENV['RECAPTCHA_PUBLIC_KEY']
|
16
|
+
error = options[:error] ||= session[:recaptcha_error]
|
17
|
+
uri = options[:ssl] ? RECAPTCHA_API_SECURE_SERVER : RECAPTCHA_API_SERVER
|
18
|
+
xhtml = Builder::XmlMarkup.new :target => out=(''), :indent => 2 # Because I can.
|
19
|
+
if options[:display]
|
20
|
+
xhtml.script(:type => "text/javascript"){ |x| x << "var RecaptchaOptions = #{options[:display].to_json};\n"}
|
21
|
+
end
|
22
|
+
if options[:ajax]
|
23
|
+
xhtml.div(:id => 'dynamic_recaptcha') {}
|
24
|
+
xhtml.script(:type => "text/javascript", :src => "#{uri}/js/recaptcha_ajax.js") {}
|
25
|
+
xhtml.script(:type => "text/javascript") do |x|
|
26
|
+
x << "Recaptcha.create('#{key}', document.getElementById('dynamic_recaptcha') );"
|
27
|
+
end
|
28
|
+
else
|
29
|
+
xhtml.script(:type => "text/javascript", :src => :"#{uri}/challenge?k=#{key}&error=#{error}") {}
|
30
|
+
unless options[:noscript] == false
|
31
|
+
xhtml.noscript do
|
32
|
+
xhtml.iframe(:src => "#{uri}/noscript?k=#{key}",
|
33
|
+
:height => options[:iframe_height] ||= 300,
|
34
|
+
:width => options[:iframe_width] ||= 500,
|
35
|
+
:frameborder => 0) {}; xhtml.br
|
36
|
+
xhtml.textarea nil, :name => "recaptcha_challenge_field",
|
37
|
+
:rows => options[:textarea_rows] ||= 3,
|
38
|
+
:cols => options[:textarea_cols] ||= 40
|
39
|
+
xhtml.input :name => "recaptcha_response_field",
|
40
|
+
:type => "hidden", :value => "manual_challenge"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
raise ReCaptchaError, "No public key specified." unless key
|
45
|
+
return out
|
46
|
+
end # recaptcha_tags
|
47
|
+
end # Helpers
|
48
|
+
|
49
|
+
module Controller
|
50
|
+
# Your private API can be specified in the +options+ hash or preferably the environment
|
51
|
+
# variable +RECAPTCHA_PUBLIC_KEY+.
|
52
|
+
def verify_recaptcha(options = {})
|
53
|
+
return true if SKIP_VERIFY_ENV.include? ENV['RAILS_ENV']
|
54
|
+
private_key = options[:private_key] ||= ENV['RECAPTCHA_PRIVATE_KEY']
|
55
|
+
raise ReCaptchaError, "No private key specified." unless private_key
|
56
|
+
begin
|
57
|
+
recaptcha = Net::HTTP.post_form URI.parse("http://#{RECAPTCHA_VERIFY_SERVER}/verify"), {
|
58
|
+
:privatekey => private_key,
|
59
|
+
:remoteip => request.remote_ip,
|
60
|
+
:challenge => params[:recaptcha_challenge_field],
|
61
|
+
:response => params[:recaptcha_response_field]
|
62
|
+
}
|
63
|
+
answer, error = recaptcha.body.split.map { |s| s.chomp }
|
64
|
+
unless answer == 'true'
|
65
|
+
session[:recaptcha_error] = error
|
66
|
+
if model = options[:model]
|
67
|
+
model.valid?
|
68
|
+
model.errors.add_to_base "Captcha response is incorrect, please try again."
|
69
|
+
end
|
70
|
+
return false
|
71
|
+
else
|
72
|
+
session[:recaptcha_error] = nil
|
73
|
+
return true
|
74
|
+
end
|
75
|
+
rescue Exception => e
|
76
|
+
raise ReCaptchaError, e
|
77
|
+
end
|
78
|
+
end # verify_recaptcha
|
79
|
+
end # ControllerHelpers
|
80
|
+
|
81
|
+
class ReCaptchaError < StandardError; end
|
82
|
+
|
83
|
+
end # ReCaptcha
|
84
|
+
end # Ambethia
|
data/recaptcha.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{recaptcha}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Jason L. Perry"]
|
9
|
+
s.date = %q{2009-02-08}
|
10
|
+
s.description = %q{This plugin adds helpers for the ReCAPTCHA API}
|
11
|
+
s.email = %q{jasper@ambethia.com}
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "lib/recaptcha/recaptcha.rb", "lib/recaptcha.rb", "LICENSE", "README.rdoc", "tasks/recaptcha_tasks.rake"]
|
13
|
+
s.files = ["CHANGELOG", "init.rb", "lib/recaptcha/recaptcha.rb", "lib/recaptcha.rb", "LICENSE", "Manifest", "Rakefile", "README.rdoc", "tasks/recaptcha_tasks.rake", "test/recaptcha_test.rb", "test/verify_recaptcha_test.rb", "recaptcha.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/ambethia/recaptcha/tree/master}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Recaptcha", "--main", "README.rdoc"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{recaptcha}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{This plugin adds helpers for the ReCAPTCHA API}
|
21
|
+
s.test_files = ["test/recaptcha_test.rb", "test/verify_recaptcha_test.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 2
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
else
|
29
|
+
end
|
30
|
+
else
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'builder'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/recaptcha'
|
5
|
+
|
6
|
+
class ReCaptchaTest < Test::Unit::TestCase
|
7
|
+
include Ambethia::ReCaptcha
|
8
|
+
include Ambethia::ReCaptcha::Helper
|
9
|
+
include Ambethia::ReCaptcha::Controller
|
10
|
+
|
11
|
+
attr_accessor :session
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@session = {}
|
15
|
+
ENV['RECAPTCHA_PUBLIC_KEY'] = '0000000000000000000000000000000000000000'
|
16
|
+
ENV['RECAPTCHA_PRIVATE_KEY'] = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_recaptcha_tags
|
20
|
+
# Might as well match something...
|
21
|
+
assert_match /http:\/\/api.recaptcha.net/, recaptcha_tags
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_recaptcha_tags_with_ssl
|
25
|
+
assert_match /https:\/\/api-secure.recaptcha.net/, recaptcha_tags(:ssl => true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_recaptcha_tags_without_noscript
|
29
|
+
assert_no_match /noscript/, recaptcha_tags(:noscript => false)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_raise_exception_without_public_key
|
33
|
+
assert_raise ReCaptchaError do
|
34
|
+
ENV['RECAPTCHA_PUBLIC_KEY'] = nil
|
35
|
+
recaptcha_tags
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'mocha'
|
4
|
+
require 'net/http'
|
5
|
+
require File.dirname(__FILE__) + '/../lib/recaptcha'
|
6
|
+
|
7
|
+
class VerifyReCaptchaTest < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
ENV['RECAPTCHA_PRIVATE_KEY'] = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
|
10
|
+
|
11
|
+
@controller = TestController.new
|
12
|
+
@controller.request = stub(:remote_ip => "1.1.1.1")
|
13
|
+
@controller.params = {:recaptcha_challenge_field => "challenge", :recaptcha_response_field => "response"}
|
14
|
+
|
15
|
+
@expected_post_data = {}
|
16
|
+
@expected_post_data[:privatekey] = ENV['RECAPTCHA_PRIVATE_KEY']
|
17
|
+
@expected_post_data[:remoteip] = @controller.request.remote_ip
|
18
|
+
@expected_post_data[:challenge] = "challenge"
|
19
|
+
@expected_post_data[:response] = "response"
|
20
|
+
|
21
|
+
@expected_uri = URI.parse("http://#{Ambethia::ReCaptcha::RECAPTCHA_VERIFY_SERVER}/verify")
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_raise_exception_without_private_key
|
25
|
+
assert_raise Ambethia::ReCaptcha::ReCaptchaError do
|
26
|
+
ENV['RECAPTCHA_PRIVATE_KEY'] = nil
|
27
|
+
@controller.verify_recaptcha
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_should_return_false_when_key_is_invalid
|
32
|
+
expect_http_post(response_with_body("false\ninvalid-site-private-key"))
|
33
|
+
|
34
|
+
assert !@controller.verify_recaptcha
|
35
|
+
assert_equal "invalid-site-private-key", @controller.session[:recaptcha_error]
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_returns_true_on_success
|
39
|
+
@controller.session[:recaptcha_error] = "previous error that should be cleared"
|
40
|
+
expect_http_post(response_with_body("true\n"))
|
41
|
+
|
42
|
+
assert @controller.verify_recaptcha
|
43
|
+
assert_nil @controller.session[:recaptcha_error]
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_errors_should_be_added_to_model
|
47
|
+
expect_http_post(response_with_body("false\nbad-news"))
|
48
|
+
|
49
|
+
errors = mock
|
50
|
+
errors.expects(:add_to_base).with("Captcha response is incorrect, please try again.")
|
51
|
+
|
52
|
+
model = mock
|
53
|
+
model.expects(:valid?)
|
54
|
+
model.expects(:errors).returns(errors)
|
55
|
+
|
56
|
+
assert !@controller.verify_recaptcha(:model => model)
|
57
|
+
assert_equal "bad-news", @controller.session[:recaptcha_error]
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_returns_true_on_success_with_optional_key
|
61
|
+
@controller.session[:recaptcha_error] = "previous error that should be cleared"
|
62
|
+
# reset private key
|
63
|
+
@expected_post_data[:privatekey] = 'ADIFFERENTPRIVATEKEYXXXXXXXXXXXXXX'
|
64
|
+
expect_http_post(response_with_body("true\n"))
|
65
|
+
|
66
|
+
assert @controller.verify_recaptcha(:private_key => 'ADIFFERENTPRIVATEKEYXXXXXXXXXXXXXX')
|
67
|
+
assert_nil @controller.session[:recaptcha_error]
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
class TestController
|
73
|
+
include Ambethia::ReCaptcha::Controller
|
74
|
+
attr_accessor :request, :params, :session
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
@session = {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def expect_http_post(response)
|
82
|
+
Net::HTTP.expects(:post_form).with(@expected_uri, @expected_post_data).returns(response)
|
83
|
+
end
|
84
|
+
|
85
|
+
def response_with_body(body)
|
86
|
+
stub(:body => body)
|
87
|
+
end
|
88
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mwhuss-recaptcha
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jason L. Perry
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-08 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: This plugin adds helpers for the ReCAPTCHA API
|
17
|
+
email: jasper@ambethia.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- CHANGELOG
|
24
|
+
- lib/recaptcha/recaptcha.rb
|
25
|
+
- lib/recaptcha.rb
|
26
|
+
- LICENSE
|
27
|
+
- README.rdoc
|
28
|
+
- tasks/recaptcha_tasks.rake
|
29
|
+
files:
|
30
|
+
- CHANGELOG
|
31
|
+
- init.rb
|
32
|
+
- lib/recaptcha/recaptcha.rb
|
33
|
+
- lib/recaptcha.rb
|
34
|
+
- LICENSE
|
35
|
+
- Manifest
|
36
|
+
- Rakefile
|
37
|
+
- README.rdoc
|
38
|
+
- tasks/recaptcha_tasks.rake
|
39
|
+
- test/recaptcha_test.rb
|
40
|
+
- test/verify_recaptcha_test.rb
|
41
|
+
- recaptcha.gemspec
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://github.com/ambethia/recaptcha/tree/master
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options:
|
46
|
+
- --line-numbers
|
47
|
+
- --inline-source
|
48
|
+
- --title
|
49
|
+
- Recaptcha
|
50
|
+
- --main
|
51
|
+
- README.rdoc
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "1.2"
|
65
|
+
version:
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project: recaptcha
|
69
|
+
rubygems_version: 1.2.0
|
70
|
+
signing_key:
|
71
|
+
specification_version: 2
|
72
|
+
summary: This plugin adds helpers for the ReCAPTCHA API
|
73
|
+
test_files:
|
74
|
+
- test/recaptcha_test.rb
|
75
|
+
- test/verify_recaptcha_test.rb
|