re_captcha 0.0.6

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4ff6ffb0b40900669efac36740ebcfcc1f9055e2
4
+ data.tar.gz: bda9557db4b3f23a9fd930b81742222a7450eaeb
5
+ SHA512:
6
+ metadata.gz: c8610eff65a759829a7cda08b0e941924d5e11b8ca1c0c053cdf6439f1a97f83e901a47572a356709cb85e00e7edd81d4a4d2ab2bebb46a4241c0414399d7fa1
7
+ data.tar.gz: a792fa3709ae8f52fee267e214a04311ef4fd2d6c33d0d491a85a938ce01291e3246aa64ea61ef3e329431e4d4802144e3e734b6c3aa25b032c17b8c4fbeb32b
data/.gitignore ADDED
@@ -0,0 +1,35 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /vendor/bundle
26
+ /lib/bundler/man/
27
+
28
+ # for a library or gem, you might want to ignore these files since the code is
29
+ # intended to run in multiple environments; otherwise, check them in:
30
+ Gemfile.lock
31
+ .ruby-version
32
+ .ruby-gemset
33
+
34
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
35
+ .rvmrc
data/.rubocop.yml ADDED
@@ -0,0 +1,76 @@
1
+
2
+ AllCops:
3
+ RunRailsCops: true
4
+
5
+ LineLength:
6
+ Max: 120
7
+
8
+ Style/AlignParameters:
9
+ Enabled: false
10
+
11
+ Style/Encoding:
12
+ EnforcedStyle: 'when_needed'
13
+
14
+ Style/StringLiterals:
15
+ EnforcedStyle: single_quotes
16
+
17
+ Style/Blocks:
18
+ Enabled: false
19
+
20
+ Style/TrailingComma:
21
+ Enabled: false
22
+
23
+ Style/SingleSpaceBeforeFirstArg:
24
+ Enabled: false
25
+
26
+ Metrics/MethodLength:
27
+ Enabled: false
28
+
29
+ Metrics/ClassLength:
30
+ Enabled: false
31
+
32
+ Metrics/CyclomaticComplexity:
33
+ Enabled: false
34
+
35
+ Metrics/PerceivedComplexity:
36
+ Enabled: false
37
+
38
+ Metrics/ParameterLists:
39
+ Enabled: false
40
+
41
+ Style/Documentation:
42
+ Enabled: false
43
+
44
+ Style/AccessorMethodName:
45
+ Enabled: false
46
+
47
+ Rails/ActionFilter:
48
+ Enabled: false
49
+
50
+ Metrics/BlockNesting:
51
+ Enabled: false
52
+
53
+ DotPosition:
54
+ EnforcedStyle: leading
55
+
56
+ PredicateName:
57
+ NamePrefixBlacklist:
58
+ - is_
59
+
60
+ Style/EmptyLinesAroundBlockBody:
61
+ Enabled: false
62
+
63
+ Style/CollectionMethods:
64
+ # Mapping from undesired method to desired_method
65
+ # e.g. to use `detect` over `find`:
66
+ #
67
+ # CollectionMethods:
68
+ # PreferredMethods:
69
+ # find: detect
70
+ PreferredMethods:
71
+ collect: 'map'
72
+ collect!: 'map!'
73
+ inject: 'reduce'
74
+ reduce: false
75
+ find: 'detect'
76
+ find_all: 'select'
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ * 0.0.1
2
+
3
+ - First release
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rest-client'
6
+
7
+ group :development do
8
+ gem 'rake'
9
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 David Jeusette
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # ReCaptcha
2
+
3
+ Gem to easily use reCaptcha
4
+
5
+ Run tests with ``` rake ```
6
+
7
+ Run console with preloaded library with ``` rake console ```
8
+
9
+ ## Getting started
10
+
11
+ You first need to configure the gem.
12
+
13
+ ```
14
+ ReCaptcha.configure do |config|
15
+ config.private_key = "secret key"
16
+ config.public_key = "site key"
17
+ end
18
+
19
+ ```
20
+
21
+ Other options are:
22
+ - api_endpoint (default: https://www.google.com/recaptcha/)
23
+ - skipped_env (default: ['test', 'cucumber'])
24
+ - language_table: the table to map locale with language code
25
+ - deny_on_error: if the Google reCaptcha API can't be accessed, deny the verification (default: false)
26
+
27
+ The default language table is the following:
28
+
29
+ ```
30
+ {
31
+ 'en-US' => 'en',
32
+ 'fr-FR' => 'fr',
33
+ 'es-ES' => 'es',
34
+ 'pt-PT' => 'pt-PT',
35
+ 'it-IT' => 'it',
36
+ 'en-GB' => 'en-GB',
37
+ 'de-DE' => 'de',
38
+ 'pt-BR' => 'pt-BR',
39
+ 'en-EU' => 'en-GB',
40
+ 'es-LA' => 'es-419',
41
+ 'zh-CN' => 'zh-CN',
42
+ }
43
+ ```
44
+
45
+ ## Display
46
+
47
+ To access the helpers in the views, make sure to include them in a Helper class
48
+ such as ApplicationHelper (Rails)
49
+
50
+ ```
51
+ module ApplicationHelper
52
+ include ReCaptcha::Helper
53
+ end
54
+ ```
55
+
56
+ Then include the script tag using this helper method in your view:
57
+ ```
58
+ recaptcha_script(language: nil)
59
+ ```
60
+ language is one of the locale defined in the language table.
61
+
62
+ You can now add the reCaptcha box in your forms:
63
+ ```
64
+ recaptcha_tags(options = {})
65
+ ```
66
+
67
+ The options are the following:
68
+ - theme: 'light'
69
+ - type: 'image'
70
+ - size: 'normal'
71
+ - tab_index: 0
72
+ - callback: nil
73
+ - expired_callback: nil
74
+
75
+ Check the reCaptcha doc for the available values (https://developers.google.com/recaptcha/docs/display).
76
+
77
+ ## Verification
78
+
79
+ Assuming that your application uses Rails, verify the reCaptcha response using the method below:
80
+ ```
81
+ recaptcha_valid?(response, remote_ip: nil, model: nil, message: nil)
82
+ ```
83
+
84
+ This method can be called like this in a controller: ```ReCaptcha.client.recaptcha_valid?(...)```
85
+
86
+ The response should be retrieved with ```params.fetch(:"g-recaptcha-response", "")```. Optional args model and message allow to add an error with the given message on the :base attr of the provided model.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ desc 'Open an irb session with ReCaptcha preloaded'
4
+ task :console do
5
+ exec 'irb -I lib -r re_captcha'
6
+ end
7
+
8
+ desc 'Run the test suite'
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.pattern = FileList['spec/**/*_spec.rb']
11
+ t.rspec_opts = %w(--color --format doc)
12
+ end
13
+
14
+ task default: :spec
15
+ task test: :spec
@@ -0,0 +1,30 @@
1
+ require 'rest-client'
2
+
3
+ module ReCaptcha
4
+ module API
5
+ def post(path, params, options: {}, end_point: api_endpoint)
6
+ http_request do
7
+ uri = URI(end_point).merge(path).to_s
8
+ resource = RestClient::Resource.new(uri, options.merge(default_options))
9
+ response = resource.post params.merge(default_params)
10
+ JSON.parse response
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def default_params
17
+ { secret: private_key }
18
+ end
19
+
20
+ def default_options
21
+ { read_timeout: 3, open_timeout: 3 }
22
+ end
23
+
24
+ def http_request(&block)
25
+ block.call
26
+ rescue RestClient::RequestTimeout, RestClient::ExceptionWithResponse, RestClient::RequestFailed
27
+ { 'success' => !deny_on_error }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ require 're_captcha/api'
2
+ require 're_captcha/secure_token_builder'
3
+
4
+ module ReCaptcha
5
+ module Application
6
+ include API
7
+
8
+ def recaptcha_valid?(response, remote_ip: nil, model: nil, message: nil)
9
+ return true if should_skip_verification?
10
+ params = generate_verification_params(response, remote_ip)
11
+ verification = verify_recaptcha(params)
12
+ valid = verification['success']
13
+ add_error_on_model(model, message) unless valid
14
+ valid
15
+ end
16
+
17
+ def secure_token
18
+ secure_token_builder = SecureTokenBuilder.new(private_key)
19
+ secure_token_builder.build
20
+ end
21
+
22
+ private
23
+
24
+ def verify_recaptcha(params)
25
+ post 'api/siteverify', params, options: { verify_ssl: false }
26
+ end
27
+
28
+ def generate_verification_params(response, remote_ip)
29
+ { response: response, secret: private_key, remoteip: remote_ip }
30
+ end
31
+
32
+ def should_skip_verification?
33
+ skipped_env.include? env
34
+ end
35
+
36
+ def add_error_on_model(model, message)
37
+ model.errors.add :base, message if model && message
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,22 @@
1
+ require 'forwardable'
2
+ require 're_captcha/application'
3
+ require 're_captcha/exceptions'
4
+
5
+ module ReCaptcha
6
+ class Client
7
+ extend Forwardable
8
+ include Application
9
+
10
+ attr_reader :env
11
+
12
+ def initialize(configuration)
13
+ @configuration = configuration
14
+ @env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
15
+ raise ConfigurationError.new('Invalid configuration') unless configuration.valid?
16
+ end
17
+
18
+ def_delegators :@configuration, :api_endpoint,
19
+ :public_key, :private_key, :skipped_env, :language_code,
20
+ :deny_on_error
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ require 're_captcha/configuration'
2
+
3
+ module ReCaptcha
4
+ module Configurable
5
+ def configuration
6
+ @configuration ||= Configuration.new
7
+ end
8
+
9
+ def configure
10
+ yield(configuration) if block_given?
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ module ReCaptcha
2
+ class Configuration
3
+ API_END_POINT = 'https://www.google.com/recaptcha/'
4
+ SKIPPED_ENVIRONMENTS = %w(test cucumber)
5
+ LANGUAGE_TABLE = {
6
+ 'en-US' => 'en',
7
+ 'fr-FR' => 'fr',
8
+ 'es-ES' => 'es',
9
+ 'pt-PT' => 'pt-PT',
10
+ 'it-IT' => 'it',
11
+ 'en-GB' => 'en-GB',
12
+ 'de-DE' => 'de',
13
+ 'pt-BR' => 'pt-BR',
14
+ 'en-EU' => 'en-GB',
15
+ 'es-LA' => 'es-419',
16
+ 'zh-CN' => 'zh-CN',
17
+ }
18
+
19
+ attr_accessor :api_endpoint, :public_key,
20
+ :private_key, :skipped_env, :language_table, :deny_on_error
21
+
22
+ def initialize
23
+ @api_endpoint = API_END_POINT
24
+ @skipped_env = SKIPPED_ENVIRONMENTS
25
+ @public_key = ENV['RECAPTCHA_PUBLIC_KEY']
26
+ @private_key = ENV['RECAPTCHA_PRIVATE_KEY']
27
+ @language_table = LANGUAGE_TABLE
28
+ @deny_on_error = false
29
+ end
30
+
31
+ def language_code(locale)
32
+ @language_table[locale] || 'en'
33
+ end
34
+
35
+ def valid?
36
+ !private_key.nil? && !public_key.nil?
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module ReCaptcha
2
+ class ConfigurationError < StandardError; end
3
+ end
@@ -0,0 +1,31 @@
1
+ module ReCaptcha
2
+ module Helper
3
+ def recaptcha_tags(options = {})
4
+ html = ''
5
+ html << %(<div class="g-recaptcha" data-sitekey="#{ReCaptcha.client.public_key}" )
6
+ html << %(data-stoken="#{ReCaptcha.client.secure_token}" #{tag_attributes(options)}></div>\n)
7
+ html.respond_to?(:html_safe) ? html.html_safe : html
8
+ end
9
+
10
+ def recaptcha_script(language: nil)
11
+ html = ''
12
+ html << %(<script src="#{ReCaptcha.client.api_endpoint}api.js#{language_query(language)}" async defer></script>\n)
13
+ html.respond_to?(:html_safe) ? html.html_safe : html
14
+ end
15
+
16
+ private
17
+
18
+ def tag_attributes(theme: 'light', type: 'image', size: 'normal',
19
+ tab_index: 0, callback: nil, expired_callback: nil)
20
+ attributes = ''
21
+ attributes << %(data-theme="#{theme}" data-type="#{type}" data-size="#{size}" data-tabindex="#{tab_index}" )
22
+ attributes << %(data-callback="#{callback}" ) if callback
23
+ attributes << %(data-expired-callback="#{expired_callback}") if expired_callback
24
+ attributes.respond_to?(:html_safe) ? attributes.html_safe : attributes
25
+ end
26
+
27
+ def language_query(language)
28
+ "?hl=#{ReCaptcha.client.language_code(language)}" if language
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,45 @@
1
+ require 'json'
2
+ require 'securerandom'
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module ReCaptcha
7
+ class SecureTokenBuilder
8
+ def initialize(private_key)
9
+ @private_key = private_key
10
+ end
11
+
12
+ def build
13
+ json_token = generate_json_token
14
+ private_key_digest = digest_key @private_key
15
+ cipher = prepare_cipher private_key_digest
16
+ encode_token json_token, cipher
17
+ end
18
+
19
+ private
20
+
21
+ def encode_token(token, cipher)
22
+ encrypted_token = cipher.update(token) << cipher.final
23
+ strip_padding Base64.urlsafe_encode64(encrypted_token)
24
+ end
25
+
26
+ def digest_key(key)
27
+ Digest::SHA1.digest(key)[0...16]
28
+ end
29
+
30
+ def prepare_cipher(key)
31
+ cipher = OpenSSL::Cipher::AES128.new(:ECB)
32
+ cipher.encrypt
33
+ cipher.key = key
34
+ cipher
35
+ end
36
+
37
+ def generate_json_token
38
+ { session_id: SecureRandom.uuid, ts_ms: (Time.now.to_f * 1000).to_i }.to_json
39
+ end
40
+
41
+ def strip_padding(string)
42
+ string.gsub(/\=+\Z/, '')
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module ReCaptcha
2
+ VERSION = '0.0.6'.freeze
3
+ end
data/lib/re_captcha.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 're_captcha/configurable'
2
+ require 're_captcha/client'
3
+ require 're_captcha/helper'
4
+
5
+ module ReCaptcha
6
+ extend Configurable
7
+
8
+ MUTEX = Mutex.new
9
+
10
+ def self.client
11
+ MUTEX.synchronize do
12
+ @client ||= ReCaptcha::Client.new(configuration)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path('../lib/re_captcha/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.add_development_dependency 'rspec', '~> 3.0'
5
+ gem.add_development_dependency 'bundler', '~> 1.7'
6
+ gem.add_development_dependency 'rake', '~> 10.0'
7
+ gem.add_development_dependency 'webmock', '~> 1.21'
8
+
9
+ gem.name = 're_captcha'
10
+ gem.summary = 'reCaptcha helpers'
11
+ gem.description = 'Google reCaptcha helpers and verifier'
12
+ gem.version = ReCaptcha::VERSION.dup
13
+ gem.authors = ['David Jeusette']
14
+ gem.email = ['david@textmaster.com']
15
+ gem.homepage = 'https://github.com/textmaster/re_captcha'
16
+ gem.require_paths = ['lib']
17
+ gem.license = 'MIT'
18
+
19
+ gem.files = `git ls-files`.split("\n")
20
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
21
+ end
@@ -0,0 +1,225 @@
1
+ require 'spec_helper'
2
+
3
+ describe ReCaptcha::Client do
4
+
5
+ describe '#new' do
6
+ it 'should not accept no parameters' do
7
+ expect { described_class.new }.to raise_error ArgumentError
8
+ end
9
+
10
+ context 'with an invalid configuration' do
11
+ let(:configuration) { instance_double('ReCaptcha::Configuration', valid?: false) }
12
+
13
+ it 'should raise a ReCaptcha::ConfigurationError error' do
14
+ expect { described_class.new(configuration) }.to raise_error ReCaptcha::ConfigurationError
15
+ end
16
+ end
17
+
18
+ context 'with a valid configuration' do
19
+ let(:configuration) { instance_double('ReCaptcha::Configuration', valid?: true) }
20
+ let(:instance) { described_class.new(configuration) }
21
+
22
+ it 'should accept (configuration) as parameter' do
23
+ expect(described_class).to receive(:new).with(configuration).and_return(instance)
24
+ expect { described_class.new(configuration) }.to_not raise_error
25
+ end
26
+
27
+ it 'should return a proper instance' do
28
+ expect { described_class.new(configuration) }.to_not raise_error
29
+ end
30
+ end
31
+ end
32
+
33
+ describe 'instance' do
34
+ before(:all) do
35
+ ReCaptcha.configure do |config|
36
+ config.public_key = 'foo'
37
+ config.private_key = 'bar'
38
+ config.skipped_env = ['my-test']
39
+ end
40
+ end
41
+
42
+ let(:configuration) { ReCaptcha.configuration }
43
+ let(:instance) { ReCaptcha.client }
44
+
45
+ it { expect(instance).to respond_to(:env) }
46
+ it { expect(instance).to respond_to(:private_key) }
47
+ it { expect(instance).to respond_to(:public_key) }
48
+ it { expect(instance).to respond_to(:api_endpoint) }
49
+ it { expect(instance).to respond_to(:skipped_env) }
50
+ it { expect(instance).to respond_to(:recaptcha_valid?) }
51
+ it { expect(instance).to respond_to(:secure_token) }
52
+
53
+ describe '#private_key' do
54
+ subject(:private_key) { instance.private_key }
55
+
56
+ it 'delegates to configuration' do
57
+ expect(configuration).to receive(:private_key)
58
+ instance.private_key
59
+ end
60
+
61
+ it 'returns the correct private key' do
62
+ expect(subject).to eq('bar')
63
+ end
64
+ end
65
+
66
+ describe '#public_key' do
67
+ subject(:public_key) { instance.public_key }
68
+
69
+ it 'delegates to configuration' do
70
+ expect(configuration).to receive(:public_key)
71
+ instance.public_key
72
+ end
73
+
74
+ it 'returns the correct public key' do
75
+ expect(subject).to eq('foo')
76
+ end
77
+ end
78
+
79
+ describe '#api_endpoint' do
80
+ subject(:api_endpoint) { instance.api_endpoint }
81
+
82
+ it 'delegates to configuration' do
83
+ expect(configuration).to receive(:api_endpoint)
84
+ instance.api_endpoint
85
+ end
86
+
87
+ it 'returns the correct api_endpoint' do
88
+ expect(subject).to eq('https://www.google.com/recaptcha/')
89
+ end
90
+ end
91
+
92
+ describe '#skipped_env' do
93
+ subject(:skipped_env) { instance.skipped_env }
94
+
95
+ it 'delegates to configuration' do
96
+ expect(configuration).to receive(:skipped_env)
97
+ instance.skipped_env
98
+ end
99
+
100
+ it 'returns the correct skipped_env' do
101
+ expect(subject).to be_a(Array)
102
+ expect(subject).to eq(['my-test'])
103
+ end
104
+ end
105
+
106
+ describe '#deny_on_error' do
107
+ subject(:deny_on_error) { instance.deny_on_error }
108
+
109
+ it 'delegates to configuration' do
110
+ expect(configuration).to receive(:deny_on_error)
111
+ instance.deny_on_error
112
+ end
113
+
114
+ it 'returns the correct value' do
115
+ expect(subject).to be_a(FalseClass)
116
+ expect(subject).to eq(false)
117
+ end
118
+ end
119
+
120
+ describe '#language_code' do
121
+ it 'delegates to configuration' do
122
+ expect(configuration).to receive(:language_code)
123
+ instance.language_code 'zh-CN'
124
+ end
125
+ end
126
+
127
+ describe '#recaptcha_valid?' do
128
+ context 'with a correct response' do
129
+
130
+ before(:all) do
131
+ body = { 'remoteip' => '', 'response' => 'correct response', 'secret' => 'bar' }
132
+ stub_request(:post, 'https://www.google.com/recaptcha/api/siteverify')
133
+ .with(body: body)
134
+ .to_return(status: 200, body: { success: true }.to_json)
135
+ end
136
+
137
+ let(:response) { 'correct response' }
138
+
139
+ it 'returns true' do
140
+ expect(instance.recaptcha_valid?(response)).to eq(true)
141
+ end
142
+ end
143
+
144
+ context 'with an incorrect response' do
145
+
146
+ before(:all) do
147
+ body = { 'remoteip' => '', 'response' => 'incorrect response', 'secret' => 'bar' }
148
+ stub_request(:post, 'https://www.google.com/recaptcha/api/siteverify')
149
+ .with(body: body)
150
+ .to_return(status: 200, body: { success: false }.to_json)
151
+ end
152
+
153
+ let(:response) { 'incorrect response' }
154
+
155
+ it 'returns false' do
156
+ expect(instance.recaptcha_valid?(response)).to eq(false)
157
+ end
158
+ end
159
+
160
+ context 'in a skipped environment' do
161
+
162
+ it 'always returns true' do
163
+ allow(instance).to receive(:env) { 'my-test' }
164
+ expect(instance.recaptcha_valid?('foo')).to eq(true)
165
+ end
166
+ end
167
+
168
+ context 'when the request timeouts' do
169
+ before(:all) do
170
+ stub_request(:post, 'https://www.google.com/recaptcha/api/siteverify').to_timeout
171
+ end
172
+
173
+ it 'returns true' do
174
+ expect(instance.recaptcha_valid?('foo')).to eq(true)
175
+ end
176
+ end
177
+
178
+ context 'when Google responds with a 40X error' do
179
+ before(:all) do
180
+ stub_request(:post, 'https://www.google.com/recaptcha/api/siteverify')
181
+ .to_return(status: [401, 'Unauthorized'])
182
+ end
183
+
184
+ it 'returns true' do
185
+ expect(instance.recaptcha_valid?('foo')).to eq(true)
186
+ end
187
+ end
188
+
189
+ context 'when Google responds with a 50X error' do
190
+ before(:all) do
191
+ stub_request(:post, 'https://www.google.com/recaptcha/api/siteverify')
192
+ .to_return(status: [500, 'Internal Server Error'])
193
+ end
194
+
195
+ it 'returns true' do
196
+ expect(instance.recaptcha_valid?('foo')).to eq(true)
197
+ end
198
+ end
199
+
200
+ context 'with deny on error activated' do
201
+ context 'when Google responds with a 50X error' do
202
+ before(:all) do
203
+ stub_request(:post, 'https://www.google.com/recaptcha/api/siteverify')
204
+ .to_return(status: [500, 'Internal Server Error'])
205
+ end
206
+
207
+ it 'returns false' do
208
+ allow(instance).to receive(:deny_on_error) { true }
209
+ expect(instance.recaptcha_valid?('foo')).to eq(false)
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ describe '#secure_token' do
216
+ let(:secure_token_builder) { instance_double("ReCaptcha::SecureTokenBuilder") }
217
+
218
+ it 'delegates to SecureTokenBuilder' do
219
+ ReCaptcha::SecureTokenBuilder.stub(:new).and_return(secure_token_builder)
220
+ expect(secure_token_builder).to receive(:build)
221
+ instance.secure_token
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe ReCaptcha::Configuration do
4
+ let(:instance) { described_class.new }
5
+
6
+ describe 'instance' do
7
+ it { expect(instance).to respond_to(:api_endpoint) }
8
+ it { expect(instance).to respond_to(:public_key) }
9
+ it { expect(instance).to respond_to(:private_key) }
10
+ it { expect(instance).to respond_to(:skipped_env) }
11
+ it { expect(instance).to respond_to(:language_code) }
12
+ it { expect(instance).to respond_to(:deny_on_error) }
13
+
14
+ describe '#language_code' do
15
+ context 'when the language table contains the locale' do
16
+ it 'should return the correct language code' do
17
+ expect(instance.language_code('zh-CN')).to eq('zh-CN')
18
+ end
19
+ end
20
+
21
+ context 'when the language table does not contain the locale' do
22
+ it 'should return "en"' do
23
+ expect(instance.language_code('foo')).to eq('en')
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#valid?' do
29
+ context 'when private and public keys are given' do
30
+ it 'should return true' do
31
+ allow(instance).to receive(:private_key) { 'foo' }
32
+ allow(instance).to receive(:public_key) { 'bar' }
33
+ expect(instance.valid?).to eq(true)
34
+ end
35
+ end
36
+
37
+ context 'when the private or public key is missing' do
38
+ it 'should return false' do
39
+ allow(instance).to receive(:public_key) { 'bar' }
40
+ allow(instance).to receive(:private_key) { nil }
41
+ expect(instance.valid?).to eq(false)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe ReCaptcha::Helper do
4
+ let(:dummy_class) { Class.new { include ReCaptcha::Helper } }
5
+
6
+ before(:all) do
7
+ ReCaptcha.configure do |config|
8
+ config.private_key = 'foo'
9
+ config.public_key = 'bar'
10
+ end
11
+ end
12
+
13
+ subject { dummy_class.new }
14
+
15
+ it { expect(subject).to respond_to(:recaptcha_tags) }
16
+ it { expect(subject).to respond_to(:recaptcha_script) }
17
+
18
+ describe '#recaptcha_tags' do
19
+ context 'with options' do
20
+ it 'should return an html tag' do
21
+ expect(subject.recaptcha_tags(theme: 'dark')).to be_a(String)
22
+ expect(subject.recaptcha_tags(theme: 'dark')).to match(/data-theme="dark"/)
23
+ end
24
+ end
25
+
26
+ context 'without options' do
27
+ it 'should return an html tag' do
28
+ expect(subject.recaptcha_tags).to be_a(String)
29
+ expect(subject.recaptcha_tags).to match(/data-theme="light"/)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe '#recaptcha_script' do
35
+ context 'without locale' do
36
+ it 'should return an html tag without language code' do
37
+ expect(subject.recaptcha_script).to be_a(String)
38
+ expect(subject.recaptcha_script).to match(/api.js\" async defer>/)
39
+ end
40
+ end
41
+
42
+ context 'with a known locale' do
43
+ it 'should return an html tag with the right language code' do
44
+ expect(subject.recaptcha_script(language: 'zh-CN')).to be_a(String)
45
+ expect(subject.recaptcha_script(language: 'zh-CN')).to match(/api.js\?hl=zh-CN\" async defer>/)
46
+ end
47
+ end
48
+
49
+ context 'with an unknown locale' do
50
+ it 'should return an html tag with the "en" language code' do
51
+ expect(subject.recaptcha_script(language: 'foo-BAR')).to be_a(String)
52
+ expect(subject.recaptcha_script(language: 'foo-BAR')).to match(/api.js\?hl=en\" async defer>/)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe ReCaptcha::SecureTokenBuilder do
4
+ let(:key) { 'my secret key' }
5
+ let(:instance) { described_class.new(key)}
6
+
7
+ describe 'instance' do
8
+ it { expect(instance).to respond_to(:build) }
9
+
10
+ describe '#build' do
11
+ it 'builds a secure token' do
12
+ expect(instance.build).to be_a(String)
13
+ expect(instance.build).not_to be_empty
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe ReCaptcha do
4
+ it { expect(described_class).to respond_to(:client) }
5
+ it { expect(described_class).to respond_to(:configuration) }
6
+ it { expect(described_class).to respond_to(:configure) }
7
+
8
+ describe '.configure' do
9
+ let(:configuration) { instance_double('ReCaptcha::Configuration') }
10
+
11
+ it 'yields configuration' do
12
+ expect(described_class).to receive(:configure).and_yield(configuration)
13
+ expect { |b| described_class.configure(&b) }.to yield_with_args(configuration)
14
+ end
15
+ end
16
+
17
+ describe '.configuration' do
18
+ let(:configuration) { instance_double('ReCaptcha::Configuration') }
19
+
20
+ it 'returns the configuration' do
21
+ expect(described_class).to receive(:configuration).and_return(configuration)
22
+ described_class.configuration
23
+ end
24
+ end
25
+
26
+ describe '.client' do
27
+ let(:client) { instance_double('ReCaptcha::Client') }
28
+
29
+ it 'returns the client instance' do
30
+ expect(described_class).to receive(:client).and_return(client)
31
+ described_class.client
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ require 're_captcha'
2
+ require 'webmock/rspec'
3
+ require 'json'
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: re_captcha
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ platform: ruby
6
+ authors:
7
+ - David Jeusette
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.21'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.21'
69
+ description: Google reCaptcha helpers and verifier
70
+ email:
71
+ - david@textmaster.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rubocop.yml"
78
+ - CHANGELOG.md
79
+ - Gemfile
80
+ - LICENSE
81
+ - README.md
82
+ - Rakefile
83
+ - lib/re_captcha.rb
84
+ - lib/re_captcha/api.rb
85
+ - lib/re_captcha/application.rb
86
+ - lib/re_captcha/client.rb
87
+ - lib/re_captcha/configurable.rb
88
+ - lib/re_captcha/configuration.rb
89
+ - lib/re_captcha/exceptions.rb
90
+ - lib/re_captcha/helper.rb
91
+ - lib/re_captcha/secure_token_builder.rb
92
+ - lib/re_captcha/version.rb
93
+ - re_captcha.gemspec
94
+ - spec/re_captcha/client_spec.rb
95
+ - spec/re_captcha/configuration_spec.rb
96
+ - spec/re_captcha/helper_spec.rb
97
+ - spec/re_captcha/secure_token_builder_spec.rb
98
+ - spec/re_captcha_spec.rb
99
+ - spec/spec_helper.rb
100
+ homepage: https://github.com/textmaster/re_captcha
101
+ licenses:
102
+ - MIT
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.4.6
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: reCaptcha helpers
124
+ test_files:
125
+ - spec/re_captcha/client_spec.rb
126
+ - spec/re_captcha/configuration_spec.rb
127
+ - spec/re_captcha/helper_spec.rb
128
+ - spec/re_captcha/secure_token_builder_spec.rb
129
+ - spec/re_captcha_spec.rb
130
+ - spec/spec_helper.rb