bootic_client 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/bootic/bootic_client.rb.svg?branch=master)](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
|