1#
2# Copyright 2014 the Melange authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#   http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Helper methods for creating & verifying XSRF tokens."""
17
18__authors__ = [
19    '"Doug Coker" <dcoker@google.com>',
20    '"Joe Gregorio" <jcgregorio@google.com>',
21]
22
23
24import base64
25import hmac
26import time
27
28import six
29from oauth2client import util
30
31
32# Delimiter character
33DELIMITER = b':'
34
35
36# 1 hour in seconds
37DEFAULT_TIMEOUT_SECS = 1*60*60
38
39
40def _force_bytes(s):
41    if isinstance(s, bytes):
42        return s
43    s = str(s)
44    if isinstance(s, six.text_type):
45        return s.encode('utf-8')
46    return s
47
48
49@util.positional(2)
50def generate_token(key, user_id, action_id="", when=None):
51  """Generates a URL-safe token for the given user, action, time tuple.
52
53  Args:
54    key: secret key to use.
55    user_id: the user ID of the authenticated user.
56    action_id: a string identifier of the action they requested
57      authorization for.
58    when: the time in seconds since the epoch at which the user was
59      authorized for this action. If not set the current time is used.
60
61  Returns:
62    A string XSRF protection token.
63  """
64  when = _force_bytes(when or int(time.time()))
65  digester = hmac.new(_force_bytes(key))
66  digester.update(_force_bytes(user_id))
67  digester.update(DELIMITER)
68  digester.update(_force_bytes(action_id))
69  digester.update(DELIMITER)
70  digester.update(when)
71  digest = digester.digest()
72
73  token = base64.urlsafe_b64encode(digest + DELIMITER + when)
74  return token
75
76
77@util.positional(3)
78def validate_token(key, token, user_id, action_id="", current_time=None):
79  """Validates that the given token authorizes the user for the action.
80
81  Tokens are invalid if the time of issue is too old or if the token
82  does not match what generateToken outputs (i.e. the token was forged).
83
84  Args:
85    key: secret key to use.
86    token: a string of the token generated by generateToken.
87    user_id: the user ID of the authenticated user.
88    action_id: a string identifier of the action they requested
89      authorization for.
90
91  Returns:
92    A boolean - True if the user is authorized for the action, False
93    otherwise.
94  """
95  if not token:
96    return False
97  try:
98    decoded = base64.urlsafe_b64decode(token)
99    token_time = int(decoded.split(DELIMITER)[-1])
100  except (TypeError, ValueError):
101    return False
102  if current_time is None:
103    current_time = time.time()
104  # If the token is too old it's not valid.
105  if current_time - token_time > DEFAULT_TIMEOUT_SECS:
106    return False
107
108  # The given token should match the generated one with the same time.
109  expected_token = generate_token(key, user_id, action_id=action_id,
110                                  when=token_time)
111  if len(token) != len(expected_token):
112    return False
113
114  # Perform constant time comparison to avoid timing attacks
115  different = 0
116  for x, y in zip(bytearray(token), bytearray(expected_token)):
117    different |= x ^ y
118  return not different
119