globus_client 0.12.1 → 0.13.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
  SHA256:
3
- metadata.gz: 65d27626412e2eff8ff6514be3ece81fd5f27e471137fcfd8409e51cd6e0f105
4
- data.tar.gz: 321b1d89734daacb8ad54487836a68f1fdf1af931fad426d72ec35bc2f355ec8
3
+ metadata.gz: 9129e55c5e5e6f4f62fc0d8912eca9310eff036e1c0095d24cae07367947054c
4
+ data.tar.gz: 1265f2f6c1b68bb6d76634f7bd636ed920b1095fd3bfd5ab6702525d994bf295
5
5
  SHA512:
6
- metadata.gz: a4ce82aac85e8c00df5310cdb3b4f821cd1415ee0d525938562b20d97c86f330707691a8ee69dcbfeee53d0e20064f78fd902678d5aa4a0113cc7ce38f209c9a
7
- data.tar.gz: 75888830f1b0660f78d551eb3fd2a323f40fb21e5ac9a361c0f8ea22be0b32a11a014dd42719e4b25c33f5f497e2d1b6b296c8db15f5dc89abaed5d4f03ff5e1
6
+ metadata.gz: 407e55c907f990f0492898e636525c8238221167fabf6d0b4bccef0c9eb163f0d75b5bb3b05b58130be46bb823b809a524490f762cbd5b3c56143e2abf9fd7c4
7
+ data.tar.gz: e7300d3adbaa6b75d43cafe1faae5eccfc212f558ce7d38889438d3ceaac99becc55242d2a10f2ec3eba759d2038649fe2b80585e97cba4e58ebd215b4767594
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- globus_client (0.12.1)
4
+ globus_client (0.13.0)
5
5
  activesupport (>= 4.2, < 8)
6
6
  faraday
7
7
  faraday-retry
@@ -76,14 +76,14 @@ GEM
76
76
  unicode-display_width (>= 2.4.0, < 3.0)
77
77
  rubocop-ast (1.29.0)
78
78
  parser (>= 3.2.1.0)
79
- rubocop-capybara (2.18.0)
79
+ rubocop-capybara (2.19.0)
80
80
  rubocop (~> 1.41)
81
81
  rubocop-factory_bot (2.24.0)
82
82
  rubocop (~> 1.33)
83
83
  rubocop-performance (1.19.1)
84
84
  rubocop (>= 1.7.0, < 2.0)
85
85
  rubocop-ast (>= 0.4.0)
86
- rubocop-rspec (2.24.0)
86
+ rubocop-rspec (2.24.1)
87
87
  rubocop (~> 1.33)
88
88
  rubocop-capybara (~> 2.17)
89
89
  rubocop-factory_bot (~> 2.22)
data/api_test.rb CHANGED
@@ -27,7 +27,7 @@ Benchmark.bm(20) do |benchmark|
27
27
 
28
28
  benchmark.report("before_perms:") do
29
29
  # Not part of the public API but this allows us to test access changes
30
- @before_permissions = GlobusClient::Endpoint.new(GlobusClient.config, user_id:, path:).send(:access_rule)["permissions"]
30
+ @before_permissions = GlobusClient::Endpoint.new(GlobusClient.instance, user_id:, path:).send(:access_rule)["permissions"]
31
31
  end
32
32
 
33
33
  benchmark.report("has_files?:") do
@@ -48,7 +48,7 @@ Benchmark.bm(20) do |benchmark|
48
48
 
49
49
  benchmark.report("after_perms:") do
50
50
  # Not part of the public API but this allows us to test access changes
51
- @after_permissions = GlobusClient::Endpoint.new(GlobusClient.config, user_id:, path:).send(:access_rule)["permissions"]
51
+ @after_permissions = GlobusClient::Endpoint.new(GlobusClient.instance, user_id:, path:).send(:access_rule)["permissions"]
52
52
  end
53
53
 
54
54
  puts "User #{user_id} exists: #{@user_exists}"
@@ -17,6 +17,8 @@ class GlobusClient
17
17
  def token
18
18
  response = connection.post("/v2/oauth2/token", form_data)
19
19
 
20
+ UnexpectedResponse.call(response) unless response.success?
21
+
20
22
  JSON.parse(response.body)["access_token"]
21
23
  end
22
24
 
@@ -7,11 +7,11 @@ class GlobusClient
7
7
 
8
8
  FileInfo = Struct.new(:name, :size)
9
9
 
10
- # @param config [#token, #uploads_directory, #transfer_endpoint_id, #transfer_url, #auth_url] configuration for the gem
10
+ # @param client [GlobusClient] a configured instance of the GlobusClient
11
11
  # @param path [String] the path to operate on
12
12
  # @param user_id [String] a Globus user ID (e.g., a @stanford.edu email address)
13
- def initialize(config, path:, user_id:)
14
- @config = config
13
+ def initialize(client, path:, user_id:)
14
+ @client = client
15
15
  @user_id = user_id
16
16
  @path = path
17
17
  end
@@ -30,23 +30,12 @@ class GlobusClient
30
30
  def mkdir
31
31
  # transfer API does not support recursive directory creation
32
32
  paths.each do |path|
33
- response = connection.post("#{transfer_path}/mkdir") do |req|
34
- req.headers["Content-Type"] = "application/json"
35
- req.body = {
36
- DATA_TYPE: "mkdir",
37
- path:
38
- }.to_json
39
- end
40
-
41
- next if response.success?
42
-
43
- # Ignore error if directory already exists
44
- if response.status == 502
45
- error = JSON.parse(response.body)
46
- next if error["code"] == "ExternalError.MkdirFailed.Exists"
47
- end
48
-
49
- UnexpectedResponse.call(response)
33
+ client.post(
34
+ base_url: client.config.transfer_url,
35
+ path: "#{transfer_path}/mkdir",
36
+ body: {DATA_TYPE: "mkdir", path:},
37
+ expected_response: ->(resp) { resp.status == 502 && JSON.parse(resp.body)["code"] == "ExternalError.MkdirFailed.Exists" }
38
+ )
50
39
  end
51
40
  end
52
41
 
@@ -64,35 +53,18 @@ class GlobusClient
64
53
  def delete_access_rule
65
54
  raise(StandardError, "Access rule not found for #{path}") if !access_rule_id
66
55
 
67
- response = connection.delete("#{access_path}/#{access_rule_id}")
68
- return true if response.success?
69
-
70
- UnexpectedResponse.call(response)
56
+ client.delete(
57
+ base_url: client.config.transfer_url,
58
+ path: "#{access_path}/#{access_rule_id}"
59
+ )
71
60
  end
72
61
 
73
62
  private
74
63
 
75
- attr_reader :config, :path, :user_id
76
-
77
- def connection
78
- # faraday/retry is used here to catch Faraday::ConnectionFailed exceptions
79
- # see: https://github.com/sul-dlss/happy-heron/issues/3008
80
- @connection ||= Faraday.new(
81
- url: config.transfer_url,
82
- headers: {Authorization: "Bearer #{config.token}"}
83
- ) do |faraday|
84
- faraday.request :retry, {
85
- max: 10,
86
- interval: 0.05,
87
- interval_randomness: 0.5,
88
- backoff_factor: 2,
89
- exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS + [Faraday::ConnectionFailed]
90
- }
91
- end
92
- end
64
+ attr_reader :client, :path, :user_id
93
65
 
94
66
  def globus_identity_id
95
- Identity.new(config).get_identity_id(user_id)
67
+ Identity.new(client).get_identity_id(user_id)
96
68
  end
97
69
 
98
70
  # Builds up a path from a list of path elements. E.g., input would look like:
@@ -102,7 +74,7 @@ class GlobusClient
102
74
  def paths
103
75
  @paths ||= path_segments.map.with_index do |_segment, index|
104
76
  File
105
- .join(config.uploads_directory, path_segments.slice(..index))
77
+ .join(client.config.uploads_directory, path_segments.slice(..index))
106
78
  .concat(PATH_SEPARATOR)
107
79
  end
108
80
  end
@@ -123,18 +95,21 @@ class GlobusClient
123
95
  # @param files [Array<FileInfo>] an array of FileInfo structs, each of which has a name and a size
124
96
  # @param return_presence [Boolean] if true, return a boolean to indicate if any files at all are present, short-circuiting the recursive operation
125
97
  def ls_path(filepath, files, return_presence: false)
126
- response = connection.get("#{transfer_path}/ls?path=#{CGI.escape(filepath)}")
127
- return UnexpectedResponse.call(response) unless response.success?
98
+ response = client.get(
99
+ base_url: client.config.transfer_url,
100
+ path: "#{transfer_path}/ls",
101
+ params: {path: filepath}
102
+ )
128
103
 
129
- data = JSON.parse(response.body)["DATA"]
130
- data
104
+ response["DATA"]
131
105
  .select { |object| object["type"] == "file" }
132
106
  .each do |file|
133
107
  return true if return_presence
134
108
 
135
109
  files << FileInfo.new("#{filepath}#{file["name"]}", file["size"])
136
110
  end
137
- data
111
+
112
+ response["DATA"]
138
113
  .select { |object| object["type"] == "dir" }
139
114
  .each do |dir|
140
115
  # NOTE: This allows the recursive method to short-circuit iff ls_path
@@ -149,62 +124,45 @@ class GlobusClient
149
124
  end
150
125
 
151
126
  def access_request(permissions:)
152
- response = if access_rule_id
153
- connection.put("#{access_path}/#{access_rule_id}") do |req|
154
- req.body = {
155
- DATA_TYPE: "access",
156
- permissions:
157
- }.to_json
158
- req.headers["Content-Type"] = "application/json"
159
- end
127
+ if access_rule_id
128
+ update_access_request(permissions:)
160
129
  else
161
- connection.post(access_path) do |req|
162
- req.body = {
130
+ client.post(
131
+ base_url: client.config.transfer_url,
132
+ path: access_path,
133
+ body: {
163
134
  DATA_TYPE: "access",
164
135
  principal_type: "identity",
165
136
  principal: globus_identity_id,
166
137
  path: full_path,
167
138
  permissions:,
168
139
  notify_email: user_id
169
- }.to_json
170
- req.headers["Content-Type"] = "application/json"
171
- end
140
+ }
141
+ )
172
142
  end
173
-
174
- return true if response.success?
175
-
176
- UnexpectedResponse.call(response)
177
143
  end
178
144
 
179
145
  def update_access_request(permissions:)
180
146
  raise(StandardError, "Access rule not found for #{path}") if !access_rule_id
181
147
 
182
- response = connection.put("#{access_path}/#{access_rule_id}") do |req|
183
- req.body = {
148
+ client.put(
149
+ base_url: client.config.transfer_url,
150
+ path: "#{access_path}/#{access_rule_id}",
151
+ body: {
184
152
  DATA_TYPE: "access",
185
153
  permissions:
186
- }.to_json
187
- req.headers["Content-Type"] = "application/json"
188
- end
189
-
190
- return true if response.success?
191
-
192
- UnexpectedResponse.call(response)
154
+ }
155
+ )
193
156
  end
194
157
 
195
158
  def access_rule
196
- response = connection.get(access_list_path) do |req|
197
- req.headers["Content-Type"] = "application/json"
198
- end
199
-
200
- # debugging of Globus responses
201
- response_body = JSON.parse(response.body)
202
-
203
- UnexpectedResponse.call(response, message: "Response is missing DATA in: #{response_body}") unless response.success? && response_body.key?("DATA")
159
+ response = client.get(
160
+ base_url: client.config.transfer_url,
161
+ path: access_list_path,
162
+ content_type: "application/json"
163
+ )
204
164
 
205
- JSON
206
- .parse(response.body)["DATA"]
207
- .find { |acl| acl["path"] == full_path }
165
+ response.fetch("DATA").find { |acl| acl["path"] == full_path }
208
166
  end
209
167
 
210
168
  def access_rule_id
@@ -212,15 +170,15 @@ class GlobusClient
212
170
  end
213
171
 
214
172
  def transfer_path
215
- "/v0.10/operation/endpoint/#{config.transfer_endpoint_id}"
173
+ "/v0.10/operation/endpoint/#{client.config.transfer_endpoint_id}"
216
174
  end
217
175
 
218
176
  def access_path
219
- "/v0.10/endpoint/#{config.transfer_endpoint_id}/access"
177
+ "/v0.10/endpoint/#{client.config.transfer_endpoint_id}/access"
220
178
  end
221
179
 
222
180
  def access_list_path
223
- "/v0.10/endpoint/#{config.transfer_endpoint_id}/access_list"
181
+ "/v0.10/endpoint/#{client.config.transfer_endpoint_id}/access_list"
224
182
  end
225
183
  end
226
184
  end
@@ -3,19 +3,20 @@
3
3
  class GlobusClient
4
4
  # Lookup of a Globus identity ID
5
5
  class Identity
6
- def initialize(config)
7
- @config = config
6
+ def initialize(client)
7
+ @client = client
8
8
  end
9
9
 
10
10
  # @param user_id [String] the username in the form of an email addresss
11
11
  # @return [Hash] id and status of Globus identity
12
12
  def get_identity(user_id)
13
- @email = user_id
14
- response = lookup_identity
15
- UnexpectedResponse.call(response) unless response.success?
13
+ response = client.get(
14
+ base_url: client.config.auth_url,
15
+ path: "/v2/api/identities",
16
+ params: {usernames: user_id}
17
+ )
16
18
 
17
- data = JSON.parse(response.body)
18
- extract_id(data)
19
+ response["identities"].find { |id| id["username"] == user_id }
19
20
  end
20
21
 
21
22
  # @param user_id [String] the username in the form of an email addresss
@@ -32,23 +33,6 @@ class GlobusClient
32
33
 
33
34
  private
34
35
 
35
- attr_reader :config
36
-
37
- def connection
38
- Faraday.new(url: config.auth_url)
39
- end
40
-
41
- def lookup_identity
42
- id_endpoint = "/v2/api/identities"
43
- connection.get(id_endpoint) do |req|
44
- req.params["usernames"] = @email
45
- req.headers["Authorization"] = "Bearer #{config.token}"
46
- end
47
- end
48
-
49
- def extract_id(data)
50
- identities = data["identities"]
51
- identities.find { |id| id["username"] == @email }
52
- end
36
+ attr_reader :client
53
37
  end
54
38
  end
@@ -26,7 +26,7 @@ class GlobusClient
26
26
  # https://docs.globus.org/api/transfer/file_operations/#errors
27
27
  # https://docs.globus.org/api/transfer/acl/#common_errors
28
28
  # https://docs.globus.org/api/auth/reference/
29
- def self.call(response, message: "")
29
+ def self.call(response)
30
30
  case response.status
31
31
  when 400
32
32
  raise BadRequestError, "Invalid path or another error with the request: #{response.body}"
@@ -37,11 +37,11 @@ class GlobusClient
37
37
  when 404
38
38
  raise ResourceNotFound, "Endpoint ID not found or resource does not exist: #{response.body}"
39
39
  when 502
40
- raise EndpointError, "Other error with endpoint: #{response.status} #{response.body}. #{message}"
40
+ raise EndpointError, "Other error with endpoint: #{response.status} #{response.body}."
41
41
  when 503
42
42
  raise ServiceUnavailable, "The service is down for maintenance."
43
43
  else
44
- raise StandardError, "Unexpected response: #{response.status} #{response.body}. #{message}"
44
+ raise StandardError, "Unexpected response: #{response.status} #{response.body}."
45
45
  end
46
46
  end
47
47
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class GlobusClient
4
- VERSION = "0.12.1"
4
+ VERSION = "0.13.0"
5
5
  end
data/lib/globus_client.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/module/delegation"
4
+ require "active_support/core_ext/object/blank"
4
5
  require "faraday"
5
6
  require "faraday/retry"
6
7
  require "ostruct"
@@ -24,15 +25,15 @@ class GlobusClient
24
25
  def configure(client_id:, client_secret:, uploads_directory:, transfer_endpoint_id:, transfer_url: default_transfer_url, auth_url: default_auth_url)
25
26
  instance.config = OpenStruct.new(
26
27
  # For the initial token, use a dummy value to avoid hitting any APIs
27
- # during configuration, allowing the `TokenWrapper` to handle auto-magic
28
- # token refreshing. Why not immediately get a valid token? Our apps
28
+ # during configuration, allowing `with_token_refresh_when_unauthorized` to handle
29
+ # auto-magic token refreshing. Why not immediately get a valid token? Our apps
29
30
  # commonly invoke client `.configure` methods in the initializer in all
30
31
  # application environments, even those that are never expected to
31
32
  # connect to production APIs, such as local development machines.
32
33
  #
33
34
  # NOTE: `nil` and blank string cannot be used as dummy values here as
34
35
  # they lead to a malformed request to be sent, which triggers an
35
- # exception not rescued by `TokenWrapper`
36
+ # exception not rescued by `with_token_refresh_when_unauthorized`
36
37
  token: "a temporary dummy token to avoid hitting the API before it is needed",
37
38
  client_id:,
38
39
  client_secret:,
@@ -46,7 +47,7 @@ class GlobusClient
46
47
  end
47
48
 
48
49
  delegate :config, :disallow_writes, :delete_access_rule, :file_count, :list_files, :mkdir, :total_size,
49
- :user_valid?, :get_filenames, :has_files?, to: :instance
50
+ :user_valid?, :get_filenames, :has_files?, :delete, :get, :post, :put, to: :instance
50
51
 
51
52
  def default_transfer_url
52
53
  "https://transfer.api.globusonline.org"
@@ -59,70 +60,176 @@ class GlobusClient
59
60
 
60
61
  attr_accessor :config
61
62
 
63
+ # Send an authenticated GET request
64
+ # @param base_url [String] the base URL of the Globus API
65
+ # @param path [String] the path to the Globus API request
66
+ # @param params [Hash] params to get to the API
67
+ def get(base_url:, path:, params: {}, content_type: nil)
68
+ response = with_token_refresh_when_unauthorized do
69
+ connection(base_url).get(path, params) do |request|
70
+ request.headers["Authorization"] = "Bearer #{config.token}"
71
+ request.headers["Content-Type"] = content_type if content_type
72
+ end
73
+ end
74
+
75
+ UnexpectedResponse.call(response) unless response.success?
76
+
77
+ return nil if response.body.blank?
78
+
79
+ JSON.parse(response.body)
80
+ end
81
+
82
+ # Send an authenticated POST request
83
+ # @param base_url [String] the base URL of the Globus API
84
+ # @param path [String] the path to the Globus API request
85
+ # @param body [String] the body of the Globus API request
86
+ # @param expected_response [#call] an expected response handler to allow short-circuiting the unexpected response
87
+ def post(base_url:, path:, body:, expected_response: ->(resp) { false })
88
+ response = with_token_refresh_when_unauthorized do
89
+ connection(base_url).post(path) do |request|
90
+ request.headers["Authorization"] = "Bearer #{config.token}"
91
+ request.headers["Content-Type"] = "application/json"
92
+ request.body = body.to_json
93
+ end
94
+ end
95
+
96
+ UnexpectedResponse.call(response) unless response.success? || expected_response.call(response)
97
+
98
+ return nil if response.body.blank?
99
+
100
+ JSON.parse(response.body)
101
+ end
102
+
103
+ # Send an authenticated PUT request
104
+ # @param base_url [String] the base URL of the Globus API
105
+ # @param path [String] the path to the Globus API request
106
+ # @param body [String] the body of the Globus API request
107
+ def put(base_url:, path:, body:)
108
+ response = with_token_refresh_when_unauthorized do
109
+ connection(base_url).put(path) do |request|
110
+ request.headers["Authorization"] = "Bearer #{config.token}"
111
+ request.headers["Content-Type"] = "application/json"
112
+ request.body = body.to_json
113
+ end
114
+ end
115
+
116
+ UnexpectedResponse.call(response) unless response.success?
117
+
118
+ return nil if response.body.blank?
119
+
120
+ JSON.parse(response.body)
121
+ end
122
+
123
+ # Send an authenticated DELETE request
124
+ # @param base_url [String] the base URL of the Globus API
125
+ # @param path [String] the path to the Globus API request
126
+ def delete(base_url:, path:)
127
+ response = with_token_refresh_when_unauthorized do
128
+ connection(base_url).delete(path) do |request|
129
+ request.headers["Authorization"] = "Bearer #{config.token}"
130
+ end
131
+ end
132
+
133
+ UnexpectedResponse.call(response) unless response.success?
134
+
135
+ return nil if response.body.blank?
136
+
137
+ JSON.parse(response.body)
138
+ end
139
+
62
140
  def mkdir(...)
63
- TokenWrapper.refresh(config) do
64
- endpoint = Endpoint.new(config, ...)
141
+ Endpoint.new(self, ...).tap do |endpoint|
65
142
  endpoint.mkdir
66
143
  endpoint.allow_writes
67
144
  end
68
145
  end
69
146
 
70
147
  def disallow_writes(...)
71
- TokenWrapper.refresh(config) do
72
- endpoint = Endpoint.new(config, ...)
73
- endpoint.disallow_writes
74
- end
148
+ Endpoint
149
+ .new(self, ...)
150
+ .disallow_writes
75
151
  end
76
152
 
77
153
  def delete_access_rule(...)
78
- TokenWrapper.refresh(config) do
79
- endpoint = Endpoint.new(config, ...)
80
- endpoint.delete_access_rule
81
- end
154
+ Endpoint
155
+ .new(self, ...)
156
+ .delete_access_rule
82
157
  end
83
158
 
84
159
  # NOTE: Can't use the `...` (argument forwarding) operator here because we
85
160
  # want to route the keyword args to `Endpoint#new` and the block arg to
86
161
  # `Endpoint#list_files`
87
162
  def list_files(**keywords, &block)
88
- TokenWrapper.refresh(config) do
89
- endpoint = Endpoint.new(config, **keywords)
90
- endpoint.list_files(&block)
91
- end
163
+ Endpoint
164
+ .new(self, **keywords)
165
+ .list_files(&block)
92
166
  end
93
167
 
94
168
  def file_count(...)
95
- TokenWrapper.refresh(config) do
96
- endpoint = Endpoint.new(config, ...)
97
- endpoint.list_files { |files| return files.count }
98
- end
169
+ Endpoint
170
+ .new(self, ...)
171
+ .list_files { |files| return files.count }
99
172
  end
100
173
 
101
174
  def total_size(...)
102
- TokenWrapper.refresh(config) do
103
- endpoint = Endpoint.new(config, ...)
104
- endpoint.list_files { |files| return files.sum(&:size) }
105
- end
175
+ Endpoint
176
+ .new(self, ...)
177
+ .list_files { |files| return files.sum(&:size) }
106
178
  end
107
179
 
108
180
  def get_filenames(...)
109
- TokenWrapper.refresh(config) do
110
- endpoint = Endpoint.new(config, ...)
111
- endpoint.list_files { |files| return files.map(&:name) }
112
- end
181
+ Endpoint
182
+ .new(self, ...)
183
+ .list_files { |files| return files.map(&:name) }
113
184
  end
114
185
 
115
186
  def has_files?(...)
116
- TokenWrapper.refresh(config) do
117
- endpoint = Endpoint.new(config, ...)
118
- endpoint.has_files?
119
- end
187
+ Endpoint
188
+ .new(self, ...)
189
+ .has_files?
120
190
  end
121
191
 
122
192
  def user_valid?(...)
123
- TokenWrapper.refresh(config) do
124
- identity = Identity.new(config)
125
- identity.valid?(...)
193
+ Identity
194
+ .new(self)
195
+ .valid?(...)
196
+ end
197
+
198
+ private
199
+
200
+ def connection(base_url)
201
+ Faraday.new(url: base_url) do |conn|
202
+ conn.request :retry, {
203
+ max: 10,
204
+ interval: 0.05,
205
+ interval_randomness: 0.5,
206
+ backoff_factor: 2,
207
+ exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS + [Faraday::ConnectionFailed]
208
+ }
126
209
  end
127
210
  end
211
+
212
+ # Wraps API operations to request new access token if expired.
213
+ # @yieldreturn response [Faraday::Response] the response to inspect
214
+ #
215
+ # @note You likely want to make sure you're wrapping a _single_ HTTP request in this
216
+ # method, because 1) all calls in the block will be retried from the top if there's
217
+ # an authN failure detected, and 2) only the response returned by the block will be
218
+ # inspected for authN failure.
219
+ # Related: consider that the client instance and its token will live across many
220
+ # invocations of the GlobusClient methods once the client is configured by a consuming application,
221
+ # since this class is a Singleton. Thus, a token may expire between any two calls (i.e. it
222
+ # isn't necessary for a set of operations to collectively take longer than the token lifetime for
223
+ # expiry to fall in the middle of that related set of HTTP calls).
224
+ def with_token_refresh_when_unauthorized
225
+ response = yield
226
+
227
+ # if unauthorized, token has likely expired. try to get a new token and then retry the same request(s).
228
+ if response.status == 401
229
+ config.token = Authenticator.token(config.client_id, config.client_secret, config.auth_url)
230
+ response = yield
231
+ end
232
+
233
+ response
234
+ end
128
235
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: globus_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Collier
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2023-09-22 00:00:00.000000000 Z
13
+ date: 2023-09-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -183,7 +183,6 @@ files:
183
183
  - lib/globus_client/authenticator.rb
184
184
  - lib/globus_client/endpoint.rb
185
185
  - lib/globus_client/identity.rb
186
- - lib/globus_client/token_wrapper.rb
187
186
  - lib/globus_client/unexpected_response.rb
188
187
  - lib/globus_client/version.rb
189
188
  homepage: https://github.com/sul-dlss/globus_client
@@ -208,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
208
207
  - !ruby/object:Gem::Version
209
208
  version: '0'
210
209
  requirements: []
211
- rubygems_version: 3.4.13
210
+ rubygems_version: 3.4.19
212
211
  signing_key:
213
212
  specification_version: 4
214
213
  summary: Interface for interacting with the Globus API.
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class GlobusClient
4
- # Wraps API operations to request new access token if expired
5
- class TokenWrapper
6
- def self.refresh(config)
7
- yield
8
- rescue UnexpectedResponse::UnauthorizedError
9
- config.token = Authenticator.token(config.client_id, config.client_secret, config.auth_url)
10
- yield
11
- end
12
- end
13
- end