omniauth-google-oauth2 0.1.13 → 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
data/README.md
CHANGED
@@ -1,8 +1,94 @@
|
|
1
1
|
# OmniAuth Google OAuth2 Strategy
|
2
2
|
|
3
|
-
Strategy to
|
3
|
+
Strategy to authenticate with Google via OAuth2 in OmniAuth.
|
4
4
|
|
5
|
-
Get your API key at https://code.google.com/apis/console/
|
5
|
+
Get your API key at: https://code.google.com/apis/console/
|
6
|
+
|
7
|
+
For more details, read the Google docs: https://developers.google.com/accounts/docs/OAuth2
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add to your `Gemfile`:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem "omniauth-google-oauth2"
|
15
|
+
```
|
16
|
+
|
17
|
+
Then `bundle install`.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Here's an example, adding the middleware to a Rails app in `config/initializers/omniauth.rb`:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
25
|
+
provider :google_oauth2, ENV["GOOGLE_KEY"], ENV["GOOGLE_SECRET"]
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
You can now access the OmniAuth Google OAuth2 URL: `/auth/google_oauth2`
|
30
|
+
|
31
|
+
## Configuration
|
32
|
+
|
33
|
+
You can configure several options, which you pass in to the `provider` method via a hash:
|
34
|
+
|
35
|
+
* `scope`: A comma-separated list, without spaces, of permissions you want to request from the user. See the [Google OAuth 2.0 Playground](https://developers.google.com/oauthplayground/) for a full list of available permissions. Caveats:
|
36
|
+
* The `userinfo.email` and `userinfo.profile` scopes are used by default. By defining your own `scope`, you override these defaults. If you need these scopes, don't forget to add them yourself!
|
37
|
+
* Scopes starting with `https://www.googleapis.com/auth/` do not need that prefix specified. So while you should use the smaller scope `books` since that permission starts with the mentioned prefix, you should use the full scope URL `https://docs.google.com/feeds/` to access a user's docs, for example.
|
38
|
+
* `approval_prompt`: Determines whether the user is always re-prompted for consent. It's set to `force` by default so a user sees a consent page even if he has previously allowed access a given set of scopes. Set this value to `auto` so that the user only sees the consent page the first time he authorizes a given set of scopes.
|
39
|
+
* `access_type`: Defaults to `offline`, so a refresh token is sent to be used when the user is not present at the browser. Can be set to `online`.
|
40
|
+
|
41
|
+
Here's an example of a possible configuration where the user is asked for extra permissions and is only prompted once for such permissions:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
45
|
+
provider :google_oauth2, ENV["GOOGLE_KEY"], ENV["GOOGLE_SECRET"],
|
46
|
+
{
|
47
|
+
:scope => "userinfo.email,userinfo.profile,plus.me,http://gdata.youtube.com",
|
48
|
+
:approval_prompt => "auto"
|
49
|
+
}
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
## Auth Hash
|
54
|
+
|
55
|
+
Here's an example of an authentication hash available in the callback by accessing `request.env["omniauth.auth"]`:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
{
|
59
|
+
:provider => "google_oauth2",
|
60
|
+
:uid => "123456789",
|
61
|
+
:info => {
|
62
|
+
:name => "John Doe",
|
63
|
+
:email => "john@company_name.com",
|
64
|
+
:first_name => "John",
|
65
|
+
:last_name => "Doe",
|
66
|
+
:image => "https://lh3.googleusercontent.com/url/photo.jpg"
|
67
|
+
},
|
68
|
+
:credentials => {
|
69
|
+
:token => "token",
|
70
|
+
:refresh_token => "another_token",
|
71
|
+
:expires_at => 1354920555,
|
72
|
+
:expires => true
|
73
|
+
},
|
74
|
+
:extra => {
|
75
|
+
:raw_info => {
|
76
|
+
:id => "123456789",
|
77
|
+
:email => "user@domain.example.com",
|
78
|
+
:verified_email => true,
|
79
|
+
:name => "John Doe",
|
80
|
+
:given_name => "John",
|
81
|
+
:family_name => "Doe",
|
82
|
+
:link => "https://plus.google.com/123456789",
|
83
|
+
:picture => "https://lh3.googleusercontent.com/url/photo.jpg",
|
84
|
+
:gender => "male",
|
85
|
+
:birthday => "0000-06-25",
|
86
|
+
:locale => "en",
|
87
|
+
:hd => "company_name.com"
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
```
|
6
92
|
|
7
93
|
## License
|
8
94
|
|
@@ -12,4 +98,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
12
98
|
|
13
99
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
14
100
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
101
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/examples/config.ru
CHANGED
@@ -8,6 +8,8 @@ require 'sinatra'
|
|
8
8
|
require 'omniauth'
|
9
9
|
require 'omniauth-google-oauth2'
|
10
10
|
|
11
|
+
# Do not use for production code.
|
12
|
+
# This is only to make setup easier when running through the sample.
|
11
13
|
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
12
14
|
|
13
15
|
class App < Sinatra::Base
|
@@ -20,8 +20,8 @@ module OmniAuth
|
|
20
20
|
base_scope_url = "https://www.googleapis.com/auth/"
|
21
21
|
super.tap do |params|
|
22
22
|
# Read the params if passed directly to omniauth_authorize_path
|
23
|
-
|
24
|
-
params[k
|
23
|
+
options[:authorize_options].each do |k|
|
24
|
+
params[k] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
|
25
25
|
end
|
26
26
|
scopes = (params[:scope] || DEFAULT_SCOPE).split(",")
|
27
27
|
scopes.map! { |s| s =~ /^https?:\/\// ? s : "#{base_scope_url}#{s}" }
|
@@ -43,7 +43,10 @@ module OmniAuth
|
|
43
43
|
:email => verified_email,
|
44
44
|
:first_name => raw_info['given_name'],
|
45
45
|
:last_name => raw_info['family_name'],
|
46
|
-
:image => raw_info['picture']
|
46
|
+
:image => raw_info['picture'],
|
47
|
+
:urls => {
|
48
|
+
'Google' => raw_info['link']
|
49
|
+
}
|
47
50
|
})
|
48
51
|
end
|
49
52
|
|
@@ -57,11 +60,19 @@ module OmniAuth
|
|
57
60
|
@raw_info ||= access_token.get('https://www.googleapis.com/oauth2/v1/userinfo').parsed
|
58
61
|
end
|
59
62
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
63
|
+
def build_access_token_with_access_token
|
64
|
+
if !request.params['id_token'].nil? &&
|
65
|
+
!request.params['access_token'].nil? &&
|
66
|
+
verify_token(request.params['id_token'], request.params['access_token'])
|
67
|
+
::OAuth2::AccessToken.from_hash(client, request.params.dup)
|
68
|
+
else
|
69
|
+
build_access_token_without_access_token
|
70
|
+
end
|
64
71
|
end
|
72
|
+
alias_method :build_access_token_without_access_token, :build_access_token
|
73
|
+
alias :build_access_token :build_access_token_with_access_token
|
74
|
+
|
75
|
+
private
|
65
76
|
|
66
77
|
def prune!(hash)
|
67
78
|
hash.delete_if do |_, value|
|
@@ -74,6 +85,14 @@ module OmniAuth
|
|
74
85
|
raw_info['verified_email'] ? raw_info['email'] : nil
|
75
86
|
end
|
76
87
|
|
88
|
+
def verify_token(id_token, access_token)
|
89
|
+
# Verify id_token as well
|
90
|
+
# request fails and raises error when id_token or access_token is invalid
|
91
|
+
raw_response = client.request(:get, 'https://www.googleapis.com/oauth2/v2/tokeninfo',
|
92
|
+
params: {id_token: id_token, access_token: access_token}).parsed
|
93
|
+
raw_response['issued_to'] == options.client_id
|
94
|
+
end
|
95
|
+
|
77
96
|
end
|
78
97
|
end
|
79
98
|
end
|
@@ -46,9 +46,9 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
46
46
|
end
|
47
47
|
|
48
48
|
describe '#authorize_params' do
|
49
|
-
%w(approval_prompt access_type state hd).each do |k|
|
49
|
+
%w(approval_prompt access_type state hd any_other).each do |k|
|
50
50
|
it "should set the #{k} authorize option dynamically in the request" do
|
51
|
-
@options = {k.to_sym => ''}
|
51
|
+
@options = {:authorize_options => [k.to_sym], k.to_sym => ''}
|
52
52
|
subject.stub(:request) { double('Request', {:params => { k => 'something' }, :env => {}}) }
|
53
53
|
subject.authorize_params[k].should eq('something')
|
54
54
|
end
|
@@ -139,4 +139,74 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
139
139
|
subject.extra.should_not have_key(:raw_info)
|
140
140
|
end
|
141
141
|
end
|
142
|
+
|
143
|
+
describe 'populate auth hash url' do
|
144
|
+
it 'should populate url map in auth hash if link present in raw_info' do
|
145
|
+
subject.stub(:raw_info) { { 'name' => 'Foo', 'link' => 'https://plus.google.com/123456' } }
|
146
|
+
subject.info[:urls]['Google'].should eq('https://plus.google.com/123456')
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'should not populate url map in auth hash if no link present in raw_info' do
|
150
|
+
subject.stub(:raw_info) { { 'name' => 'Foo' } }
|
151
|
+
subject.info.should_not have_key(:urls)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'build_access_token' do
|
156
|
+
it 'should read access_token from hash' do
|
157
|
+
@request.stub(:params).and_return('id_token' => 'valid_id_token', 'access_token' => 'valid_access_token')
|
158
|
+
subject.should_receive(:verify_token).with('valid_id_token', 'valid_access_token').and_return true
|
159
|
+
subject.should_receive(:client).and_return(:client)
|
160
|
+
|
161
|
+
token = subject.build_access_token
|
162
|
+
token.should be_instance_of(::OAuth2::AccessToken)
|
163
|
+
token.token.should eq('valid_access_token')
|
164
|
+
token.client.should eq(:client)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should call super' do
|
168
|
+
subject.should_receive(:build_access_token_without_access_token)
|
169
|
+
subject.build_access_token
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe 'verify_token' do
|
174
|
+
before(:each) do
|
175
|
+
subject.options.client_options[:connection_build] = proc do |builder|
|
176
|
+
builder.request :url_encoded
|
177
|
+
builder.adapter :test do |stub|
|
178
|
+
stub.get('/oauth2/v2/tokeninfo?id_token=valid_id_token&access_token=valid_access_token') do |env|
|
179
|
+
[200, {'Content-Type' => 'application/json; charset=UTF-8'}, MultiJson.encode(
|
180
|
+
:issued_to => '000000000000.apps.googleusercontent.com',
|
181
|
+
:audience => '000000000000.apps.googleusercontent.com',
|
182
|
+
:user_id => '000000000000000000000',
|
183
|
+
:scope => 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email',
|
184
|
+
:expires_in => 3514,
|
185
|
+
:email => 'me@example.com',
|
186
|
+
:verified_email => true,
|
187
|
+
:access_type => 'online'
|
188
|
+
)]
|
189
|
+
end
|
190
|
+
stub.get('/oauth2/v2/tokeninfo?id_token=invalid_id_token&access_token=invalid_access_token') do |env|
|
191
|
+
[400, {'Content-Type' => 'application/json; charset=UTF-8'}, MultiJson.encode(:error_description => 'Invalid Value')]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'should verify token if access_token and id_token are valid and app_id equals' do
|
198
|
+
subject.options.client_id = '000000000000.apps.googleusercontent.com'
|
199
|
+
subject.send(:verify_token, 'valid_id_token', 'valid_access_token').should == true
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'should not verify token if access_token and id_token are valid but app_id is false' do
|
203
|
+
subject.send(:verify_token, 'valid_id_token', 'valid_access_token').should == false
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'should raise error if access_token or id_token is invalid' do
|
207
|
+
expect {
|
208
|
+
subject.send(:verify_token, 'invalid_id_token', 'invalid_access_token')
|
209
|
+
}.to raise_error(OAuth2::Error)
|
210
|
+
end
|
211
|
+
end
|
142
212
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-google-oauth2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.15
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2013-04-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: omniauth
|
@@ -112,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
112
|
version: '0'
|
113
113
|
segments:
|
114
114
|
- 0
|
115
|
-
hash:
|
115
|
+
hash: -4040101131185532486
|
116
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
117
|
none: false
|
118
118
|
requirements:
|
@@ -121,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
121
|
version: '0'
|
122
122
|
segments:
|
123
123
|
- 0
|
124
|
-
hash:
|
124
|
+
hash: -4040101131185532486
|
125
125
|
requirements: []
|
126
126
|
rubyforge_project:
|
127
127
|
rubygems_version: 1.8.24
|