Add a small web sample showing incremental authorization & use of APIs

This commit is contained in:
Steve Bazyl 2016-01-20 12:21:08 -08:00
parent 36a197d885
commit 87ed3e421b
19 changed files with 319 additions and 1 deletions

View File

@ -16,6 +16,11 @@ group :development do
gem 'launchy', '~> 2.4' gem 'launchy', '~> 2.4'
gem 'dotenv', '~> 2.0' gem 'dotenv', '~> 2.0'
gem 'fakefs', '~> 0.6', require: "fakefs/safe" 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 end
platforms :jruby do platforms :jruby do

View File

@ -1,6 +1,6 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'google-api-client', '~> 0.9.pre4' gem 'google-api-client', '~> 0.9'
gem 'thor', '~> 0.19' gem 'thor', '~> 0.19'
gem 'os', '~> 0.9' gem 'os', '~> 0.9'
gem 'rmail', '~> 1.1' gem 'rmail', '~> 1.1'

7
samples/web/Gemfile Normal file
View File

@ -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'

44
samples/web/README.md Normal file
View File

@ -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.

120
samples/web/app.rb Normal file
View File

@ -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

View File

@ -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.
%>
<section>
<h5>Next 10 events</h5>
<table class="">
<thead>
<tr>
<th class="">Time</th>
<th class="">Summary</th>
</tr>
</thead>
<tbody>
<% @result.items.each do |event| %>
<tr>
<td class=""><%= event.start.date_time || event.start.date %></td>
<td class=""><%= event.summary %></td>
</tr>
<% end %>
</tbody>
</table>
</section>

View File

@ -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.
%>
<h5>10 most recently modified files</h5>
<table class="">
<thead>
<tr>
<th class="">Name</th>
<th class="">Last Modified</th>
</tr>
</thead>
<tbody>
<% @result.files.each do |file| %>
<tr>
<td class=""><a href="<%= file.web_view_link %>"><%= file.name %></a></td>
<td class=""><%= file.modified_time %></td>
</tr>
<% end %>
</tbody>
</table>

View File

@ -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.
%>
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<script>
function onSignIn(googleUser) {
var id_token = googleUser.getAuthResponse().id_token;
var formData = new FormData();
formData.append('id_token', id_token);
var req = new XMLHttpRequest();
req.addEventListener("load", function() {
console.log("Log in successful");
// Successful login...
});
req.addEventListener("error", function() {
console.log("Log in failed");
// Handle error...
});
req.addEventListener()
req.open("POST", "/signin");
req.send(formData);
}
</script>

View File

@ -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.
%>
<html>
<head>
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="<%= @client_id %>">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style type="text/css">
.navigation a {
padding: 10px;
}
.main {
padding: 10px;
}
</style>
</head>
<body>
<div class="navigation">
<a href="/">Home</a>
<a href="/drive">Google Drive Sample</a>
<a href="/calendar">Google Calendar Sample</a>
</div>
<div class="main">
<%= yield %>
</div>
</body>
</html>