3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
21 A service that enables App Engine apps to validate OAuth requests.
24 Error: base exception type
25 NotAllowedError: OAuthService exception
26 OAuthRequestError: OAuthService exception
27 InvalidOAuthParametersError: OAuthService exception
28 InvalidOAuthTokenError: OAuthService exception
29 OAuthServiceFailureError: OAuthService exception
48 from google
.appengine
.api
import apiproxy_stub_map
49 from google
.appengine
.api
import user_service_pb
50 from google
.appengine
.api
import users
51 from google
.appengine
.runtime
import apiproxy_errors
54 class Error(Exception):
55 """Base error class for this module."""
58 class OAuthRequestError(Error
):
59 """Base error type for invalid OAuth requests."""
62 class NotAllowedError(OAuthRequestError
):
63 """Raised if the requested URL does not permit OAuth authentication."""
66 class InvalidOAuthParametersError(OAuthRequestError
):
67 """Raised if the request was a malformed OAuth request.
69 For example, the request may have omitted a required parameter, contained
70 an invalid signature, or was made by an unknown consumer.
74 class InvalidOAuthTokenError(OAuthRequestError
):
75 """Raised if the request contained an invalid token.
77 For example, the token may have been revoked by the user.
81 class OAuthServiceFailureError(Error
):
82 """Raised if there was a problem communicating with the OAuth service."""
85 def get_current_user(_scope
=None):
86 """Returns the User on whose behalf the request was made.
89 _scope: The custom OAuth scope or an iterable of scopes at least one of
96 OAuthRequestError: The request was not a valid OAuth request.
97 OAuthServiceFailureError: An unknown error occurred.
100 _maybe_call_get_oauth_user(_scope
)
101 return _get_user_from_environ()
104 def is_current_user_admin(_scope
=None):
105 """Returns true if the User on whose behalf the request was made is an admin.
108 _scope: The custom OAuth scope or an iterable of scopes at least one of
115 OAuthRequestError: The request was not a valid OAuth request.
116 OAuthServiceFailureError: An unknown error occurred.
119 _maybe_call_get_oauth_user(_scope
)
120 return os
.environ
.get('OAUTH_IS_ADMIN', '0') == '1'
124 def get_oauth_consumer_key():
125 """Returns the value of the 'oauth_consumer_key' parameter from the request.
128 string: The value of the 'oauth_consumer_key' parameter from the request,
129 an identifier for the consumer that signed the request.
132 OAuthRequestError: The request was not a valid OAuth request.
133 OAuthServiceFailureError: An unknown error occurred.
135 req
= user_service_pb
.CheckOAuthSignatureRequest()
136 resp
= user_service_pb
.CheckOAuthSignatureResponse()
138 apiproxy_stub_map
.MakeSyncCall('user', 'CheckOAuthSignature', req
, resp
)
139 except apiproxy_errors
.ApplicationError
, e
:
140 if (e
.application_error
==
141 user_service_pb
.UserServiceError
.OAUTH_INVALID_REQUEST
):
142 raise InvalidOAuthParametersError(e
.error_detail
)
143 elif (e
.application_error
==
144 user_service_pb
.UserServiceError
.OAUTH_ERROR
):
145 raise OAuthServiceFailureError(e
.error_detail
)
147 raise OAuthServiceFailureError(e
.error_detail
)
148 return resp
.oauth_consumer_key()
151 def get_client_id(_scope
):
152 """Returns the value of OAuth2 Client ID from an OAuth2 request.
155 _scope: The custom OAuth scope or an iterable of scopes at least one of
159 string: The value of Client ID.
162 OAuthRequestError: The request was not a valid OAuth2 request.
163 OAuthServiceFailureError: An unknow error occurred.
165 _maybe_call_get_oauth_user(_scope
)
166 return _get_client_id_from_environ()
169 def get_authorized_scopes(scope
):
170 """Returns authorized scopes from input scopes.
173 scope: The custom OAuth scope or an iterable of scopes at least one of
177 list: A list of authorized OAuth2 scopes
180 OAuthRequestError: The request was not a valid OAuth2 request.
181 OAuthServiceFailureError: An unknow error occurred
183 _maybe_call_get_oauth_user(scope
)
184 return _get_authorized_scopes_from_environ()
187 def _maybe_call_get_oauth_user(scope
):
188 """Makes an GetOAuthUser RPC and stores the results in os.environ.
190 This method will only make the RPC if 'OAUTH_ERROR_CODE' has not already
191 been set or 'OAUTH_LAST_SCOPE' is different to str(_scopes).
194 scope: The custom OAuth scope or an iterable of scopes at least one of
200 elif isinstance(scope
, basestring
):
203 scope_str
= str(sorted(scope
))
204 if ('OAUTH_ERROR_CODE' not in os
.environ
or
205 os
.environ
.get('OAUTH_LAST_SCOPE', None) != scope_str
or
206 os
.environ
.get('TESTONLY_OAUTH_SKIP_CACHE')):
207 req
= user_service_pb
.GetOAuthUserRequest()
209 if isinstance(scope
, basestring
):
210 req
.add_scopes(scope
)
212 req
.scopes_list().extend(scope
)
214 resp
= user_service_pb
.GetOAuthUserResponse()
216 apiproxy_stub_map
.MakeSyncCall('user', 'GetOAuthUser', req
, resp
)
217 os
.environ
['OAUTH_EMAIL'] = resp
.email()
218 os
.environ
['OAUTH_AUTH_DOMAIN'] = resp
.auth_domain()
219 os
.environ
['OAUTH_USER_ID'] = resp
.user_id()
220 os
.environ
['OAUTH_CLIENT_ID'] = resp
.client_id()
221 os
.environ
['OAUTH_AUTHORIZED_SCOPES'] = cPickle
.dumps(
222 list(resp
.scopes_list()), cPickle
.HIGHEST_PROTOCOL
)
224 os
.environ
['OAUTH_IS_ADMIN'] = '1'
226 os
.environ
['OAUTH_IS_ADMIN'] = '0'
227 os
.environ
['OAUTH_ERROR_CODE'] = ''
228 except apiproxy_errors
.ApplicationError
, e
:
229 os
.environ
['OAUTH_ERROR_CODE'] = str(e
.application_error
)
230 os
.environ
['OAUTH_ERROR_DETAIL'] = e
.error_detail
231 os
.environ
['OAUTH_LAST_SCOPE'] = scope_str
232 _maybe_raise_exception()
235 def _maybe_raise_exception():
236 """Raises an error if one has been stored in os.environ.
238 This method requires that 'OAUTH_ERROR_CODE' has already been set (an empty
239 string indicates that there is no actual error).
241 assert 'OAUTH_ERROR_CODE' in os
.environ
242 error
= os
.environ
['OAUTH_ERROR_CODE']
244 assert 'OAUTH_ERROR_DETAIL' in os
.environ
245 error_detail
= os
.environ
['OAUTH_ERROR_DETAIL']
246 if error
== str(user_service_pb
.UserServiceError
.NOT_ALLOWED
):
247 raise NotAllowedError(error_detail
)
248 elif error
== str(user_service_pb
.UserServiceError
.OAUTH_INVALID_REQUEST
):
249 raise InvalidOAuthParametersError(error_detail
)
250 elif error
== str(user_service_pb
.UserServiceError
.OAUTH_INVALID_TOKEN
):
251 raise InvalidOAuthTokenError(error_detail
)
252 elif error
== str(user_service_pb
.UserServiceError
.OAUTH_ERROR
):
253 raise OAuthServiceFailureError(error_detail
)
255 raise OAuthServiceFailureError(error_detail
)
258 def _get_user_from_environ():
259 """Returns a User based on values stored in os.environ.
261 This method requires that 'OAUTH_EMAIL', 'OAUTH_AUTH_DOMAIN', and
262 'OAUTH_USER_ID' have already been set.
267 assert 'OAUTH_EMAIL' in os
.environ
268 assert 'OAUTH_AUTH_DOMAIN' in os
.environ
269 assert 'OAUTH_USER_ID' in os
.environ
270 return users
.User(email
=os
.environ
['OAUTH_EMAIL'],
271 _auth_domain
=os
.environ
['OAUTH_AUTH_DOMAIN'],
272 _user_id
=os
.environ
['OAUTH_USER_ID'])
275 def _get_client_id_from_environ():
276 """Returns Client ID based on values stored in os.environ.
278 This method requires that 'OAUTH_CLIENT_ID' has already been set.
281 string: the value of Client ID.
283 assert 'OAUTH_CLIENT_ID' in os
.environ
284 return os
.environ
['OAUTH_CLIENT_ID']
287 def _get_authorized_scopes_from_environ():
288 """Returns authorized scopes based on values stored in os.environ.
290 This method requires that 'OAUTH_AUTHORIZED_SCOPES' has already been set.
293 list: the list of OAuth scopes.
295 assert 'OAUTH_AUTHORIZED_SCOPES' in os
.environ
296 return cPickle
.loads(os
.environ
['OAUTH_AUTHORIZED_SCOPES'])