def redact(interaction, cassette): """Remove sensitive or environment-specific information from cassettes before they are saved. Parameters ---------- interaction cassette Returns ------- """ url = interaction.data['request']['uri'] host = urlsplit(url).netloc if Placeholder(placeholder='hostname.com', replace=host) not in cassette.placeholders: cassette.placeholders.append( Placeholder(placeholder='hostname.com', replace=host)) # Server name in Origin header may differ from hostname that was sent the request. for origin in interaction.data['response']['headers'].get('Origin', []): host = urlsplit(origin).netloc if host != '' and Placeholder( placeholder='hostname.com', replace=host) not in cassette.placeholders: cassette.placeholders.append( Placeholder(placeholder='hostname.com', replace=host)) def add_placeholder(pattern, string, placeholder, group): if isinstance(string, bytes): pattern = pattern.encode('utf-8') # placeholder = placeholder.encode('utf-8') match = re.search(pattern, string) if match: # old_text = match.group(group).encode('utf-8') if isinstance(string, bytes) else match.group(group) old_text = match.group(group) cassette.placeholders.append( Placeholder(placeholder=placeholder, replace=old_text)) add_placeholder(r"(?<=&password=)([^&]*)\b", interaction.data['request']['body']['string'], '*****', 1) add_placeholder('(?<=access_token":")[^"]*', interaction.data['response']['body']['string'], '[redacted]', 0) for index, header in enumerate(interaction.data['request']['headers'].get( 'Authorization', [])): # Betamax tries to replace Placeholders on all headers. Mixed str/bytes headers will cause Betamax to break. if isinstance(header, bytes): header = header.decode('utf-8') interaction.data['request']['headers']['Authorization'][ index] = header add_placeholder(r'(?<=Basic ).*', header, '[redacted]', 0) # swat add_placeholder(r'(?<=Bearer ).*', header, '[redacted]', 0) # sasctl
def add_placeholder(pattern, string, placeholder, group): if isinstance(string, bytes): pattern = pattern.encode('utf-8') match = re.search(pattern, string) if match: old_text = match.group(group) cassette.placeholders.append( Placeholder(placeholder=placeholder, replace=old_text))
def scrub(interaction, current_cassette): request = interaction.data.get('request') or {} response = interaction.data.get('response') or {} # Exit early if the request did not return 200 OK because that's the # only time we want to look for tokens if not response or response['status']['code'] != 200: return for what in [r for r in [request, response] if r]: auths = what['headers'].get('Authorization') or [] for auth in auths: current_cassette.placeholders.append( Placeholder(placeholder='**********', replace=auth)) body_string = what['body']['string'] try: dikt = json.loads(body_string) except: dikt = {k: v[0] for k, v in parse_qs(body_string).items()} for token in ['access_token', 'refresh_token']: if token in dikt: current_cassette.placeholders.append( Placeholder(placeholder='**********', replace=dikt[token]))
def sanitize_cookies(interaction, cassette): response = interaction.as_response() response_cookies = response.cookies request_cookies = dict() for cookie in (interaction.as_response().request.headers.get('Cookie') or '').split('; '): name, sep, val = cookie.partition('=') if sep: request_cookies[name] = val secret_values = set() for name in CLASSIFIED_COOKIES: potential_val = response_cookies.get(name) if potential_val: secret_values.add(potential_val) potential_val = request_cookies.get(name) if potential_val: secret_values.add(potential_val) for val in secret_values: cassette.placeholders.append( Placeholder(placeholder='<AUTH COOKIE>', replace=val) )
def sanitize_cookies(interaction, cassette): response = interaction.as_response() response_cookies = response.cookies request_body = response.request.body or '' # where secret values hide # the or '' is necessary above because sometimes response.request.body # is empty bytes, and that makes the later code complain. secret_values = set() for name in CLASSIFIED_COOKIES: potential_val = response_cookies.get(name) if potential_val: secret_values.add(potential_val) named_parameter_str = '&{}='.format(name) if (named_parameter_str in request_body or request_body.startswith(named_parameter_str[1:])): i = request_body.index(name) + len(name) + 1 # +1 for the = sign val = request_body[i:].split(',')[ 0] # after the comma is another cookie secret_values.add(val) for val in secret_values: cassette.placeholders.append( Placeholder(placeholder='<AUTH COOKIE>', replace=val))
def _sanitize_betamax_cookies(interaction, cassette): # TODO handle also request body occurence of __RequestVerificationToken response = interaction.as_response() response_cookies = response.cookies request_cookies = dict() for cookie in (interaction.as_response().request.headers.get("Cookie") or "").split("; "): name, sep, val = cookie.partition("=") if sep: request_cookies[name] = val secret_values = set() for name in CLASSIFIED_COOKIES: potential_val = response_cookies.get(name) if potential_val: secret_values.add(potential_val) potential_val = request_cookies.get(name) if potential_val: secret_values.add(potential_val) for val in secret_values: cassette.placeholders.append( Placeholder(placeholder="<AUTH COOKIE>", replace=val))