Esempio n. 1
0
    def valid(self):
        """
        Checks if the resource data is valid

        :return: True, if the resource is valid
        """
        # As long as the type is valid and type string.
        # validate the condition should be string

        if self.condition:

            if not is_str()(self.condition, should_raise=False):
                raise InvalidDocumentException([
                    InvalidTemplateException(
                        "Every Condition member must be a string.")
                ])

        if self.deletion_policy:

            if not is_str()(self.deletion_policy, should_raise=False):
                raise InvalidDocumentException([
                    InvalidTemplateException(
                        "Every DeletionPolicy member must be a string.")
                ])

        if self.update_replace_policy:

            if not is_str()(self.update_replace_policy, should_raise=False):
                raise InvalidDocumentException([
                    InvalidTemplateException(
                        "Every UpdateReplacePolicy member must be a string.")
                ])

        return SamResourceType.has_value(self.type)
    def _get_type_from_intrinsic_if(self, policy):
        """
        Returns the type of the given policy assuming that it is an intrinsic if function

        :param policy: Input value to get type from
        :return: PolicyTypes: Type of the given policy. PolicyTypes.UNKNOWN, if type could not be inferred
        """
        intrinsic_if_value = policy["Fn::If"]

        try:
            validate_intrinsic_if_items(intrinsic_if_value)
        except ValueError as e:
            raise InvalidTemplateException(e)

        if_data = intrinsic_if_value[1]
        else_data = intrinsic_if_value[2]

        if_data_type = self._get_type(if_data)
        else_data_type = self._get_type(else_data)

        if if_data_type == else_data_type:
            return if_data_type

        if is_intrinsic_no_value(if_data):
            return else_data_type

        if is_intrinsic_no_value(else_data):
            return if_data_type

        raise InvalidTemplateException(
            "Different policy types within the same Fn::If statement is unsupported. "
            "Separate different policy types into different Fn::If statements")
    def __init__(self, parameters, supported_intrinsics=None):
        """
        Instantiate the resolver
        :param dict parameters: Map of parameter names to their values
        :param dict supported_intrinsics: Dictionary of intrinsic functions this class supports along with the
            Action class that can process this intrinsic
        :raises TypeError: If parameters or the supported_intrinsics arguments are invalid
        """

        if supported_intrinsics is None:
            supported_intrinsics = DEFAULT_SUPPORTED_INTRINSICS
        if parameters is None or not isinstance(parameters, dict):
            raise InvalidDocumentException([
                InvalidTemplateException(
                    "'Mappings' or 'Parameters' is either null or not a valid dictionary."
                )
            ])

        if not isinstance(supported_intrinsics, dict) or not all([
                isinstance(value, Action)
                for value in supported_intrinsics.values()
        ]):
            raise TypeError(
                "supported_intrinsics argument must be intrinsic names to corresponding Action classes"
            )

        self.supported_intrinsics = supported_intrinsics
        self.parameters = parameters
    def add_path(self, path, method=None):
        """
        Adds the path/method combination to the Swagger, if not already present

        :param string path: Path name
        :param string method: HTTP method
        :raises ValueError: If the value of `path` in Swagger is not a dictionary
        """
        method = self._normalize_method_name(method)

        path_dict = self.paths.setdefault(path, {})

        if not isinstance(path_dict, dict):
            # Either customers has provided us an invalid Swagger, or this class has messed it somehow
            raise InvalidDocumentException(
                [
                    InvalidTemplateException(
                        "Value of '{}' path must be a dictionary according to Swagger spec.".format(path)
                    )
                ]
            )

        if self._CONDITIONAL_IF in path_dict:
            path_dict = path_dict[self._CONDITIONAL_IF][1]

        path_dict.setdefault(method, {})
Esempio n. 5
0
    def _add_iam_resource_policy_for_method(self, policy_list, effect, resource_list):
        """
        This method generates a policy statement to grant/deny specific IAM users access to the API method and
        appends it to the swagger under `x-amazon-apigateway-policy`
        :raises ValueError: If the effect passed in does not match the allowed values.
        """
        if not policy_list:
            return

        if effect not in ["Allow", "Deny"]:
            raise ValueError('Effect must be one of {}'.format(['Allow', 'Deny']))

        if not isinstance(policy_list, (dict, list)):
            raise InvalidDocumentException(
                [InvalidTemplateException("Type of '{}' must be a list or dictionary"
                                          .format(policy_list))])

        if not isinstance(policy_list, list):
            policy_list = [policy_list]

        self.resource_policy['Version'] = '2012-10-17'
        policy_statement = {}
        policy_statement['Effect'] = effect
        policy_statement['Action'] = "execute-api:Invoke"
        policy_statement['Resource'] = resource_list
        policy_statement['Principal'] = {"AWS": policy_list}

        if self.resource_policy.get('Statement') is None:
            self.resource_policy['Statement'] = policy_statement
        else:
            statement = self.resource_policy['Statement']
            if not isinstance(statement, list):
                statement = [statement]
            statement.extend([policy_statement])
            self.resource_policy['Statement'] = statement
Esempio n. 6
0
    def _validate(self, sam_template, parameter_values):
        """ Validates the template and parameter values and raises exceptions if there's an issue

        :param dict sam_template: SAM template
        :param dict parameter_values: Dictionary of parameter values provided by the user
        """
        if parameter_values is None:
            raise ValueError("`parameter_values` argument is required")

        if (
            "Resources" not in sam_template
            or not isinstance(sam_template["Resources"], dict)
            or not sam_template["Resources"]
        ):
            raise InvalidDocumentException([InvalidTemplateException("'Resources' section is required")])

        if not all(isinstance(sam_resource, dict) for sam_resource in sam_template["Resources"].values()):
            raise InvalidDocumentException(
                [
                    InvalidTemplateException(
                        "All 'Resources' must be Objects. If you're using YAML, this may be an " "indentation issue."
                    )
                ]
            )

        sam_template_instance = SamTemplate(sam_template)

        for resource_logical_id, sam_resource in sam_template_instance.iterate():
            # NOTE: Properties isn't required for SimpleTable, so we can't check
            # `not isinstance(sam_resources.get("Properties"), dict)` as this would be a breaking change.
            # sam_resource.properties defaults to {} in SamTemplate init
            if not isinstance(sam_resource.properties, dict):
                raise InvalidDocumentException(
                    [
                        InvalidResourceException(
                            resource_logical_id,
                            "All 'Resources' must be Objects and have a 'Properties' Object. If "
                            "you're using YAML, this may be an indentation issue.",
                        )
                    ]
                )

        SamTemplateValidator.validate(sam_template)
Esempio n. 7
0
    def _validate(self, sam_template):
        """ Validates the template and parameter values and raises exceptions if there's an issue

        :param dict sam_template: SAM template
        """

        if "Resources" not in sam_template or not isinstance(sam_template["Resources"], dict) \
                or not sam_template["Resources"]:
            raise InvalidDocumentException(
                [InvalidTemplateException("'Resources' section is required")])

        SamTemplateValidator.validate(sam_template)
Esempio n. 8
0
    def set_path_default_authorizer(self, path, default_authorizer,
                                    authorizers, api_authorizers):
        """
        Adds the default_authorizer to the security block for each method on this path unless an Authorizer
        was defined at the Function/Path/Method level. This is intended to be used to set the
        authorizer security restriction for all api methods based upon the default configured in the
        Serverless API.

        :param string path: Path name
        :param string default_authorizer: Name of the authorizer to use as the default. Must be a key in the
            authorizers param.
        :param list authorizers: List of Authorizer configurations defined on the related Api.
        """
        for method_name, method in self.get_path(path).items():
            normalized_method_name = self._normalize_method_name(method_name)
            # Excluding parameters section
            if normalized_method_name == "parameters":
                continue
            if normalized_method_name != "options":
                normalized_method_name = self._normalize_method_name(
                    method_name)
                # It is possible that the method could have two definitions in a Fn::If block.
                if normalized_method_name not in self.get_path(path):
                    raise InvalidDocumentException([
                        InvalidTemplateException(
                            "Could not find {} in {} within DefinitionBody.".
                            format(normalized_method_name, path))
                    ])
                for method_definition in self.get_method_contents(
                        self.get_path(path)[normalized_method_name]):
                    # If no integration given, then we don't need to process this definition (could be AWS::NoValue)
                    if not self.method_definition_has_integration(
                            method_definition):
                        continue
                    existing_security = method_definition.get("security", [])
                    if existing_security:
                        return
                    authorizer_list = []
                    if authorizers:
                        authorizer_list.extend(authorizers.keys())
                    security_dict = dict()
                    security_dict[
                        default_authorizer] = self._get_authorization_scopes(
                            api_authorizers, default_authorizer)
                    authorizer_security = [security_dict]

                    security = authorizer_security

                    if security:
                        method_definition["security"] = security
Esempio n. 9
0
    def _validate(self, sam_template, parameter_values):
        """ Validates the template and parameter values and raises exceptions if there's an issue

        :param dict sam_template: SAM template
        :param dict parameter_values: Dictionary of parameter values provided by the user
        """
        if parameter_values is None:
            raise ValueError("`parameter_values` argument is required")

        if "Resources" not in sam_template or not isinstance(
                sam_template["Resources"],
                dict) or not sam_template["Resources"]:
            raise InvalidDocumentException(
                [InvalidTemplateException("'Resources' section is required")])

        SamTemplateValidator.validate(sam_template)
Esempio n. 10
0
    def resolve_parameter_refs(self, input_dict, parameters):
        """
        Recursively resolves "Fn::FindInMap"references that are present in the mappings and returns the value.
        If it is not in mappings, this method simply returns the input unchanged.

        :param input_dict: Dictionary representing the FindInMap function. Must contain only one key and it
                           should be "Fn::FindInMap".

        :param parameters: Dictionary of mappings from the SAM template
        """
        if not self.can_handle(input_dict):
            return input_dict

        value = input_dict[self.intrinsic_name]

        # FindInMap expects an array with 3 values
        if not isinstance(value, list) or len(value) != 3:
            raise InvalidDocumentException([
                InvalidTemplateException(
                    "Invalid FindInMap value {}. FindInMap expects an array with 3 values."
                    .format(value))
            ])

        map_name = self.resolve_parameter_refs(value[0], parameters)
        top_level_key = self.resolve_parameter_refs(value[1], parameters)
        second_level_key = self.resolve_parameter_refs(value[2], parameters)

        if (not isinstance(map_name, string_types)
                or not isinstance(top_level_key, string_types)
                or not isinstance(second_level_key, string_types)):
            return input_dict

        if (map_name not in parameters
                or top_level_key not in parameters[map_name] or
                second_level_key not in parameters[map_name][top_level_key]):
            return input_dict

        return parameters[map_name][top_level_key][second_level_key]
Esempio n. 11
0
    def _validate(self, sam_template, parameter_values):
        """ Validates the template and parameter values and raises exceptions if there's an issue

        :param dict sam_template: SAM template
        :param dict parameter_values: Dictionary of parameter values provided by the user
        """
        if parameter_values is None:
            raise ValueError("`parameter_values` argument is required")

        if "Resources" not in sam_template or not isinstance(
                sam_template["Resources"],
                dict) or not sam_template["Resources"]:
            raise InvalidDocumentException(
                [InvalidTemplateException("'Resources' section is required")])

        validation_errors = SamTemplateValidator.validate(sam_template)
        has_errors = len(validation_errors)

        if has_errors:
            # NOTE: eventually we will throw on invalid schema
            # raise InvalidDocumentException([InvalidTemplateException(validation_errors)])
            logging.warning(
                "JSON_VALIDATION_WARNING: {0}".format(validation_errors))
Esempio n. 12
0
    def resolve_resource_refs(self, input_dict, supported_resource_refs):
        """
        Resolve resource references within a GetAtt dict.

        Example:
            { "Fn::GetAtt": ["LogicalId.Property", "Arn"] }  =>  {"Fn::GetAtt":  ["ResolvedLogicalId", "Arn"]}


        Theoretically, only the first element of the array can contain reference to SAM resources. The second element
        is name of an attribute (like Arn) of the resource.

        However tools like AWS CLI apply the assumption that first element of the array is a LogicalId and cannot
        contain a 'dot'. So they break at the first dot to convert YAML tag to JSON map like this:

             `!GetAtt LogicalId.Property.Arn` => {"Fn::GetAtt": [ "LogicalId", "Property.Arn" ] }

        Therefore to resolve the reference, we join the array into a string, break it back up to check if it contains
        a known reference, and resolve it if we can.

        :param input_dict: Dictionary to be resolved
        :param samtransaltor.intrinsics.resource_refs.SupportedResourceReferences supported_resource_refs: Instance of
            an `SupportedResourceReferences` object that contain value of the property.
        :return: Resolved dictionary
        """

        if not self.can_handle(input_dict):
            return input_dict

        key = self.intrinsic_name
        value = input_dict[key]

        # Value must be an array with *at least* two elements. If not, this is invalid GetAtt syntax. We just pass along
        # the input to CFN for it to do the "official" validation.
        if not isinstance(value, list) or len(value) < 2:
            return input_dict

        if not all(isinstance(entry, string_types) for entry in value):
            raise InvalidDocumentException([
                InvalidTemplateException(
                    "Invalid GetAtt value {}. GetAtt expects an array with 2 strings."
                    .format(value))
            ])

        # Value of GetAtt is an array. It can contain any number of elements, with first being the LogicalId of
        # resource and rest being the attributes. In a SAM template, a reference to a resource can be used in the
        # first parameter. However tools like AWS CLI might break them down as well. So let's just concatenate
        # all elements, and break them into separate parts in a more standard way.
        #
        # Example:
        #   { Fn::GetAtt: ["LogicalId.Property", "Arn"] } is equivalent to { Fn::GetAtt: ["LogicalId", "Property.Arn"] }
        #   Former is the correct notation. However tools like AWS CLI can construct the later style.
        #   Let's normalize the value into "LogicalId.Property.Arn" to handle both scenarios

        value_str = self._resource_ref_separator.join(value)
        splits = value_str.split(self._resource_ref_separator)
        logical_id = splits[0]
        property = splits[1]
        remaining = splits[2:]  # if any

        resolved_value = supported_resource_refs.get(logical_id, property)
        return self._get_resolved_dictionary(input_dict, key, resolved_value,
                                             remaining)