duo-api 0.1.0 → 0.2.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 +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 [](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
|