blob: 08ed864215b8e5569b30736cffe48e5ba180ee95 [file] [log] [blame]
ade@google.com60a53c02011-01-10 02:33:17 +00001# 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
15from google.appengine.api import users
16from google.appengine.ext import db
17
18import apiclient.ext.appengine
19import logging
ade@google.com7911c2d2011-01-11 23:17:02 +000020import simple_wrapper
ade@google.com60a53c02011-01-10 02:33:17 +000021
22
23class Flow(db.Model):
24 flow = apiclient.ext.appengine.FlowThreeLeggedProperty()
25
26
27class Credentials(db.Model):
28 credentials = apiclient.ext.appengine.OAuthCredentialsProperty()
29
30
ade@google.comdcdd0382011-01-11 23:01:45 +000031class 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.com60a53c02011-01-10 02:33:17 +000034
ade@google.comdcdd0382011-01-11 23:01:45 +000035 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.com60a53c02011-01-10 02:33:17 +000041
ade@google.comdcdd0382011-01-11 23:01:45 +000042 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.com7539ce92011-01-12 23:02:24 +000050
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.comdcdd0382011-01-11 23:01:45 +000090
ade@google.com7539ce92011-01-12 23:02:24 +000091 # 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.comdcdd0382011-01-11 23:01:45 +0000101 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.com60a53c02011-01-10 02:33:17 +0000108
ade@google.comdcdd0382011-01-11 23:01:45 +0000109 # 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.com60a53c02011-01-10 02:33:17 +0000113
ade@google.comdcdd0382011-01-11 23:01:45 +0000114 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.com60a53c02011-01-10 02:33:17 +0000119
ade@google.comdcdd0382011-01-11 23:01:45 +0000120 # 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.com60a53c02011-01-10 02:33:17 +0000126
ade@google.comdcdd0382011-01-11 23:01:45 +0000127 # 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.com60a53c02011-01-10 02:33:17 +0000133
ade@google.comdcdd0382011-01-11 23:01:45 +0000134 # 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.com7539ce92011-01-12 23:02:24 +0000137 flow_settings = self.__load_settings__()
ade@google.comdcdd0382011-01-11 23:01:45 +0000138
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.com60a53c02011-01-10 02:33:17 +0000147
ade@google.comdcdd0382011-01-11 23:01:45 +0000148 # 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.com60a53c02011-01-10 02:33:17 +0000152
ade@google.comdcdd0382011-01-11 23:01:45 +0000153 f = Flow(key_name=user.user_id(), flow=flow)
154 f.put()
ade@google.com60a53c02011-01-10 02:33:17 +0000155
ade@google.comdcdd0382011-01-11 23:01:45 +0000156 handler_instance.redirect(authorize_url)
157 return
ade@google.com60a53c02011-01-10 02:33:17 +0000158
ade@google.comdcdd0382011-01-11 23:01:45 +0000159 # If the user already has a token then call the wrapped handler
160 handler_method(*args)
161 return check_oauth_credentials_wrapper
ade@google.com60a53c02011-01-10 02:33:17 +0000162
ade@google.combd46c722011-01-12 23:09:53 +0000163def build_buzz_wrapper_for_current_user(api_key=None):
ade@google.com60a53c02011-01-10 02:33:17 +0000164 user = users.get_current_user()
165 credentials = Credentials.get_by_key_name(user.user_id()).credentials
ade@google.com89487c62011-01-13 18:53:36 +0000166 if not api_key:
167 try:
168 import settings
169 api_key = getattr(settings, 'API_KEY', None)
170 except ImportError:
171 return {}
ade@google.com7911c2d2011-01-11 23:17:02 +0000172 return simple_wrapper.SimpleWrapper(api_key=api_key,
ade@google.com60a53c02011-01-10 02:33:17 +0000173 credentials=credentials)