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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « appengine/monorail/framework/validate.py ('k') | appengine/monorail/gae.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/monorail/framework/xsrf.py
diff --git a/appengine/monorail/framework/xsrf.py b/appengine/monorail/framework/xsrf.py
new file mode 100644
index 0000000000000000000000000000000000000000..283b8ed977d8d5885071ccc443057fb1b55b2c59
--- /dev/null
+++ b/appengine/monorail/framework/xsrf.py
@@ -0,0 +1,132 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is govered by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Utility routines for avoiding cross-site-request-forgery."""
+
+import base64
+import hmac
+import logging
+import time
+
+# This is a file in the top-level directory that you must edit before deploying
+import settings
+from framework import framework_constants
+from services import secrets_svc
+
+# This is how long tokens are valid.
+TOKEN_TIMEOUT_SEC = 2 * framework_constants.SECS_PER_HOUR
+
+# The token refresh servlet accepts old tokens to generate new ones, but
+# we still impose a limit on how old they can be.
+REFRESH_TOKEN_TIMEOUT_SEC = 10 * framework_constants.SECS_PER_DAY
+
+# When the JS on a page decides whether or not it needs to refresh the
+# XSRF token before submitting a form, there could be some clock skew,
+# so we subtract a little time to avoid having the JS use an existing
+# token that the server might consider expired already.
+TOKEN_TIMEOUT_MARGIN_SEC = 5 * framework_constants.SECS_PER_MINUTE
+
+# Form tokens and issue stars are limited to only work with the specific
+# servlet path for the servlet that processes them. There are several
+# XHR handlers that mainly read data without making changes, so we just
+# use 'xhr' with all of them.
+XHR_SERVLET_PATH = 'xhr'
+
+DELIMITER = ':'
+
+
+def GenerateToken(user_id, servlet_path, token_time=None):
+ """Return a security token specifically for the given user.
+
+ Args:
+ user_id: int user ID of the user viewing an HTML form.
+ servlet_path: string URI path to limit the use of the token.
+ token_time: Time at which the token is generated in seconds since the
+ epoch. This is used in validation and testing. Defaults to the
+ current time.
+
+ Returns:
+ A url-safe security token. The token is a string with the digest
+ the user_id and time, followed by plain-text copy of the time that is
+ used in validation.
+
+ Raises:
+ ValueError: if the XSRF secret was not configured.
+ """
+ if not user_id:
+ return '' # Don't give tokens out to anonymous visitors.
+
+ token_time = token_time or int(time.time())
+ digester = hmac.new(secrets_svc.GetXSRFKey())
+ digester.update(str(user_id))
+ digester.update(DELIMITER)
+ digester.update(servlet_path)
+ digester.update(DELIMITER)
+ digester.update(str(token_time))
+ digest = digester.digest()
+
+ token = base64.urlsafe_b64encode('%s%s%d' % (digest, DELIMITER, token_time))
+ return token
+
+
+def ValidateToken(
+ token, user_id, servlet_path, now=None, timeout=TOKEN_TIMEOUT_SEC):
+ """Return True if the given token is valid for the given scope.
+
+ Args:
+ token: String token that was presented by the user.
+ user_id: int user ID.
+ servlet_path: string URI path to limit the use of the token.
+ now: Time in seconds since th epoch. Defaults to the current time.
+ It is explicitly specified only in tests.
+
+ Raises:
+ TokenIncorrect: if the token is missing or invalid.
+ """
+ if not token:
+ raise TokenIncorrect('missing token')
+
+ try:
+ decoded = base64.urlsafe_b64decode(str(token))
+ token_time = long(decoded.split(DELIMITER)[-1])
+ except (TypeError, ValueError):
+ raise TokenIncorrect('could not decode token')
+ now = now or int(time.time())
+
+ # The given token should match the generated one with the same time.
+ expected_token = GenerateToken(user_id, servlet_path, token_time=token_time)
+ if len(token) != len(expected_token):
+ raise TokenIncorrect('presented token is wrong size')
+
+ # Perform constant time comparison to avoid timing attacks
+ different = 0
+ for x, y in zip(token, expected_token):
+ different |= ord(x) ^ ord(y)
+ if different:
+ raise TokenIncorrect(
+ 'presented token does not match expected token: %r != %r' % (
+ token, expected_token))
+
+ # We check expiration last so that we only raise the expriration error
+ # if the token would have otherwise been valid.
+ if now - token_time > timeout:
+ raise TokenIncorrect('token has expired')
+
+
+def TokenExpiresSec(now=None):
+ """Return timestamp when current tokens will expire, minus a safety margin."""
+ now = now or int(time.time())
+ return now + TOKEN_TIMEOUT_SEC - TOKEN_TIMEOUT_MARGIN_SEC
+
+
+class Error(Exception):
+ """Base class for errors from this module."""
+ pass
+
+
+# Caught separately in servlet.py
+class TokenIncorrect(Error):
+ """The POST body has an incorrect URL Command Attack token."""
+ pass
« 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