From 87ed3e421b7c9798444b9233219f259762c8b119 Mon Sep 17 00:00:00 2001 From: Steve Bazyl Date: Wed, 20 Jan 2016 12:21:08 -0800 Subject: [PATCH] Add a small web sample showing incremental authorization & use of APIs --- Gemfile | 5 + samples/{ => cli}/Gemfile | 2 +- samples/{ => cli}/README.md | 0 samples/{ => cli}/google-api-samples | 0 samples/{ => cli}/lib/base_cli.rb | 0 samples/{ => cli}/lib/samples/analytics.rb | 0 samples/{ => cli}/lib/samples/calendar.rb | 0 samples/{ => cli}/lib/samples/drive.rb | 0 samples/{ => cli}/lib/samples/gmail.rb | 0 samples/{ => cli}/lib/samples/pubsub.rb | 0 samples/{ => cli}/lib/samples/translate.rb | 0 samples/{ => cli}/lib/samples/you_tube.rb | 0 samples/web/Gemfile | 7 ++ samples/web/README.md | 44 ++++++++ samples/web/app.rb | 120 +++++++++++++++++++++ samples/web/views/calendar.erb | 34 ++++++ samples/web/views/drive.erb | 33 ++++++ samples/web/views/home.erb | 35 ++++++ samples/web/views/layout.erb | 40 +++++++ 19 files changed, 319 insertions(+), 1 deletion(-) rename samples/{ => cli}/Gemfile (73%) rename samples/{ => cli}/README.md (100%) rename samples/{ => cli}/google-api-samples (100%) rename samples/{ => cli}/lib/base_cli.rb (100%) rename samples/{ => cli}/lib/samples/analytics.rb (100%) rename samples/{ => cli}/lib/samples/calendar.rb (100%) rename samples/{ => cli}/lib/samples/drive.rb (100%) rename samples/{ => cli}/lib/samples/gmail.rb (100%) rename samples/{ => cli}/lib/samples/pubsub.rb (100%) rename samples/{ => cli}/lib/samples/translate.rb (100%) rename samples/{ => cli}/lib/samples/you_tube.rb (100%) create mode 100644 samples/web/Gemfile create mode 100644 samples/web/README.md create mode 100644 samples/web/app.rb create mode 100644 samples/web/views/calendar.erb create mode 100644 samples/web/views/drive.erb create mode 100644 samples/web/views/home.erb create mode 100644 samples/web/views/layout.erb diff --git a/Gemfile b/Gemfile index 7b151dc5f..a7a40d797 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,11 @@ group :development do gem 'launchy', '~> 2.4' gem 'dotenv', '~> 2.0' gem 'fakefs', '~> 0.6', require: "fakefs/safe" + gem 'google-id-token', '~> 1.3' + gem 'os', '~> 0.9' + gem 'rmail', '~> 1.1' + gem 'sinatra', '~> 1.4' + gem 'redis', '~> 3.2' end platforms :jruby do diff --git a/samples/Gemfile b/samples/cli/Gemfile similarity index 73% rename from samples/Gemfile rename to samples/cli/Gemfile index 828cb840a..25ee33dbf 100644 --- a/samples/Gemfile +++ b/samples/cli/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem 'google-api-client', '~> 0.9.pre4' +gem 'google-api-client', '~> 0.9' gem 'thor', '~> 0.19' gem 'os', '~> 0.9' gem 'rmail', '~> 1.1' diff --git a/samples/README.md b/samples/cli/README.md similarity index 100% rename from samples/README.md rename to samples/cli/README.md diff --git a/samples/google-api-samples b/samples/cli/google-api-samples similarity index 100% rename from samples/google-api-samples rename to samples/cli/google-api-samples diff --git a/samples/lib/base_cli.rb b/samples/cli/lib/base_cli.rb similarity index 100% rename from samples/lib/base_cli.rb rename to samples/cli/lib/base_cli.rb diff --git a/samples/lib/samples/analytics.rb b/samples/cli/lib/samples/analytics.rb similarity index 100% rename from samples/lib/samples/analytics.rb rename to samples/cli/lib/samples/analytics.rb diff --git a/samples/lib/samples/calendar.rb b/samples/cli/lib/samples/calendar.rb similarity index 100% rename from samples/lib/samples/calendar.rb rename to samples/cli/lib/samples/calendar.rb diff --git a/samples/lib/samples/drive.rb b/samples/cli/lib/samples/drive.rb similarity index 100% rename from samples/lib/samples/drive.rb rename to samples/cli/lib/samples/drive.rb diff --git a/samples/lib/samples/gmail.rb b/samples/cli/lib/samples/gmail.rb similarity index 100% rename from samples/lib/samples/gmail.rb rename to samples/cli/lib/samples/gmail.rb diff --git a/samples/lib/samples/pubsub.rb b/samples/cli/lib/samples/pubsub.rb similarity index 100% rename from samples/lib/samples/pubsub.rb rename to samples/cli/lib/samples/pubsub.rb diff --git a/samples/lib/samples/translate.rb b/samples/cli/lib/samples/translate.rb similarity index 100% rename from samples/lib/samples/translate.rb rename to samples/cli/lib/samples/translate.rb diff --git a/samples/lib/samples/you_tube.rb b/samples/cli/lib/samples/you_tube.rb similarity index 100% rename from samples/lib/samples/you_tube.rb rename to samples/cli/lib/samples/you_tube.rb diff --git a/samples/web/Gemfile b/samples/web/Gemfile new file mode 100644 index 000000000..9c86e389f --- /dev/null +++ b/samples/web/Gemfile @@ -0,0 +1,7 @@ +source 'https://rubygems.org' + +gem 'google-api-client', '~> 0.9' +gem 'google-id-token', '~> 1.3' +gem 'sinatra', '~> 1.4' +gem 'redis', '~> 3.2' +gem 'dotenv' diff --git a/samples/web/README.md b/samples/web/README.md new file mode 100644 index 000000000..b8c4a10dd --- /dev/null +++ b/samples/web/README.md @@ -0,0 +1,44 @@ +# API Samples + +This directory contains a simple Sinatra web app illustrating how to use the client +in a server-side web environment. + +It illustrates a few key concepts: + +* Using [Google Sign-in](https://developers.google.com/identity) for authentication. +* Using the [googleauth gem](https://github.com/google/google-auth-library-ruby) to + request incremental authorization as more permissions are needed. + +# Setup + +* Create a project at https://console.developers.google.com +* Go to the `API Manager` and enable the `Drive` and `Calendar` APIs +* Go to `Credentials` and create a new OAuth Client ID of type 'Web application' + * Use `http://localhost:4567/oauth2callback` as the redirect URL + * Use `http://localhost:4567` as the JavaScript origin + +Additional details on how to enable APIs and create credentials can be +found in the help guide in the console. + +## Example Environment Settings + +For convenience, application credentials can be read from the shell environment +or placed in a .env file. + +After setup, your .env file might look something like: + +``` +GOOGLE_CLIENT_ID=479164972499-i7j6av7bp2s4on5ltb7pjXXXXXXXXXX.apps.googleusercontent.com +GOOGLE_CLIENT_SECRET=JBotCTG5biFWGzXXXXXXXXXX +``` + +# Running the samples + +To start the server, run + +``` +ruby app.rb +``` + +Open `http://localhost:4567/` in your browser to explore the sample. + diff --git a/samples/web/app.rb b/samples/web/app.rb new file mode 100644 index 000000000..a19ce278d --- /dev/null +++ b/samples/web/app.rb @@ -0,0 +1,120 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'sinatra' +require 'googleauth' +require 'googleauth/stores/redis_token_store' +require 'google/apis/drive_v3' +require 'google/apis/calendar_v3' +require 'google-id-token' +require 'dotenv' + +LOGIN_URL = '/' + +configure do + Dotenv.load + + Google::Apis::ClientOptions.default.application_name = 'Ruby client samples' + Google::Apis::ClientOptions.default.application_version = '0.9' + Google::Apis::RequestOptions.default.retries = 3 + + enable :sessions + set :show_exceptions, false + set :client_id, Google::Auth::ClientId.new(ENV['GOOGLE_CLIENT_ID'], + ENV['GOOGLE_CLIENT_SECRET']) + set :token_store, Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) +end + +helpers do + # Returns credentials authorized for the requested scopes. If no credentials are available, + # redirects the user to authorize access. + def credentials_for(scope) + authorizer = Google::Auth::WebUserAuthorizer.new(settings.client_id, scope, settings.token_store) + user_id = session[:user_id] + redirect LOGIN_URL if user_id.nil? + credentials = authorizer.get_credentials(user_id, request) + if credentials.nil? + redirect authorizer.get_authorization_url(login_hint: user_id, request: request) + end + credentials + end + + def resize(url, width) + url.sub(/s220/, sprintf('s%d', width)) + end +end + +# Home page +get('/') do + @client_id = settings.client_id.id + erb :home +end + +# Log in the user by validating the identity token generated by the Google Sign-In button. +# This checks that the token is signed by Google, current, and is intended for this application. +# +post('/signin') do + audience = settings.client_id.id + # Important: The google-id-token gem is not production ready. If using, consider fetching and + # supplying the valid keys separately rather than using the built-in certificate fetcher. + validator = GoogleIDToken::Validator.new + claim = validator.check(params['id_token'], audience, audience) + if claim + session[:user_id] = claim['sub'] + session[:user_email] = claim['email'] + 200 + else + logger.info('No valid identity token present') + 401 + end +end + +# Retrieve the 10 most recently modified files in Google Drive +get('/drive') do + drive = Google::Apis::DriveV3::DriveService.new + drive.authorization = credentials_for(Google::Apis::DriveV3::AUTH_DRIVE) + @result = drive.list_files(page_size: 10, + fields: 'files(name,modified_time,web_view_link),next_page_token') + erb :drive +end + +# Retrieve the next 10 upcoming events from Google Calendar +get('/calendar') do + calendar = Google::Apis::CalendarV3::CalendarService.new + calendar.authorization = credentials_for(Google::Apis::CalendarV3::AUTH_CALENDAR) + calendar_id = 'primary' + @result = calendar.list_events(calendar_id, + max_results: 10, + single_events: true, + order_by: 'startTime', + time_min: Time.now.iso8601) + erb :calendar +end + + +# Callback for authorization requests. This saves the autorization code and +# redirects back to the URL that originally requested authorization. The code is +# redeemed on the next request. +# +# Important: While the deferred approach is generally easier, it doesn't play well +# with developer mode and sinatra's default cookie-based session implementation. Changes to the +# session state are lost if the page doesn't render due to error, which can lead to further +# errors indicating the code has already been redeemed. +# +# Disabling show_exceptions or using a different session provider (E.g. Rack::Session::Memcache) +# avoids the issue. +get('/oauth2callback') do + target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request) + redirect target_url +end diff --git a/samples/web/views/calendar.erb b/samples/web/views/calendar.erb new file mode 100644 index 000000000..97ecc4696 --- /dev/null +++ b/samples/web/views/calendar.erb @@ -0,0 +1,34 @@ +<%# +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +%> +
+
Next 10 events
+ + + + + + + + + <% @result.items.each do |event| %> + + + + + <% end %> + +
TimeSummary
<%= event.start.date_time || event.start.date %><%= event.summary %>
+
diff --git a/samples/web/views/drive.erb b/samples/web/views/drive.erb new file mode 100644 index 000000000..28fce3361 --- /dev/null +++ b/samples/web/views/drive.erb @@ -0,0 +1,33 @@ +<%# +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +%> + +
10 most recently modified files
+ + + + + + + + + <% @result.files.each do |file| %> + + + + + <% end %> + +
NameLast Modified
<%= file.name %><%= file.modified_time %>
diff --git a/samples/web/views/home.erb b/samples/web/views/home.erb new file mode 100644 index 000000000..afd9a4e6a --- /dev/null +++ b/samples/web/views/home.erb @@ -0,0 +1,35 @@ +<%# +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +%> +
+ diff --git a/samples/web/views/layout.erb b/samples/web/views/layout.erb new file mode 100644 index 000000000..732860c76 --- /dev/null +++ b/samples/web/views/layout.erb @@ -0,0 +1,40 @@ +<%# +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +%> + + + + + + + + + +
+ <%= yield %> +
+ +