ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 1 | # Copyright (C) 2010 Google Inc. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | from google.appengine.api import users |
| 16 | from google.appengine.ext import db |
| 17 | |
| 18 | import apiclient.ext.appengine |
| 19 | import logging |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 20 | import simple_buzz_wrapper |
| 21 | |
| 22 | |
| 23 | class Flow(db.Model): |
| 24 | flow = apiclient.ext.appengine.FlowThreeLeggedProperty() |
| 25 | |
| 26 | |
| 27 | class Credentials(db.Model): |
| 28 | credentials = apiclient.ext.appengine.OAuthCredentialsProperty() |
| 29 | |
| 30 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 31 | class oauth_required(object): |
| 32 | def __init__(self, *decorator_args, **decorator_kwargs): |
| 33 | """A decorator to require that a user has gone through the OAuth dance before accessing a handler. |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 34 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 35 | To use it, decorate your get() method like this: |
| 36 | @oauth_required |
| 37 | def get(self): |
| 38 | buzz_wrapper = oauth_handlers.build_buzz_wrapper_for_current_user() |
| 39 | user_profile_data = buzz_wrapper.get_profile() |
| 40 | self.response.out.write('Hello, ' + user_profile_data.displayName) |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 41 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 42 | We will redirect the user to the OAuth endpoint and afterwards the OAuth |
| 43 | will send the user back to the DanceFinishingHandler that you have configured. |
| 44 | |
| 45 | This should only used for GET requests since any payload in a POST request |
| 46 | will be lost. Any parameters in the original URL will be preserved. |
| 47 | """ |
| 48 | self.decorator_args = decorator_args |
| 49 | self.decorator_kwargs = decorator_kwargs |
| 50 | |
| 51 | def __call__(self, handler_method): |
| 52 | def check_oauth_credentials_wrapper(*args, **kwargs): |
| 53 | handler_instance = args[0] |
| 54 | # TODO(ade) Add support for POST requests |
| 55 | if handler_instance.request.method != 'GET': |
| 56 | raise webapp.Error('The check_oauth decorator can only be used for GET ' |
| 57 | 'requests') |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 58 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 59 | # Is this a request from the OAuth system after finishing the OAuth dance? |
| 60 | if handler_instance.request.get('oauth_verifier'): |
| 61 | user = users.get_current_user() |
| 62 | logging.debug('Finished OAuth dance for: %s' % user.email()) |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 63 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 64 | f = Flow.get_by_key_name(user.user_id()) |
| 65 | if f: |
| 66 | credentials = f.flow.step2_exchange(handler_instance.request.params) |
| 67 | c = Credentials(key_name=user.user_id(), credentials=credentials) |
| 68 | c.put() |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 69 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 70 | # We delete the flow so that a malicious actor can't pretend to be the OAuth service |
| 71 | # and replace a valid token with an invalid token |
| 72 | f.delete() |
| 73 | |
| 74 | handler_method(*args) |
| 75 | return |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 76 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 77 | # Find out who the user is. If we don't know who you are then we can't |
| 78 | # look up your OAuth credentials thus we must ensure the user is logged in. |
| 79 | user = users.get_current_user() |
| 80 | if not user: |
| 81 | handler_instance.redirect(users.create_login_url(handler_instance.request.uri)) |
| 82 | return |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 83 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 84 | # Now that we know who the user is look up their OAuth credentials |
| 85 | # if we don't find the credentials then send them through the OAuth dance |
| 86 | if not Credentials.get_by_key_name(user.user_id()): |
| 87 | |
| 88 | # Set up the default arguments and override them with whatever values have been given to the decorator |
| 89 | flow_settings = { |
| 90 | 'consumer_key' : 'anonymous', |
| 91 | 'consumer_secret' : 'anonymous', |
| 92 | 'user_agent' : 'google-api-client-python-buzz-webapp/1.0', |
| 93 | 'domain' : 'anonymous', |
| 94 | 'scope' : 'https://www.googleapis.com/auth/buzz', |
| 95 | 'xoauth_display_name' : 'Default Display Name For OAuth Application' |
| 96 | } |
| 97 | |
| 98 | flow_settings.update(self.decorator_kwargs) |
| 99 | |
| 100 | p = apiclient.discovery.build("buzz", "v1") |
| 101 | flow = apiclient.oauth.FlowThreeLegged(p.auth_discovery(), |
| 102 | consumer_key=flow_settings['consumer_key'], |
| 103 | consumer_secret=flow_settings['consumer_secret'], |
| 104 | user_agent=flow_settings['user_agent'], |
| 105 | domain=flow_settings['domain'], |
| 106 | scope=flow_settings['scope'], |
| 107 | xoauth_displayname=flow_settings['xoauth_display_name']) |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 108 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 109 | # The OAuth system needs to send the user right back here so that they |
| 110 | # get to the page they originally intended to visit. |
| 111 | oauth_return_url = handler_instance.request.uri |
| 112 | authorize_url = flow.step1_get_authorize_url(oauth_return_url) |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 113 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 114 | f = Flow(key_name=user.user_id(), flow=flow) |
| 115 | f.put() |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 116 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 117 | handler_instance.redirect(authorize_url) |
| 118 | return |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 119 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 120 | # If the user already has a token then call the wrapped handler |
| 121 | handler_method(*args) |
| 122 | return check_oauth_credentials_wrapper |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 123 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 124 | def build_buzz_wrapper_for_current_user(api_key): |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 125 | user = users.get_current_user() |
| 126 | credentials = Credentials.get_by_key_name(user.user_id()).credentials |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame^] | 127 | return simple_buzz_wrapper.SimpleBuzzWrapper(api_key=api_key, |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 128 | credentials=credentials) |