checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9104c94aa76173b5aaa26efb5577358bfb57e8fcc5fafeb7f044d413a01af40e
4
+ data.tar.gz: 03156dd1bba125b360eef9dcf311b9fd9791fc08e0e4aefe092169eface776c8
5
+ SHA512:
6
+ metadata.gz: 5c77a6c4c3866b0ad92a1ad31097d92b80dbfca091c617986e9bb27909bb8accd891bf79d3af38f79ac25ffeea33b46ed7fab2f9b707cbbb78d9ccb5e319404f
7
+ data.tar.gz: bd04da0ef51a21c19c55dbc6fc7a88252b2be8e28f9afc3fdf19592ea19484c8da242f1312f0f4b1759d6de0af62a3fcab8f2733bd0737ae70f80b285dec3d8d
data/.gitignore ADDED
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/.rubocop.yml ADDED
@@ -0,0 +1,209 @@
1
+ # from https://github.com/rails/rails/blob/master/.rubocop.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.5
5
+ # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
6
+ # to ignore them, so only the ones explicitly set in this file are enabled.
7
+ DisabledByDefault: true
8
+
9
+ # Prefer &&/|| over and/or.
10
+ Style/AndOr:
11
+ Enabled: true
12
+
13
+ # Do not use braces for hash literals when they are the last argument of a
14
+ # method call.
15
+ Style/BracesAroundHashParameters:
16
+ Enabled: true
17
+ EnforcedStyle: context_dependent
18
+
19
+ # Align `when` with `case`.
20
+ Layout/CaseIndentation:
21
+ Enabled: true
22
+
23
+ # Align comments with method definitions.
24
+ Layout/CommentIndentation:
25
+ Enabled: true
26
+
27
+ Layout/ElseAlignment:
28
+ Enabled: true
29
+
30
+ # Align `end` with the matching keyword or starting expression except for
31
+ # assignments, where it should be aligned with the LHS.
32
+ Layout/EndAlignment:
33
+ Enabled: true
34
+ EnforcedStyleAlignWith: variable
35
+ AutoCorrect: true
36
+
37
+ Layout/EmptyLineAfterMagicComment:
38
+ Enabled: true
39
+
40
+ Layout/EmptyLinesAroundBlockBody:
41
+ Enabled: true
42
+
43
+ # In a regular class definition, no empty lines around the body.
44
+ Layout/EmptyLinesAroundClassBody:
45
+ Enabled: true
46
+
47
+ # In a regular method definition, no empty lines around the body.
48
+ Layout/EmptyLinesAroundMethodBody:
49
+ Enabled: true
50
+
51
+ # In a regular module definition, no empty lines around the body.
52
+ Layout/EmptyLinesAroundModuleBody:
53
+ Enabled: true
54
+
55
+ Layout/IndentFirstArgument:
56
+ Enabled: true
57
+
58
+ # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
59
+ Style/HashSyntax:
60
+ Enabled: true
61
+
62
+ # Method definitions after `private` or `protected` isolated calls need one
63
+ # extra level of indentation.
64
+ Layout/IndentationConsistency:
65
+ Enabled: true
66
+ EnforcedStyle: indented_internal_methods
67
+
68
+ # Two spaces, no tabs (for indentation).
69
+ Layout/IndentationWidth:
70
+ Enabled: true
71
+
72
+ Layout/LeadingCommentSpace:
73
+ Enabled: true
74
+
75
+ Layout/SpaceAfterColon:
76
+ Enabled: true
77
+
78
+ Layout/SpaceAfterComma:
79
+ Enabled: true
80
+
81
+ Layout/SpaceAfterSemicolon:
82
+ Enabled: true
83
+
84
+ Layout/SpaceAroundEqualsInParameterDefault:
85
+ Enabled: true
86
+
87
+ Layout/SpaceAroundKeyword:
88
+ Enabled: true
89
+
90
+ Layout/SpaceAroundOperators:
91
+ Enabled: true
92
+
93
+ Layout/SpaceBeforeComma:
94
+ Enabled: true
95
+
96
+ Layout/SpaceBeforeComment:
97
+ Enabled: true
98
+
99
+ Layout/SpaceBeforeFirstArg:
100
+ Enabled: true
101
+
102
+ Style/DefWithParentheses:
103
+ Enabled: true
104
+
105
+ # Defining a method with parameters needs parentheses.
106
+ Style/MethodDefParentheses:
107
+ Enabled: true
108
+
109
+ Style/FrozenStringLiteralComment:
110
+ Enabled: true
111
+ EnforcedStyle: always
112
+ Exclude:
113
+ - 'actionview/test/**/*.builder'
114
+ - 'actionview/test/**/*.ruby'
115
+ - 'actionpack/test/**/*.builder'
116
+ - 'actionpack/test/**/*.ruby'
117
+ - 'activestorage/db/migrate/**/*.rb'
118
+ - 'activestorage/db/update_migrate/**/*.rb'
119
+ - 'actionmailbox/db/migrate/**/*.rb'
120
+ - 'actiontext/db/migrate/**/*.rb'
121
+
122
+ Style/RedundantFreeze:
123
+ Enabled: true
124
+
125
+ # Use `foo {}` not `foo{}`.
126
+ Layout/SpaceBeforeBlockBraces:
127
+ Enabled: true
128
+
129
+ # Use `foo { bar }` not `foo {bar}`.
130
+ Layout/SpaceInsideBlockBraces:
131
+ Enabled: true
132
+ EnforcedStyleForEmptyBraces: space
133
+
134
+ # Use `{ a: 1 }` not `{a:1}`.
135
+ Layout/SpaceInsideHashLiteralBraces:
136
+ Enabled: true
137
+
138
+ Layout/SpaceInsideParens:
139
+ Enabled: true
140
+
141
+ # Check quotes usage according to lint rule below.
142
+ Style/StringLiterals:
143
+ Enabled: true
144
+ EnforcedStyle: double_quotes
145
+
146
+ # Detect hard tabs, no hard tabs.
147
+ Layout/Tab:
148
+ Enabled: true
149
+
150
+ # Blank lines should not have any spaces.
151
+ Layout/TrailingBlankLines:
152
+ Enabled: true
153
+
154
+ # No trailing whitespace.
155
+ Layout/TrailingWhitespace:
156
+ Enabled: true
157
+
158
+ # Use quotes for string literals when they are enough.
159
+ Style/UnneededPercentQ:
160
+ Enabled: true
161
+
162
+ Lint/AmbiguousOperator:
163
+ Enabled: true
164
+
165
+ Lint/AmbiguousRegexpLiteral:
166
+ Enabled: true
167
+
168
+ Lint/ErbNewArguments:
169
+ Enabled: true
170
+
171
+ # Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
172
+ Lint/RequireParentheses:
173
+ Enabled: true
174
+
175
+ Lint/ShadowingOuterLocalVariable:
176
+ Enabled: true
177
+
178
+ Lint/StringConversionInInterpolation:
179
+ Enabled: true
180
+
181
+ Lint/UriEscapeUnescape:
182
+ Enabled: true
183
+
184
+ Lint/UselessAssignment:
185
+ Enabled: true
186
+
187
+ Lint/DeprecatedClassMethods:
188
+ Enabled: true
189
+
190
+ Style/ParenthesesAroundCondition:
191
+ Enabled: true
192
+
193
+ Style/RedundantBegin:
194
+ Enabled: true
195
+
196
+ Style/RedundantReturn:
197
+ Enabled: true
198
+ AllowMultipleReturnValues: true
199
+
200
+ Style/Semicolon:
201
+ Enabled: true
202
+ AllowAsExpressionSeparator: true
203
+
204
+ # Prefer Foo.method over Foo::method
205
+ Style/ColonMethodCall:
206
+ Enabled: true
207
+
208
+ Style/TrivialAccessors:
209
+ Enabled: true
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.4.1
4
+ install:
5
+ - travis_retry bundle install
6
+ script: bundle exec rspec
7
+ notifications:
8
+ slack: datacite:Wt8En0ALoTA6Kjc5EOKNDWxN
9
+ deploy:
10
+ provider: rubygems
11
+ api_key:
12
+ secure: uE7wQ63qRd3hnuzi2omMNoAcJJfQ/UQqk67qw2llQVYMbQlwgD6nnF5ipkPuqdAxPKZycUoZuD/uFKGK1B5pSPZeMTfFmTls3vPj6kLWeUCNZkZXfOsPPzhipRmxMbuxaCn6IqNtkTavsjC5xkOeBDw9aRTMRphyrwewWtk83NlwpRefNVx1SxU6IAxpySEWpjQ/PsLvJ+NmQuPl6KXlTooDPpaVHaaW9IDLtFoTK4GGWLGiDYWkhwQerUvczxvyuNGr/o0j8obCKau6lgv8eAj1f9W8pXmbvxq8Opp3/8chSvT98sO+L5RwigJYu6X7B1xVZGSNAuOVzNRagGkF2LE20b4p+1RqpmqCLViZcfPXRuSLPD4QlyRqjPbuma1mSQ/7zR8JTyobDJfyrQbuJNu+q96Edf4ZzHATDWik6sWRaU3Qcv5MN3NKgSwB0jbHgdgUeSKZusN4vkpNN4n1uxXnII/7A2b7W9U8wPFqmwhopB9egOxP9BQERFu4GxtI1TOPjIlh3tKy8SjSF0KnunHbaI1s6UrpjEh+mS5k9WryJtRmINFRI+ZHeCI6Sl5sXococnczDImG2AH7PEJ1zZ3rq/JH7J4HJ2moJW6xhvqVhoI1i9ti1VRqDE82GwRb0QE88eS2DZX2/b6J1faxlGuj1Cup+xzOrLnj9pctZLc=
13
+ gem: omniauth-globus
14
+ on:
15
+ tags: true
16
+ repo: datacite/omniauth-globus
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in omniauth-github.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,98 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ omniauth-globus (0.8.3)
5
+ jwt (>= 2.0)
6
+ omniauth (~> 1.9)
7
+ omniauth-oauth2 (~> 1.6)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.7.0)
13
+ public_suffix (>= 2.0.2, < 5.0)
14
+ ast (2.4.0)
15
+ codeclimate-test-reporter (1.0.9)
16
+ simplecov (<= 0.13)
17
+ crack (0.4.3)
18
+ safe_yaml (~> 1.0.0)
19
+ diff-lcs (1.3)
20
+ docile (1.1.5)
21
+ faraday (0.15.4)
22
+ multipart-post (>= 1.2, < 3)
23
+ hashdiff (1.0.0)
24
+ hashie (3.6.0)
25
+ jaro_winkler (1.5.3)
26
+ json (2.2.0)
27
+ jwt (2.2.1)
28
+ multi_json (1.13.1)
29
+ multi_xml (0.6.0)
30
+ multipart-post (2.1.1)
31
+ oauth2 (1.4.1)
32
+ faraday (>= 0.8, < 0.16.0)
33
+ jwt (>= 1.0, < 3.0)
34
+ multi_json (~> 1.3)
35
+ multi_xml (~> 0.5)
36
+ rack (>= 1.2, < 3)
37
+ omniauth (1.9.0)
38
+ hashie (>= 3.4.6, < 3.7.0)
39
+ rack (>= 1.6.2, < 3)
40
+ omniauth-oauth2 (1.6.0)
41
+ oauth2 (~> 1.1)
42
+ omniauth (~> 1.9)
43
+ parallel (1.17.0)
44
+ parser (2.6.4.0)
45
+ ast (~> 2.4.0)
46
+ public_suffix (4.0.1)
47
+ rack (2.0.7)
48
+ rack-test (0.6.3)
49
+ rack (>= 1.0)
50
+ rainbow (3.0.0)
51
+ rspec (3.8.0)
52
+ rspec-core (~> 3.8.0)
53
+ rspec-expectations (~> 3.8.0)
54
+ rspec-mocks (~> 3.8.0)
55
+ rspec-core (3.8.2)
56
+ rspec-support (~> 3.8.0)
57
+ rspec-expectations (3.8.4)
58
+ diff-lcs (>= 1.2.0, < 2.0)
59
+ rspec-support (~> 3.8.0)
60
+ rspec-mocks (3.8.1)
61
+ diff-lcs (>= 1.2.0, < 2.0)
62
+ rspec-support (~> 3.8.0)
63
+ rspec-support (3.8.2)
64
+ rubocop (0.74.0)
65
+ jaro_winkler (~> 1.5.1)
66
+ parallel (~> 1.10)
67
+ parser (>= 2.6)
68
+ rainbow (>= 2.2.2, < 4.0)
69
+ ruby-progressbar (~> 1.7)
70
+ unicode-display_width (>= 1.4.0, < 1.7)
71
+ ruby-progressbar (1.10.1)
72
+ safe_yaml (1.0.5)
73
+ simplecov (0.13.0)
74
+ docile (~> 1.1.0)
75
+ json (>= 1.8, < 3)
76
+ simplecov-html (~> 0.10.0)
77
+ simplecov-html (0.10.2)
78
+ unicode-display_width (1.6.0)
79
+ webmock (3.7.2)
80
+ addressable (>= 2.3.6)
81
+ crack (>= 0.3.2)
82
+ hashdiff (>= 0.4.0, < 2.0.0)
83
+
84
+ PLATFORMS
85
+ ruby
86
+
87
+ DEPENDENCIES
88
+ bundler (~> 1.0)
89
+ codeclimate-test-reporter (~> 1.0.0)
90
+ omniauth-globus!
91
+ rack-test (~> 0.6.3)
92
+ rspec (~> 3.4)
93
+ rubocop (~> 0.68)
94
+ simplecov
95
+ webmock (~> 3.0, >= 3.0.1)
96
+
97
+ BUNDLED WITH
98
+ 1.17.3
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 DataCite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # omniauth-globus
2
+
3
+ Globus Auth Strategy for OmniAuth
data/lib/omniauth-globus.rb ADDED
@@ -0,0 +1 @@
1
+ require 'omniauth/globus'
data/lib/omniauth/globus.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'omniauth/strategies/globus'
data/lib/omniauth/globus/version.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAuth
4
+ module Globus
5
+ VERSION = "0.8.3"
6
+ end
7
+ end
data/lib/omniauth/strategies/globus.rb ADDED
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jwt"
4
+ require "omniauth/strategies/oauth2"
5
+ require "uri"
6
+
7
+ module OmniAuth
8
+ module Strategies
9
+ class Globus < OmniAuth::Strategies::OAuth2
10
+ option :name, "globus"
11
+ option :issuer, "https://auth.globus.org"
12
+ option :scope, "openid profile email"
13
+ option :authorize_options, %i[access_type login_hint prompt request_visible_actions scope state redirect_uri include_granted_scopes openid_realm device_id device_name]
14
+
15
+ option(:client_options, site: 'https://auth.globus.org',
16
+ authorize_url: 'https://auth.globus.org/v2/oauth2/authorize',
17
+ token_url: 'https://auth.globus.org/v2/oauth2/token',
18
+ discovery_endpoint: "https://auth.globus.org/.well-known/openid-configuration",
19
+ authorization_endpoint: "https://auth.globus.org/v2/oauth2/authorize",
20
+ token_endpoint: "https://auth.globus.org/v2/oauth2/token",
21
+ userinfo_endpoint: "https://auth.globus.org/v2/oauth2/userinfo",
22
+ jwks_uri: "https://auth.globus.org/jwk.json",
23
+ end_session_endpoint: "https://auth.globus.org/v2/oauth2/token/revoke")
24
+
25
+ def authorize_params
26
+ super.tap do |params|
27
+ options[:authorize_options].each do |k|
28
+ params[k] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
29
+ end
30
+
31
+ params[:scope] = get_scope(params)
32
+ params[:access_type] = 'offline' if params[:access_type].nil?
33
+ params['openid.realm'] = params.delete(:openid_realm) unless params[:openid_realm].nil?
34
+
35
+ session['omniauth.state'] = params[:state] if params[:state]
36
+ end
37
+ end
38
+
39
+ uid { raw_info['sub'] }
40
+
41
+ info do
42
+ prune!(
43
+ name: raw_info['name'],
44
+ first_name: raw_info['given_name'],
45
+ last_name: raw_info['family_name'],
46
+ email: raw_info['email']
47
+ )
48
+ end
49
+
50
+ extra do
51
+ hash = {}
52
+ hash[:id_token] = access_token['id_token']
53
+ if !access_token['id_token'].nil?
54
+ decoded = ::JWT.decode(access_token['id_token'], nil, false).first
55
+
56
+ # We have to manually verify the claims because the third parameter to
57
+ # JWT.decode is false since no verification key is provided.
58
+ ::JWT::Verify.verify_claims(decoded,
59
+ verify_iss: true,
60
+ iss: options.issuer,
61
+ verify_expiration: true)
62
+
63
+ hash[:id_info] = decoded
64
+ end
65
+ hash[:raw_info] = raw_info unless skip_info?
66
+ prune! hash
67
+ end
68
+
69
+ def raw_info
70
+ @raw_info ||= access_token.get(options.client_options.userinfo_endpoint).parsed
71
+ end
72
+
73
+ def custom_build_access_token
74
+ get_access_token(request)
75
+ end
76
+
77
+ alias build_access_token custom_build_access_token
78
+
79
+ private
80
+
81
+ def callback_url
82
+ options[:redirect_uri] || (full_host + script_name + callback_path)
83
+ end
84
+
85
+ def get_access_token(request)
86
+ verifier = request.params['code']
87
+ redirect_uri = request.params['redirect_uri']
88
+ if verifier && request.xhr?
89
+ client_get_token(verifier, redirect_uri || 'postmessage')
90
+ elsif verifier
91
+ client_get_token(verifier, redirect_uri || callback_url)
92
+ elsif verify_token(request.params['access_token'])
93
+ ::OAuth2::AccessToken.from_hash(client, request.params.dup)
94
+ elsif request.content_type =~ /json/i
95
+ begin
96
+ body = JSON.parse(request.body.read)
97
+ request.body.rewind # rewind request body for downstream middlewares
98
+ verifier = body && body['code']
99
+ client_get_token(verifier, 'postmessage') if verifier
100
+ rescue JSON::ParserError => e
101
+ warn "[omniauth globus] JSON parse error=#{e}"
102
+ end
103
+ end
104
+ end
105
+
106
+ def client_get_token(verifier, redirect_uri)
107
+ client.auth_code.get_token(verifier, get_token_options(redirect_uri), get_token_params)
108
+ end
109
+
110
+ def get_token_params
111
+ deep_symbolize(options.auth_token_params || {})
112
+ end
113
+
114
+ def get_scope(params)
115
+ raw_scope = params[:scope] || options.scope
116
+ scope_list = raw_scope.split(" ").map { |item| item.split(",") }.flatten
117
+ scope_list.join(" ")
118
+ end
119
+
120
+ def get_token_options(redirect_uri = "")
121
+ { redirect_uri: redirect_uri }.merge(token_params.to_hash(symbolize_keys: true))
122
+ end
123
+
124
+ def prune!(hash)
125
+ hash.delete_if do |_, v|
126
+ prune!(v) if v.is_a?(Hash)
127
+ v.nil? || (v.respond_to?(:empty?) && v.empty?)
128
+ end
129
+ end
130
+
131
+ def strip_unnecessary_query_parameters(query_parameters)
132
+ # strip `sz` parameter (defaults to sz=50) which overrides `image_size` options
133
+ return nil if query_parameters.nil?
134
+
135
+ params = CGI.parse(query_parameters)
136
+ stripped_params = params.delete_if { |key| key == 'sz' }
137
+
138
+ # don't return an empty Hash since that would result
139
+ # in URLs with a trailing ? character: http://image.url?
140
+ return nil if stripped_params.empty?
141
+
142
+ URI.encode_www_form(stripped_params)
143
+ end
144
+
145
+ def verify_token(access_token)
146
+ return false unless access_token
147
+
148
+ raw_response = client.request(:get, options.client_options.userinfo_endpoint,
149
+ params: { access_token: access_token }).parsed
150
+ raw_response["aud"] == options.client_id
151
+ end
152
+ end
153
+ end
154
+ end
data/omniauth-globus.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require File.expand_path("../lib/omniauth/globus/version", __FILE__)
5
+
6
+ Gem::Specification.new do |s|
7
+ s.authors = ["Martin Fenner"]
8
+ s.email = ["mfenner@datacite.org"]
9
+ s.name = "omniauth-globus"
10
+ s.homepage = "https://github.com/datacite/omniauth-globus"
11
+ s.summary = "Globus Auth OpenId connect Strategy for OmniAuth 1.0"
12
+ s.date = Date.today
13
+ s.description = "Enables third-party client apps to authenticate with the Globus Auth service via OpenID Connect"
14
+ s.require_paths = ["lib"]
15
+ s.version = OmniAuth::Globus::VERSION
16
+ s.extra_rdoc_files = ["README.md"]
17
+ s.license = "MIT"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+
22
+ s.required_ruby_version = ">= 2.3"
23
+
24
+ # Declary dependencies here, rather than in the Gemfile
25
+ s.add_dependency "jwt", ">= 2.0"
26
+ s.add_dependency "omniauth", "~> 1.9"
27
+ s.add_dependency "omniauth-oauth2", "~> 1.6"
28
+ s.add_development_dependency "bundler", "~> 1.0"
29
+ s.add_development_dependency "codeclimate-test-reporter", "~> 1.0.0"
30
+ s.add_development_dependency "rack-test", "~> 0.6.3"
31
+ s.add_development_dependency "rspec", "~> 3.4"
32
+ s.add_development_dependency "rubocop", "~> 0.68"
33
+ s.add_development_dependency "simplecov"
34
+ s.add_development_dependency "webmock", "~> 3.0", ">= 3.0.1"
35
+ end
data/spec/fixtures/access_token.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "Martin Fenner",
3
+ "access_token": "123",
4
+ "expires_in": 631138518,
5
+ "token_type": "bearer",
6
+ "orcid": "0000-0001-6528-2027",
7
+ "scope": "/read-limited",
8
+ "refresh_token": "456"
9
+ }
data/spec/fixtures/request_info.json ADDED
@@ -0,0 +1,213 @@
1
+ {
2
+ "name": {
3
+ "credit-name": {
4
+ "value": "Martin Fenner"
5
+ },
6
+ "family-name": {
7
+ "value": "Fenner"
8
+ },
9
+ "last-modified-date": {
10
+ "value": 1500190904445
11
+ },
12
+ "created-date": {
13
+ "value": 1460669194723
14
+ },
15
+ "visibility": "PUBLIC",
16
+ "source": null,
17
+ "given-names": {
18
+ "value": "Martin"
19
+ },
20
+ "path": "0000-0001-6528-2027"
21
+ },
22
+ "last-modified-date": {
23
+ "value": 1500192622273
24
+ },
25
+ "researcher-urls": {
26
+ "researcher-url": [
27
+ {
28
+ "put-code": 42818,
29
+ "last-modified-date": {
30
+ "value": 1500192382862
31
+ },
32
+ "url": {
33
+ "value": "http://blog.martinfenner.org"
34
+ },
35
+ "created-date": {
36
+ "value": 1500192382862
37
+ },
38
+ "visibility": "PUBLIC",
39
+ "source": {
40
+ "source-orcid": {
41
+ "path": "0000-0001-6528-2027",
42
+ "host": "sandbox.orcid.org",
43
+ "uri": "http://sandbox.orcid.org/0000-0001-6528-2027"
44
+ },
45
+ "source-name": {
46
+ "value": "Martin Fenner"
47
+ },
48
+ "source-client-id": null
49
+ },
50
+ "display-index": 1,
51
+ "path": "/0000-0001-6528-2027/researcher-urls/42818",
52
+ "url-name": "Blog"
53
+ }
54
+ ],
55
+ "path": "/0000-0001-6528-2027/researcher-urls",
56
+ "last-modified-date": {
57
+ "value": 1500192382862
58
+ }
59
+ },
60
+ "other-names": {
61
+ "other-name": [
62
+ {
63
+ "put-code": 16403,
64
+ "last-modified-date": {
65
+ "value": 1500191301536
66
+ },
67
+ "created-date": {
68
+ "value": 1500191301534
69
+ },
70
+ "visibility": "PUBLIC",
71
+ "content": "Martin Hellmut Fenner",
72
+ "source": {
73
+ "source-orcid": {
74
+ "path": "0000-0001-6528-2027",
75
+ "host": "sandbox.orcid.org",
76
+ "uri": "http://sandbox.orcid.org/0000-0001-6528-2027"
77
+ },
78
+ "source-name": {
79
+ "value": "Martin Fenner"
80
+ },
81
+ "source-client-id": null
82
+ },
83
+ "display-index": 1,
84
+ "path": "/0000-0001-6528-2027/other-names/16403"
85
+ }
86
+ ],
87
+ "path": "/0000-0001-6528-2027/other-names",
88
+ "last-modified-date": {
89
+ "value": 1500191301536
90
+ }
91
+ },
92
+ "keywords": {
93
+ "path": "/0000-0001-6528-2027/keywords",
94
+ "keyword": [],
95
+ "last-modified-date": null
96
+ },
97
+ "path": "/0000-0001-6528-2027/person",
98
+ "external-identifiers": {
99
+ "path": "/0000-0001-6528-2027/external-identifiers",
100
+ "external-identifier": [
101
+ {
102
+ "external-id-value": "mfenner",
103
+ "put-code": 3882,
104
+ "last-modified-date": {
105
+ "value": 1499800792190
106
+ },
107
+ "external-id-relationship": "SELF",
108
+ "created-date": {
109
+ "value": 1499800792189
110
+ },
111
+ "external-id-type": "GitHub",
112
+ "source": {
113
+ "source-orcid": null,
114
+ "source-name": {
115
+ "value": "DataCite/ORCID Claim Tool"
116
+ },
117
+ "source-client-id": {
118
+ "path": "APP-127VAQ5PMV9OKM9F",
119
+ "host": "sandbox.orcid.org",
120
+ "uri": "http://sandbox.orcid.org/client/APP-127VAQ5PMV9OKM9F"
121
+ }
122
+ },
123
+ "visibility": "PUBLIC",
124
+ "display-index": 0,
125
+ "path": "/0000-0001-6528-2027/external-identifiers/3882",
126
+ "external-id-url": {
127
+ "value": "https://github.com/mfenner"
128
+ }
129
+ }
130
+ ],
131
+ "last-modified-date": {
132
+ "value": 1499800792190
133
+ }
134
+ },
135
+ "emails": {
136
+ "path": "/0000-0001-6528-2027/email",
137
+ "email": [
138
+ {
139
+ "put-code": null,
140
+ "verified": true,
141
+ "last-modified-date": {
142
+ "value": 1438058214424
143
+ },
144
+ "created-date": {
145
+ "value": 1438058214424
146
+ },
147
+ "visibility": "LIMITED",
148
+ "primary": true,
149
+ "source": {
150
+ "source-orcid": {
151
+ "path": "0000-0001-6528-2027",
152
+ "host": "sandbox.orcid.org",
153
+ "uri": "http://sandbox.orcid.org/0000-0001-6528-2027"
154
+ },
155
+ "source-name": {
156
+ "value": "Martin Fenner"
157
+ },
158
+ "source-client-id": null
159
+ },
160
+ "path": null,
161
+ "email": "martin.fenner@datacite.org"
162
+ }
163
+ ],
164
+ "last-modified-date": {
165
+ "value": 1438058214424
166
+ }
167
+ },
168
+ "biography": {
169
+ "content": "Martin Fenner is the DataCite Technical Director and manages the technical architecture for Datacite as well as DataCite\u2019s technical contributions for the EU-funded THOR project. From 2012 to 2015 he was the technical lead for the PLOS Article-Level Metrics project. Martin has a medical degree from the Free University of Berlin and is a Board-certified medical oncologist.",
170
+ "path": "/0000-0001-6528-2027/biography",
171
+ "created-date": {
172
+ "value": 1460669194724
173
+ },
174
+ "visibility": "PUBLIC",
175
+ "last-modified-date": {
176
+ "value": 1500206408067
177
+ }
178
+ },
179
+ "addresses": {
180
+ "path": "/0000-0001-6528-2027/address",
181
+ "address": [
182
+ {
183
+ "put-code": 4809,
184
+ "last-modified-date": {
185
+ "value": 1500192622273
186
+ },
187
+ "country": {
188
+ "value": "DE"
189
+ },
190
+ "created-date": {
191
+ "value": 1500192622273
192
+ },
193
+ "visibility": "PUBLIC",
194
+ "source": {
195
+ "source-orcid": {
196
+ "path": "0000-0001-6528-2027",
197
+ "host": "sandbox.orcid.org",
198
+ "uri": "http://sandbox.orcid.org/0000-0001-6528-2027"
199
+ },
200
+ "source-name": {
201
+ "value": "Martin Fenner"
202
+ },
203
+ "source-client-id": null
204
+ },
205
+ "display-index": 1,
206
+ "path": "/0000-0001-6528-2027/address/4809"
207
+ }
208
+ ],
209
+ "last-modified-date": {
210
+ "value": 1500192622273
211
+ }
212
+ }
213
+ }
data/spec/omniauth/strategies/globus_spec.rb ADDED
@@ -0,0 +1,514 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'json'
5
+ require 'omniauth-globus'
6
+ require 'stringio'
7
+
8
+ describe OmniAuth::Strategies::Globus do
9
+ let(:request) { double('Request', params: {}, cookies: {}, env: {}) }
10
+ let(:app) do
11
+ lambda do
12
+ [200, {}, ['Hello.']]
13
+ end
14
+ end
15
+
16
+ subject do
17
+ OmniAuth::Strategies::Globus.new(app, 'appid', 'secret', @options || {}).tap do |strategy|
18
+ allow(strategy).to receive(:request) do
19
+ request
20
+ end
21
+ end
22
+ end
23
+
24
+ before do
25
+ OmniAuth.config.test_mode = true
26
+ end
27
+
28
+ after do
29
+ OmniAuth.config.test_mode = false
30
+ end
31
+
32
+ describe '#client_options' do
33
+ it 'has correct site' do
34
+ expect(subject.client.site).to eq('https://auth.globus.org')
35
+ end
36
+
37
+ it 'has correct authorize_url' do
38
+ expect(subject.client.options[:authorize_url]).to eq('https://auth.globus.org/v2/oauth2/authorize')
39
+ end
40
+
41
+ it 'has correct token_url' do
42
+ expect(subject.client.options[:token_url]).to eq('https://auth.globus.org/v2/oauth2/token')
43
+ end
44
+
45
+ describe 'overrides' do
46
+ context 'as strings' do
47
+ it 'should allow overriding the site' do
48
+ @options = { client_options: { 'site' => 'https://example.com' } }
49
+ expect(subject.client.site).to eq('https://example.com')
50
+ end
51
+
52
+ it 'should allow overriding the authorization_endpoint' do
53
+ @options = { client_options: { 'authorization_endpoint' => 'https://example.com' } }
54
+ expect(subject.client.options[:authorization_endpoint]).to eq('https://example.com')
55
+ end
56
+
57
+ it 'should allow overriding the token_endpoint' do
58
+ @options = { client_options: { 'token_endpoint' => 'https://example.com' } }
59
+ expect(subject.client.options[:token_endpoint]).to eq('https://example.com')
60
+ end
61
+ end
62
+
63
+ context 'as symbols' do
64
+ it 'should allow overriding the site' do
65
+ @options = { client_options: { site: 'https://example.com' } }
66
+ expect(subject.client.site).to eq('https://example.com')
67
+ end
68
+
69
+ it 'should allow overriding the authorization_endpoint' do
70
+ @options = { client_options: { authorization_endpoint: 'https://example.com' } }
71
+ expect(subject.client.options[:authorization_endpoint]).to eq('https://example.com')
72
+ end
73
+
74
+ it 'should allow overriding the token_endpoint' do
75
+ @options = { client_options: { token_endpoint: 'https://example.com' } }
76
+ expect(subject.client.options[:token_endpoint]).to eq('https://example.com')
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '#authorize_options' do
83
+ %i[access_type login_hint prompt scope state device_id device_name].each do |k|
84
+ it "should support #{k}" do
85
+ @options = { k => 'http://someval' }
86
+ expect(subject.authorize_params[k.to_s]).to eq('http://someval')
87
+ end
88
+ end
89
+
90
+ describe 'redirect_uri' do
91
+ it 'should default to nil' do
92
+ @options = {}
93
+ expect(subject.authorize_params['redirect_uri']).to eq(nil)
94
+ end
95
+
96
+ it 'should set the redirect_uri parameter if present' do
97
+ @options = { redirect_uri: 'https://example.com' }
98
+ expect(subject.authorize_params['redirect_uri']).to eq('https://example.com')
99
+ end
100
+ end
101
+
102
+ describe 'access_type' do
103
+ it 'should default to "offline"' do
104
+ @options = {}
105
+ expect(subject.authorize_params['access_type']).to eq('offline')
106
+ end
107
+
108
+ it 'should set the access_type parameter if present' do
109
+ @options = { access_type: 'online' }
110
+ expect(subject.authorize_params['access_type']).to eq('online')
111
+ end
112
+ end
113
+
114
+ describe 'login_hint' do
115
+ it 'should default to nil' do
116
+ expect(subject.authorize_params['login_hint']).to eq(nil)
117
+ end
118
+
119
+ it 'should set the login_hint parameter if present' do
120
+ @options = { login_hint: 'john@example.com' }
121
+ expect(subject.authorize_params['login_hint']).to eq('john@example.com')
122
+ end
123
+ end
124
+
125
+ describe 'prompt' do
126
+ it 'should default to nil' do
127
+ expect(subject.authorize_params['prompt']).to eq(nil)
128
+ end
129
+
130
+ it 'should set the prompt parameter if present' do
131
+ @options = { prompt: 'consent select_account' }
132
+ expect(subject.authorize_params['prompt']).to eq('consent select_account')
133
+ end
134
+ end
135
+
136
+ describe 'request_visible_actions' do
137
+ it 'should default to nil' do
138
+ expect(subject.authorize_params['request_visible_actions']).to eq(nil)
139
+ end
140
+
141
+ it 'should set the request_visible_actions parameter if present' do
142
+ @options = { request_visible_actions: 'something' }
143
+ expect(subject.authorize_params['request_visible_actions']).to eq('something')
144
+ end
145
+ end
146
+
147
+ describe 'include_granted_scopes' do
148
+ it 'should default to nil' do
149
+ expect(subject.authorize_params['include_granted_scopes']).to eq(nil)
150
+ end
151
+
152
+ it 'should set the include_granted_scopes parameter if present' do
153
+ @options = { include_granted_scopes: 'true' }
154
+ expect(subject.authorize_params['include_granted_scopes']).to eq('true')
155
+ end
156
+ end
157
+
158
+ describe 'scope' do
159
+ it 'should leave base scopes as is' do
160
+ @options = { scope: 'profile' }
161
+ expect(subject.authorize_params['scope']).to eq('profile')
162
+ end
163
+
164
+ it 'should join scopes' do
165
+ @options = { scope: 'profile,email' }
166
+ expect(subject.authorize_params['scope']).to eq('profile email')
167
+ end
168
+
169
+ it 'should deal with whitespace when joining scopes' do
170
+ @options = { scope: 'profile, email' }
171
+ expect(subject.authorize_params['scope']).to eq('profile email')
172
+ end
173
+
174
+ it 'should set default scope to openid, profile, email' do
175
+ expect(subject.authorize_params['scope']).to eq("openid profile email")
176
+ end
177
+
178
+ it 'should support space delimited scopes' do
179
+ @options = { scope: 'profile email' }
180
+ expect(subject.authorize_params['scope']).to eq('profile email')
181
+ end
182
+
183
+ it 'should support extremely badly formed scopes' do
184
+ @options = { scope: 'profile email,foo,steve yeah' }
185
+ expect(subject.authorize_params['scope']).to eq('profile email foo steve yeah')
186
+ end
187
+ end
188
+
189
+ describe 'state' do
190
+ it 'should set the state parameter' do
191
+ @options = { state: 'some_state' }
192
+ expect(subject.authorize_params['state']).to eq('some_state')
193
+ expect(subject.authorize_params[:state]).to eq('some_state')
194
+ expect(subject.session['omniauth.state']).to eq('some_state')
195
+ end
196
+
197
+ it 'should set the omniauth.state dynamically' do
198
+ allow(subject).to receive(:request) { double('Request', params: { 'state' => 'some_state' }, env: {}) }
199
+ expect(subject.authorize_params['state']).to eq('some_state')
200
+ expect(subject.authorize_params[:state]).to eq('some_state')
201
+ expect(subject.session['omniauth.state']).to eq('some_state')
202
+ end
203
+ end
204
+
205
+ describe 'overrides' do
206
+ it 'should include top-level options that are marked as :authorize_options' do
207
+ @options = { authorize_options: %i[scope foo request_visible_actions], scope: 'http://bar', foo: 'baz', request_visible_actions: 'something' }
208
+ expect(subject.authorize_params['scope']).to eq('http://bar')
209
+ expect(subject.authorize_params['foo']).to eq('baz')
210
+ expect(subject.authorize_params['request_visible_actions']).to eq('something')
211
+ end
212
+
213
+ describe 'request overrides' do
214
+ %i[access_type login_hint prompt scope state].each do |k|
215
+ context "authorize option #{k}" do
216
+ let(:request) { double('Request', params: { k.to_s => 'http://example.com' }, cookies: {}, env: {}) }
217
+
218
+ it "should set the #{k} authorize option dynamically in the request" do
219
+ @options = { k: '' }
220
+ expect(subject.authorize_params[k.to_s]).to eq('http://example.com')
221
+ end
222
+ end
223
+ end
224
+
225
+ describe 'custom authorize_options' do
226
+ let(:request) { double('Request', params: { 'foo' => 'something' }, cookies: {}, env: {}) }
227
+
228
+ it 'should support request overrides from custom authorize_options' do
229
+ @options = { authorize_options: [:foo], foo: '' }
230
+ expect(subject.authorize_params['foo']).to eq('something')
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ describe '#authorize_params' do
238
+ it 'should include any authorize params passed in the :authorize_params option' do
239
+ @options = { authorize_params: { request_visible_actions: 'something', foo: 'bar', baz: 'zip' }, bad: 'not_included' }
240
+ expect(subject.authorize_params['request_visible_actions']).to eq('something')
241
+ expect(subject.authorize_params['foo']).to eq('bar')
242
+ expect(subject.authorize_params['baz']).to eq('zip')
243
+ expect(subject.authorize_params['bad']).to eq(nil)
244
+ end
245
+ end
246
+
247
+ describe '#token_params' do
248
+ it 'should include any token params passed in the :token_params option' do
249
+ @options = { token_params: { foo: 'bar', baz: 'zip' } }
250
+ expect(subject.token_params['foo']).to eq('bar')
251
+ expect(subject.token_params['baz']).to eq('zip')
252
+ end
253
+ end
254
+
255
+ describe '#token_options' do
256
+ it 'should include top-level options that are marked as :token_options' do
257
+ @options = { token_options: %i[scope foo], scope: 'bar', foo: 'baz', bad: 'not_included' }
258
+ expect(subject.token_params['scope']).to eq('bar')
259
+ expect(subject.token_params['foo']).to eq('baz')
260
+ expect(subject.token_params['bad']).to eq(nil)
261
+ end
262
+ end
263
+
264
+ describe '#callback_path' do
265
+ it 'has the correct default callback path' do
266
+ expect(subject.callback_path).to eq("/auth/globus/callback")
267
+ end
268
+
269
+ it 'should set the callback_path parameter if present' do
270
+ @options = { callback_path: '/auth/foo/callback' }
271
+ expect(subject.callback_path).to eq('/auth/foo/callback')
272
+ end
273
+ end
274
+
275
+ describe '#info' do
276
+ let(:client) do
277
+ OAuth2::Client.new('abc', 'def') do |builder|
278
+ builder.request :url_encoded
279
+ builder.adapter :test do |stub|
280
+ stub.get("/v2/oauth2/userinfo") { [200, { "content-type" => "application/json" }, response_hash.to_json] }
281
+ end
282
+ end
283
+ end
284
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, {}) }
285
+ before { allow(subject).to receive(:access_token).and_return(access_token) }
286
+
287
+ context 'with email' do
288
+ let(:response_hash) do
289
+ { email: 'something@domain.invalid' }
290
+ end
291
+
292
+ it 'should return email' do
293
+ expect(subject.info[:email]).to eq('something@domain.invalid')
294
+ end
295
+ end
296
+ end
297
+
298
+ describe '#extra' do
299
+ let(:client) do
300
+ OAuth2::Client.new('abc', 'def') do |builder|
301
+ builder.request :url_encoded
302
+ builder.adapter :test do |stub|
303
+ stub.get('/v2/oauth2/userinfo') { [200, { 'content-type' => 'application/json' }, '{"sub": "12345"}'] }
304
+ end
305
+ end
306
+ end
307
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, {}) }
308
+
309
+ before { allow(subject).to receive(:access_token).and_return(access_token) }
310
+
311
+ describe 'id_token' do
312
+ shared_examples 'id_token issued by valid issuer' do |issuer| # rubocop:disable Metrics/BlockLength
313
+ context 'when the id_token is passed into the access token' do
314
+ let(:token_info) do
315
+ {
316
+ identity_provider_display_name: "ORCID",
317
+ sub: "abc",
318
+ preferred_username: "0000-0003-1419-2405@orcid.org",
319
+ identity_provider: "def",
320
+ organization: "DataCite",
321
+ email: "mfenner@datacite.org",
322
+ name: "Martin Fenner",
323
+ exp: Time.now.to_i + 3600,
324
+ iss: "https://auth.globus.org"
325
+ }
326
+ end
327
+ let(:id_token) { JWT.encode(token_info, 'secret') }
328
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, 'id_token' => id_token) }
329
+
330
+ it 'should include id_token when set on the access_token' do
331
+ expect(subject.extra).to include(id_token: id_token)
332
+ end
333
+ end
334
+ end
335
+
336
+ it_behaves_like 'id_token issued by valid issuer', 'https://auth.globus.org'
337
+
338
+ context 'when the id_token is issued by an invalid issuer' do
339
+ let(:token_info) do
340
+ {
341
+ identity_provider_display_name: "ORCID",
342
+ sub: "abc",
343
+ preferred_username: "0000-0003-1419-2405@orcid.org",
344
+ identity_provider: "def",
345
+ organization: "DataCite",
346
+ email: "mfenner@datacite.org",
347
+ name: "Martin Fenner",
348
+ exp: Time.now.to_i + 3600,
349
+ iss: "https://fake.globus.org"
350
+ }
351
+ end
352
+ let(:id_token) { JWT.encode(token_info, 'secret') }
353
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, 'id_token' => id_token) }
354
+
355
+ it 'raises JWT::InvalidIssuerError' do
356
+ expect { subject.extra }.to raise_error(JWT::InvalidIssuerError)
357
+ end
358
+ end
359
+
360
+ context 'when the id_token is missing' do
361
+ it 'should not include id_token' do
362
+ expect(subject.extra).not_to have_key(:id_token)
363
+ end
364
+
365
+ it 'should not include id_info' do
366
+ expect(subject.extra).not_to have_key(:id_info)
367
+ end
368
+ end
369
+ end
370
+
371
+ describe 'raw_info' do
372
+ context 'when skip_info is true' do
373
+ before { subject.options[:skip_info] = true }
374
+
375
+ it 'should not include raw_info' do
376
+ expect(subject.extra).not_to have_key(:raw_info)
377
+ end
378
+ end
379
+
380
+ context 'when skip_info is false' do
381
+ before { subject.options[:skip_info] = false }
382
+
383
+ it 'should include raw_info' do
384
+ expect(subject.extra[:raw_info]).to eq('sub' => '12345')
385
+ end
386
+ end
387
+ end
388
+ end
389
+
390
+ describe 'build_access_token' do
391
+ it 'should use a hybrid authorization request_uri if this is an AJAX request with a code parameter' do
392
+ allow(request).to receive(:xhr?).and_return(true)
393
+ allow(request).to receive(:params).and_return('code' => 'valid_code')
394
+
395
+ client = double(:client)
396
+ auth_code = double(:auth_code)
397
+ allow(client).to receive(:auth_code).and_return(auth_code)
398
+ expect(subject).to receive(:client).and_return(client)
399
+ expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: 'postmessage' }, {})
400
+
401
+ expect(subject).not_to receive(:orig_build_access_token)
402
+ subject.build_access_token
403
+ end
404
+
405
+ it 'should use a hybrid authorization request_uri if this is an AJAX request (mobile) with a code parameter' do
406
+ allow(request).to receive(:xhr?).and_return(true)
407
+ allow(request).to receive(:params).and_return('code' => 'valid_code', 'redirect_uri' => '')
408
+
409
+ client = double(:client)
410
+ auth_code = double(:auth_code)
411
+ allow(client).to receive(:auth_code).and_return(auth_code)
412
+ expect(subject).to receive(:client).and_return(client)
413
+ expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: '' }, {})
414
+
415
+ expect(subject).not_to receive(:orig_build_access_token)
416
+ subject.build_access_token
417
+ end
418
+
419
+ it 'should use the request_uri from params if this not an AJAX request (request from installed app) with a code parameter' do
420
+ allow(request).to receive(:xhr?).and_return(false)
421
+ allow(request).to receive(:params).and_return('code' => 'valid_code', 'redirect_uri' => 'redirect_uri')
422
+
423
+ client = double(:client)
424
+ auth_code = double(:auth_code)
425
+ allow(client).to receive(:auth_code).and_return(auth_code)
426
+ expect(subject).to receive(:client).and_return(client)
427
+ expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: 'redirect_uri' }, {})
428
+
429
+ expect(subject).not_to receive(:orig_build_access_token)
430
+ subject.build_access_token
431
+ end
432
+
433
+ it 'should read access_token from hash if this is not an AJAX request with a code parameter' do
434
+ allow(request).to receive(:xhr?).and_return(false)
435
+ allow(request).to receive(:params).and_return('access_token' => 'valid_access_token')
436
+ expect(subject).to receive(:verify_token).with('valid_access_token').and_return true
437
+ expect(subject).to receive(:client).and_return(:client)
438
+
439
+ token = subject.build_access_token
440
+ expect(token).to be_instance_of(::OAuth2::AccessToken)
441
+ expect(token.token).to eq('valid_access_token')
442
+ expect(token.client).to eq(:client)
443
+ end
444
+
445
+ it 'reads the code from a json request body' do
446
+ body = StringIO.new(%({"code":"json_access_token"}))
447
+ client = double(:client)
448
+ auth_code = double(:auth_code)
449
+
450
+ allow(request).to receive(:xhr?).and_return(false)
451
+ allow(request).to receive(:content_type).and_return('application/json')
452
+ allow(request).to receive(:body).and_return(body)
453
+ allow(client).to receive(:auth_code).and_return(auth_code)
454
+ expect(subject).to receive(:client).and_return(client)
455
+
456
+ expect(auth_code).to receive(:get_token).with('json_access_token', { redirect_uri: 'postmessage' }, {})
457
+
458
+ subject.build_access_token
459
+ end
460
+
461
+ it 'should use callback_url without query_string if this is not an AJAX request' do
462
+ allow(request).to receive(:xhr?).and_return(false)
463
+ allow(request).to receive(:params).and_return('code' => 'valid_code')
464
+ allow(request).to receive(:content_type).and_return('application/x-www-form-urlencoded')
465
+
466
+ client = double(:client)
467
+ auth_code = double(:auth_code)
468
+ allow(client).to receive(:auth_code).and_return(auth_code)
469
+ allow(subject).to receive(:callback_url).and_return('redirect_uri_without_query_string')
470
+
471
+ expect(subject).to receive(:client).and_return(client)
472
+ expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: 'redirect_uri_without_query_string' }, {})
473
+ subject.build_access_token
474
+ end
475
+ end
476
+
477
+ describe 'verify_token' do
478
+ before(:each) do
479
+ subject.options.client_options[:connection_build] = proc do |builder|
480
+ builder.request :url_encoded
481
+ builder.adapter :test do |stub|
482
+ stub.get('/v2/oauth2/userinfo?access_token=valid_access_token') do
483
+ [200, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump(
484
+ {
485
+ identity_provider_display_name: "ORCID",
486
+ sub: "abc",
487
+ preferred_username: "0000-0003-1419-2405@orcid.org",
488
+ identity_provider: "def",
489
+ organization: "DataCite",
490
+ email: "mfenner@datacite.org",
491
+ name: "Martin Fenner",
492
+ exp: 1568009648,
493
+ iss: "https://auth.globus.org"
494
+ }
495
+ )]
496
+ end
497
+ stub.get('/v2/oauth2/userinfo?access_token=invalid_access_token') do
498
+ [400, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump(error_description: 'Invalid Value')]
499
+ end
500
+ end
501
+ end
502
+ end
503
+
504
+ # it 'should verify token if access_token is valid' do
505
+ # expect(subject.send(:verify_token, 'valid_access_token')).to eq(true)
506
+ # end
507
+
508
+ it 'should raise error if access_token is invalid' do
509
+ expect do
510
+ subject.send(:verify_token, 'invalid_access_token')
511
+ end.to raise_error(OAuth2::Error)
512
+ end
513
+ end
514
+ end
data/spec/rubocop_spec.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'spec_helper'
4
+
5
+ describe 'Rubocop' do
6
+ it 'should pass with no offenses detected' do
7
+ expect(`rubocop`).to include('no offenses detected')
8
+ end
9
+ end
data/spec/spec_helper.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ Bundler.setup
5
+
6
+ require "simplecov"
7
+ SimpleCov.start
8
+
9
+ require "rspec"
10
+ require "rack/test"
11
+ require "webmock/rspec"
12
+ require "omniauth-globus"
13
+
14
+ def fixture_path
15
+ File.expand_path("fixtures", __dir__) + "/"
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.include WebMock::API
20
+ config.include Rack::Test::Methods
21
+ config.extend OmniAuth::Test::StrategyMacros, type: :strategy
22
+ config.expect_with :rspec do |c|
23
+ c.syntax = :expect
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-globus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.3
5
+ platform: ruby
6
+ authors:
7
+ - Martin Fenner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-09-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jwt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: omniauth
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: omniauth-oauth2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: codeclimate-test-reporter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rack-test
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.6.3
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.6.3
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.4'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.4'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.68'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.68'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 3.0.1
149
+ type: :development
150
+ prerelease: false
151
+ version_requirements: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - "~>"
154
+ - !ruby/object:Gem::Version
155
+ version: '3.0'
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: 3.0.1
159
+ description: Enables third-party client apps to authenticate with the Globus Auth
160
+ service via OpenID Connect
161
+ email:
162
+ - mfenner@datacite.org
163
+ executables: []
164
+ extensions: []
165
+ extra_rdoc_files:
166
+ - README.md
167
+ files:
168
+ - ".gitignore"
169
+ - ".rubocop.yml"
170
+ - ".travis.yml"
171
+ - Gemfile
172
+ - Gemfile.lock
173
+ - LICENSE
174
+ - README.md
175
+ - lib/omniauth-globus.rb
176
+ - lib/omniauth/globus.rb
177
+ - lib/omniauth/globus/version.rb
178
+ - lib/omniauth/strategies/globus.rb
179
+ - omniauth-globus.gemspec
180
+ - spec/fixtures/access_token.json
181
+ - spec/fixtures/request_info.json
182
+ - spec/omniauth/strategies/globus_spec.rb
183
+ - spec/rubocop_spec.rb
184
+ - spec/spec_helper.rb
185
+ homepage: https://github.com/datacite/omniauth-globus
186
+ licenses:
187
+ - MIT
188
+ metadata: {}
189
+ post_install_message:
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: '2.3'
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubygems_version: 3.0.1
205
+ signing_key:
206
+ specification_version: 4
207
+ summary: Globus Auth OpenId connect Strategy for OmniAuth 1.0
208
+ test_files:
209
+ - spec/fixtures/access_token.json
210
+ - spec/fixtures/request_info.json
211
+ - spec/omniauth/strategies/globus_spec.rb
212
+ - spec/rubocop_spec.rb
213
+ - spec/spec_helper.rb