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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/javascripts/devise_duo_security/Duo-Web-v2.js +366 -0
  6. data/app/assets/stylesheets/devise_duo_security/Duo-Frame.css +10 -0
  7. data/app/controllers/devise/duo_security_controller.rb +39 -0
  8. data/app/views/devise/duo_security/_test_iframe_response.html.erb +144 -0
  9. data/app/views/devise/duo_security/show.html.erb +15 -0
  10. data/lib/devise/duo_security/controllers/helpers.rb +41 -0
  11. data/lib/devise/duo_security/engine.rb +14 -0
  12. data/lib/devise/duo_security/version.rb +5 -0
  13. data/lib/devise_duo_sec.rb +43 -0
  14. data/lib/duo_web.rb +107 -0
  15. data/lib/tasks/devise_duo_security_tasks.rake +4 -0
  16. data/test/devise_duo_security_test.rb +16 -0
  17. data/test/dummy/Gemfile +10 -0
  18. data/test/dummy/Gemfile.lock +138 -0
  19. data/test/dummy/README.rdoc +28 -0
  20. data/test/dummy/Rakefile +6 -0
  21. data/test/dummy/app/assets/javascripts/application.js +15 -0
  22. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  23. data/test/dummy/app/controllers/application_controller.rb +5 -0
  24. data/test/dummy/app/controllers/home_controller.rb +13 -0
  25. data/test/dummy/app/helpers/application_helper.rb +2 -0
  26. data/test/dummy/app/models/user.rb +6 -0
  27. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  28. data/test/dummy/bin/bundle +3 -0
  29. data/test/dummy/bin/rails +4 -0
  30. data/test/dummy/bin/rake +4 -0
  31. data/test/dummy/bin/setup +29 -0
  32. data/test/dummy/config.ru +4 -0
  33. data/test/dummy/config/application.rb +26 -0
  34. data/test/dummy/config/boot.rb +5 -0
  35. data/test/dummy/config/database.yml +25 -0
  36. data/test/dummy/config/environment.rb +5 -0
  37. data/test/dummy/config/environments/development.rb +42 -0
  38. data/test/dummy/config/environments/production.rb +78 -0
  39. data/test/dummy/config/environments/test.rb +42 -0
  40. data/test/dummy/config/initializers/assets.rb +11 -0
  41. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  42. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  43. data/test/dummy/config/initializers/devise.rb +259 -0
  44. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  45. data/test/dummy/config/initializers/inflections.rb +16 -0
  46. data/test/dummy/config/initializers/mime_types.rb +4 -0
  47. data/test/dummy/config/initializers/session_store.rb +3 -0
  48. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  49. data/test/dummy/config/locales/devise.en.yml +60 -0
  50. data/test/dummy/config/locales/en.yml +23 -0
  51. data/test/dummy/config/routes.rb +7 -0
  52. data/test/dummy/config/secrets.yml +22 -0
  53. data/test/dummy/db/migrate/20150320103707_devise_create_users.rb +42 -0
  54. data/test/dummy/db/schema.rb +34 -0
  55. data/test/dummy/public/404.html +67 -0
  56. data/test/dummy/public/422.html +67 -0
  57. data/test/dummy/public/500.html +66 -0
  58. data/test/dummy/public/favicon.ico +0 -0
  59. data/test/dummy/test/fixtures/users.yml +11 -0
  60. data/test/dummy/test/models/user_test.rb +7 -0
  61. data/test/integration/navigation_test.rb +25 -0
  62. data/test/support/helpers.rb +40 -0
  63. data/test/test_helper.rb +46 -0
  64. 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
@@ -0,0 +1,3 @@
1
+ = Devise::DuoSecurity
2
+
3
+ This project rocks and uses MIT-LICENSE.
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));
@@ -0,0 +1,10 @@
1
+ body {
2
+ text-align: center;
3
+ }
4
+
5
+ iframe {
6
+ width: 100%;
7
+ min-width: 304px;
8
+ max-width: 620px;
9
+ height: 330px;
10
+ }