omniauth-microsoft_graph 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of omniauth-microsoft_graph might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/Rakefile +5 -7
- data/lib/omniauth/microsoft_graph/version.rb +1 -1
- data/lib/omniauth/strategies/microsoft_graph.rb +87 -28
- data/omniauth-microsoft_graph.gemspec +6 -6
- data/spec/omniauth/strategies/microsoft_graph_oauth2_spec.rb +443 -0
- data/spec/spec_helper.rb +4 -0
- metadata +41 -21
- data/test/strategy_test.rb +0 -26
- data/test/test_helper.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5fb7e4f89d46058d18c857dbe0935cbf720c802
|
4
|
+
data.tar.gz: 29291329512d0d36bad6e0ff1a3917f842a4b83c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 339872f74676b8269b0a92c46f56bdb8b350a6fe64df495bd23a8580ed45645b258bd181bbb82ab68faa362de0a9b1823bd25e71a0ba60a629d4e517fe33cbf5
|
7
|
+
data.tar.gz: b562fde6e3529990b1caa343c7044f7ae3dd02d66c41065aa6332c8282290397f21a310080f109581784dbd58b91f3ea2a5662518326705e47fac38727de4ef4
|
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
|
2
|
-
require "rake/testtask"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
t.test_files = FileList['test/*_test.rb']
|
7
|
-
end
|
3
|
+
require File.join('bundler', 'gem_tasks')
|
4
|
+
require File.join('rspec', 'core', 'rake_task')
|
8
5
|
|
9
|
-
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
7
|
|
8
|
+
task default: :spec
|
@@ -3,64 +3,123 @@ require 'omniauth-oauth2'
|
|
3
3
|
module OmniAuth
|
4
4
|
module Strategies
|
5
5
|
class MicrosoftGraph < OmniAuth::Strategies::OAuth2
|
6
|
+
BASE_SCOPE_URL = 'https://graph.microsoft.com/'
|
7
|
+
BASE_SCOPES = %w[offline_access openid email profile].freeze
|
8
|
+
DEFAULT_SCOPE = 'openid email profile User.Read'.freeze
|
9
|
+
|
6
10
|
option :name, :microsoft_graph
|
7
11
|
|
8
12
|
option :client_options, {
|
9
|
-
site: 'https://login.microsoftonline.com',
|
10
|
-
token_url: '
|
11
|
-
authorize_url: '
|
13
|
+
site: 'https://login.microsoftonline.com/',
|
14
|
+
token_url: 'common/oauth2/v2.0/token',
|
15
|
+
authorize_url: 'common/oauth2/v2.0/authorize'
|
16
|
+
}
|
17
|
+
|
18
|
+
option :authorize_options, %i[state callback_url access_type display score auth_type scope prompt login_hint domain_hint response_mode]
|
19
|
+
|
20
|
+
option :token_params, {
|
12
21
|
}
|
13
22
|
|
14
|
-
option :
|
23
|
+
option :scope, DEFAULT_SCOPE
|
24
|
+
option :authorized_client_ids, []
|
15
25
|
|
16
26
|
uid { raw_info["id"] }
|
17
27
|
|
18
28
|
info do
|
19
29
|
{
|
20
|
-
email
|
21
|
-
first_name
|
22
|
-
last_name
|
23
|
-
name
|
24
|
-
nickname
|
30
|
+
'email' => raw_info["mail"],
|
31
|
+
'first_name' => raw_info["givenName"],
|
32
|
+
'last_name' => raw_info["surname"],
|
33
|
+
'name' => [raw_info["givenName"], raw_info["surname"]].join(' '),
|
34
|
+
'nickname' => raw_info["displayName"],
|
25
35
|
}
|
26
36
|
end
|
27
37
|
|
28
38
|
extra do
|
29
39
|
{
|
30
40
|
'raw_info' => raw_info,
|
31
|
-
'params' => access_token.params
|
41
|
+
'params' => access_token.params,
|
42
|
+
'aud' => options.client_id
|
32
43
|
}
|
33
44
|
end
|
34
|
-
|
35
|
-
def callback_url
|
36
|
-
options[:redirect_uri] || (full_host + script_name + callback_path)
|
37
|
-
end
|
38
|
-
|
39
|
-
def raw_info
|
40
|
-
@raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me').parsed
|
41
|
-
end
|
42
45
|
|
43
46
|
def authorize_params
|
44
47
|
super.tap do |params|
|
45
|
-
|
46
|
-
|
47
|
-
params[v.to_sym] = request.params[v]
|
48
|
-
end
|
48
|
+
options[:authorize_options].each do |k|
|
49
|
+
params[k] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
|
49
50
|
end
|
51
|
+
|
52
|
+
params[:scope] = get_scope(params)
|
53
|
+
params[:access_type] = 'offline' if params[:access_type].nil?
|
54
|
+
|
55
|
+
session['omniauth.state'] = params[:state] if params[:state]
|
50
56
|
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def raw_info
|
60
|
+
@raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me').parsed
|
51
61
|
end
|
52
62
|
|
53
|
-
def
|
54
|
-
|
63
|
+
def callback_url
|
64
|
+
options[:callback_url] || full_host + script_name + callback_path
|
65
|
+
end
|
66
|
+
|
67
|
+
def custom_build_access_token
|
68
|
+
access_token = get_access_token(request)
|
69
|
+
access_token
|
55
70
|
end
|
56
71
|
|
57
|
-
|
58
|
-
|
72
|
+
alias build_access_token custom_build_access_token
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def get_access_token(request)
|
77
|
+
verifier = request.params['code']
|
78
|
+
redirect_uri = request.params['redirect_uri'] || request.params['callback_url']
|
79
|
+
if verifier && request.xhr?
|
80
|
+
client_get_token(verifier, redirect_uri || '/auth/microsoft_graph/callback')
|
81
|
+
elsif verifier
|
82
|
+
client_get_token(verifier, redirect_uri || callback_url)
|
83
|
+
elsif verify_token(request.params['access_token'])
|
59
84
|
::OAuth2::AccessToken.from_hash(client, request.params.dup)
|
60
|
-
|
61
|
-
|
85
|
+
elsif request.content_type =~ /json/i
|
86
|
+
begin
|
87
|
+
body = JSON.parse(request.body.read)
|
88
|
+
request.body.rewind # rewind request body for downstream middlewares
|
89
|
+
verifier = body && body['code']
|
90
|
+
client_get_token(verifier, '/auth/microsoft_graph/callback') if verifier
|
91
|
+
rescue JSON::ParserError => e
|
92
|
+
warn "[omniauth google-oauth2] JSON parse error=#{e}"
|
93
|
+
end
|
62
94
|
end
|
63
95
|
end
|
96
|
+
|
97
|
+
def client_get_token(verifier, redirect_uri)
|
98
|
+
client.auth_code.get_token(verifier, get_token_options(redirect_uri), get_token_params)
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_token_params
|
102
|
+
deep_symbolize(options.auth_token_params || {})
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_token_options(redirect_uri = '')
|
106
|
+
{ redirect_uri: redirect_uri }.merge(token_params.to_hash(symbolize_keys: true))
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_scope(params)
|
110
|
+
raw_scope = params[:scope] || DEFAULT_SCOPE
|
111
|
+
scope_list = raw_scope.split(' ').map { |item| item.split(',') }.flatten
|
112
|
+
scope_list.map! { |s| s =~ %r{^https?://} || BASE_SCOPES.include?(s) ? s : "#{BASE_SCOPE_URL}#{s}" }
|
113
|
+
scope_list.join(' ')
|
114
|
+
end
|
115
|
+
|
116
|
+
def verify_token(access_token)
|
117
|
+
return false unless access_token
|
118
|
+
# access_token.get('https://graph.microsoft.com/v1.0/me').parsed
|
119
|
+
raw_response = client.request(:get, 'https://graph.microsoft.com/v1.0/me',
|
120
|
+
params: { access_token: access_token }).parsed
|
121
|
+
(raw_response['aud'] == options.client_id) || options.authorized_client_ids.include?(raw_response['aud'])
|
122
|
+
end
|
64
123
|
end
|
65
124
|
end
|
66
125
|
end
|
@@ -18,10 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_runtime_dependency
|
22
|
-
|
23
|
-
spec.add_development_dependency "sinatra"
|
24
|
-
spec.add_development_dependency "rake"
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency "mocha"
|
21
|
+
spec.add_runtime_dependency 'omniauth', '~> 1.1', '>= 1.1.1'
|
22
|
+
spec.add_runtime_dependency 'omniauth-oauth2', '~> 1.6'
|
23
|
+
spec.add_development_dependency "sinatra", '~> 0'
|
24
|
+
spec.add_development_dependency "rake", '~> 0'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.6'
|
26
|
+
spec.add_development_dependency "mocha", '~> 0'
|
27
27
|
end
|
@@ -0,0 +1,443 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'json'
|
5
|
+
require 'omniauth_microsoft_graph'
|
6
|
+
require 'stringio'
|
7
|
+
|
8
|
+
describe OmniAuth::Strategies::MicrosoftGraph 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::MicrosoftGraph.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://login.microsoftonline.com/')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'has correct authorize_url' do
|
38
|
+
expect(subject.client.options[:authorize_url]).to eq('common/oauth2/v2.0/authorize')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'has correct token_url' do
|
42
|
+
expect(subject.client.options[:token_url]).to eq('common/oauth2/v2.0/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 authorize_url' do
|
53
|
+
@options = { client_options: { 'authorize_url' => 'https://example.com' } }
|
54
|
+
expect(subject.client.options[:authorize_url]).to eq('https://example.com')
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should allow overriding the token_url' do
|
58
|
+
@options = { client_options: { 'token_url' => 'https://example.com' } }
|
59
|
+
expect(subject.client.options[:token_url]).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 authorize_url' do
|
70
|
+
@options = { client_options: { authorize_url: 'https://example.com' } }
|
71
|
+
expect(subject.client.options[:authorize_url]).to eq('https://example.com')
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should allow overriding the token_url' do
|
75
|
+
@options = { client_options: { token_url: 'https://example.com' } }
|
76
|
+
expect(subject.client.options[:token_url]).to eq('https://example.com')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#authorize_options' do
|
83
|
+
%i[display score auth_type scope prompt login_hint domain_hint response_mode].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 'callback_url' do
|
91
|
+
it 'should default to nil' do
|
92
|
+
@options = {}
|
93
|
+
expect(subject.authorize_params['callback_url']).to eq(nil)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should set the callback_url parameter if present' do
|
97
|
+
@options = { callback_url: 'https://example.com' }
|
98
|
+
expect(subject.authorize_params['callback_url']).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 'scope' do
|
137
|
+
|
138
|
+
it 'should leave base scopes as is' do
|
139
|
+
@options = { scope: 'profile' }
|
140
|
+
expect(subject.authorize_params['scope']).to eq('profile')
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should join scopes' do
|
144
|
+
@options = { scope: 'profile,email' }
|
145
|
+
expect(subject.authorize_params['scope']).to eq('profile email')
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should deal with whitespace when joining scopes' do
|
149
|
+
@options = { scope: 'profile, email' }
|
150
|
+
expect(subject.authorize_params['scope']).to eq('profile email')
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should set default scope to email,profile' do
|
154
|
+
expect(subject.authorize_params['scope']).to eq('openid email profile https://graph.microsoft.com/User.Read')
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should support space delimited scopes' do
|
158
|
+
@options = { scope: 'profile email' }
|
159
|
+
expect(subject.authorize_params['scope']).to eq('profile email')
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should support extremely badly formed scopes' do
|
163
|
+
@options = { scope: 'profile email,foo,steve yeah http://example.com' }
|
164
|
+
expect(subject.authorize_params['scope']).to eq('profile email https://graph.microsoft.com/foo https://graph.microsoft.com/steve https://graph.microsoft.com/yeah http://example.com')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe 'state' do
|
169
|
+
it 'should set the state parameter' do
|
170
|
+
@options = { state: 'some_state' }
|
171
|
+
expect(subject.authorize_params['state']).to eq('some_state')
|
172
|
+
expect(subject.authorize_params[:state]).to eq('some_state')
|
173
|
+
expect(subject.session['omniauth.state']).to eq('some_state')
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should set the omniauth.state dynamically' do
|
177
|
+
allow(subject).to receive(:request) { double('Request', params: { 'state' => 'some_state' }, env: {}) }
|
178
|
+
expect(subject.authorize_params['state']).to eq('some_state')
|
179
|
+
expect(subject.authorize_params[:state]).to eq('some_state')
|
180
|
+
expect(subject.session['omniauth.state']).to eq('some_state')
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe 'overrides' do
|
185
|
+
it 'should include top-level options that are marked as :authorize_options' do
|
186
|
+
@options = { authorize_options: %i[scope foo request_visible_actions], scope: 'http://bar', foo: 'baz', hd: 'wow', request_visible_actions: 'something' }
|
187
|
+
expect(subject.authorize_params['scope']).to eq('http://bar')
|
188
|
+
expect(subject.authorize_params['foo']).to eq('baz')
|
189
|
+
expect(subject.authorize_params['hd']).to eq(nil)
|
190
|
+
expect(subject.authorize_params['request_visible_actions']).to eq('something')
|
191
|
+
end
|
192
|
+
|
193
|
+
describe 'request overrides' do
|
194
|
+
%i[access_type login_hint prompt scope state].each do |k|
|
195
|
+
context "authorize option #{k}" do
|
196
|
+
let(:request) { double('Request', params: { k.to_s => 'http://example.com' }, cookies: {}, env: {}) }
|
197
|
+
|
198
|
+
it "should set the #{k} authorize option dynamically in the request" do
|
199
|
+
@options = { k: '' }
|
200
|
+
expect(subject.authorize_params[k.to_s]).to eq('http://example.com')
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe 'custom authorize_options' do
|
206
|
+
let(:request) { double('Request', params: { 'foo' => 'something' }, cookies: {}, env: {}) }
|
207
|
+
|
208
|
+
it 'should support request overrides from custom authorize_options' do
|
209
|
+
@options = { authorize_options: [:foo], foo: '' }
|
210
|
+
expect(subject.authorize_params['foo']).to eq('something')
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe '#authorize_params' do
|
218
|
+
it 'should include any authorize params passed in the :authorize_params option' do
|
219
|
+
@options = { authorize_params: { request_visible_actions: 'something', foo: 'bar', baz: 'zip' }, hd: 'wow', bad: 'not_included' }
|
220
|
+
expect(subject.authorize_params['request_visible_actions']).to eq('something')
|
221
|
+
expect(subject.authorize_params['foo']).to eq('bar')
|
222
|
+
expect(subject.authorize_params['baz']).to eq('zip')
|
223
|
+
expect(subject.authorize_params['bad']).to eq(nil)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe '#token_params' do
|
228
|
+
it 'should include any token params passed in the :token_params option' do
|
229
|
+
@options = { token_params: { foo: 'bar', baz: 'zip' } }
|
230
|
+
expect(subject.token_params['foo']).to eq('bar')
|
231
|
+
expect(subject.token_params['baz']).to eq('zip')
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe '#token_options' do
|
236
|
+
it 'should include top-level options that are marked as :token_options' do
|
237
|
+
@options = { token_options: %i[scope foo], scope: 'bar', foo: 'baz', bad: 'not_included' }
|
238
|
+
expect(subject.token_params['scope']).to eq('bar')
|
239
|
+
expect(subject.token_params['foo']).to eq('baz')
|
240
|
+
expect(subject.token_params['bad']).to eq(nil)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe '#callback_path' do
|
245
|
+
it 'has the correct default callback path' do
|
246
|
+
expect(subject.callback_path).to eq('/auth/microsoft_graph/callback')
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'should set the callback_path parameter if present' do
|
250
|
+
@options = { callback_path: '/auth/foo/callback' }
|
251
|
+
expect(subject.callback_path).to eq('/auth/foo/callback')
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
describe '#info' do
|
256
|
+
let(:client) do
|
257
|
+
OAuth2::Client.new('abc', 'def') do |builder|
|
258
|
+
builder.request :url_encoded
|
259
|
+
builder.adapter :test do |stub|
|
260
|
+
stub.get('/v1.0/me') { [200, { 'content-type' => 'application/json' }, response_hash.to_json] }
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
let(:access_token) { OAuth2::AccessToken.from_hash(client, {}) }
|
265
|
+
before { allow(subject).to receive(:access_token).and_return(access_token) }
|
266
|
+
|
267
|
+
context 'with verified email' do
|
268
|
+
let(:response_hash) do
|
269
|
+
{ mail: 'something@domain.invalid' }
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should return equal email ' do
|
273
|
+
expect(subject.info['email']).to eq('something@domain.invalid')
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
describe '#extra' do
|
280
|
+
let(:client) do
|
281
|
+
OAuth2::Client.new('abc', 'def') do |builder|
|
282
|
+
builder.request :url_encoded
|
283
|
+
builder.adapter :test do |stub|
|
284
|
+
stub.get('/v1.0/me') { [200, { 'content-type' => 'application/json' }, '{"id": "12345"}'] }
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
let(:access_token) { OAuth2::AccessToken.from_hash(client, {}) }
|
289
|
+
|
290
|
+
before { allow(subject).to receive(:access_token).and_return(access_token) }
|
291
|
+
|
292
|
+
describe 'raw_info' do
|
293
|
+
it 'should include raw_info' do
|
294
|
+
expect(subject.extra['raw_info']).to eq('id' => '12345')
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
describe 'build_access_token' do
|
300
|
+
it 'should use a hybrid authorization request_uri if this is an AJAX request with a code parameter' do
|
301
|
+
allow(request).to receive(:scheme).and_return('https')
|
302
|
+
allow(request).to receive(:url).and_return('https://example.com')
|
303
|
+
allow(request).to receive(:xhr?).and_return(true)
|
304
|
+
allow(request).to receive(:params).and_return('code' => 'valid_code')
|
305
|
+
|
306
|
+
client = double(:client)
|
307
|
+
auth_code = double(:auth_code)
|
308
|
+
allow(client).to receive(:auth_code).and_return(auth_code)
|
309
|
+
expect(subject).to receive(:client).and_return(client)
|
310
|
+
expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: '/auth/microsoft_graph/callback' }, {})
|
311
|
+
|
312
|
+
expect(subject).not_to receive(:orig_build_access_token)
|
313
|
+
subject.instance_variable_set("@env", {})
|
314
|
+
subject.send(:build_access_token)
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'should use a hybrid authorization request_uri if this is an AJAX request (mobile) with a code parameter' do
|
318
|
+
allow(request).to receive(:scheme).and_return('https')
|
319
|
+
allow(request).to receive(:url).and_return('https://example.com')
|
320
|
+
allow(request).to receive(:xhr?).and_return(true)
|
321
|
+
allow(request).to receive(:params).and_return('code' => 'valid_code', 'callback_url' => 'localhost')
|
322
|
+
|
323
|
+
client = double(:client)
|
324
|
+
auth_code = double(:auth_code)
|
325
|
+
allow(client).to receive(:auth_code).and_return(auth_code)
|
326
|
+
expect(subject).to receive(:client).and_return(client)
|
327
|
+
expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: 'localhost' }, {})
|
328
|
+
|
329
|
+
expect(subject).not_to receive(:orig_build_access_token)
|
330
|
+
subject.instance_variable_set("@env", {})
|
331
|
+
subject.send(:build_access_token)
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'should use the request_uri from params if this not an AJAX request (request from installed app) with a code parameter' do
|
335
|
+
allow(request).to receive(:scheme).and_return('https')
|
336
|
+
allow(request).to receive(:url).and_return('https://example.com')
|
337
|
+
allow(request).to receive(:xhr?).and_return(false)
|
338
|
+
allow(request).to receive(:params).and_return('code' => 'valid_code', 'callback_url' => 'callback_url')
|
339
|
+
|
340
|
+
client = double(:client)
|
341
|
+
auth_code = double(:auth_code)
|
342
|
+
allow(client).to receive(:auth_code).and_return(auth_code)
|
343
|
+
expect(subject).to receive(:client).and_return(client)
|
344
|
+
expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: 'callback_url' }, {})
|
345
|
+
|
346
|
+
expect(subject).not_to receive(:orig_build_access_token)
|
347
|
+
subject.send(:build_access_token)
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'should read access_token from hash if this is not an AJAX request with a code parameter' do
|
351
|
+
allow(request).to receive(:scheme).and_return('https')
|
352
|
+
allow(request).to receive(:url).and_return('https://example.com')
|
353
|
+
allow(request).to receive(:xhr?).and_return(false)
|
354
|
+
allow(request).to receive(:params).and_return('access_token' => 'valid_access_token')
|
355
|
+
expect(subject).to receive(:verify_token).with('valid_access_token').and_return true
|
356
|
+
expect(subject).to receive(:client).and_return(:client)
|
357
|
+
|
358
|
+
token = subject.send(:build_access_token)
|
359
|
+
expect(token).to be_instance_of(::OAuth2::AccessToken)
|
360
|
+
expect(token.token).to eq('valid_access_token')
|
361
|
+
expect(token.client).to eq(:client)
|
362
|
+
end
|
363
|
+
|
364
|
+
it 'reads the code from a json request body' do
|
365
|
+
body = StringIO.new(%({"code":"json_access_token"}))
|
366
|
+
client = double(:client)
|
367
|
+
auth_code = double(:auth_code)
|
368
|
+
|
369
|
+
allow(request).to receive(:scheme).and_return('https')
|
370
|
+
allow(request).to receive(:url).and_return('https://example.com')
|
371
|
+
allow(request).to receive(:xhr?).and_return(false)
|
372
|
+
allow(request).to receive(:content_type).and_return('application/json')
|
373
|
+
allow(request).to receive(:body).and_return(body)
|
374
|
+
allow(client).to receive(:auth_code).and_return(auth_code)
|
375
|
+
expect(subject).to receive(:client).and_return(client)
|
376
|
+
|
377
|
+
expect(auth_code).to receive(:get_token).with('json_access_token', { redirect_uri: '/auth/microsoft_graph/callback' }, {})
|
378
|
+
|
379
|
+
subject.send(:build_access_token)
|
380
|
+
end
|
381
|
+
|
382
|
+
it 'should use callback_url without query_string if this is not an AJAX request' do
|
383
|
+
allow(request).to receive(:scheme).and_return('https')
|
384
|
+
allow(request).to receive(:url).and_return('https://example.com')
|
385
|
+
allow(request).to receive(:xhr?).and_return(false)
|
386
|
+
allow(request).to receive(:params).and_return('code' => 'valid_code')
|
387
|
+
allow(request).to receive(:content_type).and_return('application/x-www-form-urlencoded')
|
388
|
+
|
389
|
+
client = double(:client)
|
390
|
+
auth_code = double(:auth_code)
|
391
|
+
allow(client).to receive(:auth_code).and_return(auth_code)
|
392
|
+
allow(subject).to receive(:callback_url).and_return('callback_url_without_query_string')
|
393
|
+
|
394
|
+
expect(subject).to receive(:client).and_return(client)
|
395
|
+
expect(auth_code).to receive(:get_token).with('valid_code', { redirect_uri: 'callback_url_without_query_string' }, {})
|
396
|
+
subject.send(:build_access_token)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
describe 'verify_token' do
|
401
|
+
before(:each) do
|
402
|
+
subject.options.client_options[:connection_build] = proc do |builder|
|
403
|
+
builder.request :url_encoded
|
404
|
+
builder.adapter :test do |stub|
|
405
|
+
stub.get('/v1.0/me?access_token=valid_access_token') do
|
406
|
+
[200, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump(
|
407
|
+
aud: '000000000000.apps.googleusercontent.com',
|
408
|
+
id: '123456789',
|
409
|
+
email: 'example@example.com',
|
410
|
+
access_type: 'offline',
|
411
|
+
scope: 'profile email',
|
412
|
+
expires_in: 436
|
413
|
+
)]
|
414
|
+
end
|
415
|
+
stub.get('/v1.0/me?access_token=invalid_access_token') do
|
416
|
+
[400, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump(error_description: 'Invalid Value')]
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'should verify token if access_token is valid and app_id equals' do
|
423
|
+
subject.options.client_id = '000000000000.apps.googleusercontent.com'
|
424
|
+
expect(subject.send(:verify_token, 'valid_access_token')).to eq(true)
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'should verify token if access_token is valid and app_id authorized' do
|
428
|
+
subject.options.authorized_client_ids = ['000000000000.apps.googleusercontent.com']
|
429
|
+
expect(subject.send(:verify_token, 'valid_access_token')).to eq(true)
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'should not verify token if access_token is valid but app_id is false' do
|
433
|
+
expect(subject.send(:verify_token, 'valid_access_token')).to eq(false)
|
434
|
+
end
|
435
|
+
|
436
|
+
it 'should raise error if access_token is invalid' do
|
437
|
+
expect do
|
438
|
+
subject.send(:verify_token, 'invalid_access_token')
|
439
|
+
end.to raise_error(OAuth2::Error)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-microsoft_graph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Philips
|
@@ -9,76 +9,96 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2020-03-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name: omniauth
|
15
|
+
name: omniauth
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.1'
|
18
21
|
- - ">="
|
19
22
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
23
|
+
version: 1.1.1
|
21
24
|
type: :runtime
|
22
25
|
prerelease: false
|
23
26
|
version_requirements: !ruby/object:Gem::Requirement
|
24
27
|
requirements:
|
28
|
+
- - "~>"
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '1.1'
|
25
31
|
- - ">="
|
26
32
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
33
|
+
version: 1.1.1
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: omniauth-oauth2
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
type: :runtime
|
42
|
+
prerelease: false
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
28
48
|
- !ruby/object:Gem::Dependency
|
29
49
|
name: sinatra
|
30
50
|
requirement: !ruby/object:Gem::Requirement
|
31
51
|
requirements:
|
32
|
-
- - "
|
52
|
+
- - "~>"
|
33
53
|
- !ruby/object:Gem::Version
|
34
54
|
version: '0'
|
35
55
|
type: :development
|
36
56
|
prerelease: false
|
37
57
|
version_requirements: !ruby/object:Gem::Requirement
|
38
58
|
requirements:
|
39
|
-
- - "
|
59
|
+
- - "~>"
|
40
60
|
- !ruby/object:Gem::Version
|
41
61
|
version: '0'
|
42
62
|
- !ruby/object:Gem::Dependency
|
43
63
|
name: rake
|
44
64
|
requirement: !ruby/object:Gem::Requirement
|
45
65
|
requirements:
|
46
|
-
- - "
|
66
|
+
- - "~>"
|
47
67
|
- !ruby/object:Gem::Version
|
48
68
|
version: '0'
|
49
69
|
type: :development
|
50
70
|
prerelease: false
|
51
71
|
version_requirements: !ruby/object:Gem::Requirement
|
52
72
|
requirements:
|
53
|
-
- - "
|
73
|
+
- - "~>"
|
54
74
|
- !ruby/object:Gem::Version
|
55
75
|
version: '0'
|
56
76
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
77
|
+
name: rspec
|
58
78
|
requirement: !ruby/object:Gem::Requirement
|
59
79
|
requirements:
|
60
|
-
- - "
|
80
|
+
- - "~>"
|
61
81
|
- !ruby/object:Gem::Version
|
62
|
-
version: '
|
82
|
+
version: '3.6'
|
63
83
|
type: :development
|
64
84
|
prerelease: false
|
65
85
|
version_requirements: !ruby/object:Gem::Requirement
|
66
86
|
requirements:
|
67
|
-
- - "
|
87
|
+
- - "~>"
|
68
88
|
- !ruby/object:Gem::Version
|
69
|
-
version: '
|
89
|
+
version: '3.6'
|
70
90
|
- !ruby/object:Gem::Dependency
|
71
91
|
name: mocha
|
72
92
|
requirement: !ruby/object:Gem::Requirement
|
73
93
|
requirements:
|
74
|
-
- - "
|
94
|
+
- - "~>"
|
75
95
|
- !ruby/object:Gem::Version
|
76
96
|
version: '0'
|
77
97
|
type: :development
|
78
98
|
prerelease: false
|
79
99
|
version_requirements: !ruby/object:Gem::Requirement
|
80
100
|
requirements:
|
81
|
-
- - "
|
101
|
+
- - "~>"
|
82
102
|
- !ruby/object:Gem::Version
|
83
103
|
version: '0'
|
84
104
|
description: omniauth provider for new Microsoft Graph API
|
@@ -101,8 +121,8 @@ files:
|
|
101
121
|
- lib/omniauth/strategies/microsoft_graph.rb
|
102
122
|
- lib/omniauth_microsoft_graph.rb
|
103
123
|
- omniauth-microsoft_graph.gemspec
|
104
|
-
-
|
105
|
-
-
|
124
|
+
- spec/omniauth/strategies/microsoft_graph_oauth2_spec.rb
|
125
|
+
- spec/spec_helper.rb
|
106
126
|
homepage: https://github.com/synth/omniauth-microsoft_graph
|
107
127
|
licenses:
|
108
128
|
- MIT
|
@@ -123,10 +143,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
143
|
version: '0'
|
124
144
|
requirements: []
|
125
145
|
rubyforge_project:
|
126
|
-
rubygems_version: 2.5.2
|
146
|
+
rubygems_version: 2.5.2
|
127
147
|
signing_key:
|
128
148
|
specification_version: 4
|
129
149
|
summary: omniauth provider for Microsoft Graph
|
130
150
|
test_files:
|
131
|
-
-
|
132
|
-
-
|
151
|
+
- spec/omniauth/strategies/microsoft_graph_oauth2_spec.rb
|
152
|
+
- spec/spec_helper.rb
|
data/test/strategy_test.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class UidTest < StrategyTestCase
|
4
|
-
def setup
|
5
|
-
super
|
6
|
-
strategy.stubs(:raw_info).returns({ 'id' => '123' })
|
7
|
-
end
|
8
|
-
|
9
|
-
def test_return_id_from_raw_info
|
10
|
-
assert_equal '123', strategy.uid
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class AccessTokenTest < StrategyTestCase
|
15
|
-
def setup
|
16
|
-
super
|
17
|
-
@request.stubs(:params).returns({ 'access_token' => 'valid_access_token' })
|
18
|
-
strategy.stubs(:client).returns(:client)
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_build_access_token
|
22
|
-
token = strategy.build_access_token
|
23
|
-
assert_equal token.token, 'valid_access_token'
|
24
|
-
assert_equal token.client, :client
|
25
|
-
end
|
26
|
-
end
|
data/test/test_helper.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
require 'minitest/autorun'
|
3
|
-
require 'mocha/setup'
|
4
|
-
require 'omniauth/strategies/microsoft_graph'
|
5
|
-
|
6
|
-
OmniAuth.config.test_mode = true
|
7
|
-
|
8
|
-
class StrategyTestCase < Minitest::Test
|
9
|
-
def setup
|
10
|
-
@request = stub('Request')
|
11
|
-
@request.stubs(:params).returns({})
|
12
|
-
@request.stubs(:cookies).returns({})
|
13
|
-
@request.stubs(:env).returns({})
|
14
|
-
@request.stubs(:scheme).returns({})
|
15
|
-
@request.stubs(:ssl?).returns(false)
|
16
|
-
|
17
|
-
@client_id = '123'
|
18
|
-
@client_secret = '53cr3tz'
|
19
|
-
@options = {}
|
20
|
-
end
|
21
|
-
|
22
|
-
def strategy
|
23
|
-
@strategy ||= begin
|
24
|
-
args = [@client_id, @client_secret, @options].compact
|
25
|
-
OmniAuth::Strategies::MicrosoftGraph.new(nil, *args).tap do |strategy|
|
26
|
-
strategy.stubs(:request).returns(@request)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|