Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prep for 0.5 release #54

Merged
merged 5 commits into from
Dec 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ rvm:
- jruby
matrix:
allow_failures:
- rbx-2 # See rubinius/rubinius#3485 - rubocop segfaults
- rvm: rbx-2 # See rubinius/rubinius#3485 - rubocop segfaults
script: "bundle exec rake"
addons:
apt:
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.5.0 (12/10/2015)

### Changes

* Initial support for user credentials ([@sqrrrl][])
* Update Signet to 0.7

## 0.4.2 (05/08/2015)

### Changes
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ group :development do
gem 'fakeredis', '~> 0.5'
gem 'webmock', '~> 1.21'
gem 'rack-test', '~> 0.6'
gem 'sinatra'
end

platforms :jruby do
Expand Down
81 changes: 80 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ $ gem install googleauth
require 'googleauth'

# Get the environment configured authorization
scopes = ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/compute']
scopes = ['https://www.googleapis.com/auth/cloud-platform',
'https://www.googleapis.com/auth/compute']
authorization = Google::Auth.get_application_default(scopes)

# Add the the access token obtained using the authorization to a hash, e.g
Expand All @@ -61,6 +62,84 @@ and authorization level for the application independent of the user. This is
the recommended approach to authorize calls to Cloud APIs, particularly when
you're building an application that uses Google Compute Engine.

## User Credentials

The library also provides support for requesting and storing user
credentials (3-Legged OAuth2.) Two implementations are currently available,
a generic authorizer useful for command line apps or custom integrations as
well as a web variant tailored toward Rack-based applications.

The authorizers are intended for authorization use cases. For sign-on,
see [Google Idenity Platform](https://developers.google.com/identity/)

### Example (Web)

```ruby
require 'googleauth'
require 'googleauth/web_user_authorizer'
require 'googleauth/stores/redis_token_store'
require 'redis'

client_id = Google::Auth::ClientId.from_file('/path/to/client_secrets.json')
scope = ['https://www.googleapis.com/auth/drive']
token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)
authorizer = Google::Auth::WebUserAuthorizer.new(
client_id, scope, token_store, '/oauth2callback')


get('/authorize') do
# NOTE: Assumes the user is already authenticated to the app
user_id = request.session['user_id']
credentials = authorizer.get_credentials(user_id, request)
if credentials.nil?
redirect authorizer.get_authorization_url(user_id: user_id, request: request)
end
# Credentials are valid, can call APIs
# ...
end

get('/oauth2callback') do
target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(
request)
redirect target_url
end
```

### Example (Command Line)

```ruby
require 'googleauth'
require 'googleauth/stores/file_token_store'

scope = 'https://www.googleapis.com/auth/drive'
client_id = Google::Auth::ClientId.from_file('/path/to/client_secrets.json')
token_store = Google::Auth::Stores::FileTokenStore.new(
:file => '/path/to/tokens.yaml')
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)

credentials = authorizer.get_credentials(user_id)
if credentials.nil?
url = authorizer.get_authorization_url(base_url: 'urn:ietf:wg:oauth:2.0:oob')
puts "Open #{url} in your browser and enter the resulting code:"
code = gets
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code, base_url: OOB_URI)
end

# OK to use credentials
```

### Storage

Authorizers require a storage instance to manage long term persistence of
access and refresh tokens. Two storage implementations are included:

* Google::Auth::Stores::FileTokenStore
* Google::Auth::Stores::RedisTokenStore

Custom storage implementations can also be used. See
[token_store.rb](lib/googleauth/token_store.rb) for additional details.

## What about auth in google-apis-ruby-client?

The goal is for all auth done by
Expand Down
2 changes: 1 addition & 1 deletion googleauth.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ Gem::Specification.new do |s|
s.add_dependency 'jwt', '~> 1.4'
s.add_dependency 'memoist', '~> 0.12'
s.add_dependency 'multi_json', '~> 1.11'
s.add_dependency 'signet', '~> 0.6'
s.add_dependency 'signet', '~> 0.7'
end
4 changes: 2 additions & 2 deletions lib/googleauth/signet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Client
def apply!(a_hash, opts = {})
# fetch the access token there is currently not one, or if the client
# has expired
fetch_access_token!(opts) if access_token.nil? || expired?
fetch_access_token!(opts) if access_token.nil? || expires_within?(60)
a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}"
end

Expand All @@ -65,7 +65,7 @@ def on_refresh(&block)
end

alias_method :orig_fetch_access_token!, :fetch_access_token!
def fetch_access_token!(options)
def fetch_access_token!(options = {})
info = orig_fetch_access_token!(options)
notify_refresh_listeners
info
Expand Down
2 changes: 1 addition & 1 deletion lib/googleauth/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ module Google
# Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs.
module Auth
VERSION = '0.4.2'
VERSION = '0.5.0'
end
end
9 changes: 5 additions & 4 deletions lib/googleauth/web_user_authorizer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,17 @@ module Auth
# user_id = request.session['user_email']
# credentials = authorizer.get_credentials(user_id, request)
# if credentials.nil?
# redirect authorizer.get_redirect_uri(user_id, request)
# redirect authorizer.get_authorization_url(user_id: user_id,
# request: request)
# end
# # Credentials are valid, can call APIs
# ...
# end
#
# get('/oauth2callback') do
# user_id = request.session['user_email']
# _, return_uri = authorizer.handle_auth_callback(user_id, request)
# redirect return_uri
# url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(
# request)
# redirect url
# end
#
# Instead of implementing the callback directly, applications are
Expand Down
85 changes: 25 additions & 60 deletions spec/googleauth/apply_auth_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,6 @@
require 'faraday'
require 'spec_helper'

def build_json_response(payload)
[200,
{ 'Content-Type' => 'application/json; charset=utf-8' },
MultiJson.dump(payload)]
end

def build_access_token_json(token)
build_json_response('access_token' => token,
'token_type' => 'Bearer',
'expires_in' => 3600)
end

shared_examples 'apply/apply! are OK' do
let(:auth_key) { :Authorization }

Expand All @@ -57,124 +45,101 @@ def build_access_token_json(token)
# auth client
describe '#fetch_access_token' do
let(:token) { '1/abcdef1234567890' }
let(:stubs) do
let(:stub) do
make_auth_stubs access_token: token
end
let(:connection) do
Faraday.new do |b|
b.adapter(:test, stubs)
end
end

it 'should set access_token to the fetched value' do
@client.fetch_access_token!(connection: connection)
stub
@client.fetch_access_token!
expect(@client.access_token).to eq(token)
stubs.verify_stubbed_calls
expect(stub).to have_been_requested
end

it 'should notify refresh listeners after updating' do
stub
expect do |b|
@client.on_refresh(&b)
@client.fetch_access_token!(connection: connection)
@client.fetch_access_token!
end.to yield_with_args(have_attributes(
access_token: '1/abcdef1234567890'))
stubs.verify_stubbed_calls
expect(stub).to have_been_requested
end
end

describe '#apply!' do
it 'should update the target hash with fetched access token' do
token = '1/abcdef1234567890'
stubs = make_auth_stubs access_token: token
c = Faraday.new do |b|
b.adapter(:test, stubs)
end
stub = make_auth_stubs access_token: token

md = { foo: 'bar' }
@client.apply!(md, connection: c)
@client.apply!(md)
want = { :foo => 'bar', auth_key => "Bearer #{token}" }
expect(md).to eq(want)
stubs.verify_stubbed_calls
expect(stub).to have_been_requested
end
end

describe 'updater_proc' do
it 'should provide a proc that updates a hash with the access token' do
token = '1/abcdef1234567890'
stubs = make_auth_stubs access_token: token
c = Faraday.new do |b|
b.adapter(:test, stubs)
end

stub = make_auth_stubs access_token: token
md = { foo: 'bar' }
the_proc = @client.updater_proc
got = the_proc.call(md, connection: c)
got = the_proc.call(md)
want = { :foo => 'bar', auth_key => "Bearer #{token}" }
expect(got).to eq(want)
stubs.verify_stubbed_calls
expect(stub).to have_been_requested
end
end

describe '#apply' do
it 'should not update the original hash with the access token' do
token = '1/abcdef1234567890'
stubs = make_auth_stubs access_token: token
c = Faraday.new do |b|
b.adapter(:test, stubs)
end
stub = make_auth_stubs access_token: token

md = { foo: 'bar' }
@client.apply(md, connection: c)
@client.apply(md)
want = { foo: 'bar' }
expect(md).to eq(want)
stubs.verify_stubbed_calls
expect(stub).to have_been_requested
end

it 'should add the token to the returned hash' do
token = '1/abcdef1234567890'
stubs = make_auth_stubs access_token: token
c = Faraday.new do |b|
b.adapter(:test, stubs)
end
stub = make_auth_stubs access_token: token

md = { foo: 'bar' }
got = @client.apply(md, connection: c)
got = @client.apply(md)
want = { :foo => 'bar', auth_key => "Bearer #{token}" }
expect(got).to eq(want)
stubs.verify_stubbed_calls
expect(stub).to have_been_requested
end

it 'should not fetch a new token if the current is not expired' do
token = '1/abcdef1234567890'
stubs = make_auth_stubs access_token: token
c = Faraday.new do |b|
b.adapter(:test, stubs)
end
stub = make_auth_stubs access_token: token

n = 5 # arbitrary
n.times do |_t|
md = { foo: 'bar' }
got = @client.apply(md, connection: c)
got = @client.apply(md)
want = { :foo => 'bar', auth_key => "Bearer #{token}" }
expect(got).to eq(want)
end
stubs.verify_stubbed_calls
expect(stub).to have_been_requested
end

it 'should fetch a new token if the current one is expired' do
token_1 = '1/abcdef1234567890'
token_2 = '2/abcdef1234567890'
token_2 = '2/abcdef1234567891'

[token_1, token_2].each do |t|
stubs = make_auth_stubs access_token: t
c = Faraday.new do |b|
b.adapter(:test, stubs)
end
make_auth_stubs access_token: t
md = { foo: 'bar' }
got = @client.apply(md, connection: c)
got = @client.apply(md)
want = { :foo => 'bar', auth_key => "Bearer #{t}" }
expect(got).to eq(want)
stubs.verify_stubbed_calls
@client.expires_at -= 3601 # default is to expire in 1hr
end
end
Expand Down
Loading