Chromium Code Reviews| Index: chromium-committers/auth_util.py |
| =================================================================== |
| --- chromium-committers/auth_util.py (revision 0) |
| +++ chromium-committers/auth_util.py (revision 0) |
| @@ -0,0 +1,106 @@ |
| +# Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +"""Utilities for talking to another appengine app with authentication.""" |
| + |
| +__author__ = 'agable@google.com (Aaron Gable)' |
| + |
| + |
| +import datetime |
| +import hashlib |
| +import hmac |
| +import time |
| + |
| +from google.appengine.ext import ndb |
| + |
| + |
| +class AuthError(Exception): |
| + pass |
| + |
| + |
| +class AuthToken(ndb.Model): |
| + """Represents an id/key pair for authentication. |
| + |
| + This app also uses the id field as the id/name (unique datastore key) of the |
| + object for easy lookup. The app's own AuthToken is stored with name 'self'. |
| + |
| + Attributes: |
| + id: The unique identity for this token. |
| + token: The corresponding authentication key. |
| + """ |
| + id = ndb.StringProperty() |
| + token = ndb.StringProperty() |
| + |
| + |
| +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.
|
| + """Given an https request, confirms that it is correctly authenticated. |
| + |
| + Expects the request to contain: |
| + id: Unique ID of requester, used to get an AuthToken from ndb |
| + time: Unix epoch time the request was made (to prevent replay attacks) |
| + auth: The hmac(key, id+time+params, sha256), to authenticate the request, |
| + where the key is the corresponding token in the ID's AuthToken |
| + **params: All of the request GET/POST parameters |
| + |
| + Throws AuthError if things don't check out right. |
| + """ |
| + id = request.get('id') |
| + if not id: |
| + raise AuthError('No id in request.') |
| + authtoken = ndb.Key(AuthToken, id).get() |
| + if not authtoken: |
| + raise AuthError('No auth token found for id %s.' % id) |
| + key = authtoken.token |
| + |
| + time = request.get('time') |
| + if not time: |
| + raise AuthError('No time in request.') |
| + then = datetime.datetime.fromtimestamp(time) |
| + now = datetime.datetime.now() |
| + if abs(now - then) > datetime.timedelta(minutes=10): |
|
iannucci
2013/10/03 03:05:02
I would make this 10 min a configuration option
|
| + raise AuthError('Time more than 10 minutes out of sync.') |
| + |
| + auth = request.get('auth') |
| + if not auth: |
| + raise AuthError('No auth in request.') |
| + |
| + check = hmac.new(key, digestmod=hashlib.sha256) |
| + for param, value in sorted(request.params.items()): |
| + # Don't include the auth hmac itself in the check. |
| + if param == 'auth': |
| + continue |
| + check.update('%s=%s' % (param, value)) |
| + |
| + 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.
|
| + raise AuthError('Incorrect authentication.') |
| + |
| + |
| +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
|
| + """Given a payload to send, constructs an authenticated request. |
| + |
| + Returns a dictionary containing: |
| + id: Unique ID of this app, from the datastore AuthToken 'self' |
| + time: Current Unix epoch time |
| + auth: The hmac(key, id+time+parms, sha256), to authenticate the request, |
| + where the key is the corresponding token in the app's AuthToken |
| + **params: All of the GET/POST parameters |
| + |
| + It is up to the calling code to convert this dictionary into valid GET/POST |
| + parameters. |
| + """ |
| + authtoken = ndb.Key(AuthToken, 'self').get() |
| + if not authtoken: |
| + raise AuthError('No AuthToken found for this app.') |
| + id = authtoken.id |
| + key = authtoken.token |
| + |
| + time = time.mktime(datetime.datetime.now().timetuple()) |
| + |
| + auth = hmac.new(key, digestmod=hashlib.sha256) |
| + for param, value in sorted(params.items()): |
| + 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.
|
| + |
| + ret = params.copy() |
| + ret.update({'id': id, 'time': time, 'auth': auth.hexdigest()}) |
| + return ret |