amorail 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rspec.yml +23 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +3 -0
  5. data/CHANGELOG.md +19 -0
  6. data/README.md +49 -8
  7. data/RELEASING.md +43 -0
  8. data/amorail.gemspec +5 -3
  9. data/lib/amorail.rb +27 -6
  10. data/lib/amorail/access_token.rb +44 -0
  11. data/lib/amorail/client.rb +84 -23
  12. data/lib/amorail/config.rb +14 -8
  13. data/lib/amorail/entities/company.rb +2 -0
  14. data/lib/amorail/entities/contact.rb +3 -0
  15. data/lib/amorail/entities/contact_link.rb +2 -0
  16. data/lib/amorail/entities/elementable.rb +4 -2
  17. data/lib/amorail/entities/lead.rb +3 -0
  18. data/lib/amorail/entities/leadable.rb +3 -0
  19. data/lib/amorail/entities/note.rb +2 -0
  20. data/lib/amorail/entities/task.rb +2 -0
  21. data/lib/amorail/entities/webhook.rb +44 -0
  22. data/lib/amorail/entity.rb +17 -3
  23. data/lib/amorail/entity/finders.rb +5 -2
  24. data/lib/amorail/entity/params.rb +3 -2
  25. data/lib/amorail/entity/persistence.rb +5 -0
  26. data/lib/amorail/exceptions.rb +2 -0
  27. data/lib/amorail/property.rb +7 -3
  28. data/lib/amorail/railtie.rb +3 -1
  29. data/lib/amorail/store_adapters.rb +15 -0
  30. data/lib/amorail/store_adapters/abstract_store_adapter.rb +23 -0
  31. data/lib/amorail/store_adapters/memory_store_adapter.rb +50 -0
  32. data/lib/amorail/store_adapters/redis_store_adapter.rb +83 -0
  33. data/lib/amorail/version.rb +3 -1
  34. data/lib/tasks/amorail.rake +2 -0
  35. data/spec/access_token_spec.rb +59 -0
  36. data/spec/client_spec.rb +36 -24
  37. data/spec/company_spec.rb +2 -0
  38. data/spec/contact_link_spec.rb +2 -0
  39. data/spec/contact_spec.rb +2 -0
  40. data/spec/entity_spec.rb +2 -0
  41. data/spec/fixtures/amorail_test.yml +5 -3
  42. data/spec/fixtures/authorize.json +6 -0
  43. data/spec/fixtures/webhooks/list.json +24 -0
  44. data/spec/fixtures/webhooks/subscribe.json +17 -0
  45. data/spec/fixtures/webhooks/unsubscribe.json +17 -0
  46. data/spec/helpers/webmock_helpers.rb +80 -13
  47. data/spec/lead_spec.rb +2 -0
  48. data/spec/my_contact_spec.rb +2 -0
  49. data/spec/note_spec.rb +2 -0
  50. data/spec/property_spec.rb +2 -0
  51. data/spec/spec_helper.rb +4 -2
  52. data/spec/store_adapters/memory_store_adapter_spec.rb +56 -0
  53. data/spec/store_adapters/redis_store_adapter_spec.rb +67 -0
  54. data/spec/support/elementable_example.rb +2 -0
  55. data/spec/support/entity_class_example.rb +2 -0
  56. data/spec/support/leadable_example.rb +2 -0
  57. data/spec/support/my_contact.rb +2 -0
  58. data/spec/support/my_entity.rb +2 -0
  59. data/spec/task_spec.rb +2 -0
  60. data/spec/webhook_spec.rb +61 -0
  61. metadata +48 -17
  62. data/.travis.yml +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 42a2851b1c9adb5f49a331d622c3fd276c63761b
4
- data.tar.gz: 8012dce427d24125bfeb3a10059033c577bcbd5b
2
+ SHA256:
3
+ metadata.gz: 3b18605e272eb7a0c72af69e2d017f54753ff715d474d79cc9071791a036cd6d
4
+ data.tar.gz: d77fee259b877786fd18a044aaa52aff432be6936834095b5d8ab2d857d0df11
5
5
  SHA512:
6
- metadata.gz: a102ba6296a1e3ed6ab4f2c4160415429f351398e83240d0d2453a72c6302eefaf83c42624b43f037e00fb7dbc40f669f37ea27bd32d6311fe5d250a5b0dc248
7
- data.tar.gz: b7214099abe616f5902717c4f823cabab5e818e84752cf649330cf70191c8b0b6488495f1b4e26fd7afcc27c5712d2431ef728356daefd5fd53a6d03b6b3af2b
6
+ metadata.gz: ab1e1221b40f406909ed76f4b444b7f7f7805d8f04c1e386030563471172175149ff718438bb4ee97d9bee28f2a3d9b9a78733828f8ab2d7b62edca6f631f56e
7
+ data.tar.gz: 4034fbcc9fea9004e159e62ac910ce9bfbe301ea76013f7d62d05a26cae8df2084c2836a4e4c65707cc4451fc8380fc163c5afe580068bdb8c4a40f79f632ff3
@@ -0,0 +1,23 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+
9
+ jobs:
10
+ build:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - name: Set up Ruby 2.5
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: 2.5
20
+ bundler-cache: true
21
+ bundler: 1.13.6
22
+ - name: Build and test
23
+ run: bundle exec rspec
data/.gitignore CHANGED
@@ -12,4 +12,5 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
- *.gem
15
+ *.gem
16
+ .idea
data/.rubocop.yml CHANGED
@@ -56,3 +56,6 @@ Style/SignalException:
56
56
 
57
57
  Layout/MultilineMethodCallBraceLayout:
58
58
  Enabled: false
59
+
60
+ Lint/MissingCopEnableDirective:
61
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Change log
2
+
3
+ ## master (unreleased)
4
+
5
+ ## 0.7.0 (2021-07-16)
6
+
7
+ ### Features
8
+
9
+ - Introduce [#53](https://github.com/teachbase/amorail/issues/48) Implement OAuth authentication ([@lHydra][])
10
+
11
+ ### Changes
12
+
13
+ - increased ruby version to >= 2.5.0
14
+ - updates dependencies versions
15
+
16
+
17
+ [@palkan]: https://github.com/palkan
18
+ [@AlexanderShvaykin]: https://github.com/AlexanderShvaykin
19
+ [@lHydra]: https://github.com/lHydra
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Gem Version](https://badge.fury.io/rb/amorail.svg)](https://rubygems.org/gems/amorail) [![Build Status](https://travis-ci.org/teachbase/amorail.svg?branch=master)](https://travis-ci.org/teachbase/amorail)
1
+ [![Gem Version](https://badge.fury.io/rb/amorail.svg)](https://rubygems.org/gems/amorail) ![Build](https://github.com/teachbase/amorail/workflows/Build/badge.svg)
2
2
 
3
3
  # Amorail
4
4
 
@@ -25,12 +25,48 @@ Or install it yourself as:
25
25
  With Amorail you can manipulate the following AmoCRM entities: Companies, Contacts, Leads and Tasks.
26
26
  We're triying to build simple AR-like interface.
27
27
 
28
+ ### Store configuration
29
+
30
+ In order to configure a token store you should set up a store adapter in a following way: `Amorail.token_store = :redis, { redis_url: 'redis://127.0.0.1:6379/0' }` (options can be omitted). Currently supported stores are `:redis` and `:memory`. Memory adapter is used **by default**.
31
+
32
+ Here is a default configuration for Redis:
33
+
34
+ ```ruby
35
+ Amorail.token_store = :redis, {
36
+ redis_host: "127.0.0.1",
37
+ redis_port: "6379",
38
+ redis_db_name: "0"
39
+ }
40
+ ```
41
+
42
+ You can also provide a Redis URL instead:
43
+
44
+ ```ruby
45
+ Amorail.token_store = :redis, { redis_url: "redis://localhost:6397" }
46
+ ```
47
+
48
+ **NOTE**: if `REDIS_URL` environment variable is set it is used automatically.
49
+
50
+ ### Add custom store
51
+
52
+ To add custom store you need declare a class that implements the interface `AbstractStoreAdapter`.
53
+ For example `class FileStoreAdapter < Amorail::StoreAdapters::AbstractStoreAdapter`
54
+
55
+ The class must contain constructor `initialize(**options)` and **4 required methods**:
56
+
57
+ 1. `fetch_access` — method that should return Hash with token data (**required keys:** `token`, `refresh_token` and `expiration`) or empty Hash (`{}`) if no value was received
58
+ 2. `persist_access` — method that stores data in storage
59
+ 3. `update_access` — method that updates existed token data in storage
60
+ 4. `access_expired?` — method that returns `true` if token was expired otherwise `false`
61
+
28
62
  ### Auth configuration
29
63
 
30
64
  Amorail uses [anyway_config](https://github.com/palkan/anyway_config) for configuration, so you
31
65
  can provide configuration parameters through env vars, seperate config file (`config/amorail.yml`) or `secrets.yml`.
32
66
 
33
- Required params: **usermail**, **api_key** and **api_endpoint**.
67
+ Required params: **client_id**, **client_secret** **code**, **redirect_uri** and **api_endpoint**.
68
+
69
+ An authorization **code** is required for the initial obtaining of a pair of access and refresh tokens. You can see it in the interface or through a Redirect URI if the authorization was run from the modal window for permissions. The lifespan of the code is 20 minutes. [More details](https://www.amocrm.com/developers/content/oauth/oauth/)
34
70
 
35
71
  Example:
36
72
 
@@ -39,9 +75,14 @@ Example:
39
75
  development:
40
76
  ...
41
77
  amorail:
42
- usermail: 'amorail@test.com'
43
- api_key: '75742b166417fe32ae132282ce178cf6'
44
- api_endpoint: 'https://test.amocrm.ru'
78
+ client_id: c0df457d-eacc-47cc-behb-3d8f962g4lbf
79
+ client_secret: a36b564b64398d3e53004c12e4997eb340e32b18ee185389ddb409292ebc5ebae297a3eab96be4a9d38ecbf274d90bbb54a7e8f282f40d1b29e5c9b2e2e357a6
80
+ code: a911ff963f58ea6c846901056114d37a14d2efa4d05ffb6ef0a8d60d32e5d6dae785bd317cbc9b0bd04261cb0cf9905af0cc32b5567c1eb84433328d08888f5c613608b822c1928272769ffd284b
81
+ redirect_uri: https://example.ru
82
+ api_endpoint: https://test.amocrm.ru
83
+ redis_host: 127.0.0.1
84
+ redis_port: 6379
85
+ redis_db_name: 0
45
86
  ```
46
87
 
47
88
  ### Running from console
@@ -50,7 +91,7 @@ You can try amorail in action from console ([PRY](https://github.com/pry/pry) is
50
91
 
51
92
  ```shell
52
93
  # amorail gem directory
53
- AMORAIL_USERMAIL=my_mail@test.com AMORAIL_API_KEY=my_key AMORAIL_API_ENDPOINT=my@amo.com bundle exec rake console
94
+ AMORAIL_CLIENT_ID=integration_id AMORAIL_CLIENT_SECRET=secret_key AMORAIL_CODE=my_code AMORAIL_REDIRECT_URI=https://example.com AMORAIL_API_ENDPOINT=https://test.amocrm.ru bundle exec rake console
54
95
  pry> Amorail.properties
55
96
  # ... prints properties (custom_fields) data
56
97
  pry> Amorail::Contact.find_by_query("test_contact")
@@ -223,12 +264,12 @@ It is possible to use Amorail with multiple AmoCRM accounts. To do so use `Amora
223
264
  which receive client params or client instance and a block to execute within custom context:
224
265
 
225
266
  ```ruby
226
- Amorail.with_client(usermail: "custom@mail.com", api_endpoint: "https://my.acmocrm.ru", api_key: "my_secret_key") do
267
+ Amorail.with_client(client_id: "my_id", client_secret: "my_secret", code: "my_code", api_endpoint: "https://my.acmocrm.ru", redirect_uri: "https://example.com") do
227
268
  # Client specific code here
228
269
  end
229
270
 
230
271
  # or using client instance
231
- my_client = Amorail::Client.new(usermail: "custom@mail.com", api_endpoint: "https://my.acmocrm.ru", api_key: "my_secret_key")
272
+ my_client = Amorail::Client.new(client_id: "my_id", client_secret: "my_secret", code: "my_code", api_endpoint: "https://my.acmocrm.ru", redirect_uri: "https://example.com")
232
273
 
233
274
  Amorail.with_client(client) do
234
275
  ...
data/RELEASING.md ADDED
@@ -0,0 +1,43 @@
1
+ # How to release a gem
2
+
3
+ This document describes a process of releasing a new version of a gem.
4
+
5
+ 1. Bump version.
6
+
7
+ ```sh
8
+ git commit -m "Bump 1.<x>.<y>"
9
+ ```
10
+
11
+ We're (kinda) using semantic versioning:
12
+
13
+ - Bugfixes should be released as fast as possible as patch versions.
14
+ - New features could be combined and released as minor or patch version upgrades (depending on the _size of the feature_—it's up to maintainers to decide).
15
+ - Breaking API changes should be avoided in minor and patch releases.
16
+ - Breaking dependencies changes (e.g., dropping older Ruby support) could be released in minor versions.
17
+
18
+ How to bump a version:
19
+
20
+ - Change the version number in `lib/amorail/version.rb` file.
21
+ - Update the changelog (add new heading with the version name and date).
22
+ - Update the installation documentation if necessary (e.g., during minor and major updates).
23
+
24
+ 2. Push code to GitHub and make sure CI passes.
25
+
26
+ ```sh
27
+ git push
28
+ ```
29
+
30
+ 3. Release a gem.
31
+
32
+ ```sh
33
+ gem release -t
34
+ git push --tags
35
+ ```
36
+
37
+ We use [gem-release](https://github.com/svenfuchs/gem-release) for publishing gems with a single command:
38
+
39
+ ```sh
40
+ gem release -t
41
+ ```
42
+
43
+ Don't forget to push tags and write release notes on GitHub (if necessary).
data/amorail.gemspec CHANGED
@@ -12,6 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = %q{Ruby API client for AmoCRM. You can integrate your system with it.}
13
13
  spec.homepage = ""
14
14
  spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0")
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -25,9 +26,10 @@ Gem::Specification.new do |spec|
25
26
  spec.add_development_dependency "pry"
26
27
  spec.add_development_dependency "shoulda-matchers", "~> 2.0"
27
28
  spec.add_development_dependency "rubocop", "~> 0.49"
28
- spec.add_dependency "anyway_config", "~> 0", ">= 0.3"
29
+ spec.add_dependency "anyway_config", ">= 1.0"
29
30
  spec.add_dependency "faraday"
30
31
  spec.add_dependency "faraday_middleware"
31
- spec.add_dependency 'activemodel'
32
- spec.add_dependency 'json'
32
+ spec.add_dependency "activemodel"
33
+ spec.add_dependency "json"
34
+ spec.add_dependency "redis"
33
35
  end
data/lib/amorail.rb CHANGED
@@ -1,37 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amorail/version'
2
4
  require 'amorail/config'
3
5
  require 'amorail/client'
4
6
  require 'amorail/exceptions'
5
7
  require 'amorail/entity'
6
8
  require 'amorail/property'
9
+ require 'amorail/access_token'
10
+ require 'amorail/store_adapters'
7
11
 
8
12
  Gem.find_files('amorail/entities/*.rb').each { |path| require path }
9
13
 
10
14
  # AmoCRM API integration.
11
15
  # https://www.amocrm.com/
12
16
  module Amorail
13
- def self.config
17
+ extend self
18
+
19
+ def config
14
20
  @config ||= Config.new
15
21
  end
16
22
 
17
- def self.properties
23
+ def properties
18
24
  client.properties
19
25
  end
20
26
 
21
- def self.configure
27
+ def configure
22
28
  yield(config) if block_given?
23
29
  end
24
30
 
25
- def self.client
31
+ def client
26
32
  ClientRegistry.client || (@client ||= Client.new)
27
33
  end
28
34
 
29
- def self.reset
35
+ def reset
30
36
  @config = nil
31
37
  @client = nil
32
38
  end
33
39
 
34
- def self.with_client(client)
40
+ def with_client(client)
35
41
  client = Client.new(client) unless client.is_a?(Client)
36
42
  ClientRegistry.client = client
37
43
  yield
@@ -39,6 +45,21 @@ module Amorail
39
45
  ClientRegistry.client = nil
40
46
  end
41
47
 
48
+ def token_store=(args)
49
+ adapter, options = Array(args)
50
+ @token_store = StoreAdapters.build_by_name(adapter, options)
51
+ rescue NameError => e
52
+ raise e.class, "Token store adapter for :#{adapter} haven't been found", e.backtrace
53
+ end
54
+
55
+ def token_store
56
+ unless instance_variable_defined?(:@token_store)
57
+ self.token_store = :memory
58
+ end
59
+
60
+ @token_store
61
+ end
62
+
42
63
  class ClientRegistry # :nodoc:
43
64
  extend ActiveSupport::PerThreadRegistry
44
65
 
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amorail
4
+ class AccessToken
5
+ attr_reader :token, :secret, :expiration, :refresh_token, :store
6
+
7
+ def initialize(secret, token, refresh_token, expiration, store)
8
+ @secret = secret
9
+ @token = token
10
+ @refresh_token = refresh_token
11
+ @expiration = expiration
12
+ @store = store
13
+ end
14
+
15
+ def expired?
16
+ store.access_expired?(secret)
17
+ end
18
+
19
+ class << self
20
+ def create(secret, token, refresh_token, expiration, store)
21
+ new(secret, token, refresh_token, expiration, store).tap do |access_token|
22
+ store.persist_access(access_token.secret, access_token.token, access_token.refresh_token, access_token.expiration)
23
+ end
24
+ end
25
+
26
+ def find(secret, store)
27
+ token_attrs = store.fetch_access(secret)
28
+ build_with_token_attrs(store, secret, token_attrs)
29
+ end
30
+
31
+ def refresh(secret, token, refresh_token, expiration, store)
32
+ new(secret, token, refresh_token, expiration, store).tap do |access_token|
33
+ store.update_access(access_token.secret, access_token.token, access_token.refresh_token, access_token.expiration)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def build_with_token_attrs(store, secret, token_attrs)
40
+ new(secret, token_attrs[:token], token_attrs[:refresh_token], token_attrs[:expiration], store)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
  require 'faraday_middleware'
3
5
  require 'json'
@@ -8,14 +10,31 @@ module Amorail
8
10
  class Client
9
11
  SUCCESS_STATUS_CODES = [200, 204].freeze
10
12
 
11
- attr_reader :usermail, :api_key, :api_endpoint
13
+ attr_accessor :store
14
+ attr_reader :api_endpoint,
15
+ :access_token,
16
+ :refresh_token,
17
+ :access,
18
+ :client_id,
19
+ :client_secret,
20
+ :code,
21
+ :redirect_uri
12
22
 
13
23
  def initialize(api_endpoint: Amorail.config.api_endpoint,
14
- api_key: Amorail.config.api_key,
15
- usermail: Amorail.config.usermail)
24
+ client_id: Amorail.config.client_id,
25
+ client_secret: Amorail.config.client_secret,
26
+ code: Amorail.config.code,
27
+ redirect_uri: Amorail.config.redirect_uri)
28
+ @store = Amorail.token_store
16
29
  @api_endpoint = api_endpoint
17
- @api_key = api_key
18
- @usermail = usermail
30
+ @client_id = client_id
31
+ @client_secret = client_secret
32
+ @code = code
33
+ @redirect_uri = redirect_uri
34
+ @access = AccessToken.find(@client_secret, store)
35
+ @access_token = @access.token
36
+ @refresh_token = @access.refresh_token
37
+
19
38
  @connect = Faraday.new(url: api_endpoint) do |faraday|
20
39
  faraday.response :json, content_type: /\bjson$/
21
40
  faraday.use :instrumentation
@@ -32,33 +51,53 @@ module Amorail
32
51
  end
33
52
 
34
53
  def authorize
35
- self.cookies = nil
36
- response = post(
37
- Amorail.config.auth_url,
38
- 'USER_LOGIN' => usermail,
39
- 'USER_HASH' => api_key
40
- )
41
- cookie_handler(response)
54
+ response = post(Amorail.config.auth_url, auth_params)
55
+ create_access_token(response)
56
+ response
57
+ end
58
+
59
+ def refresh_token!
60
+ response = post(Amorail.config.auth_url, refresh_params)
61
+ update_access_token(response)
42
62
  response
43
63
  end
44
64
 
65
+ def auth_params
66
+ {
67
+ client_id: client_id,
68
+ client_secret: client_secret,
69
+ grant_type: 'authorization_code',
70
+ code: @code,
71
+ redirect_uri: redirect_uri
72
+ }
73
+ end
74
+
75
+ def refresh_params
76
+ {
77
+ client_id: client_id,
78
+ client_secret: client_secret,
79
+ grant_type: 'refresh_token',
80
+ refresh_token: refresh_token,
81
+ redirect_uri: redirect_uri
82
+ }
83
+ end
84
+
45
85
  def safe_request(method, url, params = {})
46
- public_send(method, url, params)
47
- rescue ::Amorail::AmoUnauthorizedError
48
- authorize
86
+ authorize if access_token.blank?
87
+ refresh_token! if access.expired?
49
88
  public_send(method, url, params)
50
89
  end
51
90
 
52
91
  def get(url, params = {})
53
92
  response = connect.get(url, params) do |request|
54
- request.headers['Cookie'] = cookies if cookies.present?
93
+ request.headers['Authorization'] = "Bearer #{access_token}" if access_token.present?
55
94
  end
56
95
  handle_response(response)
57
96
  end
58
97
 
59
98
  def post(url, params = {})
60
99
  response = connect.post(url) do |request|
61
- request.headers['Cookie'] = cookies if cookies.present?
100
+ request.headers['Authorization'] = "Bearer #{access_token}" if access_token.present?
62
101
  request.headers['Content-Type'] = 'application/json'
63
102
  request.body = params.to_json
64
103
  end
@@ -67,12 +106,6 @@ module Amorail
67
106
 
68
107
  private
69
108
 
70
- attr_accessor :cookies
71
-
72
- def cookie_handler(response)
73
- self.cookies = response.headers['set-cookie'].split('; ')[0]
74
- end
75
-
76
109
  def handle_response(response) # rubocop:disable all
77
110
  return response if SUCCESS_STATUS_CODES.include?(response.status)
78
111
 
@@ -97,5 +130,33 @@ module Amorail
97
130
  fail ::Amorail::AmoUnknownError, response.body
98
131
  end
99
132
  end
133
+
134
+ def create_access_token(response)
135
+ _access = AccessToken.create(
136
+ client_secret,
137
+ response.body['access_token'],
138
+ response.body['refresh_token'],
139
+ expiration(response.body['expires_in']),
140
+ store
141
+ )
142
+ @access_token = _access.token
143
+ @refresh_token = _access.refresh_token
144
+ end
145
+
146
+ def update_access_token(response)
147
+ _access = AccessToken.refresh(
148
+ client_secret,
149
+ response.body['access_token'],
150
+ response.body['refresh_token'],
151
+ expiration(response.body['expires_in']),
152
+ store
153
+ )
154
+ @access_token = _access.token
155
+ @refresh_token = _access.refresh_token
156
+ end
157
+
158
+ def expiration(expires_in)
159
+ Time.now.to_i + expires_in.to_i
160
+ end
100
161
  end
101
162
  end