Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(405)

Side by Side Diff: chromium-committers/auth_util.py

Issue 25515004: Add chromium-committers appengine app. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 functools
13 import hmac
14 import json
15 import logging
16 import time
17
18 from google.appengine.ext import ndb
19
20
21 class AuthError(Exception):
22 pass
23
24
25 class AuthToken(ndb.Model):
26 """Represents an id/key pair for authentication.
27
28 This app also uses the id field as the id/name (unique datastore key) of the
29 object for easy lookup. The app's own AuthToken is stored with name 'self'.
30
31 Attributes:
32 id: The unique identity for this token.
33 token: The corresponding authentication key.
34 """
35 id = ndb.StringProperty()
36 token = ndb.StringProperty()
37
38
39 def CheckHmacAuth(should_403=True):
Vadim Sh. 2013/10/04 04:38:19 single default arg in such pseudo decorators (deco
agable 2013/10/04 21:10:15 I absolutely agree. I thought about this for a whi
40 """Decorator for webapp2 request handler methods.
41
42 Only use on webapp2.RequestHandler methods (e.g. get, post, put).
43
44 Expects the request to contain:
45 id: Unique ID of requester, used to get an AuthToken from ndb
46 time: Unix epoch time the request was made (to prevent replay attacks)
47 auth: The hmac(key, id+time+params, sha256).hexdigest, to authenticate
48 the request, where the key is the matching token in the ID's AuthToken
49 **params: All of the request GET/POST parameters
50
51 If should_403 is True, simply 403s if things don't check out right.
52 Otherwise, sets request.hmac_authenticated to True or False, as appropriate.
53 """
54 def decorator(handler):
55 """The real decorator, conditioned on should_throw."""
Vadim Sh. 2013/10/04 04:38:19 should_403?
agable 2013/10/04 21:10:15 Done.
56 @functools.wrap(handler)
Vadim Sh. 2013/10/04 04:38:19 wraps
agable 2013/10/04 21:10:15 Already done (I may have uploaded this patchset be
57 def wrapper(self, *args, **kwargs):
58 """Does the real legwork and calls the wrapped handler or 403s."""
59 def abort_auth(log_msg):
60 """Helper method to be an exit hatch when authentication fails.
61
62 If should_403 is True, writes a 403 to the response.
63 If should_403 is False, sets hmac_authenticated to False and falls
64 through to the wrapped handler.
65 """
66 logging.info(log_msg)
67 if should_403:
68 self.response.headers['Content-Type'] = 'text/plain'
69 self.reponse.status = 403
70 self.response.write('403: Forbidden')
Vadim Sh. 2013/10/04 04:38:19 Same code shorter and ensuring request processing
agable 2013/10/04 21:10:15 Thanks, didn't know about that! Done.
71 else:
72 setattr(self.request, 'hmac_authenticated', False)
iannucci 2013/10/03 22:20:51 self.request.hmac_authenticated = False doesn't w
agable 2013/10/04 21:10:15 Done.
73 handler(self, *args, **kwargs)
74
75 # Get the id, time, and auth fields from the request.
76 id = self.request.get('id')
77 if not id:
78 abort_auth('No id in request.')
Vadim Sh. 2013/10/04 04:38:19 I'd rename it into 'finish_auth' or something with
agable 2013/10/04 21:10:15 I'd say that it still clearly is an abort -- of th
79 return
80 authtoken = ndb.Key(AuthToken, id).get()
81 if not authtoken:
82 abort_auth('No auth token found for id %s.' % id)
83 return
84 key = authtoken.token
85
86 time = self.request.get('time')
87 if not time:
88 abort_auth('No time in request.')
89 return
90 then = datetime.datetime.fromtimestamp(time)
91 now = datetime.datetime.now()
92 if abs(now - then) > datetime.timedelta(minutes=10):
Vadim Sh. 2013/10/04 04:38:19 for inter app engine communication 10 min is forev
agable 2013/10/04 21:10:15 Agreed. This isn't solely for inter-appengine comm
93 abort_auth('Time more than 10 minutes out of sync.')
94 return
95
96 auth = self.request.get('auth')
97 if not auth:
98 abort_auth('No auth in request.')
99 return
100
101 # Don't include the auth hmac itself in the check.
102 params = self.request.params.copy()
103 params.pop('auth')
104 blob = json.dumps(.params, ensure_ascii=True, sort_keys=True,
105 separators=('\0', '='))
iannucci 2013/10/03 22:20:51 no! normal separators! blargh!
agable 2013/10/04 21:10:15 Done.
106 check = hmac.new(key, blob, hashlib.sha256).hexdigest()
iannucci 2013/10/03 22:20:51 hmac should really return a file-like object which
107
108 if len(check) != len(auth):
iannucci 2013/10/03 22:20:51 you can do this check before the dumps since you k
agable 2013/10/04 21:10:15 Done.
109 abort_auth('Incorrect authentication (length mismatch).')
110 return
111
112 # Constant time comparison.
113 if reduce(lambda x,y: x or y,
iannucci 2013/10/03 22:20:51 I think you want | here not or, since you don't wa
Vadim Sh. 2013/10/04 04:38:19 I see you like functional languages a lot, Aaron :
agable 2013/10/04 21:10:15 Done.
agable 2013/10/04 21:10:15 I do! And I disagree -- this is exactly explicitly
114 (ord(a) ^ ord(b) for a, b in zip(check, auth)), 0):
115 abort_auth('Incorrect authentication.')
116 return
117
118 # Hooray, they made it!
119 setattr(self.request, 'hmac_authenticated', True)
120 handler(self, *args, **kwargs)
121
122 return wrapper
123 return decorator
124
125
126 def CreateRequest(**params):
127 """Given a payload to send, constructs an authenticated request.
128
129 Returns a dictionary containing:
130 id: Unique ID of this app, from the datastore AuthToken 'self'
131 time: Current Unix epoch time
132 auth: The hmac(key, id+time+parms, sha256), to authenticate the request,
133 where the key is the corresponding token in the app's AuthToken
134 **params: All of the GET/POST parameters
135
136 It is up to the calling code to convert this dictionary into valid GET/POST
137 parameters.
138 """
139 authtoken = ndb.Key(AuthToken, 'self').get()
140 if not authtoken:
141 raise AuthError('No AuthToken found for this app.')
142 id = authtoken.id
143 key = authtoken.token
144
145 time = time.mktime(datetime.datetime.now().timetuple())
Vadim Sh. 2013/10/04 04:38:19 I think code that generates HMAC for a request, an
agable 2013/10/04 21:10:15 Done.
146
147 ret = params.copy()
148 ret.update({'id': id, 'time': time})
149
150 blob = json.dumps(ret, ensure_ascii=True, sort_keys=True,
151 separators=('\0'. '='))
iannucci 2013/10/03 22:20:51 I think this should be a comma, not a dot also no
agable 2013/10/04 21:10:15 Already Done.
152 auth = hmac.new(key, blob, hashlib.sha256).hexdigest()
153
154 ret['auth'] = auth
155 return ret
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698