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 | 7911c2d | 2011-01-11 23:17:02 +0000 | [diff] [blame] | 20 | import simple_wrapper |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 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 |
ade@google.com | 7539ce9 | 2011-01-12 23:02:24 +0000 | [diff] [blame] | 50 | |
| 51 | def __load_settings_from_file__(self): |
| 52 | # Load settings from settings.py module if it's available |
| 53 | # Only return the keys that the user has explicitly set |
| 54 | try: |
| 55 | import settings |
| 56 | |
| 57 | # This uses getattr so that the user can set just the parameters they care about |
| 58 | flow_settings = { |
| 59 | 'consumer_key' : getattr(settings, 'CONSUMER_KEY', None), |
| 60 | 'consumer_secret' : getattr(settings, 'CONSUMER_SECRET', None), |
| 61 | 'user_agent' : getattr(settings, 'USER_AGENT', None), |
| 62 | 'domain' : getattr(settings, 'DOMAIN', None), |
| 63 | 'scope' : getattr(settings, 'SCOPE', None), |
| 64 | 'xoauth_display_name' : getattr(settings, 'XOAUTH_DISPLAY_NAME', None) |
| 65 | } |
| 66 | |
| 67 | # Strip out all the keys that weren't specified in the settings.py |
| 68 | # This is needed to ensure that those keys don't override what's |
| 69 | # specified in the decorator invocation |
| 70 | cleaned_flow_settings = {} |
| 71 | for key,value in flow_settings.items(): |
| 72 | if value is not None: |
| 73 | cleaned_flow_settings[key] = value |
| 74 | |
| 75 | return cleaned_flow_settings |
| 76 | except ImportError: |
| 77 | return {} |
| 78 | |
| 79 | def __load_settings__(self): |
| 80 | # Set up the default arguments and override them with whatever values have been given to the decorator |
| 81 | flow_settings = { |
| 82 | 'consumer_key' : 'anonymous', |
| 83 | 'consumer_secret' : 'anonymous', |
| 84 | 'user_agent' : 'google-api-client-python-buzz-webapp/1.0', |
| 85 | 'domain' : 'anonymous', |
| 86 | 'scope' : 'https://www.googleapis.com/auth/buzz', |
| 87 | 'xoauth_display_name' : 'Default Display Name For OAuth Application' |
| 88 | } |
| 89 | logging.info('OAuth settings: %s ' % flow_settings) |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame] | 90 | |
ade@google.com | 7539ce9 | 2011-01-12 23:02:24 +0000 | [diff] [blame] | 91 | # Override the defaults with whatever the user may have put into settings.py |
| 92 | settings_kwargs = self.__load_settings_from_file__() |
| 93 | flow_settings.update(settings_kwargs) |
| 94 | logging.info('OAuth settings: %s ' % flow_settings) |
| 95 | |
| 96 | # Override the defaults with whatever the user have specified in the decorator's invocation |
| 97 | flow_settings.update(self.decorator_kwargs) |
| 98 | logging.info('OAuth settings: %s ' % flow_settings) |
| 99 | return flow_settings |
| 100 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame] | 101 | def __call__(self, handler_method): |
| 102 | def check_oauth_credentials_wrapper(*args, **kwargs): |
| 103 | handler_instance = args[0] |
| 104 | # TODO(ade) Add support for POST requests |
| 105 | if handler_instance.request.method != 'GET': |
| 106 | raise webapp.Error('The check_oauth decorator can only be used for GET ' |
| 107 | 'requests') |
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 | # Is this a request from the OAuth system after finishing the OAuth dance? |
| 110 | if handler_instance.request.get('oauth_verifier'): |
| 111 | user = users.get_current_user() |
| 112 | logging.debug('Finished OAuth dance for: %s' % user.email()) |
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.get_by_key_name(user.user_id()) |
| 115 | if f: |
| 116 | credentials = f.flow.step2_exchange(handler_instance.request.params) |
| 117 | c = Credentials(key_name=user.user_id(), credentials=credentials) |
| 118 | c.put() |
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 | # We delete the flow so that a malicious actor can't pretend to be the OAuth service |
| 121 | # and replace a valid token with an invalid token |
| 122 | f.delete() |
| 123 | |
| 124 | handler_method(*args) |
| 125 | return |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 126 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame] | 127 | # Find out who the user is. If we don't know who you are then we can't |
| 128 | # look up your OAuth credentials thus we must ensure the user is logged in. |
| 129 | user = users.get_current_user() |
| 130 | if not user: |
| 131 | handler_instance.redirect(users.create_login_url(handler_instance.request.uri)) |
| 132 | return |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 133 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame] | 134 | # Now that we know who the user is look up their OAuth credentials |
| 135 | # if we don't find the credentials then send them through the OAuth dance |
| 136 | if not Credentials.get_by_key_name(user.user_id()): |
ade@google.com | 7539ce9 | 2011-01-12 23:02:24 +0000 | [diff] [blame] | 137 | flow_settings = self.__load_settings__() |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame] | 138 | |
| 139 | p = apiclient.discovery.build("buzz", "v1") |
| 140 | flow = apiclient.oauth.FlowThreeLegged(p.auth_discovery(), |
| 141 | consumer_key=flow_settings['consumer_key'], |
| 142 | consumer_secret=flow_settings['consumer_secret'], |
| 143 | user_agent=flow_settings['user_agent'], |
| 144 | domain=flow_settings['domain'], |
| 145 | scope=flow_settings['scope'], |
| 146 | xoauth_displayname=flow_settings['xoauth_display_name']) |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 147 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame] | 148 | # The OAuth system needs to send the user right back here so that they |
| 149 | # get to the page they originally intended to visit. |
| 150 | oauth_return_url = handler_instance.request.uri |
| 151 | authorize_url = flow.step1_get_authorize_url(oauth_return_url) |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 152 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame] | 153 | f = Flow(key_name=user.user_id(), flow=flow) |
| 154 | f.put() |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 155 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame] | 156 | handler_instance.redirect(authorize_url) |
| 157 | return |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 158 | |
ade@google.com | dcdd038 | 2011-01-11 23:01:45 +0000 | [diff] [blame] | 159 | # If the user already has a token then call the wrapped handler |
| 160 | handler_method(*args) |
| 161 | return check_oauth_credentials_wrapper |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 162 | |
ade@google.com | bd46c72 | 2011-01-12 23:09:53 +0000 | [diff] [blame] | 163 | def build_buzz_wrapper_for_current_user(api_key=None): |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 164 | user = users.get_current_user() |
| 165 | credentials = Credentials.get_by_key_name(user.user_id()).credentials |
ade@google.com | 89487c6 | 2011-01-13 18:53:36 +0000 | [diff] [blame^] | 166 | if not api_key: |
| 167 | try: |
| 168 | import settings |
| 169 | api_key = getattr(settings, 'API_KEY', None) |
| 170 | except ImportError: |
| 171 | return {} |
ade@google.com | 7911c2d | 2011-01-11 23:17:02 +0000 | [diff] [blame] | 172 | return simple_wrapper.SimpleWrapper(api_key=api_key, |
ade@google.com | 60a53c0 | 2011-01-10 02:33:17 +0000 | [diff] [blame] | 173 | credentials=credentials) |