duo-api 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -1
- data/README.md +29 -6
- data/lib/duo-api/client.rb +45 -0
- data/lib/duo-api/header_signature.rb +6 -9
- data/lib/duo-api/request.rb +5 -5
- data/lib/duo-api/signature.rb +21 -14
- data/lib/duo-api/util.rb +0 -4
- data/lib/duo-api/version.rb +1 -1
- data/lib/duo-api.rb +17 -10
- metadata +2 -2
- data/lib/duo-api/configuration.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1bb5876e908970de5f707d26d2c297cc0a0d3cd
|
4
|
+
data.tar.gz: 9b8a515a3671db2330c7a5bde791f2572fc9b32c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38fa93a9b47f17794fd0333d47ad56c1fc6e8639381793fb09d9dd1b8b6c45cb5a376b28067f7ca1b1f3d890cfd9a4e3eda4a205faf612faa34abd67fd3831ec
|
7
|
+
data.tar.gz: 95a1841f1a71b1c4a43cbcf10e9159dbd0ce695bc01d7f7a07145a1269468ba08edba6b3b4eb11f905c5d1403bb0b13c7859b6a20e04bd37022a304e2c164276
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# DuoApi [![Build Status](https://travis-ci.org/highrisehq/duo-api.svg)](https://travis-ci.org/highrisehq/duo-api)
|
2
2
|
|
3
|
-
A thin, zero depedency library for connecting to the Duo
|
3
|
+
A thin, zero depedency library for connecting to the [Duo](https://www.duosecurity.com/) API.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -48,10 +48,8 @@ Keep in mind, you likely don't want to store these keys in your repository.
|
|
48
48
|
|
49
49
|
### Configuration Caveat
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
should be adding a way to switch between credentials or perhaps it would be best
|
54
|
-
to initialize a Client class for each credential set
|
51
|
+
If you're using more than one Duo API, you may want to instead use DuoApi::Clients
|
52
|
+
more directly. See usage below.
|
55
53
|
|
56
54
|
## Usage
|
57
55
|
|
@@ -75,6 +73,31 @@ response.message # => "Missing required request parameters"
|
|
75
73
|
See the Duo [API documentation](https://www.duosecurity.com/docs/authapi) for a full list of
|
76
74
|
available endpoints.
|
77
75
|
|
76
|
+
### Client Usage
|
77
|
+
|
78
|
+
If you're using more than one Duo API, you may want to instead use DuoApi::Clients
|
79
|
+
more directly:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
client = DuoApi::Client.new :hostname => "api-xxxxx.duosecurity.com",
|
83
|
+
:integration_key => "asdf",
|
84
|
+
:secret_key => "asdf"
|
85
|
+
|
86
|
+
response = client.get("/auth/v2/check")
|
87
|
+
response.success? # => true
|
88
|
+
response.body["response"]["time"] # => 1357020061
|
89
|
+
|
90
|
+
response = client.post("/auth/v2/preauth", :params => { :username => "jphenow" })
|
91
|
+
response.success? # => true
|
92
|
+
response.code # => "200"
|
93
|
+
response.body["response"]["result"] # => "auth"
|
94
|
+
response.message # => "Account is active"
|
95
|
+
|
96
|
+
response = client.post("/auth/v2/preauth")
|
97
|
+
response.success? # => false
|
98
|
+
response.code # => "400"
|
99
|
+
response.message # => "Missing required request parameters"
|
100
|
+
```
|
78
101
|
|
79
102
|
## Development
|
80
103
|
|
@@ -84,7 +107,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
84
107
|
|
85
108
|
## Contributing
|
86
109
|
|
87
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
110
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/highrisehq/duo-api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
88
111
|
|
89
112
|
|
90
113
|
## License
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module DuoApi
|
2
|
+
class Client
|
3
|
+
ATTRIBUTES = %w[
|
4
|
+
integration_key
|
5
|
+
secret_key
|
6
|
+
app_secret
|
7
|
+
hostname
|
8
|
+
]
|
9
|
+
ATTRIBUTES.each do |attr|
|
10
|
+
attr_accessor attr
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
options.each do |key, value|
|
15
|
+
if ATTRIBUTES.include?(key.to_s)
|
16
|
+
instance_variable_set("@#{key}", value)
|
17
|
+
else
|
18
|
+
raise ArgumentError, "no attribute #{key}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(path, options = {})
|
24
|
+
Request.request(self, path, { :method => "GET" }.merge(options))
|
25
|
+
end
|
26
|
+
|
27
|
+
def post(path, options = {})
|
28
|
+
Request.request(self, path, { :method => "POST" }.merge(options))
|
29
|
+
end
|
30
|
+
|
31
|
+
def sign(user_key)
|
32
|
+
signer.sign(user_key)
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify(signed_response)
|
36
|
+
signer.verify(signed_response)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def signer
|
42
|
+
Signature.new(self)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,23 +1,24 @@
|
|
1
1
|
require 'time'
|
2
2
|
module DuoApi
|
3
3
|
class HeaderSignature
|
4
|
-
extend Util
|
5
4
|
include Util
|
6
5
|
include Digesting
|
7
6
|
|
7
|
+
attr_reader :client
|
8
8
|
attr_reader :method
|
9
9
|
attr_reader :path
|
10
10
|
attr_reader :query_body_string
|
11
11
|
|
12
|
-
def initialize(method, path, query_body_string)
|
12
|
+
def initialize(client, method, path, query_body_string)
|
13
|
+
@client = client
|
13
14
|
@method = method
|
14
15
|
@path = path
|
15
16
|
@query_body_string = query_body_string
|
16
17
|
end
|
17
18
|
|
18
19
|
def basic_auth
|
19
|
-
username =
|
20
|
-
password = digest(
|
20
|
+
username = client.integration_key
|
21
|
+
password = digest(client.secret_key, body)
|
21
22
|
|
22
23
|
[username, password]
|
23
24
|
end
|
@@ -31,14 +32,10 @@ module DuoApi
|
|
31
32
|
def body
|
32
33
|
components = [date_header]
|
33
34
|
components << method
|
34
|
-
components << hostname
|
35
|
+
components << client.hostname
|
35
36
|
components << path
|
36
37
|
components << query_body_string
|
37
38
|
components.join("\n")
|
38
39
|
end
|
39
|
-
|
40
|
-
def hostname
|
41
|
-
DuoApi.config.hostname
|
42
|
-
end
|
43
40
|
end
|
44
41
|
end
|
data/lib/duo-api/request.rb
CHANGED
@@ -9,10 +9,10 @@ module DuoApi
|
|
9
9
|
extend Util
|
10
10
|
include Util
|
11
11
|
|
12
|
-
def self.request(path, options = {})
|
12
|
+
def self.request(client, path, options = {})
|
13
13
|
options = stringify_hash(options)
|
14
|
-
hostname = options["hostname"] ||
|
15
|
-
instance = new(options["method"], hostname, path, options["params"], options["headers"])
|
14
|
+
hostname = options["hostname"] || client.hostname
|
15
|
+
instance = new(client, options["method"], hostname, path, options["params"], options["headers"])
|
16
16
|
instance.run
|
17
17
|
end
|
18
18
|
|
@@ -22,7 +22,7 @@ module DuoApi
|
|
22
22
|
attr_reader :headers
|
23
23
|
attr_reader :uri
|
24
24
|
|
25
|
-
def initialize(method, hostname, path, params, headers)
|
25
|
+
def initialize(client, method, hostname, path, params, headers)
|
26
26
|
@method = method.to_s.upcase
|
27
27
|
@method = "GET" if @method.length == 0
|
28
28
|
hostname = hostname.to_s.downcase.sub(/\/\z/, "")
|
@@ -34,7 +34,7 @@ module DuoApi
|
|
34
34
|
map {|k,v| "#{URI.encode(k.to_s)}=#{URI.encode(v.to_s)}" }.
|
35
35
|
join("&")
|
36
36
|
|
37
|
-
@signature = HeaderSignature.new(method, path, query_string)
|
37
|
+
@signature = HeaderSignature.new(client, method, path, query_string)
|
38
38
|
|
39
39
|
@headers = stringify_hash(headers)
|
40
40
|
@headers["Date"] = signature.date_header
|
data/lib/duo-api/signature.rb
CHANGED
@@ -7,7 +7,8 @@ require "duo-api/digesting"
|
|
7
7
|
module DuoApi
|
8
8
|
class Signature
|
9
9
|
extend Util
|
10
|
-
|
10
|
+
include Util
|
11
|
+
include Digesting
|
11
12
|
|
12
13
|
DUO_PREFIX = 'TX'
|
13
14
|
APP_PREFIX = 'APP'
|
@@ -19,26 +20,32 @@ module DuoApi
|
|
19
20
|
|
20
21
|
ERR_USER = error_with_message('The user_key passed to sign with is invalid.')
|
21
22
|
|
22
|
-
|
23
|
+
attr_reader :client
|
24
|
+
|
25
|
+
def initialize(client)
|
26
|
+
@client = client
|
27
|
+
end
|
28
|
+
|
29
|
+
def sign(user_key)
|
23
30
|
raise ERR_USER if !user_key || user_key.to_s.length == 0 if user_key.include?('|')
|
24
|
-
if
|
25
|
-
|
26
|
-
|
31
|
+
if client.integration_key.to_s.length == 0 ||
|
32
|
+
client.secret_key.to_s.length == 0 ||
|
33
|
+
client.app_secret.to_s.length == 0
|
27
34
|
raise InvalidConfiguration, "your DuoApi doesn't seem to be configured properly"
|
28
35
|
end
|
29
36
|
|
30
|
-
vals = [user_key.to_s,
|
37
|
+
vals = [user_key.to_s, client.integration_key]
|
31
38
|
|
32
|
-
duo_sig = sign_values(
|
33
|
-
app_sig = sign_values(
|
39
|
+
duo_sig = sign_values(client.secret_key, vals, DUO_PREFIX, DUO_EXPIRE)
|
40
|
+
app_sig = sign_values(client.app_secret, vals, APP_PREFIX, APP_EXPIRE)
|
34
41
|
|
35
42
|
return [duo_sig, app_sig].join(':')
|
36
43
|
end
|
37
44
|
|
38
|
-
def
|
45
|
+
def verify(signed_response)
|
39
46
|
auth_sig, app_sig = signed_response.to_s.split(':')
|
40
|
-
auth_user = parse_vals(
|
41
|
-
app_user = parse_vals(
|
47
|
+
auth_user = parse_vals(client.secret_key, auth_sig, AUTH_PREFIX)
|
48
|
+
app_user = parse_vals(client.app_secret, app_sig, APP_PREFIX)
|
42
49
|
|
43
50
|
return nil if auth_user != app_user
|
44
51
|
|
@@ -47,7 +54,7 @@ module DuoApi
|
|
47
54
|
|
48
55
|
private
|
49
56
|
|
50
|
-
def
|
57
|
+
def parse_vals(key, val, prefix)
|
51
58
|
ts = Time.now.to_i
|
52
59
|
|
53
60
|
parts = val.to_s.split('|')
|
@@ -66,14 +73,14 @@ module DuoApi
|
|
66
73
|
return nil if cookie_parts.length != 3
|
67
74
|
user, u_ikey, exp = cookie_parts
|
68
75
|
|
69
|
-
return nil if u_ikey !=
|
76
|
+
return nil if u_ikey != client.integration_key
|
70
77
|
|
71
78
|
return nil if ts >= exp.to_i
|
72
79
|
|
73
80
|
return user
|
74
81
|
end
|
75
82
|
|
76
|
-
def
|
83
|
+
def sign_values(key, values, prefix, expiration)
|
77
84
|
exp = Time.now.to_i + expiration
|
78
85
|
|
79
86
|
val_list = values + [exp]
|
data/lib/duo-api/util.rb
CHANGED
data/lib/duo-api/version.rb
CHANGED
data/lib/duo-api.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "duo-api/version"
|
2
|
-
require "duo-api/
|
2
|
+
require "duo-api/client"
|
3
3
|
require "duo-api/signature"
|
4
4
|
require "duo-api/request"
|
5
5
|
|
@@ -15,24 +15,31 @@ module DuoApi
|
|
15
15
|
InvalidConfiguration = Class.new(StandardError)
|
16
16
|
|
17
17
|
def self.config
|
18
|
-
@config ||=
|
18
|
+
@config ||= Client.new
|
19
19
|
yield @config if block_given?
|
20
20
|
@config
|
21
21
|
end
|
22
|
+
class << self
|
23
|
+
alias client config
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.get(*args)
|
27
|
+
client.get(*args)
|
28
|
+
end
|
22
29
|
|
23
|
-
def self.
|
24
|
-
|
30
|
+
def self.post(*args)
|
31
|
+
client.post(*args)
|
25
32
|
end
|
26
33
|
|
27
|
-
def self.
|
28
|
-
|
34
|
+
def self.request(*args)
|
35
|
+
client.request(*args)
|
29
36
|
end
|
30
37
|
|
31
|
-
def self.sign(
|
32
|
-
|
38
|
+
def self.sign(*args)
|
39
|
+
client.sign(*args)
|
33
40
|
end
|
34
41
|
|
35
|
-
def self.verify(
|
36
|
-
|
42
|
+
def self.verify(*args)
|
43
|
+
client.verify(*args)
|
37
44
|
end
|
38
45
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: duo-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Phenow
|
@@ -76,7 +76,7 @@ files:
|
|
76
76
|
- exe/duo-api
|
77
77
|
- gemfiles/Gemfile.18
|
78
78
|
- lib/duo-api.rb
|
79
|
-
- lib/duo-api/
|
79
|
+
- lib/duo-api/client.rb
|
80
80
|
- lib/duo-api/digesting.rb
|
81
81
|
- lib/duo-api/header_signature.rb
|
82
82
|
- lib/duo-api/request.rb
|