From e2274737a87a902de2c3d977b8d6e9b8627736e9 Mon Sep 17 00:00:00 2001 From: Kevin Jansen Date: Tue, 28 Nov 2023 11:18:24 +0100 Subject: [PATCH] Add Request Signing & verify functionality Refactor flow to add the Supports function to signing methods --- .editorconfig | 861 ++++++++++++++++++ .github/workflows/code-analysis.yaml | 36 + .github/workflows/phpunit.yaml | 42 + .gitignore | 5 + README.md | 82 ++ composer.json | 56 ++ phpcs.xml | 14 + phpstan.neon | 5 + phpunit.xml.dist | 29 + src/Constant/SignatureMethod.php | 14 + .../SignatureKeyNotFoundException.php | 15 + .../UnknownSigningMethodException.php | 15 + .../UnsupportedHashingAlgorithmException.php | 15 + src/Methods/HmacSignature.php | 117 +++ src/Methods/RequestSigningMethodInterface.php | 48 + .../HmacSignatureKeyRepositoryInterface.php | 20 + src/RequestSigningService.php | 99 ++ src/ValueObject/SignatureData.php | 50 + src/ValueObject/SignatureKey.php | 28 + tests/Unit/Methods/HmacSignatureTest.php | 140 +++ tests/Unit/RequestSigningServiceTest.php | 98 ++ 21 files changed, 1789 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/code-analysis.yaml create mode 100644 .github/workflows/phpunit.yaml create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 phpcs.xml create mode 100644 phpstan.neon create mode 100644 phpunit.xml.dist create mode 100644 src/Constant/SignatureMethod.php create mode 100644 src/Exception/SignatureKeyNotFoundException.php create mode 100644 src/Exception/UnknownSigningMethodException.php create mode 100644 src/Exception/UnsupportedHashingAlgorithmException.php create mode 100644 src/Methods/HmacSignature.php create mode 100644 src/Methods/RequestSigningMethodInterface.php create mode 100644 src/Repository/HmacSignatureKeyRepositoryInterface.php create mode 100644 src/RequestSigningService.php create mode 100644 src/ValueObject/SignatureData.php create mode 100644 src/ValueObject/SignatureKey.php create mode 100644 tests/Unit/Methods/HmacSignatureTest.php create mode 100644 tests/Unit/RequestSigningServiceTest.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..aa3583f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,861 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 180 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_visual_guides = none +ij_wrap_on_typing = false + +[*.blade.php] +ij_blade_keep_indents_on_empty_lines = false + +[*.css] +ij_css_align_closing_brace_with_properties = false +ij_css_blank_lines_around_nested_selector = 1 +ij_css_blank_lines_between_blocks = 1 +ij_css_brace_placement = end_of_line +ij_css_enforce_quotes_on_format = false +ij_css_hex_color_long_format = false +ij_css_hex_color_lower_case = false +ij_css_hex_color_short_format = false +ij_css_hex_color_upper_case = false +ij_css_keep_blank_lines_in_code = 2 +ij_css_keep_indents_on_empty_lines = false +ij_css_keep_single_line_blocks = false +ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_css_space_after_colon = true +ij_css_space_before_opening_brace = true +ij_css_use_double_quotes = true +ij_css_value_alignment = do_not_align + +[*.feature] +ij_gherkin_keep_indents_on_empty_lines = false + +[*.haml] +ij_haml_keep_indents_on_empty_lines = false + +[*.less] +ij_less_align_closing_brace_with_properties = false +ij_less_blank_lines_around_nested_selector = 1 +ij_less_blank_lines_between_blocks = 1 +ij_less_brace_placement = 0 +ij_less_enforce_quotes_on_format = false +ij_less_hex_color_long_format = false +ij_less_hex_color_lower_case = false +ij_less_hex_color_short_format = false +ij_less_hex_color_upper_case = false +ij_less_keep_blank_lines_in_code = 2 +ij_less_keep_indents_on_empty_lines = false +ij_less_keep_single_line_blocks = false +ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_less_space_after_colon = true +ij_less_space_before_opening_brace = true +ij_less_use_double_quotes = true +ij_less_value_alignment = 0 + +[*.sass] +ij_sass_align_closing_brace_with_properties = false +ij_sass_blank_lines_around_nested_selector = 1 +ij_sass_blank_lines_between_blocks = 1 +ij_sass_brace_placement = 0 +ij_sass_enforce_quotes_on_format = false +ij_sass_hex_color_long_format = false +ij_sass_hex_color_lower_case = false +ij_sass_hex_color_short_format = false +ij_sass_hex_color_upper_case = false +ij_sass_keep_blank_lines_in_code = 2 +ij_sass_keep_indents_on_empty_lines = false +ij_sass_keep_single_line_blocks = false +ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_sass_space_after_colon = true +ij_sass_space_before_opening_brace = true +ij_sass_use_double_quotes = true +ij_sass_value_alignment = 0 + +[*.scss] +ij_scss_align_closing_brace_with_properties = false +ij_scss_blank_lines_around_nested_selector = 1 +ij_scss_blank_lines_between_blocks = 1 +ij_scss_brace_placement = 0 +ij_scss_enforce_quotes_on_format = false +ij_scss_hex_color_long_format = false +ij_scss_hex_color_lower_case = false +ij_scss_hex_color_short_format = false +ij_scss_hex_color_upper_case = false +ij_scss_keep_blank_lines_in_code = 2 +ij_scss_keep_indents_on_empty_lines = false +ij_scss_keep_single_line_blocks = false +ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_scss_space_after_colon = true +ij_scss_space_before_opening_brace = true +ij_scss_use_double_quotes = true +ij_scss_value_alignment = 0 + +[*.twig] +ij_twig_keep_indents_on_empty_lines = false +ij_twig_spaces_inside_comments_delimiters = true +ij_twig_spaces_inside_delimiters = true +ij_twig_spaces_inside_variable_delimiters = true + +[*.vue] +ij_continuation_indent_size = 4 +ij_vue_indent_children_of_top_level = template +ij_vue_interpolation_new_line_after_start_delimiter = true +ij_vue_interpolation_new_line_before_end_delimiter = true +ij_vue_interpolation_wrap = off +ij_vue_keep_indents_on_empty_lines = false +ij_vue_spaces_within_interpolation_expressions = true + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul,phpunit.xml.dist}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.ats,*.ts}] +ij_continuation_indent_size = 4 +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = true +ij_typescript_align_multiline_parameters = true +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = off +ij_typescript_assignment_wrap = off +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = off +ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 1 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = off +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = never +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = keep +ij_typescript_extends_keyword_wrap = off +ij_typescript_extends_list_wrap = off +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = never +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = off +ij_typescript_force_quote_style = false +ij_typescript_force_semicolon_style = false +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_if_brace_force = never +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = false +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = on_every_item +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = true +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 2 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = true +ij_typescript_keep_simple_blocks_in_one_line = false +ij_typescript_keep_simple_methods_in_one_line = false +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = off +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = off +ij_typescript_object_literal_wrap = on_every_item +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = false +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_reformat_c_style_comments = false +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = true +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = true +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = false +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = true +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = false +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = false +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = off +ij_typescript_union_types_wrap = on_every_item +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = true +ij_typescript_use_explicit_js_extension = global +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = true +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = never +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = false + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.cjs,*.js}] +ij_continuation_indent_size = 4 +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = true +ij_javascript_align_multiline_parameters = true +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = off +ij_javascript_assignment_wrap = off +ij_javascript_binary_operation_sign_on_next_line = false +ij_javascript_binary_operation_wrap = off +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 1 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = off +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = never +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = keep +ij_javascript_extends_keyword_wrap = off +ij_javascript_extends_list_wrap = off +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = never +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = off +ij_javascript_force_quote_style = false +ij_javascript_force_semicolon_style = false +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_if_brace_force = never +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = on_every_item +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = true +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 2 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = true +ij_javascript_keep_simple_blocks_in_one_line = false +ij_javascript_keep_simple_methods_in_one_line = false +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = off +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = off +ij_javascript_object_literal_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_reformat_c_style_comments = false +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = true +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = true +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = true +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = false +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = false +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = false +ij_javascript_ternary_operation_wrap = off +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = true +ij_javascript_use_explicit_js_extension = global +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = true +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = never +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = false + +[{*.cjsx,*.coffee}] +ij_coffeescript_align_function_body = false +ij_coffeescript_align_imports = false +ij_coffeescript_align_multiline_array_initializer_expression = true +ij_coffeescript_align_multiline_parameters = true +ij_coffeescript_align_multiline_parameters_in_calls = false +ij_coffeescript_align_object_properties = 0 +ij_coffeescript_align_union_types = false +ij_coffeescript_align_var_statements = 0 +ij_coffeescript_array_initializer_new_line_after_left_brace = false +ij_coffeescript_array_initializer_right_brace_on_new_line = false +ij_coffeescript_array_initializer_wrap = normal +ij_coffeescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_coffeescript_blank_lines_around_function = 1 +ij_coffeescript_call_parameters_new_line_after_left_paren = false +ij_coffeescript_call_parameters_right_paren_on_new_line = false +ij_coffeescript_call_parameters_wrap = normal +ij_coffeescript_chained_call_dot_on_new_line = true +ij_coffeescript_comma_on_new_line = false +ij_coffeescript_enforce_trailing_comma = keep +ij_coffeescript_field_prefix = _ +ij_coffeescript_file_name_style = relaxed +ij_coffeescript_force_quote_style = false +ij_coffeescript_force_semicolon_style = false +ij_coffeescript_function_expression_brace_style = end_of_line +ij_coffeescript_import_merge_members = global +ij_coffeescript_import_prefer_absolute_path = global +ij_coffeescript_import_sort_members = true +ij_coffeescript_import_sort_module_name = false +ij_coffeescript_import_use_node_resolution = true +ij_coffeescript_imports_wrap = on_every_item +ij_coffeescript_indent_chained_calls = true +ij_coffeescript_indent_package_children = 0 +ij_coffeescript_jsx_attribute_value = braces +ij_coffeescript_keep_blank_lines_in_code = 2 +ij_coffeescript_keep_first_column_comment = true +ij_coffeescript_keep_indents_on_empty_lines = false +ij_coffeescript_keep_line_breaks = true +ij_coffeescript_keep_simple_methods_in_one_line = false +ij_coffeescript_method_parameters_new_line_after_left_paren = false +ij_coffeescript_method_parameters_right_paren_on_new_line = false +ij_coffeescript_method_parameters_wrap = off +ij_coffeescript_object_literal_wrap = on_every_item +ij_coffeescript_prefer_as_type_cast = false +ij_coffeescript_prefer_explicit_types_function_expression_returns = false +ij_coffeescript_prefer_explicit_types_function_returns = false +ij_coffeescript_prefer_explicit_types_vars_fields = false +ij_coffeescript_reformat_c_style_comments = false +ij_coffeescript_space_after_comma = true +ij_coffeescript_space_after_dots_in_rest_parameter = false +ij_coffeescript_space_after_generator_mult = true +ij_coffeescript_space_after_property_colon = true +ij_coffeescript_space_after_type_colon = true +ij_coffeescript_space_after_unary_not = false +ij_coffeescript_space_before_async_arrow_lparen = true +ij_coffeescript_space_before_class_lbrace = true +ij_coffeescript_space_before_comma = false +ij_coffeescript_space_before_function_left_parenth = true +ij_coffeescript_space_before_generator_mult = false +ij_coffeescript_space_before_property_colon = false +ij_coffeescript_space_before_type_colon = false +ij_coffeescript_space_before_unary_not = false +ij_coffeescript_spaces_around_additive_operators = true +ij_coffeescript_spaces_around_arrow_function_operator = true +ij_coffeescript_spaces_around_assignment_operators = true +ij_coffeescript_spaces_around_bitwise_operators = true +ij_coffeescript_spaces_around_equality_operators = true +ij_coffeescript_spaces_around_logical_operators = true +ij_coffeescript_spaces_around_multiplicative_operators = true +ij_coffeescript_spaces_around_relational_operators = true +ij_coffeescript_spaces_around_shift_operators = true +ij_coffeescript_spaces_around_unary_operator = false +ij_coffeescript_spaces_within_array_initializer_braces = false +ij_coffeescript_spaces_within_array_initializer_brackets = false +ij_coffeescript_spaces_within_imports = false +ij_coffeescript_spaces_within_index_brackets = false +ij_coffeescript_spaces_within_interpolation_expressions = false +ij_coffeescript_spaces_within_method_call_parentheses = false +ij_coffeescript_spaces_within_method_parentheses = false +ij_coffeescript_spaces_within_object_braces = false +ij_coffeescript_spaces_within_object_literal_braces = false +ij_coffeescript_spaces_within_object_type_braces = true +ij_coffeescript_spaces_within_range_brackets = false +ij_coffeescript_spaces_within_type_assertion = false +ij_coffeescript_spaces_within_union_types = true +ij_coffeescript_union_types_wrap = on_every_item +ij_coffeescript_use_chained_calls_group_indents = false +ij_coffeescript_use_double_quotes = true +ij_coffeescript_use_explicit_js_extension = global +ij_coffeescript_use_path_mapping = always +ij_coffeescript_use_public_modifier = false +ij_coffeescript_use_semicolon_after_statement = false +ij_coffeescript_var_declaration_wrap = normal + +[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml,artisan}] +ij_continuation_indent_size = 4 +ij_php_align_assignments = false +ij_php_align_class_constants = false +ij_php_align_group_field_declarations = false +ij_php_align_inline_comments = false +ij_php_align_key_value_pairs = true +ij_php_align_match_arm_bodies = false +ij_php_align_multiline_array_initializer_expression = true +ij_php_align_multiline_binary_operation = false +ij_php_align_multiline_chained_methods = false +ij_php_align_multiline_extends_list = true +ij_php_align_multiline_for = true +ij_php_align_multiline_parameters = false +ij_php_align_multiline_parameters_in_calls = true +ij_php_align_multiline_ternary_operation = true +ij_php_align_named_arguments = false +ij_php_align_phpdoc_comments = false +ij_php_align_phpdoc_param_names = false +ij_php_anonymous_brace_style = end_of_line +ij_php_api_weight = 28 +ij_php_array_initializer_new_line_after_left_brace = true +ij_php_array_initializer_right_brace_on_new_line = true +ij_php_array_initializer_wrap = on_every_item +ij_php_assignment_wrap = off +ij_php_attributes_wrap = off +ij_php_author_weight = 28 +ij_php_binary_operation_sign_on_next_line = false +ij_php_binary_operation_wrap = off +ij_php_blank_lines_after_class_header = 0 +ij_php_blank_lines_after_function = 1 +ij_php_blank_lines_after_imports = 1 +ij_php_blank_lines_after_opening_tag = 1 +ij_php_blank_lines_after_package = 1 +ij_php_blank_lines_around_class = 1 +ij_php_blank_lines_around_constants = 0 +ij_php_blank_lines_around_field = 0 +ij_php_blank_lines_around_method = 1 +ij_php_blank_lines_before_class_end = 0 +ij_php_blank_lines_before_imports = 1 +ij_php_blank_lines_before_method_body = 0 +ij_php_blank_lines_before_package = 1 +ij_php_blank_lines_before_return_statement = 0 +ij_php_blank_lines_between_imports = 1 +ij_php_block_brace_style = end_of_line +ij_php_call_parameters_new_line_after_left_paren = true +ij_php_call_parameters_right_paren_on_new_line = true +ij_php_call_parameters_wrap = on_every_item +ij_php_catch_on_new_line = false +ij_php_category_weight = 28 +ij_php_class_brace_style = next_line +ij_php_comma_after_last_array_element = false +ij_php_concat_spaces = true +ij_php_copyright_weight = 28 +ij_php_deprecated_weight = 28 +ij_php_do_while_brace_force = always +ij_php_else_if_style = combine +ij_php_else_on_new_line = false +ij_php_example_weight = 28 +ij_php_extends_keyword_wrap = off +ij_php_extends_list_wrap = off +ij_php_fields_default_visibility = private +ij_php_filesource_weight = 28 +ij_php_finally_on_new_line = false +ij_php_for_brace_force = always +ij_php_for_statement_new_line_after_left_paren = true +ij_php_for_statement_right_paren_on_new_line = true +ij_php_for_statement_wrap = off +ij_php_force_short_declaration_array_style = false +ij_php_getters_setters_naming_style = camel_case +ij_php_getters_setters_order_style = getters_first +ij_php_global_weight = 28 +ij_php_group_use_wrap = on_every_item +ij_php_if_brace_force = always +ij_php_if_lparen_on_next_line = false +ij_php_if_rparen_on_next_line = false +ij_php_ignore_weight = 28 +ij_php_import_sorting = alphabetic +ij_php_indent_break_from_case = true +ij_php_indent_case_from_switch = true +ij_php_indent_code_in_php_tags = false +ij_php_internal_weight = 28 +ij_php_keep_blank_lines_after_lbrace = 0 +ij_php_keep_blank_lines_before_right_brace = 0 +ij_php_keep_blank_lines_in_code = 2 +ij_php_keep_blank_lines_in_declarations = 2 +ij_php_keep_control_statement_in_one_line = true +ij_php_keep_first_column_comment = true +ij_php_keep_indents_on_empty_lines = false +ij_php_keep_line_breaks = true +ij_php_keep_rparen_and_lbrace_on_one_line = true +ij_php_keep_simple_classes_in_one_line = false +ij_php_keep_simple_methods_in_one_line = false +ij_php_lambda_brace_style = end_of_line +ij_php_license_weight = 28 +ij_php_line_comment_add_space = false +ij_php_line_comment_at_first_column = true +ij_php_link_weight = 28 +ij_php_lower_case_boolean_const = true +ij_php_lower_case_keywords = true +ij_php_lower_case_null_const = true +ij_php_method_brace_style = next_line +ij_php_method_call_chain_wrap = off +ij_php_method_parameters_new_line_after_left_paren = true +ij_php_method_parameters_right_paren_on_new_line = true +ij_php_method_parameters_wrap = on_every_item +ij_php_method_weight = 28 +ij_php_modifier_list_wrap = false +ij_php_multiline_chained_calls_semicolon_on_new_line = false +ij_php_namespace_brace_style = 1 +ij_php_new_line_after_php_opening_tag = true +ij_php_null_type_position = in_the_end +ij_php_package_weight = 28 +ij_php_param_weight = 0 +ij_php_parameters_attributes_wrap = off +ij_php_parentheses_expression_new_line_after_left_paren = false +ij_php_parentheses_expression_right_paren_on_new_line = false +ij_php_phpdoc_blank_line_before_tags = false +ij_php_phpdoc_blank_lines_around_parameters = true +ij_php_phpdoc_keep_blank_lines = true +ij_php_phpdoc_param_spaces_between_name_and_description = 1 +ij_php_phpdoc_param_spaces_between_tag_and_type = 1 +ij_php_phpdoc_param_spaces_between_type_and_name = 1 +ij_php_phpdoc_use_fqcn = false +ij_php_phpdoc_wrap_long_lines = false +ij_php_place_assignment_sign_on_next_line = false +ij_php_place_parens_for_constructor = 0 +ij_php_property_read_weight = 28 +ij_php_property_weight = 28 +ij_php_property_write_weight = 28 +ij_php_return_type_on_new_line = false +ij_php_return_weight = 2 +ij_php_see_weight = 28 +ij_php_since_weight = 28 +ij_php_sort_phpdoc_elements = true +ij_php_space_after_colon = true +ij_php_space_after_colon_in_enum_backed_type = true +ij_php_space_after_colon_in_named_argument = true +ij_php_space_after_colon_in_return_type = true +ij_php_space_after_comma = true +ij_php_space_after_for_semicolon = true +ij_php_space_after_quest = true +ij_php_space_after_type_cast = false +ij_php_space_after_unary_not = false +ij_php_space_before_array_initializer_left_brace = false +ij_php_space_before_catch_keyword = true +ij_php_space_before_catch_left_brace = true +ij_php_space_before_catch_parentheses = true +ij_php_space_before_class_left_brace = true +ij_php_space_before_closure_left_parenthesis = true +ij_php_space_before_colon = true +ij_php_space_before_colon_in_enum_backed_type = false +ij_php_space_before_colon_in_named_argument = false +ij_php_space_before_colon_in_return_type = false +ij_php_space_before_comma = false +ij_php_space_before_do_left_brace = true +ij_php_space_before_else_keyword = true +ij_php_space_before_else_left_brace = true +ij_php_space_before_finally_keyword = true +ij_php_space_before_finally_left_brace = true +ij_php_space_before_for_left_brace = true +ij_php_space_before_for_parentheses = true +ij_php_space_before_for_semicolon = false +ij_php_space_before_if_left_brace = true +ij_php_space_before_if_parentheses = true +ij_php_space_before_method_call_parentheses = false +ij_php_space_before_method_left_brace = true +ij_php_space_before_method_parentheses = false +ij_php_space_before_quest = true +ij_php_space_before_short_closure_left_parenthesis = false +ij_php_space_before_switch_left_brace = true +ij_php_space_before_switch_parentheses = true +ij_php_space_before_try_left_brace = true +ij_php_space_before_unary_not = false +ij_php_space_before_while_keyword = true +ij_php_space_before_while_left_brace = true +ij_php_space_before_while_parentheses = true +ij_php_space_between_ternary_quest_and_colon = false +ij_php_spaces_around_additive_operators = true +ij_php_spaces_around_arrow = false +ij_php_spaces_around_assignment_in_declare = false +ij_php_spaces_around_assignment_operators = true +ij_php_spaces_around_bitwise_operators = true +ij_php_spaces_around_equality_operators = true +ij_php_spaces_around_logical_operators = true +ij_php_spaces_around_multiplicative_operators = true +ij_php_spaces_around_null_coalesce_operator = true +ij_php_spaces_around_pipe_in_union_type = false +ij_php_spaces_around_relational_operators = true +ij_php_spaces_around_shift_operators = true +ij_php_spaces_around_unary_operator = false +ij_php_spaces_around_var_within_brackets = false +ij_php_spaces_within_array_initializer_braces = false +ij_php_spaces_within_brackets = false +ij_php_spaces_within_catch_parentheses = false +ij_php_spaces_within_for_parentheses = false +ij_php_spaces_within_if_parentheses = false +ij_php_spaces_within_method_call_parentheses = false +ij_php_spaces_within_method_parentheses = false +ij_php_spaces_within_parentheses = false +ij_php_spaces_within_short_echo_tags = true +ij_php_spaces_within_switch_parentheses = false +ij_php_spaces_within_while_parentheses = false +ij_php_special_else_if_treatment = false +ij_php_subpackage_weight = 28 +ij_php_ternary_operation_signs_on_next_line = false +ij_php_ternary_operation_wrap = off +ij_php_throws_weight = 1 +ij_php_todo_weight = 28 +ij_php_unknown_tag_weight = 28 +ij_php_upper_case_boolean_const = false +ij_php_upper_case_null_const = false +ij_php_uses_weight = 28 +ij_php_var_weight = 28 +ij_php_variable_naming_style = mixed +ij_php_version_weight = 28 +ij_php_while_brace_force = always +ij_php_while_on_new_line = false + +[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,composer.lock,jest.config}] +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = true +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 + +[{*.neon,*.yaml,*.yml}] +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true \ No newline at end of file diff --git a/.github/workflows/code-analysis.yaml b/.github/workflows/code-analysis.yaml new file mode 100644 index 0000000..d561bd2 --- /dev/null +++ b/.github/workflows/code-analysis.yaml @@ -0,0 +1,36 @@ +name: Static Code Analysis + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run php tests + run: composer run-script analyse \ No newline at end of file diff --git a/.github/workflows/phpunit.yaml b/.github/workflows/phpunit.yaml new file mode 100644 index 0000000..252196b --- /dev/null +++ b/.github/workflows/phpunit.yaml @@ -0,0 +1,42 @@ +name: PHPUnit tests + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run php tests + run: composer run-script phpunit-clover + + - name: Upload coverage results to Coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + run: | + vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48aa30c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +vendor/ +coverage-report/ +.idea/ +.phpunit.result.cache +composer.lock diff --git a/README.md b/README.md index 0f8b01f..32566a1 100644 --- a/README.md +++ b/README.md @@ -1 +1,83 @@ # Request Signing + +[![Static Code Analysis](https://github.com/paynl/request-signing/actions/workflows/code-analysis.yaml/badge.svg)](https://github.com/paynl/request-signing/actions/workflows/code-analysis.yaml) +[![PHPUnit tests](https://github.com/paynl/request-signing/actions/workflows/phpunit.yaml/badge.svg)](https://github.com/paynl/request-signing/actions/workflows/phpunit.yaml) +[![Coverage Status](https://coveralls.io/repos/github/paynl/request-signing/badge.svg?branch=main)](https://coveralls.io/github/paynl/request-signing?branch=main) + +This package adds functionality to sign & verify requests sent by the Pay. platform. + +## Requirements + +To install this package you need: + +* PHP >= 7.4; +* composer. + +## Installation + +``` +composer require paynl/psr-server-request +``` + +## Usage + +The `PayNL\RequestSigning\RequestSigningService` simplifies both signing and verifying requests, avoiding the need for manual class instantiation. + +The constructor function of this service requires an array of _SingingMethods_ that you wish to support. Each signing method has different needs and functionality, and which one(s) you opt to support falls under your discretion. + +By providing the service with the array of these chosen methods, the service can handle the configuration and functionality. This decouples the setup process from your main application logic, allowing a more streamlined integration. + +### Signing +The below code snippet shows the basis of singing a request using this package: +```php +use PayNL\RequestSigning\RequestSigningService; +use PayNL\RequestSigning\Constant\SignatureMethod; +use PayNL\RequestSigning\Methods\HmacSignature; + +// Instantiate the RequestSigningService by providing it with your supported SigningMethods +$signingService = new RequestSigningService([ + new HmacSignature( + new SignatureKeyRepository() // Your implementation of the HmacSignatureKeyRepositoryInterface + ) +]); + +// Create your PSR Request, in this example we use the Nyholm PSR7 Request +$request = new \Nyholm\Psr7\Request('POST', 'https://pay.nl', [], '{"hello": "world"}') + +// Sign the request providing the id of the key you want to use, the algorithm and the signature method +// This will return the given request with the signature headers attached to it +$signedRequest = $signingService->sign($request, 'SL-1234-1234', 'sha512', SignatureMethod::HMAC); +``` + +### Verify +The below code snippet shows the basis of verifying a request signed using the above-mentioned method: +```php +use PayNL\RequestSigning\RequestSigningService; + +// Instantiate the RequestSigningService by providing it with your supported SigningMethods +$signingService = new RequestSigningService([ + new HmacSignature( + new SignatureKeyRepository() // Your implementation of the HmacSignatureKeyRepositoryInterface + ) +]); + +// Retrieve your request, in this example we'll use the paynl/psr-server-request package to create a PSR Server Request from the PHP Global Variables +$request = create_psr_server_request(); + +// Pass this request to the verify method. The request argument is optional, if not provided it will attempt to create a request using the above-mentioned method. +$requestValid = $signingService->verify($request); +``` + +### Exception handling + +The request signing service and the underlying signing / verifying methods may throw exceptions when unexpected values are encountered, these are: +- SignatureKeyNotFound, this exception must be thrown when the implementation of the `PayNL\RequestSigning\Repository\SignatureKeyRepositoryInterface` can not find the key based on the provided id; +- UnknownSigningMethodException, this exception will be thrown by the singing / verifying methods when they are requested to sign / verify a request with an algorithm they do not support; +- UnsupportedHashingAlgorithmException, this exception will be thrown by the `PayNL\RequestSigning\RequestSigningService` when it is requested to sign / verify a request with a method it doesn't support. + +### Supported Signing / Verifying methods +#### HMAC +The `PayNL\RequestSigning\Methods\HmacSignature` class enables the signing and verification of requests made with HMAC signatures. +To utilize this class's method, a single argument is required for its constructor. This argument is of type `PayNL\RequestSigning\Repository\HmacSignatureKeyRepositoryInterface`. + +_That is all you need to know to integrate this package, happy coding!_ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..92a1c78 --- /dev/null +++ b/composer.json @@ -0,0 +1,56 @@ +{ + "name": "paynl/request-signing", + "description": "A package to sign and verify request sent by PAY.", + "type": "library", + "license": "proprietary", + "minimum-stability": "stable", + "authors": [ + { + "name": "Kevin Jansen", + "email": "k.jansen@pay.nl", + "role": "Maintainer" + }, + { + "name": "Wesley de Kanter", + "email": "wesley@pay.nl", + "role": "Maintainer" + } + ], + "support" : { + "email" : "support@pay.nl" + }, + "require": { + "php": "^7.4 | ^8", + "psr/http-factory": "^1.0", + "paynl/psr-server-request": "^1.0" + }, + "require-dev": { + "psr/http-message": "^2.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.7", + "php-coveralls/php-coveralls": "^2.7", + "phpunit/phpcov": "^8.2" + }, + "autoload": { + "psr-4": { + "PayNL\\RequestSigning\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PayNL\\RequestSigning\\Tests\\": "tests/" + } + }, + "scripts": { + "phpcs": "vendor/bin/phpcs --standard=phpcs.xml", + "phpcbf" : "vendor/bin/phpcbf", + "phpstan": "vendor/bin/phpstan", + "phpunit" : "vendor/bin/phpunit", + "phpunit-clover": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover build/logs/clover.xml", + "analyse": [ + "@phpcs", + "@phpstan" + ] + } +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..31b902b --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + src + + + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..0ead39b --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - src + treatPhpDocTypesAsCertain: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..e801cc9 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + tests + + + + + + src + + + diff --git a/src/Constant/SignatureMethod.php b/src/Constant/SignatureMethod.php new file mode 100644 index 0000000..b038630 --- /dev/null +++ b/src/Constant/SignatureMethod.php @@ -0,0 +1,14 @@ +signatureKeyRepository = $signatureKeyRepository; + } + + /** + * Generate a signature for a given request using the specified key and algorithm. + * + * @param RequestInterface $request + * @param string $keyId The ID of the key used for signing the request. + * @param string $algorithm The hashing algorithm to be used for signing the request. + * + * @throws UnsupportedHashingAlgorithmException If the specified algorithm is not supported. + * @throws SignatureKeyNotFoundException if the key for the given key id cannot be found + * + * @return RequestInterface The generated signature data. + */ + public function sign(RequestInterface $request, string $keyId, string $algorithm): RequestInterface + { + $signatureData = $this->generateSignature((string) $request->getBody(), $keyId, $algorithm); + + return $request + ->withHeader(RequestSigningMethodInterface::SIGNATURE_HEADER, $signatureData->getSignature()) + ->withHeader(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER, $signatureData->getKeyId()) + ->withHeader(RequestSigningMethodInterface::SIGNATURE_METHOD_HEADER, $signatureData->getMethod()) + ->withHeader(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER, $signatureData->getAlgorithm()); + } + + /** + * Generate a signature using the given body, key ID, and algorithm. + * + * @param string $body The body to sign. + * @param string $keyId The ID of the key to use for signing. + * @param string $algorithm The hashing algorithm to use for signing. + * + * @throws UnsupportedHashingAlgorithmException if the provided algorithm is not supported. + * @throws SignatureKeyNotFoundException if the key for the given key id cannot be found + * @return SignatureData The generated signature data. + */ + private function generateSignature(string $body, string $keyId, string $algorithm): SignatureData + { + $key = $this->signatureKeyRepository->findOneById($keyId); + + $algorithm = strtolower($algorithm); + + if (in_array($algorithm, hash_algos()) === false) { + throw UnsupportedHashingAlgorithmException::forAlgorithm($algorithm); + } + + $generatedSignature = hash($algorithm, implode( + ':', + [ + $key->getId(), + $key->getSecret(), + $body, + ] + )); + + return new SignatureData( + $generatedSignature, + $keyId, + $algorithm, + self::METHOD_NAME + ); + } + + /** + * Verifies the signature of a request. + * + * @param RequestInterface $request The request to verify. + * + * @return bool Returns true if the signature is valid, false otherwise. + */ + public function verify(RequestInterface $request): bool + { + try { + $keyId = $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER); + $signature = $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_HEADER); + $algorithm = $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER); + + $generatedSignature = $this + ->generateSignature((string) $request->getBody(), $keyId, $algorithm) + ->getSignature(); + + return $signature === $generatedSignature; + } catch (Throwable $throwable) { + // We do nothing with the exception. The request stays marked as "invalid". + } + + return false; + } + + public function supports(string $method): bool + { + return strtolower(self::METHOD_NAME) === strtolower($method); + } +} diff --git a/src/Methods/RequestSigningMethodInterface.php b/src/Methods/RequestSigningMethodInterface.php new file mode 100644 index 0000000..cc8e3e9 --- /dev/null +++ b/src/Methods/RequestSigningMethodInterface.php @@ -0,0 +1,48 @@ +requestSigningMethods = $requestSigningMethods; + } + + /** + * Signs a request using the given key id, algorithm, and method. + * + * @param RequestInterface $request The request to be signed. + * @param string $keyId The ID of the key to be used for signing. + * @param string $algorithm The algorithm to be used for signing. + * @param string $method The method to be used for signing (e.g., HMAC, RSA, etc.). + * + * @throws UnknownSigningMethodException if the given signing method is not known + * @throws UnsupportedHashingAlgorithmException If the specified algorithm is not supported. + * @throws SignatureKeyNotFoundException if the key for the given key id cannot be found + * + * @return RequestInterface The signed request. + */ + public function sign(RequestInterface $request, string $keyId, string $algorithm, string $method): RequestInterface + { + return $this->getSignatureMethod($method)->sign($request, $keyId, $algorithm); + } + + /** + * Verifies the authenticity of the given request. + * + * @param RequestInterface|null $request The request to be verified. + * If not provided, a new request will be created from globals. + * + * @throws UnknownSigningMethodException if the given signing method is not known + * + * @return bool True if the request is valid, false otherwise. + */ + public function verify(?RequestInterface $request = null): bool + { + if ($request instanceof RequestInterface === false) { + $request = create_psr_server_request(); + } + + return $this->getSignatureMethodFromRequest($request)->verify($request); + } + + /** + * Retrieves the signature method from the request. + * + * @param RequestInterface $request The request object. + * + * @return RequestSigningMethodInterface The signature method object. + */ + private function getSignatureMethodFromRequest(RequestInterface $request): RequestSigningMethodInterface + { + return $this->getSignatureMethod( + $request->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_METHOD_HEADER) + ); + } + + /** + * Returns the RequestSigningMethodInterface based on the given method name. + * + * @param string $method The method name. + * + * @throws UnknownSigningMethodException When an unknown method is provided. + * @return RequestSigningMethodInterface The RequestSigningMethodInterface object. + */ + private function getSignatureMethod(string $method): RequestSigningMethodInterface + { + foreach ($this->requestSigningMethods as $requestSigningMethod) { + if ( + $requestSigningMethod instanceof RequestSigningMethodInterface && + $requestSigningMethod->supports($method) + ) { + return $requestSigningMethod; + } + } + + throw UnknownSigningMethodException::forUnknownMethod($method); + } +} diff --git a/src/ValueObject/SignatureData.php b/src/ValueObject/SignatureData.php new file mode 100644 index 0000000..9eb0c57 --- /dev/null +++ b/src/ValueObject/SignatureData.php @@ -0,0 +1,50 @@ +signature = $signature; + $this->keyId = $keyId; + $this->algorithm = $algorithm; + $this->method = $method; + } + + public function getSignature(): string + { + return $this->signature; + } + + public function getKeyId(): string + { + return $this->keyId; + } + + public function getAlgorithm(): string + { + return $this->algorithm; + } + + public function getMethod(): string + { + return $this->method; + } +} diff --git a/src/ValueObject/SignatureKey.php b/src/ValueObject/SignatureKey.php new file mode 100644 index 0000000..9867f5c --- /dev/null +++ b/src/ValueObject/SignatureKey.php @@ -0,0 +1,28 @@ +id = $id; + $this->secret = $secret; + } + + public function getId(): string + { + return $this->id; + } + + public function getSecret(): string + { + return $this->secret; + } +} diff --git a/tests/Unit/Methods/HmacSignatureTest.php b/tests/Unit/Methods/HmacSignatureTest.php new file mode 100644 index 0000000..95cd578 --- /dev/null +++ b/tests/Unit/Methods/HmacSignatureTest.php @@ -0,0 +1,140 @@ +getKeyRepository($this->getDummySignatureKey())); + + // Next, we'll sign a dummy request + $signedRequest = $signingMethod->sign($this->getDummyRequest(), self::SIGNATURE_KEY_ID, self::SIGNATURE_ALGORITHM); + + $this->assertInstanceOf(RequestInterface::class, $signedRequest); + $this->assertEquals(self::SIGNATURE_KEY_ID, $signedRequest->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER)); + $this->assertEquals(self::SIGNATURE_ALGORITHM, $signedRequest->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER)); + $this->assertEquals(SignatureMethod::HMAC, $signedRequest->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_METHOD_HEADER)); + $this->assertNotEmpty($signedRequest->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_HEADER)); + } + + /** + * @throws SignatureKeyNotFoundException + */ + public function testItThrowsAnExceptionWithUnsupportedHashingAlgorithm(): void + { + $this->expectException(UnsupportedHashingAlgorithmException::class); + + (new HmacSignature($this->getKeyRepository($this->getDummySignatureKey())))->sign($this->getDummyRequest(), self::SIGNATURE_KEY_ID, 'Unknown Algorithm'); + } + + public function testItCanVerifyAGivenRequest(): void + { + // First, we'll create a dummy request + $request = $this->getSignedDummyRequest(); + + // Next, we'll instantiate the HmacSignature class + $hmacSignature = new HmacSignature($this->getKeyRepository($this->getDummySignatureKey())); + + // Next, we'll verify that this request passes its verification + $this->assertTrue($hmacSignature->verify($request)); + } + + public function testItWillFailForAnInvalidSignature(): void + { + // First, we'll create a dummy request + $request = $this->getDummyRequest() + ->withHeader(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER, self::SIGNATURE_ALGORITHM) + ->withHeader(RequestSigningMethodInterface::SIGNATURE_METHOD_HEADER, SignatureMethod::HMAC) + ->withHeader(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER, self::SIGNATURE_KEY_ID) + ->withHeader( + RequestSigningMethodInterface::SIGNATURE_HEADER, + 'Not a signature' + ); + + // Next, we'll instantiate the HmacSignature class + $hmacSignature = new HmacSignature($this->getKeyRepository($this->getDummySignatureKey())); + + // Next, we'll verify that this request passes its verification + $this->assertFalse($hmacSignature->verify($request)); + } + + public function testItReturnsFalseForAnUnknownKey(): void + { + // Next, we'll mock a repository that throws an "SignatureKeyNotFound" exception + $repository = $this->createMock(HmacSignatureKeyRepositoryInterface::class); + $repository->method('findOneById')->willThrowException(SignatureKeyNotFoundException::forKeyId(self::SIGNATURE_KEY_ID)); + + // Next, we'll instantiate the HmacSignature class + $hmacSignature = new HmacSignature($repository); + + // Next, we'll call the verify method, this should return false as it couldn't find the key + $this->assertFalse($hmacSignature->verify($this->getSignedDummyRequest())); + } + + public function testItReturnsFalseForAnHashingAlgorithm(): void + { + // First, we'll mock a request that has an unkown algorithm in its header + $request = $this->getSignedDummyRequest() + ->withHeader(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER, 'Unknown algorithm'); + + // Next, we'll instantiate the HmacSignature class + $hmacSignature = new HmacSignature($this->getKeyRepository($this->getDummySignatureKey())); + + // Next, we'll call the verify method, this should return false as it couldn't find the key + $this->assertFalse($hmacSignature->verify($request)); + } + + private function getDummyRequest(): RequestInterface + { + return new Request('POST', 'https://pay.nl'); + } + + private function getSignedDummyRequest(): RequestInterface + { + return $this->getDummyRequest() + ->withHeader(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER, self::SIGNATURE_ALGORITHM) + ->withHeader(RequestSigningMethodInterface::SIGNATURE_METHOD_HEADER, SignatureMethod::HMAC) + ->withHeader(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER, self::SIGNATURE_KEY_ID) + ->withHeader( + RequestSigningMethodInterface::SIGNATURE_HEADER, + '6086a395c7fe9b47f47cee4623b76cc4cc79cb3f44e968091edc447cfe7e7533dc2564f49b22bc1f225c24f172be0d2a5b5893e0825c6fde782a63f3e43ce7d1' + ); + } + + private function getDummySignatureKey(): SignatureKey + { + return new SignatureKey(self::SIGNATURE_KEY_ID, self::KEY_SECRET); + } + + private function getKeyRepository(?SignatureKey $signatureKey = null): HmacSignatureKeyRepositoryInterface + { + $repository = $this->createMock(HmacSignatureKeyRepositoryInterface::class); + $repository->method('findOneById')->willReturn($signatureKey); + + return $repository; + } +} diff --git a/tests/Unit/RequestSigningServiceTest.php b/tests/Unit/RequestSigningServiceTest.php new file mode 100644 index 0000000..ce0f9c1 --- /dev/null +++ b/tests/Unit/RequestSigningServiceTest.php @@ -0,0 +1,98 @@ +createMock(HmacSignatureKeyRepositoryInterface::class); + $repository->method('findOneById')->willReturn($signatureKey); + + $signedRequest = (new RequestSigningService([new HmacSignature($repository)]))->sign($request, $signatureKey->getId(), $algorithm, $method); + + $this->assertInstanceOf(RequestInterface::class, $signedRequest); + $this->assertEquals($signatureKey->getId(), $signedRequest->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER)); + $this->assertEquals($algorithm, $signedRequest->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER)); + $this->assertEquals($method, $signedRequest->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_METHOD_HEADER)); + $this->assertNotEmpty($signedRequest->getHeaderLine(RequestSigningMethodInterface::SIGNATURE_HEADER)); + } + + /** @dataProvider requestVerificationDataProvider */ + public function testItCanVerifyARequest(Request $request, SignatureKey $signatureKey): void + { + $repository = $this->createMock(HmacSignatureKeyRepositoryInterface::class); + $repository->method('findOneById')->willReturn($signatureKey); + + $this->assertTrue((new RequestSigningService([new HmacSignature($repository)]))->verify($request)); + } + + public function testItThrowsAnExceptionOnAnUnknownSigningMethod(): void + { + $this->expectException(UnknownSigningMethodException::class); + + $repository = $this->createMock(HmacSignatureKeyRepositoryInterface::class); + $repository->method('findOneById')->willReturn(self::getDummySignatureKey()); + + (new RequestSigningService([new HmacSignature($repository)]))->verify(); + } + + private static function getDummyRequest(): RequestInterface + { + return new Request('POST', 'https://pay.nl'); + } + + private static function getHmacSignedDummyRequest(): RequestInterface + { + return self::getDummyRequest() + ->withHeader(RequestSigningMethodInterface::SIGNATURE_ALGORITHM_HEADER, self::SIGNATURE_ALGORITHM) + ->withHeader(RequestSigningMethodInterface::SIGNATURE_METHOD_HEADER, SignatureMethod::HMAC) + ->withHeader(RequestSigningMethodInterface::SIGNATURE_KEY_ID_HEADER, self::SIGNATURE_KEY_ID) + ->withHeader( + RequestSigningMethodInterface::SIGNATURE_HEADER, + '6086a395c7fe9b47f47cee4623b76cc4cc79cb3f44e968091edc447cfe7e7533dc2564f49b22bc1f225c24f172be0d2a5b5893e0825c6fde782a63f3e43ce7d1' + ); + } + + private static function getDummySignatureKey(): SignatureKey + { + return new SignatureKey(self::SIGNATURE_KEY_ID, self::KEY_SECRET); + } + + public static function requestSigningDataProvider(): Generator + { + yield 'HMAC' => [ + 'request' => self::getDummyRequest(), + 'signatureKey' => self::getDummySignatureKey(), + 'algorithm' => self::SIGNATURE_ALGORITHM, + 'method' => SignatureMethod::HMAC, + ]; + } + + public static function requestVerificationDataProvider(): Generator + { + yield 'HMAC' => [ + 'request' => self::getHmacSignedDummyRequest(), + 'signatureKey' => self::getDummySignatureKey(), + ]; + } +}