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

Side by Side Diff: appengine/monorail/framework/xsrf.py

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 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
« no previous file with comments | « appengine/monorail/framework/validate.py ('k') | appengine/monorail/gae.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 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is govered by a BSD-style
3 # license that can be found in the LICENSE file or at
4 # https://developers.google.com/open-source/licenses/bsd
5
6 """Utility routines for avoiding cross-site-request-forgery."""
7
8 import base64
9 import hmac
10 import logging
11 import time
12
13 # This is a file in the top-level directory that you must edit before deploying
14 import settings
15 from framework import framework_constants
16 from services import secrets_svc
17
18 # This is how long tokens are valid.
19 TOKEN_TIMEOUT_SEC = 2 * framework_constants.SECS_PER_HOUR
20
21 # The token refresh servlet accepts old tokens to generate new ones, but
22 # we still impose a limit on how old they can be.
23 REFRESH_TOKEN_TIMEOUT_SEC = 10 * framework_constants.SECS_PER_DAY
24
25 # When the JS on a page decides whether or not it needs to refresh the
26 # XSRF token before submitting a form, there could be some clock skew,
27 # so we subtract a little time to avoid having the JS use an existing
28 # token that the server might consider expired already.
29 TOKEN_TIMEOUT_MARGIN_SEC = 5 * framework_constants.SECS_PER_MINUTE
30
31 # Form tokens and issue stars are limited to only work with the specific
32 # servlet path for the servlet that processes them. There are several
33 # XHR handlers that mainly read data without making changes, so we just
34 # use 'xhr' with all of them.
35 XHR_SERVLET_PATH = 'xhr'
36
37 DELIMITER = ':'
38
39
40 def GenerateToken(user_id, servlet_path, token_time=None):
41 """Return a security token specifically for the given user.
42
43 Args:
44 user_id: int user ID of the user viewing an HTML form.
45 servlet_path: string URI path to limit the use of the token.
46 token_time: Time at which the token is generated in seconds since the
47 epoch. This is used in validation and testing. Defaults to the
48 current time.
49
50 Returns:
51 A url-safe security token. The token is a string with the digest
52 the user_id and time, followed by plain-text copy of the time that is
53 used in validation.
54
55 Raises:
56 ValueError: if the XSRF secret was not configured.
57 """
58 if not user_id:
59 return '' # Don't give tokens out to anonymous visitors.
60
61 token_time = token_time or int(time.time())
62 digester = hmac.new(secrets_svc.GetXSRFKey())
63 digester.update(str(user_id))
64 digester.update(DELIMITER)
65 digester.update(servlet_path)
66 digester.update(DELIMITER)
67 digester.update(str(token_time))
68 digest = digester.digest()
69
70 token = base64.urlsafe_b64encode('%s%s%d' % (digest, DELIMITER, token_time))
71 return token
72
73
74 def ValidateToken(
75 token, user_id, servlet_path, now=None, timeout=TOKEN_TIMEOUT_SEC):
76 """Return True if the given token is valid for the given scope.
77
78 Args:
79 token: String token that was presented by the user.
80 user_id: int user ID.
81 servlet_path: string URI path to limit the use of the token.
82 now: Time in seconds since th epoch. Defaults to the current time.
83 It is explicitly specified only in tests.
84
85 Raises:
86 TokenIncorrect: if the token is missing or invalid.
87 """
88 if not token:
89 raise TokenIncorrect('missing token')
90
91 try:
92 decoded = base64.urlsafe_b64decode(str(token))
93 token_time = long(decoded.split(DELIMITER)[-1])
94 except (TypeError, ValueError):
95 raise TokenIncorrect('could not decode token')
96 now = now or int(time.time())
97
98 # The given token should match the generated one with the same time.
99 expected_token = GenerateToken(user_id, servlet_path, token_time=token_time)
100 if len(token) != len(expected_token):
101 raise TokenIncorrect('presented token is wrong size')
102
103 # Perform constant time comparison to avoid timing attacks
104 different = 0
105 for x, y in zip(token, expected_token):
106 different |= ord(x) ^ ord(y)
107 if different:
108 raise TokenIncorrect(
109 'presented token does not match expected token: %r != %r' % (
110 token, expected_token))
111
112 # We check expiration last so that we only raise the expriration error
113 # if the token would have otherwise been valid.
114 if now - token_time > timeout:
115 raise TokenIncorrect('token has expired')
116
117
118 def TokenExpiresSec(now=None):
119 """Return timestamp when current tokens will expire, minus a safety margin."""
120 now = now or int(time.time())
121 return now + TOKEN_TIMEOUT_SEC - TOKEN_TIMEOUT_MARGIN_SEC
122
123
124 class Error(Exception):
125 """Base class for errors from this module."""
126 pass
127
128
129 # Caught separately in servlet.py
130 class TokenIncorrect(Error):
131 """The POST body has an incorrect URL Command Attack token."""
132 pass
OLDNEW
« no previous file with comments | « appengine/monorail/framework/validate.py ('k') | appengine/monorail/gae.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698