devise_duo_sec 0.0.7
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/devise_duo_security/Duo-Web-v2.js +366 -0
- data/app/assets/stylesheets/devise_duo_security/Duo-Frame.css +10 -0
- data/app/controllers/devise/duo_security_controller.rb +39 -0
- data/app/views/devise/duo_security/_test_iframe_response.html.erb +144 -0
- data/app/views/devise/duo_security/show.html.erb +15 -0
- data/lib/devise/duo_security/controllers/helpers.rb +41 -0
- data/lib/devise/duo_security/engine.rb +14 -0
- data/lib/devise/duo_security/version.rb +5 -0
- data/lib/devise_duo_sec.rb +43 -0
- data/lib/duo_web.rb +107 -0
- data/lib/tasks/devise_duo_security_tasks.rake +4 -0
- data/test/devise_duo_security_test.rb +16 -0
- data/test/dummy/Gemfile +10 -0
- data/test/dummy/Gemfile.lock +138 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/home_controller.rb +13 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/user.rb +6 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +42 -0
- data/test/dummy/config/environments/production.rb +78 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/devise.rb +259 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/devise.en.yml +60 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +7 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/db/migrate/20150320103707_devise_create_users.rb +42 -0
- data/test/dummy/db/schema.rb +34 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/test/fixtures/users.yml +11 -0
- data/test/dummy/test/models/user_test.rb +7 -0
- data/test/integration/navigation_test.rb +25 -0
- data/test/support/helpers.rb +40 -0
- data/test/test_helper.rb +46 -0
- metadata +337 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b03257e334c11b9b162063479a449311b33ec6ae
|
4
|
+
data.tar.gz: 468bb188c5f389516d1d5e7d2de8c1384482df9a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a2805b48ae2b8ab0acc6cb98a547edffb0ac28b38dfb446a0e6e325ca894c44d59e03e8d2d774ba4767f69e6d56c44a34d83a28a6e5622755eae969c25de3e80
|
7
|
+
data.tar.gz: 2fd5895337e55b082907e8fa3c86dd1c022ede1aa83c917518c0c6948cd8620fda504ffd51595eb34f843f28ed475baecf7901e5e2545b023d701a1fa5aa0516
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 Greg Molnar
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Devise::DuoSecurity'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'lib'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task default: :test
|
@@ -0,0 +1,366 @@
|
|
1
|
+
/**
|
2
|
+
* Duo Web SDK v2
|
3
|
+
* Copyright 2015, Duo Security
|
4
|
+
*/
|
5
|
+
window.Duo = (function(document, window) {
|
6
|
+
var DUO_MESSAGE_FORMAT = /^(?:AUTH|ENROLL)+\|[A-Za-z0-9\+\/=]+\|[A-Za-z0-9\+\/=]+$/;
|
7
|
+
var DUO_ERROR_FORMAT = /^ERR\|[\w\s\.\(\)]+$/;
|
8
|
+
|
9
|
+
var iframeId = 'duo_iframe',
|
10
|
+
postAction = '',
|
11
|
+
postArgument = 'sig_response',
|
12
|
+
host,
|
13
|
+
sigRequest,
|
14
|
+
duoSig,
|
15
|
+
appSig,
|
16
|
+
iframe,
|
17
|
+
submitCallback;
|
18
|
+
|
19
|
+
function throwError(message, url) {
|
20
|
+
throw new Error(
|
21
|
+
'Duo Web SDK error: ' + message +
|
22
|
+
(url ? ('\n' + 'See ' + url + ' for more information') : '')
|
23
|
+
);
|
24
|
+
}
|
25
|
+
|
26
|
+
function hyphenize(str) {
|
27
|
+
return str.replace(/([a-z])([A-Z])/, '$1-$2').toLowerCase();
|
28
|
+
}
|
29
|
+
|
30
|
+
// cross-browser data attributes
|
31
|
+
function getDataAttribute(element, name) {
|
32
|
+
if ('dataset' in element) {
|
33
|
+
return element.dataset[name];
|
34
|
+
} else {
|
35
|
+
return element.getAttribute('data-' + hyphenize(name));
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
// cross-browser event binding/unbinding
|
40
|
+
function on(context, event, fallbackEvent, callback) {
|
41
|
+
if ('addEventListener' in window) {
|
42
|
+
context.addEventListener(event, callback, false);
|
43
|
+
} else {
|
44
|
+
context.attachEvent(fallbackEvent, callback);
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
function off(context, event, fallbackEvent, callback) {
|
49
|
+
if ('removeEventListener' in window) {
|
50
|
+
context.removeEventListener(event, callback, false);
|
51
|
+
} else {
|
52
|
+
context.detachEvent(fallbackEvent, callback);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
function onReady(callback) {
|
57
|
+
on(document, 'DOMContentLoaded', 'onreadystatechange', callback);
|
58
|
+
}
|
59
|
+
|
60
|
+
function offReady(callback) {
|
61
|
+
off(document, 'DOMContentLoaded', 'onreadystatechange', callback);
|
62
|
+
}
|
63
|
+
|
64
|
+
function onMessage(callback) {
|
65
|
+
on(window, 'message', 'onmessage', callback);
|
66
|
+
}
|
67
|
+
|
68
|
+
function offMessage(callback) {
|
69
|
+
off(window, 'message', 'onmessage', callback);
|
70
|
+
}
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Parse the sig_request parameter, throwing errors if the token contains
|
74
|
+
* a server error or if the token is invalid.
|
75
|
+
*
|
76
|
+
* @param {String} sig Request token
|
77
|
+
*/
|
78
|
+
function parseSigRequest(sig) {
|
79
|
+
if (!sig) {
|
80
|
+
// nothing to do
|
81
|
+
return;
|
82
|
+
}
|
83
|
+
|
84
|
+
// see if the token contains an error, throwing it if it does
|
85
|
+
if (sig.indexOf('ERR|') === 0) {
|
86
|
+
throwError(sig.split('|')[1]);
|
87
|
+
}
|
88
|
+
|
89
|
+
// validate the token
|
90
|
+
if (sig.indexOf(':') === -1 || sig.split(':').length !== 2) {
|
91
|
+
throwError(
|
92
|
+
'Duo was given a bad token. This might indicate a configuration ' +
|
93
|
+
'problem with one of Duo\'s client libraries.',
|
94
|
+
'https://www.duosecurity.com/docs/duoweb#first-steps'
|
95
|
+
);
|
96
|
+
}
|
97
|
+
|
98
|
+
var sigParts = sig.split(':');
|
99
|
+
|
100
|
+
// hang on to the token, and the parsed duo and app sigs
|
101
|
+
sigRequest = sig;
|
102
|
+
duoSig = sigParts[0];
|
103
|
+
appSig = sigParts[1];
|
104
|
+
|
105
|
+
return {
|
106
|
+
sigRequest: sig,
|
107
|
+
duoSig: sigParts[0],
|
108
|
+
appSig: sigParts[1]
|
109
|
+
};
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* This function is set up to run when the DOM is ready, if the iframe was
|
114
|
+
* not available during `init`.
|
115
|
+
*/
|
116
|
+
function onDOMReady() {
|
117
|
+
iframe = document.getElementById(iframeId);
|
118
|
+
|
119
|
+
if (!iframe) {
|
120
|
+
throw new Error(
|
121
|
+
'This page does not contain an iframe for Duo to use.' +
|
122
|
+
'Add an element like <iframe id="duo_iframe"></iframe> ' +
|
123
|
+
'to this page. ' +
|
124
|
+
'See https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe ' +
|
125
|
+
'for more information.'
|
126
|
+
);
|
127
|
+
}
|
128
|
+
|
129
|
+
// we've got an iframe, away we go!
|
130
|
+
ready();
|
131
|
+
|
132
|
+
// always clean up after yourself
|
133
|
+
offReady(onDOMReady);
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Validate that a MessageEvent came from the Duo service, and that it
|
138
|
+
* is a properly formatted payload.
|
139
|
+
*
|
140
|
+
* The Google Chrome sign-in page injects some JS into pages that also
|
141
|
+
* make use of postMessage, so we need to do additional validation above
|
142
|
+
* and beyond the origin.
|
143
|
+
*
|
144
|
+
* @param {MessageEvent} event Message received via postMessage
|
145
|
+
*/
|
146
|
+
function isDuoMessage(event) {
|
147
|
+
return Boolean(
|
148
|
+
event.origin === ('https://' + host) &&
|
149
|
+
typeof event.data === 'string' &&
|
150
|
+
(
|
151
|
+
event.data.match(DUO_MESSAGE_FORMAT) ||
|
152
|
+
event.data.match(DUO_ERROR_FORMAT)
|
153
|
+
)
|
154
|
+
);
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Validate the request token and prepare for the iframe to become ready.
|
159
|
+
*
|
160
|
+
* All options below can be passed into an options hash to `Duo.init`, or
|
161
|
+
* specified on the iframe using `data-` attributes.
|
162
|
+
*
|
163
|
+
* Options specified using the options hash will take precedence over
|
164
|
+
* `data-` attributes.
|
165
|
+
*
|
166
|
+
* Example using options hash:
|
167
|
+
* ```javascript
|
168
|
+
* Duo.init({
|
169
|
+
* iframe: "some_other_id",
|
170
|
+
* host: "api-main.duo.test",
|
171
|
+
* sig_request: "...",
|
172
|
+
* post_action: "/auth",
|
173
|
+
* post_argument: "resp"
|
174
|
+
* });
|
175
|
+
* ```
|
176
|
+
*
|
177
|
+
* Example using `data-` attributes:
|
178
|
+
* ```
|
179
|
+
* <iframe id="duo_iframe"
|
180
|
+
* data-host="api-main.duo.test"
|
181
|
+
* data-sig-request="..."
|
182
|
+
* data-post-action="/auth"
|
183
|
+
* data-post-argument="resp"
|
184
|
+
* >
|
185
|
+
* </iframe>
|
186
|
+
* ```
|
187
|
+
*
|
188
|
+
* @param {Object} options
|
189
|
+
* @param {String} options.iframe The iframe, or id of an iframe to set up
|
190
|
+
* @param {String} options.host Hostname
|
191
|
+
* @param {String} options.sig_request Request token
|
192
|
+
* @param {String} [options.post_action=''] URL to POST back to after successful auth
|
193
|
+
* @param {String} [options.post_argument='sig_response'] Parameter name to use for response token
|
194
|
+
* @param {Function} [options.submit_callback] If provided, duo will not submit the form instead execute
|
195
|
+
* the callback function with reference to the "duo_form" form object
|
196
|
+
* submit_callback can be used to prevent the webpage from reloading.
|
197
|
+
*/
|
198
|
+
function init(options) {
|
199
|
+
if (options) {
|
200
|
+
if (options.host) {
|
201
|
+
host = options.host;
|
202
|
+
}
|
203
|
+
|
204
|
+
if (options.sig_request) {
|
205
|
+
parseSigRequest(options.sig_request);
|
206
|
+
}
|
207
|
+
|
208
|
+
if (options.post_action) {
|
209
|
+
postAction = options.post_action;
|
210
|
+
}
|
211
|
+
|
212
|
+
if (options.post_argument) {
|
213
|
+
postArgument = options.post_argument;
|
214
|
+
}
|
215
|
+
|
216
|
+
if (options.iframe) {
|
217
|
+
if ('tagName' in options.iframe) {
|
218
|
+
iframe = options.iframe;
|
219
|
+
} else if (typeof options.iframe === 'string') {
|
220
|
+
iframeId = options.iframe;
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
if (typeof options.submit_callback === 'function') {
|
225
|
+
submitCallback = options.submit_callback;
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
// if we were given an iframe, no need to wait for the rest of the DOM
|
230
|
+
if (iframe) {
|
231
|
+
ready();
|
232
|
+
} else {
|
233
|
+
// try to find the iframe in the DOM
|
234
|
+
iframe = document.getElementById(iframeId);
|
235
|
+
|
236
|
+
// iframe is in the DOM, away we go!
|
237
|
+
if (iframe) {
|
238
|
+
ready();
|
239
|
+
} else {
|
240
|
+
// wait until the DOM is ready, then try again
|
241
|
+
onReady(onDOMReady);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
// always clean up after yourself!
|
246
|
+
offReady(init);
|
247
|
+
}
|
248
|
+
|
249
|
+
/**
|
250
|
+
* This function is called when a message was received from another domain
|
251
|
+
* using the `postMessage` API. Check that the event came from the Duo
|
252
|
+
* service domain, and that the message is a properly formatted payload,
|
253
|
+
* then perform the post back to the primary service.
|
254
|
+
*
|
255
|
+
* @param event Event object (contains origin and data)
|
256
|
+
*/
|
257
|
+
function onReceivedMessage(event) {
|
258
|
+
if (isDuoMessage(event)) {
|
259
|
+
// the event came from duo, do the post back
|
260
|
+
doPostBack(event.data);
|
261
|
+
|
262
|
+
// always clean up after yourself!
|
263
|
+
offMessage(onReceivedMessage);
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
/**
|
268
|
+
* Point the iframe at Duo, then wait for it to postMessage back to us.
|
269
|
+
*/
|
270
|
+
function ready() {
|
271
|
+
if (!host) {
|
272
|
+
host = getDataAttribute(iframe, 'host');
|
273
|
+
|
274
|
+
if (!host) {
|
275
|
+
throwError(
|
276
|
+
'No API hostname is given for Duo to use. Be sure to pass ' +
|
277
|
+
'a `host` parameter to Duo.init, or through the `data-host` ' +
|
278
|
+
'attribute on the iframe element.',
|
279
|
+
'https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe'
|
280
|
+
);
|
281
|
+
}
|
282
|
+
}
|
283
|
+
|
284
|
+
if (!duoSig || !appSig) {
|
285
|
+
parseSigRequest(getDataAttribute(iframe, 'sigRequest'));
|
286
|
+
|
287
|
+
if (!duoSig || !appSig) {
|
288
|
+
throwError(
|
289
|
+
'No valid signed request is given. Be sure to give the ' +
|
290
|
+
'`sig_request` parameter to Duo.init, or use the ' +
|
291
|
+
'`data-sig-request` attribute on the iframe element.',
|
292
|
+
'https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe'
|
293
|
+
);
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
// if postAction/Argument are defaults, see if they are specified
|
298
|
+
// as data attributes on the iframe
|
299
|
+
if (postAction === '') {
|
300
|
+
postAction = getDataAttribute(iframe, 'postAction') || postAction;
|
301
|
+
}
|
302
|
+
|
303
|
+
if (postArgument === 'sig_response') {
|
304
|
+
postArgument = getDataAttribute(iframe, 'postArgument') || postArgument;
|
305
|
+
}
|
306
|
+
|
307
|
+
// point the iframe at Duo
|
308
|
+
iframe.src = [
|
309
|
+
'https://', host, '/frame/web/v1/auth?tx=', duoSig,
|
310
|
+
'&parent=', encodeURIComponent(document.location.href),
|
311
|
+
'&v=2.3'
|
312
|
+
].join('');
|
313
|
+
|
314
|
+
// listen for the 'message' event
|
315
|
+
onMessage(onReceivedMessage);
|
316
|
+
}
|
317
|
+
|
318
|
+
/**
|
319
|
+
* We received a postMessage from Duo. POST back to the primary service
|
320
|
+
* with the response token, and any additional user-supplied parameters
|
321
|
+
* given in form#duo_form.
|
322
|
+
*/
|
323
|
+
function doPostBack(response) {
|
324
|
+
// create a hidden input to contain the response token
|
325
|
+
var input = document.createElement('input');
|
326
|
+
input.type = 'hidden';
|
327
|
+
input.name = postArgument;
|
328
|
+
input.value = response + ':' + appSig;
|
329
|
+
|
330
|
+
// user may supply their own form with additional inputs
|
331
|
+
var form = document.getElementById('duo_form');
|
332
|
+
|
333
|
+
// if the form doesn't exist, create one
|
334
|
+
if (!form) {
|
335
|
+
form = document.createElement('form');
|
336
|
+
|
337
|
+
// insert the new form after the iframe
|
338
|
+
iframe.parentElement.insertBefore(form, iframe.nextSibling);
|
339
|
+
}
|
340
|
+
|
341
|
+
// make sure we are actually posting to the right place
|
342
|
+
form.method = 'POST';
|
343
|
+
form.action = postAction;
|
344
|
+
|
345
|
+
// add the response token input to the form
|
346
|
+
form.appendChild(input);
|
347
|
+
|
348
|
+
// away we go!
|
349
|
+
if (typeof submitCallback === "function") {
|
350
|
+
submitCallback.call(null, form);
|
351
|
+
} else {
|
352
|
+
form.submit();
|
353
|
+
}
|
354
|
+
}
|
355
|
+
|
356
|
+
// when the DOM is ready, initialize
|
357
|
+
// note that this will get cleaned up if the user calls init directly!
|
358
|
+
onReady(init);
|
359
|
+
|
360
|
+
return {
|
361
|
+
init: init,
|
362
|
+
_parseSigRequest: parseSigRequest,
|
363
|
+
_isDuoMessage: isDuoMessage,
|
364
|
+
_doPostBack: doPostBack
|
365
|
+
};
|
366
|
+
}(document, window));
|