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, {})
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
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)
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)
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
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)
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]
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))
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)