Skip to content

Commit

Permalink
Add a small web sample showing incremental authorization & use of APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
sqrrrl committed Jan 20, 2016
1 parent 36a197d commit 87ed3e4
Show file tree
Hide file tree
Showing 19 changed files with 319 additions and 1 deletion.
5 changes: 5 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion samples/Gemfile → samples/cli/Gemfile
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions samples/web/Gemfile
Original file line number Diff line number Diff line change
@@ -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 changes: 44 additions & 0 deletions samples/web/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 120 additions & 0 deletions samples/web/app.rb
Original file line number Diff line number Diff line change
@@ -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
34 changes: 34 additions & 0 deletions samples/web/views/calendar.erb
Original file line number Diff line number Diff line change
@@ -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>
33 changes: 33 additions & 0 deletions samples/web/views/drive.erb
Original file line number Diff line number Diff line change
@@ -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>
35 changes: 35 additions & 0 deletions samples/web/views/home.erb
Original file line number Diff line number Diff line change
@@ -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>
40 changes: 40 additions & 0 deletions samples/web/views/layout.erb
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit 87ed3e4

Please sign in to comment.