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 process_event(event, context, default_api_info, response_formatter): if is_verbose(): print(f'Input: {event}') try: jsonschema.validate(event, create_urls_input_schema) except jsonschema.ValidationError as e: return response_formatter(400, {}, { 'error': 'InvalidJSON', 'message': f'{str(e)}', }) transaction_id = uuid.uuid4().hex timestamp = datetime.datetime.now() log_event = { 'transaction_id': transaction_id, 'timestamp': timestamp.isoformat(), 'actions': [], } try: # Allow the user to specify another URL endpoint, either for a separate sfn-callback-urls # deployment, for example in a multi-region or multi-account scenario. The user is on # their own for getting the same KMS key in both places. if 'base_url' in event: if isinstance(event['base_url'], str): base_url = event['base_url'] else: api_spec = event['base_url'] region = api_spec.get('region', default_api_info.region) api_id = api_spec['api_id'] stage = api_spec['stage'] base_url = get_api_gateway_url(api_id, stage, region) else: region = default_api_info.region api_id = default_api_info.api_id stage = default_api_info.stage base_url = get_api_gateway_url(api_id, stage, region) log_event.update({ 'api_id': api_id, 'stage': stage, 'region': region, }) response = { 'transaction_id': transaction_id, 'urls': {}, } expiration = None if 'expiration' in event: try: expiration = dateutil.parser.parse(event['expiration']) except Exception as e: raise InvalidDate(f'Invalid expiration: {str(e)}') expiration_delta = (expiration - timestamp).total_seconds() log_event['expiration_delta'] = expiration_delta if expiration_delta <= 0: raise InvalidDate('Expiration is in the past') response['expiration'] = expiration.isoformat() payload_builder = PayloadBuilder( transaction_id, timestamp, event['token'], enable_output_parameters=event.get('enable_output_parameters'), expiration=expiration, issuer=getattr(context, 'invoked_function_arn', None)) actions_for_log = {} for action in event['actions']: action_name = action['name'] action_type = action['type'] if action_name in actions_for_log: raise DuplicateActionName( f'Action {action_name} provided more than once') if action_type == 'post': validate_post_action(action) actions_for_log[action_name] = action_type action_response = action.get('response', {}) if 'redirect' in action_response: log_event['redirect'] = True elif any(v in action_response for v in ['json', 'html', 'text']): log_event['response_override'] = True payload = payload_builder.build(action, log_event=log_event) encoded_payload = encode_payload(payload, MASTER_KEY_PROVIDER) response['urls'][action_name] = get_url(base_url, action_name, action_type, encoded_payload, log_event=log_event) log_event['actions'] = actions_for_log return_value = response_formatter(200, {}, response) send_log_event(log_event) if is_verbose(): print(f'Response: {json.dumps(return_value)}') return return_value except BaseError as e: response = { 'transaction_id': transaction_id, 'error': e.code(), 'message': e.message(), } log_event['error'] = { 'type': 'RequestError', 'error': e.code(), 'message': e.message(), } return_value = response_formatter(400, {}, response) 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 = { 'error': 'ServiceError', 'message': f'{error_class_name}: {str(e)}' } log_event['error'] = { 'type': 'Unexpected', 'error': error_class_name, 'message': str(e), } return_value = response_formatter(500, {}, response) send_log_event(log_event) if is_verbose(): print(f'Response: {json.dumps(return_value)}') return return_value
def process_event(event, context, default_api_info, response_formatter): if is_verbose: print(f'Input: {event}') try: jsonschema.validate(event, CREATE_URL_EVENT_SCHEMA) except jsonschema.ValidationError as e: return response_formatter(400, { 'error': 'InvalidJSON', 'message': f'{str(e)}', }) transaction_id = uuid.uuid4().hex timestamp = datetime.datetime.now() log_event = { 'transaction_id': transaction_id, 'timestamp': timestamp.isoformat(), 'actions': [], } try: if 'api' in event: api_spec = event['api'] region = api_spec.get('region', default_api_info.region) api_id = api_spec.get('api_id') stage = api_spec.get('stage') missing = [] if not api_id: missing.append('API id') if not stage: missing.append('stage') if missing: message = 'Missing ' + ' and '.join(missing) raise MissingApiParametersError(message) else: region = default_api_info.region api_id = default_api_info.api_id stage = default_api_info.stage log_event.update({ 'api_id': api_id, 'stage': stage, 'region': region, }) response = { 'transaction_id': transaction_id, 'urls': {}, } expiration = None if 'expiration' in event: try: expiration = dateutil.parser.parse(event['expiration']) except Exception as e: raise InvalidDateError(f'Invalid expiration: {str(e)}') expiration_delta = (expiration - timestamp).total_seconds() if expiration_delta <= 0: raise InvalidDateError('Expiration is in the past') log_event['expiration_delta'] = expiration_delta response['expiration'] = expiration.isoformat() payload_builder = PayloadBuilder( transaction_id, timestamp, event['token'], enable_output_parameters=event.get('enable_output_parameters'), expiration=expiration) actions = {} for action_name, action_data in event['actions'].items(): action_type = action_data['type'] actions[action_name] = action_type action_payload_data = {} if action_type == 'success': action_payload_data['output'] = action_data['output'] elif action_type == 'failure': for key in ['error', 'cause']: if key in action_data: action_payload_data[key] = action_data[key] elif action_type != 'heartbeat': raise InvalidActionError( f'Unexpected action type {action_type}') action_response_data = action_data.get('response', {}) payload = payload_builder.build(action_name, action_type, action_payload_data, response=action_response_data, log_event=log_event) encoded_payload = encode_payload(payload, MASTER_KEY_PROVIDER) response['urls'][action_name] = get_url(action_name, action_type, encoded_payload, api_id, stage, region, log_event=log_event) log_event['actions'] = actions return_value = response_formatter(200, response) send_log_event(log_event) if is_verbose: print(f'Response: {json.dumps(return_value)}') return return_value except BaseError as e: response = { 'transaction_id': transaction_id, 'error': e.code(), 'message': e.message(), } log_event['error'] = { 'type': 'RequestError', 'error': e.code(), 'message': e.message(), } return_value = response_formatter(400, response) 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 = { 'error': 'ServiceError', 'message': f'{error_class_name}: {str(e)}' } log_event['error'] = { 'type': 'Unexpected', 'error': error_class_name, 'message': str(e), } return_value = response_formatter(500, response) send_log_event(log_event) if is_verbose: print(f'Response: {json.dumps(return_value)}') return return_value