Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/framework/xsrf.py b/framework/xsrf.py
new file mode 100644
index 0000000..75581ef
--- /dev/null
+++ b/framework/xsrf.py
@@ -0,0 +1,138 @@
+# Copyright 2016 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 or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Utility routines for avoiding cross-site-request-forgery."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+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
+
+# When checking that the token is not from the future, allow a little
+# margin for the possibliity that the clock of the GAE instance that
+# generated the token could be a little ahead of the one checking.
+CLOCK_SKEW_SEC = 5
+
+# 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.
+
+  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.
+  """
+  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, 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.
+
+  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 = int(decoded.split(DELIMITER)[-1])
+  except (TypeError, ValueError):
+    raise TokenIncorrect('could not decode token')
+  now = 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 reject tokens from the future.
+  if token_time > now + CLOCK_SKEW_SEC:
+    raise TokenIncorrect('token is from future')
+
+  # 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():
+  """Return timestamp when current tokens will expire, minus a safety margin."""
+  now = 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