Skip to content

Commit

Permalink
add the utility tool inspect_cookie.py for debugging
Browse files Browse the repository at this point in the history
  • Loading branch information
Nakaner committed Nov 6, 2019
1 parent 4801ea2 commit 7a6e540
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 12 deletions.
71 changes: 71 additions & 0 deletions inspect_cookie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#! /usr/bin/env python3

import argparse
import sys
import nacl.exceptions
from sendfile_osm_oauth_protector.config import Config
from sendfile_osm_oauth_protector.key_manager import KeyManager
from sendfile_osm_oauth_protector.oauth_data_cookie import OAuthDataCookie
from sendfile_osm_oauth_protector.oauth_error import OAuthError


def print_cookie_content(content):
sys.stdout.write("Cookie content:\n")
for i in range(0, len(contents)):
field = ""
if i == 0:
field = "state"
elif i == 1:
field = "key_name"
elif i == 2:
field = "signed_content"
sys.stdout.write(" {}: {}\n".format(field, contents[i]))


def print_tokens(cookie):
sys.stdout.write("Decrypted cookie content:\n")
sys.stdout.write(" access_token: {}\n".format(cookie.access_token))
sys.stdout.write(" access_token_secret: {}\n".format(cookie.access_token_secret))
sys.stdout.write(" valid_until: {}\n".format(cookie.valid_until))


parser = argparse.ArgumentParser(description="Read a cookie from standard input, decrypt its content and write to standard output. This program is intended to run on the server by an administrator for debugging purposes.")
args = parser.parse_args()

config = Config()
key_manager = KeyManager(config.KEY_DIR)

cookie_raw = sys.stdin.readline()

environ = {"HTTP_COOKIE": cookie_raw, "QUERY_STRING": ""}
data_cookie = OAuthDataCookie(config, environ, key_manager)

contents = data_cookie.parse_cookie_step1()

print_cookie_content(contents)
if len(contents) == 1:
sys.stdout.write("WARNING: The cookie could not be verified because it is invalid or set to 'logout'.\n")
exit(0)
if len(contents) != 3:
sys.stdout.write("ERROR: The cookie has an unusual length.\n")
exit(1)

try:
access_tokens_encr = data_cookie.parse_cookie_step2(contents)
except nacl.exceptions.BadSignatureError:
sys.stdout.write("RESULT: Verification of signature failed.\n")
exit(0)
except KeyError:
sys.stdout.write("ERROR: Key error during verification of signature. Maybe loading the key failed.\n")
exit(1)
except Exception:
sys.stdout.write("ERROR: Other error during verification of signature.\n")
exit(1)

sys.stdout.write("INFO: Signed content has a valid signature.\n")

try:
data_cookie.parse_cookie_step3(access_tokens_encr)
except OAuthError as err:
sys.stdout.write("ERROR: {}\n".format(err))
print_tokens(data_cookie)
1 change: 1 addition & 0 deletions sendfile_osm_oauth_protector/authentication_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ class AuthenticationState(enum.Enum):
OAUTH_ACCESS_TOKEN_VALID = 4
OAUTH_ACCESS_TOKEN_RECHECK = 8
SIGNATURE_VERIFICATION_FAILED = 16
OTHER_FAILURE = 32
62 changes: 50 additions & 12 deletions sendfile_osm_oauth_protector/oauth_data_cookie.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,53 @@ def get_access_token_from_api(self):
except KeyError as err:
raise OAuthError("Incomplete response of OSM API, oauth_token or oauth_token_secret is missing.", "502 Bad Gateway") from err

def parse_cookie_step1(self):
"""Get the three main parts of the cookie: state, key name and signed content.
Returns:
list of string
"""
return self.cookie[self.config.COOKIE_NAME].value.split("|")

def parse_cookie_step2(self, contents):
"""
Verify the cookie.
Args:
contents : result of parse_cookie_step1()
Returns:
str : encrypted access tokesn (can be None)
Throws:
KeyError : key not found
nacl.exceptions.BadSignatureError : invalid signature
"""
key_name = contents[1]
self._load_read_keys(key_name)
signed = contents[2].encode("ascii")
access_tokens_encr = self.verify_key.verify(base64.urlsafe_b64decode(signed))
return access_tokens_encr

def parse_cookie_step3(self, access_tokens_encr):
"""
Get decrypted access tokens and validity date of the cookie. This method sets the
properties self.access_token, self.access_token_secret and self.valid_until
Args:
access_tokens_encr (str) : result of parse_cookie_step2()
Throws:
OAuthError : decryption has failed
"""
try:
parts = self.read_crypto_box.decrypt(access_tokens_encr).decode("ascii").split("|")
self.access_token = parts[0]
self.access_token_secret = parts[1]
self.valid_until = datetime.datetime.strptime(parts[2], "%Y-%m-%dT%H:%M:%S")
except Exception as err:
raise OAuthError("decryption of tokens failed", "400 Bad Request") from err

def get_state(self):
"""
Check if the signature of the cookie is valid, decrypt the cookie.
Expand All @@ -103,31 +150,22 @@ def get_state(self):
elif self.cookie is None:
return AuthenticationState.SHOW_LANDING_PAGE
try:
contents = self.cookie[self.config.COOKIE_NAME].value.split("|")
contents = self.parse_cookie_step1()
if len(contents) < 3 or contents[0] != "login":
if is_redirected_from_osm:
return AuthenticationState.LOGGED_IN
if landing_page[0] != "true":
return AuthenticationState.SHOW_LANDING_PAGE
# landing page has been seen already
return AuthenticationState.NONE
key_name = contents[1]
self._load_read_keys(key_name)
signed = contents[2].encode("ascii")
access_tokens_encr = self.verify_key.verify(base64.urlsafe_b64decode(signed))
access_tokens_encr = self.parse_cookie_step2(contents)
except KeyError:
# if something fails here, they normal authentication-authorization loop should start and
# users not treated like not having seen the landing page
return AuthenticationState.NONE
except Exception:
return AuthenticationState.SIGNATURE_VERIFICATION_FAILED
try:
parts = self.read_crypto_box.decrypt(access_tokens_encr).decode("ascii").split("|")
self.access_token = parts[0]
self.access_token_secret = parts[1]
self.valid_until = datetime.datetime.strptime(parts[2], "%Y-%m-%dT%H:%M:%S")
except Exception as err:
raise OAuthError("decryption of tokens failed", "400 Bad Request") from err
self.parse_cookie_step3(access_tokens_encr)
# If users sends us an old cookie but it is too old and has parameters like being redirected back to our site,
# treat him like being redirected from OSM back to our site.
if is_redirected_from_osm and datetime.datetime.utcnow() > self.valid_until:
Expand Down

0 comments on commit 7a6e540

Please sign in to comment.