duo_web 1.0.0
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/js/Duo-Web-v2.js +366 -0
- data/js/Duo-Web-v2.min.js +1 -0
- data/lib/duo_web.rb +107 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7c058b1950046167876439b51f1704b6352f255e
|
4
|
+
data.tar.gz: cb1a60d911a9874e70eefcadb16a4273e339d8f9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c57215b09fc5cc0135347425b65016e8b086962302677faaa12ca09d0c2481f9208228e8a96959ec1fff010dd79e980800dd30ed8223b830bc547603a3ba0d82
|
7
|
+
data.tar.gz: c2733a950b847ab7e66e330d8207d0d64d1676b76cc5a5add8fe4bb0893d98f6a6c2b75e4ed6fe8f6a3a6cd8e18f0f13ac34c778869017cab31cf60452ef157b
|
data/js/Duo-Web-v2.js
ADDED
@@ -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=', document.location.href,
|
311
|
+
'&v=2.2'
|
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 @@
|
|
1
|
+
window.Duo=function(e,t){var i=/^(?:AUTH|ENROLL)+\|[A-Za-z0-9\+\/=]+\|[A-Za-z0-9\+\/=]+$/;var o=/^ERR\|[\w\s\.\(\)]+$/;var n="duo_iframe",a="",s="sig_response",r,f,u,d,m,c;function h(e,t){throw new Error("Duo Web SDK error: "+e+(t?"\n"+"See "+t+" for more information":""))}function g(e){return e.replace(/([a-z])([A-Z])/,"$1-$2").toLowerCase()}function l(e,t){if("dataset"in e){return e.dataset[t]}else{return e.getAttribute("data-"+g(t))}}function p(e,i,o,n){if("addEventListener"in t){e.addEventListener(i,n,false)}else{e.attachEvent(o,n)}}function w(e,i,o,n){if("removeEventListener"in t){e.removeEventListener(i,n,false)}else{e.detachEvent(o,n)}}function v(t){p(e,"DOMContentLoaded","onreadystatechange",t)}function b(t){w(e,"DOMContentLoaded","onreadystatechange",t)}function E(e){p(t,"message","onmessage",e)}function _(e){w(t,"message","onmessage",e)}function y(e){if(!e){return}if(e.indexOf("ERR|")===0){h(e.split("|")[1])}if(e.indexOf(":")===-1||e.split(":").length!==2){h("Duo was given a bad token. This might indicate a configuration "+"problem with one of Duo's client libraries.","https://www.duosecurity.com/docs/duoweb#first-steps")}var t=e.split(":");f=e;u=t[0];d=t[1];return{sigRequest:e,duoSig:t[0],appSig:t[1]}}function D(){m=e.getElementById(n);if(!m){throw new Error("This page does not contain an iframe for Duo to use."+'Add an element like <iframe id="duo_iframe"></iframe> '+"to this page. "+"See https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe "+"for more information.")}B();b(D)}function A(e){return Boolean(e.origin==="https://"+r&&typeof e.data==="string"&&(e.data.match(i)||e.data.match(o)))}function L(t){if(t){if(t.host){r=t.host}if(t.sig_request){y(t.sig_request)}if(t.post_action){a=t.post_action}if(t.post_argument){s=t.post_argument}if(t.iframe){if("tagName"in t.iframe){m=t.iframe}else if(typeof t.iframe==="string"){n=t.iframe}}if(typeof t.submit_callback==="function"){c=t.submit_callback}}if(m){B()}else{m=e.getElementById(n);if(m){B()}else{v(D)}}b(L)}function q(e){if(A(e)){R(e.data);_(q)}}function B(){if(!r){r=l(m,"host");if(!r){h("No API hostname is given for Duo to use. Be sure to pass "+"a `host` parameter to Duo.init, or through the `data-host` "+"attribute on the iframe element.","https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe")}}if(!u||!d){y(l(m,"sigRequest"));if(!u||!d){h("No valid signed request is given. Be sure to give the "+"`sig_request` parameter to Duo.init, or use the "+"`data-sig-request` attribute on the iframe element.","https://www.duosecurity.com/docs/duoweb#3.-show-the-iframe")}}if(a===""){a=l(m,"postAction")||a}if(s==="sig_response"){s=l(m,"postArgument")||s}m.src=["https://",r,"/frame/web/v1/auth?tx=",u,"&parent=",e.location.href,"&v=2.2"].join("");E(q)}function R(t){var i=e.createElement("input");i.type="hidden";i.name=s;i.value=t+":"+d;var o=e.getElementById("duo_form");if(!o){o=e.createElement("form");m.parentElement.insertBefore(o,m.nextSibling)}o.method="POST";o.action=a;o.appendChild(i);if(typeof c==="function"){c.call(null,o)}else{o.submit()}}v(L);return{init:L,_parseSigRequest:y,_isDuoMessage:A,_doPostBack:R}}(document,window);
|
data/lib/duo_web.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
##
|
5
|
+
# A Ruby implementation of the Duo WebSDK
|
6
|
+
#
|
7
|
+
module Duo
|
8
|
+
DUO_PREFIX = 'TX'.freeze
|
9
|
+
APP_PREFIX = 'APP'.freeze
|
10
|
+
AUTH_PREFIX = 'AUTH'.freeze
|
11
|
+
|
12
|
+
DUO_EXPIRE = 300
|
13
|
+
APP_EXPIRE = 3600
|
14
|
+
|
15
|
+
IKEY_LEN = 20
|
16
|
+
SKEY_LEN = 40
|
17
|
+
AKEY_LEN = 40
|
18
|
+
|
19
|
+
ERR_USER = 'ERR|The username passed to sign_request() is invalid.'.freeze
|
20
|
+
ERR_IKEY = 'ERR|The Duo integration key passed to sign_request() is invalid.'.freeze
|
21
|
+
ERR_SKEY = 'ERR|The Duo secret key passed to sign_request() is invalid.'.freeze
|
22
|
+
ERR_AKEY = "ERR|The application secret key passed to sign_request() must be at least #{Duo::AKEY_LEN} characters.".freeze
|
23
|
+
|
24
|
+
# Sign a Duo 2FA request
|
25
|
+
# @param ikey [String] The Duo IKEY
|
26
|
+
# @param skey [String] The Duo SKEY
|
27
|
+
# @param akey [String] The Duo AKEY
|
28
|
+
# @param username [String] Username to authenticate as
|
29
|
+
def sign_request(ikey, skey, akey, username)
|
30
|
+
return Duo::ERR_USER if !username || username.empty?
|
31
|
+
return Duo::ERR_USER if username.include? '|'
|
32
|
+
return Duo::ERR_IKEY if !ikey || ikey.to_s.length != Duo::IKEY_LEN
|
33
|
+
return Duo::ERR_SKEY if !skey || skey.to_s.length != Duo::SKEY_LEN
|
34
|
+
return Duo::ERR_AKEY if !akey || akey.to_s.length < Duo::AKEY_LEN
|
35
|
+
|
36
|
+
vals = [username, ikey]
|
37
|
+
|
38
|
+
duo_sig = sign_vals(skey, vals, Duo::DUO_PREFIX, Duo::DUO_EXPIRE)
|
39
|
+
app_sig = sign_vals(akey, vals, Duo::APP_PREFIX, Duo::APP_EXPIRE)
|
40
|
+
|
41
|
+
return [duo_sig, app_sig].join(':')
|
42
|
+
end
|
43
|
+
|
44
|
+
# Verify a Duo 2FA request
|
45
|
+
# @param ikey [String] The Duo IKEY
|
46
|
+
# @param skey [String] The Duo SKEY
|
47
|
+
# @param akey [String] The Duo AKEY
|
48
|
+
# @param sig_response [String] Response from Duo service
|
49
|
+
def verify_response(ikey, skey, akey, sig_response)
|
50
|
+
begin
|
51
|
+
auth_sig, app_sig = sig_response.to_s.split(':')
|
52
|
+
auth_user = parse_vals(skey, auth_sig, Duo::AUTH_PREFIX, ikey)
|
53
|
+
app_user = parse_vals(akey, app_sig, Duo::APP_PREFIX, ikey)
|
54
|
+
rescue
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
|
58
|
+
return nil if auth_user != app_user
|
59
|
+
|
60
|
+
return auth_user
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def hmac_sha1(key, data)
|
66
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), key, data.to_s)
|
67
|
+
end
|
68
|
+
|
69
|
+
def sign_vals(key, vals, prefix, expire)
|
70
|
+
exp = Time.now.to_i + expire
|
71
|
+
|
72
|
+
val_list = vals + [exp]
|
73
|
+
val = val_list.join('|')
|
74
|
+
|
75
|
+
b64 = Base64.encode64(val).delete("\n")
|
76
|
+
cookie = prefix + '|' + b64
|
77
|
+
|
78
|
+
sig = hmac_sha1(key, cookie)
|
79
|
+
return [cookie, sig].join('|')
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse_vals(key, val, prefix, ikey)
|
83
|
+
ts = Time.now.to_i
|
84
|
+
|
85
|
+
parts = val.to_s.split('|')
|
86
|
+
return nil if parts.length != 3
|
87
|
+
u_prefix, u_b64, u_sig = parts
|
88
|
+
|
89
|
+
sig = hmac_sha1(key, [u_prefix, u_b64].join('|'))
|
90
|
+
|
91
|
+
return nil if hmac_sha1(key, sig) != hmac_sha1(key, u_sig)
|
92
|
+
|
93
|
+
return nil if u_prefix != prefix
|
94
|
+
|
95
|
+
cookie_parts = Base64.decode64(u_b64).to_s.split('|')
|
96
|
+
return nil if cookie_parts.length != 3
|
97
|
+
user, u_ikey, exp = cookie_parts
|
98
|
+
|
99
|
+
return nil if u_ikey != ikey
|
100
|
+
|
101
|
+
return nil if ts >= exp.to_i
|
102
|
+
|
103
|
+
return user
|
104
|
+
end
|
105
|
+
|
106
|
+
extend self
|
107
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: duo_web
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Duo Security
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: A Ruby implementation of the Duo Web SDK.
|
42
|
+
email: support@duo.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- js/Duo-Web-v2.js
|
48
|
+
- js/Duo-Web-v2.min.js
|
49
|
+
- lib/duo_web.rb
|
50
|
+
homepage: https://github.com/duosecurity/duo_ruby
|
51
|
+
licenses:
|
52
|
+
- BSD-3-Clause
|
53
|
+
metadata: {}
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 2.5.1
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: Duo Web Ruby
|
74
|
+
test_files: []
|