bootic_client 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +115 -0
- data/Rakefile +6 -0
- data/bootic_client.gemspec +33 -0
- data/lib/bootic_client/client.rb +73 -0
- data/lib/bootic_client/entity.rb +125 -0
- data/lib/bootic_client/errors.rb +9 -0
- data/lib/bootic_client/relation.rb +60 -0
- data/lib/bootic_client/stores/memcache.rb +33 -0
- data/lib/bootic_client/strategies/authorized.rb +38 -0
- data/lib/bootic_client/strategies/client_credentials.rb +19 -0
- data/lib/bootic_client/strategies/strategy.rb +59 -0
- data/lib/bootic_client/version.rb +3 -0
- data/lib/bootic_client.rb +47 -0
- data/spec/authorized_strategy_spec.rb +117 -0
- data/spec/bootic_client_spec.rb +8 -0
- data/spec/client_credentials_strategy_spec.rb +96 -0
- data/spec/client_spec.rb +139 -0
- data/spec/entity_spec.rb +171 -0
- data/spec/memcache_storage_spec.rb +55 -0
- data/spec/relation_spec.rb +55 -0
- data/spec/spec_helper.rb +8 -0
- metadata +232 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4311719bc6b53b09e5026c67183f7313c491f48f
|
4
|
+
data.tar.gz: 7f85b7946a404ae8b32019cb05df811ce6a60709
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 125c0102f1890b005ef8e4ff9ca74f4f597d16c8c542c097edb1e5541ee17c05edc3cd64b0200e7dc61282bd1d5b72dcf8a32e8f3b5f1ce2e0f11ee4907e86f9
|
7
|
+
data.tar.gz: f9b42eeb52e5e031b35d3b2d3c25a20408e0753348c60e358b3fd8fa8f198a21918c8958377a809484f65304ff2b60a1209f1081eb8cc36c75da6ab34e45d187
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Ismael Celis
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
[](https://travis-ci.org/bootic/bootic_client.rb)
|
2
|
+
|
3
|
+
## WORK IN PROGRESS
|
4
|
+
|
5
|
+
# BooticClient
|
6
|
+
|
7
|
+
Official Ruby client for the [Bootic API](https://developers.bootic.net)
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'bootic_client'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install bootic_client
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Configure with you app's credentials
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
BooticClient.configure do |c|
|
29
|
+
c.client_id = ENV['BOOTIC_CLIENT_ID']
|
30
|
+
c.client_secret = ENV['BOOTIC_CLIENT_SECRET']
|
31
|
+
c.logger = Logger.new(STDOUT)
|
32
|
+
c.logging = true
|
33
|
+
c.cache_store = Rails.cache
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
### Using with an existing access token
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
bootic = BooticClient.client(:authorized, access_token: 'beidjbewjdiedue...', logging: true)
|
41
|
+
|
42
|
+
root = bootic.root
|
43
|
+
|
44
|
+
if root.has?(:products)
|
45
|
+
# All products
|
46
|
+
all_products = root.products(q: 'xmas presents')
|
47
|
+
all_products.total_items # => 23443
|
48
|
+
all_products.each do |product|
|
49
|
+
puts product.title
|
50
|
+
puts product.price
|
51
|
+
end
|
52
|
+
|
53
|
+
if all_product.has?(:next)
|
54
|
+
next_page = all_products.next
|
55
|
+
next_page.each{...}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
## 1. Refresh token flow (web apps)
|
61
|
+
|
62
|
+
In this flow you first get a token by authorizing an app. ie. using [omniauth-bootic](https://github.com/bootic/omniauth-bootic)
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
def client
|
66
|
+
@client ||= BooticClient.client(:authorized, access_token: session[:access_token]) do |new_token|
|
67
|
+
session[:access_token] = new_token
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
|
73
|
+
## 2. User-less flow (client credentials - automated scripts)
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
client = BooticClient.client(:client_credentials, scope: 'admin', access_token: some_store[:access_token]) do |new_token|
|
77
|
+
some_store[:access_token] = new_token
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
|
82
|
+
## Cache storage
|
83
|
+
|
84
|
+
`BooticClient` honours HTTP caching headers included in API responses (such as `ETag` and `Last-Modified`).
|
85
|
+
|
86
|
+
By default a simple memory store is used. It is recommended that you use a distributed store in production, such as Memcache. In Rails applications you can use the `Rails.cache` interface.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
BooticClient.configure do |c|
|
90
|
+
...
|
91
|
+
c.cache_store = Rails.cache
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
Outside of Rails, BooticClient ships with a wrapper around the [Dalli](https://github.com/mperham/dalli) memcache client.
|
96
|
+
You must include Dalli in your Gemfile and require the wrapper explicitely.
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
require 'bootic_client/stores/memcache'
|
100
|
+
CACHE_STORE = BooticClient::Stores::Memcache.new(ENV['MEMCACHE_SERVER'])
|
101
|
+
|
102
|
+
BooticClient.configure do |c|
|
103
|
+
...
|
104
|
+
c.cache_store = CACHE_STORE
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## Contributing
|
109
|
+
|
110
|
+
1. Fork it
|
111
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
112
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
113
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
114
|
+
5. Create new Pull Request
|
115
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bootic_client/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "bootic_client"
|
8
|
+
spec.version = BooticClient::VERSION
|
9
|
+
spec.authors = ["Ismael Celis"]
|
10
|
+
spec.email = ["ismaelct@gmail.com"]
|
11
|
+
spec.description = %q{Official Ruby client for the Bootic API}
|
12
|
+
spec.summary = %q{Official Ruby client for the Bootic API}
|
13
|
+
spec.homepage = "https://developers.bootic.net"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "faraday", '~> 0.9'
|
22
|
+
spec.add_dependency "uri_template", '~> 0.7'
|
23
|
+
spec.add_dependency "faraday_middleware", '~> 0.9'
|
24
|
+
spec.add_dependency "faraday-http-cache", '~> 0.4'
|
25
|
+
spec.add_dependency "net-http-persistent", '~> 2.9'
|
26
|
+
spec.add_dependency "oauth2"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
29
|
+
spec.add_development_dependency "rake"
|
30
|
+
spec.add_development_dependency "rspec"
|
31
|
+
spec.add_development_dependency "jwt"
|
32
|
+
spec.add_development_dependency "dalli"
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require 'faraday-http-cache'
|
4
|
+
require "bootic_client/errors"
|
5
|
+
require 'faraday/adapter/net_http_persistent'
|
6
|
+
|
7
|
+
module BooticClient
|
8
|
+
|
9
|
+
class Client
|
10
|
+
|
11
|
+
USER_AGENT = "[BooticClient v#{VERSION}] Ruby-#{RUBY_VERSION} - #{RUBY_PLATFORM}".freeze
|
12
|
+
|
13
|
+
attr_reader :options, :api_root
|
14
|
+
|
15
|
+
def initialize(api_root, options = {}, &block)
|
16
|
+
@api_root = api_root
|
17
|
+
@options = {
|
18
|
+
access_token: nil,
|
19
|
+
logging: false
|
20
|
+
}.merge(options.dup)
|
21
|
+
|
22
|
+
@options[:cache_store] = @options[:cache_store] || Faraday::HttpCache::MemoryStore.new
|
23
|
+
|
24
|
+
conn &block if block_given?
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_and_wrap(href, wrapper_class, query = {})
|
28
|
+
wrapper_class.new get(href, query).body, self
|
29
|
+
end
|
30
|
+
|
31
|
+
def get(href, query = {})
|
32
|
+
validate_request!
|
33
|
+
|
34
|
+
resp = conn.get do |req|
|
35
|
+
req.url href
|
36
|
+
req.params.update(query)
|
37
|
+
req.headers['Authorization'] = "Bearer #{options[:access_token]}"
|
38
|
+
req.headers['User-Agent'] = USER_AGENT
|
39
|
+
end
|
40
|
+
|
41
|
+
raise_if_invalid! resp
|
42
|
+
|
43
|
+
resp
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def conn(&block)
|
49
|
+
@conn ||= Faraday.new(url: api_root) do |f|
|
50
|
+
cache_options = {shared_cache: false, store: options[:cache_store]}
|
51
|
+
cache_options[:logger] = options[:logger] if options[:logging]
|
52
|
+
|
53
|
+
f.use :http_cache, cache_options
|
54
|
+
f.response :logger, options[:logger] if options[:logging]
|
55
|
+
f.response :json
|
56
|
+
yield f if block_given?
|
57
|
+
f.adapter :net_http_persistent
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_request!
|
62
|
+
raise NoAccessTokenError, "Missing access token" unless options[:access_token]
|
63
|
+
end
|
64
|
+
|
65
|
+
def raise_if_invalid!(resp)
|
66
|
+
raise ServerError, "Server Error" if resp.status > 499
|
67
|
+
raise NotFoundError, "Not Found" if resp.status == 404
|
68
|
+
raise UnauthorizedError, "Unauthorized request" if resp.status == 401
|
69
|
+
raise AccessForbiddenError, "Access Forbidden" if resp.status == 403
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "bootic_client/relation"
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module BooticClient
|
5
|
+
class Entity
|
6
|
+
|
7
|
+
CURIE_EXP = /(.+):(.+)/.freeze
|
8
|
+
CURIES_REL = 'curies'.freeze
|
9
|
+
SPECIAL_PROP_EXP = /^_.+/.freeze
|
10
|
+
|
11
|
+
attr_reader :curies, :entities
|
12
|
+
|
13
|
+
def initialize(attrs, client, top = self)
|
14
|
+
@attrs, @client, @top = attrs, client, top
|
15
|
+
build!
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
properties[key.to_sym]
|
20
|
+
end
|
21
|
+
|
22
|
+
def has?(prop_name)
|
23
|
+
has_property?(prop_name) || has_entity?(prop_name) || has_rel?(prop_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
%(#<#{self.class.name} props: [#{properties.keys.join(', ')}] rels: [#{rels.keys.join(', ')}] entities: [#{entities.keys.join(', ')}]>)
|
28
|
+
end
|
29
|
+
|
30
|
+
def properties
|
31
|
+
@properties ||= attrs.select{|k,v| !(k =~ SPECIAL_PROP_EXP)}.each_with_object({}) do |(k,v),memo|
|
32
|
+
memo[k.to_sym] = Entity.wrap(v)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def links
|
37
|
+
@links ||= attrs.fetch('_links', {})
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.wrap(obj)
|
41
|
+
case obj
|
42
|
+
when Hash
|
43
|
+
OpenStruct.new(obj)
|
44
|
+
when Array
|
45
|
+
obj.map{|e| wrap(e)}
|
46
|
+
else
|
47
|
+
obj
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(name, *args, &block)
|
52
|
+
if !block_given?
|
53
|
+
if has_property?(name)
|
54
|
+
self[name]
|
55
|
+
elsif has_entity?(name)
|
56
|
+
entities[name]
|
57
|
+
elsif has_rel?(name)
|
58
|
+
rels[name].get(*args)
|
59
|
+
else
|
60
|
+
super
|
61
|
+
end
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def respond_to_missing?(method_name, include_private = false)
|
68
|
+
has?(method_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
def has_property?(prop_name)
|
72
|
+
properties.has_key? prop_name.to_sym
|
73
|
+
end
|
74
|
+
|
75
|
+
def has_entity?(prop_name)
|
76
|
+
entities.has_key? prop_name.to_sym
|
77
|
+
end
|
78
|
+
|
79
|
+
def has_rel?(prop_name)
|
80
|
+
rels.has_key? prop_name.to_sym
|
81
|
+
end
|
82
|
+
|
83
|
+
def each(&block)
|
84
|
+
iterable? ? entities[:items].each(&block) : [self].each(&block)
|
85
|
+
end
|
86
|
+
|
87
|
+
def rels
|
88
|
+
@rels ||= (
|
89
|
+
links = attrs.fetch('_links', {})
|
90
|
+
links.each_with_object({}) do |(rel,rel_attrs),memo|
|
91
|
+
if rel =~ CURIE_EXP
|
92
|
+
_, curie_namespace, rel = rel.split(CURIE_EXP)
|
93
|
+
if curie = curies.find{|c| c['name'] == curie_namespace}
|
94
|
+
rel_attrs['docs'] = Relation.expand(curie['href'], rel: rel)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
if rel != CURIES_REL
|
98
|
+
rel_attrs['name'] = rel
|
99
|
+
memo[rel.to_sym] = Relation.new(rel_attrs, client, Entity)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
attr_reader :client, :top, :attrs
|
108
|
+
|
109
|
+
def iterable?
|
110
|
+
has_entity?(:items) && entities[:items].respond_to?(:each)
|
111
|
+
end
|
112
|
+
|
113
|
+
def build!
|
114
|
+
@curies = top.links.fetch('curies', [])
|
115
|
+
|
116
|
+
@entities = attrs.fetch('_embedded', {}).each_with_object({}) do |(k,v),memo|
|
117
|
+
memo[k.to_sym] = if v.kind_of?(Array)
|
118
|
+
v.map{|ent_attrs| Entity.new(ent_attrs, client, top)}
|
119
|
+
else
|
120
|
+
Entity.new(v, client, top)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module BooticClient
|
2
|
+
class TransportError < StandardError; end
|
3
|
+
class ServerError < TransportError; end
|
4
|
+
class NotFoundError < ServerError; end
|
5
|
+
class TokenError < ServerError; end
|
6
|
+
class UnauthorizedError < TokenError; end
|
7
|
+
class AccessForbiddenError < TokenError; end
|
8
|
+
class NoAccessTokenError < TokenError; end
|
9
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'uri_template'
|
2
|
+
require "bootic_client/entity"
|
3
|
+
|
4
|
+
module BooticClient
|
5
|
+
|
6
|
+
class Relation
|
7
|
+
|
8
|
+
def initialize(attrs, client, wrapper_class = Entity)
|
9
|
+
@attrs, @client, @wrapper_class = attrs, client, wrapper_class
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
%(#<#{self.class.name} #{attrs.inspect}>)
|
14
|
+
end
|
15
|
+
|
16
|
+
def href
|
17
|
+
attrs['href']
|
18
|
+
end
|
19
|
+
|
20
|
+
def templated?
|
21
|
+
!!attrs['templated']
|
22
|
+
end
|
23
|
+
|
24
|
+
def name
|
25
|
+
attrs['name']
|
26
|
+
end
|
27
|
+
|
28
|
+
def title
|
29
|
+
attrs['title']
|
30
|
+
end
|
31
|
+
|
32
|
+
def type
|
33
|
+
attrs['type']
|
34
|
+
end
|
35
|
+
|
36
|
+
def docs
|
37
|
+
attrs['docs']
|
38
|
+
end
|
39
|
+
|
40
|
+
def get(opts = {})
|
41
|
+
if templated?
|
42
|
+
client.get_and_wrap uri.expand(opts), wrapper_class
|
43
|
+
else
|
44
|
+
client.get_and_wrap href, wrapper_class, opts
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.expand(href, opts = {})
|
49
|
+
URITemplate.new(href).expand(opts)
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
attr_reader :wrapper_class, :client, :attrs
|
54
|
+
|
55
|
+
def uri
|
56
|
+
@uri ||= URITemplate.new(href)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'dalli'
|
2
|
+
|
3
|
+
module BooticClient
|
4
|
+
module Stores
|
5
|
+
class Memcache
|
6
|
+
attr_reader :client
|
7
|
+
|
8
|
+
def initialize(server_hosts, dalli_options = {})
|
9
|
+
@client = Dalli::Client.new(Array(server_hosts), dalli_options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def read(key)
|
13
|
+
@client.get key.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(key, data, ttl = nil)
|
17
|
+
@client.set key.to_s, data, ttl
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(key)
|
21
|
+
@client.get key
|
22
|
+
end
|
23
|
+
|
24
|
+
def set(key, data, ttl = nil)
|
25
|
+
@client.set key, data, ttl
|
26
|
+
end
|
27
|
+
|
28
|
+
def stats
|
29
|
+
@client.stats
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'bootic_client/strategies/strategy'
|
2
|
+
|
3
|
+
module BooticClient
|
4
|
+
module Strategies
|
5
|
+
|
6
|
+
class Authorized < Strategy
|
7
|
+
protected
|
8
|
+
|
9
|
+
def validate!(options)
|
10
|
+
raise "options MUST include access_token" unless options[:access_token]
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_token
|
14
|
+
# The JWT grant must have an expiration date, in seconds since the epoch.
|
15
|
+
# For most cases a few seconds should be enough.
|
16
|
+
exp = Time.now.utc.to_i + 5
|
17
|
+
|
18
|
+
# Use the "assertion" flow to exchange the JWT grant for an access token
|
19
|
+
access_token = auth.assertion.get_token(
|
20
|
+
hmac_secret: config.client_secret,
|
21
|
+
iss: config.client_id,
|
22
|
+
prn: client.options[:access_token],
|
23
|
+
aud: 'api',
|
24
|
+
exp: exp
|
25
|
+
)
|
26
|
+
|
27
|
+
access_token.token
|
28
|
+
end
|
29
|
+
|
30
|
+
def auth
|
31
|
+
@auth ||= OAuth2::Client.new(nil, nil, site: config.auth_host)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
strategies[:authorized] = Strategies::Authorized
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bootic_client/strategies/strategy'
|
2
|
+
|
3
|
+
module BooticClient
|
4
|
+
module Strategies
|
5
|
+
|
6
|
+
class ClientCredentials < Strategy
|
7
|
+
protected
|
8
|
+
def get_token
|
9
|
+
opts = {}
|
10
|
+
opts['scope'] = options.delete(:scope) if options[:scope]
|
11
|
+
token = auth.client_credentials.get_token(opts, 'auth_scheme' => 'basic')
|
12
|
+
token.token
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
strategies[:client_credentials] = Strategies::ClientCredentials
|
19
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'oauth2'
|
2
|
+
|
3
|
+
module BooticClient
|
4
|
+
module Strategies
|
5
|
+
class Strategy
|
6
|
+
|
7
|
+
def initialize(config, client_opts = {}, &on_new_token)
|
8
|
+
@config, @options, @on_new_token = config, client_opts, (on_new_token || Proc.new)
|
9
|
+
raise "MUST include client_id" unless config.client_id
|
10
|
+
raise "MUST include client_secret" unless config.client_secret
|
11
|
+
raise "MUST include api_root" unless config.api_root
|
12
|
+
validate! @options
|
13
|
+
end
|
14
|
+
|
15
|
+
def root
|
16
|
+
get config.api_root
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(href, query = {})
|
20
|
+
begin
|
21
|
+
client.get_and_wrap(href, Entity, query)
|
22
|
+
rescue TokenError => e
|
23
|
+
new_token = get_token
|
24
|
+
client.options[:access_token] = new_token
|
25
|
+
on_new_token.call new_token
|
26
|
+
client.get_and_wrap(href, Entity, query)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect
|
31
|
+
%(#<#{self.class.name} cid: #{config.client_id} root: #{config.api_root} auth: #{config.auth_host}>)
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
attr_reader :config, :options, :on_new_token
|
37
|
+
|
38
|
+
def validate!(options)
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_token
|
43
|
+
raise "Implement this in subclasses"
|
44
|
+
end
|
45
|
+
|
46
|
+
def auth
|
47
|
+
@auth ||= OAuth2::Client.new(
|
48
|
+
config.client_id,
|
49
|
+
config.client_secret,
|
50
|
+
site: config.auth_host
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def client
|
55
|
+
@client ||= Client.new(config.api_root, options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|