def test_basic_payload_coding(): payload = { 'iss': 'issuer', 'iat': 0, 'tid': 'asdf', 'exp': 0, 'token': 'jkljkl', 'action': { 'name': 'foo', 'type': 'success', 'output': {} }, 'param': False, 'url': 'https://example.com', } validate_payload_schema(payload) encoded_payload = encode_payload(payload, None) decoded_payload = decode_payload(encoded_payload, None) validate_payload_schema(decoded_payload) assert_dicts_equal(payload, decoded_payload)
def test_encrypted_payload_coding(): key_id = os.environ['KEY_ID'] session = boto3.Session() mkp = aws_encryption_sdk.KMSMasterKeyProvider( key_ids=[key_id], botocore_session=session._session) payload = { 'iss': 'issuer', 'iat': 0, 'tid': 'asdf', 'exp': 0, 'token': 'jkljkl', 'action': { 'name': 'foo', 'type': 'success', 'output': {} }, 'param': False, } validate_payload_schema(payload) encoded_payload = encode_payload(payload, mkp) assert encoded_payload.startswith('2-') decoded_payload = decode_payload(encoded_payload, mkp) validate_payload_schema(decoded_payload) assert_dicts_equal(payload, decoded_payload) encoded_payload = encode_payload(payload, None) assert encoded_payload.startswith('1-') with pytest.raises(EncryptionRequired): decoded_payload = decode_payload(encoded_payload, mkp) encoded_payload = encode_payload(payload, mkp) assert encoded_payload.startswith('2-') with pytest.raises(DecryptionUnsupported): decoded_payload = decode_payload(encoded_payload, None)
def handler(request, context): if is_verbose(): print(f'Request: {json.dumps(request)}') timestamp = datetime.datetime.now() log_event = { 'timestamp': timestamp.isoformat(), } try: response = OrderedDict( ) # ordered so it appears sensibly in the HTML output (action_name_from_url, action_type_from_url, encoded_payload, parameters) = load_from_request(request) decode_start = time.perf_counter() payload = decode_payload(encoded_payload, MASTER_KEY_PROVIDER) decode_finish = time.perf_counter() log_event['decode_time'] = (decode_finish - decode_start) validate_payload_schema(payload) if is_verbose(): print(f'Payload: {json.dumps(payload)}') # use the same transaction id given out in the create urls call log_event['transaction_id'] = payload['tid'] response['transaction_id'] = payload['tid'] validate_payload_expiration(payload, timestamp) # we put the action name and type in the query string directly for convenience # but we only trust the version that's in the payload. If the query string # versions differ from the payload, something funny is going on and we reject # the request. But if they are absent, it's not a problem. action_name_in_payload = payload['action']['name'] if action_name_from_url and action_name_from_url != action_name_in_payload: raise ActionMismatched( f'The action name says {action_name_from_url} in the url but {action_name_in_payload} in the payload' ) action_name = action_name_in_payload action_type_in_payload = payload['action']['type'] if action_type_from_url and action_type_from_url != action_type_in_payload: raise ActionMismatched( f'The action type says {action_type_in_payload} in the url but {action_type_in_payload} in the payload' ) action_type = action_type_in_payload log_event['action'] = {'name': action_name, 'type': action_type} response['action'] = OrderedDict(( ('name', action_name), ('type', action_type), )) # If parameters are disabled, refuse to service a request # that has parameters enabled, even though it was presumably # valid at creation time to have parameters enabled. force_disable_parameters = get_force_disable_parameters() use_parameters = payload.get('param', False) if use_parameters and force_disable_parameters: raise ParametersDisabled('Parameters are disabled') if not use_parameters: parameters = None action = payload['action'] response_spec = action.get('response', {}) outcome_name = action_name outcome_type = action_type if action_type == 'post': (post_outcome_name, post_outcome_type, outcome_response_spec, method_params) = process_post_action(action, request, parameters, log_event) outcome_name = outcome_name + '.' + post_outcome_name outcome_type = post_outcome_type if outcome_response_spec is not None: response_spec = outcome_response_spec else: method_params = prepare_method_params(action, parameters, log_event=log_event) log_event['outcome_name'] = outcome_name log_event['outcome_type'] = outcome_type if is_verbose(): print(f'Input for {outcome_type}: {json.dumps(method_params)}') method = f'send_task_{outcome_type}' method_params['taskToken'] = payload['token'] try: sfn_call_start = time.perf_counter() sfn_response = getattr(STEP_FUNCTIONS_CLIENT, method)(**method_params) sfn_call_finish = time.perf_counter() log_event['sfn_call_time'] = (sfn_call_finish - sfn_call_start) except botocore.exceptions.ClientError as e: error_code = e.response['Error']['Code'] error_msg = e.response['Error']['Message'] # These errors are related to the state machine itself, and # should be 400 errors. # Other ClientErrors, like invalid permissions, should be # considered 500 errors. errors = [ 'InvalidOutput', 'InvalidToken', 'TaskDoesNotExist', 'TaskTimedOut', ] if error_code in errors: raise StepFunctionsError(f'{error_code}:{error_msg}') raise return_value = format_response(200, response, request, response_spec, parameters, log_event) send_log_event(log_event) if is_verbose(): print(f'Response: {json.dumps(return_value)}') return return_value except ReturnHttpResponse as e: log_event['error'] = { 'type': e.TYPE, 'error': e.code(), 'message': e.message(), } return_value = e.get_response() send_log_event(log_event) if is_verbose(): print(f'Response: {json.dumps(return_value)}') return return_value except BaseError as e: response = OrderedDict(( ('error', e.code()), ('message', e.message()), )) log_event['error'] = { 'type': e.TYPE, 'error': e.code(), 'message': e.message(), } return_value = format_response(400, response, request, {}, None, log_event) send_log_event(log_event) if is_verbose(): print(f'Response: {json.dumps(return_value)}') return return_value except Exception as e: traceback.print_exc() error_class_name = type(e).__module__ + '.' + type(e).__name__ response = OrderedDict(( ('error', 'ServiceError'), ('message', f'{error_class_name}: {str(e)}'), )) log_event['error'] = { 'type': 'Unexpected', 'error': error_class_name, 'message': str(e), } return_value = format_response(500, response, request, {}, None, log_event) send_log_event(log_event) if is_verbose(): print(f'Response: {json.dumps(return_value)}') return return_value
def handler(request, context): if is_verbose: print(f'Request: {json.dumps(request)}') timestamp = datetime.datetime.now() log_event = { 'timestamp': timestamp.isoformat(), } try: response = OrderedDict() (action_name_from_url, action_type_from_url, encoded_payload, parameters) = load_from_request(request) decode_start = time.perf_counter() payload = decode_payload(encoded_payload, MASTER_KEY_PROVIDER) decode_finish = time.perf_counter() log_event['decode_time'] = (decode_finish - decode_start) validate_payload_schema(payload) if is_verbose: print(f'Payload: {json.dumps(payload)}') log_event['transaction_id'] = payload['tid'] response['transaction_id'] = payload['tid'] validate_payload_expiration(payload, timestamp) action_name_in_payload = payload['name'] if action_name_from_url and action_name_from_url != action_name_in_payload: raise ActionMismatchedError( f'The action name says {action_name_from_url} in the url but {action_name_in_payload} in the payload' ) action_name = action_name_in_payload action_type_in_payload = payload['act'] if action_type_from_url and action_type_from_url != action_type_in_payload: raise ActionMismatchedError( f'The action type says {action_type_in_payload} in the url but {action_type_in_payload} in the payload' ) action_type = action_type_in_payload log_event['action'] = {'name': action_name, 'type': action_type} response['action'] = OrderedDict(( ('name', action_name), ('type', action_type), )) force_disable_parameters = get_force_disable_parameters() use_parameters = payload.get('par', False) if use_parameters and force_disable_parameters: raise ParametersDisabledError('Parameters are disabled') if not use_parameters: parameters = None action_data = payload.get('data', {}) response_spec = payload.get('resp', {}) return_value = format_response(200, response, request, response_spec, parameters, log_event) method = f'send_task_{action_type}' method_params = {'taskToken': payload['token']} if action_type == 'success': output = action_data.get('output', {}) output = format_output(output, parameters) method_params['output'] = json.dumps(output) elif action_type == 'failure': for key in ['error', 'cause']: if key in action_data: method_params[key] = action_data[key] try: sfn_call_start = time.perf_counter() sfn_response = getattr(STEP_FUNCTIONS_CLIENT, method)(**method_params) sfn_call_finish = time.perf_counter() log_event['sfn_call_time'] = (sfn_call_finish - sfn_call_start) except botocore.exceptions.ClientError as e: error_code = e.response['Error']['Code'] error_msg = e.response['Error']['Message'] errors = [ 'InvalidOutput', 'InvalidToken', 'TaskDoesNotExist', 'TaskTimedOut', ] if error_code in errors: raise StepFunctionsError(error_msg) raise send_log_event(log_event) if is_verbose: print(f'Response: {json.dumps(return_value)}') return return_value except BaseError as e: response = OrderedDict(( ('error', e.code()), ('message', e.message()), )) log_event['error'] = { 'type': e.TYPE, 'error': e.code(), 'message': e.message(), } return_value = format_response(400, response, request, {}, None, log_event) send_log_event(log_event) if is_verbose: print(f'Response: {json.dumps(return_value)}') return return_value except Exception as e: traceback.print_exc() error_class_name = type(e).__module__ + '.' + type(e).__name__ response = OrderedDict(( ('error', 'ServiceError'), ('message', f'{error_class_name}: {str(e)}'), )) log_event['error'] = { 'type': 'Unexpected', 'error': error_class_name, 'message': str(e), } return_value = format_response(500, response, request, {}, None, log_event) send_log_event(log_event) if is_verbose: print(f'Response: {json.dumps(return_value)}') return return_value