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
Esempio n. 4
0
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