duo_api 1.3.0 → 1.4.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/lib/duo_api.rb +73 -19
- metadata +20 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83307a3ad328ddd423760e517cb7450656253fedb3d2bc513cab405a2c37206c
|
4
|
+
data.tar.gz: 8ec8965d2e8405e7909652275f7cab0a1ff06e10976a86b0d88590d6e81c10bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0b38b626b9a891665eb6850be2e924a8ab01746d7697d6b3b04ca51c7bed6cc84608ef8b6b437b506f59463229e7138380579ce29c2eaa88e0d4f4b272fdc16
|
7
|
+
data.tar.gz: 57abcf5b82835915ce3e6ea847efc0144257fabfc42e58ac22a3b53d3ab4b7a042f89d54e2ecbcece470c14a1e86712a4292cce6e611eee2fa90cffb13325e1b
|
data/lib/duo_api.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'erb'
|
2
|
+
require 'json'
|
2
3
|
require 'openssl'
|
3
4
|
require 'net/https'
|
4
5
|
require 'time'
|
@@ -10,6 +11,12 @@ require 'uri'
|
|
10
11
|
class DuoApi
|
11
12
|
attr_accessor :ca_file
|
12
13
|
|
14
|
+
if Gem.loaded_specs['duo_api']
|
15
|
+
VERSION = Gem.loaded_specs['duo_api'].version
|
16
|
+
else
|
17
|
+
VERSION = '0.0.0'
|
18
|
+
end
|
19
|
+
|
13
20
|
# Constants for handling rate limit backoff
|
14
21
|
MAX_BACKOFF_WAIT_SECS = 32
|
15
22
|
INITIAL_BACKOFF_WAIT_SECS = 1
|
@@ -32,26 +39,40 @@ class DuoApi
|
|
32
39
|
]
|
33
40
|
end
|
34
41
|
@ca_file = ca_file ||
|
35
|
-
|
42
|
+
File.join(File.dirname(__FILE__), '..', 'ca_certs.pem')
|
36
43
|
end
|
37
44
|
|
38
|
-
def request(method, path, params = nil)
|
45
|
+
def request(method, path, params = {}, additional_headers = nil)
|
46
|
+
params_go_in_body = %w[POST PUT PATCH].include?(method)
|
47
|
+
if params_go_in_body
|
48
|
+
body = canon_json(params)
|
49
|
+
params = {}
|
50
|
+
else
|
51
|
+
body = ''
|
52
|
+
end
|
53
|
+
|
39
54
|
uri = request_uri(path, params)
|
40
|
-
current_date, signed = sign(method, uri.host, path, params)
|
55
|
+
current_date, signed = sign(method, uri.host, path, params, body, additional_headers)
|
41
56
|
|
42
57
|
request = Net::HTTP.const_get(method.capitalize).new uri.to_s
|
43
58
|
request.basic_auth(@ikey, signed)
|
44
59
|
request['Date'] = current_date
|
45
|
-
request['User-Agent'] =
|
60
|
+
request['User-Agent'] = "duo_api_ruby/#{VERSION}"
|
61
|
+
if params_go_in_body
|
62
|
+
request['Content-Type'] = 'application/json'
|
63
|
+
request.body = body
|
64
|
+
end
|
46
65
|
|
47
|
-
Net::HTTP.start(
|
48
|
-
|
49
|
-
|
66
|
+
Net::HTTP.start(
|
67
|
+
uri.host, uri.port, *@proxy,
|
68
|
+
use_ssl: true, ca_file: @ca_file,
|
69
|
+
verify_mode: OpenSSL::SSL::VERIFY_PEER
|
70
|
+
) do |http|
|
50
71
|
wait_secs = INITIAL_BACKOFF_WAIT_SECS
|
51
72
|
while true do
|
52
73
|
resp = http.request(request)
|
53
74
|
if resp.code != RATE_LIMITED_RESP_CODE or wait_secs > MAX_BACKOFF_WAIT_SECS
|
54
|
-
|
75
|
+
return resp
|
55
76
|
end
|
56
77
|
random_offset = rand()
|
57
78
|
sleep(wait_secs + random_offset)
|
@@ -69,7 +90,7 @@ class DuoApi
|
|
69
90
|
key + '=' + value
|
70
91
|
end
|
71
92
|
|
72
|
-
def
|
93
|
+
def canon_params(params_hash = nil)
|
73
94
|
return '' if params_hash.nil?
|
74
95
|
params_hash.sort.map do |k, v|
|
75
96
|
# when it is an array, we want to add that as another param
|
@@ -82,30 +103,63 @@ class DuoApi
|
|
82
103
|
end.join('&')
|
83
104
|
end
|
84
105
|
|
85
|
-
def
|
86
|
-
|
106
|
+
def canon_json(params_hash = nil)
|
107
|
+
return '' if params_hash.nil?
|
108
|
+
JSON.generate(Hash[params_hash.sort])
|
109
|
+
end
|
110
|
+
|
111
|
+
def canon_x_duo_headers(additional_headers)
|
112
|
+
additional_headers ||= {}
|
113
|
+
|
114
|
+
if not additional_headers.select{|k,v| k.nil? or v.nil?}.empty?
|
115
|
+
raise 'Not allowed "nil" as a header name or value'
|
116
|
+
end
|
117
|
+
|
118
|
+
canon_list = []
|
119
|
+
added_headers = []
|
120
|
+
additional_headers.keys.sort.each do |header_name|
|
121
|
+
header_name_lowered = header_name.downcase
|
122
|
+
header_value = additional_headers[header_name]
|
123
|
+
validate_additional_header(header_name_lowered, header_value, added_headers)
|
124
|
+
canon_list.append(header_name_lowered, header_value)
|
125
|
+
added_headers.append(header_name_lowered)
|
126
|
+
end
|
127
|
+
|
128
|
+
canon = canon_list.join("\x00")
|
129
|
+
OpenSSL::Digest::SHA512.hexdigest(canon)
|
130
|
+
end
|
131
|
+
|
132
|
+
def validate_additional_header(header_name, value, added_headers)
|
133
|
+
raise 'Not allowed "Null" character in header name' if header_name.include?("\x00")
|
134
|
+
raise 'Not allowed "Null" character in header value' if value.include?("\x00")
|
135
|
+
raise 'Additional headers must start with \'X-Duo-\'' unless header_name.downcase.start_with?('x-duo-')
|
136
|
+
raise "Duplicate header passed, header=#{header_name}" if added_headers.include?(header_name.downcase)
|
87
137
|
end
|
88
138
|
|
89
139
|
def request_uri(path, params = nil)
|
90
140
|
u = 'https://' + @host + path
|
91
|
-
u += '?' +
|
141
|
+
u += '?' + canon_params(params) unless params.nil?
|
92
142
|
URI.parse(u)
|
93
143
|
end
|
94
144
|
|
95
|
-
def canonicalize(method, host, path, params,
|
96
|
-
options[:date]
|
145
|
+
def canonicalize(method, host, path, params, body = '', additional_headers = nil, options: {})
|
146
|
+
# options[:date] being passed manually is specifically for tests
|
147
|
+
date = options[:date] || Time.now.rfc2822()
|
97
148
|
canon = [
|
98
|
-
|
149
|
+
date,
|
99
150
|
method.upcase,
|
100
151
|
host.downcase,
|
101
152
|
path,
|
102
|
-
|
153
|
+
canon_params(params),
|
154
|
+
OpenSSL::Digest::SHA512.hexdigest(body),
|
155
|
+
canon_x_duo_headers(additional_headers)
|
103
156
|
]
|
104
|
-
[
|
157
|
+
[date, canon.join("\n")]
|
105
158
|
end
|
106
159
|
|
107
|
-
def sign(method, host, path, params,
|
108
|
-
date
|
160
|
+
def sign(method, host, path, params, body = '', additional_headers = nil, options: {})
|
161
|
+
# options[:date] being passed manually is specifically for tests
|
162
|
+
date, canon = canonicalize(method, host, path, params, body, additional_headers, options: options)
|
109
163
|
[date, OpenSSL::HMAC.hexdigest('sha512', @skey, canon)]
|
110
164
|
end
|
111
165
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: duo_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Duo Security
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 1.8.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: ostruct
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.1.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.1.0
|
69
83
|
description: A Ruby implementation of the Duo API.
|
70
84
|
email: support@duo.com
|
71
85
|
executables: []
|
@@ -78,7 +92,7 @@ homepage: https://github.com/duosecurity/duo_api_ruby
|
|
78
92
|
licenses:
|
79
93
|
- BSD-3-Clause
|
80
94
|
metadata: {}
|
81
|
-
post_install_message:
|
95
|
+
post_install_message:
|
82
96
|
rdoc_options: []
|
83
97
|
require_paths:
|
84
98
|
- lib
|
@@ -93,8 +107,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
107
|
- !ruby/object:Gem::Version
|
94
108
|
version: '0'
|
95
109
|
requirements: []
|
96
|
-
rubygems_version: 3.
|
97
|
-
signing_key:
|
110
|
+
rubygems_version: 3.4.19
|
111
|
+
signing_key:
|
98
112
|
specification_version: 4
|
99
113
|
summary: Duo API Ruby
|
100
114
|
test_files: []
|