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

Side by Side Diff: chromium-committers/hmac_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
« no previous file with comments | « chromium-committers/constants.py ('k') | chromium-committers/model.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 generating and verifying hmac authentication."""
6
7 __author__ = 'agable@google.com (Aaron Gable)'
8
9
10 import collections
11 import hashlib
12 import functools
13 import hmac
14 import json
15 import logging
16 import operator
17 import time
18 import urllib
19
20 from google.appengine.ext import ndb
21
22
23 class AuthToken(ndb.Model):
24 """Represents an id/key pair for authentication.
25
26 Attributes:
27 client_name: The human-readable name of the client.
28 client_id: The unique identity for the client that uses this token.
29 secret: The corresponding authentication key.
30 """
31 client_name = ndb.StringProperty()
32 client_id = ndb.StringProperty()
33 secret = ndb.StringProperty()
34
35
36 def GenerateHmac(authtoken, t=None, **params):
37 """Generates an HMAC cryptographic hash of the given parameters.
38
39 Can be used either both for generating outgoing authentication and for
40 validating incoming requests. Automatically included the authtoken's client_id
41 and the time in the hashed parameter blob. If t (timestamp) is None, uses now.
42 """
43 if t is None:
44 t = str(int(time.time()))
45 hmac_params = params.copy()
46 hmac_params.update({'id': authtoken.client_id, 't': t})
47 for obj in hmac_params.iteritems():
48 assert isinstance(obj, collections.Hashable)
iannucci 2013/10/09 23:06:49 assert all(isinstance(i, collections.Hashable) for
agable 2013/10/10 00:19:37 Done.
49 blob = urllib.urlencode(sorted(hmac_params.items()))
50 logging.debug('Generating HMAC from blob: %s' % blob)
51 return hmac.new(authtoken.secret, blob, hashlib.sha256).hexdigest()
52
53
54 def CheckHmacAuth(handler):
55 """Decorator for webapp2 request handler methods.
56
57 Only use on webapp2.RequestHandler methods (e.g. get, post, put).
58
59 Expects the handler's self.request to contain:
60 id: Unique ID of requester, used to get an AuthToken from ndb
61 t: Unix epoch time the request was made (to prevent replay attacks)
62 auth: The hmac(key, id+time+params, sha256).hexdigest, to authenticate
63 the request, where the key is the matching token in the ID's AuthToken
64 **params: All of the request GET/POST parameters
65
66 Sets request.authenticated to 'hmac' if successful. Otherwise, None.
67 """
68 @functools.wraps(handler)
69 def wrapper(self, *args, **kwargs):
70 """Does the real legwork and calls the wrapped handler."""
71 def abort_auth(log_msg):
72 """Helper method to be an exit hatch when authentication fails."""
73 logging.warning(log_msg)
74 self.request.authenticated = None
75 handler(self, *args, **kwargs)
76
77 def finish_auth(log_msg):
78 """Helper method to be an exit hatch when authentication succeeds."""
79 logging.info(log_msg)
80 handler(self, *args, **kwargs)
81
82 if getattr(self.request, 'authenticated', None):
83 finish_auth('Already authenticated.')
84 return
85
86 # Get the id, time, and auth fields from the request.
87 client_id = self.request.get('id')
88 if not client_id:
89 abort_auth('No id in request.')
90 return
91 logging.debug('Request contained id: %s' % client_id)
92 authtoken = AuthToken.query(AuthToken.client_id == client_id).get()
93 if not authtoken:
94 abort_auth('No auth token stored for client.')
95 return
96 logging.debug('AuthToken is from client: %s' % authtoken.client)
97
98 then = int(self.request.get('t', '0'))
99 if not then:
100 abort_auth('No timestamp in request.')
101 return
102 logging.debug('Request generated at time: %s' % then)
103 now = int(time.time())
104 if abs(now - then) > 60:
105 abort_auth('Timestamp too far off, token expired.')
106 return
107
108 auth = self.request.get('auth')
109 if not auth:
110 abort_auth('No auth in request.')
111 return
112 logging.debug('Request contained auth hash: %s' % auth)
113
114 # Don't include the auth hmac itself in the check.
115 params = self.request.params.copy()
116 params.pop('auth')
117 check = GenerateHmac(authtoken, **params)
118 logging.debug('Expected auth hash is: %s' % check)
119
120 # Constant time comparison.
121 if len(auth) != len(check):
122 abort_auth('Incorrect authentication (length mismatch).')
iannucci 2013/10/09 23:06:49 I don't see why the exact length of the hash is te
123 return
124 if reduce(operator.or_,
125 (ord(a) ^ ord(b) for a, b in zip(check, auth)), 0):
126 abort_auth('Incorrect authentication.')
127 return
128
129 # Hooray, they made it!
130 self.request.authenticated = 'hmac'
131 handler(self, *args, **kwargs)
132
133 return wrapper
134
135
136 def CreateRequest(**params):
137 """Given a payload to send, constructs an authenticated request.
138
139 Returns a dictionary containing:
140 id: Unique ID of this app, from the datastore AuthToken 'self'
141 t: Current Unix epoch time
142 auth: The hmac(key, id+time+parms, sha256), to authenticate the request,
143 where the key is the corresponding secret in the app's AuthToken
144 **params: All of the GET/POST parameters
145
146 It is up to the calling code to convert this dictionary into valid GET/POST
147 parameters.
148 """
149 authtoken = ndb.Key(AuthToken, 'self').get()
150 if not authtoken:
151 raise AuthError('No AuthToken found for this app.')
152
153 now = str(int(time.time()))
154
155 ret = params.copy()
156 ret.update({'id': authtoken.client_id, 't': now,
157 'auth': GenerateHmac(authtoken, t=now, **params)})
158 return ret
159
160
161 class AuthError(Exception):
162 pass
163
164
165 # There needs to be one AuthToken in the datastore so it can be added or edited
166 # from the admin console. Do this one-time setup when this module is imported.
167 if not ndb.Key(AuthToken, 'self').get():
168 AuthToken(key=ndb.Key(AuthToken, 'self')).put()
OLDNEW
« no previous file with comments | « chromium-committers/constants.py ('k') | chromium-committers/model.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698