recaptcha 0.6.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{CHANGELOG → CHANGELOG.md} +23 -8
- data/README.md +72 -204
- data/lib/recaptcha.rb +36 -16
- data/lib/recaptcha/client_helper.rb +36 -115
- data/lib/recaptcha/configuration.rb +11 -25
- data/lib/recaptcha/verify.rb +44 -79
- data/lib/recaptcha/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19150c1891f5ef4fda5c568d941c8035ef19c0aa
|
4
|
+
data.tar.gz: ffac23399306ce2b5c6c5199389cc755abfe2833
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0734d303c81f727913edef8c3f637b2fbf4d2f4612f170656bf1899b9a2be45fdd06401e63bd8eb9a98dac8eb57f9ffbc139a2d2d17a09f93ed0986cd349d452
|
7
|
+
data.tar.gz: cd08fa57ef7c4b3afdc03f8311ab7f4299d0c056686289a7bdff87dd87b6aa6fb5979a7313d0437c3b49ce9730c92000451f429aafe521fc66b5103f5989b8a5
|
data/{CHANGELOG → CHANGELOG.md}
RENAMED
@@ -1,4 +1,19 @@
|
|
1
|
-
|
1
|
+
## NEXT
|
2
|
+
* remove api v1 support
|
3
|
+
* remove ssl_api_server_url, nonssl_api_server_url, change api_server_url to always need ssl option
|
4
|
+
* removed activesupport dependency for .to_query
|
5
|
+
* made flash and models both have descriptive errors
|
6
|
+
|
7
|
+
## 0.6.0
|
8
|
+
* extract token module
|
9
|
+
* need to use `gem "recaptcha", require: "recaptcha/rails"` to get rails helpers installed
|
10
|
+
|
11
|
+
## 0.5.0
|
12
|
+
* size option
|
13
|
+
* support disabling stoken
|
14
|
+
* support Rails.env
|
15
|
+
|
16
|
+
## 0.3.6 / 2012-01-07
|
2
17
|
|
3
18
|
* Many documentation changes
|
4
19
|
* Fixed deprecations in dependencies
|
@@ -6,27 +21,27 @@
|
|
6
21
|
* Fixes for options hash
|
7
22
|
* Fixes for failing tests
|
8
23
|
|
9
|
-
|
24
|
+
## 0.3.5 / 2012-05-02
|
10
25
|
|
11
26
|
* I18n for error messages
|
12
27
|
* Rails: delete flash keys if unused
|
13
28
|
|
14
|
-
|
29
|
+
## 0.3.4 / 2011-12-13
|
15
30
|
|
16
31
|
* Rails 3
|
17
32
|
* Remove jeweler
|
18
33
|
|
19
|
-
|
34
|
+
## 0.2.2 / 2009-09-14
|
20
35
|
|
21
36
|
* Add a timeout to the validator
|
22
37
|
* Give the documentation some love
|
23
38
|
|
24
|
-
|
39
|
+
## 0.2.1 / 2009-09-14
|
25
40
|
|
26
41
|
* Removed Ambethia namespace, and restructured classes a bit
|
27
42
|
* Added an example rails app in the example-rails branch
|
28
43
|
|
29
|
-
|
44
|
+
## 0.2.0 / 2009-09-12
|
30
45
|
|
31
46
|
* RecaptchaOptions AJAX API Fix
|
32
47
|
* Added 'cucumber' as a test environment to skip
|
@@ -35,7 +50,7 @@
|
|
35
50
|
* Removed dependency on ActiveRecord constant
|
36
51
|
* Add I18n
|
37
52
|
|
38
|
-
|
53
|
+
## 0.1.0 / 2008-2-8
|
39
54
|
|
40
55
|
* 1 major enhancement
|
41
|
-
|
56
|
+
* Initial Gem Release
|
data/README.md
CHANGED
@@ -11,102 +11,56 @@ views you can use the `recaptcha_tags` method to embed the needed javascript,
|
|
11
11
|
and you can validate in your controllers with `verify_recaptcha` or `verify_recaptcha!`,
|
12
12
|
which throws an error on failiure.
|
13
13
|
|
14
|
-
Beforehand you need to configure Recaptcha with your custom private and public
|
15
|
-
key. You may find detailed examples below. Exceptions will be raised if you
|
16
|
-
call these methods and the keys can't be found.
|
17
|
-
|
18
14
|
## Rails Installation
|
19
15
|
|
20
|
-
|
21
|
-
gem "recaptcha", require: "recaptcha/rails"
|
22
|
-
```
|
23
|
-
|
24
|
-
(Rails < 3.0: install an older release and view it's README)
|
25
|
-
|
26
|
-
## Setting up your API Keys
|
27
|
-
|
28
|
-
There are multiple ways to setup your reCAPTCHA API key once you
|
29
|
-
[obtain a pair](https://www.google.com/recaptcha/admin).
|
30
|
-
|
31
|
-
### Recaptcha.configure
|
32
|
-
|
33
|
-
You may use the block style configuration. The following code could be placed
|
34
|
-
into a `config/initializers/recaptcha.rb` when used in a Rails project.
|
35
|
-
|
36
|
-
```Ruby
|
37
|
-
Recaptcha.configure do |config|
|
38
|
-
config.public_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
39
|
-
config.private_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
40
|
-
# Uncomment the following line if you are using a proxy server:
|
41
|
-
# config.proxy = 'http://myproxy.com.au:8080'
|
42
|
-
# Uncomment if you want to use the older version of the API,
|
43
|
-
# only works for versions >= 0.3.7, default value is 'v2':
|
44
|
-
# config.api_version = 'v1'
|
45
|
-
end
|
46
|
-
```
|
47
|
-
|
48
|
-
This way, you may also set additional options to fit recaptcha into your
|
49
|
-
deployment environment.
|
50
|
-
|
51
|
-
### Recaptcha.with_configuration
|
52
|
-
|
53
|
-
If you want to temporarily overwrite the configuration you set with
|
54
|
-
`Recaptcha.configure` (when testing, for example), you can use a
|
55
|
-
`Recaptcha#with_configuration` block:
|
16
|
+
[obtain a reCAPTCHA API key](https://www.google.com/recaptcha/admin).
|
56
17
|
|
57
18
|
```Ruby
|
58
|
-
|
59
|
-
# Do stuff with the overwritten public_key.
|
60
|
-
end
|
19
|
+
gem "recaptcha", require: "recaptcha/rails"
|
61
20
|
```
|
62
21
|
|
63
|
-
|
22
|
+
Keep keys out of the code base with environment variables.<br/>
|
23
|
+
Set in production and locally use [dotenv](https://github.com/bkeepers/dotenv), make sure to add it above recaptcha.
|
64
24
|
|
65
|
-
|
66
|
-
environment variables. You might do this in the .profile/rc, or equivalent for
|
67
|
-
the user running your application. This would also be the preffered method
|
68
|
-
in an Heroku deployment.
|
25
|
+
Otherwise see [Alternative API key setup](#alternative-api-key-setup).
|
69
26
|
|
70
27
|
```
|
71
28
|
export RECAPTCHA_PUBLIC_KEY = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
72
29
|
export RECAPTCHA_PRIVATE_KEY = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
73
30
|
```
|
74
31
|
|
75
|
-
|
76
|
-
|
77
|
-
You can also pass in your keys as options at runtime, for example:
|
32
|
+
Add `recaptcha_tags` to the forms you want to protect.
|
78
33
|
|
79
|
-
```
|
80
|
-
|
34
|
+
```Erb
|
35
|
+
<%= form_for @foo do |f| %>
|
36
|
+
# ... other tags
|
37
|
+
<%= recaptcha_tags %>
|
38
|
+
# ... other tags
|
39
|
+
<% end %>
|
81
40
|
```
|
82
41
|
|
83
|
-
|
42
|
+
And, add `verify_recaptcha` logic to each form action that you've protected.
|
84
43
|
|
85
44
|
```Ruby
|
86
|
-
|
45
|
+
# app/controllers/users_controller.rb
|
46
|
+
@user = User.new(params[:user].permit(:name))
|
47
|
+
if verify_recaptcha(model: @user) && @user.save
|
48
|
+
redirect_to @user
|
49
|
+
else
|
50
|
+
render 'new'
|
51
|
+
end
|
87
52
|
```
|
88
53
|
|
89
|
-
|
90
|
-
reCAPTCHA setups.
|
54
|
+
## Sinatra / Rack / Ruby installation
|
91
55
|
|
92
|
-
|
56
|
+
See [sinatra demo](/demo/sinatra) for details.
|
93
57
|
|
94
|
-
|
95
|
-
|
58
|
+
- add `gem 'recaptcha'` to `Gemfile`
|
59
|
+
- set env variables
|
60
|
+
- `include Recaptcha::ClientHelper` where you need `recaptcha_tags`
|
61
|
+
- `include Recaptcha::Verify` where you need `verify_recaptcha`
|
96
62
|
|
97
|
-
|
98
|
-
|
99
|
-
```Erb
|
100
|
-
<%= form_for @foo do |f| %>
|
101
|
-
# ... additional lines truncated for brevity ...
|
102
|
-
<%= recaptcha_tags %>
|
103
|
-
# ... additional lines truncated for brevity ...
|
104
|
-
<% end %>
|
105
|
-
```
|
106
|
-
|
107
|
-
And, add `verify_recaptcha` logic to each form action that you've protected.
|
108
|
-
|
109
|
-
### recaptcha_tags
|
63
|
+
## recaptcha_tags
|
110
64
|
|
111
65
|
Some of the options available:
|
112
66
|
|
@@ -116,16 +70,15 @@ Some of the options available:
|
|
116
70
|
| :noscript | Include <noscript> content (default `true`)|
|
117
71
|
| :display | Takes a hash containing the `theme` and `tabindex` options per the API. (default `nil`), options: 'red', 'white', 'blackglass', 'clean', 'custom'|
|
118
72
|
| :ajax | Render the dynamic AJAX captcha per the API. (default `false`)|
|
119
|
-
| :public_key |
|
73
|
+
| :public_key | Override public API key |
|
120
74
|
| :error | Override the error code returned from the reCAPTCHA API (default `nil`)|
|
121
75
|
| :stoken | Include in reCAPTCHA API v2 the security token (default `true`)|
|
122
76
|
| :size | Specify a size (default `nil`)|
|
123
77
|
|
124
|
-
|
125
78
|
You can also override the html attributes for the sizes of the generated `textarea` and `iframe`
|
126
79
|
elements, if CSS isn't your thing. Inspect the source of `recaptcha_tags` to see these options.
|
127
80
|
|
128
|
-
|
81
|
+
## verify_recaptcha
|
129
82
|
|
130
83
|
This method returns `true` or `false` after processing the parameters from the reCAPTCHA widget. Why
|
131
84
|
isn't this a model validation? Because that violates MVC. You can use it like this, or how ever you
|
@@ -139,135 +92,10 @@ Some of the options available:
|
|
139
92
|
| :model | Model to set errors
|
140
93
|
| :attribute | Model attribute to receive errors (default :base)
|
141
94
|
| :message | Custom error message
|
142
|
-
| :private_key |
|
95
|
+
| :private_key | Override private API key.
|
143
96
|
| :timeout | The number of seconds to wait for reCAPTCHA servers before give up. (default `3`)
|
97
|
+
| :response | Custom response parameter (default: params['g-recaptcha-response'])
|
144
98
|
|
145
|
-
```Ruby
|
146
|
-
respond_to do |format|
|
147
|
-
if verify_recaptcha(model @post, message: "Oh! It's error with reCAPTCHA!") && @post.save
|
148
|
-
# ...
|
149
|
-
else
|
150
|
-
# ...
|
151
|
-
end
|
152
|
-
end
|
153
|
-
```
|
154
|
-
|
155
|
-
### Add multiple widgets to the same page
|
156
|
-
|
157
|
-
This is an example taken from the [official google documentation](https://developers.google.com/recaptcha/docs/display).
|
158
|
-
|
159
|
-
Add a script tag for a callback
|
160
|
-
|
161
|
-
```Html
|
162
|
-
<script type="text/javascript">
|
163
|
-
var verifyCallback = function(response) {
|
164
|
-
alert(response);
|
165
|
-
};
|
166
|
-
var widgetId1;
|
167
|
-
var widgetId2;
|
168
|
-
var onloadCallback = function() {
|
169
|
-
// Renders the HTML element with id 'example1' as a reCAPTCHA widget.
|
170
|
-
// The id of the reCAPTCHA widget is assigned to 'widgetId1'.
|
171
|
-
widgetId1 = grecaptcha.render('example1', {
|
172
|
-
'sitekey' : "<%= Recaptcha.configuration.public_key %>",
|
173
|
-
'theme' : 'light'
|
174
|
-
});
|
175
|
-
widgetId2 = grecaptcha.render(document.getElementById('example2'), {
|
176
|
-
'sitekey' : "<%= Recaptcha.configuration.public_key %>"
|
177
|
-
});
|
178
|
-
grecaptcha.render('example3', {
|
179
|
-
'sitekey' : "<%= Recaptcha.configuration.public_key %>",
|
180
|
-
'callback' : verifyCallback,
|
181
|
-
'theme' : 'dark'
|
182
|
-
});
|
183
|
-
};
|
184
|
-
</script>
|
185
|
-
```
|
186
|
-
|
187
|
-
In the callback you will have the `sitekey` generated by this gem with `<%= Recaptcha.configuration.public_key %>`
|
188
|
-
|
189
|
-
Next you need to have some elements with an id matching those specified in the callback
|
190
|
-
|
191
|
-
```Erb
|
192
|
-
<%= form_for @foo do |f| %>
|
193
|
-
# ... additional lines truncated for brevity ...
|
194
|
-
<div id="example1"></div>
|
195
|
-
# ... additional lines truncated for brevity ...
|
196
|
-
<% end %>
|
197
|
-
<%= form_for @foo2 do |f| %>
|
198
|
-
# ... additional lines truncated for brevity ...
|
199
|
-
<div id="example2"></div>
|
200
|
-
# ... additional lines truncated for brevity ...
|
201
|
-
<% end %>
|
202
|
-
<%= form_for @foo3 do |f| %>
|
203
|
-
# ... additional lines truncated for brevity ...
|
204
|
-
<div id="example3"></div>
|
205
|
-
# ... additional lines truncated for brevity ...
|
206
|
-
<% end %>
|
207
|
-
```
|
208
|
-
|
209
|
-
And finally you need a script tag that gets the reCAPTCHA code from google and tells it that your code is explicit and gives it the callback.
|
210
|
-
|
211
|
-
```Html
|
212
|
-
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
|
213
|
-
```
|
214
|
-
|
215
|
-
Now all together:
|
216
|
-
|
217
|
-
```Html
|
218
|
-
<html>
|
219
|
-
<head>
|
220
|
-
<title>reCAPTCHA demo: Explicit render for multiple widgets</title>
|
221
|
-
<script type="text/javascript">
|
222
|
-
var verifyCallback = function(response) {
|
223
|
-
alert(response);
|
224
|
-
};
|
225
|
-
var widgetId1;
|
226
|
-
var widgetId2;
|
227
|
-
var onloadCallback = function() {
|
228
|
-
// Renders the HTML element with id 'example1' as a reCAPTCHA widget.
|
229
|
-
// The id of the reCAPTCHA widget is assigned to 'widgetId1'.
|
230
|
-
widgetId1 = grecaptcha.render('example1', {
|
231
|
-
'sitekey' : "<%= Recaptcha.configuration.public_key %>",
|
232
|
-
'theme' : 'light'
|
233
|
-
});
|
234
|
-
widgetId2 = grecaptcha.render(document.getElementById('example2'), {
|
235
|
-
'sitekey' : "<%= Recaptcha.configuration.public_key %>"
|
236
|
-
});
|
237
|
-
grecaptcha.render('example3', {
|
238
|
-
'sitekey' : "<%= Recaptcha.configuration.public_key %>",
|
239
|
-
'callback' : verifyCallback,
|
240
|
-
'theme' : 'dark'
|
241
|
-
});
|
242
|
-
};
|
243
|
-
</script>
|
244
|
-
</head>
|
245
|
-
<body>
|
246
|
-
<%= form_for @foo do |f| %>
|
247
|
-
# ... additional lines truncated for brevity ...
|
248
|
-
<div id="example1"></div>
|
249
|
-
# ... additional lines truncated for brevity ...
|
250
|
-
<% end %>
|
251
|
-
<%= form_for @foo2 do |f| %>
|
252
|
-
# ... additional lines truncated for brevity ...
|
253
|
-
<div id="example2"></div>
|
254
|
-
# ... additional lines truncated for brevity ...
|
255
|
-
<% end %>
|
256
|
-
<%= form_for @foo3 do |f| %>
|
257
|
-
# ... additional lines truncated for brevity ...
|
258
|
-
<div id="example3"></div>
|
259
|
-
# ... additional lines truncated for brevity ...
|
260
|
-
<% end %>
|
261
|
-
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
|
262
|
-
</body>
|
263
|
-
</html>
|
264
|
-
```
|
265
|
-
|
266
|
-
The only real difference between this example and the google example is you will use the `<%= Recaptcha.configuration.public_key %>` for the `sitekey`
|
267
|
-
|
268
|
-
If your callback has to live on another file (maybe a layout), then you would set the callback on window `window.onloadCallback = function() {...}`
|
269
|
-
|
270
|
-
Then on the backend, you will still use the `verify_recaptcha` as explained in this readme.
|
271
99
|
|
272
100
|
## I18n support
|
273
101
|
reCAPTCHA passes two types of error explanation to a linked model. It will use the I18n gem
|
@@ -287,9 +115,49 @@ en:
|
|
287
115
|
```
|
288
116
|
|
289
117
|
## Testing
|
290
|
-
By default, reCAPTCHA is skipped on "test" env, so if you need to make sure it's working properly, just remove the "test" entry of the skip_environment inside Recapcha class, look:
|
291
118
|
|
119
|
+
By default, reCAPTCHA is skipped in "test" and "cucumber" env. To enable it during test:
|
292
120
|
|
293
121
|
```Ruby
|
294
122
|
Recaptcha.configuration.skip_verify_env.delete("test")
|
295
123
|
```
|
124
|
+
|
125
|
+
## Alternative API key setup
|
126
|
+
|
127
|
+
### Recaptcha.configure
|
128
|
+
|
129
|
+
```Ruby
|
130
|
+
# config/initializers/recaptcha.rb
|
131
|
+
Recaptcha.configure do |config|
|
132
|
+
config.public_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
133
|
+
config.private_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
134
|
+
# Uncomment the following line if you are using a proxy server:
|
135
|
+
# config.proxy = 'http://myproxy.com.au:8080'
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
### Recaptcha.with_configuration
|
140
|
+
|
141
|
+
For temporary overwrites (not thread safe).
|
142
|
+
|
143
|
+
```Ruby
|
144
|
+
Recaptcha.with_configuration(public_key: '12345') do
|
145
|
+
# Do stuff with the overwritten public_key.
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
### Per call
|
150
|
+
|
151
|
+
Pass in keys as options at runtime, for code base with multiple reCAPTCHA setups:
|
152
|
+
|
153
|
+
```Ruby
|
154
|
+
recaptcha_tags public_key: '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
|
155
|
+
|
156
|
+
and
|
157
|
+
|
158
|
+
verify_recaptcha private_key: '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
|
159
|
+
```
|
160
|
+
|
161
|
+
## Misc
|
162
|
+
- Check out the [wiki](https://github.com/ambethia/recaptcha/wiki) and leave whatever you found valuable there.
|
163
|
+
- [Add multiple widgets to the same page](https://github.com/ambethia/recaptcha/wiki/Add-multiple-widgets-to-the-same-page)
|
data/lib/recaptcha.rb
CHANGED
@@ -2,27 +2,19 @@ require 'recaptcha/configuration'
|
|
2
2
|
require 'recaptcha/client_helper'
|
3
3
|
require 'recaptcha/verify'
|
4
4
|
require 'recaptcha/token'
|
5
|
+
require 'uri'
|
5
6
|
|
6
7
|
module Recaptcha
|
7
|
-
CONFIG =
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
},
|
14
|
-
|
15
|
-
'v2' => {
|
16
|
-
'server_url' => '//www.google.com/recaptcha/api.js',
|
17
|
-
'secure_server_url' => 'https://www.google.com/recaptcha/api.js',
|
18
|
-
'verify_url' => 'https://www.google.com/recaptcha/api/siteverify'
|
19
|
-
}
|
20
|
-
}
|
21
|
-
|
22
|
-
RECAPTCHA_API_VERSION = 'v2'
|
8
|
+
CONFIG = {
|
9
|
+
'server_url' => '//www.google.com/recaptcha/api.js',
|
10
|
+
'secure_server_url' => 'https://www.google.com/recaptcha/api.js',
|
11
|
+
'verify_url' => 'https://www.google.com/recaptcha/api/siteverify'
|
12
|
+
}
|
13
|
+
|
23
14
|
USE_SSL_BY_DEFAULT = false
|
24
15
|
HANDLE_TIMEOUTS_GRACEFULLY = true
|
25
16
|
SKIP_VERIFY_ENV = ['test', 'cucumber']
|
17
|
+
DEFAULT_TIMEOUT = 3
|
26
18
|
|
27
19
|
# Gives access to the current Configuration.
|
28
20
|
def self.configuration
|
@@ -54,6 +46,34 @@ module Recaptcha
|
|
54
46
|
result
|
55
47
|
end
|
56
48
|
|
49
|
+
def self.get(verify_hash, options)
|
50
|
+
http = if Recaptcha.configuration.proxy
|
51
|
+
proxy_server = URI.parse(Recaptcha.configuration.proxy)
|
52
|
+
Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password)
|
53
|
+
else
|
54
|
+
Net::HTTP
|
55
|
+
end
|
56
|
+
query = URI.encode_www_form(verify_hash)
|
57
|
+
uri = URI.parse(Recaptcha.configuration.verify_url + '?' + query)
|
58
|
+
http_instance = http.new(uri.host, uri.port)
|
59
|
+
http_instance.read_timeout = http_instance.open_timeout = options[:timeout] || DEFAULT_TIMEOUT
|
60
|
+
if uri.port == 443
|
61
|
+
http_instance.use_ssl = true
|
62
|
+
http_instance.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
63
|
+
end
|
64
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
65
|
+
http_instance.request(request).body
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.i18n(key, default)
|
69
|
+
if defined?(I18n)
|
70
|
+
I18n.translate(key, default: default)
|
71
|
+
else
|
72
|
+
default
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
57
77
|
class RecaptchaError < StandardError
|
58
78
|
end
|
59
79
|
|
@@ -3,129 +3,50 @@ module Recaptcha
|
|
3
3
|
# Your public API can be specified in the +options+ hash or preferably
|
4
4
|
# using the Configuration.
|
5
5
|
def recaptcha_tags(options = {})
|
6
|
-
|
7
|
-
return v2_tags(options) if Recaptcha.configuration.v2?
|
8
|
-
end # recaptcha_tags
|
6
|
+
public_key = options[:public_key] || Recaptcha.configuration.public_key!
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
raise RecaptchaError, "No public key specified." unless key
|
14
|
-
error = options[:error] ||= ((defined? flash) ? flash[:recaptcha_error] : "")
|
15
|
-
uri = Recaptcha.configuration.api_server_url(options[:ssl])
|
16
|
-
lang = options[:display] && options[:display][:lang] ? options[:display][:lang].to_sym : ""
|
17
|
-
html = ""
|
18
|
-
if options[:display]
|
19
|
-
html << %{<script type="text/javascript">\n}
|
20
|
-
html << %{ var RecaptchaOptions = #{hash_to_json(options[:display])};\n}
|
21
|
-
html << %{</script>\n}
|
22
|
-
end
|
23
|
-
if options[:ajax]
|
24
|
-
if options[:display] && options[:display][:custom_theme_widget]
|
25
|
-
widget = options[:display][:custom_theme_widget]
|
26
|
-
else
|
27
|
-
widget = "dynamic_recaptcha"
|
28
|
-
html << <<-EOS
|
29
|
-
<div id="#{widget}"></div>
|
30
|
-
EOS
|
31
|
-
end
|
32
|
-
html << <<-EOS
|
33
|
-
<script type="text/javascript">
|
34
|
-
var rc_script_tag = document.createElement('script'),
|
35
|
-
rc_init_func = function(){Recaptcha.create("#{key}", document.getElementById("#{widget}")#{',RecaptchaOptions' if options[:display]});}
|
36
|
-
rc_script_tag.src = "#{uri}/js/recaptcha_ajax.js";
|
37
|
-
rc_script_tag.type = 'text/javascript';
|
38
|
-
rc_script_tag.onload = function(){rc_init_func.call();};
|
39
|
-
rc_script_tag.onreadystatechange = function(){
|
40
|
-
if (rc_script_tag.readyState == 'loaded' || rc_script_tag.readyState == 'complete') {rc_init_func.call();}
|
41
|
-
};
|
42
|
-
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(rc_script_tag);
|
43
|
-
</script>
|
44
|
-
EOS
|
45
|
-
else
|
46
|
-
html << %{<script type="text/javascript" src="#{uri}/challenge?k=#{key}}
|
47
|
-
html << %{#{error ? "&error=#{CGI::escape(error)}" : ""}}
|
48
|
-
html << %{#{lang ? "&lang=#{lang}" : ""}"></script>\n}
|
49
|
-
unless options[:noscript] == false
|
50
|
-
html << %{<noscript>\n }
|
51
|
-
html << %{<iframe src="#{uri}/noscript?k=#{key}" }
|
52
|
-
html << %{height="#{options[:iframe_height] ||= 300}" }
|
53
|
-
html << %{width="#{options[:iframe_width] ||= 500}" }
|
54
|
-
html << %{style="border:none;"></iframe><br/>\n }
|
55
|
-
html << %{<textarea name="recaptcha_challenge_field" }
|
56
|
-
html << %{rows="#{options[:textarea_rows] ||= 3}" }
|
57
|
-
html << %{cols="#{options[:textarea_cols] ||= 40}"></textarea>\n }
|
58
|
-
html << %{<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>}
|
59
|
-
html << %{</noscript>\n}
|
60
|
-
end
|
61
|
-
end
|
62
|
-
return (html.respond_to?(:html_safe) && html.html_safe) || html
|
63
|
-
end
|
64
|
-
|
65
|
-
def v2_tags(options)
|
66
|
-
public_key = options[:public_key] ||= Recaptcha.configuration.public_key
|
67
|
-
raise RecaptchaError, "No public key specified." unless public_key
|
68
|
-
private_key = options[:private_key] ||= Recaptcha.configuration.private_key
|
69
|
-
raise RecaptchaError, "No private key specified." unless private_key
|
70
|
-
error = options[:error] ||= ((defined? flash) ? flash[:recaptcha_error] : "") # TODO not being used !?
|
71
|
-
uri = Recaptcha.configuration.api_server_url(options[:ssl])
|
72
|
-
uri += "?hl=#{options[:hl]}" unless options[:hl].blank?
|
73
|
-
|
74
|
-
html = ""
|
75
|
-
html << %{<script src="#{uri}" async defer></script>\n}
|
76
|
-
|
77
|
-
data_attributes = options.slice(:theme, :type, :callback, :expired_callback, :size)
|
78
|
-
data_attributes[:sitekey] = public_key
|
8
|
+
script_url = Recaptcha.configuration.api_server_url(ssl: options[:ssl])
|
9
|
+
script_url += "?hl=#{options[:hl]}" unless options[:hl].to_s == ""
|
10
|
+
fallback_uri = "#{script_url.chomp('.js')}/fallback?k=#{public_key}"
|
79
11
|
|
80
|
-
|
81
|
-
|
12
|
+
data_attributes = [:theme, :type, :callback, :expired_callback, :size]
|
13
|
+
data_attributes = options.each_with_object({}) do |(k, v), a|
|
14
|
+
a[k] = v if data_attributes.include?(k)
|
82
15
|
end
|
16
|
+
data_attributes[:sitekey] = public_key
|
17
|
+
data_attributes[:stoken] = Recaptcha::Token.secure_token if options[:stoken] != false
|
18
|
+
data_attributes = data_attributes.map { |k,v| %{data-#{k.to_s.tr('_','-')}="#{v}"} }.join(" ")
|
83
19
|
|
84
|
-
|
85
|
-
|
20
|
+
html = %{<script src="#{script_url}" async defer></script>\n}
|
86
21
|
html << %{<div class="g-recaptcha" #{data_attributes}></div>\n}
|
87
22
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
23
|
+
if options[:noscript] != false
|
24
|
+
html << <<-HTML
|
25
|
+
<noscript>
|
26
|
+
<div style="width: 302px; height: 352px;">
|
27
|
+
<div style="width: 302px; height: 352px; position: relative;">
|
28
|
+
<div style="width: 302px; height: 352px; position: absolute;">
|
29
|
+
<iframe
|
30
|
+
src="#{fallback_uri}"
|
31
|
+
frameborder="0" scrolling="no"
|
32
|
+
style="width: 302px; height:352px; border-style: none;">
|
33
|
+
</iframe>
|
34
|
+
</div>
|
35
|
+
<div style="width: 250px; height: 80px; position: absolute; border-style: none;
|
36
|
+
bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
|
37
|
+
<textarea id="g-recaptcha-response" name="g-recaptcha-response"
|
38
|
+
class="g-recaptcha-response"
|
39
|
+
style="width: 250px; height: 80px; border: 1px solid #c1c1c1;
|
40
|
+
margin: 0px; padding: 0px; resize: none;" value="">
|
41
|
+
</textarea>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
</noscript>
|
46
|
+
HTML
|
110
47
|
end
|
111
48
|
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
private
|
116
|
-
|
117
|
-
def hash_to_json(hash)
|
118
|
-
result = "{"
|
119
|
-
result << hash.map do |k, v|
|
120
|
-
if v.is_a?(Hash)
|
121
|
-
"\"#{k}\": #{hash_to_json(v)}"
|
122
|
-
elsif ! v.is_a?(String) || k.to_s =~ %r{(callback|expired-callback)}
|
123
|
-
"\"#{k}\": #{v}"
|
124
|
-
else
|
125
|
-
"\"#{k}\": \"#{v}\""
|
126
|
-
end
|
127
|
-
end.join(", ")
|
128
|
-
result << "}"
|
49
|
+
html.respond_to?(:html_safe) ? html.html_safe : html
|
129
50
|
end
|
130
51
|
end
|
131
52
|
end
|
@@ -28,47 +28,33 @@ module Recaptcha
|
|
28
28
|
# end
|
29
29
|
#
|
30
30
|
class Configuration
|
31
|
-
attr_accessor :
|
32
|
-
:skip_verify_env,
|
33
|
-
:private_key,
|
34
|
-
:public_key,
|
35
|
-
:proxy,
|
36
|
-
:handle_timeouts_gracefully,
|
37
|
-
:use_ssl_by_default
|
31
|
+
attr_accessor :skip_verify_env, :private_key, :public_key, :proxy, :handle_timeouts_gracefully, :use_ssl_by_default
|
38
32
|
|
39
33
|
def initialize #:nodoc:
|
40
|
-
@api_version = RECAPTCHA_API_VERSION
|
41
34
|
@skip_verify_env = SKIP_VERIFY_ENV
|
42
35
|
@handle_timeouts_gracefully = HANDLE_TIMEOUTS_GRACEFULLY
|
43
36
|
@use_ssl_by_default = USE_SSL_BY_DEFAULT
|
44
37
|
|
45
38
|
@private_key = ENV['RECAPTCHA_PRIVATE_KEY']
|
46
|
-
@public_key = ENV['RECAPTCHA_PUBLIC_KEY']
|
39
|
+
@public_key = ENV['RECAPTCHA_PUBLIC_KEY']
|
47
40
|
end
|
48
41
|
|
49
|
-
def
|
50
|
-
|
51
|
-
ssl ? ssl_api_server_url : nonssl_api_server_url
|
42
|
+
def private_key!
|
43
|
+
private_key || raise(RecaptchaError, "No private key specified.")
|
52
44
|
end
|
53
45
|
|
54
|
-
def
|
55
|
-
|
46
|
+
def public_key!
|
47
|
+
public_key || raise(RecaptchaError, "No public key specified.")
|
56
48
|
end
|
57
49
|
|
58
|
-
def
|
59
|
-
|
50
|
+
def api_server_url(ssl: nil)
|
51
|
+
ssl = use_ssl_by_default if ssl.nil?
|
52
|
+
key = (ssl ? 'secure_server_url' : 'server_url')
|
53
|
+
CONFIG.fetch(key)
|
60
54
|
end
|
61
55
|
|
62
56
|
def verify_url
|
63
|
-
CONFIG
|
64
|
-
end
|
65
|
-
|
66
|
-
def v1?
|
67
|
-
@api_version == 'v1'
|
68
|
-
end
|
69
|
-
|
70
|
-
def v2?
|
71
|
-
@api_version == 'v2'
|
57
|
+
CONFIG.fetch('verify_url')
|
72
58
|
end
|
73
59
|
end
|
74
60
|
end
|
data/lib/recaptcha/verify.rb
CHANGED
@@ -1,101 +1,53 @@
|
|
1
|
-
require
|
2
|
-
require "json"
|
1
|
+
require 'json'
|
3
2
|
|
4
3
|
module Recaptcha
|
5
4
|
module Verify
|
6
|
-
DEFAULT_TIMEOUT = 3
|
7
|
-
|
8
5
|
# Your private API can be specified in the +options+ hash or preferably
|
9
6
|
# using the Configuration.
|
10
7
|
def verify_recaptcha(options = {})
|
11
8
|
options = {:model => options} unless options.is_a? Hash
|
12
|
-
|
13
|
-
env_options = options[:env] || ENV['RAILS_ENV'] || (Rails.env if defined? Rails.env)
|
14
|
-
return true if Recaptcha.configuration.skip_verify_env.include? env_options
|
15
9
|
model = options[:model]
|
16
10
|
attribute = options[:attribute] || :base
|
17
|
-
private_key = options[:private_key] || Recaptcha.configuration.private_key
|
18
|
-
raise RecaptchaError, "No private key specified." unless private_key
|
19
11
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
http = Net::HTTP::Proxy(proxy_server.host, proxy_server.port, proxy_server.user, proxy_server.password)
|
25
|
-
else
|
26
|
-
http = Net::HTTP
|
27
|
-
end
|
12
|
+
return true if Recaptcha::Verify.skip?(options[:env])
|
13
|
+
|
14
|
+
private_key = options[:private_key] || Recaptcha.configuration.private_key!
|
15
|
+
recaptcha_response = options[:response] || params['g-recaptcha-response'].to_s
|
28
16
|
|
17
|
+
begin
|
29
18
|
# env['REMOTE_ADDR'] to retrieve IP for Grape API
|
30
19
|
remote_ip = (request.respond_to?(:remote_ip) && request.remote_ip) || (env && env['REMOTE_ADDR'])
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
"response" => params[:recaptcha_response_field]
|
37
|
-
}
|
38
|
-
Timeout::timeout(options[:timeout] || DEFAULT_TIMEOUT) do
|
39
|
-
recaptcha = http.post_form(URI.parse(Recaptcha.configuration.verify_url), verify_hash)
|
40
|
-
end
|
41
|
-
answer, error = recaptcha.body.split.map { |s| s.chomp }
|
42
|
-
end
|
20
|
+
verify_hash = {
|
21
|
+
"secret" => private_key,
|
22
|
+
"remoteip" => remote_ip.to_s,
|
23
|
+
"response" => recaptcha_response
|
24
|
+
}
|
43
25
|
|
44
|
-
|
45
|
-
|
46
|
-
"secret" => private_key,
|
47
|
-
"remoteip" => remote_ip,
|
48
|
-
"response" => params['g-recaptcha-response']
|
49
|
-
}
|
50
|
-
|
51
|
-
Timeout::timeout(options[:timeout] || DEFAULT_TIMEOUT) do
|
52
|
-
uri = URI.parse(Recaptcha.configuration.verify_url + '?' + verify_hash.to_query)
|
53
|
-
http_instance = http.new(uri.host, uri.port)
|
54
|
-
if uri.port == 443
|
55
|
-
http_instance.use_ssl =
|
56
|
-
http_instance.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
57
|
-
end
|
58
|
-
request = Net::HTTP::Get.new(uri.request_uri)
|
59
|
-
recaptcha = http_instance.request(request)
|
60
|
-
end
|
61
|
-
answer, error = JSON.parse(recaptcha.body).values
|
62
|
-
end
|
26
|
+
reply = Recaptcha.get(verify_hash, options)
|
27
|
+
answer = JSON.parse(reply)['success']
|
63
28
|
|
64
29
|
if answer.to_s == 'true'
|
65
|
-
flash.delete(:recaptcha_error) if
|
30
|
+
flash.delete(:recaptcha_error) if recaptcha_flash_supported?
|
66
31
|
true
|
67
32
|
else
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
if model
|
78
|
-
message = "Word verification response is incorrect, please try again."
|
79
|
-
message = I18n.translate('recaptcha.errors.verification_failed', default: message) if defined?(I18n)
|
80
|
-
model.errors.add attribute, options[:message] || message
|
81
|
-
end
|
33
|
+
recaptcha_error(
|
34
|
+
model,
|
35
|
+
attribute,
|
36
|
+
options[:message],
|
37
|
+
"recaptcha.errors.verification_failed",
|
38
|
+
"Word verification response is incorrect, please try again."
|
39
|
+
)
|
82
40
|
false
|
83
41
|
end
|
84
42
|
rescue Timeout::Error
|
85
43
|
if Recaptcha.configuration.handle_timeouts_gracefully
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
if model
|
95
|
-
message = "Oops, we failed to validate your word verification response. Please try again."
|
96
|
-
message = I18n.translate('recaptcha.errors.recaptcha_unreachable', default: message) if defined?(I18n)
|
97
|
-
model.errors.add attribute, options[:message] || message
|
98
|
-
end
|
44
|
+
recaptcha_error(
|
45
|
+
model,
|
46
|
+
attribute,
|
47
|
+
options[:message],
|
48
|
+
"recaptcha.errors.recaptcha_unreachable",
|
49
|
+
"Oops, we failed to validate your word verification response. Please try again."
|
50
|
+
)
|
99
51
|
false
|
100
52
|
else
|
101
53
|
raise RecaptchaError, "Recaptcha unreachable."
|
@@ -105,12 +57,25 @@ module Recaptcha
|
|
105
57
|
end
|
106
58
|
end
|
107
59
|
|
108
|
-
def
|
60
|
+
def verify_recaptcha!(options = {})
|
61
|
+
verify_recaptcha(options) or raise VerifyError
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def recaptcha_error(model, attribute, message, key, default)
|
67
|
+
message = message || Recaptcha.i18n(key, default)
|
68
|
+
flash[:recaptcha_error] = message if recaptcha_flash_supported?
|
69
|
+
model.errors.add attribute, message if model
|
70
|
+
end
|
71
|
+
|
72
|
+
def recaptcha_flash_supported?
|
109
73
|
request.respond_to?(:format) && request.format == :html && respond_to?(:flash)
|
110
74
|
end
|
111
75
|
|
112
|
-
def
|
113
|
-
|
76
|
+
def self.skip?(env)
|
77
|
+
env ||= ENV['RAILS_ENV'] || (Rails.env if defined? Rails.env)
|
78
|
+
Recaptcha.configuration.skip_verify_env.include? env
|
114
79
|
end
|
115
80
|
end
|
116
81
|
end
|
data/lib/recaptcha/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: recaptcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason L Perry
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: webmock
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
description: Helpers for the reCAPTCHA API
|
126
140
|
email:
|
127
141
|
- jasper@ambethia.com
|
@@ -129,7 +143,7 @@ executables: []
|
|
129
143
|
extensions: []
|
130
144
|
extra_rdoc_files: []
|
131
145
|
files:
|
132
|
-
- CHANGELOG
|
146
|
+
- CHANGELOG.md
|
133
147
|
- LICENSE
|
134
148
|
- README.md
|
135
149
|
- lib/recaptcha.rb
|