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 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