Skip to content

Commit

Permalink
3LO support - Rails integration
Browse files Browse the repository at this point in the history
  • Loading branch information
sqrrrl committed Oct 14, 2015
1 parent cdc12a2 commit 346b090
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 0 deletions.
9 changes: 9 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@ group :development do
gem 'fakeredis', '~> 0.5'
gem 'webmock', '~> 1.21'
gem 'rack-test', '~> 0.6'
gem 'activerecord', '~> 4.2'
end

platforms :jruby do
group :development do
gem 'jdbc-sqlite3', '~> 3.8'
gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3'
end
end

platforms :ruby do
group :development do
gem 'sqlite3', '~> 1.3'
end
end
55 changes: 55 additions & 0 deletions lib/generators/googleauth/googleauth_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Rails generator for configuring the google auth library for Rails. Performs
# the following actions
#
# - Creates a route for "/oauth2callback' in `config/routes.rb` for the 3LO
# callback handler
# - Generates a migration for storing user credentials via ActiveRecord
# - Creates an initializer for further customization
#
class GoogleauthGenerator < Rails::Generators::Base
source_root File.expand_path('../templates', __FILE__)
class_option :generate_route,
type: :boolean,
default: true,
description: 'Don\'t insert routes in config/routes.rb'
class_option :generate_migration,
type: :boolean,
default: true,
description: 'Don\'t generate a migration for token storage'
class_option :generate_initializer,
type: :boolean,
default: true,
description: 'Don\t generate an initializer'

def generate_config
add_route unless options.skip_route
add_migration unless options.skip_migration
add_initializer unless options.skip_initializer

say 'Please download your application credentials from ' \
'http://console.developers.google.com ' \
'and copy to config/client_secret.json.' unless client_secret_exists?
end

private

def add_route
route "match '/oauth2callback', "\
'to: Google::Auth::WebUserAuthorizer::CallbackApp, '\
'via: :all'
end

def add_migration
generate 'migration', 'CreateGoogleAuthTokens user_id:string:index ' \
'token:string'
end

def add_initializer
copy_file 'googleauth.rb', 'config/initializers/googleauth.rb'
end

def client_secret_exists?
path = File.join(Rails.root, 'config', 'client_secret.json')
File.exist?(path)
end
end
26 changes: 26 additions & 0 deletions lib/generators/googleauth/templates/googleauth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Default token store uses ActiveRecord. Use the following to use Redis instead
#
# Rails.application.config.googleauth.token_store = :redis
# Rails.application.config.googleauth.token_store_options = {
# :url => 'redis://localhost:6380'
# }

# Default client secret location is config/client_secret.json. Alternate
# locations can be specified as:
# Rails.application.config.googleauth.client_secret_path =
# '/etc/googleauth/client_secret.json'
#
# Or configured directly:
# Rails.application.config.googleauth.id = 'myclientsecret'
# Rails.application.config.googleauth.secret = 'mysecret'

# Default scopes to request
# Rails.application.config.googleauth.scope = %w(email profile)

# Redirect URI path
# Rails.application.config.googleauth.callback_uri = '/oauth2callback'

# Uncommment to disable automatic injection of helpers into controllers.
# If disabled, helpers can me added as needed by
# including the module 'Google::Auth::Rails::ControllerHelpers'
# Rails.application.config.googleauth.include_helpers = false
1 change: 1 addition & 0 deletions lib/googleauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
require 'googleauth/client_id'
require 'googleauth/user_authorizer'
require 'googleauth/web_user_authorizer'
require 'googleauth/rails/railtie' if defined?(Rails)

module Google
# Module Auth provides classes that provide Google-specific authorization
Expand Down
124 changes: 124 additions & 0 deletions lib/googleauth/rails/controller_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
require 'rails'
require 'googleauth/token_store'
require 'googleauth/web_user_authorizer'

module Google
module Auth
module Rails
# Helpers for rails controllers to simplify the most common usage
# patterns.
#
# In the most basic form, authorization can be added by declaring a
# before_action filter for a controller:
#
# before_action :require_google_credentials
#
# This assumes that:
# - The authorization scope required is configured via
# `config.googleauth.scope`
# - The unique ID of the user is available at `session[:user_id]`
#
# Upon passing the filter, the user credentials are available in the
# instance variable `@google_user_credentials` and can be used to access
# the corresponding APIs.
#
# The filter can be customized by supplying a block instead. This can be
# used to supply a different user ID or require a different scope
# depending on the controller. The following sample uses the Google API
# client from Google Drive:
#
# class FileController < ApplicationController
# before_action do
# require_google_credentials(
# user_id: current_user.email,
# scope: 'https://www.googleapis.com/auth/drive')
# end
#
# def index
# drive = Google::Apis::DriveV2::DriveService.new
# drive.authorization = @google_user_credentials
# @files = drive.list_files(q: "mimeType = 'application/pdf')
# end
# end
#
module ControllerHelpers
# Ensure that user credentials are available for the request.
# Intended to be used as a filter on controllers, but can be called
# directly within a controller method as well.
#
# After calling, credentials are available via the
# `@google_user_credentials` instance variable on the controller.
#
# If no credentials available, the user will be redirected for
# authorization.
#
# @param [String] user_id
# Unique user ID to load credentials for. Defaults to
# `session[:user_id]` if nil.
# @param [String] login_hint
# Optional email address or google profile ID of the user to request
# authorization for.
# @param [Array<String>,String] scope
# Scope to require authorization for. If specified, credentials will
# only be made available if and only if they are authorized for the
# specified scope. If nil, uses the default scope configured for
# the app.
# @return [Google::Auth::UserRefreshCredentials]
# Credentials, if present
def require_google_credentials(options = {})
@google_user_credentials = google_user_credentials(options)
redirect_to_google_auth_url(options) if @google_user_credentials.nil?
@google_user_credentials
end

# Retrieve user credentials.
#
# @param [String] user_id
# Unique user ID to load credentials for. Defaults to
# `session[:user_id]` if nil.
# @param [String] scope
# Scope to require authorization for. If specified, credentials will
# only be made available if and only if they are authorized for the
# specified scope. If nil, no scope check is performed and any
# available credentials are returned as is.
# @return [Google::Auth::UserRefreshCredentials]
# Credentials, if present
def google_user_credentials(options = {})
user_id = options[:user_id] || session[:user_id]
google_user_authorizer.get_credentials(user_id,
request,
options[:scope])
end

# Redirects the user to request authorization.
#
# @param [String] login_hint
# Optional email address or google profile ID of the user to request
# authorization for.
# @param [String] redirect_to
# Optional URL to proceed to after authorization complete. Defaults
# to the current URL.
# @param [String, Array<String>] scope
# Authorization scope to request. Overrides the instance scopes
# if not nil.
# @return [Google::Auth::UserRefreshCredentials]
# Credentials, if present
def redirect_to_google_auth_url(options = {})
url = google_user_authorizer.get_authorization_url(
login_hint: options[:login_hint],
request: request,
redirect_to: options[:redirect_to],
scope: options[:scope])
redirect_to url
end

# Retrieves the default authorizer
#
# @return [Google::Auth::WebUserAuthorizer]
def google_user_authorizer
Google::Auth::WebUserAuthorizer.default
end
end
end
end
end
86 changes: 86 additions & 0 deletions lib/googleauth/rails/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
require 'rails'
require 'googleauth/token_store'
require 'googleauth/web_user_authorizer'
require 'googleauth/rails/controller_helpers'

module Google
module Auth
# Rails-specific extensions
module Rails
# Railtie for simplified integration with Rails. Exposes configuration
# via Rails config and performs initialiation on startup.
class Railtie < Rails::Railtie
MISSING_CLIENT_ID_ERROR =
'Unable to configure googleauth library, no client secret available'
MISSING_TOKEN_STORE_ERROR =
'Unable to configure googleauth library, no token store configured'
config.googleauth = ActiveSupport::OrderedOptions.new
config.googleauth.token_store = :active_record
config.googleauth.client_secret_path = nil
config.googleauth.id = nil
config.googleauth.secret = nil
config.googleauth.scope = %w(email profile)
config.googleauth.callback_uri = '/oauth2callback'
config.googleauth.token_store_options = {}
config.googleauth.include_helpers = true

# Initialize authorizers based on config
config.after_initialize do
opts = config.googleauth
client_id = load_client_id
token_store = load_token_store
if client_id.nil?
Rails.logger.warn(MISSING_CLIENT_ID_ERROR)
elsif token_store.nil?
Rails.logger.warn(MISING_TOKEN_STORE_ERROR)
else
Google::Auth::WebUserAuthorizer.default =
Google::Auth::WebUserAuthorizer.new(
client_id,
opts.scope,
token_store,
opts.callback_uri)
if config.googleauth.include_helpers
ActionController::Base.send(
:include, Google::Auth::Rails::ControllerHelpers)
end
end
end

# Load the client ID
def load_client_id
opts = config.googleauth
return Google::Auth::ClientId.new(opts.id, opts.secret) if opts.id
client_secret = config.googleauth.client_secret_path ||
File.join(Rails.root, 'config', 'client_secret.json')
return nil unless File.exist?(client_secret)
Rails.logger.info("Initializing client ID from #{client_secret}")
Google::Auth::ClientId.from_file(client_secret)
end

# Initialize the token store
def load_token_store
token_store = config.googleauth.token_store
case token_store
when Google::Auth::TokenStore
token_store
when :active_record
require 'googleauth/stores/active_record_token_store'
Google::Auth::Stores::ActiveRecordTokenStore.new(
config.googleauth.token_store_options)
when :redis
require 'googleauth/stores/redis_token_store'
Google::Auth::Stores::RedisTokenStore.new(
config.googleauth.token_store_options)
when :file
require 'googleauth/stores/file_token_store'
Google::Auth::Stores::FileTokenStore.new(
config.googleauth.token_store_options)
else
fail "Unsupported token store: #{token_store}"
end
end
end
end
end
end
68 changes: 68 additions & 0 deletions lib/googleauth/stores/active_record_token_store.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright 2014, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

require 'active_record'
require 'googleauth/token_store'

module Google
module Auth
module Stores
# Simple model for storing tokens
class GoogleAuthToken < ActiveRecord::Base
validates :user_id, presence: true
validates :token, presence: true
end

# Implementation of user token storage using ActiveRecord.
class ActiveRecordTokenStore < Google::Auth::TokenStore
def initialize(*)
end

# (see Google::Auth::Stores::TokenStore#load)
def load(id)
entry = GoogleAuthToken.find_by(user_id: id)
return nil if entry.nil?
entry.token
end

# (see Google::Auth::Stores::TokenStore#store)
def store(id, token)
entry = GoogleAuthToken.find_or_initialize_by(user_id: id)
entry.update(token: token)
end

# (see Google::Auth::Stores::TokenStore#delete)
def delete(id)
entry = GoogleAuthToken.find_by(user_id: id)
entry.destroy unless entry.nil?
end
end
end
end
end
Loading

0 comments on commit 346b090

Please sign in to comment.