mais_orcid_client 0.2.0 → 0.3.1

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: 78b55c51d036f61b67cae0d7fd72ec1bc231e787301d1f4b4b1298dc45ef9a60
4
- data.tar.gz: 2826a1359658edc044a94abd4163827e810b6f9b64063d17837fc0441aab350d
3
+ metadata.gz: b93e75dc3042635205b947c21d9697808b9bdefa9d9dae8598e03db1eac3e128
4
+ data.tar.gz: d714d5c0985596191dba26e801a43455dd926b307e5a88753c66e5a8550d918d
5
5
  SHA512:
6
- metadata.gz: 1f31a13fb73f0643476343081e33dc50b3fb22847f32abb0f95599252efa13293c3f1a94d684dbfec11d3e0799b4370f166db2d4e436f9eb5ce05def99986cbb
7
- data.tar.gz: e930d20928af633900545f1cfd8aca7df794c1340b753042704dbbe6a037f95b109aeaadb23f948418f459f720f8d4e094c56dbe4a238faaa44f1cbc12ff6136
6
+ metadata.gz: 227b68aa40fee6c5a55dbea773ef788bc5235c094a8ede1b717881ad9cb4e431de368a4d8e20b7564e60dc9c02f4f0657d47835a2bc490a4f2db174f6ba0f8e0
7
+ data.tar.gz: 8860974d178d49ddca9153d9a71a31ca25d4e2050541c02fc7d185c7ad04d45854dc4087022c6194b9c52883a2d7e385a4a6beb7be0ab5858c41608627e4cb37
data/.rubocop/custom.yml CHANGED
@@ -52,3 +52,33 @@ RSpec/Rails/InferredSpecType: # new in 2.14
52
52
  RSpec/MultipleExpectations:
53
53
  Enabled: false
54
54
 
55
+ Capybara/MatchStyle: # new in 2.17
56
+ Enabled: true
57
+ FactoryBot/AssociationStyle: # new in 2.23
58
+ Enabled: true
59
+ FactoryBot/FactoryAssociationWithStrategy: # new in 2.23
60
+ Enabled: true
61
+ FactoryBot/FactoryNameStyle: # new in 2.16
62
+ Enabled: true
63
+ FactoryBot/RedundantFactoryOption: # new in 2.23
64
+ Enabled: true
65
+ RSpec/BeEmpty: # new in 2.20
66
+ Enabled: true
67
+ RSpec/ContainExactly: # new in 2.19
68
+ Enabled: true
69
+ RSpec/DuplicatedMetadata: # new in 2.16
70
+ Enabled: true
71
+ RSpec/IndexedLet: # new in 2.20
72
+ Enabled: true
73
+ RSpec/MatchArray: # new in 2.19
74
+ Enabled: true
75
+ RSpec/PendingWithoutReason: # new in 2.16
76
+ Enabled: true
77
+ RSpec/RedundantAround: # new in 2.19
78
+ Enabled: true
79
+ RSpec/SkipBlockInsideExample: # new in 2.19
80
+ Enabled: true
81
+ RSpec/Rails/MinitestAssertions: # new in 2.17
82
+ Enabled: true
83
+ RSpec/Rails/TravelAround: # new in 2.19
84
+ Enabled: true
data/.rubocop.yml CHANGED
@@ -4,6 +4,8 @@ inherit_mode:
4
4
 
5
5
  require:
6
6
  - standard
7
+ - standard-custom
8
+ - standard-performance
7
9
  - rubocop-performance
8
10
  - rubocop-rspec
9
11
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mais_orcid_client (0.2.0)
4
+ mais_orcid_client (0.3.1)
5
5
  activesupport (>= 4.2, < 8)
6
6
  faraday
7
7
  faraday-retry
@@ -11,12 +11,12 @@ PATH
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- activesupport (7.0.6)
14
+ activesupport (7.0.7)
15
15
  concurrent-ruby (~> 1.0, >= 1.0.2)
16
16
  i18n (>= 1.6, < 2)
17
17
  minitest (>= 5.1)
18
18
  tzinfo (~> 2.0)
19
- addressable (2.8.4)
19
+ addressable (2.8.5)
20
20
  public_suffix (>= 2.0.2, < 6.0)
21
21
  ast (2.4.2)
22
22
  byebug (11.1.3)
@@ -38,8 +38,8 @@ GEM
38
38
  json (2.6.3)
39
39
  jwt (2.7.1)
40
40
  language_server-protocol (3.17.0.3)
41
- lint_roller (1.0.0)
42
- minitest (5.18.1)
41
+ lint_roller (1.1.0)
42
+ minitest (5.19.0)
43
43
  multi_xml (0.6.0)
44
44
  oauth2 (2.0.9)
45
45
  faraday (>= 0.17.3, < 3.0)
@@ -52,13 +52,13 @@ GEM
52
52
  parser (3.2.2.3)
53
53
  ast (~> 2.4.1)
54
54
  racc
55
- public_suffix (5.0.1)
55
+ public_suffix (5.0.3)
56
56
  racc (1.7.1)
57
57
  rack (3.0.8)
58
58
  rainbow (3.1.1)
59
59
  rake (13.0.6)
60
60
  regexp_parser (2.8.1)
61
- rexml (3.2.5)
61
+ rexml (3.2.6)
62
62
  rspec (3.12.0)
63
63
  rspec-core (~> 3.12.0)
64
64
  rspec-expectations (~> 3.12.0)
@@ -68,7 +68,7 @@ GEM
68
68
  rspec-expectations (3.12.3)
69
69
  diff-lcs (>= 1.2.0, < 2.0)
70
70
  rspec-support (~> 3.12.0)
71
- rspec-mocks (3.12.5)
71
+ rspec-mocks (3.12.6)
72
72
  diff-lcs (>= 1.2.0, < 2.0)
73
73
  rspec-support (~> 3.12.0)
74
74
  rspec-support (3.12.1)
@@ -91,7 +91,7 @@ GEM
91
91
  rubocop-performance (1.18.0)
92
92
  rubocop (>= 1.7.0, < 2.0)
93
93
  rubocop-ast (>= 0.4.0)
94
- rubocop-rspec (2.22.0)
94
+ rubocop-rspec (2.23.2)
95
95
  rubocop (~> 1.33)
96
96
  rubocop-capybara (~> 2.17)
97
97
  rubocop-factory_bot (~> 2.22)
@@ -106,16 +106,17 @@ GEM
106
106
  snaky_hash (2.0.1)
107
107
  hashie
108
108
  version_gem (~> 1.1, >= 1.1.1)
109
- standard (1.29.0)
109
+ standard (1.30.1)
110
110
  language_server-protocol (~> 3.17.0.2)
111
111
  lint_roller (~> 1.0)
112
112
  rubocop (~> 1.52.0)
113
113
  standard-custom (~> 1.0.0)
114
114
  standard-performance (~> 1.1.0)
115
- standard-custom (1.0.1)
116
- lint_roller (~> 1.0)
117
- standard-performance (1.1.0)
115
+ standard-custom (1.0.2)
118
116
  lint_roller (~> 1.0)
117
+ rubocop (~> 1.50)
118
+ standard-performance (1.1.2)
119
+ lint_roller (~> 1.1)
119
120
  rubocop-performance (~> 1.18.0)
120
121
  tzinfo (2.0.6)
121
122
  concurrent-ruby (~> 1.0)
@@ -126,7 +127,7 @@ GEM
126
127
  addressable (>= 2.8.0)
127
128
  crack (>= 0.3.2)
128
129
  hashdiff (>= 0.4.0, < 2.0.0)
129
- zeitwerk (2.6.8)
130
+ zeitwerk (2.6.11)
130
131
 
131
132
  PLATFORMS
132
133
  x86_64-darwin-19
data/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
  # mais_orcid_client
7
7
  API client for accessing MAIS's ORCID endpoints.
8
8
 
9
+ MAIS's ORCID API provides access to ORCID information for Stanford users. (This is different from orcid.org's ORCID API, which is supported by https://github.com/sul-dlss/orcid_client.)
10
+
9
11
  ## Installation
10
12
 
11
13
  Install the gem and add to the application's Gemfile by executing:
@@ -68,8 +70,8 @@ recorded.
68
70
 
69
71
  To record new cassettes:
70
72
  1. Temporarily adjust the configuration (client_id, client_secret for the MaIS UAT URL) at the top of `spec/mais_orcid_client_spec.rb` so it matches the real MaIS UAT environment.
71
- 2. Add your new spec with a new cassette name (or delete a cassette to re-create it).
72
- 3. Run just that new spec.
73
+ 2. Add your new spec with a new cassette name (or delete a previous cassette to re-create it).
74
+ 3. Run just that new spec (important: else previous specs may use cassettes that have redacted credentials, causing your new spec to fail).
73
75
  4. You should get a new cassette with the name you specified in the spec.
74
76
  5. The cassette should have access tokens and secrets sanitized by the config in `spec_helper.rb`, but you can double check, EXCEPT for user access tokens in the user response. These should be sanitized manaully (e.g. "access_token":"8d13b8bb-XXXX-YYYY-b7d6-87aecd5a8975")
75
77
  6. Set your configuration at the top of the spec back to the fake client_id and client_secret values.
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MaisOrcidClient
4
+ # The namespace for the "login" command
5
+ class Authenticator
6
+ attr_reader :client_id, :client_secret, :base_url
7
+
8
+ def self.token(client_id, client_secret, base_url)
9
+ new(client_id, client_secret, base_url).token
10
+ end
11
+
12
+ def initialize(client_id, client_secret, base_url)
13
+ @client_id = client_id
14
+ @client_secret = client_secret
15
+ @base_url = base_url
16
+ end
17
+
18
+ # @return [String]
19
+ def token
20
+ client = OAuth2::Client.new(client_id, client_secret, site: base_url,
21
+ token_url: "/api/oauth/token", authorize_url: "/api/oauth/authorize",
22
+ auth_scheme: :request_body)
23
+ client.client_credentials.get_token.token
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MaisOrcidClient
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.base_url)
10
+ yield
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MaisOrcidClient
4
+ # Handles unexpected responses when communicating with Mais
5
+ class UnexpectedResponse
6
+ # Error raised when the Mais API returns a 401 Unauthorized
7
+ class UnauthorizedError < StandardError; end
8
+
9
+ # Error raised when the Mais API returns a 500 error
10
+ class ServerError < StandardError; end
11
+
12
+ # Error raised when the Mais API returns a response with an error message in it
13
+ class ResponseError < StandardError; end
14
+
15
+ def self.call(response)
16
+ case response.status
17
+ when 401
18
+ raise UnauthorizedError, "There was a problem with the access token: #{response.body}"
19
+ when 500
20
+ raise ServerError, "Mais server error: #{response.body}"
21
+ else
22
+ raise StandardError, "Unexpected response: #{response.status} #{response.body}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MaisOrcidClient
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -28,16 +28,20 @@ class MaisOrcidClient
28
28
  # @param client_secret [String] the client secret to authenticate with MAIS
29
29
  # @param base_url [String] the base URL for the API
30
30
  def configure(client_id:, client_secret:, base_url:)
31
- instance.base_url = base_url
32
- instance.client_id = client_id
33
- instance.client_secret = client_secret
31
+ instance.config = OpenStruct.new(
32
+ token: Authenticator.token(client_id, client_secret, base_url),
33
+ client_id:,
34
+ client_secret:,
35
+ base_url:
36
+ )
37
+
34
38
  self
35
39
  end
36
40
 
37
- delegate :fetch_orcid_users, :fetch_orcid_user, to: :instance
41
+ delegate :config, :fetch_orcid_users, :fetch_orcid_user, to: :instance
38
42
  end
39
43
 
40
- attr_accessor :base_url, :client_id, :client_secret
44
+ attr_accessor :config
41
45
 
42
46
  # @param [int] limit number of users requested
43
47
  # @param [int] page_size number of users per page
@@ -79,26 +83,18 @@ class MaisOrcidClient
79
83
  OrcidUser.new(result[:sunet_id], result[:orcid_id], result[:scope], result[:access_token], result[:last_updated])
80
84
  end
81
85
 
82
- # @param [string] orcidid to fetch
86
+ # @param [string] orcidid to fetch (note any ORCID URI will be stripped, as MaIS endpoint requires bare ORCIDID only)
83
87
  # @return [<OrcidUser>, nil] orcid user or nil if not found
84
88
  def fetch_by_orcidid(orcidid)
85
- # NOTE: This is intended to be a temporary implementation that iterates over all users
86
- # until we find the orcidid of interest and then return it. It can be slow (1-2 minutes)
87
- # if the orcidid queried is at the end of the list (or doesn't exist).
88
- # The idea is that when MaIS implements this functionality in their API, we replace this
89
- # iteration with a simple call to their API.
90
- # see https://github.com/sul-dlss/happy-heron/issues/3164
91
- next_page = first_page
92
- loop do
93
- response = get_response(next_page)
94
- response[:results].each do |result|
95
- if result[:orcid_id] == orcidid
96
- return OrcidUser.new(result[:sunet_id], result[:orcid_id], result[:scope], result[:access_token], result[:last_updated])
97
- end
98
- end
99
- next_page = response.dig(:links, :next)
100
- return nil if last_page?(response[:links])
101
- end
89
+ bare_orcid = orcidid_without_uri(orcidid)
90
+
91
+ return if bare_orcid.empty? # don't even bother sending the search if the incoming orcidid is bogus
92
+
93
+ result = get_response("/users/#{bare_orcid}", allow404: true)
94
+
95
+ return if result.nil?
96
+
97
+ OrcidUser.new(result[:sunet_id], result[:orcid_id], result[:scope], result[:access_token], result[:last_updated])
102
98
  end
103
99
 
104
100
  def first_page(page_size = nil)
@@ -112,40 +108,37 @@ class MaisOrcidClient
112
108
  end
113
109
 
114
110
  def get_response(path, allow404: false)
115
- response = conn.get("/mais/orcid/v1#{path}")
111
+ TokenWrapper.refresh(config) do
112
+ response = conn.get("/mais/orcid/v1#{path}")
116
113
 
117
- return if allow404 && response.status == 404
114
+ return if allow404 && response.status == 404
118
115
 
119
- raise "UIT MAIS ORCID User API returned #{response.status}" if response.status != 200
116
+ return UnexpectedResponse.call(response) unless response.success?
120
117
 
121
- body = JSON.parse(response.body).with_indifferent_access
122
- raise "UIT MAIS ORCID User API returned an error: #{response.body}" if body.key?(:error)
118
+ body = JSON.parse(response.body).with_indifferent_access
119
+ raise UnexpectedResponse::ResponseError, "UIT MAIS ORCID User API returned an error: #{response.body}" if body.key?(:error)
123
120
 
124
- body
121
+ body
122
+ end
125
123
  end
126
124
 
127
- # @return [Faraday::Connection]
128
125
  def conn
129
- @conn ||= begin
130
- conn = Faraday.new(url: base_url) do |faraday|
131
- faraday.request :retry, max: 3,
132
- interval: 0.5,
133
- interval_randomness: 0.5,
134
- backoff_factor: 2
135
- end
136
- conn.options.timeout = 500
137
- conn.options.open_timeout = 10
138
- conn.headers[:user_agent] = "stanford-library-sul-pub"
139
- conn.headers[:authorization] = token
140
- conn
126
+ conn = Faraday.new(url: config.base_url) do |faraday|
127
+ faraday.request :retry, max: 3,
128
+ interval: 0.5,
129
+ interval_randomness: 0.5,
130
+ backoff_factor: 2
141
131
  end
132
+ conn.options.timeout = 500
133
+ conn.options.open_timeout = 10
134
+ conn.headers[:user_agent] = "stanford-library-sul-pub"
135
+ conn.headers[:authorization] = "Bearer #{config.token}"
136
+ conn
142
137
  end
143
138
 
144
- def token
145
- client = OAuth2::Client.new(client_id, client_secret, site: base_url,
146
- token_url: "/api/oauth/token", authorize_url: "/api/oauth/authorize",
147
- auth_scheme: :request_body)
148
- token = client.client_credentials.get_token
149
- "Bearer #{token.token}"
139
+ # @param [string] orcidid which can include a full URI, e.g. "https://sandbox.orcid.org/0000-0002-7262-6251"
140
+ # @return [string] orcidid without URI (if valid), e.g. "0000-0002-7262-6251" or empty string if none found or orcidid invalid
141
+ def orcidid_without_uri(orcidid)
142
+ orcidid.match(/\d{4}-\d{4}-\d{4}-\d{3}(\d|X){1}\z/).to_s
150
143
  end
151
144
  end
@@ -44,4 +44,5 @@ Gem::Specification.new do |spec|
44
44
  spec.add_development_dependency "simplecov"
45
45
  spec.add_development_dependency "webmock"
46
46
  spec.add_development_dependency "vcr"
47
+ spec.add_development_dependency "byebug"
47
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mais_orcid_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Mangiafico
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-07-12 00:00:00.000000000 Z
12
+ date: 2023-08-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -185,6 +185,20 @@ dependencies:
185
185
  - - ">="
186
186
  - !ruby/object:Gem::Version
187
187
  version: '0'
188
+ - !ruby/object:Gem::Dependency
189
+ name: byebug
190
+ requirement: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ type: :development
196
+ prerelease: false
197
+ version_requirements: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
188
202
  description: This provides API interaction with the MAIS's ORCID API
189
203
  email:
190
204
  - pmangiafico@stanford.edu
@@ -203,6 +217,9 @@ files:
203
217
  - README.md
204
218
  - Rakefile
205
219
  - lib/mais_orcid_client.rb
220
+ - lib/mais_orcid_client/authenticator.rb
221
+ - lib/mais_orcid_client/token_wrapper.rb
222
+ - lib/mais_orcid_client/unexpected_response.rb
206
223
  - lib/mais_orcid_client/version.rb
207
224
  - mais_orcid_client.gemspec
208
225
  homepage: https://github.com/sul-dlss/mais_orcid_client
@@ -227,7 +244,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
244
  - !ruby/object:Gem::Version
228
245
  version: '0'
229
246
  requirements: []
230
- rubygems_version: 3.4.14
247
+ rubygems_version: 3.4.13
231
248
  signing_key:
232
249
  specification_version: 4
233
250
  summary: Interface for interacting with the MAIS's ORCID API.