We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
TypeError: 'unicode' does not have the buffer interface
#### ALL software version info (bokeh, python, notebook, OS, browser, any other relevant packages)

bokeh 0.12.6
python 2.7.12
64 bit Ubuntu 16.04
Chrome browser

#### Description of expected behavior and the observed behavior

I get `500: Internal Server Error` when I try to use signed sessions with bokeh. 

#### Complete, minimal, self-contained example code that reproduces the issue

I use `sliders.py` example from Bokeh Gallery. I use the following to run the app and generate the session id:

Run the app:
```bash
export BOKEH_SECRET_KEY=KKXYhtUJmQ5f8LiQJdeUGOF4HIeTzf5zOBZmlxrXtF6D
export BOKEH_SIGN_SESSIONS=True
bokeh serve sliders.py --session-ids external-signed
```

Generate session id:
```bash 
export BOKEH_SECRET_KEY=KKXYhtUJmQ5f8LiQJdeUGOF4HIeTzf5zOBZmlxrXtF6D
export BOKEH_SIGN_SESSIONS=True
python -c "from bokeh.util.session_id import generate_session_id; print(generate_session_id())"
```

Example url to access app:
http://localhost:5006/sliders?bokeh-session-id=4RJKVrnFVe60gB5urh9sE3jUnSGDkJAfCwvoaDsoMB8f-W6QAfyDoxORtN7mb6DHAzftAhpfnxVdzC-6gIT13uV0

#### Stack traceback and/or browser JavaScript console output

JavaScript console output:
```
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
```

Bokeh traceback:
```
2017-07-21 00:23:58,161 Uncaught exception GET /sliders?bokeh-session-id=4RJKVrnFVe60gB5urh9sE3jUnSGDkJAfCwvoaDsoMB8f-W6QAfyDoxORtN7mb6DHAzftAhpfnxVdzC-6gIT13uV0 (::1)
HTTPServerRequest(protocol='http', host='localhost:5006', method='GET', uri='/sliders?bokeh-session-id=4RJKVrnFVe60gB5urh9sE3jUnSGDkJAfCwvoaDsoMB8f-W6QAfyDoxORtN7mb6DHAzftAhpfnxVdzC-6gIT13uV0', version='HTTP/1.1', remote_ip='::1', headers={'Accept-Language': 'en-US,en;q=0.8,ru;q=0.6,de;q=0.4', 'Accept-Encoding': 'gzip, deflate, br', 'Host': 'localhost:5006', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/59.0.3071.109 Chrome/59.0.3071.109 Safari/537.36', 'Connection': 'keep-alive', 'Cookie': '_xsrf=2|4ab25a11|f7d3fbdf1fab4d2b01135d63518a4f9a|1498856594; username-localhost-8888="2|1:0|10:1499014969|23:username-localhost-8888|44:ODVmNmU2NjIwYjUwNDlhYzk2MzY4OWQ5NDU2ZTExYjU=|3a908d5ba83bca558deae2665732f340eeef5ce69a2763c6cef367fd892e22b7"', 'Upgrade-Insecure-Requests': '1'})
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/tornado/web.py", line 1469, in _execute
    result = yield result
  File "/usr/local/lib/python2.7/dist-packages/tornado/gen.py", line 1015, in run
    value = future.result()
  File "/usr/local/lib/python2.7/dist-packages/tornado/concurrent.py", line 237, in result
    raise_exc_info(self._exc_info)
  File "/usr/local/lib/python2.7/dist-packages/tornado/gen.py", line 1021, in run
    yielded = self.gen.throw(*exc_info)
  File "/usr/local/lib/python2.7/dist-packages/bokeh/server/views/doc_handler.py", line 27, in get
    session = yield self.get_session()
  File "/usr/local/lib/python2.7/dist-packages/tornado/gen.py", line 1015, in run
    value = future.result()
  File "/usr/local/lib/python2.7/dist-packages/tornado/concurrent.py", line 237, in result
    raise_exc_info(self._exc_info)
  File "/usr/local/lib/python2.7/dist-packages/tornado/gen.py", line 285, in wrapper
    yielded = next(result)
  File "/usr/local/lib/python2.7/dist-packages/bokeh/server/views/session_handler.py", line 36, in get_session
    signed=self.application.sign_sessions):
  File "/usr/local/lib/python2.7/dist-packages/bokeh/util/session_id.py", line 156, in check_session_id_signature
    return hmac.compare_digest(expected_signature, provided_signature)
TypeError: 'unicode' does not have the buffer interface
```

#### Screenshots or screencasts of the bug in action

![eg](https://user-images.githubusercontent.com/14038787/28441559-a70ff970-6dab-11e7-9ff0-c0218679bfbf.png)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bokeh/util/session_id.py`
Content:
```
1 ''' Utilities for generating and manipulating session IDs.
2 
3 A session ID would typically be associated with each browser tab viewing
4 an application or plot. Each session has its own state separate from any
5 other sessions hosted by the server.
6 
7 '''
8 from __future__ import absolute_import, print_function
9 
10 import base64
11 import codecs
12 import hashlib
13 import hmac
14 import random
15 import time
16 
17 from six import binary_type
18 
19 from bokeh.settings import settings
20 
21 # Use the system PRNG for session id generation (if possible)
22 # NOTE: secure random string generation implementation is adapted
23 #       from the Django project. Reference:
24 #       https://github.com/django/django/blob/0ed7d155635da9f79d4dd67e4889087d3673c6da/django/utils/crypto.py
25 try:
26     random = random.SystemRandom()
27     using_sysrandom = True
28 except NotImplementedError:
29     import warnings
30     warnings.warn('A secure pseudo-random number generator is not available '
31                   'on your system. Falling back to Mersenne Twister.')
32     if settings.secret_key() is None:
33         warnings.warn('A secure pseudo-random number generator is not available '
34                       'and no BOKEH_SECRET_KEY has been set. '
35                       'Setting a secret key will mitigate the lack of a secure '
36                       'generator.')
37     using_sysrandom = False
38 
39 def _ensure_bytes(secret_key):
40     if secret_key is None:
41         return None
42     elif isinstance(secret_key, binary_type):
43         return secret_key
44     else:
45         return codecs.encode(secret_key, 'utf-8')
46 
47 # this is broken out for unit testability
48 def _reseed_if_needed(using_sysrandom, secret_key):
49     secret_key = _ensure_bytes(secret_key)
50     if not using_sysrandom:
51         # This is ugly, and a hack, but it makes things better than
52         # the alternative of predictability. This re-seeds the PRNG
53         # using a value that is hard for an attacker to predict, every
54         # time a random string is required. This may change the
55         # properties of the chosen random sequence slightly, but this
56         # is better than absolute predictability.
57         random.seed(
58             hashlib.sha256(
59                 ("%s%s%s" % (
60                     random.getstate(),
61                     time.time(),
62                     secret_key)).encode('utf-8')
63             ).digest())
64 
65 def _base64_encode(decoded):
66     # base64 encode both takes and returns bytes, we want to work with strings.
67     # If 'decoded' isn't bytes already, assume it's utf-8
68     decoded_as_bytes = _ensure_bytes(decoded)
69     encoded = codecs.decode(base64.urlsafe_b64encode(decoded_as_bytes), 'ascii')
70     # remove padding '=' chars that cause trouble
71     return str(encoded.rstrip('='))
72 
73 def _signature(base_id, secret_key):
74     secret_key = _ensure_bytes(secret_key)
75     base_id = codecs.encode(base_id, "utf-8")
76     signer = hmac.new(secret_key, base_id, hashlib.sha256)
77     return _base64_encode(signer.digest())
78 
79 def _get_random_string(length=44,
80                        allowed_chars='abcdefghijklmnopqrstuvwxyz'
81                        'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
82                        secret_key=settings.secret_key_bytes()):
83     """
84     Return a securely generated random string.
85     With the a-z, A-Z, 0-9 character set:
86     Length 12 is a 71-bit value. log_2((26+26+10)^12) =~ 71
87     Length 44 is a 261-bit value. log_2((26+26+10)^44) = 261
88     """
89     secret_key = _ensure_bytes(secret_key)
90     _reseed_if_needed(using_sysrandom, secret_key)
91     return ''.join(random.choice(allowed_chars) for i in range(length))
92 
93 def generate_secret_key():
94     """
95     Generate a new securely-generated secret key appropriate
96     for SHA-256 HMAC signatures. This key could be used to
97     sign Bokeh server session IDs for example.
98     """
99     return _get_random_string()
100 
101 def generate_session_id(secret_key=settings.secret_key_bytes(), signed=settings.sign_sessions()):
102     """Generate a random session ID.
103 
104     Typically, each browser tab connected to a Bokeh application
105     has its own session ID.  In production deployments of a Bokeh
106     app, session IDs should be random and unguessable - otherwise
107     users of the app could interfere with one another.
108 
109     If session IDs are signed with a secret key, the server can
110     verify that the generator of the session ID was "authorized"
111     (the generator had to know the secret key). This can be used
112     to have a separate process, such as another web application,
113     which generates new sessions on a Bokeh server. This other
114     process may require users to log in before redirecting them to
115     the Bokeh server with a valid session ID, for example.
116 
117     Args:
118         secret_key (str, optional) : Secret key (default: value of 'BOKEH_SECRET_KEY' env var)
119         signed (bool, optional) : Whether to sign the session ID (default: value of
120                                   'BOKEH_SIGN_SESSIONS' env var)
121 
122     """
123     secret_key = _ensure_bytes(secret_key)
124     if signed:
125         # note: '-' can also be in the base64 encoded signature
126         base_id = _get_random_string(secret_key=secret_key)
127         return base_id + '-' + _signature(base_id, secret_key)
128     else:
129         return _get_random_string(secret_key=secret_key)
130 
131 def check_session_id_signature(session_id, secret_key=settings.secret_key_bytes(),
132                                signed=settings.sign_sessions()):
133     """Check the signature of a session ID, returning True if it's valid.
134 
135     The server uses this function to check whether a session ID
136     was generated with the correct secret key. If signed sessions are disabled,
137     this function always returns True.
138 
139     Args:
140         session_id (str) : The session ID to check
141         secret_key (str, optional) : Secret key (default: value of 'BOKEH_SECRET_KEY' env var)
142         signed (bool, optional) : Whether to check anything (default: value of
143                                   'BOKEH_SIGN_SESSIONS' env var)
144 
145     """
146     secret_key = _ensure_bytes(secret_key)
147     if signed:
148         pieces = session_id.split('-', 1)
149         if len(pieces) != 2:
150             return False
151         base_id = pieces[0]
152         provided_signature = pieces[1]
153         expected_signature = _signature(base_id, secret_key)
154         # hmac.compare_digest() uses a string compare algorithm that doesn't
155         # short-circuit so we don't allow timing analysis
156         return hmac.compare_digest(expected_signature, provided_signature)
157     else:
158         return True
159
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bokeh/util/session_id.py b/bokeh/util/session_id.py
--- a/bokeh/util/session_id.py
+++ b/bokeh/util/session_id.py
@@ -17,6 +17,7 @@
 from six import binary_type
 
 from bokeh.settings import settings
+from bokeh.util.string import encode_utf8
 
 # Use the system PRNG for session id generation (if possible)
 # NOTE: secure random string generation implementation is adapted
@@ -153,6 +154,7 @@
         expected_signature = _signature(base_id, secret_key)
         # hmac.compare_digest() uses a string compare algorithm that doesn't
         # short-circuit so we don't allow timing analysis
-        return hmac.compare_digest(expected_signature, provided_signature)
+        # encode_utf8 is used to ensure that strings have same encoding
+        return hmac.compare_digest(encode_utf8(expected_signature), encode_utf8(provided_signature))
     else:
         return True

{"golden_diff": "diff --git a/bokeh/util/session_id.py b/bokeh/util/session_id.py\n--- a/bokeh/util/session_id.py\n+++ b/bokeh/util/session_id.py\n@@ -17,6 +17,7 @@\n from six import binary_type\n \n from bokeh.settings import settings\n+from bokeh.util.string import encode_utf8\n \n # Use the system PRNG for session id generation (if possible)\n # NOTE: secure random string generation implementation is adapted\n@@ -153,6 +154,7 @@\n         expected_signature = _signature(base_id, secret_key)\n         # hmac.compare_digest() uses a string compare algorithm that doesn't\n         # short-circuit so we don't allow timing analysis\n-        return hmac.compare_digest(expected_signature, provided_signature)\n+        # encode_utf8 is used to ensure that strings have same encoding\n+        return hmac.compare_digest(encode_utf8(expected_signature), encode_utf8(provided_signature))\n     else:\n         return True\n", "issue": "TypeError: 'unicode' does not have the buffer interface\n#### ALL software version info (bokeh, python, notebook, OS, browser, any other relevant packages)\r\n\r\nbokeh 0.12.6\r\npython 2.7.12\r\n64 bit Ubuntu 16.04\r\nChrome browser\r\n\r\n#### Description of expected behavior and the observed behavior\r\n\r\nI get `500: Internal Server Error` when I try to use signed sessions with bokeh. \r\n\r\n#### Complete, minimal, self-contained example code that reproduces the issue\r\n\r\nI use `sliders.py` example from Bokeh Gallery. I use the following to run the app and generate the session id:\r\n\r\nRun the app:\r\n```bash\r\nexport BOKEH_SECRET_KEY=KKXYhtUJmQ5f8LiQJdeUGOF4HIeTzf5zOBZmlxrXtF6D\r\nexport BOKEH_SIGN_SESSIONS=True\r\nbokeh serve sliders.py --session-ids external-signed\r\n```\r\n\r\nGenerate session id:\r\n```bash \r\nexport BOKEH_SECRET_KEY=KKXYhtUJmQ5f8LiQJdeUGOF4HIeTzf5zOBZmlxrXtF6D\r\nexport BOKEH_SIGN_SESSIONS=True\r\npython -c \"from bokeh.util.session_id import generate_session_id; print(generate_session_id())\"\r\n```\r\n\r\nExample url to access app:\r\nhttp://localhost:5006/sliders?bokeh-session-id=4RJKVrnFVe60gB5urh9sE3jUnSGDkJAfCwvoaDsoMB8f-W6QAfyDoxORtN7mb6DHAzftAhpfnxVdzC-6gIT13uV0\r\n\r\n#### Stack traceback and/or browser JavaScript console output\r\n\r\nJavaScript console output:\r\n```\r\nFailed to load resource: the server responded with a status of 500 (Internal Server Error)\r\n```\r\n\r\nBokeh traceback:\r\n```\r\n2017-07-21 00:23:58,161 Uncaught exception GET /sliders?bokeh-session-id=4RJKVrnFVe60gB5urh9sE3jUnSGDkJAfCwvoaDsoMB8f-W6QAfyDoxORtN7mb6DHAzftAhpfnxVdzC-6gIT13uV0 (::1)\r\nHTTPServerRequest(protocol='http', host='localhost:5006', method='GET', uri='/sliders?bokeh-session-id=4RJKVrnFVe60gB5urh9sE3jUnSGDkJAfCwvoaDsoMB8f-W6QAfyDoxORtN7mb6DHAzftAhpfnxVdzC-6gIT13uV0', version='HTTP/1.1', remote_ip='::1', headers={'Accept-Language': 'en-US,en;q=0.8,ru;q=0.6,de;q=0.4', 'Accept-Encoding': 'gzip, deflate, br', 'Host': 'localhost:5006', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/59.0.3071.109 Chrome/59.0.3071.109 Safari/537.36', 'Connection': 'keep-alive', 'Cookie': '_xsrf=2|4ab25a11|f7d3fbdf1fab4d2b01135d63518a4f9a|1498856594; username-localhost-8888=\"2|1:0|10:1499014969|23:username-localhost-8888|44:ODVmNmU2NjIwYjUwNDlhYzk2MzY4OWQ5NDU2ZTExYjU=|3a908d5ba83bca558deae2665732f340eeef5ce69a2763c6cef367fd892e22b7\"', 'Upgrade-Insecure-Requests': '1'})\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python2.7/dist-packages/tornado/web.py\", line 1469, in _execute\r\n    result = yield result\r\n  File \"/usr/local/lib/python2.7/dist-packages/tornado/gen.py\", line 1015, in run\r\n    value = future.result()\r\n  File \"/usr/local/lib/python2.7/dist-packages/tornado/concurrent.py\", line 237, in result\r\n    raise_exc_info(self._exc_info)\r\n  File \"/usr/local/lib/python2.7/dist-packages/tornado/gen.py\", line 1021, in run\r\n    yielded = self.gen.throw(*exc_info)\r\n  File \"/usr/local/lib/python2.7/dist-packages/bokeh/server/views/doc_handler.py\", line 27, in get\r\n    session = yield self.get_session()\r\n  File \"/usr/local/lib/python2.7/dist-packages/tornado/gen.py\", line 1015, in run\r\n    value = future.result()\r\n  File \"/usr/local/lib/python2.7/dist-packages/tornado/concurrent.py\", line 237, in result\r\n    raise_exc_info(self._exc_info)\r\n  File \"/usr/local/lib/python2.7/dist-packages/tornado/gen.py\", line 285, in wrapper\r\n    yielded = next(result)\r\n  File \"/usr/local/lib/python2.7/dist-packages/bokeh/server/views/session_handler.py\", line 36, in get_session\r\n    signed=self.application.sign_sessions):\r\n  File \"/usr/local/lib/python2.7/dist-packages/bokeh/util/session_id.py\", line 156, in check_session_id_signature\r\n    return hmac.compare_digest(expected_signature, provided_signature)\r\nTypeError: 'unicode' does not have the buffer interface\r\n```\r\n\r\n#### Screenshots or screencasts of the bug in action\r\n\r\n![eg](https://user-images.githubusercontent.com/14038787/28441559-a70ff970-6dab-11e7-9ff0-c0218679bfbf.png)\r\n\n", "before_files": [{"content": "''' Utilities for generating and manipulating session IDs.\n\nA session ID would typically be associated with each browser tab viewing\nan application or plot. Each session has its own state separate from any\nother sessions hosted by the server.\n\n'''\nfrom __future__ import absolute_import, print_function\n\nimport base64\nimport codecs\nimport hashlib\nimport hmac\nimport random\nimport time\n\nfrom six import binary_type\n\nfrom bokeh.settings import settings\n\n# Use the system PRNG for session id generation (if possible)\n# NOTE: secure random string generation implementation is adapted\n#       from the Django project. Reference:\n#       https://github.com/django/django/blob/0ed7d155635da9f79d4dd67e4889087d3673c6da/django/utils/crypto.py\ntry:\n    random = random.SystemRandom()\n    using_sysrandom = True\nexcept NotImplementedError:\n    import warnings\n    warnings.warn('A secure pseudo-random number generator is not available '\n                  'on your system. Falling back to Mersenne Twister.')\n    if settings.secret_key() is None:\n        warnings.warn('A secure pseudo-random number generator is not available '\n                      'and no BOKEH_SECRET_KEY has been set. '\n                      'Setting a secret key will mitigate the lack of a secure '\n                      'generator.')\n    using_sysrandom = False\n\ndef _ensure_bytes(secret_key):\n    if secret_key is None:\n        return None\n    elif isinstance(secret_key, binary_type):\n        return secret_key\n    else:\n        return codecs.encode(secret_key, 'utf-8')\n\n# this is broken out for unit testability\ndef _reseed_if_needed(using_sysrandom, secret_key):\n    secret_key = _ensure_bytes(secret_key)\n    if not using_sysrandom:\n        # This is ugly, and a hack, but it makes things better than\n        # the alternative of predictability. This re-seeds the PRNG\n        # using a value that is hard for an attacker to predict, every\n        # time a random string is required. This may change the\n        # properties of the chosen random sequence slightly, but this\n        # is better than absolute predictability.\n        random.seed(\n            hashlib.sha256(\n                (\"%s%s%s\" % (\n                    random.getstate(),\n                    time.time(),\n                    secret_key)).encode('utf-8')\n            ).digest())\n\ndef _base64_encode(decoded):\n    # base64 encode both takes and returns bytes, we want to work with strings.\n    # If 'decoded' isn't bytes already, assume it's utf-8\n    decoded_as_bytes = _ensure_bytes(decoded)\n    encoded = codecs.decode(base64.urlsafe_b64encode(decoded_as_bytes), 'ascii')\n    # remove padding '=' chars that cause trouble\n    return str(encoded.rstrip('='))\n\ndef _signature(base_id, secret_key):\n    secret_key = _ensure_bytes(secret_key)\n    base_id = codecs.encode(base_id, \"utf-8\")\n    signer = hmac.new(secret_key, base_id, hashlib.sha256)\n    return _base64_encode(signer.digest())\n\ndef _get_random_string(length=44,\n                       allowed_chars='abcdefghijklmnopqrstuvwxyz'\n                       'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',\n                       secret_key=settings.secret_key_bytes()):\n    \"\"\"\n    Return a securely generated random string.\n    With the a-z, A-Z, 0-9 character set:\n    Length 12 is a 71-bit value. log_2((26+26+10)^12) =~ 71\n    Length 44 is a 261-bit value. log_2((26+26+10)^44) = 261\n    \"\"\"\n    secret_key = _ensure_bytes(secret_key)\n    _reseed_if_needed(using_sysrandom, secret_key)\n    return ''.join(random.choice(allowed_chars) for i in range(length))\n\ndef generate_secret_key():\n    \"\"\"\n    Generate a new securely-generated secret key appropriate\n    for SHA-256 HMAC signatures. This key could be used to\n    sign Bokeh server session IDs for example.\n    \"\"\"\n    return _get_random_string()\n\ndef generate_session_id(secret_key=settings.secret_key_bytes(), signed=settings.sign_sessions()):\n    \"\"\"Generate a random session ID.\n\n    Typically, each browser tab connected to a Bokeh application\n    has its own session ID.  In production deployments of a Bokeh\n    app, session IDs should be random and unguessable - otherwise\n    users of the app could interfere with one another.\n\n    If session IDs are signed with a secret key, the server can\n    verify that the generator of the session ID was \"authorized\"\n    (the generator had to know the secret key). This can be used\n    to have a separate process, such as another web application,\n    which generates new sessions on a Bokeh server. This other\n    process may require users to log in before redirecting them to\n    the Bokeh server with a valid session ID, for example.\n\n    Args:\n        secret_key (str, optional) : Secret key (default: value of 'BOKEH_SECRET_KEY' env var)\n        signed (bool, optional) : Whether to sign the session ID (default: value of\n                                  'BOKEH_SIGN_SESSIONS' env var)\n\n    \"\"\"\n    secret_key = _ensure_bytes(secret_key)\n    if signed:\n        # note: '-' can also be in the base64 encoded signature\n        base_id = _get_random_string(secret_key=secret_key)\n        return base_id + '-' + _signature(base_id, secret_key)\n    else:\n        return _get_random_string(secret_key=secret_key)\n\ndef check_session_id_signature(session_id, secret_key=settings.secret_key_bytes(),\n                               signed=settings.sign_sessions()):\n    \"\"\"Check the signature of a session ID, returning True if it's valid.\n\n    The server uses this function to check whether a session ID\n    was generated with the correct secret key. If signed sessions are disabled,\n    this function always returns True.\n\n    Args:\n        session_id (str) : The session ID to check\n        secret_key (str, optional) : Secret key (default: value of 'BOKEH_SECRET_KEY' env var)\n        signed (bool, optional) : Whether to check anything (default: value of\n                                  'BOKEH_SIGN_SESSIONS' env var)\n\n    \"\"\"\n    secret_key = _ensure_bytes(secret_key)\n    if signed:\n        pieces = session_id.split('-', 1)\n        if len(pieces) != 2:\n            return False\n        base_id = pieces[0]\n        provided_signature = pieces[1]\n        expected_signature = _signature(base_id, secret_key)\n        # hmac.compare_digest() uses a string compare algorithm that doesn't\n        # short-circuit so we don't allow timing analysis\n        return hmac.compare_digest(expected_signature, provided_signature)\n    else:\n        return True\n", "path": "bokeh/util/session_id.py"}], "after_files": [{"content": "''' Utilities for generating and manipulating session IDs.\n\nA session ID would typically be associated with each browser tab viewing\nan application or plot. Each session has its own state separate from any\nother sessions hosted by the server.\n\n'''\nfrom __future__ import absolute_import, print_function\n\nimport base64\nimport codecs\nimport hashlib\nimport hmac\nimport random\nimport time\n\nfrom six import binary_type\n\nfrom bokeh.settings import settings\nfrom bokeh.util.string import encode_utf8\n\n# Use the system PRNG for session id generation (if possible)\n# NOTE: secure random string generation implementation is adapted\n#       from the Django project. Reference:\n#       https://github.com/django/django/blob/0ed7d155635da9f79d4dd67e4889087d3673c6da/django/utils/crypto.py\ntry:\n    random = random.SystemRandom()\n    using_sysrandom = True\nexcept NotImplementedError:\n    import warnings\n    warnings.warn('A secure pseudo-random number generator is not available '\n                  'on your system. Falling back to Mersenne Twister.')\n    if settings.secret_key() is None:\n        warnings.warn('A secure pseudo-random number generator is not available '\n                      'and no BOKEH_SECRET_KEY has been set. '\n                      'Setting a secret key will mitigate the lack of a secure '\n                      'generator.')\n    using_sysrandom = False\n\ndef _ensure_bytes(secret_key):\n    if secret_key is None:\n        return None\n    elif isinstance(secret_key, binary_type):\n        return secret_key\n    else:\n        return codecs.encode(secret_key, 'utf-8')\n\n# this is broken out for unit testability\ndef _reseed_if_needed(using_sysrandom, secret_key):\n    secret_key = _ensure_bytes(secret_key)\n    if not using_sysrandom:\n        # This is ugly, and a hack, but it makes things better than\n        # the alternative of predictability. This re-seeds the PRNG\n        # using a value that is hard for an attacker to predict, every\n        # time a random string is required. This may change the\n        # properties of the chosen random sequence slightly, but this\n        # is better than absolute predictability.\n        random.seed(\n            hashlib.sha256(\n                (\"%s%s%s\" % (\n                    random.getstate(),\n                    time.time(),\n                    secret_key)).encode('utf-8')\n            ).digest())\n\ndef _base64_encode(decoded):\n    # base64 encode both takes and returns bytes, we want to work with strings.\n    # If 'decoded' isn't bytes already, assume it's utf-8\n    decoded_as_bytes = _ensure_bytes(decoded)\n    encoded = codecs.decode(base64.urlsafe_b64encode(decoded_as_bytes), 'ascii')\n    # remove padding '=' chars that cause trouble\n    return str(encoded.rstrip('='))\n\ndef _signature(base_id, secret_key):\n    secret_key = _ensure_bytes(secret_key)\n    base_id = codecs.encode(base_id, \"utf-8\")\n    signer = hmac.new(secret_key, base_id, hashlib.sha256)\n    return _base64_encode(signer.digest())\n\ndef _get_random_string(length=44,\n                       allowed_chars='abcdefghijklmnopqrstuvwxyz'\n                       'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',\n                       secret_key=settings.secret_key_bytes()):\n    \"\"\"\n    Return a securely generated random string.\n    With the a-z, A-Z, 0-9 character set:\n    Length 12 is a 71-bit value. log_2((26+26+10)^12) =~ 71\n    Length 44 is a 261-bit value. log_2((26+26+10)^44) = 261\n    \"\"\"\n    secret_key = _ensure_bytes(secret_key)\n    _reseed_if_needed(using_sysrandom, secret_key)\n    return ''.join(random.choice(allowed_chars) for i in range(length))\n\ndef generate_secret_key():\n    \"\"\"\n    Generate a new securely-generated secret key appropriate\n    for SHA-256 HMAC signatures. This key could be used to\n    sign Bokeh server session IDs for example.\n    \"\"\"\n    return _get_random_string()\n\ndef generate_session_id(secret_key=settings.secret_key_bytes(), signed=settings.sign_sessions()):\n    \"\"\"Generate a random session ID.\n\n    Typically, each browser tab connected to a Bokeh application\n    has its own session ID.  In production deployments of a Bokeh\n    app, session IDs should be random and unguessable - otherwise\n    users of the app could interfere with one another.\n\n    If session IDs are signed with a secret key, the server can\n    verify that the generator of the session ID was \"authorized\"\n    (the generator had to know the secret key). This can be used\n    to have a separate process, such as another web application,\n    which generates new sessions on a Bokeh server. This other\n    process may require users to log in before redirecting them to\n    the Bokeh server with a valid session ID, for example.\n\n    Args:\n        secret_key (str, optional) : Secret key (default: value of 'BOKEH_SECRET_KEY' env var)\n        signed (bool, optional) : Whether to sign the session ID (default: value of\n                                  'BOKEH_SIGN_SESSIONS' env var)\n\n    \"\"\"\n    secret_key = _ensure_bytes(secret_key)\n    if signed:\n        # note: '-' can also be in the base64 encoded signature\n        base_id = _get_random_string(secret_key=secret_key)\n        return base_id + '-' + _signature(base_id, secret_key)\n    else:\n        return _get_random_string(secret_key=secret_key)\n\ndef check_session_id_signature(session_id, secret_key=settings.secret_key_bytes(),\n                               signed=settings.sign_sessions()):\n    \"\"\"Check the signature of a session ID, returning True if it's valid.\n\n    The server uses this function to check whether a session ID\n    was generated with the correct secret key. If signed sessions are disabled,\n    this function always returns True.\n\n    Args:\n        session_id (str) : The session ID to check\n        secret_key (str, optional) : Secret key (default: value of 'BOKEH_SECRET_KEY' env var)\n        signed (bool, optional) : Whether to check anything (default: value of\n                                  'BOKEH_SIGN_SESSIONS' env var)\n\n    \"\"\"\n    secret_key = _ensure_bytes(secret_key)\n    if signed:\n        pieces = session_id.split('-', 1)\n        if len(pieces) != 2:\n            return False\n        base_id = pieces[0]\n        provided_signature = pieces[1]\n        expected_signature = _signature(base_id, secret_key)\n        # hmac.compare_digest() uses a string compare algorithm that doesn't\n        # short-circuit so we don't allow timing analysis\n        # encode_utf8 is used to ensure that strings have same encoding\n        return hmac.compare_digest(encode_utf8(expected_signature), encode_utf8(provided_signature))\n    else:\n        return True\n", "path": "bokeh/util/session_id.py"}]}