Add XSRF protection to oauth2decorator callback.
Also update all samples to use XSRF callback protection.
Reviewed in https://codereview.appspot.com/6473053/.
diff --git a/samples/appengine/grant.html b/samples/appengine/grant.html
index 0087325..aeb678b 100644
--- a/samples/appengine/grant.html
+++ b/samples/appengine/grant.html
@@ -8,7 +8,7 @@
application</a>.</p>
{% else %}
<p><a href="{{ url }}">Grant</a> this application permission to read your
- Buzz information and it will let you know how many followers you have.</p>
+ Google+ information and it will let you know how many followers you have.</p>
{% endif %}
<p>You can always <a
href="https://www.google.com/accounts/b/0/IssuedAuthSubTokens">revoke</a>
diff --git a/samples/dailymotion/README b/samples/dailymotion/README
deleted file mode 100644
index 7139bde..0000000
--- a/samples/dailymotion/README
+++ /dev/null
@@ -1,3 +0,0 @@
-Demonstrates using oauth2client against the DailyMotion API.
-
-keywords: oauth2 appengine
diff --git a/samples/dailymotion/app.yaml b/samples/dailymotion/app.yaml
deleted file mode 100644
index a712bd2..0000000
--- a/samples/dailymotion/app.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-application: dailymotoauth2test
-version: 1
-runtime: python
-api_version: 1
-
-handlers:
-- url: .*
- script: main.py
-
diff --git a/samples/dailymotion/index.yaml b/samples/dailymotion/index.yaml
deleted file mode 100644
index a3b9e05..0000000
--- a/samples/dailymotion/index.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-indexes:
-
-# AUTOGENERATED
-
-# This index.yaml is automatically updated whenever the dev_appserver
-# detects that a new type of query is run. If you want to manage the
-# index.yaml file manually, remove the above marker line (the line
-# saying "# AUTOGENERATED"). If you want to manage some indexes
-# manually, move them above the marker line. The index.yaml file is
-# automatically uploaded to the admin console when you next deploy
-# your application using appcfg.py.
diff --git a/samples/dailymotion/main.py b/samples/dailymotion/main.py
deleted file mode 100644
index 7f7256f..0000000
--- a/samples/dailymotion/main.py
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2007 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.
-#
-
-__author__ = 'jcgregorio@google.com (Joe Gregorio)'
-
-
-import httplib2
-import logging
-import os
-import pickle
-
-from oauth2client.appengine import CredentialsProperty
-from oauth2client.appengine import StorageByKeyName
-from oauth2client.client import OAuth2WebServerFlow
-from google.appengine.api import users
-from google.appengine.ext import db
-from google.appengine.ext import webapp
-from google.appengine.ext.webapp import template
-from google.appengine.ext.webapp import util
-from google.appengine.ext.webapp.util import login_required
-
-
-FLOW = OAuth2WebServerFlow(
- client_id='2ad565600216d25d9cde',
- client_secret='03b56df2949a520be6049ff98b89813f17b467dc',
- scope='read',
- redirect_uri='https://dailymotoauth2test.appspot.com/auth_return',
- user_agent='oauth2client-sample/1.0',
- auth_uri='https://api.dailymotion.com/oauth/authorize',
- token_uri='https://api.dailymotion.com/oauth/token'
- )
-
-
-class Credentials(db.Model):
- credentials = CredentialsProperty()
-
-
-class MainHandler(webapp.RequestHandler):
-
- @login_required
- def get(self):
- user = users.get_current_user()
- credentials = StorageByKeyName(
- Credentials, user.user_id(), 'credentials').get()
-
- if credentials is None or credentials.invalid == True:
- authorize_url = FLOW.step1_get_authorize_url()
- self.redirect(authorize_url)
- else:
- http = httplib2.Http()
- http = credentials.authorize(http)
-
- resp, content = http.request('https://api.dailymotion.com/me')
-
- path = os.path.join(os.path.dirname(__file__), 'welcome.html')
- logout = users.create_logout_url('/')
- variables = {
- 'content': content,
- 'logout': logout
- }
- self.response.out.write(template.render(path, variables))
-
-
-class OAuthHandler(webapp.RequestHandler):
-
- @login_required
- def get(self):
- user = users.get_current_user()
- credentials = FLOW.step2_exchange(self.request.params)
- StorageByKeyName(
- Credentials, user.user_id(), 'credentials').put(credentials)
- self.redirect("/")
-
-
-def main():
- application = webapp.WSGIApplication(
- [
- ('/', MainHandler),
- ('/auth_return', OAuthHandler)
- ],
- debug=True)
- util.run_wsgi_app(application)
-
-
-if __name__ == '__main__':
- main()
diff --git a/samples/dailymotion/welcome.html b/samples/dailymotion/welcome.html
deleted file mode 100644
index f86902f..0000000
--- a/samples/dailymotion/welcome.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<html>
- <head>
- <title>Daily Motion Sample</title>
- <style type=text/css>
- td { vertical-align: top; padding: 0.5em }
- img { border:0 }
- </style>
- </head>
- <body>
- <p><a href="{{ logout }}">Logout</a></p>
- <h2>Response body:</h2>
- <pre>{{ content }} </pre>
- </body>
-</html>
diff --git a/samples/django_sample/client_secrets.json b/samples/django_sample/client_secrets.json
new file mode 100644
index 0000000..a232f37
--- /dev/null
+++ b/samples/django_sample/client_secrets.json
@@ -0,0 +1,9 @@
+{
+ "web": {
+ "client_id": "[[INSERT CLIENT ID HERE]]",
+ "client_secret": "[[INSERT CLIENT SECRET HERE]]",
+ "redirect_uris": [],
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://accounts.google.com/o/oauth2/token"
+ }
+}
diff --git a/samples/django_sample/plus/models.py b/samples/django_sample/plus/models.py
index fd4bf74..890b278 100644
--- a/samples/django_sample/plus/models.py
+++ b/samples/django_sample/plus/models.py
@@ -18,8 +18,4 @@
pass
-class FlowAdmin(admin.ModelAdmin):
- pass
-
-
admin.site.register(CredentialsModel, CredentialsAdmin)
diff --git a/samples/django_sample/plus/views.py b/samples/django_sample/plus/views.py
index f5d5d18..f273b8d 100644
--- a/samples/django_sample/plus/views.py
+++ b/samples/django_sample/plus/views.py
@@ -2,27 +2,29 @@
import logging
import httplib2
-from django.http import HttpResponse
-from django.core.urlresolvers import reverse
-from django.contrib.auth.decorators import login_required
-
-from oauth2client.django_orm import Storage
-from oauth2client.client import OAuth2WebServerFlow
-from django_sample.plus.models import CredentialsModel
from apiclient.discovery import build
-
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse
+from django.http import HttpResponseBadRequest
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
+from django_sample.plus.models import CredentialsModel
+from django_sample import settings
+from oauth2client import xsrfutil
+from oauth2client.client import flow_from_clientsecrets
+from oauth2client.django_orm import Storage
-STEP2_URI = 'http://localhost:8000/oauth2callback'
+# CLIENT_SECRETS, name of a file containing the OAuth 2.0 information for this
+# application, including client_id and client_secret, which are found
+# on the API Access tab on the Google APIs
+# Console <http://code.google.com/apis/console>
+CLIENT_SECRETS = os.path.join(os.path.dirname(__file__), '..', 'client_secrets.json')
-FLOW = OAuth2WebServerFlow(
- client_id='[[Insert Client ID here.]]',
- client_secret='[[Insert Client Secret here.]]',
+FLOW = flow_from_clientsecrets(
+ CLIENT_SECRETS,
scope='https://www.googleapis.com/auth/plus.me',
- redirect_uri=STEP2_URI,
- user_agent='plus-django-sample/1.0',
- )
+ redirect_uri='http://localhost:8000/oauth2callback')
@login_required
@@ -30,6 +32,8 @@
storage = Storage(CredentialsModel, 'id', request.user, 'credential')
credential = storage.get()
if credential is None or credential.invalid == True:
+ FLOW.params['state'] = xsrfutil.generate_token(settings.SECRET_KEY,
+ request.user)
authorize_url = FLOW.step1_get_authorize_url()
return HttpResponseRedirect(authorize_url)
else:
@@ -48,6 +52,9 @@
@login_required
def auth_return(request):
+ if not xsrfutil.validate_token(settings.SECRET_KEY, request.REQUEST['state'],
+ request.user):
+ return HttpResponseBadRequest()
credential = FLOW.step2_exchange(request.REQUEST)
storage = Storage(CredentialsModel, 'id', request.user, 'credential')
storage.put(credential)