Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """Utilities for talking to another appengine app with authentication.""" | |
| 6 | |
| 7 __author__ = 'agable@google.com (Aaron Gable)' | |
| 8 | |
| 9 | |
| 10 import datetime | |
| 11 import hashlib | |
| 12 import hmac | |
| 13 import time | |
| 14 | |
| 15 from google.appengine.ext import ndb | |
| 16 | |
| 17 | |
| 18 class AuthError(Exception): | |
| 19 pass | |
| 20 | |
| 21 | |
| 22 class AuthToken(ndb.Model): | |
| 23 """Represents an id/key pair for authentication. | |
| 24 | |
| 25 This app also uses the id field as the id/name (unique datastore key) of the | |
| 26 object for easy lookup. The app's own AuthToken is stored with name 'self'. | |
| 27 | |
| 28 Attributes: | |
| 29 id: The unique identity for this token. | |
| 30 token: The corresponding authentication key. | |
| 31 """ | |
| 32 id = ndb.StringProperty() | |
| 33 token = ndb.StringProperty() | |
| 34 | |
| 35 | |
| 36 def CheckRequest(request): | |
|
iannucci
2013/10/03 03:05:02
I would make this a decorator. Or a handler to sub
agable
2013/10/03 15:26:40
Then it couldn't be used the way it is in Chromium
agable
2013/10/03 19:59:03
Ok, decoratorized.
| |
| 37 """Given an https request, confirms that it is correctly authenticated. | |
| 38 | |
| 39 Expects the request to contain: | |
| 40 id: Unique ID of requester, used to get an AuthToken from ndb | |
| 41 time: Unix epoch time the request was made (to prevent replay attacks) | |
| 42 auth: The hmac(key, id+time+params, sha256), to authenticate the request, | |
| 43 where the key is the corresponding token in the ID's AuthToken | |
| 44 **params: All of the request GET/POST parameters | |
| 45 | |
| 46 Throws AuthError if things don't check out right. | |
| 47 """ | |
| 48 id = request.get('id') | |
| 49 if not id: | |
| 50 raise AuthError('No id in request.') | |
| 51 authtoken = ndb.Key(AuthToken, id).get() | |
| 52 if not authtoken: | |
| 53 raise AuthError('No auth token found for id %s.' % id) | |
| 54 key = authtoken.token | |
| 55 | |
| 56 time = request.get('time') | |
| 57 if not time: | |
| 58 raise AuthError('No time in request.') | |
| 59 then = datetime.datetime.fromtimestamp(time) | |
| 60 now = datetime.datetime.now() | |
| 61 if abs(now - then) > datetime.timedelta(minutes=10): | |
|
iannucci
2013/10/03 03:05:02
I would make this 10 min a configuration option
| |
| 62 raise AuthError('Time more than 10 minutes out of sync.') | |
| 63 | |
| 64 auth = request.get('auth') | |
| 65 if not auth: | |
| 66 raise AuthError('No auth in request.') | |
| 67 | |
| 68 check = hmac.new(key, digestmod=hashlib.sha256) | |
| 69 for param, value in sorted(request.params.items()): | |
| 70 # Don't include the auth hmac itself in the check. | |
| 71 if param == 'auth': | |
| 72 continue | |
| 73 check.update('%s=%s' % (param, value)) | |
| 74 | |
| 75 if check.hexdigest() != auth: | |
|
iannucci
2013/10/03 03:05:02
Need to do a constant-time comparison here. Assert
agable
2013/10/03 19:59:03
Done.
| |
| 76 raise AuthError('Incorrect authentication.') | |
| 77 | |
| 78 | |
| 79 def CreateRequest(**params): | |
|
iannucci
2013/10/03 03:05:02
decorator.
Actually... when would this ever be us
agable
2013/10/03 15:26:40
Uh, this is exactly what you, Vadim, and I were di
iannucci
2013/10/03 22:20:51
Right, so the appengine code would never really ca
| |
| 80 """Given a payload to send, constructs an authenticated request. | |
| 81 | |
| 82 Returns a dictionary containing: | |
| 83 id: Unique ID of this app, from the datastore AuthToken 'self' | |
| 84 time: Current Unix epoch time | |
| 85 auth: The hmac(key, id+time+parms, sha256), to authenticate the request, | |
| 86 where the key is the corresponding token in the app's AuthToken | |
| 87 **params: All of the GET/POST parameters | |
| 88 | |
| 89 It is up to the calling code to convert this dictionary into valid GET/POST | |
| 90 parameters. | |
| 91 """ | |
| 92 authtoken = ndb.Key(AuthToken, 'self').get() | |
| 93 if not authtoken: | |
| 94 raise AuthError('No AuthToken found for this app.') | |
| 95 id = authtoken.id | |
| 96 key = authtoken.token | |
| 97 | |
| 98 time = time.mktime(datetime.datetime.now().timetuple()) | |
| 99 | |
| 100 auth = hmac.new(key, digestmod=hashlib.sha256) | |
| 101 for param, value in sorted(params.items()): | |
| 102 auth.update('%s=%s' % (param, value)) | |
|
Vadim Sh.
2013/10/03 17:04:53
you need to hmac(time + id + all other stuff), and
agable
2013/10/03 17:46:30
That's exactly what .update() does.
auth.update(a)
agable
2013/10/03 17:47:13
Oh whoops I see what you mean. Fixing.
| |
| 103 | |
| 104 ret = params.copy() | |
| 105 ret.update({'id': id, 'time': time, 'auth': auth.hexdigest()}) | |
| 106 return ret | |
| OLD | NEW |