whiplash-app 0.3.1 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9acc3cba8b3e4cef52a2068e9fe3f16c81d7815f
4
- data.tar.gz: c351232810577e3f357512c9c4b7e550bf0dbb2b
3
+ metadata.gz: 5b252f73755dcd2e6cc7d907d0009393a0661993
4
+ data.tar.gz: d38c18badde2221b2c9de962db701ca54a486739
5
5
  SHA512:
6
- metadata.gz: f4bc91939f74965a430b4c5d6f21ca888d568fe9e31fa3e9ef67d94f45324768f983693e8db756836408768ad40cfa7fd9047ffa352fcaabcc88c44332dcc02a
7
- data.tar.gz: 25b613c592e8c728bf9b447624df347e2989bf2b04e2109b371ea200ae57a494883d2382c2a90b56abd8d4e62a56e43bdcb30765aea81b68e5b99d0cb44493f9
6
+ metadata.gz: ae66c0ded848674943ad97c2781236569d4d3cfff515ad30218b7025028660ce0cf10374f243d516b553781ca3a023460ca4b25e21713225b0a135c3ec6708cb
7
+ data.tar.gz: '04845cdd6a2eb36c1f05b43932f11bf25c6d32d0c91fbb74db2cb7f23b5ca9d84d3f8719fe58926831a4d26c1c07119aa03d3b74a92b7f1528f706fa169b37f1'
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .env
11
+ .ruby-version
data/README.md CHANGED
@@ -22,7 +22,27 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- ###Authentication
25
+ **NOTE: 0.4.0 introduces a breaking change and is NOT backward compatible with previous versions.**
26
+
27
+ To upgrade from < 0.4.0, you need to make two small changes:
28
+ 1. `Whiplash::App` must now be instantiated.
29
+ 2. Tokens are **not** automatically refreshed
30
+
31
+ Before:
32
+ ```ruby
33
+ api = Whiplash::App
34
+ ```
35
+
36
+ After:
37
+ ```ruby
38
+ api = Whiplash::App.new
39
+ api.refresh_token! # Since you don't have one yet
40
+ api.token # Confirm you've got a token
41
+ . . .
42
+ api.refresh_token! if api.token_expired?
43
+ ```
44
+
45
+ ### Authentication
26
46
  In order to authenticate, make sure the following `ENV` vars are set:
27
47
 
28
48
  ```ruby
@@ -30,85 +50,90 @@ ENV["WHIPLASH_CLIENT_ID"]
30
50
  ENV["WHIPLASH_CLIENT_SECRET"]
31
51
  ENV["WHIPLASH_CLIENT_SCOPE"]
32
52
  ```
33
- Once those are set, authentication is handled in app. While authentication is
34
- mostly baked into the calls, there is a method that allows for getting the app's
35
- token from the main app: `Whiplash::App.token`.
53
+
54
+ Once those are set, authentication is handled in app.
55
+
56
+ ### Oauth Client Credentials
57
+ You can authenticate using Oauth Client Credentials (i.e. auth an entire app).
58
+ You probably want this for apps that work offline, _on behalf_ of users or customers, or that don't work at the user/customer-level at all.
59
+
60
+ ```ruby
61
+ api = Whiplash::App.new
62
+ api.refresh_token! # Since you don't have one yet
63
+ api.token # Confirm you've got a token
64
+ ```
65
+
66
+ ### Oauth Authorization Code
67
+ You can also authenticate using Oauth Authorization Code (i.e. auth an individual user). This is most common for user-facing app's with a front end.
68
+
69
+ ```ruby
70
+ # Authenticate using Devise Omniauthenticateable strategy; you'll get oauth creds back as a hash
71
+ api = Whiplash::App.new(oauth_credentials_hash)
72
+ api.token # Confirm you've got a token
73
+ ```
36
74
 
37
75
  ### API URL
38
76
  In order to set your api url, you can use the following environment URL:
39
77
  ```
40
78
  ENV["WHIPLASH_API_URL"]
41
79
  ```
42
- If it isn't set, then the API URL defaults to either `https://testing.whiplashmerch.com` (test or dev environment) or `https://www.whiplashmerch.com` (prod environment).
80
+ If it isn't set, then the API URL defaults to either `https://sandbox.getwhiplash.com` (test or dev environment) or `https://www.getwhiplash.com` (prod environment).
81
+
82
+ ### Sending Customer ID and Shop ID headers
83
+ You can send the headers in `headers` array, like `{customer_id: 123, shop_id: 111}`.
84
+ Alternatively, you can set them on instantiation like `Whiplash::App.new(token, {customer_id: 123, shop_id: 111})`.
43
85
 
44
- ###Rails AR type calls
86
+ ### Rails AR type calls
45
87
 
46
88
  In order to make the use of the gem seem more "AR-ish", we've added AR oriented methods that can be used for basic object creation/deletion/updating/viewing. The basic gist of these AR style CRUD methods is that they will all follow the same pattern. If you are performing a collection action, such as `create` or `find`, the pattern is this:
47
89
 
48
90
  ```ruby
49
- Whiplash::App.create(resource, params, headers)
91
+ api.create(resource, params, headers)
50
92
  ```
51
93
 
52
94
  For member actions, such as `show`, or `destroy` methods, the pattern is this:
53
95
 
54
96
  ```ruby
55
- Whiplash::App.find(resource, id, headers)
56
- Whiplash::App.destroy(resource, id, headers)
97
+ api.find(resource, id, headers)
98
+ api.destroy(resource, id, headers)
57
99
  ```
58
100
 
59
101
  Finally, for `update` calls, it's a mixture of those:
60
102
 
61
103
  ```ruby
62
- Whiplash::App.update(resource, id, params_to_update, headers)
104
+ api.update(resource, id, params_to_update, headers)
63
105
  ```
64
106
 
65
107
  So, basic AR style calls can be performed like so:
66
108
 
67
109
  ```ruby
68
- Whiplash::App.find_all('orders', {}, { customer_id: 187 })
69
- Whiplash::App.find('orders', 1)
70
- Whiplash::App.create('orders', { key: "value", key2: "value" }, { customer_id: 187 } )
71
- Whiplash::App.update('orders', 1, { key: "value"}, { customer_id: 187 } )
72
- Whiplash::App.destroy('orders', 1, { customer_id: 187 } )
73
- Whiplash::App.count('customers') #unlike other calls, which return Faraday responses, this call returns an integer.
110
+ api.find_all('orders', {}, { customer_id: 187 })
111
+ api.find('orders', 1)
112
+ api.create('orders', { key: "value", key2: "value" }, { customer_id: 187 } )
113
+ api.update('orders', 1, { key: "value"}, { customer_id: 187 } )
114
+ api.destroy('orders', 1, { customer_id: 187 } )
115
+ api.count('customers')
74
116
  ```
75
117
 
76
- ###CRUD Wrapper methods
118
+ ### CRUD Wrapper methods
77
119
  In reality, all of these methods are simply wrapper methods around simple `GET/POST/PUT/DELETE` wrappers on Faraday, so if you want to get more granular,you can also make calls that simply reference the lower level REST verb:
78
120
 
79
121
  ```ruby
80
- Whiplash::App.get('orders', {}, {})
122
+ api.get('orders')
81
123
  ```
82
124
  Which will return all orders and roughly correspond to an index call. If you need to use `Whiplash::App` for nonRESTful calls, simply drop the full endpoint in as your first argument:
83
125
 
84
126
  ```ruby
85
- Whiplash::App.get('orders/non_restful_action', {}, {})
127
+ api.get('orders/non_restful_action', {}, {})
86
128
  ```
87
129
  `POST`, `PUT`, and `DELETE` calls can be performed in much the same way:
88
130
  ```ruby
89
- Whiplash::App.post(endpoint, params, headers) #POST request to the specified endpoint passing the payload in params
131
+ api.post(endpoint, params, headers) # POST request to the specified endpoint passing the payload in params
132
+ api.put(endpoint, params, headers) # PUT request to the specified endpoint passing the payload in params
133
+ api.delete(endpoint, params, headers) # DELETE request to the specified endpoint. Params would probably just be an id.
90
134
  ```
91
- ```ruby
92
- Whiplash::App.put(endpoint, params, headers) #PUT request to the specified endpoint passing the payload in params
93
- ```
94
- ```ruby
95
- Whiplash::App.delete(endpoint, params, headers) #DELETE request to the specified endpoint. Params would probably just be an id.
96
- ```
97
-
98
- *IMPORTANT!!!! PLEASE READ!*
99
- It's best if possible to use the AR style methods. They hide a lot of issues with the way Faraday handles params. For example, this should, in theory, work:
100
- ```ruby
101
- Whiplash::App.get('orders', {id: 1}, {customer_id: 187})
102
- ```
103
- BUT, due to the way Faraday handles params, this would not, as expected, route to `orders#show` in the Whiplash App, but would instead route to `orders#index`, so it wouldn't return the expected singular order with an ID of 1, but all orders for that customer.
104
-
105
- The gem works best when the base CRUD methods are used ONLY for situations where a singular endpoint can't be reached, such as a cancel action, or uncancel, which would have to be done like so:
106
- ```ruby
107
- Whiplash::App.post('orders/1/cancel', {}, {customer_id: 187})
108
- ```
109
-
110
135
 
111
- ###Signing and Verifying.
136
+ ### Signing and Verifying.
112
137
  `whiplash-app` supports signing and verifying signatures like so:
113
138
  ```ruby
114
139
  Whiplash::App.signature(request_body)
@@ -118,10 +143,9 @@ and verifications are done like so:
118
143
  Whiplash::App.verified?(request)
119
144
  ```
120
145
 
121
- ###Caching
122
- `whiplash-app` is Cache agnostic, relying on the `moneta` gem to provide that
123
- interface. However, if you intend to specify `REDIS` as your key-value store of
124
- choice, it's dead simple. Simply declare the following variables:
146
+ ### Caching
147
+ `whiplash-app` is Cache agnostic, relying on the `moneta` gem to provide a local store, if needed.
148
+ However, if you intend to specify `REDIS` as your key-value store of choice, it's dead simple. Simply declare the following variables:
125
149
  ```
126
150
  ENV["REDIS_HOST"]
127
151
  ENV["REDIS_PORT"]
@@ -130,6 +154,19 @@ ENV["REDIS_NAMESPACE"]
130
154
  ```
131
155
  If those are provided, `moneta` will use your redis connection and will namespace your cache storage under the redis namespace. By default, if you do not declare a `REDIS_NAMESPACE` value, the app will default to the `WHIPLASH_CLIENT_ID`.
132
156
 
157
+ **For user-facing apps, best practice is to store the `oauth_credentials_hash` in a session variable.**
158
+
159
+ ### Gotchas
160
+ Due to the way Faraday handles params, this would not, as expected, route to `orders#show` in the Whiplash App, but would instead route to `orders#index`, so it wouldn't return the expected singular order with an ID of 1, but all orders for that customer.
161
+ ```ruby
162
+ api.get('orders', {id: 1}, {customer_id: 187})
163
+ ```
164
+ Instead, you'd want to do:
165
+ ```ruby
166
+ api.get('orders/1', {}, {customer_id: 187})
167
+ ```
168
+
169
+
133
170
  ## Development
134
171
 
135
172
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module ApiConfig
4
4
 
5
5
  def api_url
@@ -1,7 +1,7 @@
1
1
  require "moneta"
2
2
  require "whiplash/app/moneta/namespace"
3
3
  module Whiplash
4
- module App
4
+ class App
5
5
  module Caching
6
6
 
7
7
  def cache_store
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module Connections
4
4
 
5
5
  def app_request(options = {})
@@ -8,6 +8,9 @@ module Whiplash
8
8
  else
9
9
  endpoint = options[:endpoint]
10
10
  end
11
+ options[:headers] ||= {}
12
+ options[:headers][:customer_id] ||= customer_id if customer_id
13
+ options[:headers][:shop_id] ||= shop_id if shop_id
11
14
  connection.send(options[:method],
12
15
  endpoint,
13
16
  options[:params],
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module FinderMethods
4
4
 
5
5
  def count(resource, params = {}, headers = nil)
@@ -1,11 +1,6 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module Signing
4
-
5
- def request_body(body)
6
- body.blank? ? ENV["WHIPLASH_CLIENT_ID"] : body
7
- end
8
-
9
4
  def signature(body)
10
5
  sha256 = OpenSSL::Digest::SHA256.new
11
6
  OpenSSL::HMAC.hexdigest(sha256,
@@ -17,6 +12,11 @@ module Whiplash
17
12
  request.headers["X-WHIPLASH-SIGNATURE"] == signature(body)
18
13
  end
19
14
 
15
+ private
16
+
17
+ def request_body(body)
18
+ body.blank? ? ENV["WHIPLASH_CLIENT_ID"] : body
19
+ end
20
20
  end
21
21
  end
22
22
  end
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
3
- VERSION = "0.3.1"
2
+ class App
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
data/lib/whiplash/app.rb CHANGED
@@ -8,42 +8,71 @@ require "oauth2"
8
8
  require "faraday_middleware"
9
9
 
10
10
  module Whiplash
11
+ class App
12
+ include Whiplash::App::ApiConfig
13
+ include Whiplash::App::Caching
14
+ include Whiplash::App::Connections
15
+ include Whiplash::App::FinderMethods
16
+ extend Whiplash::App::Signing
11
17
 
12
- module App
13
- class << self
14
- include Whiplash::App::ApiConfig
15
- include Whiplash::App::Caching
16
- include Whiplash::App::Connections
17
- include Whiplash::App::FinderMethods
18
- include Whiplash::App::Signing
18
+ attr_accessor :customer_id, :shop_id, :token
19
19
 
20
- attr_accessor :customer_id, :shop_id
20
+ def initialize(token=nil, options={})
21
+ opts = options.with_indifferent_access
22
+ @token = format_token(token) unless token.nil?
23
+ @customer_id = options[:customer_id]
24
+ @shop_id = options[:shop_id]
25
+ end
21
26
 
22
- def client
23
- OAuth2::Client.new(ENV["WHIPLASH_CLIENT_ID"], ENV["WHIPLASH_CLIENT_SECRET"], site: api_url)
24
- end
27
+ def client
28
+ OAuth2::Client.new(ENV["WHIPLASH_CLIENT_ID"], ENV["WHIPLASH_CLIENT_SECRET"], site: api_url)
29
+ end
25
30
 
26
- def connection
27
- out = Faraday.new [api_url, "api/v2"].join("/") do |conn|
28
- conn.request :oauth2, token, token_type: "bearer"
29
- conn.request :json
30
- conn.response :json, :content_type => /\bjson$/
31
- conn.use :instrumentation
32
- conn.adapter Faraday.default_adapter
33
- end
34
- return out
31
+ def connection
32
+ out = Faraday.new [api_url, "api/v2"].join("/") do |conn|
33
+ conn.request :oauth2, token.token, token_type: "bearer"
34
+ conn.request :json
35
+ conn.response :json, :content_type => /\bjson$/
36
+ conn.use :instrumentation
37
+ conn.adapter Faraday.default_adapter
35
38
  end
39
+ return out
40
+ end
41
+
42
+ def token=(oauth_token)
43
+ instance_variable_set("@token", format_token(oauth_token))
44
+ end
36
45
 
37
- def refresh_token!
38
- oauth_token = client.client_credentials.get_token(scope: ENV["WHIPLASH_CLIENT_SCOPE"])
39
- new_token = oauth_token.token
46
+ def refresh_token!
47
+ if token.blank? # If we're storing locally, grab a new token and cache it
48
+ access_token = client.client_credentials.get_token(scope: ENV["WHIPLASH_CLIENT_SCOPE"])
49
+ new_token = access_token.to_hash
40
50
  cache_store["whiplash_api_token"] = new_token
51
+ else
52
+ access_token = token.refresh!
41
53
  end
54
+ self.token = access_token
55
+ end
56
+
57
+ def token_expired?
58
+ return token.expired? unless token.blank?
59
+ return true unless cache_store.has_key?("whiplash_api_token")
60
+ return true if cache_store["whiplash_api_token"].blank?
61
+ false
62
+ end
42
63
 
43
- def token
44
- refresh_token! unless cache_store["whiplash_api_token"]
45
- return cache_store["whiplash_api_token"]
64
+ private
65
+ def format_token(oauth_token)
66
+ unless oauth_token.is_a?(OAuth2::AccessToken)
67
+ raise StandardError, "Token must either be a Hash or an OAuth2::AccessToken" unless oauth_token.is_a?(Hash)
68
+ oauth_token['expires'] = oauth_token['expires'].to_s # from_hash expects 'true'
69
+ if oauth_token.has_key?('token')
70
+ oauth_token['access_token'] = oauth_token['token']
71
+ oauth_token.delete('token')
72
+ end
73
+ oauth_token = OAuth2::AccessToken.from_hash(client, oauth_token)
46
74
  end
47
75
  end
76
+
48
77
  end
49
78
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whiplash-app
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Don Sullivan, Mark Dickson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-17 00:00:00.000000000 Z
11
+ date: 2017-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oauth2
@@ -138,10 +138,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
138
  version: '0'
139
139
  requirements: []
140
140
  rubyforge_project:
141
- rubygems_version: 2.4.5.1
141
+ rubygems_version: 2.6.11
142
142
  signing_key:
143
143
  specification_version: 4
144
144
  summary: this gem provides connectivity to the Whiplash API for authentication and
145
145
  REST requests.
146
146
  test_files: []
147
- has_rdoc: