Add a small web sample showing incremental authorization & use of APIs
This commit is contained in:
parent
36a197d885
commit
87ed3e421b
5
Gemfile
5
Gemfile
|
@ -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
|
||||||
|
|
|
@ -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'
|
|
@ -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'
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in New Issue