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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d94b9975331db7555613be8c9d9530a781d8e165
4
- data.tar.gz: 9a91f75be1618ec3f22997d3c5956374c0a487e3
3
+ metadata.gz: b1bb5876e908970de5f707d26d2c297cc0a0d3cd
4
+ data.tar.gz: 9b8a515a3671db2330c7a5bde791f2572fc9b32c
5
5
  SHA512:
6
- metadata.gz: eaa86d28caf9535753db8ef94ca7b50a62151eeca3846ce67bfcf705b09ae8c8f5bd85dca02890aff8e8b58f2b29db60595256cd989d2bcb7a6ba1f5a58a8133
7
- data.tar.gz: 61e9fc8c95f731abcde8e0807382f65d02415642b6550e0382a25c1d90e11670fd48b560e81f9b7235c988c4aafb2142d0715ea10f23e4f95f7f51ea0d968039
6
+ metadata.gz: 38fa93a9b47f17794fd0333d47ad56c1fc6e8639381793fb09d9dd1b8b6c45cb5a376b28067f7ca1b1f3d890cfd9a4e3eda4a205faf612faa34abd67fd3831ec
7
+ data.tar.gz: 95a1841f1a71b1c4a43cbcf10e9159dbd0ce695bc01d7f7a07145a1269468ba08edba6b3b4eb11f905c5d1403bb0b13c7859b6a20e04bd37022a304e2c164276
data/.travis.yml CHANGED
@@ -11,4 +11,7 @@ matrix:
11
11
  gemfile: gemfiles/Gemfile.18
12
12
  - rvm: ree
13
13
  gemfile: gemfiles/Gemfile.18
14
-
14
+ notifications:
15
+ email:
16
+ recipients:
17
+ - jon@highrisehq.com
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 Auth API.
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
- Upon completion of the general features we noticed that Duo needs separate
52
- credentials for different APIs. This config is currently Global. We (or someone)
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/[USERNAME]/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.
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 = DuoApi.config.integration_key
20
- password = digest(config.secret_key, body)
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
@@ -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"] || DuoApi.config.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
@@ -7,7 +7,8 @@ require "duo-api/digesting"
7
7
  module DuoApi
8
8
  class Signature
9
9
  extend Util
10
- extend Digesting
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
- def self.sign(user_key)
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 config.integration_key.to_s.length == 0 ||
25
- config.secret_key.to_s.length == 0 ||
26
- config.app_secret.to_s.length == 0
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, config.integration_key]
37
+ vals = [user_key.to_s, client.integration_key]
31
38
 
32
- duo_sig = sign_values(config.secret_key, vals, DUO_PREFIX, DUO_EXPIRE)
33
- app_sig = sign_values(config.app_secret, vals, APP_PREFIX, APP_EXPIRE)
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 self.verify(signed_response)
45
+ def verify(signed_response)
39
46
  auth_sig, app_sig = signed_response.to_s.split(':')
40
- auth_user = parse_vals(config.secret_key, auth_sig, AUTH_PREFIX)
41
- app_user = parse_vals(config.app_secret, app_sig, APP_PREFIX)
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 self.parse_vals(key, val, prefix)
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 != config.integration_key
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 self.sign_values(key, values, prefix, expiration)
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
@@ -7,10 +7,6 @@ module DuoApi
7
7
  Hash[hash.map { |k, v| [k.to_s, v] }]
8
8
  end
9
9
 
10
- def config
11
- DuoApi.config
12
- end
13
-
14
10
  def error_with_message(message)
15
11
  klass = Class.new(DuoApiError)
16
12
  klass.send :define_method, :initialize do |*msg|
@@ -1,3 +1,3 @@
1
1
  module DuoApi
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/duo-api.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require "duo-api/version"
2
- require "duo-api/configuration"
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 ||= Configuration.new
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.get(path, options = {})
24
- Request.request(path, { :method => "GET" }.merge(options))
30
+ def self.post(*args)
31
+ client.post(*args)
25
32
  end
26
33
 
27
- def self.post(path, options = {})
28
- Request.request(path, { :method => "POST" }.merge(options))
34
+ def self.request(*args)
35
+ client.request(*args)
29
36
  end
30
37
 
31
- def self.sign(user_key)
32
- Signature.sign(user_key)
38
+ def self.sign(*args)
39
+ client.sign(*args)
33
40
  end
34
41
 
35
- def self.verify(signed_response)
36
- Signature.verify(signed_response)
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.1.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/configuration.rb
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
@@ -1,4 +0,0 @@
1
- module DuoApi
2
- class Configuration < Struct.new(:hostname, :integration_key, :secret_key, :app_secret)
3
- end
4
- end