diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..c95ac984a1d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,1815 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = 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_block_comment_add_space = false +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 + +[*.csv] +indent_style = tab +ij_csv_wrap_long_lines = false + +[*.java] +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_deconstruction_list_components = true +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_align_types_in_multi_catch = true +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_add_space = false +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 20 +ij_java_class_names_in_javadoc = 1 +ij_java_deconstruction_list_wrap = normal +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_entity_dd_prefix = +ij_java_entity_dd_suffix = EJB +ij_java_entity_eb_prefix = +ij_java_entity_eb_suffix = Bean +ij_java_entity_hi_prefix = +ij_java_entity_hi_suffix = Home +ij_java_entity_lhi_prefix = Local +ij_java_entity_lhi_suffix = Home +ij_java_entity_li_prefix = Local +ij_java_entity_li_suffix = +ij_java_entity_pk_class = java.lang.String +ij_java_entity_ri_prefix = +ij_java_entity_ri_suffix = +ij_java_entity_vo_prefix = +ij_java_entity_vo_suffix = VO +ij_java_enum_constants_wrap = off +ij_java_enum_field_annotation_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_field_name_prefix = +ij_java_field_name_suffix = +ij_java_filter_class_prefix = +ij_java_filter_class_suffix = +ij_java_filter_dd_prefix = +ij_java_filter_dd_suffix = +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = never +ij_java_imports_layout = *,|,javax.**,java.**,|,$* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_listener_class_prefix = +ij_java_listener_class_suffix = +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +ij_java_message_dd_prefix = +ij_java_message_dd_suffix = EJB +ij_java_message_eb_prefix = +ij_java_message_eb_suffix = Bean +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = off +ij_java_modifier_list_wrap = false +ij_java_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 20 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_when_body_is_presented = false +ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +ij_java_parameter_annotation_wrap = off +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_annotation = false +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +ij_java_rparen_on_new_line_in_record_header = false +ij_java_servlet_class_prefix = +ij_java_servlet_class_suffix = +ij_java_servlet_dd_prefix = +ij_java_servlet_dd_suffix = +ij_java_session_dd_prefix = +ij_java_session_dd_suffix = EJB +ij_java_session_eb_prefix = +ij_java_session_eb_suffix = Bean +ij_java_session_hi_prefix = +ij_java_session_hi_suffix = Home +ij_java_session_lhi_prefix = Local +ij_java_session_lhi_suffix = Home +ij_java_session_li_prefix = Local +ij_java_session_li_suffix = +ij_java_session_ri_prefix = +ij_java_session_ri_suffix = +ij_java_session_si_prefix = +ij_java_session_si_suffix = Service +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_deconstruction_list = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_annotation_eq = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_deconstruction_list = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_static_field_name_prefix = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = +ij_java_subclass_name_suffix = Impl +ij_java_switch_expressions_wrap = normal +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_prefix = +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false +ij_java_wrap_semicolon_after_call_chain = false + +[*.less] +indent_size = 2 +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_block_comment_add_space = false +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_line_comment_add_space = false +ij_less_line_comment_at_first_column = 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 + +[*.prisma] +indent_size = 2 +tab_width = 2 +ij_prisma_line_comment_add_space = true +ij_prisma_line_comment_add_space_on_reformat = true +ij_prisma_line_comment_at_first_column = false +ij_prisma_run_prisma_fmt_on_reformat = true + +[*.proto] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_protobuf_keep_blank_lines_in_code = 2 +ij_protobuf_keep_indents_on_empty_lines = false +ij_protobuf_keep_line_breaks = true +ij_protobuf_space_after_comma = true +ij_protobuf_space_before_comma = false +ij_protobuf_spaces_around_assignment_operators = true +ij_protobuf_spaces_within_braces = false +ij_protobuf_spaces_within_brackets = false + +[*.rs] +max_line_length = 100 +ij_continuation_indent_size = 4 +ij_rust_align_multiline_chained_methods = false +ij_rust_align_multiline_parameters = true +ij_rust_align_multiline_parameters_in_calls = true +ij_rust_align_ret_type = true +ij_rust_align_type_params = false +ij_rust_align_where_bounds = true +ij_rust_align_where_clause = false +ij_rust_allow_one_line_match = false +ij_rust_block_comment_at_first_column = false +ij_rust_indent_where_clause = true +ij_rust_keep_blank_lines_in_code = 2 +ij_rust_keep_blank_lines_in_declarations = 2 +ij_rust_keep_indents_on_empty_lines = false +ij_rust_keep_line_breaks = true +ij_rust_line_comment_add_space = true +ij_rust_line_comment_at_first_column = false +ij_rust_min_number_of_blanks_between_items = 1 +ij_rust_preserve_punctuation = false +ij_rust_spaces_around_assoc_type_binding = false + +[*.sass] +indent_size = 2 +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_line_comment_add_space = false +ij_sass_line_comment_at_first_column = 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 + +[*.scala] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_scala_align_composite_pattern = true +ij_scala_align_extends_with = 0 +ij_scala_align_group_field_declarations = false +ij_scala_align_if_else = false +ij_scala_align_in_columns_case_branch = false +ij_scala_align_multiline_binary_operation = false +ij_scala_align_multiline_chained_methods = false +ij_scala_align_multiline_for = true +ij_scala_align_multiline_parameters = true +ij_scala_align_multiline_parameters_in_calls = false +ij_scala_align_multiline_parenthesized_expression = false +ij_scala_align_parameter_types_in_multiline_declarations = 0 +ij_scala_align_tuple_elements = false +ij_scala_alternate_continuation_indent_for_params = 4 +ij_scala_binary_operation_wrap = off +ij_scala_blank_lines_after_anonymous_class_header = 0 +ij_scala_blank_lines_after_class_header = 0 +ij_scala_blank_lines_after_imports = 1 +ij_scala_blank_lines_after_package = 1 +ij_scala_blank_lines_around_class = 1 +ij_scala_blank_lines_around_class_in_inner_scopes = 0 +ij_scala_blank_lines_around_field = 0 +ij_scala_blank_lines_around_field_in_inner_scopes = 0 +ij_scala_blank_lines_around_field_in_interface = 0 +ij_scala_blank_lines_around_method = 1 +ij_scala_blank_lines_around_method_in_inner_scopes = 1 +ij_scala_blank_lines_around_method_in_interface = 1 +ij_scala_blank_lines_before_class_end = 0 +ij_scala_blank_lines_before_imports = 1 +ij_scala_blank_lines_before_method_body = 0 +ij_scala_blank_lines_before_package = 0 +ij_scala_block_brace_style = end_of_line +ij_scala_block_comment_add_space = false +ij_scala_block_comment_at_first_column = true +ij_scala_call_parameters_new_line_after_lparen = 0 +ij_scala_call_parameters_right_paren_on_new_line = false +ij_scala_call_parameters_wrap = off +ij_scala_case_clause_brace_force = never +ij_scala_catch_on_new_line = false +ij_scala_class_annotation_wrap = split_into_lines +ij_scala_class_brace_style = end_of_line +ij_scala_closure_brace_force = never +ij_scala_do_not_align_block_expr_params = true +ij_scala_do_not_indent_case_clause_body = false +ij_scala_do_not_indent_tuples_close_brace = true +ij_scala_do_while_brace_force = never +ij_scala_else_on_new_line = false +ij_scala_enable_scaladoc_formatting = true +ij_scala_enforce_functional_syntax_for_unit = true +ij_scala_extends_keyword_wrap = off +ij_scala_extends_list_wrap = off +ij_scala_field_annotation_wrap = split_into_lines +ij_scala_finally_brace_force = never +ij_scala_finally_on_new_line = false +ij_scala_for_brace_force = never +ij_scala_for_statement_wrap = off +ij_scala_formatter = 0 +ij_scala_if_brace_force = never +ij_scala_implicit_value_class_prefix = +ij_scala_implicit_value_class_suffix = Ops +ij_scala_indent_braced_function_args = true +ij_scala_indent_case_from_switch = true +ij_scala_indent_fewer_braces_in_method_call_chains = false +ij_scala_indent_first_parameter = true +ij_scala_indent_first_parameter_clause = false +ij_scala_indent_type_arguments = true +ij_scala_indent_type_parameters = true +ij_scala_indent_yield_after_one_line_enumerators = true +ij_scala_keep_blank_lines_before_right_brace = 2 +ij_scala_keep_blank_lines_in_code = 2 +ij_scala_keep_blank_lines_in_declarations = 2 +ij_scala_keep_comments_on_same_line = true +ij_scala_keep_first_column_comment = false +ij_scala_keep_indents_on_empty_lines = false +ij_scala_keep_line_breaks = true +ij_scala_keep_one_line_lambdas_in_arg_list = false +ij_scala_keep_simple_blocks_in_one_line = false +ij_scala_keep_simple_methods_in_one_line = false +ij_scala_keep_xml_formatting = false +ij_scala_line_comment_add_space = false +ij_scala_line_comment_at_first_column = true +ij_scala_method_annotation_wrap = split_into_lines +ij_scala_method_brace_force = never +ij_scala_method_brace_style = end_of_line +ij_scala_method_call_chain_wrap = off +ij_scala_method_parameters_new_line_after_left_paren = false +ij_scala_method_parameters_right_paren_on_new_line = false +ij_scala_method_parameters_wrap = off +ij_scala_modifier_list_wrap = false +ij_scala_multiline_string_align_dangling_closing_quotes = false +ij_scala_multiline_string_closing_quotes_on_new_line = false +ij_scala_multiline_string_insert_margin_on_enter = true +ij_scala_multiline_string_margin_char = | +ij_scala_multiline_string_margin_indent = 2 +ij_scala_multiline_string_opening_quotes_on_new_line = true +ij_scala_multiline_string_process_margin_on_copy_paste = true +ij_scala_new_line_after_case_clause_arrow_when_multiline_body = false +ij_scala_newline_after_annotations = false +ij_scala_not_continuation_indent_for_params = false +ij_scala_parameter_annotation_wrap = off +ij_scala_parentheses_expression_new_line_after_left_paren = false +ij_scala_parentheses_expression_right_paren_on_new_line = false +ij_scala_place_closure_parameters_on_new_line = false +ij_scala_place_self_type_on_new_line = true +ij_scala_prefer_parameters_wrap = false +ij_scala_preserve_space_after_method_declaration_name = false +ij_scala_reformat_on_compile = false +ij_scala_replace_case_arrow_with_unicode_char = false +ij_scala_replace_for_generator_arrow_with_unicode_char = false +ij_scala_replace_lambda_with_greek_letter = false +ij_scala_replace_map_arrow_with_unicode_char = false +ij_scala_scalafmt_config_path = +ij_scala_scalafmt_fallback_to_default_settings = false +ij_scala_scalafmt_reformat_on_files_save = false +ij_scala_scalafmt_show_invalid_code_warnings = true +ij_scala_scalafmt_use_intellij_formatter_for_range_format = true +ij_scala_sd_align_exception_comments = true +ij_scala_sd_align_list_item_content = true +ij_scala_sd_align_other_tags_comments = true +ij_scala_sd_align_parameters_comments = true +ij_scala_sd_align_return_comments = true +ij_scala_sd_blank_line_after_parameters_comments = false +ij_scala_sd_blank_line_after_return_comments = false +ij_scala_sd_blank_line_before_parameters = false +ij_scala_sd_blank_line_before_tags = true +ij_scala_sd_blank_line_between_parameters = false +ij_scala_sd_keep_blank_lines_between_tags = false +ij_scala_sd_preserve_spaces_in_tags = false +ij_scala_space_after_comma = true +ij_scala_space_after_for_semicolon = true +ij_scala_space_after_modifiers_constructor = false +ij_scala_space_after_type_colon = true +ij_scala_space_before_brace_method_call = true +ij_scala_space_before_class_left_brace = true +ij_scala_space_before_for_parentheses = true +ij_scala_space_before_if_parentheses = true +ij_scala_space_before_infix_like_method_parentheses = false +ij_scala_space_before_infix_method_call_parentheses = false +ij_scala_space_before_infix_operator_like_method_call_parentheses = true +ij_scala_space_before_method_call_parentheses = false +ij_scala_space_before_method_left_brace = true +ij_scala_space_before_method_parentheses = false +ij_scala_space_before_type_colon = false +ij_scala_space_before_type_parameter_in_def_list = false +ij_scala_space_before_type_parameter_leading_context_bound_colon = false +ij_scala_space_before_type_parameter_leading_context_bound_colon_hk = true +ij_scala_space_before_type_parameter_list = false +ij_scala_space_before_type_parameter_rest_context_bound_colons = true +ij_scala_space_before_while_parentheses = true +ij_scala_space_inside_closure_braces = true +ij_scala_space_inside_self_type_braces = true +ij_scala_space_within_empty_method_call_parentheses = false +ij_scala_spaces_around_at_in_patterns = false +ij_scala_spaces_in_imports = false +ij_scala_spaces_in_one_line_blocks = false +ij_scala_spaces_within_brackets = false +ij_scala_spaces_within_for_parentheses = false +ij_scala_spaces_within_if_parentheses = false +ij_scala_spaces_within_method_call_parentheses = false +ij_scala_spaces_within_method_parentheses = false +ij_scala_spaces_within_parentheses = false +ij_scala_spaces_within_while_parentheses = false +ij_scala_special_else_if_treatment = true +ij_scala_trailing_comma_arg_list_enabled = true +ij_scala_trailing_comma_import_selector_enabled = false +ij_scala_trailing_comma_mode = trailing_comma_keep +ij_scala_trailing_comma_params_enabled = true +ij_scala_trailing_comma_pattern_arg_list_enabled = false +ij_scala_trailing_comma_tuple_enabled = false +ij_scala_trailing_comma_tuple_type_enabled = false +ij_scala_trailing_comma_type_params_enabled = false +ij_scala_try_brace_force = never +ij_scala_type_annotation_exclude_constant = true +ij_scala_type_annotation_exclude_in_dialect_sources = true +ij_scala_type_annotation_exclude_in_test_sources = false +ij_scala_type_annotation_exclude_member_of_anonymous_class = false +ij_scala_type_annotation_exclude_member_of_private_class = false +ij_scala_type_annotation_exclude_when_type_is_stable = true +ij_scala_type_annotation_function_parameter = false +ij_scala_type_annotation_implicit_modifier = true +ij_scala_type_annotation_local_definition = false +ij_scala_type_annotation_private_member = false +ij_scala_type_annotation_protected_member = true +ij_scala_type_annotation_public_member = true +ij_scala_type_annotation_structural_type = true +ij_scala_type_annotation_underscore_parameter = false +ij_scala_type_annotation_unit_type = true +ij_scala_use_alternate_continuation_indent_for_params = false +ij_scala_use_scala3_indentation_based_syntax = true +ij_scala_use_scaladoc2_formatting = false +ij_scala_variable_annotation_wrap = off +ij_scala_while_brace_force = never +ij_scala_while_on_new_line = false +ij_scala_wrap_before_with_keyword = false +ij_scala_wrap_first_method_in_call_chain = false +ij_scala_wrap_long_lines = false + +[*.scss] +indent_size = 2 +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_block_comment_add_space = false +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_line_comment_add_space = false +ij_scss_line_comment_at_first_column = 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 + +[*.vue] +indent_size = 2 +tab_width = 2 +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 + +[{*.ad,*.adoc,*.asciidoc,.asciidoctorconfig}] +ij_asciidoc_blank_lines_after_header = 1 +ij_asciidoc_blank_lines_keep_after_header = 1 +ij_asciidoc_formatting_enabled = true +ij_asciidoc_one_sentence_per_line = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.wsdd,*.wsdl,*.xjb,*.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_add_space = false +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,*.cts,*.mts,*.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_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +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_enum_constants_wrap = on_every_item +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_object_types_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_property_prefix = +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 = auto +ij_typescript_use_import_type = auto +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,*.bats,*.dash,*.ksh,*.mksh,*.sh,.bash_aliases,.bash_logout,.bash_profile,.bashrc,.profile}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_function_brace_newline = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_simplify_code = false +ij_shell_switch_cases_indented = false +ij_shell_unix_line_feeds = true +ij_shell_use_google_code_style = 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_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +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_object_types_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_property_prefix = +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 = auto +ij_javascript_use_import_type = auto +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 + +[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}] +ij_continuation_indent_size = 4 +ij_php_align_assignments = false +ij_php_align_class_constants = false +ij_php_align_enum_cases = false +ij_php_align_group_field_declarations = false +ij_php_align_inline_comments = false +ij_php_align_key_value_pairs = false +ij_php_align_match_arm_bodies = false +ij_php_align_multiline_array_initializer_expression = false +ij_php_align_multiline_binary_operation = false +ij_php_align_multiline_chained_methods = false +ij_php_align_multiline_extends_list = false +ij_php_align_multiline_for = true +ij_php_align_multiline_parameters = true +ij_php_align_multiline_parameters_in_calls = false +ij_php_align_multiline_ternary_operation = false +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 = false +ij_php_array_initializer_right_brace_on_new_line = false +ij_php_array_initializer_wrap = off +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 = 0 +ij_php_blank_lines_after_package = 0 +ij_php_blank_lines_around_class = 1 +ij_php_blank_lines_around_constants = 0 +ij_php_blank_lines_around_enum_cases = 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 = 0 +ij_php_block_brace_style = end_of_line +ij_php_call_parameters_new_line_after_left_paren = false +ij_php_call_parameters_right_paren_on_new_line = false +ij_php_call_parameters_wrap = off +ij_php_catch_on_new_line = false +ij_php_category_weight = 28 +ij_php_class_brace_style = next_line +ij_php_comma_after_last_argument = false +ij_php_comma_after_last_array_element = false +ij_php_comma_after_last_closure_use_var = false +ij_php_comma_after_last_match_arm = false +ij_php_comma_after_last_parameter = false +ij_php_concat_spaces = true +ij_php_copyright_weight = 28 +ij_php_deprecated_weight = 28 +ij_php_do_while_brace_force = never +ij_php_else_if_style = as_is +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 = never +ij_php_for_statement_new_line_after_left_paren = false +ij_php_for_statement_right_paren_on_new_line = false +ij_php_for_statement_wrap = off +ij_php_force_empty_methods_in_one_line = false +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 = never +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 = 2 +ij_php_keep_blank_lines_before_right_brace = 2 +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 = false +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 = false +ij_php_lower_case_keywords = true +ij_php_lower_case_null_const = false +ij_php_method_brace_style = next_line +ij_php_method_call_chain_wrap = off +ij_php_method_parameters_new_line_after_left_paren = false +ij_php_method_parameters_right_paren_on_new_line = false +ij_php_method_parameters_wrap = off +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 = false +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 = false +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 = 1 +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 = 2 +ij_php_todo_weight = 28 +ij_php_treat_multiline_arrays_and_lambdas_multiline = false +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 = never +ij_php_while_on_new_line = false + +[{*.erb,*.rhtml}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_rhtml_keep_indents_on_empty_lines = false + +[{*.ft,*.vm,*.vsl}] +ij_vtl_keep_indents_on_empty_lines = false + +[{*.gant,*.groovy,*.gy}] +ij_groovy_align_group_field_declarations = false +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = off +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_add_space = false +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 5 +ij_groovy_do_while_brace_force = never +ij_groovy_else_on_new_line = false +ij_groovy_enable_groovydoc_formatting = true +ij_groovy_enum_constants_wrap = off +ij_groovy_extends_keyword_wrap = off +ij_groovy_extends_list_wrap = off +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = never +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_ginq_general_clause_wrap_policy = 2 +ij_groovy_ginq_having_wrap_policy = 1 +ij_groovy_ginq_indent_having_clause = true +ij_groovy_ginq_indent_on_clause = true +ij_groovy_ginq_on_wrap_policy = 1 +ij_groovy_ginq_space_after_keyword = true +ij_groovy_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_add_space_on_reformat = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = false +ij_groovy_method_parameters_right_paren_on_new_line = false +ij_groovy_method_parameters_wrap = off +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = false +ij_groovy_resource_list_right_paren_on_new_line = false +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_record_parentheses = false +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = off +ij_groovy_throws_list_wrap = off +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = never +ij_groovy_while_on_new_line = false +ij_groovy_wrap_chain_calls_after_dot = false +ij_groovy_wrap_long_lines = false + +[{*.gemspec,*.jbuilder,*.rake,*.rb,*.rbi,*.rbw,*.ru,*.thor,.simplecov,capfile,gemfile,guardfile,isolate,rakefile,steepfile,vagrantfile}] +ij_ruby_align_group_field_declarations = false +ij_ruby_align_multiline_parameters = true +ij_ruby_blank_lines_around_class = 1 +ij_ruby_blank_lines_around_method = 1 +ij_ruby_chain_calls_alignment = 2 +ij_ruby_convert_brace_block_by_enter = true +ij_ruby_empty_declarations_style = 1 +ij_ruby_force_newlines_around_visibility_mods = true +ij_ruby_indent_private_methods = false +ij_ruby_indent_protected_methods = false +ij_ruby_indent_public_methods = false +ij_ruby_indent_visibility_modifiers = true +ij_ruby_indent_when_cases = false +ij_ruby_keep_blank_lines_in_code = 1 +ij_ruby_keep_blank_lines_in_declarations = 1 +ij_ruby_keep_line_breaks = true +ij_ruby_parentheses_around_method_arguments = true +ij_ruby_spaces_around_assignment_operators = true +ij_ruby_spaces_around_hashrocket = true +ij_ruby_spaces_around_other_operators = true +ij_ruby_spaces_around_pow_operators = true +ij_ruby_spaces_around_range_operators = false +ij_ruby_spaces_around_relational_operators = true +ij_ruby_spaces_within_array_initializer_braces = true +ij_ruby_spaces_within_braces = true +ij_ruby_spaces_within_pipes = false +ij_ruby_use_external_formatter = false + +[{*.go,*.go2}] +indent_style = tab +ij_continuation_indent_size = 4 +ij_smart_tabs = true +ij_go_GROUP_CURRENT_PROJECT_IMPORTS = false +ij_go_add_leading_space_to_comments = false +ij_go_add_parentheses_for_single_import = false +ij_go_call_parameters_new_line_after_left_paren = true +ij_go_call_parameters_right_paren_on_new_line = true +ij_go_call_parameters_wrap = off +ij_go_fill_paragraph_width = 80 +ij_go_group_stdlib_imports = false +ij_go_import_sorting = gofmt +ij_go_keep_indents_on_empty_lines = false +ij_go_local_group_mode = project +ij_go_local_package_prefixes = +ij_go_move_all_imports_in_one_declaration = false +ij_go_move_all_stdlib_imports_in_one_group = false +ij_go_remove_redundant_import_aliases = false +ij_go_run_go_fmt_on_reformat = true +ij_go_use_back_quotes_for_imports = false +ij_go_wrap_comp_lit = off +ij_go_wrap_comp_lit_newline_after_lbrace = true +ij_go_wrap_comp_lit_newline_before_rbrace = true +ij_go_wrap_func_params = off +ij_go_wrap_func_params_newline_after_lparen = true +ij_go_wrap_func_params_newline_before_rparen = true +ij_go_wrap_func_result = off +ij_go_wrap_func_result_newline_after_lparen = true +ij_go_wrap_func_result_newline_before_rparen = true + +[{*.gradle.kts,*.kt,*.kts,*.main.kts,*.space.kts}] +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = normal +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_add_space = false +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_continuation_indent_for_chained_calls = false +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 2 +ij_kotlin_keep_blank_lines_in_code = 2 +ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_break_after_multiline_when_entry = true +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_add_space_on_reformat = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.graphqlconfig,*.graphqlrc,*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,bowerrc,brakeman.ignore,composer.lock,jest.config}] +indent_size = 2 +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +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,*.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_add_space = false +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 + +[{*.http,*.rest}] +indent_size = 0 +ij_continuation_indent_size = 4 +ij_http-request_call_parameters_wrap = normal +ij_http-request_method_parameters_wrap = split_into_lines +ij_http-request_space_before_comma = true +ij_http-request_spaces_around_assignment_operators = true + +[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] +ij_jsp_jsp_prefer_comma_separated_import_list = false +ij_jsp_keep_indents_on_empty_lines = false + +[{*.jspx,*.tagx}] +ij_jspx_keep_indents_on_empty_lines = false + +[{*.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_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +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 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.pb,*.textproto,*.txtpb}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_prototext_keep_blank_lines_in_code = 2 +ij_prototext_keep_indents_on_empty_lines = false +ij_prototext_keep_line_breaks = true +ij_prototext_space_after_colon = true +ij_prototext_space_after_comma = true +ij_prototext_space_before_colon = false +ij_prototext_space_before_comma = false +ij_prototext_spaces_within_braces = true +ij_prototext_spaces_within_brackets = false + +[{*.properties,spring.handlers,spring.schemas}] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[{*.py,*.pyw}] +ij_python_align_collections_and_comprehensions = true +ij_python_align_multiline_imports = true +ij_python_align_multiline_parameters = true +ij_python_align_multiline_parameters_in_calls = true +ij_python_blank_line_at_file_end = true +ij_python_blank_lines_after_imports = 1 +ij_python_blank_lines_after_local_imports = 0 +ij_python_blank_lines_around_class = 1 +ij_python_blank_lines_around_method = 1 +ij_python_blank_lines_around_top_level_classes_functions = 2 +ij_python_blank_lines_before_first_method = 0 +ij_python_call_parameters_new_line_after_left_paren = false +ij_python_call_parameters_right_paren_on_new_line = false +ij_python_call_parameters_wrap = normal +ij_python_dict_alignment = 0 +ij_python_dict_new_line_after_left_brace = false +ij_python_dict_new_line_before_right_brace = false +ij_python_dict_wrapping = 1 +ij_python_from_import_new_line_after_left_parenthesis = false +ij_python_from_import_new_line_before_right_parenthesis = false +ij_python_from_import_parentheses_force_if_multiline = false +ij_python_from_import_trailing_comma_if_multiline = false +ij_python_from_import_wrapping = 1 +ij_python_hang_closing_brackets = false +ij_python_keep_blank_lines_in_code = 1 +ij_python_keep_blank_lines_in_declarations = 1 +ij_python_keep_indents_on_empty_lines = false +ij_python_keep_line_breaks = true +ij_python_method_parameters_new_line_after_left_paren = false +ij_python_method_parameters_right_paren_on_new_line = false +ij_python_method_parameters_wrap = normal +ij_python_new_line_after_colon = false +ij_python_new_line_after_colon_multi_clause = true +ij_python_optimize_imports_always_split_from_imports = false +ij_python_optimize_imports_case_insensitive_order = false +ij_python_optimize_imports_join_from_imports_with_same_source = false +ij_python_optimize_imports_sort_by_type_first = true +ij_python_optimize_imports_sort_imports = true +ij_python_optimize_imports_sort_names_in_from_imports = false +ij_python_space_after_comma = true +ij_python_space_after_number_sign = true +ij_python_space_after_py_colon = true +ij_python_space_before_backslash = true +ij_python_space_before_comma = false +ij_python_space_before_for_semicolon = false +ij_python_space_before_lbracket = false +ij_python_space_before_method_call_parentheses = false +ij_python_space_before_method_parentheses = false +ij_python_space_before_number_sign = true +ij_python_space_before_py_colon = false +ij_python_space_within_empty_method_call_parentheses = false +ij_python_space_within_empty_method_parentheses = false +ij_python_spaces_around_additive_operators = true +ij_python_spaces_around_assignment_operators = true +ij_python_spaces_around_bitwise_operators = true +ij_python_spaces_around_eq_in_keyword_argument = false +ij_python_spaces_around_eq_in_named_parameter = false +ij_python_spaces_around_equality_operators = true +ij_python_spaces_around_multiplicative_operators = true +ij_python_spaces_around_power_operator = true +ij_python_spaces_around_relational_operators = true +ij_python_spaces_around_shift_operators = true +ij_python_spaces_within_braces = false +ij_python_spaces_within_brackets = false +ij_python_spaces_within_method_call_parentheses = false +ij_python_spaces_within_method_parentheses = false +ij_python_use_continuation_indent_for_arguments = false +ij_python_use_continuation_indent_for_collection_and_comprehensions = false +ij_python_use_continuation_indent_for_parameters = true +ij_python_wrap_long_lines = false + +[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] +ij_toml_keep_indents_on_empty_lines = false + +[{*.yaml,*.yml}] +indent_size = 2 +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 diff --git a/build.gradle b/build.gradle index abb6ec78129..457f7e4d21e 100644 --- a/build.gradle +++ b/build.gradle @@ -54,6 +54,10 @@ allprojects { subprojects { apply(plugin: "java") + java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } configurations.all { exclude(group: "org.hamcrest", module: "hamcrest-all") @@ -64,6 +68,24 @@ subprojects { exclude(group: "org.apache.directory.server", module: "apacheds-protocol-ldap") exclude(group: "org.skyscreamer", module: "jsonassert") exclude(group: "com.vaadin.external.google", module: "android-json") + exclude(group: "com.unboundid.components", module: "json") + + // Exclude opensaml-security-api and non-FIPS bouncycastle libs, and use Shadow library for FIPS compliance + exclude(group: "org.bouncycastle", module: "bcpkix-jdk15on") + exclude(group: "org.bouncycastle", module: "bcprov-jdk15on") + exclude(group: "org.bouncycastle", module: "bcutil-jdk15on") + exclude(group: "org.bouncycastle", module: "bcprov-jdk18on") + exclude(group: "org.bouncycastle", module: "bcpkix-jdk18on") + exclude(group: "org.bouncycastle", module: "bcutil-jdk18on") + + resolutionStrategy { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + if (details.requested.group == 'org.opensaml' && details.requested.name.startsWith("opensaml-")) { + details.useVersion "${versions.opensaml}" + details.because 'Spring Security 5.8.x allows OpenSAML 3 or 4. OpenSAML 3 has reached its end-of-life. Spring Security 6 drops support for 3, using 4.' + } + } + } } dependencies { @@ -81,9 +103,6 @@ subprojects { [compileJava, compileTestJava]*.options*.compilerArgs = ["-Xlint:none", "-nowarn"] - java.sourceCompatibility = JavaVersion.VERSION_17 - java.targetCompatibility = JavaVersion.VERSION_17 - test { maxParallelForks = 1 // when failFast = true AND retry is on, there is a serious issue: diff --git a/dependencies.gradle b/dependencies.gradle index 247faacd873..62cd6a07a75 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -6,7 +6,9 @@ ext { // Versions shared between multiple dependencies versions.aspectJVersion = "1.9.4" versions.apacheDsVersion = "2.0.0.AM27" -versions.bouncyCastleVersion = "2.0.0" +versions.bouncyCastleFipsVersion = "2.0.0" +versions.bouncyCastlePkixFipsVersion = "2.0.7" +versions.bouncyCastleTlsFipsVersion = "2.0.19" versions.hamcrestVersion = "3.0" versions.springBootVersion = "2.7.18" versions.springFrameworkVersion = "5.3.39" @@ -18,6 +20,8 @@ versions.seleniumVersion = "4.26.0" versions.braveVersion = "6.0.3" versions.jacksonVersion = "2.18.1" versions.jsonPathVersion = "2.9.0" +versions.awaitilityVersion = "4.2.2" +versions.opensaml = "4.0.1" // Spring Security 5.8.x allows OpenSAML 3 or 4. OpenSAML 3 has reached its end-of-life. Spring Security 6 drops support for 3, using 4. // Versions we're overriding from the Spring Boot Bom (Dependabot does not issue PRs to bump these versions, so we need to manually bump them) ext["mariadb.version"] = "2.7.12" // Bumping to v3 breaks some pipeline jobs (and compatibility with Amazon Aurora MySQL), so pinning to v2 for now. v2 (current version) is stable and will be supported until about September 2025 (https://mariadb.com/kb/en/about-mariadb-connector-j/). @@ -43,8 +47,10 @@ libraries.apacheDsProtocolLdap = "org.apache.directory.server:apacheds-protocol- libraries.apacheLdapApi = "org.apache.directory.api:api-ldap-model:2.1.7" libraries.aspectJRt = "org.aspectj:aspectjrt" libraries.aspectJWeaver = "org.aspectj:aspectjweaver" -libraries.bouncyCastlePkix = "org.bouncycastle:bcpkix-fips:2.0.7" -libraries.bouncyCastleProv = "org.bouncycastle:bc-fips:${versions.bouncyCastleVersion}" +libraries.awaitility = "org.awaitility:awaitility:${versions.awaitilityVersion}" +libraries.bouncyCastlePkixFips = "org.bouncycastle:bcpkix-fips:${versions.bouncyCastlePkixFipsVersion}" +libraries.bouncyCastleFipsProv = "org.bouncycastle:bc-fips:${versions.bouncyCastleFipsVersion}" +libraries.bouncyCastleTlsFips = "org.bouncycastle:bctls-fips:${versions.bouncyCastleTlsFipsVersion}" libraries.braveInstrumentationSpringWebmvc = "io.zipkin.brave:brave-instrumentation-spring-webmvc:${versions.braveVersion}" libraries.braveContextSlf4j = "io.zipkin.brave:brave-context-slf4j:${versions.braveVersion}" libraries.commonsCodec = "commons-codec:commons-codec:1.17.1" @@ -78,6 +84,7 @@ libraries.lombok = "org.projectlombok:lombok" libraries.mariaJdbcDriver = "org.mariadb.jdbc:mariadb-java-client" libraries.mockito = "org.mockito:mockito-core" libraries.mockitoJunit5 = "org.mockito:mockito-junit-jupiter" +libraries.openSamlApi = "org.opensaml:opensaml-saml-api:${versions.opensaml}" libraries.passay = "org.passay:passay:1.6.6" libraries.postgresql = "org.postgresql:postgresql:42.7.4" libraries.selenium = "org.seleniumhq.selenium:selenium-java:${versions.seleniumVersion}" @@ -103,7 +110,7 @@ libraries.springRetry = "org.springframework.retry:spring-retry" libraries.springSecurityConfig = "org.springframework.security:spring-security-config:${versions.springSecurityVersion}" libraries.springSecurityCore = "org.springframework.security:spring-security-core:${versions.springSecurityVersion}" libraries.springSecurityLdap = "org.springframework.security:spring-security-ldap:${versions.springSecurityVersion}" -libraries.springSecuritySaml = "org.springframework.security.extensions:spring-security-saml2-core:${versions.springSecuritySamlVersion}" +libraries.springSecuritySamlServiceProvider = "org.springframework.security:spring-security-saml2-service-provider:${versions.springSecurityVersion}" libraries.springSecurityTaglibs = "org.springframework.security:spring-security-taglibs:${versions.springSecurityVersion}" libraries.springSecurityTest = "org.springframework.security:spring-security-test:${versions.springSecurityVersion}" libraries.springSecurityWeb = "org.springframework.security:spring-security-web:${versions.springSecurityVersion}" @@ -127,6 +134,7 @@ libraries.velocity = "org.apache.velocity:velocity-engine-core:2.4.1" libraries.xerces = "xerces:xercesImpl:2.12.2" libraries.nimbusJwt = "com.nimbusds:nimbus-jose-jwt:9.41.2" libraries.xmlSecurity = "org.apache.santuario:xmlsec:4.0.3" +libraries.xmlUnit = "org.xmlunit:xmlunit-assertj:2.10.0" libraries.orgJson = "org.json:json:20240303" libraries.owaspEsapi = "org.owasp.esapi:esapi:2.5.5.0" libraries.jodaTime = "joda-time:joda-time:2.13.0" @@ -138,4 +146,4 @@ libraries.cargoGradlePlugin = "com.bmuschko:gradle-cargo-plugin:2.9.0" libraries.springBootGradlePlugin = "org.springframework.boot:spring-boot-gradle-plugin:${versions.springBootVersion}" libraries.springDependencyMangementGradlePlugin = "io.spring.gradle:dependency-management-plugin" libraries.gradleJcocoPlugin = "org.barfuin.gradle.jacocolog:gradle-jacoco-log:3.1.0" -libraries.sonarqubePlugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:5.1.0.4882" +libraries.sonarqubePlugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:5.1.0.4882" \ No newline at end of file diff --git a/lombok.config b/lombok.config new file mode 100644 index 00000000000..8f7e8aa1ac9 --- /dev/null +++ b/lombok.config @@ -0,0 +1 @@ +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java b/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java index 0baa2c0ecfc..2c2dd7836ea 100644 --- a/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java +++ b/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java @@ -26,7 +26,7 @@ import java.util.Map; public class JsonUtils { - private static ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectMapper objectMapper = new ObjectMapper(); public static String writeValueAsString(Object object) throws JsonUtilException { try { @@ -67,7 +67,7 @@ public static Map readValueAsMap(final String input) { public static T readValue(byte[] data, Class clazz) throws JsonUtilException { try { - if (data!=null && data.length>0) { + if (data != null && data.length > 0) { return objectMapper.readValue(data, clazz); } else { return null; @@ -91,7 +91,7 @@ public static T readValue(String s, TypeReference typeReference) { public static T readValue(byte[] data, TypeReference typeReference) { try { - if (data!=null && data.length>0) { + if (data != null && data.length > 0) { return objectMapper.readValue(data, typeReference); } else { return null; @@ -134,20 +134,18 @@ public static JsonNode readTree(String s) { } public static class JsonUtilException extends RuntimeException { - private static final long serialVersionUID = -4804245225960963421L; public JsonUtilException(Throwable cause) { super(cause); } - } public static String serializeExcludingProperties(Object object, String... propertiesToExclude) { String serialized = JsonUtils.writeValueAsString(object); - Map properties = JsonUtils.readValue(serialized, new TypeReference>() {}); - for(String property : propertiesToExclude) { - if(property.contains(".")) { + Map properties = JsonUtils.readValue(serialized, new TypeReference<>() {}); + for (String property : propertiesToExclude) { + if (property.contains(".")) { String[] split = property.split("\\.", 2); if (properties != null && properties.containsKey(split[0])) { Object inner = properties.get(split[0]); @@ -180,19 +178,19 @@ public static boolean getNodeAsBoolean(JsonNode node, String fieldName, boolean public static Date getNodeAsDate(JsonNode node, String fieldName) { JsonNode typeNode = node.get(fieldName); long date = typeNode == null ? -1 : typeNode.asLong(-1); - if (date==-1) { + if (date == -1) { return null; } else { return new Date(date); } } - public static Map getNodeAsMap(JsonNode node) { + public static Map getNodeAsMap(JsonNode node) { return objectMapper.convertValue(node, Map.class); } public static boolean hasLength(CharSequence str) { - return !(str == null || str.length()==0); + return !(str == null || str.length() == 0); } public static boolean hasText(CharSequence str) { diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java index 7fbdf0d1b6b..69764554e17 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; - +import lombok.Getter; import org.cloudfoundry.identity.uaa.EntityWithAlias; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.util.StringUtils; @@ -45,6 +45,7 @@ import static org.cloudfoundry.identity.uaa.util.JsonUtils.getNodeAsInt; import static org.cloudfoundry.identity.uaa.util.JsonUtils.getNodeAsString; +@Getter @JsonSerialize(using = IdentityProvider.IdentityProviderSerializer.class) @JsonDeserialize(using = IdentityProvider.IdentityProviderDeserializer.class) public class IdentityProvider implements EntityWithAlias { @@ -79,45 +80,32 @@ public class IdentityProvider impl private String identityZoneId; private String aliasId; private String aliasZid; - public Date getCreated() { - return created; - } + @JsonIgnore + private boolean serializeConfigRaw; - public IdentityProvider setCreated(Date created) { + public IdentityProvider setCreated(Date created) { this.created = created; return this; } - public Date getLastModified() { - return lastModified; - } - - public IdentityProvider setLastModified(Date lastModified) { + public IdentityProvider setLastModified(Date lastModified) { this.lastModified = lastModified; return this; } - public IdentityProvider setVersion(int version) { + public IdentityProvider setVersion(int version) { this.version = version; return this; } - public int getVersion() { - return version; - } - - public String getName() { - return name; - } - - public IdentityProvider setName(String name) { + public IdentityProvider setName(String name) { this.name = name; return this; } - @Override - public String getId() { - return id; + public IdentityProvider setId(String id) { + this.id = id; + return this; } @Override @@ -125,53 +113,44 @@ public String getZoneId() { return getIdentityZoneId(); } - public IdentityProvider setId(String id) { - this.id = id; - return this; - } - - public T getConfig() { - return config; - } - - public IdentityProvider setConfig(T config) { - if (config == null) { - this.type = UNKNOWN; - } else { - Class clazz = config.getClass(); - if (SamlIdentityProviderDefinition.class.isAssignableFrom(clazz)) { - this.type = SAML; + public IdentityProvider setConfig(T config) { + this.type = UNKNOWN; + if (config != null) { + this.type = determineType(config.getClass()); + if (SAML.equals(this.type)) { if (StringUtils.hasText(getOriginKey())) { ((SamlIdentityProviderDefinition) config).setIdpEntityAlias(getOriginKey()); } if (StringUtils.hasText(getIdentityZoneId())) { ((SamlIdentityProviderDefinition) config).setZoneId(getIdentityZoneId()); } - } else if (UaaIdentityProviderDefinition.class.isAssignableFrom(clazz)) { - this.type = UAA; - } else if (RawExternalOAuthIdentityProviderDefinition.class.isAssignableFrom(clazz)) { - this.type = OAUTH20; - } else if (OIDCIdentityProviderDefinition.class.isAssignableFrom(clazz)) { - this.type = OIDC10; - } else if (LdapIdentityProviderDefinition.class.isAssignableFrom(clazz)) { - this.type = LDAP; - } else if (KeystoneIdentityProviderDefinition.class.isAssignableFrom(clazz)) { - this.type = KEYSTONE; - } else if (AbstractIdentityProviderDefinition.class.isAssignableFrom(clazz)) { - this.type = UNKNOWN; - } else { - throw new IllegalArgumentException("Unknown identity provider configuration type:" + clazz.getName()); } } this.config = config; return this; } - public String getOriginKey() { - return originKey; + private static String determineType(Class clazz) { + if (SamlIdentityProviderDefinition.class.isAssignableFrom(clazz)) { + return SAML; + } else if (UaaIdentityProviderDefinition.class.isAssignableFrom(clazz)) { + return UAA; + } else if (RawExternalOAuthIdentityProviderDefinition.class.isAssignableFrom(clazz)) { + return OAUTH20; + } else if (OIDCIdentityProviderDefinition.class.isAssignableFrom(clazz)) { + return OIDC10; + } else if (LdapIdentityProviderDefinition.class.isAssignableFrom(clazz)) { + return LDAP; + } else if (KeystoneIdentityProviderDefinition.class.isAssignableFrom(clazz)) { + return KEYSTONE; + } else if (AbstractIdentityProviderDefinition.class.isAssignableFrom(clazz)) { + return UNKNOWN; + } else { + throw new IllegalArgumentException("Unknown identity provider configuration type:" + clazz.getName()); + } } - public IdentityProvider setOriginKey(String originKey) { + public IdentityProvider setOriginKey(String originKey) { this.originKey = originKey; if (config != null && config instanceof SamlIdentityProviderDefinition) { ((SamlIdentityProviderDefinition) config).setIdpEntityAlias(originKey); @@ -180,29 +159,17 @@ public IdentityProvider setOriginKey(String originKey) { return this; } - public String getType() { - return type; - } - - public IdentityProvider setType(String type) { + public IdentityProvider setType(String type) { this.type = type; return this; } - public boolean isActive() { - return active; - } - - public IdentityProvider setActive(boolean active) { + public IdentityProvider setActive(boolean active) { this.active = active; return this; } - public String getIdentityZoneId() { - return identityZoneId; - } - - public IdentityProvider setIdentityZoneId(String identityZoneId) { + public IdentityProvider setIdentityZoneId(String identityZoneId) { this.identityZoneId = identityZoneId; if (config != null && config instanceof SamlIdentityProviderDefinition) { ((SamlIdentityProviderDefinition) config).setZoneId(identityZoneId); @@ -210,21 +177,11 @@ public IdentityProvider setIdentityZoneId(String identityZoneId) { return this; } - @Override - public String getAliasId() { - return aliasId; - } - @Override public void setAliasId(String aliasId) { this.aliasId = aliasId; } - @Override - public String getAliasZid() { - return aliasZid; - } - @Override public void setAliasZid(String aliasZid) { this.aliasZid = aliasZid; @@ -305,9 +262,7 @@ public boolean equals(Object obj) { } else if (!aliasZid.equals(other.aliasZid)) { return false; } - if (version != other.version) - return false; - return true; + return version == other.version; } @Override @@ -345,13 +300,6 @@ public String toString() { return sb.toString(); } - private boolean serializeConfigRaw; - - @JsonIgnore - public boolean isSerializeConfigRaw() { - return serializeConfigRaw; - } - @JsonIgnore public void setSerializeConfigRaw(boolean serializeConfigRaw) { this.serializeConfigRaw = serializeConfigRaw; @@ -447,8 +395,5 @@ public IdentityProvider deserialize(JsonParser jp, DeserializationContext ctxt) result.setAliasZid(getNodeAsString(node, FIELD_ALIAS_ZID, null)); return result; } - - } - } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java index becdfe6260c..192893b3f99 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java @@ -15,6 +15,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.NoArgsConstructor; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.springframework.util.StringUtils; import org.xml.sax.InputSource; @@ -31,20 +33,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; + @JsonIgnoreProperties(ignoreUnknown = true) +@Data +@NoArgsConstructor public class SamlIdentityProviderDefinition extends ExternalIdentityProviderDefinition { - public enum MetadataLocation { - URL, - DATA, - UNKNOWN - } - - public enum ExternalGroupMappingMode { - EXPLICITLY_MAPPED, - AS_SCOPES - } - private String metaDataLocation; private String idpEntityAlias; private String zoneId; @@ -61,13 +55,11 @@ public enum ExternalGroupMappingMode { @JsonIgnore private String idpEntityId; - public SamlIdentityProviderDefinition() {} - public SamlIdentityProviderDefinition clone() { List emailDomain = getEmailDomain() != null ? new ArrayList<>(getEmailDomain()) : null; List externalGroupsWhitelist = getExternalGroupsWhitelist() != null ? new ArrayList<>(getExternalGroupsWhitelist()) : null; List authnContext = getAuthnContext() != null ? new ArrayList<>(getAuthnContext()) : null; - Map attributeMappings = getAttributeMappings() != null ? new HashMap(getAttributeMappings()) : null; + Map attributeMappings = getAttributeMappings() != null ? new HashMap<>(getAttributeMappings()) : null; SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); def.setMetaDataLocation(metaDataLocation); def.setIdpEntityAlias(idpEntityAlias); @@ -95,20 +87,28 @@ public SamlIdentityProviderDefinition clone() { @JsonIgnore public MetadataLocation getType() { - String trimmedLocation = metaDataLocation.trim(); - if (trimmedLocation.startsWith(" getAuthnContext() { - return authnContext; - } - public SamlIdentityProviderDefinition setAuthnContext(List authnContext) { this.authnContext = authnContext; return this; } - public int getAssertionConsumerIndex() { - return assertionConsumerIndex; - } - public SamlIdentityProviderDefinition setAssertionConsumerIndex(int assertionConsumerIndex) { this.assertionConsumerIndex = assertionConsumerIndex; return this; } - public boolean isMetadataTrustCheck() { - return metadataTrustCheck; - } - public SamlIdentityProviderDefinition setMetadataTrustCheck(boolean metadataTrustCheck) { this.metadataTrustCheck = metadataTrustCheck; return this; } - public boolean isShowSamlLink() { - return showSamlLink; - } - public SamlIdentityProviderDefinition setShowSamlLink(boolean showSamlLink) { this.showSamlLink = showSamlLink; return this; } - public ExternalGroupMappingMode getGroupMappingMode() { - return groupMappingMode; - } - - public void setGroupMappingMode(ExternalGroupMappingMode asScopes) { - this.groupMappingMode = asScopes; - } - public String getSocketFactoryClassName() { return null; } @@ -226,32 +190,16 @@ public SamlIdentityProviderDefinition setLinkText(String linkText) { return this; } - public String getIconUrl() { - return iconUrl; - } - public SamlIdentityProviderDefinition setIconUrl(String iconUrl) { this.iconUrl = iconUrl; return this; } - public String getZoneId() { - return zoneId; - } - public SamlIdentityProviderDefinition setZoneId(String zoneId) { this.zoneId = zoneId; return this; } - public boolean isSkipSslValidation() { - return skipSslValidation; - } - - public void setSkipSslValidation(boolean skipSslValidation) { - this.skipSslValidation = skipSslValidation; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -266,30 +214,40 @@ public boolean equals(Object o) { @Override public int hashCode() { String alias = getUniqueAlias(); - return alias==null ? 0 : alias.hashCode(); + return alias == null ? 0 : alias.hashCode(); } @JsonIgnore public String getUniqueAlias() { - return getIdpEntityAlias()+"###"+getZoneId(); + return getIdpEntityAlias() + "###" + getZoneId(); } @Override public String toString() { return "SamlIdentityProviderDefinition{" + - "idpEntityAlias='" + idpEntityAlias + '\'' + - ", metaDataLocation='" + metaDataLocation + '\'' + - ", nameID='" + nameID + '\'' + - ", assertionConsumerIndex=" + assertionConsumerIndex + - ", metadataTrustCheck=" + metadataTrustCheck + - ", showSamlLink=" + showSamlLink + - ", socketFactoryClassName='deprected-not used'" + - ", skipSslValidation=" + skipSslValidation + - ", linkText='" + linkText + '\'' + - ", iconUrl='" + iconUrl + '\'' + - ", zoneId='" + zoneId + '\'' + - ", addShadowUserOnLogin='" + isAddShadowUserOnLogin() + '\'' + - '}'; + "idpEntityAlias='" + idpEntityAlias + '\'' + + ", metaDataLocation='" + metaDataLocation + '\'' + + ", nameID='" + nameID + '\'' + + ", assertionConsumerIndex=" + assertionConsumerIndex + + ", metadataTrustCheck=" + metadataTrustCheck + + ", showSamlLink=" + showSamlLink + + ", socketFactoryClassName='deprected-not used'" + + ", skipSslValidation=" + skipSslValidation + + ", linkText='" + linkText + '\'' + + ", iconUrl='" + iconUrl + '\'' + + ", zoneId='" + zoneId + '\'' + + ", addShadowUserOnLogin='" + isAddShadowUserOnLogin() + '\'' + + '}'; } - } + public enum MetadataLocation { + URL, + DATA, + UNKNOWN + } + + public enum ExternalGroupMappingMode { + EXPLICITLY_MAPPED, + AS_SCOPES + } +} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java index 11f7d0f5ad4..11ade43b8c3 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java @@ -21,6 +21,7 @@ public class UaaIdentityProviderDefinition extends AbstractIdentityProviderDefin private PasswordPolicy passwordPolicy; private LockoutPolicy lockoutPolicy; private boolean disableInternalUserManagement = false; + public UaaIdentityProviderDefinition() { } @@ -57,5 +58,4 @@ public boolean isDisableInternalUserManagement() { public void setDisableInternalUserManagement(boolean disableInternalUserManagement) { this.disableInternalUserManagement = disableInternalUserManagement; } - } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/saml/SamlKey.java b/model/src/main/java/org/cloudfoundry/identity/uaa/saml/SamlKey.java index a7d56e71e9e..7bd1e982af5 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/saml/SamlKey.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/saml/SamlKey.java @@ -17,45 +17,19 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; - +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@AllArgsConstructor +@NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class SamlKey { - private String key; + @ToString.Exclude private String passphrase; private String certificate; - - public SamlKey() { - } - - public SamlKey(String key, String passphrase, String certificate) { - this.key = key; - this.passphrase = passphrase; - this.certificate = certificate; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public String getPassphrase() { - return passphrase; - } - - public void setPassphrase(String passphrase) { - this.passphrase = passphrase; - } - - public String getCertificate() { - return certificate; - } - - public void setCertificate(String certificate) { - this.certificate = certificate; - } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java b/model/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java index 299c6dd10a0..25476090337 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java @@ -20,12 +20,14 @@ public class ObjectUtils { - private ObjectUtils(){} + private ObjectUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } public static T castInstance(Object o, Class clazz) { try { return clazz.cast(o); - } catch(ClassCastException e) { + } catch (ClassCastException e) { throw new IllegalArgumentException(e); } } @@ -43,11 +45,11 @@ public static DocumentBuilder getDocumentBuilder() throws ParserConfigurationExc return factory.newDocumentBuilder(); } - public static int countNonNull( Object... objects ) { + public static int countNonNull(Object... objects) { int count = 0; - if ( objects != null ) { - for ( Object o : objects ) { - if ( o != null ) { + if (objects != null) { + for (Object o : objects) { + if (o != null) { count++; } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java b/model/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java index 41d30f796fe..b8e0e2a5212 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java @@ -55,6 +55,7 @@ public final class UaaStringUtils { public static final String DEFAULT_UAA_URL = "http://localhost:8080/uaa"; private UaaStringUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); } public static String replaceZoneVariables(String s, IdentityZone zone) { @@ -148,13 +149,14 @@ public static boolean containsWildcard(String s) { return !escapeRegExCharacters(s).equals(constructSimpleWildcardPattern(s)); } return false; - } + } /** * Escapes all regular expression patterns in a string so that when * using the string itself in a regular expression, only an exact literal match will * return true. For example, the string ".*" will not match any string, it will only * match ".*". The value ".*" when escaped will be "\.\*" + * * @param s - the string for which we need to escape regular expression constructs * @return a regular expression string that will only match exact literals */ @@ -166,7 +168,8 @@ public static String escapeRegExCharacters(String s) { * Escapes all regular expression patterns in a string so that when * using the string itself in a regular expression, only an exact literal match will * return true. - * @param s - the string for which we need to escape regular expression constructs + * + * @param s - the string for which we need to escape regular expression constructs * @param pattern - the pattern containing the characters we wish to remain string literals * @return a regular expression string that will only match exact literals */ @@ -177,6 +180,7 @@ public static String escapeRegExCharacters(String s, String pattern) { /** * Returns a pattern that does a single level regular expression match where * the * character is a wildcard until it encounters the next literal + * * @param s * @return the wildcard pattern */ @@ -194,7 +198,6 @@ public static String constructSimpleWildcardPatternWithAnyCharDelimiter(String s return result.replace("\\*", ".*"); } - public static Set constructWildcards(Collection wildcardStrings) { return constructWildcards(wildcardStrings, UaaStringUtils::constructSimpleWildcardPattern); } @@ -222,10 +225,10 @@ public static boolean matches(Iterable wildcards, String scope) { * names. * * @param properties the properties to use - * @param prefix the prefix to strip from key names + * @param prefix the prefix to strip from key names * @return a map of String values */ - public static Map getMapFromProperties(Properties properties, String prefix) { + public static Map getMapFromProperties(Properties properties, String prefix) { Map result = new HashMap<>(); for (String key : properties.stringPropertyNames()) { if (key.startsWith(prefix)) { @@ -249,15 +252,14 @@ public static String getHostIfArgIsURL(String arg) { private static boolean isPassword(String key) { key = key.toLowerCase(Locale.US); return - key.endsWith("password") || - key.endsWith("secret") || - key.endsWith("signing-key") || - key.contains("serviceproviderkey") - ; + key.endsWith("password") || + key.endsWith("secret") || + key.endsWith("signing-key") || + key.contains("serviceproviderkey"); } public static Set getStringsFromAuthorities(Collection authorities) { - if (authorities==null) { + if (authorities == null) { return Collections.emptySet(); } Set result = new HashSet<>(); @@ -268,7 +270,7 @@ public static Set getStringsFromAuthorities(Collection getAuthoritiesFromStrings(Collection authorities) { - if (authorities==null) { + if (authorities == null) { return Collections.emptyList(); } @@ -292,10 +294,12 @@ public static boolean isNullOrEmpty(final String input) { return input == null || input.length() == 0; } - public static boolean isNotEmpty(final String input) { return !isNullOrEmpty(input); } + public static boolean isNotEmpty(final String input) { + return !isNullOrEmpty(input); + } public static String convertISO8859_1_to_UTF_8(String s) { - if (s==null) { + if (s == null) { return null; } else { return new String(s.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); @@ -307,7 +311,7 @@ public static String toJsonString(String s) { return null; } String result = JsonUtils.writeValueAsString(s); - return result.substring(1, result.length()-1); + return result.substring(1, result.length() - 1); } public static String getCleanedUserControlString(String input) { diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java index 307d9f45574..69f2696db18 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java @@ -4,39 +4,21 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import javax.validation.constraints.NotNull; import java.util.Calendar; import java.util.Date; +@Data +@EqualsAndHashCode(onlyExplicitlyIncluded = true) @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class IdentityZone { - public static IdentityZone getUaa() { - Calendar calendar = Calendar.getInstance(); - calendar.clear(); - calendar.set(Calendar.YEAR, 2000); - IdentityZone uaa = new IdentityZone(); - uaa.setCreated(calendar.getTime()); - uaa.setLastModified(calendar.getTime()); - uaa.setVersion(0); - uaa.setId(OriginKeys.UAA); - uaa.setName(OriginKeys.UAA); - uaa.setDescription("The system zone for backwards compatibility"); - uaa.setSubdomain(""); - return uaa; - } - - public static String getUaaZoneId() { - return getUaa().getId(); - } - - @JsonIgnore - public boolean isUaa() { - return this.equals(getUaa()); - } + @EqualsAndHashCode.Include private String id; @NotNull @@ -58,97 +40,27 @@ public boolean isUaa() { private boolean active = true; - public Date getCreated() { - return created; - } - - public void setCreated(Date created) { - this.created = created; - } - - public Date getLastModified() { - return lastModified; - } - - public void setLastModified(Date lastModified) { - this.lastModified = lastModified; - } - - public void setVersion(int version) { - this.version = version; - } - - public int getVersion() { - return version; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getSubdomain() { - return subdomain; - } - - public void setSubdomain(String subdomain) { - this.subdomain = subdomain; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - public IdentityZoneConfiguration getConfig() { - return config; - } - - public void setConfig(IdentityZoneConfiguration config) { - this.config = config; + public static IdentityZone getUaa() { + Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(Calendar.YEAR, 2000); + IdentityZone uaa = new IdentityZone(); + uaa.setCreated(calendar.getTime()); + uaa.setLastModified(calendar.getTime()); + uaa.setVersion(0); + uaa.setId(OriginKeys.UAA); + uaa.setName(OriginKeys.UAA); + uaa.setDescription("The system zone for backwards compatibility"); + uaa.setSubdomain(""); + return uaa; } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((id == null) ? 0 : id.hashCode()); - return result; + public static String getUaaZoneId() { + return OriginKeys.UAA; } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - IdentityZone other = (IdentityZone) obj; - if (id == null) { - return other.id == null; - } else return id.equals(other.id); + @JsonIgnore + public boolean isUaa() { + return OriginKeys.UAA.equals(getId()); } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java index 79cfd45c6ef..2430766f55a 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; import org.cloudfoundry.identity.uaa.login.Prompt; import java.net.MalformedURLException; @@ -22,128 +23,57 @@ import java.util.Arrays; import java.util.List; +@Data @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class IdentityZoneConfiguration { + private static final String PASSWORD = "password"; + private ClientSecretPolicy clientSecretPolicy = new ClientSecretPolicy(); private TokenPolicy tokenPolicy = new TokenPolicy(); private SamlConfig samlConfig = new SamlConfig(); private CorsPolicy corsPolicy = new CorsPolicy(); private Links links = new Links(); private List prompts = Arrays.asList( - new Prompt("username", "text", "Email"), - new Prompt("password", "password", "Password"), - new Prompt("passcode", "password", "Temporary Authentication Code (Get on at /passcode)") + new Prompt("username", "text", "Email"), + new Prompt(PASSWORD, PASSWORD, "Password"), + new Prompt("passcode", PASSWORD, "Temporary Authentication Code (Get on at /passcode)") ); private boolean idpDiscoveryEnabled = false; private BrandingInformation branding; private boolean accountChooserEnabled; private UserConfig userConfig = new UserConfig(); + @JsonInclude(JsonInclude.Include.NON_NULL) private String issuer; private String defaultIdentityProvider; - public IdentityZoneConfiguration() {} - - public IdentityZoneConfiguration(TokenPolicy tokenPolicy) { - this.tokenPolicy = tokenPolicy; - } - - public ClientSecretPolicy getClientSecretPolicy() { - return clientSecretPolicy; - } - - public void setClientSecretPolicy(ClientSecretPolicy clientSecretPolicy) { - this.clientSecretPolicy = clientSecretPolicy; - } - - public TokenPolicy getTokenPolicy() { - return tokenPolicy; + public IdentityZoneConfiguration() { } - public void setTokenPolicy(TokenPolicy tokenPolicy) { + public IdentityZoneConfiguration(TokenPolicy tokenPolicy) { this.tokenPolicy = tokenPolicy; } - public SamlConfig getSamlConfig() { - return samlConfig; - } - public IdentityZoneConfiguration setSamlConfig(SamlConfig samlConfig) { this.samlConfig = samlConfig; return this; } - public Links getLinks() { - return links; - } - public IdentityZoneConfiguration setLinks(Links links) { this.links = links; return this; } - public List getPrompts() { - return prompts; - } - public IdentityZoneConfiguration setPrompts(List prompts) { this.prompts = prompts; return this; } - public boolean isIdpDiscoveryEnabled() { - return idpDiscoveryEnabled; - } - - public void setIdpDiscoveryEnabled(boolean idpDiscoveryEnabled) { - this.idpDiscoveryEnabled = idpDiscoveryEnabled; - } - - public BrandingInformation getBranding() { - return branding; - } - - public void setBranding(BrandingInformation branding) { - this.branding = branding; - } - - public void setAccountChooserEnabled(boolean accountChooserEnabled) { - this.accountChooserEnabled = accountChooserEnabled; - } - - public CorsPolicy getCorsPolicy() { - return corsPolicy; - } - public IdentityZoneConfiguration setCorsPolicy(CorsPolicy corsPolicy) { this.corsPolicy = corsPolicy; return this; } - public boolean isAccountChooserEnabled() { - return accountChooserEnabled; - } - - public UserConfig getUserConfig() { - return userConfig; - } - - public void setUserConfig(UserConfig userConfig) { - this.userConfig = userConfig; - } - - public String getDefaultIdentityProvider() { - return defaultIdentityProvider; - } - - public void setDefaultIdentityProvider(String defaultIdentityProvider) { - this.defaultIdentityProvider = defaultIdentityProvider; - } - - @JsonInclude(JsonInclude.Include.NON_NULL) - public String getIssuer() { - return issuer; - } @JsonInclude(JsonInclude.Include.NON_NULL) public void setIssuer(String issuer) { diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java index fd93f3f24bb..3d7fcd3bd90 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java @@ -18,16 +18,21 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; import org.cloudfoundry.identity.uaa.saml.SamlKey; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; import static org.springframework.util.StringUtils.hasText; @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) +@Data public class SamlConfig { public static final String LEGACY_KEY_ID = "legacy-saml-key"; @@ -48,87 +53,71 @@ public void setEntityID(String entityID) { this.entityID = entityID; } - public boolean isRequestSigned() { - return requestSigned; - } - - public void setRequestSigned(boolean requestSigned) { - this.requestSigned = requestSigned; - } - - public boolean isWantAssertionSigned() { - return wantAssertionSigned; - } - - public void setWantAssertionSigned(boolean wantAssertionSigned) { - this.wantAssertionSigned = wantAssertionSigned; - } - @JsonProperty("certificate") public void setCertificate(String certificate) { - SamlKey legacyKey = keys.get(LEGACY_KEY_ID); - if (hasText(certificate) && null == legacyKey) { - legacyKey = new SamlKey(); - } - if (legacyKey != null) { - legacyKey.setCertificate(certificate); - keys.put(LEGACY_KEY_ID, legacyKey); + if (hasText(certificate)) { + keys.computeIfAbsent(LEGACY_KEY_ID, k -> new SamlKey()); } + keys.computeIfPresent(LEGACY_KEY_ID, (k, v) -> { + v.setCertificate(certificate); + return v; + }); } @JsonProperty("privateKey") public void setPrivateKey(String privateKey) { - SamlKey legacyKey = keys.get(LEGACY_KEY_ID); - if (hasText(privateKey) && null == legacyKey) { - legacyKey = new SamlKey(); - } - if (legacyKey != null) { - legacyKey.setKey(privateKey); - keys.put(LEGACY_KEY_ID, legacyKey); + if (hasText(privateKey)) { + keys.computeIfAbsent(LEGACY_KEY_ID, k -> new SamlKey()); } + keys.computeIfPresent(LEGACY_KEY_ID, (k, v) -> { + v.setKey(privateKey); + return v; + }); } @JsonProperty("privateKeyPassword") public void setPrivateKeyPassword(String privateKeyPassword) { - SamlKey legacyKey = keys.get(LEGACY_KEY_ID); - if (hasText(privateKeyPassword) && null == legacyKey) { - legacyKey = new SamlKey(); - } - if (legacyKey != null) { - legacyKey.setPassphrase(privateKeyPassword); - keys.put(LEGACY_KEY_ID, legacyKey); + if (hasText(privateKeyPassword)) { + keys.computeIfAbsent(LEGACY_KEY_ID, k -> new SamlKey()); } + keys.computeIfPresent(LEGACY_KEY_ID, (k, v) -> { + v.setPassphrase(privateKeyPassword); + return v; + }); } @JsonProperty("certificate") public String getCertificate() { - SamlKey legacyKey = keys.get(LEGACY_KEY_ID); - if (null != legacyKey) { - return legacyKey.getCertificate(); - } - return null; + return Optional.ofNullable(keys.get(LEGACY_KEY_ID)) + .map(SamlKey::getCertificate) + .orElse(null); } @JsonProperty public String getPrivateKey() { - SamlKey legacyKey = keys.get(LEGACY_KEY_ID); - if (null != legacyKey) { - return legacyKey.getKey(); - } - return null; + return Optional.ofNullable(keys.get(LEGACY_KEY_ID)) + .map(SamlKey::getKey) + .orElse(null); } @JsonProperty public String getPrivateKeyPassword() { - SamlKey legacyKey = keys.get(LEGACY_KEY_ID); - if (null != legacyKey) { - return legacyKey.getPassphrase(); - } - return null; + return Optional.ofNullable(keys.get(LEGACY_KEY_ID)) + .map(SamlKey::getPassphrase) + .orElse(null); } public String getActiveKeyId() { - return hasText(activeKeyId) ? activeKeyId : hasLegacyKey() ? LEGACY_KEY_ID : null; + if (hasText(activeKeyId)) { + return activeKeyId; + } + return hasLegacyKey() ? LEGACY_KEY_ID : null; + } + + @JsonIgnore + public SamlKey getActiveKey() { + String keyId = getActiveKeyId(); + return keyId != null ? keys.get(keyId) : null; } public void setActiveKeyId(String activeKeyId) { @@ -137,10 +126,28 @@ public void setActiveKeyId(String activeKeyId) { } } + /** + * @return a map of all keys by keyName + */ public Map getKeys() { return Collections.unmodifiableMap(keys); } + /** + * @return the list of keys, with the active key first. + */ + @JsonIgnore + public List getKeyList() { + List keyList = new ArrayList<>(); + String resolvedActiveKeyId = getActiveKeyId(); + Optional.ofNullable(getActiveKey()).ifPresent(keyList::add); + keyList.addAll(keys.entrySet().stream() + .filter(e -> !e.getKey().equals(resolvedActiveKeyId)) + .map(Map.Entry::getValue) + .toList()); + return Collections.unmodifiableList(keyList); + } + public void setKeys(Map keys) { this.keys = new HashMap<>(keys); } @@ -165,12 +172,4 @@ protected boolean hasLegacyKey() { public SamlKey removeKey(String keyId) { return keys.remove(keyId); } - - public boolean isDisableInResponseToCheck() { - return disableInResponseToCheck; - } - - public void setDisableInResponseToCheck(boolean disableInResponseToCheck) { - this.disableInResponseToCheck = disableInResponseToCheck; - } } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderTest.java index 2d68ccc47c9..32949f1982a 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderTest.java @@ -1,7 +1,13 @@ package org.cloudfoundry.identity.uaa.provider; import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.KEYSTONE; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.SAML; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UNKNOWN; import org.junit.jupiter.api.Test; @@ -78,19 +84,19 @@ void testEqualsAndHashCode() { idp2.setLastModified(idp1.getLastModified()); // initially, the tow IdPs should be equal - assertThat(idp1.equals(idp2)).isTrue(); - assertThat(idp1).hasSameHashCodeAs(idp2); + assertThat(idp1).isEqualTo(idp2) + .hasSameHashCodeAs(idp2); // remove aliasZid idp2.setAliasZid(null); - assertThat(idp1.equals(idp2)).isFalse(); - assertThat(idp2.equals(idp1)).isFalse(); + assertThat(idp1).isNotEqualTo(idp2); + assertThat(idp2).isNotEqualTo(idp1); idp2.setAliasZid(customZoneId); // remove aliasId idp2.setAliasId(null); - assertThat(idp1.equals(idp2)).isFalse(); - assertThat(idp2.equals(idp1)).isFalse(); + assertThat(idp1).isNotEqualTo(idp2); + assertThat(idp2).isNotEqualTo(idp1); } @Test @@ -111,4 +117,77 @@ void testGetAliasDescription() { "IdentityProvider[id='12345',zid='uaa',aliasId='id-of-alias-idp',aliasZid='custom-zone']" ); } -} \ No newline at end of file + + @Test + void setConfigSamlType() { + final IdentityProvider idp = new IdentityProvider<>(); + final SamlIdentityProviderDefinition config = new SamlIdentityProviderDefinition(); + idp.setConfig(config); + + assertThat(idp).returns(SAML, IdentityProvider::getType); + } + + @Test + void setConfigUAAType() { + final IdentityProvider idp = new IdentityProvider<>(); + final UaaIdentityProviderDefinition config = new UaaIdentityProviderDefinition(); + idp.setConfig(config); + + assertThat(idp).returns(UAA, IdentityProvider::getType); + } + + @Test + void setConfigOauth2Type() { + final IdentityProvider idp = new IdentityProvider<>(); + final RawExternalOAuthIdentityProviderDefinition config = new RawExternalOAuthIdentityProviderDefinition(); + idp.setConfig(config); + + assertThat(idp).returns(OAUTH20, IdentityProvider::getType); + } + + @Test + void setConfigOidcType() { + final IdentityProvider idp = new IdentityProvider<>(); + final OIDCIdentityProviderDefinition config = new OIDCIdentityProviderDefinition(); + idp.setConfig(config); + + assertThat(idp).returns(OIDC10, IdentityProvider::getType); + } + + @Test + void setConfigLdapType() { + final IdentityProvider idp = new IdentityProvider<>(); + final LdapIdentityProviderDefinition config = new LdapIdentityProviderDefinition(); + idp.setConfig(config); + + assertThat(idp).returns(LDAP, IdentityProvider::getType); + } + + @Test + void setConfigKeystoneType() { + final IdentityProvider idp = new IdentityProvider<>(); + final KeystoneIdentityProviderDefinition config = new KeystoneIdentityProviderDefinition(); + idp.setConfig(config); + + assertThat(idp).returns(KEYSTONE, IdentityProvider::getType); + } + + @Test + void setConfigUnknownType() { + final IdentityProvider idp = new IdentityProvider<>(); + final AbstractIdentityProviderDefinition config = new AbstractIdentityProviderDefinition(); + idp.setConfig(config); + + assertThat(idp).returns(UNKNOWN, IdentityProvider::getType); + } + + @Test + void setConfigNull() { + final IdentityProvider idp = new IdentityProvider<>(); + idp.setConfig(null); + + assertThat(idp) + .returns(UNKNOWN, IdentityProvider::getType) + .returns(null, IdentityProvider::getConfig); + } +} diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java index 597ae6910a7..457a88fe9c5 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java @@ -11,13 +11,10 @@ import org.junit.Test; import org.springframework.util.ReflectionUtils; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition.MetadataLocation.DATA; import static org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN; import static org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition.MetadataLocation.URL; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; public class SamlIdentityProviderDefinitionTests { @@ -47,46 +44,47 @@ public void testEquals() { SamlIdentityProviderDefinition definition2 = buildSamlIdentityProviderDefinition(); definition2.setAddShadowUserOnLogin(false); - assertNotEquals(definition, definition2); + assertThat(definition2).isNotEqualTo(definition); definition2.setAddShadowUserOnLogin(true); - assertEquals(definition, definition2); + assertThat(definition2).isEqualTo(definition); } @Test public void test_serialize_custom_attributes_field() { definition.setStoreCustomAttributes(true); SamlIdentityProviderDefinition def = JsonUtils.readValue(JsonUtils.writeValueAsString(definition), SamlIdentityProviderDefinition.class); - assertTrue(def.isStoreCustomAttributes()); + assertThat(def).isNotNull(); + assertThat(def.isStoreCustomAttributes()).isTrue(); } @Test public void testGetType() throws Exception { SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); def.setMetaDataLocation(""); - assertEquals(SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN, def.getType()); + assertThat(def.getType()).isEqualTo(SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN); def.setMetaDataLocation("https://dadas.dadas.dadas/sdada"); - assertEquals(SamlIdentityProviderDefinition.MetadataLocation.URL, def.getType()); + assertThat(def.getType()).isEqualTo(SamlIdentityProviderDefinition.MetadataLocation.URL); def.setMetaDataLocation("http://dadas.dadas.dadas/sdada"); - assertEquals(SamlIdentityProviderDefinition.MetadataLocation.URL, def.getType()); + assertThat(def.getType()).isEqualTo(SamlIdentityProviderDefinition.MetadataLocation.URL); def.setMetaDataLocation("test-file-metadata.xml"); - assertEquals(SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN, def.getType()); + assertThat(def.getType()).isEqualTo(SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN); File f = new File(System.getProperty("java.io.tmpdir"),SamlIdentityProviderDefinitionTests.class.getName()+".testcase"); f.createNewFile(); f.deleteOnExit(); def.setMetaDataLocation(f.getAbsolutePath()); - assertEquals(SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN, def.getType()); + assertThat(def.getType()).isEqualTo(SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN); f.delete(); def.setMetaDataLocation(f.getAbsolutePath()); - assertEquals(SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN, def.getType()); + assertThat(def.getType()).isEqualTo(SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN); } @Test public void test_XML_with_DOCTYPE_Fails() { definition.setMetaDataLocation(IDP_METADATA.replace("\n", "\n")); - assertEquals(UNKNOWN, definition.getType()); + assertThat(definition.getType()).isEqualTo(UNKNOWN); } @Test @@ -103,7 +101,7 @@ public void doWith(Field f) throws IllegalArgumentException, IllegalAccessExcept f.setAccessible(true); Object expectedValue = f.get(definition); Object actualValue = f.get(def); - assertEquals(f.getName(), expectedValue, actualValue); + assertThat(actualValue).as(f.getName()).isEqualTo(expectedValue); } }); @@ -113,37 +111,37 @@ public void doWith(Field f) throws IllegalArgumentException, IllegalAccessExcept @Test public void test_Get_FileType_Fails_and_is_No_Longer_Supported() { definition.setMetaDataLocation(System.getProperty("user.home")); - assertEquals(UNKNOWN, definition.getType()); + assertThat(definition.getType()).isEqualTo(UNKNOWN); } @Test public void test_Get_URL_Type_Must_Be_Valid_URL() { definition.setMetaDataLocation("http"); - assertEquals(UNKNOWN, definition.getType()); + assertThat(definition.getType()).isEqualTo(UNKNOWN); } @Test public void test_Get_URL_When_Valid() { definition.setMetaDataLocation("http://uaa.com/saml/metadata"); - assertEquals(URL, definition.getType()); + assertThat(definition.getType()).isEqualTo(URL); } @Test public void test_Get_Data_Type_Must_Be_Valid_Data() { definition.setMetaDataLocation(" + + """); + assertThat(def.getType()).isEqualTo(SamlServiceProviderDefinition.MetadataLocation.DATA); + } + + @Test + void getType_invalidXml() { + var def = new SamlServiceProviderDefinition(); + + def.setMetaDataLocation(""); + assertThat(def.getType()).isEqualTo(SamlServiceProviderDefinition.MetadataLocation.UNKNOWN); + } + + @Test + void getType_doctype() { + var def = new SamlServiceProviderDefinition(); + def.setMetaDataLocation(""" + + + """); + assertThat(def.getType()).isEqualTo(SamlServiceProviderDefinition.MetadataLocation.UNKNOWN); + } + + @Test + void getType_Url() { + var def = new SamlServiceProviderDefinition(); + def.setMetaDataLocation(METADATA_URL_LOCATION); + assertThat(def.getType()).isEqualTo(SamlServiceProviderDefinition.MetadataLocation.URL); + } + + @Test + void metaDataLocation() { + var def = new SamlServiceProviderDefinition(); + def.setMetaDataLocation(METADATA_URL_LOCATION); + + assertThat(def.getMetaDataLocation()).isEqualTo(METADATA_URL_LOCATION); + } + + @Test + void nameID() { + var def = new SamlServiceProviderDefinition(); + def.setNameID(VALUE); + assertThat(def.getNameID()).isEqualTo(VALUE); + } + + @Test + void singleSignOnServiceIndex() { + var def = new SamlServiceProviderDefinition(); + def.setSingleSignOnServiceIndex(2); + assertThat(def.getSingleSignOnServiceIndex()).isEqualTo(2); + } + + @Test + void metadataTrustCheck() { + var def = new SamlServiceProviderDefinition(); + assertThat(def.isMetadataTrustCheck()).isFalse(); + def.setMetadataTrustCheck(true); + assertThat(def.isMetadataTrustCheck()).isTrue(); + } + + @Test + void skipSslValidation() { + var def = new SamlServiceProviderDefinition(); + assertThat(def.isSkipSslValidation()).isFalse(); + def.setSkipSslValidation(true); + assertThat(def.isSkipSslValidation()).isTrue(); + } + + @Test + void enableIdpInitiatedSso() { + var def = new SamlServiceProviderDefinition(); + assertThat(def.isEnableIdpInitiatedSso()).isFalse(); + def.setEnableIdpInitiatedSso(true); + assertThat(def.isEnableIdpInitiatedSso()).isTrue(); + } + + @Test + void attributeMappings() { + var def = new SamlServiceProviderDefinition(); + assertThat(def.getAttributeMappings()).isEmpty(); + def.setAttributeMappings(Map.of("k1", "v1")); + assertThat(def.getAttributeMappings()).hasSize(1).containsEntry("k1", "v1"); + } + + @Test + void staticCustomAttributes() { + var def = new SamlServiceProviderDefinition(); + assertThat(def.getStaticCustomAttributes()).isEmpty(); + def.setStaticCustomAttributes(Map.of("k1", "v1")); + assertThat(def.getStaticCustomAttributes()).hasSize(1).containsEntry("k1", "v1"); + } + + @Test + void testHashCode() { + var def1 = new SamlServiceProviderDefinition(); + var def2 = new SamlServiceProviderDefinition(); + assertThat(def1).hasSameHashCodeAs(def2); + } + + @Test + void equals() { + var def1 = new SamlServiceProviderDefinition(); + var def2 = new SamlServiceProviderDefinition(); + assertThat(def1).isEqualTo(def2); + + def1.setNameID(VALUE); + assertThat(def1).isNotEqualTo(def2); + } + + @Test + void testToString() { + var def1 = new SamlServiceProviderDefinition(); + def1.setNameID(VALUE); + assertThat(def1).hasToString("SamlServiceProviderDefinition{metaDataLocation='null', nameID='value', singleSignOnServiceIndex=0, metadataTrustCheck=false, skipSslValidation=false, attributeMappings={}}"); + } + + @Test + void builder() { + var def1 = SamlServiceProviderDefinition.Builder.get() + .setMetaDataLocation(METADATA_URL_LOCATION) + .setNameID(VALUE) + .setSingleSignOnServiceIndex(3) + .setMetadataTrustCheck(true) + .setEnableIdpInitiatedSso(true) + .build(); + + assertThat(def1) + .returns( METADATA_URL_LOCATION, SamlServiceProviderDefinition::getMetaDataLocation) + .returns( VALUE, SamlServiceProviderDefinition::getNameID) + .returns( 3, SamlServiceProviderDefinition::getSingleSignOnServiceIndex) + .returns( true, SamlServiceProviderDefinition::isMetadataTrustCheck) + .returns( true, SamlServiceProviderDefinition::isSkipSslValidation) + .returns( true, SamlServiceProviderDefinition::isEnableIdpInitiatedSso); + + } + + @Test + void testClone() { + var def1 = SamlServiceProviderDefinition.Builder.get() + .setMetaDataLocation(METADATA_URL_LOCATION) + .setNameID(VALUE) + .setSingleSignOnServiceIndex(3) + .setMetadataTrustCheck(true) + .setEnableIdpInitiatedSso(true) + .build(); + + assertThat(def1.clone()).isEqualTo(def1); + } +} diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/util/ObjectUtilsTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/util/ObjectUtilsTest.java index 740e6201659..d38e9363976 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/util/ObjectUtilsTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/util/ObjectUtilsTest.java @@ -1,48 +1,43 @@ package org.cloudfoundry.identity.uaa.util; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; - import java.util.ArrayList; -import java.util.Arrays; +import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; class ObjectUtilsTest { - private static final Object[] NULLARRAY = null; - private static final Object[] EMPTY = {}; - private static final Object[] JUST_NULLS = {null}; - private static final Object[] VALUES = {5, 2, null, 7, "Martin Fowler"}; - - @Test - void countNonNull() { - Assertions.assertEquals( 0, ObjectUtils.countNonNull( NULLARRAY ), "NULLARRAY" ); - Assertions.assertEquals( 0, ObjectUtils.countNonNull( EMPTY ), "EMPTY" ); - Assertions.assertEquals( 0, ObjectUtils.countNonNull( JUST_NULLS ), "JUST_NULLS" ); - Assertions.assertEquals( 4, ObjectUtils.countNonNull( VALUES ), "VALUES" ); - } - - @Test - void getDocumentBuilder() throws ParserConfigurationException { - DocumentBuilder builder = ObjectUtils.getDocumentBuilder(); - assertNotNull(builder); - assertNotNull(builder.getDOMImplementation()); - assertEquals(false, builder.isValidating()); - assertEquals(true, builder.isNamespaceAware()); - assertEquals(false, builder.isXIncludeAware()); - } - - @Test - void isNotExmpty() { - assertTrue(ObjectUtils.isNotEmpty(Arrays.asList("1"))); - assertFalse(ObjectUtils.isNotEmpty(new ArrayList<>())); - assertFalse(ObjectUtils.isNotEmpty(null)); - } + private static final Object[] NULLARRAY = null; + private static final Object[] EMPTY = {}; + private static final Object[] JUST_NULLS = {null}; + private static final Object[] VALUES = {5, 2, null, 7, "Martin Fowler"}; + + @Test + void countNonNull() { + assertThat(ObjectUtils.countNonNull(NULLARRAY)).as("NULLARRAY").isZero(); + assertThat(ObjectUtils.countNonNull(EMPTY)).as("EMPTY").isZero(); + assertThat(ObjectUtils.countNonNull(JUST_NULLS)).as("JUST_NULLS").isZero(); + assertThat(ObjectUtils.countNonNull(VALUES)).as("VALUES").isEqualTo(4); + } + + @Test + void getDocumentBuilder() throws ParserConfigurationException { + DocumentBuilder builder = ObjectUtils.getDocumentBuilder(); + assertThat(builder).isNotNull(); + assertThat(builder.getDOMImplementation()).isNotNull(); + assertThat(builder.isValidating()).isFalse(); + assertThat(builder.isNamespaceAware()).isTrue(); + assertThat(builder.isXIncludeAware()).isFalse(); + } + + @Test + void isNotEmpty() { + assertThat(ObjectUtils.isNotEmpty(List.of("1"))).isTrue(); + assertThat(ObjectUtils.isNotEmpty(new ArrayList<>())).isFalse(); + assertThat(ObjectUtils.isNotEmpty(null)).isFalse(); + } } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/util/UaaStringUtilsTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/util/UaaStringUtilsTest.java index e5bef754fe5..75f7965f826 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/util/UaaStringUtilsTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/util/UaaStringUtilsTest.java @@ -19,12 +19,10 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; +import java.util.stream.Stream; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasEntry; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class UaaStringUtilsTest { @@ -53,14 +51,14 @@ void setUp() { @Test void nonNull() { - assertNull(UaaStringUtils.nonNull()); - assertNull(UaaStringUtils.nonNull((String) null)); - assertNull(UaaStringUtils.nonNull(null, null)); - assertEquals("7", UaaStringUtils.nonNull("7")); - assertEquals("6", UaaStringUtils.nonNull(null, "6")); - assertEquals("5", UaaStringUtils.nonNull(null, null, "5")); - assertEquals("1", UaaStringUtils.nonNull(null, null, "1", "2")); - assertEquals("2", UaaStringUtils.nonNull(null, null, null, "2")); + assertThat(UaaStringUtils.nonNull()).isNull(); + assertThat(UaaStringUtils.nonNull((String) null)).isNull(); + assertThat(UaaStringUtils.nonNull(null, null)).isNull(); + assertThat(UaaStringUtils.nonNull("7")).isEqualTo("7"); + assertThat(UaaStringUtils.nonNull(null, "6")).isEqualTo("6"); + assertThat(UaaStringUtils.nonNull(null, null, "5")).isEqualTo("5"); + assertThat(UaaStringUtils.nonNull(null, null, "1", "2")).isEqualTo("1"); + assertThat(UaaStringUtils.nonNull(null, null, null, "2")).isEqualTo("2"); } @Test @@ -72,16 +70,16 @@ void replace_zone_variables() { @Test void camelToUnderscore() { - assertEquals("test_camel_case", UaaStringUtils.camelToUnderscore("testCamelCase")); - assertEquals("testcamelcase", UaaStringUtils.camelToUnderscore("testcamelcase")); - assertEquals("test_camel_case", UaaStringUtils.camelToUnderscore("test_camel_case")); - assertEquals("test_camel_case", UaaStringUtils.camelToUnderscore("test_Camel_Case")); + assertThat(UaaStringUtils.camelToUnderscore("testCamelCase")).isEqualTo("test_camel_case"); + assertThat(UaaStringUtils.camelToUnderscore("testcamelcase")).isEqualTo("testcamelcase"); + assertThat(UaaStringUtils.camelToUnderscore("test_camel_case")).isEqualTo("test_camel_case"); + assertThat(UaaStringUtils.camelToUnderscore("test_Camel_Case")).isEqualTo("test_camel_case"); } @Test void getErrorName() { - assertEquals("illegal_argument", UaaStringUtils.getErrorName(new IllegalArgumentException())); - assertEquals("null_pointer", UaaStringUtils.getErrorName(new NullPointerException())); + assertThat(UaaStringUtils.getErrorName(new IllegalArgumentException())).isEqualTo("illegal_argument"); + assertThat(UaaStringUtils.getErrorName(new NullPointerException())).isEqualTo("null_pointer"); } @Test @@ -91,7 +89,7 @@ void hidePasswords() { map.put("fail", "reason"); result = UaaStringUtils.hidePasswords(map); - assertThat(map, hasEntry("fail", "reason")); + assertThat(map).containsEntry("fail", "reason"); result.remove("fail"); checkPasswords(result); @@ -100,42 +98,42 @@ void hidePasswords() { properties.put("fail", "reason"); presult = UaaStringUtils.hidePasswords(properties); - assertThat(presult, hasEntry("fail", "reason")); + assertThat(presult).containsEntry("fail", "reason"); presult.remove("fail"); checkPasswords(new HashMap(presult)); } @Test void escapeRegExCharacters() { - assertTrue(matches(UaaStringUtils.escapeRegExCharacters(".*"), ".*")); - assertFalse(matches(UaaStringUtils.escapeRegExCharacters(".*"), ".some other string")); - assertTrue(matches(UaaStringUtils.escapeRegExCharacters("x"), "x")); - assertTrue(matches(UaaStringUtils.escapeRegExCharacters("x*x"), "x*x")); - assertEquals(UaaStringUtils.escapeRegExCharacters("\\"), "\\\\"); - assertEquals(UaaStringUtils.escapeRegExCharacters("["), "\\["); + assertThat(matches(UaaStringUtils.escapeRegExCharacters(".*"), ".*")).isTrue(); + assertThat(matches(UaaStringUtils.escapeRegExCharacters(".*"), ".some other string")).isFalse(); + assertThat(matches(UaaStringUtils.escapeRegExCharacters("x"), "x")).isTrue(); + assertThat(matches(UaaStringUtils.escapeRegExCharacters("x*x"), "x*x")).isTrue(); + assertThat(UaaStringUtils.escapeRegExCharacters("\\")).isEqualTo("\\\\"); + assertThat(UaaStringUtils.escapeRegExCharacters("[")).isEqualTo("\\["); } @Test void constructSimpleWildcardPattern() { - assertEquals("space\\.[^\\\\.]+\\.developer", UaaStringUtils.constructSimpleWildcardPattern("space.*.developer")); - assertEquals("space\\.developer", UaaStringUtils.constructSimpleWildcardPattern("space.developer")); + assertThat(UaaStringUtils.constructSimpleWildcardPattern("space.*.developer")).isEqualTo("space\\.[^\\\\.]+\\.developer"); + assertThat(UaaStringUtils.constructSimpleWildcardPattern("space.developer")).isEqualTo("space\\.developer"); } @Test void containsWildcard() { - assertTrue(UaaStringUtils.containsWildcard("space.*.developer")); - assertTrue(UaaStringUtils.containsWildcard("*.developer")); - assertTrue(UaaStringUtils.containsWildcard("space.*")); - assertFalse(UaaStringUtils.containsWildcard("space.developer")); - assertTrue(UaaStringUtils.containsWildcard("space.*.*.developer")); - assertTrue(UaaStringUtils.containsWildcard("*")); - assertFalse(UaaStringUtils.containsWildcard(null)); + assertThat(UaaStringUtils.containsWildcard("space.*.developer")).isTrue(); + assertThat(UaaStringUtils.containsWildcard("*.developer")).isTrue(); + assertThat(UaaStringUtils.containsWildcard("space.*")).isTrue(); + assertThat(UaaStringUtils.containsWildcard("space.developer")).isFalse(); + assertThat(UaaStringUtils.containsWildcard("space.*.*.developer")).isTrue(); + assertThat(UaaStringUtils.containsWildcard("*")).isTrue(); + assertThat(UaaStringUtils.containsWildcard(null)).isFalse(); } @Test void constructWildcards() { - assertEquals(Set.of(), UaaStringUtils.constructWildcards(Collections.EMPTY_LIST)); - assertFalse(UaaStringUtils.constructWildcards(Collections.singletonList("any")).contains("any")); + assertThat(UaaStringUtils.constructWildcards(List.of())).isEmpty(); + assertThat(UaaStringUtils.constructWildcards(List.of("any"))).doesNotContain(Pattern.compile("any")); } @Test @@ -160,11 +158,11 @@ void constructSimpleWildcardPattern_matches() { }; for (String m : matching) { String msg = "Testing [" + m + "] against [" + s1 + "]"; - assertTrue(matches(p1, m), msg); + assertThat(matches(p1, m)).as(msg).isTrue(); } for (String n : notmatching) { String msg = "Testing [" + n + "] against [" + s1 + "]"; - assertFalse(matches(p1, n), msg); + assertThat(matches(p1, n)).as(msg).isFalse(); } } @@ -188,7 +186,7 @@ void constructSimpleWildcardPattern_includeRegExInWildcardPattern() { }; for (String n : notmatching) { String msg = "Testing [" + n + "] against [" + s1 + "]"; - assertFalse(matches(p1, n), msg); + assertThat(matches(p1, n)).as(msg).isFalse(); } } @@ -213,11 +211,11 @@ void constructSimpleWildcardPattern_beginningWildcardPattern() { }; for (String m : matching) { String msg = "Testing [" + m + "] against [" + s1 + "]"; - assertTrue(matches(p1, m), msg); + assertThat(matches(p1, m)).as(msg).isTrue(); } for (String n : notmatching) { String msg = "Testing [" + n + "] against [" + s1 + "]"; - assertFalse(matches(p1, n), msg); + assertThat(matches(p1, n)).as(msg).isFalse(); } } @@ -242,11 +240,11 @@ void constructSimpleWildcardPattern_allWildcardPattern() { }; for (String m : matching) { String msg = "Testing [" + m + "] against [" + s1 + "]"; - assertTrue(matches(p1, m), msg); + assertThat(matches(p1, m)).as(msg).isTrue(); } for (String n : notmatching) { String msg = "Testing [" + n + "] against [" + s1 + "]"; - assertFalse(matches(p1, n), msg); + assertThat(matches(p1, n)).as(msg).isFalse(); } } @@ -254,149 +252,119 @@ void constructSimpleWildcardPattern_allWildcardPattern() { void convertISO8859_1_to_UTF_8() { String s = new String(new char[]{'a', '\u0000'}); String a = UaaStringUtils.convertISO8859_1_to_UTF_8(s); - assertEquals(s, a); - assertEquals('\u0000', a.toCharArray()[1]); - assertNull(UaaStringUtils.convertISO8859_1_to_UTF_8(null)); + assertThat(a).isEqualTo(s); + assertThat(a.toCharArray()[1]).isEqualTo('\u0000'); + assertThat(UaaStringUtils.convertISO8859_1_to_UTF_8(null)).isNull(); } @Test void retainAllMatches() { - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml.group.1", - "saml.group.2", - "saml.group1.3"), - Collections.singletonList("saml.group.1") - ), - containsInAnyOrder("saml.group.1") - ); - - - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml.group.1", - "saml.group.2", - "saml.group1.3"), - Collections.singletonList("saml.group.*") - ), - containsInAnyOrder("saml.group.1", "saml.group.2") - ); - - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml.group.1", - "saml.group.2", - "saml.group1.3", - "saml.group1.3.1"), - Collections.singletonList("saml.group*.*") - ), - containsInAnyOrder("saml.group.1", "saml.group.2", "saml.group1.3", "saml.group1.3.1") - ); - - - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml-group-1", - "saml-group-2", - "saml-group1-3"), - Collections.singletonList("saml-group-*") - ), - containsInAnyOrder("saml-group-1", "saml-group-2") - ); - - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml-group-1", - "saml-group-2", - "saml-group1-3"), - Collections.singletonList("saml-*-*") - ), - containsInAnyOrder("saml-group-1", "saml-group-2", "saml-group1-3") - ); - - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml-group-1", - "saml-group-2", - "saml-group1-3"), - Collections.singletonList("saml-*") - ), - containsInAnyOrder("saml-group-1", "saml-group-2", "saml-group1-3") - ); - - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml.group.1", - "saml.group.2", - "saml.group1.3"), - Collections.singletonList("saml.grou*.*") - ), - containsInAnyOrder("saml.group.1", "saml.group.2", "saml.group1.3") - ); - - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml.group.1", - "saml.group.2", - "saml.group1.3"), - Collections.singletonList("saml.*.1") - ), - containsInAnyOrder("saml.group.1") - ); - - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml.group.1", - "saml.group.2", - "saml.group1.3"), - Collections.singletonList("*.group.*") - ), - containsInAnyOrder("saml.group.1", "saml.group.2") - ); - - assertThat( - UaaStringUtils.retainAllMatches( - Arrays.asList("saml.group.1", - "saml.group.2", - "saml.group1.3"), - Collections.singletonList("saml.group*1*") - ), - containsInAnyOrder("saml.group.1", "saml.group1.3") - ); + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml.group.1", + "saml.group.2", + "saml.group1.3"), + Collections.singletonList("saml.group.1") + )).contains("saml.group.1"); + + + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml.group.1", + "saml.group.2", + "saml.group1.3"), + Collections.singletonList("saml.group.*") + )).contains("saml.group.1", "saml.group.2"); + + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml.group.1", + "saml.group.2", + "saml.group1.3", + "saml.group1.3.1"), + Collections.singletonList("saml.group*.*") + )).contains("saml.group.1", "saml.group.2", "saml.group1.3", "saml.group1.3.1"); + + + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml-group-1", + "saml-group-2", + "saml-group1-3"), + Collections.singletonList("saml-group-*") + )).contains("saml-group-1", "saml-group-2"); + + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml-group-1", + "saml-group-2", + "saml-group1-3"), + Collections.singletonList("saml-*-*") + )).contains("saml-group-1", "saml-group-2", "saml-group1-3"); + + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml-group-1", + "saml-group-2", + "saml-group1-3"), + Collections.singletonList("saml-*") + )).contains("saml-group-1", "saml-group-2", "saml-group1-3"); + + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml.group.1", + "saml.group.2", + "saml.group1.3"), + Collections.singletonList("saml.grou*.*") + )).contains("saml.group.1", "saml.group.2", "saml.group1.3"); + + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml.group.1", + "saml.group.2", + "saml.group1.3"), + Collections.singletonList("saml.*.1") + )).contains("saml.group.1"); + + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml.group.1", + "saml.group.2", + "saml.group1.3"), + Collections.singletonList("*.group.*") + )).contains("saml.group.1", "saml.group.2"); + + assertThat(UaaStringUtils.retainAllMatches( + Arrays.asList("saml.group.1", + "saml.group.2", + "saml.group1.3"), + Collections.singletonList("saml.group*1*") + )).contains("saml.group.1", "saml.group1.3"); } @Test void toJsonString() { - assertEquals("Y1sPgF\\\"Yj4xYZ\\\"", UaaStringUtils.toJsonString("Y1sPgF\"Yj4xYZ\"")); - assertNull(UaaStringUtils.toJsonString(null)); - assertEquals("", UaaStringUtils.toJsonString("")); + assertThat(UaaStringUtils.toJsonString("Y1sPgF\"Yj4xYZ\"")).isEqualTo("Y1sPgF\\\"Yj4xYZ\\\""); + assertThat(UaaStringUtils.toJsonString(null)).isNull(); + assertThat(UaaStringUtils.toJsonString("")).isEmpty(); } @Test void testGetAuthoritiesFromStrings() { List authorities = UaaStringUtils.getAuthoritiesFromStrings(null); - assertEquals(Collections.EMPTY_LIST, authorities); - assertEquals(0, UaaStringUtils.getStringsFromAuthorities(null).size()); + assertThat(authorities).isEqualTo(Collections.EMPTY_LIST); + assertThat(UaaStringUtils.getStringsFromAuthorities(null)).isEmpty(); authorities = UaaStringUtils.getAuthoritiesFromStrings(Collections.singletonList("uaa.user")); - assertEquals(Set.of("uaa.user"), UaaStringUtils.getStringsFromAuthorities(authorities)); + assertThat(UaaStringUtils.getStringsFromAuthorities(authorities)).isEqualTo(Set.of("uaa.user")); } @Test void getCleanedUserControlString() { - assertNull(UaaStringUtils.getCleanedUserControlString(null)); - assertEquals("test_test", UaaStringUtils.getCleanedUserControlString("test\rtest")); + assertThat(UaaStringUtils.getCleanedUserControlString(null)).isNull(); + assertThat(UaaStringUtils.getCleanedUserControlString("test\rtest")).isEqualTo("test_test"); } @Test void getHostIfArgIsURL() { - assertEquals("string", UaaStringUtils.getHostIfArgIsURL("string")); - assertEquals("host", UaaStringUtils.getHostIfArgIsURL("http://host/path")); + assertThat(UaaStringUtils.getHostIfArgIsURL("string")).isEqualTo("string"); + assertThat(UaaStringUtils.getHostIfArgIsURL("http://host/path")).isEqualTo("host"); } @Test void containsIgnoreCase() { - assertTrue(UaaStringUtils.containsIgnoreCase(Arrays.asList("one", "two"), "one")); - assertFalse(UaaStringUtils.containsIgnoreCase(Arrays.asList("one", "two"), "any")); + assertThat(UaaStringUtils.containsIgnoreCase(Arrays.asList("one", "two"), "one")).isTrue(); + assertThat(UaaStringUtils.containsIgnoreCase(Arrays.asList("one", "two"), "any")).isFalse(); } @ParameterizedTest @@ -412,57 +380,59 @@ void isNotEmpty_ShouldReturnFalse(final String input) { } @ParameterizedTest - @ValueSource(strings = { " ", " ", "\t", "\n", "abc" }) + @ValueSource(strings = {" ", " ", "\t", "\n", "abc"}) void isNullOrEmpty_ShouldReturnFalse(final String input) { Assertions.assertThat(UaaStringUtils.isNullOrEmpty(input)).isFalse(); } @Test void getMapFromProperties() { - Properties properties = new Properties(); - properties.put("pre.key", "value"); - Map objectMap = UaaStringUtils.getMapFromProperties(properties, "pre."); - assertThat(objectMap, hasEntry("key", "value")); + Properties props = new Properties(); + props.put("pre.key", "value"); + Map objectMap = UaaStringUtils.getMapFromProperties(props, "pre."); + assertThat(objectMap).containsEntry("key", "value") + .doesNotContainKey("pre.key"); } @Test void getSafeParameterValue() { - assertEquals("test", UaaStringUtils.getSafeParameterValue(new String[] {"test"})); - assertEquals("", UaaStringUtils.getSafeParameterValue(new String[] {" "})); - assertEquals("", UaaStringUtils.getSafeParameterValue(new String[] {})); - assertEquals("", UaaStringUtils.getSafeParameterValue(null)); + assertThat(UaaStringUtils.getSafeParameterValue(new String[]{"test"})).isEqualTo("test"); + assertThat(UaaStringUtils.getSafeParameterValue(new String[]{" "})).isEmpty(); + assertThat(UaaStringUtils.getSafeParameterValue(new String[]{})).isEmpty(); + assertThat(UaaStringUtils.getSafeParameterValue(null)).isEmpty(); } @Test void getArrayDefaultValue() { - assertEquals(List.of("1", "2").stream().sorted().collect(Collectors.toList()), - UaaStringUtils.getValuesOrDefaultValue(Set.of("1", "2"), "1").stream().sorted().collect(Collectors.toList())); - assertEquals(List.of("1"), UaaStringUtils.getValuesOrDefaultValue(Set.of(), "1")); - assertEquals(List.of("1"), UaaStringUtils.getValuesOrDefaultValue(null, "1")); + assertThat(UaaStringUtils.getValuesOrDefaultValue(Set.of("1", "2"), "1").stream().sorted().toList()) + .isEqualTo(Stream.of("1", "2").sorted().toList()); + assertThat(UaaStringUtils.getValuesOrDefaultValue(Set.of(), "1")).isEqualTo(List.of("1")); + assertThat(UaaStringUtils.getValuesOrDefaultValue(null, "1")).isEqualTo(List.of("1")); } @Test void validateInput() { - assertEquals("foo", UaaStringUtils.getValidatedString("foo")); + assertThat(UaaStringUtils.getValidatedString("foo")).isEqualTo("foo"); } @ParameterizedTest - @ValueSource(strings = { "\0", "", "\t", "\n", "\r" }) + @ValueSource(strings = {"\0", "", "\t", "\n", "\r"}) void alertOnInvlidInput(String input) { - assertThrows(IllegalArgumentException.class, () -> UaaStringUtils.getValidatedString(input)); + assertThatThrownBy(() -> UaaStringUtils.getValidatedString(input)) + .isInstanceOf(IllegalArgumentException.class); } private static void replaceZoneVariables(IdentityZone zone) { String s = "https://{zone.subdomain}.domain.com/z/{zone.id}?id={zone.id}&domain={zone.subdomain}"; String expect = String.format("https://%s.domain.com/z/%s?id=%s&domain=%s", zone.getSubdomain(), zone.getId(), zone.getId(), zone.getSubdomain()); - assertEquals(expect, UaaStringUtils.replaceZoneVariables(s, zone)); + assertThat(UaaStringUtils.replaceZoneVariables(s, zone)).isEqualTo(expect); } private static void checkPasswords(Map map) { for (String key : map.keySet()) { Object value = map.get(key); if (value instanceof String) { - assertEquals("#", value); + assertThat(value).isEqualTo("#"); } else if (value instanceof Map) { checkPasswords((Map) value); } @@ -474,5 +444,4 @@ private static boolean matches(String pattern, String value) { Matcher m = p.matcher(value); return m.matches(); } - -} \ No newline at end of file +} diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneTest.java index ced75d30185..45d0ad76d69 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneTest.java @@ -14,35 +14,30 @@ import java.util.Set; import java.util.stream.Stream; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.test.ModelTestUtils.getResourceAsString; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; class IdentityZoneTest { @Test void getUaa() { - Calendar calendar = Calendar.getInstance(); calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); calendar.set(Calendar.MILLISECOND, 0); Date expectedDate = calendar.getTime(); IdentityZone actual = IdentityZone.getUaa(); - - assertThat(actual.getId(), is("uaa")); - assertThat(actual.getSubdomain(), is("")); - assertThat(actual.getName(), is("uaa")); - assertThat(actual.getVersion(), is(0)); - assertThat(actual.getDescription(), is("The system zone for backwards compatibility")); - assertThat(actual.isActive(), is(true)); - assertThat(actual.getCreated(), is(expectedDate)); - assertThat(actual.getLastModified(), is(expectedDate)); - - // TODO: Validate that the config is the result of `new IdentityZoneConfiguration()` - // Currently this is not possible because not all objects have a `.equals()` method -// assertThat(actual.getConfig(), is(new IdentityZoneConfiguration())); + assertThat(actual.getId()).isEqualTo("uaa"); + assertThat(actual.getSubdomain()).isEmpty(); + assertThat(actual.getName()).isEqualTo("uaa"); + assertThat(actual.getVersion()).isZero(); + assertThat(actual.getDescription()).isEqualTo("The system zone for backwards compatibility"); + assertThat(actual.isActive()).isTrue(); + assertThat(actual.getCreated()).isEqualTo(expectedDate); + assertThat(actual.getLastModified()).isEqualTo(expectedDate); + + // Validate that the config is the result of `new IdentityZoneConfiguration()` + assertThat(actual.getConfig()).usingRecursiveComparison().isEqualTo(new IdentityZoneConfiguration()); } private static class IsUaaArgumentsSource implements ArgumentsProvider { @@ -56,23 +51,23 @@ public Stream provideArguments(ExtensionContext context) { uaa.setId("uaa"); return Stream.of( - Arguments.of(IdentityZone.getUaa(), true), - Arguments.of(uaa, true), - Arguments.of(new IdentityZone(), false), - Arguments.of(notUaa, false) + Arguments.of(IdentityZone.getUaa(), true, "true:getUaa"), + Arguments.of(uaa, true, "true:id=uaa"), + Arguments.of(new IdentityZone(), false, "false:new"), + Arguments.of(notUaa, false, "false:id=something") ); } } - @ParameterizedTest + @ParameterizedTest(name = "[{index}] {2}") @ArgumentsSource(IsUaaArgumentsSource.class) - void isUaa_usesOnlyId(IdentityZone identityZone, boolean isUaa) { - assertThat(identityZone.isUaa(), is(isUaa)); + void isUaa_usesOnlyId(IdentityZone identityZone, boolean isUaa, String ignoredMessage) { + assertThat(identityZone.isUaa()).isEqualTo(isUaa); } @Test void getUaaZoneId() { - assertThat(IdentityZone.getUaaZoneId(), is("uaa")); + assertThat(IdentityZone.getUaaZoneId()).isEqualTo("uaa"); } private static class EqualsArgumentsSource implements ArgumentsProvider { @@ -90,18 +85,21 @@ public Stream provideArguments(ExtensionContext context) { zone2.setSubdomain("subdomain"); return Stream.of( - Arguments.of(new IdentityZone(), new IdentityZone(), true), - Arguments.of(IdentityZone.getUaa(), zoneWithIdUaa, true), - Arguments.of(zone1, zone2, false) + Arguments.of(new IdentityZone(), new IdentityZone(), true, "new=new"), + Arguments.of(IdentityZone.getUaa(), zoneWithIdUaa, true, "uaa=uaa"), + Arguments.of(zone1, zone1, true, "zone1=zone1"), + Arguments.of(zone1, zone2, false, "zone1!=zone2"), + Arguments.of(zone2, zone1, false, "zone2!=zone1"), + Arguments.of(zone1, null, false, "zone1=null"), + Arguments.of(zone1, "blah", false, "zone1=string") ); } } - @ParameterizedTest + @ParameterizedTest(name = "[{index}] {3}") @ArgumentsSource(EqualsArgumentsSource.class) - void equals_usesOnlyId(IdentityZone zone1, IdentityZone zone2, boolean areEqual) { - assertThat(zone1.equals(zone2), is(areEqual)); - assertThat(zone2.equals(zone1), is(areEqual)); + void equals_usesOnlyId(IdentityZone zone1, Object zone2, boolean areEqual, String ignoredMessage) { + assertThat(zone1.equals(zone2)).isEqualTo(areEqual); } private static class HashCodeArgumentsSource implements ArgumentsProvider { @@ -111,34 +109,40 @@ public Stream provideArguments(ExtensionContext context) { IdentityZone zone1 = new IdentityZone(); zone1.setSubdomain("subdomain"); zone1.setId("asdf"); + IdentityZone nullIdZone = new IdentityZone(); + final int prime = 59; + final int nullVal = prime + 43; return Stream.of( - Arguments.of(zone1, 31 + "asdf".hashCode()), - Arguments.of(IdentityZone.getUaa(), 31 + "uaa".hashCode()) + Arguments.of(zone1, prime + "asdf".hashCode(), "asdf"), + Arguments.of(zone1, prime + "asdf".hashCode(), "asdf"), + Arguments.of(IdentityZone.getUaa(), prime + "uaa".hashCode(), "uaa"), + Arguments.of(IdentityZone.getUaa(), prime + "uaa".hashCode(), "uaa"), + Arguments.of(nullIdZone, nullVal, "null id"), + Arguments.of(nullIdZone, nullVal, "null id") ); } } - @ParameterizedTest + @ParameterizedTest(name = "[{index}] {2}") @ArgumentsSource(HashCodeArgumentsSource.class) - void hashCode_usesOnlyId(IdentityZone zone, int expectedHashCode) { - assertThat(zone.hashCode(), is(expectedHashCode)); + void hashCode_usesOnlyId(IdentityZone zone, int expectedHashCode, String ignoredMessage) { + assertThat(zone.hashCode()).isEqualTo(expectedHashCode); } @Test void deserialize() { final String sampleIdentityZoneJson = getResourceAsString(getClass(), "SampleIdentityZone.json"); IdentityZone sampleIdentityZone = JsonUtils.readValue(sampleIdentityZoneJson, IdentityZone.class); - assertEquals("f7758816-ab47-48d9-9d24-25b10b92d4cc", sampleIdentityZone.getId()); - assertEquals("demo", sampleIdentityZone.getSubdomain()); - assertEquals(List.of("openid", "password.write", "uaa.user", "approvals.me", - "profile", "roles", "user_attributes", "uaa.offline_token"), - sampleIdentityZone.getConfig().getUserConfig().getDefaultGroups()); - assertEquals(Set.of("openid", "password.write", "uaa.user", "approvals.me", - "profile", "roles", "user_attributes", "uaa.offline_token", - "scim.me", "cloud_controller.user"), - sampleIdentityZone.getConfig().getUserConfig().resultingAllowedGroups()); - assertEquals(1000, sampleIdentityZone.getConfig().getUserConfig().getMaxUsers()); - assertEquals(true, sampleIdentityZone.getConfig().getUserConfig().isCheckOriginEnabled()); + assertThat(sampleIdentityZone).isNotNull() + .returns("f7758816-ab47-48d9-9d24-25b10b92d4cc", IdentityZone::getId) + .returns("demo", IdentityZone::getSubdomain); + assertThat(sampleIdentityZone.getConfig().getUserConfig().getDefaultGroups()).isEqualTo(List.of("openid", "password.write", "uaa.user", "approvals.me", + "profile", "roles", "user_attributes", "uaa.offline_token")); + assertThat(sampleIdentityZone.getConfig().getUserConfig().resultingAllowedGroups()).isEqualTo(Set.of("openid", "password.write", "uaa.user", "approvals.me", + "profile", "roles", "user_attributes", "uaa.offline_token", + "scim.me", "cloud_controller.user")); + assertThat(sampleIdentityZone.getConfig().getUserConfig().getMaxUsers()).isEqualTo(1000); + assertThat(sampleIdentityZone.getConfig().getUserConfig().isCheckOriginEnabled()).isTrue(); } } \ No newline at end of file diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java index b8ef75882dd..1926646195e 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java @@ -16,160 +16,227 @@ import org.cloudfoundry.identity.uaa.saml.SamlKey; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Collections; import java.util.Map; -import static java.util.Collections.EMPTY_MAP; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.cloudfoundry.identity.uaa.zone.SamlConfig.LEGACY_KEY_ID; -import static org.junit.Assert.*; - -public class SamlConfigTest { - - - @Rule - public ExpectedException exception = ExpectedException.none(); - - String oldJson = - "{\n" + - " \"assertionSigned\": true,\n" + - " \"assertionTimeToLiveSeconds\": 600,\n" + - " \"certificate\": \"-----BEGIN CERTIFICATE-----\\nMIID4zCCA0ygAwIBAgIJAJdmwmBdhEydMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD\\nVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xJzAl\\nBgNVBAoTHkNsb3VkIEZvdW5kcnkgRm91bmRhdGlvbiwgSW5jLjEMMAoGA1UECxMD\\nVUFBMRIwEAYDVQQDEwlsb2NhbGhvc3QxKTAnBgkqhkiG9w0BCQEWGmNmLWlkZW50\\naXR5LWVuZ0BwaXZvdGFsLmlvMB4XDTE2MDIxNjIyMTMzN1oXDTE2MDMxNzIyMTMz\\nN1owgagxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEnMCUGA1UEChMeQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMu\\nMQwwCgYDVQQLEwNVQUExEjAQBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJ\\nARYaY2YtaWRlbnRpdHktZW5nQHBpdm90YWwuaW8wgZ8wDQYJKoZIhvcNAQEBBQAD\\ngY0AMIGJAoGBAKmeo9CIMJ8ljWFVpBRkbpGzVZ3cWY/URK03vWFd5c4uiDme+lof\\njk/e/v0Qalo7Tq8fmpK7/GvqRBEE4DiH06pcZLvYEZAEfyMw0KgeqAmsgANBMdcf\\nzlFgXfxsfphynXyNyHQpWZjAp6Jos18wOeCcC/rAwM40nPvrUYG2sbX/AgMBAAGj\\nggERMIIBDTAdBgNVHQ4EFgQUdiixDfiZ61ljk7J/uUYcay26n5swgd0GA1UdIwSB\\n1TCB0oAUdiixDfiZ61ljk7J/uUYcay26n5uhga6kgaswgagxCzAJBgNVBAYTAlVT\\nMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEnMCUGA1UEChMe\\nQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMuMQwwCgYDVQQLEwNVQUExEjAQ\\nBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJARYaY2YtaWRlbnRpdHktZW5n\\nQHBpdm90YWwuaW+CCQCXZsJgXYRMnTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\\nBQUAA4GBAAPf/SPl/LuVYrl0HDUU8YDR3N7Fi4OjhF3+n+uBYRhO+9IbQ/t1sC1p\\nenWhiAfyZtgFv2OmjvtFyty9YqHhIPAg9Ceod37Q7HNSG04vbYHNJ6XhGUzacMj8\\nhQ1ZzQBv+CaKWZarBIql/TsxtpvvXhaE4QqR4NvUDnESHtxefriv\\n-----END CERTIFICATE-----\\n\",\n" + - " \"privateKey\": \"-----BEGIN RSA PRIVATE KEY-----\\nMIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa\\nH45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX\\nH85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB\\nAoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp\\noUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o\\nXDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9\\nvuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW\\n2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W\\n2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA\\noVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9\\n0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx\\ndFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U\\nOw3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4=\\n-----END RSA PRIVATE KEY-----\\n\",\n" + - " \"privateKeyPassword\": \"password\",\n" + - " \"requestSigned\": true,\n" + - " \"wantAssertionSigned\": true,\n" + - " \"wantAuthnRequestSigned\": false\n" + - "}"; - - String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa\n" + - "H45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX\n" + - "H85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB\n" + - "AoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp\n" + - "oUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o\n" + - "XDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9\n" + - "vuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW\n" + - "2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W\n" + - "2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA\n" + - "oVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9\n" + - "0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx\n" + - "dFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U\n" + - "Ow3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4=\n" + - "-----END RSA PRIVATE KEY-----\n"; + +class SamlConfigTest { + + String oldJson = """ + { + "assertionSigned": true, + "assertionTimeToLiveSeconds": 600, + "certificate": "-----BEGIN CERTIFICATE-----\\nMIID4zCCA0ygAwIBAgIJAJdmwmBdhEydMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD\\nVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xJzAl\\nBgNVBAoTHkNsb3VkIEZvdW5kcnkgRm91bmRhdGlvbiwgSW5jLjEMMAoGA1UECxMD\\nVUFBMRIwEAYDVQQDEwlsb2NhbGhvc3QxKTAnBgkqhkiG9w0BCQEWGmNmLWlkZW50\\naXR5LWVuZ0BwaXZvdGFsLmlvMB4XDTE2MDIxNjIyMTMzN1oXDTE2MDMxNzIyMTMz\\nN1owgagxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEnMCUGA1UEChMeQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMu\\nMQwwCgYDVQQLEwNVQUExEjAQBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJ\\nARYaY2YtaWRlbnRpdHktZW5nQHBpdm90YWwuaW8wgZ8wDQYJKoZIhvcNAQEBBQAD\\ngY0AMIGJAoGBAKmeo9CIMJ8ljWFVpBRkbpGzVZ3cWY/URK03vWFd5c4uiDme+lof\\njk/e/v0Qalo7Tq8fmpK7/GvqRBEE4DiH06pcZLvYEZAEfyMw0KgeqAmsgANBMdcf\\nzlFgXfxsfphynXyNyHQpWZjAp6Jos18wOeCcC/rAwM40nPvrUYG2sbX/AgMBAAGj\\nggERMIIBDTAdBgNVHQ4EFgQUdiixDfiZ61ljk7J/uUYcay26n5swgd0GA1UdIwSB\\n1TCB0oAUdiixDfiZ61ljk7J/uUYcay26n5uhga6kgaswgagxCzAJBgNVBAYTAlVT\\nMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEnMCUGA1UEChMe\\nQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMuMQwwCgYDVQQLEwNVQUExEjAQ\\nBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJARYaY2YtaWRlbnRpdHktZW5n\\nQHBpdm90YWwuaW+CCQCXZsJgXYRMnTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\\nBQUAA4GBAAPf/SPl/LuVYrl0HDUU8YDR3N7Fi4OjhF3+n+uBYRhO+9IbQ/t1sC1p\\nenWhiAfyZtgFv2OmjvtFyty9YqHhIPAg9Ceod37Q7HNSG04vbYHNJ6XhGUzacMj8\\nhQ1ZzQBv+CaKWZarBIql/TsxtpvvXhaE4QqR4NvUDnESHtxefriv\\n-----END CERTIFICATE-----\\n", + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\\nMIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa\\nH45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX\\nH85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB\\nAoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp\\noUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o\\nXDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9\\nvuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW\\n2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W\\n2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA\\noVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9\\n0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx\\ndFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U\\nOw3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4=\\n-----END RSA PRIVATE KEY-----\\n", + "privateKeyPassword": "password", + "requestSigned": true, + "wantAssertionSigned": true, + "wantAuthnRequestSigned": false + }\ + """; + + String privateKey = """ + -----BEGIN RSA PRIVATE KEY----- + MIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa + H45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX + H85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB + AoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp + oUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o + XDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9 + vuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW + 2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W + 2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA + oVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9 + 0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx + dFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U + Ow3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4= + -----END RSA PRIVATE KEY----- + """; String passphrase = "password"; - String certificate = "-----BEGIN CERTIFICATE-----\n" + - "MIID4zCCA0ygAwIBAgIJAJdmwmBdhEydMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD\n" + - "VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xJzAl\n" + - "BgNVBAoTHkNsb3VkIEZvdW5kcnkgRm91bmRhdGlvbiwgSW5jLjEMMAoGA1UECxMD\n" + - "VUFBMRIwEAYDVQQDEwlsb2NhbGhvc3QxKTAnBgkqhkiG9w0BCQEWGmNmLWlkZW50\n" + - "aXR5LWVuZ0BwaXZvdGFsLmlvMB4XDTE2MDIxNjIyMTMzN1oXDTE2MDMxNzIyMTMz\n" + - "N1owgagxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZy\n" + - "YW5jaXNjbzEnMCUGA1UEChMeQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMu\n" + - "MQwwCgYDVQQLEwNVQUExEjAQBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJ\n" + - "ARYaY2YtaWRlbnRpdHktZW5nQHBpdm90YWwuaW8wgZ8wDQYJKoZIhvcNAQEBBQAD\n" + - "gY0AMIGJAoGBAKmeo9CIMJ8ljWFVpBRkbpGzVZ3cWY/URK03vWFd5c4uiDme+lof\n" + - "jk/e/v0Qalo7Tq8fmpK7/GvqRBEE4DiH06pcZLvYEZAEfyMw0KgeqAmsgANBMdcf\n" + - "zlFgXfxsfphynXyNyHQpWZjAp6Jos18wOeCcC/rAwM40nPvrUYG2sbX/AgMBAAGj\n" + - "ggERMIIBDTAdBgNVHQ4EFgQUdiixDfiZ61ljk7J/uUYcay26n5swgd0GA1UdIwSB\n" + - "1TCB0oAUdiixDfiZ61ljk7J/uUYcay26n5uhga6kgaswgagxCzAJBgNVBAYTAlVT\n" + - "MQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEnMCUGA1UEChMe\n" + - "Q2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMuMQwwCgYDVQQLEwNVQUExEjAQ\n" + - "BgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJARYaY2YtaWRlbnRpdHktZW5n\n" + - "QHBpdm90YWwuaW+CCQCXZsJgXYRMnTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\n" + - "BQUAA4GBAAPf/SPl/LuVYrl0HDUU8YDR3N7Fi4OjhF3+n+uBYRhO+9IbQ/t1sC1p\n" + - "enWhiAfyZtgFv2OmjvtFyty9YqHhIPAg9Ceod37Q7HNSG04vbYHNJ6XhGUzacMj8\n" + - "hQ1ZzQBv+CaKWZarBIql/TsxtpvvXhaE4QqR4NvUDnESHtxefriv\n" + - "-----END CERTIFICATE-----\n"; + String certificate = """ + -----BEGIN CERTIFICATE----- + MIID4zCCA0ygAwIBAgIJAJdmwmBdhEydMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD + VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xJzAl + BgNVBAoTHkNsb3VkIEZvdW5kcnkgRm91bmRhdGlvbiwgSW5jLjEMMAoGA1UECxMD + VUFBMRIwEAYDVQQDEwlsb2NhbGhvc3QxKTAnBgkqhkiG9w0BCQEWGmNmLWlkZW50 + aXR5LWVuZ0BwaXZvdGFsLmlvMB4XDTE2MDIxNjIyMTMzN1oXDTE2MDMxNzIyMTMz + N1owgagxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZy + YW5jaXNjbzEnMCUGA1UEChMeQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMu + MQwwCgYDVQQLEwNVQUExEjAQBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJ + ARYaY2YtaWRlbnRpdHktZW5nQHBpdm90YWwuaW8wgZ8wDQYJKoZIhvcNAQEBBQAD + gY0AMIGJAoGBAKmeo9CIMJ8ljWFVpBRkbpGzVZ3cWY/URK03vWFd5c4uiDme+lof + jk/e/v0Qalo7Tq8fmpK7/GvqRBEE4DiH06pcZLvYEZAEfyMw0KgeqAmsgANBMdcf + zlFgXfxsfphynXyNyHQpWZjAp6Jos18wOeCcC/rAwM40nPvrUYG2sbX/AgMBAAGj + ggERMIIBDTAdBgNVHQ4EFgQUdiixDfiZ61ljk7J/uUYcay26n5swgd0GA1UdIwSB + 1TCB0oAUdiixDfiZ61ljk7J/uUYcay26n5uhga6kgaswgagxCzAJBgNVBAYTAlVT + MQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEnMCUGA1UEChMe + Q2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMuMQwwCgYDVQQLEwNVQUExEjAQ + BgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJARYaY2YtaWRlbnRpdHktZW5n + QHBpdm90YWwuaW+CCQCXZsJgXYRMnTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB + BQUAA4GBAAPf/SPl/LuVYrl0HDUU8YDR3N7Fi4OjhF3+n+uBYRhO+9IbQ/t1sC1p + enWhiAfyZtgFv2OmjvtFyty9YqHhIPAg9Ceod37Q7HNSG04vbYHNJ6XhGUzacMj8 + hQ1ZzQBv+CaKWZarBIql/TsxtpvvXhaE4QqR4NvUDnESHtxefriv + -----END CERTIFICATE----- + """; SamlConfig config; - @Before + @BeforeEach public void setUp() { config = new SamlConfig(); } @Test - public void testIsRequestSigned() { - assertTrue(config.isRequestSigned()); + void testIsRequestSigned() { + assertThat(config.isRequestSigned()).isTrue(); } @Test - public void legacy_key_is_part_of_map() { + void legacy_key_is_part_of_map() { config.setPrivateKey(privateKey); config.setPrivateKeyPassword(passphrase); config.setCertificate(certificate); Map keys = config.getKeys(); - assertEquals(1, keys.size()); - assertNotNull(keys.get(LEGACY_KEY_ID)); - assertEquals(privateKey, keys.get(LEGACY_KEY_ID).getKey()); - assertEquals(passphrase, keys.get(LEGACY_KEY_ID).getPassphrase()); - assertEquals(certificate, keys.get(LEGACY_KEY_ID).getCertificate()); + assertThat(keys).containsOnlyKeys(LEGACY_KEY_ID); + assertThat(keys.get(LEGACY_KEY_ID).getKey()).isEqualTo(privateKey); + assertThat(keys.get(LEGACY_KEY_ID).getPassphrase()).isEqualTo(passphrase); + assertThat(keys.get(LEGACY_KEY_ID).getCertificate()).isEqualTo(certificate); } @Test - public void addActiveKey() { + void addActiveKey() { SamlKey key = new SamlKey(privateKey, passphrase, certificate); String keyId = "testKeyId"; config.addAndActivateKey(keyId, key); Map keys = config.getKeys(); - assertNotNull(keys); - assertEquals(1, keys.size()); - assertEquals(keyId, config.getActiveKeyId()); - assertNotNull(keys.get(keyId)); - assertEquals(privateKey, keys.get(keyId).getKey()); - assertEquals(passphrase, keys.get(keyId).getPassphrase()); - assertEquals(certificate, keys.get(keyId).getCertificate()); + assertThat(keys).hasSize(1) + .containsKey(keyId); + assertThat(config.getActiveKeyId()).isEqualTo(keyId); + assertThat(keys.get(keyId)).returns(privateKey, SamlKey::getKey) + .returns(passphrase, SamlKey::getPassphrase) + .returns(certificate, SamlKey::getCertificate); + assertThat(config.getActiveKey()).isSameAs(keys.get(keyId)); + assertThat(config.getKeyList()).hasSize(1).containsExactly(key); } @Test - public void addNonActive() { + void addNonActive() { addActiveKey(); SamlKey key = new SamlKey(privateKey, passphrase, certificate); String keyId = "nonActiveKeyId"; config.addKey(keyId, key); Map keys = config.getKeys(); - assertNotNull(keys); - assertEquals(2, keys.size()); - assertNotEquals(keyId, config.getActiveKeyId()); - assertNotNull(keys.get(keyId)); - assertEquals(privateKey, keys.get(keyId).getKey()); - assertEquals(passphrase, keys.get(keyId).getPassphrase()); - assertEquals(certificate, keys.get(keyId).getCertificate()); + assertThat(keys).hasSize(2) + .containsKey(keyId); + assertThat(config.getActiveKeyId()).isNotEqualTo(keyId); + assertThat(keys.get(keyId)).returns(privateKey, SamlKey::getKey) + .returns(passphrase, SamlKey::getPassphrase) + .returns(certificate, SamlKey::getCertificate); + } + + @Test + void getKeyList() { + // Default is empty + assertThat(config.getKeyList()).isEmpty(); + + // Add active key, should only have that key + addActiveKey(); + SamlKey activeKey = config.getActiveKey(); + assertThat(config.getKeyList()).containsExactly(activeKey); + + // Add another key, should have both keys + SamlKey nonActiveKey = new SamlKey(privateKey, passphrase, certificate); + String nonActiveKeyId = "nonActiveKeyId"; + config.addKey(nonActiveKeyId, nonActiveKey); + assertThat(config.getKeyList()).containsExactly(activeKey, nonActiveKey); + + // add another active key, should have the new key first + SamlKey otherActiveKey = new SamlKey(privateKey, passphrase, certificate); + config.addAndActivateKey("anotherActiveKeyId", otherActiveKey); + assertThat(config.getKeyList()).hasSize(3).first().isSameAs(otherActiveKey); + + // remove the non-active key, should have other 2 keys + config.removeKey(nonActiveKeyId); + assertThat(config.getKeyList()).containsExactly(otherActiveKey, activeKey); + + // drop the current active key, should have only the remaining key... even though it is not active + config.removeKey("anotherActiveKeyId"); + assertThat(config.getActiveKey()).isNull(); + assertThat(config.getKeys()).hasSize(1); + assertThat(config.getKeyList()).containsExactly(activeKey); } @Test - public void map_is_not_null_by_default() { + void map_is_not_null_by_default() { Map keys = config.getKeys(); - assertNotNull(keys); - assertEquals(0, keys.size()); - assertNull(config.getActiveKeyId()); + assertThat(keys).isEmpty(); + assertThat(config.getActiveKeyId()).isNull(); } @Test - public void testIsWantAssertionSigned() { - assertTrue(config.isWantAssertionSigned()); + void testIsWantAssertionSigned() { + assertThat(config.isWantAssertionSigned()).isTrue(); } @Test - public void testSetKeyAndCert() { + void testSetKeyAndCert() { + // Default values are null + assertThat(config).returns(null, SamlConfig::getPrivateKey) + .returns(null, SamlConfig::getPrivateKeyPassword) + .returns(null, SamlConfig::getCertificate) + .extracting(SamlConfig::getActiveKey) + .isNull(); + + // Set values to null, does not create a key + config.setPrivateKey(null); + config.setPrivateKeyPassword(null); + config.setCertificate(null); + assertThat(config).returns(null, SamlConfig::getPrivateKey) + .returns(null, SamlConfig::getPrivateKeyPassword) + .returns(null, SamlConfig::getCertificate) + .extracting(SamlConfig::getActiveKey) + .isNull(); + + // Set values to non-null, creates a key object config.setPrivateKey(privateKey); config.setPrivateKeyPassword(passphrase); config.setCertificate(certificate); - assertEquals(privateKey, config.getPrivateKey()); - assertEquals(passphrase, config.getPrivateKeyPassword()); + assertThat(config).returns(privateKey, SamlConfig::getPrivateKey) + .returns(passphrase, SamlConfig::getPrivateKeyPassword) + .returns(certificate, SamlConfig::getCertificate) + .extracting(SamlConfig::getActiveKey) + .isNotNull() + .returns(privateKey, SamlKey::getKey) + .returns(certificate, SamlKey::getCertificate) + .returns(passphrase, SamlKey::getPassphrase); + + // Set values to null, retains the key object with nulls + config.setPrivateKey(null); + config.setPrivateKeyPassword(null); + config.setCertificate(null); + assertThat(config).returns(null, SamlConfig::getPrivateKey) + .returns(null, SamlConfig::getPrivateKeyPassword) + .returns(null, SamlConfig::getCertificate) + .extracting(SamlConfig::getActiveKey) + .isNotNull() + .returns(null, SamlKey::getKey) + .returns(null, SamlKey::getCertificate) + .returns(null, SamlKey::getPassphrase); } @Test - public void read_old_json_works() { + void read_old_json_works() { read_json(oldJson); - assertEquals(privateKey, config.getPrivateKey()); - assertEquals(passphrase, config.getPrivateKeyPassword()); - assertEquals(certificate, config.getCertificate()); + assertThat(config).returns(privateKey, SamlConfig::getPrivateKey) + .returns(passphrase, SamlConfig::getPrivateKeyPassword) + .returns(certificate, SamlConfig::getCertificate); } public void read_json(String json) { @@ -177,33 +244,31 @@ public void read_json(String json) { } @Test - public void to_json_ignores_legacy_values() { + void to_json_ignores_legacy_values() { read_json(oldJson); String json = JsonUtils.writeValueAsString(config); read_json(json); - assertEquals(privateKey, config.getPrivateKey()); - assertEquals(passphrase, config.getPrivateKeyPassword()); - assertEquals(certificate, config.getCertificate()); + assertThat(config).returns(privateKey, SamlConfig::getPrivateKey) + .returns(passphrase, SamlConfig::getPrivateKeyPassword) + .returns(certificate, SamlConfig::getCertificate); } @Test - public void keys_are_not_modifiable() { + void keys_are_not_modifiable() { read_json(oldJson); - exception.expect(UnsupportedOperationException.class); - config.getKeys().clear(); + Map keys = config.getKeys(); + assertThatThrownBy(keys::clear).isInstanceOf(UnsupportedOperationException.class); } @Test - public void can_clear_keys() { + void can_clear_keys() { read_json(oldJson); - assertEquals(1, config.getKeys().size()); - assertNotNull(config.getActiveKeyId()); - config.setKeys(EMPTY_MAP); - assertEquals(0, config.getKeys().size()); - assertNull(config.getActiveKeyId()); + assertThat(config.getKeys()).hasSize(1); + assertThat(config.getActiveKeyId()).isNotNull(); + assertThat(config.getActiveKey()).isNotNull(); + config.setKeys(Collections.emptyMap()); + assertThat(config.getKeys()).isEmpty(); + assertThat(config.getActiveKeyId()).isNull(); + assertThat(config.getActiveKey()).isNull(); } - - - - -} +} \ No newline at end of file diff --git a/samples/api/src/test/java/org/cloudfoundry/identity/api/web/ApiControllerTests.java b/samples/api/src/test/java/org/cloudfoundry/identity/api/web/ApiControllerTests.java index e62879e55c4..fbe5fa69468 100644 --- a/samples/api/src/test/java/org/cloudfoundry/identity/api/web/ApiControllerTests.java +++ b/samples/api/src/test/java/org/cloudfoundry/identity/api/web/ApiControllerTests.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -14,11 +14,6 @@ package org.cloudfoundry.identity.api.web; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.HashMap; - import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.Test; import org.springframework.core.io.ClassPathResource; @@ -27,24 +22,27 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.web.servlet.View; +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; + /** * @author Dave Syer - * */ public class ApiControllerTests { - private ApiController controller = new ApiController(); + private final ApiController controller = new ApiController(); UaaTestAccounts testAccounts = UaaTestAccounts.standard(null); @Test public void testNoUser() throws Exception { controller.setInfo(new ClassPathResource("info.tmpl")); - HashMap model = new HashMap(); + HashMap model = new HashMap<>(); View view = controller.info(model, null); MockHttpServletResponse response = new MockHttpServletResponse(); view.render(model, new MockHttpServletRequest(), response); String content = response.getContentAsString(); - assertFalse("Wrong content: " + content, content.contains("\"user\"")); + assertThat(content).as("Wrong content: " + content).doesNotContain("\"user\""); } @Test @@ -55,7 +53,7 @@ public void testWithUser() throws Exception { MockHttpServletResponse response = new MockHttpServletResponse(); view.render(model, new MockHttpServletRequest(), response); String content = response.getContentAsString(); - assertTrue("Wrong content: " + content, content.contains("\n \"user\": \""+testAccounts.getUserName()+"\"")); + assertThat(content).as("Wrong content: " + content).contains("\n \"user\": \"" + testAccounts.getUserName() + "\""); } } diff --git a/scripts/count_disabled_tests.sh b/scripts/count_disabled_tests.sh new file mode 100755 index 00000000000..2ee89973e65 --- /dev/null +++ b/scripts/count_disabled_tests.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# +# Gives counts of Disabled Unit/Integration tests in the project +# Usage: count_disabled_tests.sh [-l] +# -l: List the disabled/ignored tests + +####################################### +# main +# Arguments: +# 1 - flag to list the disabled/ignored tests +####################################### +function main() { + local temp_file + local search_for + local total + local unit_tests_count + local integration_tests_count + + temp_file=$(mktemp) + search_for='Disabled' + find . -type f \( ! -wholename '*/target/*' ! -wholename '*/scripts/*' ! -wholename './node_modules/*' ! -wholename '*/tmp/*' ! -wholename './out/*' ! -wholename '*/.gradle/*' ! -wholename '*/build/*' ! -wholename './.idea/*' ! -wholename './.git/*' \) -exec grep -H -A 1 "@$search_for" {} \; \ + | sed -e "s/^\.\///" \ + | sed "/^--$/d; /\@${search_for}/d" >"$temp_file" + + total=$(wc -l <"$temp_file") + unit_tests_count=$(cat "$temp_file" | grep -v "IT.java" | wc -l) + integration_tests_count=$(cat "$temp_file" | grep "IT.java" | wc -l) + echo "Unit Tests: $unit_tests_count" + echo "Integration Tests: $integration_tests_count" + echo "Total: $total" + + if [[ "$1" == "-l" ]]; then + echo + echo Unit Tests: + echo + grep -v "IT.java" "$temp_file" | sed -e 's/\.java-/,/' | sort + + echo + echo Integration Tests: + echo + grep "IT.java" "$temp_file" | sed -e 's/\.java-/,/' | sort + fi + + rm "$temp_file" +} + +main "$@" diff --git a/scripts/create_test_providers.sh b/scripts/create_test_providers.sh new file mode 100755 index 00000000000..3b7eab60108 --- /dev/null +++ b/scripts/create_test_providers.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +if [ "${1}" == "-h" ]; then + echo "USAGE: $0 [-h] [-d] + -h Show this help + -d Delete the created identity zones + No arguments creates identity providers for default zone. + " + exit 0 +fi + +if [ "${1}" == "-d" ]; then + echo "Deleting identity providers" + echo + + AT=$(uaac context | grep access_token | sed 's/.*://') + curl 'http://localhost:8080/uaa/identity-providers/cc2d4b27-f789-4501-9aaa-4bbbec4f0f3d' -i -X DELETE -H "Authorization: Bearer $AT" + exit 0 +fi + +uaac target http://localhost:8080/uaa +uaac token client get admin -s adminsecret +AT=$(uaac context | grep access_token | sed 's/.*://') + +# Add Redirect Binding IDP to Default Zone +curl 'http://localhost:8080/uaa/identity-providers?rawConfig=true' -i -X POST \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $AT" \ + -d '{ + "type" : "saml", + "config" : { + "externalGroupsWhitelist" : [ ], + "addShadowUserOnLogin" : true, + "storeCustomAttributes" : true, + "metaDataLocation" : "http://simplesamlphp.uaa-acceptance.cf-app.com/saml2/idp/metadata.php", + "assertionConsumerIndex" : 0, + "metadataTrustCheck" : true, + "showSamlLink" : true, + "linkText" : "SAML-PHP redirect-binding", + "iconUrl" : null, + "skipSslValidation" : true, + "authnContext" : null, + "socketFactoryClassName" : null + }, + "originKey" : "default-redirect-binding", + "name" : "default-redirect-binding", + "active" : true +}' + +# Add Post Binding IDP to Default Zone +curl 'http://localhost:8080/uaa/identity-providers?rawConfig=true' -i -X POST \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $AT" \ + -d '{ + "type" : "saml", + "config" : { + "externalGroupsWhitelist" : [ ], + "addShadowUserOnLogin" : true, + "storeCustomAttributes" : true, + "metaDataLocation" : "https://dev-73893672.okta.com/app/exk9ojp48mcTeKG9t5d7/sso/saml/metadata", + "assertionConsumerIndex" : 1, + "metadataTrustCheck" : true, + "showSamlLink" : true, + "linkText" : "Okta post-binding SAML", + "iconUrl" : null, + "skipSslValidation" : true, + "authnContext" : null, + "socketFactoryClassName" : null + }, + "originKey" : "default-post-binding", + "name" : "default-post-binding", + "active" : true +}' diff --git a/scripts/create_test_zones.sh b/scripts/create_test_zones.sh new file mode 100755 index 00000000000..758ff4394fd --- /dev/null +++ b/scripts/create_test_zones.sh @@ -0,0 +1,185 @@ +#!/bin/bash + +if [ "${1}" == "-h" ]; then + echo "USAGE: $0 [-h] [-d] + -h Show this help + -d Delete the created identity zones + No arguments creates the identity zones and identity providers for testzone1 and testzone2. + testzone1 has a zone entity id set, testzone2 does not. + " + exit 0 +fi + +port=${PORT:-8080} +uaac target http://localhost:${port}/uaa +uaac token client get admin -s adminsecret +AT=$(uaac context | grep access_token | sed 's/.*://') + +if [ "${1}" == "-d" ]; then + echo "Deleting identity zones" + echo + + curl http://localhost:${port}/uaa/identity-zones/testzone1 -i -X DELETE -H "Authorization: Bearer $AT" + curl http://localhost:${port}/uaa/identity-zones/testzone2 -i -X DELETE -H "Authorization: Bearer $AT" + + exit 0 +fi + +# Create TestZone1 +curl http://localhost:${port}/uaa/identity-zones -i -X POST \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $AT" \ + -d '{ + "id" : "testzone1", + "subdomain" : "testzone1", + "config" : { + "clientSecretPolicy" : { + "minLength" : -1, + "maxLength" : -1, + "requireUpperCaseCharacter" : -1, + "requireLowerCaseCharacter" : -1, + "requireDigit" : -1, + "requireSpecialCharacter" : -1 + }, + "samlConfig" : { + "assertionSigned" : true, + "requestSigned" : true, + "wantAssertionSigned" : true, + "wantAuthnRequestSigned" : false, + "assertionTimeToLiveSeconds" : 600, + "entityID" : "testzone1.cloudfoundry-saml-login", + "disableInResponseToCheck" : false + }, + "corsPolicy" : { + "xhrConfiguration" : { + "allowedOrigins" : [ ".*" ], + "allowedOriginPatterns" : [ ], + "allowedUris" : [ ".*" ], + "allowedUriPatterns" : [ ], + "allowedHeaders" : [ "Accept", "Authorization", "Content-Type" ], + "allowedMethods" : [ "GET", "POST"], + "allowedCredentials" : false, + "maxAge" : 1728000 + }, + "defaultConfiguration" : { + "allowedOrigins" : [ ".*" ], + "allowedOriginPatterns" : [ ], + "allowedUris" : [ ".*" ], + "allowedUriPatterns" : [ ], + "allowedHeaders" : [ "Accept", "Authorization", "Content-Type" ], + "allowedMethods" : [ "GET", "POST"], + "allowedCredentials" : false, + "maxAge" : 1728000 + } + } + }, + "name" : "テストゾーン 1" +}' + +# Add IDP to TestZone1 +curl http://localhost:${port}/uaa/identity-providers?rawConfig=true -i -X POST \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $AT" \ + -H 'X-Identity-Zone-Id: testzone1' \ + -d '{ + "type" : "saml", + "config" : { + "externalGroupsWhitelist" : [ ], + "addShadowUserOnLogin" : true, + "storeCustomAttributes" : true, + "metaDataLocation" : "http://simplesamlphp.uaa-acceptance.cf-app.com/saml2/idp/metadata.php", + "assertionConsumerIndex" : 0, + "metadataTrustCheck" : true, + "showSamlLink" : true, + "linkText" : "テストゾーン 1 SAML", + "iconUrl" : null, + "skipSslValidation" : true, + "authnContext" : null, + "socketFactoryClassName" : null + }, + "originKey" : "testzone1-saml", + "name" : "testzone1 SAML IdP", + "active" : true +}' + +# Create Test Zone 2, has no entity id set in the saml config +curl http://localhost:${port}/uaa/identity-zones -i -X POST \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $AT" \ + -d '{ + "id" : "testzone2", + "subdomain" : "testzone2", + "config" : { + "clientSecretPolicy" : { + "minLength" : -1, + "maxLength" : -1, + "requireUpperCaseCharacter" : -1, + "requireLowerCaseCharacter" : -1, + "requireDigit" : -1, + "requireSpecialCharacter" : -1 + }, + "samlConfig" : { + "assertionSigned" : false, + "requestSigned" : false, + "wantAssertionSigned" : false, + "wantAuthnRequestSigned" : true, + "assertionTimeToLiveSeconds" : 1600, + "disableInResponseToCheck" : true + }, + "corsPolicy" : { + "xhrConfiguration" : { + "allowedOrigins" : [ ".*" ], + "allowedOriginPatterns" : [ ], + "allowedUris" : [ ".*" ], + "allowedUriPatterns" : [ ], + "allowedHeaders" : [ "Accept", "Authorization", "Content-Type" ], + "allowedMethods" : [ "GET", "POST"], + "allowedCredentials" : false, + "maxAge" : 1728000 + }, + "defaultConfiguration" : { + "allowedOrigins" : [ ".*" ], + "allowedOriginPatterns" : [ ], + "allowedUris" : [ ".*" ], + "allowedUriPatterns" : [ ], + "allowedHeaders" : [ "Accept", "Authorization", "Content-Type" ], + "allowedMethods" : [ "GET", "POST"], + "allowedCredentials" : false, + "maxAge" : 1728000 + } + } + }, + "name" : "テストゾーン 2" +}' + +# Add IDP to TestZone2 +curl http://localhost:${port}/uaa/identity-providers?rawConfig=true -i -X POST \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $AT" \ + -H 'X-Identity-Zone-Id: testzone2' \ + -d '{ + "type" : "saml", + "config" : { + "externalGroupsWhitelist" : [ ], + "addShadowUserOnLogin" : true, + "storeCustomAttributes" : true, + "metaDataLocation" : "http://simplesamlphp.uaa-acceptance.cf-app.com/saml2/idp/metadata.php", + "assertionConsumerIndex" : 0, + "metadataTrustCheck" : true, + "showSamlLink" : true, + "linkText" : "テストゾーン 2 SAML", + "iconUrl" : null, + "skipSslValidation" : true, + "authnContext" : null, + "socketFactoryClassName" : null + }, + "originKey" : "testzone2-saml", + "name" : "testzone2 SAML IdP", + "active" : true +}' + +echo +echo Run these commands to get the metadata: +echo http :${port}/uaa/saml/metadata +echo http testzone1.localhost:${port}/uaa/saml/metadata +echo http testzone2.localhost:${port}/uaa/saml/metadata diff --git a/scripts/debug_uaa.sh b/scripts/debug_uaa.sh new file mode 100755 index 00000000000..6188171ef25 --- /dev/null +++ b/scripts/debug_uaa.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -eu -o pipefail +export ORG_GRADLE_PROJECT_port=${PORT:-8080} +echo "PORT: ${ORG_GRADLE_PROJECT_port}" + +if [ "${1:-}" == "-h" ]; then + echo USAGE: $0 [-h] [-s] [args] + echo "Run UAA in debug mode" + echo " -h: help" + echo " -s: suspend startup for debugging" + echo " -r: run UAA without debug mode" + exit 0 +fi + +DEBUG_FLAG="-Dxdebug=true" +if [ "${1:-}" == "-s" ]; then + DEBUG_FLAG="-Dxdebugs=true" + shift +elif [ "${1:-}" == "-r" ]; then + DEBUG_FLAG="" + shift +fi + +cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd +## scripts/kill_uaa.sh && \ +./gradlew run ${DEBUG_FLAG} "${@}" diff --git a/scripts/kill_uaa.sh b/scripts/kill_uaa.sh index ad96029f2f7..4300d783f9f 100755 --- a/scripts/kill_uaa.sh +++ b/scripts/kill_uaa.sh @@ -18,21 +18,45 @@ find_jps_command() { } function main() { + local pid local jps_command + local kill_count=5 + local port=${PORT:-8080} jps_command=$(find_jps_command) - while $jps_command | grep Bootstrap; do - $jps_command | grep Bootstrap | cut -f 1 -d' ' | xargs kill -HUP - echo "Waiting for Bootstrap to finish" + pid=$($jps_command -vlm | grep Bootstrap | grep uaa | grep "${port}" | cut -f 1 -d' ') + if [ -z "$pid" ]; then + echo "No UAA process found on port: ${port}" + exit 0 + fi + + echo Currently running UAA processes: + $jps_command -vlm | egrep "^${pid} " + echo + echo -n "Attempting to kill UAA process with PID=$pid: " + + while [ "$kill_count" -ge "0" ]; do + if ! $jps_command | egrep "^${pid} " >/dev/null; then + break + fi + echo -n . + kill -HUP "${pid}" || true sleep 1 + kill_count=$((kill_count - 1)) done - $jps_command | grep Bootstrap + if $jps_command | egrep "^${pid} " >/dev/null; then + echo -n " Forcibly killing: " + kill -9 "${pid}" || true + sleep 2 + fi + + $jps_command | egrep "^${pid} " if [ $? -eq 0 ]; then - echo "Bootstrap is still running" + echo " Bootstrap is still running" exit 1 else - echo "Bootstrap has finished" + echo " Bootstrap has finished" exit 0 fi } diff --git a/server/build.gradle b/server/build.gradle index c171362833a..8111360a221 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -26,16 +26,14 @@ dependencies { implementation(libraries.owaspEsapi) { transitive = false } - implementation(libraries.springSecuritySaml) { - exclude(module: "bcprov-ext-jdk15on") - exclude(module: "xalan") - } + implementation(libraries.springSecuritySamlServiceProvider) implementation(libraries.jodaTime) implementation(libraries.xmlSecurity) implementation(libraries.springSessionJdbc) - implementation(libraries.bouncyCastleProv) - implementation(libraries.bouncyCastlePkix) + implementation(libraries.bouncyCastleFipsProv) + implementation(libraries.bouncyCastleTlsFips) + implementation(libraries.bouncyCastlePkixFips) implementation(libraries.guava) @@ -112,6 +110,8 @@ dependencies { testImplementation(libraries.jsonPathAssert) testImplementation(libraries.guavaTestLib) + testImplementation(libraries.xmlUnit) + testImplementation(libraries.awaitility) implementation(libraries.commonsIo) } @@ -119,11 +119,17 @@ dependencies { configurations.all { exclude(group: "org.beanshell", module: "bsh-core") exclude(group: "org.apache-extras.beanshell", module: "bsh") - exclude(group: "org.bouncycastle", module: "bcpkix-jdk15on") - exclude(group: "org.bouncycastle", module: "bcprov-jdk15on") exclude(group: "com.fasterxml.woodstox", module: "woodstox-core") exclude(group: "commons-beanutils", module: "commons-beanutils") exclude(group: "commons-collections", module: "commons-collections") + + // Exclude non-FIPS bouncycastle libs, and use Shadow library for FIPS compliance + exclude(group: "org.bouncycastle", module: "bcpkix-jdk15on") + exclude(group: "org.bouncycastle", module: "bcprov-jdk15on") + exclude(group: "org.bouncycastle", module: "bcutil-jdk15on") + exclude(group: "org.bouncycastle", module: "bcprov-jdk18on") + exclude(group: "org.bouncycastle", module: "bcpkix-jdk18on") + exclude(group: "org.bouncycastle", module: "bcutil-jdk18on") } jar { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/approval/DescribedApproval.java b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/DescribedApproval.java index 40a2931fb2c..fddafbcd802 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/approval/DescribedApproval.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/DescribedApproval.java @@ -15,7 +15,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; -import org.cloudfoundry.identity.uaa.approval.Approval; public class DescribedApproval extends Approval { private String description; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java index dd6659e76ab..b8c41244f6f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java @@ -14,10 +14,13 @@ */ package org.cloudfoundry.identity.uaa.authentication; +import lombok.Getter; +import lombok.Setter; import org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtClientAuthentication; import org.cloudfoundry.identity.uaa.oauth.provider.AuthorizationRequest; import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2Authentication; +import org.cloudfoundry.identity.uaa.oauth.provider.error.OAuth2AuthenticationEntryPoint; import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.slf4j.Logger; @@ -28,7 +31,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; -import org.cloudfoundry.identity.uaa.oauth.provider.error.OAuth2AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.Filter; @@ -57,27 +59,16 @@ public abstract class AbstractClientParametersAuthenticationFilter implements Fi public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; public static final String CLIENT_ASSERTION = "client_assertion"; + protected final Logger logger = LoggerFactory.getLogger(getClass()); + @Setter + @Getter protected AuthenticationManager clientAuthenticationManager; + @Setter protected AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); - public AuthenticationManager getClientAuthenticationManager() { - return clientAuthenticationManager; - } - - public void setClientAuthenticationManager(AuthenticationManager clientAuthenticationManager) { - this.clientAuthenticationManager = clientAuthenticationManager; - } - - /** - * @param authenticationEntryPoint the authenticationEntryPoint to set - */ - public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { - this.authenticationEntryPoint = authenticationEntryPoint; - } - @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -109,12 +100,11 @@ protected void doClientCredentialLogin(HttpServletRequest req, Map getSingleValueMap(HttpServletRequest request) { - Map map = new HashMap(); - @SuppressWarnings("unchecked") + Map map = new HashMap<>(); Map parameters = request.getParameterMap(); - for (String key : parameters.keySet()) { - String[] values = parameters.get(key); - map.put(key, values != null && values.length > 0 ? values[0] : null); + for (Map.Entry entry : parameters.entrySet()) { + String[] values = parameters.get(entry.getKey()); + map.put(entry.getKey(), values != null && values.length > 0 ? values[0] : null); } return map; } @@ -137,12 +127,11 @@ private Authentication performClientAuthentication(HttpServletRequest req, Map authenticationDetailsSource = new WebAuthenticationDetailsSource(); + /** + * An authentication entry point that can handle unsuccessful authentication. + * Defaults to an {@link OAuth2AuthenticationEntryPoint}. + */ + @Setter private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); private final AuthenticationManager authenticationManager; private final OAuth2RequestFactory oAuth2RequestFactory; - private final SAMLProcessingFilter samlAuthenticationFilter; + private final Saml2BearerGrantAuthenticationConverter saml2BearerGrantAuthenticationConverter; private final ExternalOAuthAuthenticationManager externalOAuthAuthenticationManager; + private final AntPathRequestMatcher requestMatcher; + public BackwardsCompatibleTokenEndpointAuthenticationFilter(AuthenticationManager authenticationManager, OAuth2RequestFactory oAuth2RequestFactory) { - this(authenticationManager, oAuth2RequestFactory, null, null); + this(DEFAULT_FILTER_PROCESSES_URI, authenticationManager, oAuth2RequestFactory, null, null); } - /** - * @param authenticationManager an AuthenticationManager for the incoming request - */ + public BackwardsCompatibleTokenEndpointAuthenticationFilter(AuthenticationManager authenticationManager, OAuth2RequestFactory oAuth2RequestFactory, - SAMLProcessingFilter samlAuthenticationFilter, + Saml2BearerGrantAuthenticationConverter saml2BearerGrantAuthenticationConverter, + ExternalOAuthAuthenticationManager externalOAuthAuthenticationManager) { + this(DEFAULT_FILTER_PROCESSES_URI, authenticationManager, oAuth2RequestFactory, saml2BearerGrantAuthenticationConverter, externalOAuthAuthenticationManager); + } + + public BackwardsCompatibleTokenEndpointAuthenticationFilter(String requestMatcherUrl, + AuthenticationManager authenticationManager, + OAuth2RequestFactory oAuth2RequestFactory, + Saml2BearerGrantAuthenticationConverter saml2BearerGrantAuthenticationConverter, ExternalOAuthAuthenticationManager externalOAuthAuthenticationManager) { super(); + Assert.isTrue(requestMatcherUrl.contains("{registrationId}"), + "filterProcessesUrl must contain a {registrationId} match variable"); + requestMatcher = new AntPathRequestMatcher(requestMatcherUrl); + this.authenticationManager = authenticationManager; this.oAuth2RequestFactory = oAuth2RequestFactory; - this.samlAuthenticationFilter = samlAuthenticationFilter; + this.saml2BearerGrantAuthenticationConverter = saml2BearerGrantAuthenticationConverter; this.externalOAuthAuthenticationManager = externalOAuthAuthenticationManager; } - /** - * An authentication entry point that can handle unsuccessful authentication. Defaults to an - * {@link OAuth2AuthenticationEntryPoint}. - * - * @param authenticationEntryPoint the authenticationEntryPoint to set - */ - public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { - this.authenticationEntryPoint = authenticationEntryPoint; - } - - /** - * A source of authentication details for requests that result in authentication. - * - * @param authenticationDetailsSource the authenticationDetailsSource to set - */ - public void setAuthenticationDetailsSource( - AuthenticationDetailsSource authenticationDetailsSource) { - this.authenticationDetailsSource = authenticationDetailsSource; - } - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; @@ -129,12 +131,13 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) Authentication clientAuth = SecurityContextHolder.getContext().getAuthentication(); if (clientAuth == null) { throw new BadCredentialsException( - "No client authentication found. Remember to put a filter upstream of the TokenEndpointAuthenticationFilter."); + "No client authentication found. Remember to put a filter upstream of the TokenEndpointAuthenticationFilter."); } Map map = getSingleValueMap(request); map.put(OAuth2Utils.CLIENT_ID, clientAuth.getName()); + // seems to be overwritten with new OAuth2Authentication below SecurityContextHolder.getContext().setAuthentication(userAuthentication); AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(map); @@ -150,20 +153,20 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(authorizationRequest); SecurityContextHolder - .getContext() - .setAuthentication(new OAuth2Authentication(storedOAuth2Request, userAuthentication)); + .getContext() + .setAuthentication(new OAuth2Authentication(storedOAuth2Request, userAuthentication)); onSuccessfulAuthentication(request, response, userAuthentication); } } catch (AuthenticationException failed) { - logger.debug("Authentication request failed: " + failed.getMessage()); + log.debug("Authentication request failed: {}", failed.getMessage()); onUnsuccessfulAuthentication(request, response, failed); authenticationEntryPoint.commence(request, response, failed); return; } catch (OAuth2Exception failed) { String message = failed.getMessage(); - logger.debug("Authentication request failed with Oauth exception: " + message); - InsufficientAuthenticationException ex = new InsufficientAuthenticationException (message, failed); + log.debug("Authentication request failed with Oauth exception: {}", message); + InsufficientAuthenticationException ex = new InsufficientAuthenticationException(message, failed); onUnsuccessfulAuthentication(request, response, ex); authenticationEntryPoint.commence(request, response, ex); return; @@ -173,11 +176,11 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) } private Map getSingleValueMap(HttpServletRequest request) { - Map map = new HashMap(); + Map map = new HashMap<>(); Map parameters = request.getParameterMap(); - for (String key : parameters.keySet()) { - String[] values = parameters.get(key); - map.put(key, values != null && values.length > 0 ? values[0] : null); + for (Map.Entry entry : parameters.entrySet()) { + String[] values = entry.getValue(); + map.put(entry.getKey(), values != null && values.length > 0 ? values[0] : null); } return map; } @@ -211,15 +214,14 @@ protected Authentication extractCredentials(HttpServletRequest request) { protected Authentication attemptTokenAuthentication(HttpServletRequest request, HttpServletResponse response) { String grantType = request.getParameter("grant_type"); - logger.debug("Processing token user authentication for grant:{}",UaaStringUtils.getCleanedUserControlString(grantType)); + log.debug("Processing token user authentication for grant:{}", UaaStringUtils.getCleanedUserControlString(grantType)); Authentication authResult = null; if (GRANT_TYPE_PASSWORD.equals(grantType)) { Authentication credentials = extractCredentials(request); - logger.debug("Authentication credentials found password grant for '" + credentials.getName() + "'"); + log.debug("Authentication credentials found password grant for '" + credentials.getName() + "'"); authResult = authenticationManager.authenticate(credentials); - if (authResult != null && authResult.isAuthenticated() && authResult instanceof UaaAuthentication) { - UaaAuthentication uaaAuthentication = (UaaAuthentication) authResult; + if (authResult != null && authResult.isAuthenticated() && authResult instanceof UaaAuthentication uaaAuthentication) { if (SessionUtils.isPasswordChangeRequired(request.getSession())) { throw new PasswordChangeRequiredException(uaaAuthentication, "password change required"); } @@ -227,41 +229,54 @@ protected Authentication attemptTokenAuthentication(HttpServletRequest request, return authResult; } else if (GRANT_TYPE_SAML2_BEARER.equals(grantType)) { - logger.debug(GRANT_TYPE_SAML2_BEARER +" found. Attempting authentication with assertion"); + log.debug("{} found. Attempting authentication with assertion", GRANT_TYPE_SAML2_BEARER); String assertion = request.getParameter("assertion"); - if (assertion != null && samlAuthenticationFilter != null) { - logger.debug("Attempting SAML authentication for token endpoint."); - authResult = samlAuthenticationFilter.attemptAuthentication(request, response); + if (assertion != null && saml2BearerGrantAuthenticationConverter != null) { + resolveRegistrationId(request); + + log.debug("Attempting SAML authentication for token endpoint."); + try { + authResult = saml2BearerGrantAuthenticationConverter.convert(request); + } catch (Exception e) { + log.error("Error setting assertion in SAML filter", e); + throw new InsufficientAuthenticationException("Error setting assertion in SAML filter"); + } } else { - logger.debug("No assertion or filter, not attempting SAML authentication for token endpoint."); + log.debug("No assertion or filter, not attempting SAML authentication for token endpoint."); throw new InsufficientAuthenticationException("SAML Assertion is missing"); } } else if (GRANT_TYPE_JWT_BEARER.equals(grantType)) { - logger.debug(GRANT_TYPE_JWT_BEARER +" found. Attempting authentication with assertion"); + log.debug(GRANT_TYPE_JWT_BEARER + " found. Attempting authentication with assertion"); String assertion = request.getParameter("assertion"); if (assertion != null && externalOAuthAuthenticationManager != null) { - logger.debug("Attempting OIDC JWT authentication for token endpoint."); + log.debug("Attempting OIDC JWT authentication for token endpoint."); ExternalOAuthCodeToken token = new ExternalOAuthCodeToken(null, null, null, assertion, null, null); token.setRequestContextPath(getContextPath(request)); authResult = externalOAuthAuthenticationManager.authenticate(token); } else { - logger.debug("No assertion or authentication manager, not attempting JWT bearer authentication for token endpoint."); + log.debug("No assertion or authentication manager, not attempting JWT bearer authentication for token endpoint."); throw new InsufficientAuthenticationException("Assertion is missing"); } } + if (authResult != null && authResult.isAuthenticated()) { - logger.debug("Authentication success: " + authResult.getName()); + log.debug("Authentication success: " + authResult.getName()); return authResult; } return null; } - @Override - public void init(FilterConfig filterConfig) { - } + private void resolveRegistrationId(HttpServletRequest request) { + RequestMatcher.MatchResult result = this.requestMatcher.matcher(request); + if (!result.isMatch()) { + return; + } + String registrationId = result.getVariables().get("registrationId"); + if (registrationId == null) { + return; + } + request.setAttribute("registrationId", registrationId); - @Override - public void destroy() { } private String getContextPath(HttpServletRequest request) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/MalformedSamlResponseLogger.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/MalformedSamlResponseLogger.java new file mode 100644 index 00000000000..09f76d76628 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/MalformedSamlResponseLogger.java @@ -0,0 +1,69 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +// Using SamlResponseLoggerBinding for any backward compatibility issues +@Slf4j(topic = "org.cloudfoundry.identity.uaa.authentication.SamlResponseLoggerBinding") +@Component("samlResponseLoggerBinding") +public class MalformedSamlResponseLogger { + public static final String X_VCAP_REQUEST_ID_HEADER = "X-Vcap-Request-Id"; + + public void logMalformedResponse(HttpServletRequest httpServletRequest) { + log.warn("Malformed SAML response. More details at log level DEBUG."); + + if (httpServletRequest == null) { + log.debug("HttpServletRequest is null - no information to log"); + return; + } + + if (!log.isDebugEnabled()) { + // Logger is not in debug mode, so we don't need to log the details + return; + } + + log.debug("Method: {}, Params (name/size): {}, Content-type: {}, Request-size: {}, {}: {}", + httpServletRequest.getMethod(), + describeParameters(httpServletRequest), + httpServletRequest.getContentType(), + httpServletRequest.getContentLength(), + X_VCAP_REQUEST_ID_HEADER, + httpServletRequest.getHeader(X_VCAP_REQUEST_ID_HEADER)); + } + + private static String describeParameters(HttpServletRequest t) { + if (t == null || t.getParameterMap() == null) { + return null; + } + + return t.getParameterMap() + .entrySet() + .stream() + .map(MalformedSamlResponseLogger::formatParam) + .collect(Collectors.joining(" ")); + } + + private static String formatParam(Map.Entry p) { + if (p == null) { + return "(UNKNOWN/0)"; + } + + if (p.getValue() == null) { + return String.format("(%s/0)", p.getKey()); + } + + List formattedParams = new ArrayList<>(p.getValue().length); + + for (String val : p.getValue()) { + formattedParams.add(String.format("(%s/%s)", p.getKey(), val == null ? 0 : val.length())); + } + + return String.join(" ", formattedParams); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasscodeAuthenticationFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasscodeAuthenticationFilter.java index da5fac1813a..a1e06c57e34 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasscodeAuthenticationFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasscodeAuthenticationFilter.java @@ -14,18 +14,18 @@ package org.cloudfoundry.identity.uaa.authentication; -import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2RequestFactory; -import org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2RequestFactory; import org.cloudfoundry.identity.uaa.passcode.PasscodeInformation; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; @@ -38,7 +38,6 @@ import org.springframework.util.StringUtils; import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -56,30 +55,26 @@ /** * Authentication filter to verify one time passwords with what's cached in the - * one time password store. - * - * + * one-time password store. */ public class PasscodeAuthenticationFilter extends BackwardsCompatibleTokenEndpointAuthenticationFilter { - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private List parameterNames = Collections.emptyList(); + private List parameterNames = List.of(); public PasscodeAuthenticationFilter(UaaUserDatabase uaaUserDatabase, AuthenticationManager authenticationManager, OAuth2RequestFactory oAuth2RequestFactory, ExpiringCodeStore expiringCodeStore) { super( - new ExpiringCodeAuthenticationManager( - uaaUserDatabase, - authenticationManager, - LoggerFactory.getLogger(PasscodeAuthenticationFilter.class), - expiringCodeStore, - Collections.singleton(HttpMethod.POST.toString())), - oAuth2RequestFactory); + new ExpiringCodeAuthenticationManager( + uaaUserDatabase, + authenticationManager, + LoggerFactory.getLogger(PasscodeAuthenticationFilter.class), + expiringCodeStore, + Collections.singleton(HttpMethod.POST.toString())), + oAuth2RequestFactory); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { - PasscodeHttpServletRequest request = new PasscodeHttpServletRequest((HttpServletRequest)req); + PasscodeHttpServletRequest request = new PasscodeHttpServletRequest((HttpServletRequest) req); super.doFilter(request, res, chain); } @@ -176,10 +171,9 @@ protected ExpiringCode doRetrieveCode(String code) { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - if (!(authentication instanceof PasscodeAuthenticationFilter.ExpiringCodeAuthentication)) { + if (!(authentication instanceof PasscodeAuthenticationFilter.ExpiringCodeAuthentication expiringCodeAuthentication)) { return parent.authenticate(authentication); } else { - PasscodeAuthenticationFilter.ExpiringCodeAuthentication expiringCodeAuthentication = (PasscodeAuthenticationFilter.ExpiringCodeAuthentication) authentication; // Validate passcode logger.debug("Located credentials in request, with passcode"); if (methods != null && !methods.contains(expiringCodeAuthentication.getRequest().getMethod().toUpperCase())) { @@ -203,7 +197,7 @@ public Authentication authenticate(Authentication authentication) throws Authent if (pi == null) { throw new InsufficientAuthenticationException("Invalid passcode"); } - logger.debug("Successful passcode authentication request for " + pi.getUsername()); + logger.debug("Successful passcode authentication request for {}", pi.getUsername()); Collection externalAuthorities = null; @@ -211,7 +205,7 @@ public Authentication authenticate(Authentication authentication) throws Authent externalAuthorities = (Collection) pi.getAuthorizationParameters().get("authorities"); } UaaPrincipal principal = new UaaPrincipal(pi.getUserId(), pi.getUsername(), null, pi.getOrigin(), null, - IdentityZoneHolder.get().getId()); + IdentityZoneHolder.get().getId()); List authorities; try { UaaUser user = uaaUserDatabase.retrieveUserById(pi.getUserId()); @@ -220,16 +214,16 @@ public Authentication authenticate(Authentication authentication) throws Authent throw new BadCredentialsException("Invalid user."); } Authentication result = new UsernamePasswordAuthenticationToken( - principal, - null, - externalAuthorities == null || externalAuthorities.size() == 0 ? authorities : externalAuthorities + principal, + null, + externalAuthorities == null || externalAuthorities.isEmpty() ? authorities : externalAuthorities ); //add additional parameters for backwards compatibility - PasscodeHttpServletRequest pcRequest = (PasscodeHttpServletRequest)expiringCodeAuthentication.getRequest(); - //pcRequest.addParameter("user_id", new String[] {pi.getUserId()}); - pcRequest.addParameter("username", new String[] {pi.getUsername()}); - pcRequest.addParameter(OriginKeys.ORIGIN, new String[] {pi.getOrigin()}); + PasscodeHttpServletRequest pcRequest = (PasscodeHttpServletRequest) expiringCodeAuthentication.getRequest(); + //pcRequest.addParameter("user_id", new String[] {pi.getUserId()}) + pcRequest.addParameter("username", new String[]{pi.getUsername()}); + pcRequest.addParameter(OriginKeys.ORIGIN, new String[]{pi.getOrigin()}); return result; } @@ -243,7 +237,7 @@ protected Authentication extractCredentials(HttpServletRequest request) { if (grantType != null && grantType.equals(GRANT_TYPE_PASSWORD)) { Map credentials = UaaHttpRequestUtils.getCredentials(request, parameterNames); String passcode = credentials.get("passcode"); - if (passcode!=null) { + if (passcode != null) { return new ExpiringCodeAuthentication(request, passcode); } else { return super.extractCredentials(request); @@ -252,14 +246,6 @@ protected Authentication extractCredentials(HttpServletRequest request) { return null; } - @Override - public void init(FilterConfig filterConfig) { - } - - @Override - public void destroy() { - } - public void setParameterNames(List parameterNames) { this.parameterNames = parameterNames; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RedirectSavingSamlContextProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RedirectSavingSamlContextProvider.java deleted file mode 100644 index 6bb782f1664..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RedirectSavingSamlContextProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication; - -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.flywaydb.core.internal.util.StringUtils; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.springframework.security.saml.context.SAMLContextProvider; -import org.springframework.security.saml.context.SAMLMessageContext; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.HashMap; -import java.util.Map; - -public class RedirectSavingSamlContextProvider implements SAMLContextProvider { - - private final SAMLContextProvider contextProviderDelegate; - - public RedirectSavingSamlContextProvider(SAMLContextProvider contextProviderDelegate) { - this.contextProviderDelegate = contextProviderDelegate; - } - - @Override - public SAMLMessageContext getLocalEntity(HttpServletRequest request, HttpServletResponse response) throws MetadataProviderException { - SAMLMessageContext context = contextProviderDelegate.getLocalEntity(request, response); - return setRelayState(request, context); - } - - @Override - public SAMLMessageContext getLocalAndPeerEntity(HttpServletRequest request, HttpServletResponse response) throws MetadataProviderException { - SAMLMessageContext context = contextProviderDelegate.getLocalAndPeerEntity(request, response); - return setRelayState(request, context); - } - - private static SAMLMessageContext setRelayState(HttpServletRequest request, SAMLMessageContext context) { - Map params = new HashMap<>(); - - String redirectUri = request.getParameter("redirect"); - if(StringUtils.hasText(redirectUri)) { params.put("redirect", redirectUri); } - - String clientId = request.getParameter("client_id"); - if(StringUtils.hasText(clientId)) { params.put("client_id", clientId); } - - context.setRelayState(JsonUtils.writeValueAsString(params)); - return context; - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlAssertionBinding.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlAssertionBinding.java deleted file mode 100644 index 5f11c0d3b1c..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlAssertionBinding.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.authentication; - -import org.opensaml.ws.message.decoder.MessageDecoder; -import org.opensaml.ws.message.encoder.MessageEncoder; -import org.opensaml.ws.transport.InTransport; -import org.opensaml.ws.transport.http.HTTPInTransport; -import org.opensaml.ws.transport.http.HTTPTransport; -import org.opensaml.xml.parse.ParserPool; -import org.springframework.security.saml.processor.HTTPPostBinding; - -public class SamlAssertionBinding extends HTTPPostBinding { - - /** - * Creates default implementation of the binding. - * - * @param parserPool parserPool for message deserialization - */ - public SamlAssertionBinding(ParserPool parserPool) { - this(parserPool, new SamlAssertionDecoder(parserPool), null); - } - - /** - * Implementation of the binding with custom encoder and decoder. - * - * @param parserPool parserPool for message deserialization - * @param decoder custom decoder implementation - * @param encoder custom encoder implementation - */ - public SamlAssertionBinding(ParserPool parserPool, MessageDecoder decoder, MessageEncoder encoder) { - super(parserPool, decoder, encoder); - } - - @Override - public boolean supports(InTransport transport) { - if (transport instanceof HTTPInTransport) { - HTTPTransport t = (HTTPTransport) transport; - return "POST".equalsIgnoreCase(t.getHTTPMethod()) && t.getParameterValue("assertion") != null; - } else { - return false; - } - } - - @Override - public String getBindingURI() { - return "urn:oasis:names:tc:SAML:2.0:bindings:URI"; - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlAssertionDecoder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlAssertionDecoder.java deleted file mode 100644 index ccfbf170d94..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlAssertionDecoder.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.authentication; - -import org.cloudfoundry.identity.uaa.provider.saml.SamlRedirectUtils; -import org.opensaml.common.binding.SAMLMessageContext; -import org.opensaml.saml2.binding.decoding.BaseSAML2MessageDecoder; -import org.opensaml.saml2.core.Assertion; -import org.opensaml.saml2.core.Response; -import org.opensaml.ws.message.MessageContext; -import org.opensaml.ws.message.decoder.MessageDecodingException; -import org.opensaml.ws.transport.http.HTTPInTransport; -import org.opensaml.xml.parse.ParserPool; -import org.opensaml.xml.util.DatatypeHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -/** - * Copy/paste from org.opensaml.saml2.binding.decoding.HTTPPostDecoder - * with two minor changes - * 1. base64 decoding is doing base64url decoding - * 2. The unmarshalling of the object gets wrapped in a SamlResponse object - */ - -public class SamlAssertionDecoder extends BaseSAML2MessageDecoder { - - /** Class logger. */ - private final Logger log = LoggerFactory.getLogger(SamlAssertionDecoder.class); - - /** Constructor. */ - public SamlAssertionDecoder() { - super(); - } - - /** - * Constructor. - * - * @param pool parser pool used to deserialize messages - */ - public SamlAssertionDecoder(ParserPool pool) { - super(pool); - } - - /** {@inheritDoc} */ - public String getBindingURI() { - return "urn:oasis:names:tc:SAML:2.0:bindings:URI"; - } - - /** {@inheritDoc} */ - protected boolean isIntendedDestinationEndpointURIRequired(SAMLMessageContext samlMsgCtx) { - return isMessageSigned(samlMsgCtx); - } - - /** {@inheritDoc} */ - protected void doDecode(MessageContext messageContext) throws MessageDecodingException { - if (!(messageContext instanceof SAMLMessageContext)) { - log.error("Invalid message context type, this decoder only support SAMLMessageContext"); - throw new MessageDecodingException( - "Invalid message context type, this decoder only support SAMLMessageContext"); - } - - if (!(messageContext.getInboundMessageTransport() instanceof HTTPInTransport)) { - log.error("Invalid inbound message transport type, this decoder only support HTTPInTransport"); - throw new MessageDecodingException( - "Invalid inbound message transport type, this decoder only support HTTPInTransport"); - } - - SAMLMessageContext samlMsgCtx = (SAMLMessageContext) messageContext; - - HTTPInTransport inTransport = (HTTPInTransport) samlMsgCtx.getInboundMessageTransport(); - if (!inTransport.getHTTPMethod().equalsIgnoreCase("POST")) { - throw new MessageDecodingException("This message decoder only supports the HTTP POST method"); - } - - String relayState = inTransport.getParameterValue("RelayState"); - samlMsgCtx.setRelayState(relayState); - log.debug("Decoded SAML relay state of: {}", relayState); - - InputStream base64DecodedMessage = getBase64DecodedMessage(inTransport); - Assertion inboundMessage = (Assertion) unmarshallMessage(base64DecodedMessage); - Response response = SamlRedirectUtils.wrapAssertionIntoResponse(inboundMessage, inboundMessage.getIssuer().getValue()); - samlMsgCtx.setInboundMessage(response); - samlMsgCtx.setInboundSAMLMessage(response); - log.debug("Decoded SAML message"); - - populateMessageContext(samlMsgCtx); - } - - /** - * Gets the Base64 encoded message from the request and decodes it. - * - * @param transport inbound message transport - * - * @return decoded message - * - * @throws MessageDecodingException thrown if the message does not contain a base64 encoded SAML message - */ - protected InputStream getBase64DecodedMessage(HTTPInTransport transport) throws MessageDecodingException { - log.debug("Getting Base64 encoded message from request"); - String encodedMessage = transport.getParameterValue("assertion"); - - - if (DatatypeHelper.isEmpty(encodedMessage)) { - log.error("Request did not contain either a SAMLRequest or " - + "SAMLResponse parameter. Invalid request for SAML 2 HTTP POST binding."); - throw new MessageDecodingException("No SAML message present in request"); - } - - log.trace("Base64 decoding SAML message:\n{}", encodedMessage); - byte[] decodedBytes = org.apache.commons.codec.binary.Base64.decodeBase64(encodedMessage.getBytes(StandardCharsets.UTF_8)); - if(decodedBytes == null){ - log.error("Unable to Base64 decode SAML message"); - throw new MessageDecodingException("Unable to Base64 decode SAML message"); - } - - log.trace("Decoded SAML message:\n{}", new String(decodedBytes)); - return new ByteArrayInputStream(decodedBytes); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutRequestValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutRequestValidator.java new file mode 100644 index 00000000000..cf3ce218f97 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutRequestValidator.java @@ -0,0 +1,39 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutRequestValidator; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult; + +import java.util.Collection; + +/** + * Delegates SAML logout request validation to {@link OpenSamlLogoutRequestValidator}, + * but ignores errors due to missing signatures. + */ +public class SamlLogoutRequestValidator implements Saml2LogoutRequestValidator { + + private final Saml2LogoutRequestValidator delegate; + + public SamlLogoutRequestValidator() { + this.delegate = new OpenSamlLogoutRequestValidator(); + } + + public SamlLogoutRequestValidator(Saml2LogoutRequestValidator delegate) { + this.delegate = delegate; + } + + @Override + public Saml2LogoutValidatorResult validate(Saml2LogoutRequestValidatorParameters parameters) { + Saml2LogoutValidatorResult result = delegate.validate(parameters); + if (!result.hasErrors()) { + return result; + } + + Collection errors = result.getErrors().stream() + .filter(error -> !error.getDescription().contains("signature")) + .toList(); + return Saml2LogoutValidatorResult.withErrors().errors(c -> c.addAll(errors)).build(); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutResponseValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutResponseValidator.java new file mode 100644 index 00000000000..1cce7c85a86 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutResponseValidator.java @@ -0,0 +1,40 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutResponseValidator; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidatorParameters; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult; + +import java.util.Collection; + +/** + * Delegates SAML logout responses validation to {@link OpenSamlLogoutResponseValidator} + * but ignores errors due to missing signatures. + */ + +public class SamlLogoutResponseValidator implements Saml2LogoutResponseValidator { + + private final Saml2LogoutResponseValidator delegate; + + public SamlLogoutResponseValidator() { + this.delegate = new OpenSamlLogoutResponseValidator(); + } + + public SamlLogoutResponseValidator(Saml2LogoutResponseValidator delegate) { + this.delegate = delegate; + } + + @Override + public Saml2LogoutValidatorResult validate(Saml2LogoutResponseValidatorParameters parameters) { + Saml2LogoutValidatorResult result = delegate.validate(parameters); + if (!result.hasErrors()) { + return result; + } + + Collection errors = result.getErrors().stream() + .filter(error -> !error.getDescription().contains("signature")) + .toList(); + return Saml2LogoutValidatorResult.withErrors().errors(c -> c.addAll(errors)).build(); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java deleted file mode 100644 index 019f100ff43..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlRedirectLogoutHandler.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication; - -import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.util.StringUtils; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -public class SamlRedirectLogoutHandler implements LogoutSuccessHandler { - private final LogoutSuccessHandler wrappedHandler; - - public SamlRedirectLogoutHandler(LogoutSuccessHandler wrappedHandler) { - this.wrappedHandler = wrappedHandler; - } - - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - RequestWrapper requestWrapper = new RequestWrapper(request); - String relayState = request.getParameter("RelayState"); - Map params = JsonUtils.readValue(relayState, new TypeReference>() {}); - if(params != null) { - String redirect = params.get("redirect"); - if(StringUtils.hasText(redirect)) { requestWrapper.setParameter("redirect", redirect); } - - String clientId = params.get("client_id"); - if(StringUtils.hasText(clientId)) { requestWrapper.setParameter("client_id", clientId); } - } - - wrappedHandler.onLogoutSuccess(requestWrapper, response, authentication); - } - - private static class RequestWrapper extends HttpServletRequestWrapper { - private final Map parameterMap; - - public RequestWrapper(HttpServletRequest request) { - super(request); - parameterMap = new HashMap<>(request.getParameterMap()); - } - - public void setParameter(String name, String... value) { - parameterMap.put(name, value); - } - - public String getParameter(String name) { - String[] values = parameterMap.get(name); - return values != null && values.length > 0 ? values[0] : null; - } - - public Map getParameterMap() { - return parameterMap; - } - - public Enumeration getParameterNames() { - return new Enumeration() { - Iterator iterator = parameterMap.keySet().iterator(); - - @Override - public boolean hasMoreElements() { - return iterator.hasNext(); - } - - @Override - public String nextElement() { - return iterator.next(); - } - }; - } - - public String[] getParameterValues(String name) { - return parameterMap.get(name); - } - - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlResponseLoggerBinding.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlResponseLoggerBinding.java deleted file mode 100644 index f9d5afa7f48..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SamlResponseLoggerBinding.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication; - -import org.opensaml.ws.message.decoder.MessageDecoder; -import org.opensaml.ws.message.encoder.MessageEncoder; -import org.opensaml.ws.security.SecurityPolicyRule; -import org.opensaml.ws.transport.InTransport; -import org.opensaml.ws.transport.OutTransport; -import org.opensaml.ws.transport.http.HttpServletRequestAdapter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.saml.context.SAMLMessageContext; -import org.springframework.security.saml.processor.SAMLBinding; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Component("samlResponseLoggerBinding") -public class SamlResponseLoggerBinding implements SAMLBinding { - - private static final Logger LOGGER = LoggerFactory.getLogger(SamlResponseLoggerBinding.class); - - public static final String X_VCAP_REQUEST_ID_HEADER = "X-Vcap-Request-Id"; - - @Override - public boolean supports(InTransport transport) { - if (!(transport instanceof HttpServletRequestAdapter)) { - return false; - } - - HttpServletRequest httpServletRequest = ((HttpServletRequestAdapter) transport).getWrappedRequest(); - LOGGER.warn("Malformed SAML response. More details at log level DEBUG."); - - if (httpServletRequest == null) { - LOGGER.debug("HttpServletRequest is null - no information to log"); - return false; - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Method: {}, Params (name/size): {}, Content-type: {}, Request-size: {}, {}: {}", - httpServletRequest.getMethod(), - describeParameters(httpServletRequest), - httpServletRequest.getContentType(), - httpServletRequest.getContentLength(), - X_VCAP_REQUEST_ID_HEADER, - httpServletRequest.getHeader(X_VCAP_REQUEST_ID_HEADER)); - } - return false; - } - - private static String describeParameters(HttpServletRequest t) { - if (t == null || t.getParameterMap() == null) { - return null; - } - - return t.getParameterMap() - .entrySet() - .stream() - .map(SamlResponseLoggerBinding::formatParam) - .collect(Collectors.joining(" ")); - } - - private static String formatParam(Map.Entry p) { - - if (p == null) { - return "(UNKNOWN/0)"; - } - - if (p.getValue() == null) { - return String.format("(%s/0)", p.getKey()); - } - - List formattedParams = new ArrayList<>(p.getValue().length); - - for (String val : p.getValue()) { - formattedParams.add(String.format("(%s/%s)", p.getKey(), val == null ? 0 : val.length())); - } - - return String.join(" ", formattedParams); - } - - @Override - public boolean supports(OutTransport transport) { - return false; - } - - @Override - public MessageDecoder getMessageDecoder() { - return null; - } - - @Override - public MessageEncoder getMessageEncoder() { - return null; - } - - @Override - public String getBindingURI() { - return "NON_NULL_BINDING_URI_UNUSED_SamlResponseLoggerBinding"; - } - - @Override - public void getSecurityPolicy(List securityPolicy, SAMLMessageContext samlContext) { - - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UTF8ConversionFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UTF8ConversionFilter.java index 8581c61bde1..e264b7abba9 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UTF8ConversionFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UTF8ConversionFilter.java @@ -18,7 +18,6 @@ import javax.servlet.Filter; import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -36,24 +35,19 @@ public class UTF8ConversionFilter implements Filter { - public static final String NULL_STRING = new String(new char[] {'\u0000'}); - - @Override - public void init(FilterConfig filterConfig) { - - } + public static final String NULL_STRING = String.valueOf('\u0000'); @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest)req; - HttpServletResponse response = (HttpServletResponse)res; + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; //application/x-www-form-urlencoded is always considered ISO-8859-1 by tomcat even when //because there is no charset defined //the browser sends up UTF-8 //https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()) && - (request.getCharacterEncoding() == null || ISO_8859_1.equalsIgnoreCase(request.getCharacterEncoding())) - ) { + (request.getCharacterEncoding() == null || ISO_8859_1.equalsIgnoreCase(request.getCharacterEncoding())) + ) { request = new UtfConverterRequestWrapper(request); } validateParamsAndContinue(request, response, chain); @@ -61,12 +55,12 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) protected void validateParamsAndContinue(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { for (Map.Entry entry : request.getParameterMap().entrySet()) { - if (entry.getValue() != null && entry.getValue().length >0) { + if (entry.getValue() != null && entry.getValue().length > 0) { for (String s : entry.getValue()) { if (hasText(s) && s.contains(NULL_STRING)) { response.setStatus(400); request.setAttribute("error_message_code", "request.invalid_parameter"); - request.getRequestDispatcher("/error").forward(request,response); + request.getRequestDispatcher("/error").forward(request, response); return; } } @@ -75,11 +69,6 @@ protected void validateParamsAndContinue(HttpServletRequest request, HttpServlet chain.doFilter(request, response); } - @Override - public void destroy() { - - } - public static class UtfConverterRequestWrapper extends HttpServletRequestWrapper { public UtfConverterRequestWrapper(HttpServletRequest request) { super(request); @@ -93,11 +82,11 @@ public String getParameter(String name) { @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); - if (values==null || values.length==0) { + if (values == null || values.length == 0) { return values; } String[] result = new String[values.length]; - for (int i=0; i getParameterMap() { - Map map = new HashMap<>(); + Map map = new HashMap<>(); Enumeration names = getParameterNames(); while (names.hasMoreElements()) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java index 30bf7c55364..2b74402b5a9 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java @@ -13,36 +13,39 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.authentication; -import java.io.Serializable; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import static java.util.Collections.EMPTY_MAP; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.emptyMap; /** * Authentication token which represents a user. */ @JsonSerialize(using = UaaAuthenticationSerializer.class) @JsonDeserialize(using = UaaAuthenticationDeserializer.class) -public class UaaAuthentication implements Authentication, Serializable { - - private Collection authorities; - private Object credentials; - private UaaPrincipal principal; - private UaaAuthenticationDetails details; - private boolean authenticated; +@Getter +@Setter +@ToString +public class UaaAuthentication extends AbstractAuthenticationToken + implements Authentication, Serializable { + + private final Object credentials; + private final UaaPrincipal principal; private long authenticatedTime = -1L; private long expiresAt = -1L; private Set externalGroups; @@ -51,26 +54,13 @@ public class UaaAuthentication implements Authentication, Serializable { private Long lastLoginSuccessTime; private String idpIdToken; - private Map userAttributes; - - public Long getLastLoginSuccessTime() { - return lastLoginSuccessTime; - } - - public UaaAuthentication setLastLoginSuccessTime(Long lastLoginSuccessTime) { - this.lastLoginSuccessTime = lastLoginSuccessTime; - return this; - } - - //This is used when UAA acts as a SAML IdP - @JsonIgnore - private transient SAMLMessageContext samlMessageContext; + private Map> userAttributes; /** * Creates a token with the supplied array of authorities. * * @param authorities the collection of GrantedAuthoritys for the - * principal represented by this authentication object. + * principal represented by this authentication object. */ public UaaAuthentication(UaaPrincipal principal, Collection authorities, @@ -94,14 +84,15 @@ public UaaAuthentication(UaaPrincipal principal, boolean authenticated, long authenticatedTime, long expiresAt) { + super(authorities); + if (principal == null || authorities == null) { throw new IllegalArgumentException("principal and authorities must not be null"); } + setDetails(details); + setAuthenticated(authenticated); this.principal = principal; - this.authorities = authorities; - this.details = details; this.credentials = credentials; - this.authenticated = authenticated; this.authenticatedTime = authenticatedTime <= 0 ? -1 : authenticatedTime; this.expiresAt = expiresAt <= 0 ? -1 : expiresAt; } @@ -120,10 +111,6 @@ public UaaAuthentication(UaaPrincipal uaaPrincipal, this.userAttributes = new HashMap<>(userAttributes); } - public long getAuthenticatedTime() { - return authenticatedTime; - } - @Override public String getName() { // Should we return the ID for the principal name? (No, because the @@ -131,38 +118,13 @@ public String getName() { return principal.getName(); } - @Override - public Collection getAuthorities() { - return authorities; - } - - @Override - public Object getCredentials() { - return credentials; - } - - @Override - public Object getDetails() { - return details; - } - - @Override - public UaaPrincipal getPrincipal() { - return principal; + public UaaAuthenticationDetails getUaaAuthenticationDetails() { + return (UaaAuthenticationDetails) getDetails(); } @Override public boolean isAuthenticated() { - return authenticated && (expiresAt > 0 ? expiresAt > System.currentTimeMillis() : true); - } - - @Override - public void setAuthenticated(boolean isAuthenticated) { - authenticated = isAuthenticated; - } - - public long getExpiresAt() { - return expiresAt; + return super.isAuthenticated() && (expiresAt <= 0 || expiresAt > System.currentTimeMillis()); } @Override @@ -176,85 +138,33 @@ public boolean equals(Object o) { UaaAuthentication that = (UaaAuthentication) o; - if (!authorities.equals(that.authorities)) { - return false; - } - if (!principal.equals(that.principal)) { + if (!getAuthorities().equals(that.getAuthorities())) { return false; } - - return true; - } - - public String getIdpIdToken() { - return this.idpIdToken; - } - - public void setIdpIdToken(final String idpIdToken) { - this.idpIdToken = idpIdToken; + return principal.equals(that.principal); } @Override public int hashCode() { - int result = authorities.hashCode(); + int result = getAuthorities().hashCode(); result = 31 * result + principal.hashCode(); return result; } - public Set getExternalGroups() { - return externalGroups; - } - - public void setExternalGroups(Set externalGroups) { - this.externalGroups = externalGroups; - } - - public MultiValueMap getUserAttributes() { - return new LinkedMultiValueMap<>(userAttributes!=null? userAttributes: EMPTY_MAP); - } - - public Map> getUserAttributesAsMap() { - return userAttributes!=null ? new HashMap<>(userAttributes) : EMPTY_MAP; + public MultiValueMap getUserAttributes() { + return new LinkedMultiValueMap<>(userAttributes != null ? userAttributes : emptyMap()); } public void setUserAttributes(MultiValueMap userAttributes) { this.userAttributes = new HashMap<>(); - for (Map.Entry> entry : userAttributes.entrySet()) { - this.userAttributes.put(entry.getKey(), entry.getValue()); - } - } - - @JsonIgnore - public SAMLMessageContext getSamlMessageContext() { - return samlMessageContext; - } - - @JsonIgnore - public void setSamlMessageContext(SAMLMessageContext samlMessageContext) { - this.samlMessageContext = samlMessageContext; - } - - public Set getAuthenticationMethods() { - return authenticationMethods; - } - - public void setAuthenticationMethods(Set authenticationMethods) { - this.authenticationMethods = authenticationMethods; - } - - public Set getAuthContextClassRef() { - return authContextClassRef; - } - - public void setAuthContextClassRef(Set authContextClassRef) { - this.authContextClassRef = authContextClassRef; + this.userAttributes.putAll(userAttributes); } - public void setAuthenticatedTime(long authenticatedTime) { - this.authenticatedTime = authenticatedTime; + public Map> getUserAttributesAsMap() { + return userAttributes != null ? new HashMap<>(userAttributes) : emptyMap(); } public void setAuthenticationDetails(UaaAuthenticationDetails authenticationDetails) { - this.details = authenticationDetails; + setDetails(authenticationDetails); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaPrincipal.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaPrincipal.java index e314e4ffa4b..d9a7b99f437 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaPrincipal.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaPrincipal.java @@ -13,22 +13,26 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.authentication; -import java.io.Serializable; -import java.security.Principal; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; +import org.springframework.security.core.AuthenticatedPrincipal; + +import java.io.Serializable; +import java.security.Principal; /** - * The principal object which should end up as the representation of an + * The {@link Principal} object which should end up as the representation of an * authenticated user. *

* Contains the data required for an authenticated user within the UAA * application itself. + * Note: For SAML, the {@code UaaSamlPrincipal} subclass should be used. */ -public class UaaPrincipal implements Principal, Serializable { +@Data +public class UaaPrincipal implements AuthenticatedPrincipal, Principal, Serializable { private final String id; private final String name; private final String email; @@ -38,33 +42,34 @@ public class UaaPrincipal implements Principal, Serializable { public UaaPrincipal(UaaUser user) { this( - user.getId(), - user.getUsername(), - user.getEmail(), - user.getOrigin(), - user.getExternalId(), - user.getZoneId() + user.getId(), + user.getUsername(), + user.getEmail(), + user.getOrigin(), + user.getExternalId(), + user.getZoneId() ); } public UaaPrincipal(UaaUserPrototype userPrototype) { this( - userPrototype.getId(), - userPrototype.getUsername(), - userPrototype.getEmail(), - userPrototype.getOrigin(), - userPrototype.getExternalId(), - userPrototype.getZoneId() + userPrototype.getId(), + userPrototype.getUsername(), + userPrototype.getEmail(), + userPrototype.getOrigin(), + userPrototype.getExternalId(), + userPrototype.getZoneId() ); } + @JsonCreator public UaaPrincipal( - @JsonProperty("id") String id, - @JsonProperty("name") String username, - @JsonProperty("email") String email, - @JsonProperty("origin") String origin, - @JsonProperty("externalId") String externalId, - @JsonProperty("zoneId") String zoneId) { + @JsonProperty("id") String id, + @JsonProperty("name") String username, + @JsonProperty("email") String email, + @JsonProperty("origin") String origin, + @JsonProperty("externalId") String externalId, + @JsonProperty("zoneId") String zoneId) { this.id = id; this.name = username; this.email = email; @@ -73,25 +78,6 @@ public UaaPrincipal( this.zoneId = zoneId; } - public String getId() { - return id; - } - - @Override - public String getName() { - return name; - } - - public String getEmail() { - return email; - } - - public String getOrigin() { return origin; } - - public String getExternalId() { return externalId; } - - public String getZoneId() { return zoneId; } - /** * Returns {@code true} if the supplied object is a {@code UAAPrincipal} * instance with the @@ -102,8 +88,8 @@ public String getEmail() { */ @Override public boolean equals(Object rhs) { - if (rhs instanceof UaaPrincipal) { - return id.equals(((UaaPrincipal) rhs).id); + if (rhs instanceof UaaPrincipal uaaPrincipal) { + return id.equals(uaaPrincipal.id); } return false; } @@ -115,5 +101,4 @@ public boolean equals(Object rhs) { public int hashCode() { return id.hashCode(); } - } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java deleted file mode 100644 index 75eea3fb59f..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlLogoutFilter.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication; - -import org.opensaml.saml2.metadata.IDPSSODescriptor; -import org.opensaml.saml2.metadata.SingleLogoutService; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.saml.SAMLConstants; -import org.springframework.security.saml.SAMLCredential; -import org.springframework.security.saml.SAMLLogoutFilter; -import org.springframework.security.saml.context.SAMLMessageContext; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.List; - -public class UaaSamlLogoutFilter extends SAMLLogoutFilter { - - - public UaaSamlLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) { - super(logoutSuccessHandler, handlers, handlers); - setFilterProcessesUrl("/logout.do"); - } - - @Override - protected boolean isGlobalLogout(HttpServletRequest request, Authentication auth) { - SAMLMessageContext context; - try { - SAMLCredential credential = (SAMLCredential) auth.getCredentials(); - request.setAttribute(SAMLConstants.LOCAL_ENTITY_ID, credential.getLocalEntityID()); - request.setAttribute(SAMLConstants.PEER_ENTITY_ID, credential.getRemoteEntityID()); - context = contextProvider.getLocalAndPeerEntity(request, null); - IDPSSODescriptor idp = (IDPSSODescriptor) context.getPeerEntityRoleMetadata(); - List singleLogoutServices = idp.getSingleLogoutServices(); - return singleLogoutServices.size() != 0; - } catch (MetadataProviderException e) { - logger.debug("Error processing metadata", e); - return false; - } - } - - @Override - protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - return auth != null && auth.getCredentials() instanceof SAMLCredential && super.requiresLogout(request, response); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlPrincipal.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlPrincipal.java new file mode 100644 index 00000000000..83ab2d0f7ba --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlPrincipal.java @@ -0,0 +1,54 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +package org.cloudfoundry.identity.uaa.authentication; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.ToString; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; + +import java.io.Serializable; + +/** + * UaaSamlPrincipal extends {@link UaaPrincipal} and adds the {@link Saml2AuthenticatedPrincipal} interface. + * Notably, it allows retrieval of the relying party registration id. + *

+ * This is used to represent a SAML principal in the {@link UaaAuthentication} Object. + * The SAML Logout Handlers check if the Principal is an instance of Saml2AuthenticatedPrincipal to handle SAML Logout. + */ +@ToString(callSuper = true) +@JsonIgnoreProperties({"relyingPartyRegistrationId", "sessionIndexes", "attributes"}) +public class UaaSamlPrincipal extends UaaPrincipal implements Saml2AuthenticatedPrincipal, Serializable { + public UaaSamlPrincipal(UaaUser user) { + super(user); + } + + @JsonCreator + public UaaSamlPrincipal( + @JsonProperty("id") String id, + @JsonProperty("name") String username, + @JsonProperty("email") String email, + @JsonProperty("origin") String origin, + @JsonProperty("externalId") String externalId, + @JsonProperty("zoneId") String zoneId) { + super(id, username, email, origin, externalId, zoneId); + } + + @Override + public String getRelyingPartyRegistrationId() { + return getOrigin(); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutSuccessHandler.java similarity index 66% rename from server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutSuccessHandler.java index e859a855998..e5dcf3eada5 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutSuccessHandler.java @@ -1,44 +1,48 @@ package org.cloudfoundry.identity.uaa.authentication; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.cloudfoundry.identity.uaa.oauth.KeyInfo; import org.cloudfoundry.identity.uaa.oauth.KeyInfoService; +import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidTokenException; import org.cloudfoundry.identity.uaa.oauth.jwt.ChainedSignatureVerifier; import org.cloudfoundry.identity.uaa.oauth.jwt.SignatureVerifier; +import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetails; import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.provider.NoSuchClientException; import org.cloudfoundry.identity.uaa.util.JwtTokenSignedByThisUAA; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidTokenException; -import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetails; +import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; import static org.cloudfoundry.identity.uaa.util.JwtTokenSignedByThisUAA.buildIdTokenValidator; import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.findMatchingRedirectUri; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; -public final class WhitelistLogoutHandler extends SimpleUrlLogoutSuccessHandler { - final String OPEN_ID_TOKEN_HINT = "id_token_hint"; - private static final Logger logger = LoggerFactory.getLogger(WhitelistLogoutHandler.class); +@Slf4j +@Setter +public final class WhitelistLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { + private static final String OPEN_ID_TOKEN_HINT = "id_token_hint"; - private List whitelist = null; + private List whitelist; + @Getter private MultitenantClientServices clientDetailsService; private KeyInfoService keyInfoService; - public WhitelistLogoutHandler(List whitelist) { + public WhitelistLogoutSuccessHandler(List whitelist) { this.whitelist = whitelist; } @@ -47,22 +51,6 @@ protected boolean isAlwaysUseDefaultTargetUrl() { return false; } - public void setWhitelist(List whitelist) { - this.whitelist = whitelist; - } - - public MultitenantClientServices getClientDetailsService() { - return clientDetailsService; - } - - public void setClientDetailsService(MultitenantClientServices clientDetailsService) { - this.clientDetailsService = clientDetailsService; - } - - public void setKeyInfoService(KeyInfoService keyInfoService) { - this.keyInfoService = keyInfoService; - } - private Set getClientWhitelist(HttpServletRequest request) { String clientId = null; String idToken = request.getParameter(OPEN_ID_TOKEN_HINT); @@ -71,11 +59,11 @@ private Set getClientWhitelist(HttpServletRequest request) { if (idToken != null) { try { Map keys = keyInfoService.getKeys(); - List signatureVerifiers = keys.values().stream().map(i -> i.getVerifier()).collect(Collectors.toList()); - JwtTokenSignedByThisUAA jwtToken =buildIdTokenValidator(idToken, new ChainedSignatureVerifier(signatureVerifiers), keyInfoService); + List signatureVerifiers = keys.values().stream().map(KeyInfo::getVerifier).toList(); + JwtTokenSignedByThisUAA jwtToken = buildIdTokenValidator(idToken, new ChainedSignatureVerifier(signatureVerifiers), keyInfoService); clientId = (String) jwtToken.getClaims().get(ClaimConstants.AZP); } catch (InvalidTokenException e) { - logger.debug("Invalid token (could not verify signature)"); + log.debug("Invalid token (could not verify signature)"); } } else { clientId = request.getParameter(CLIENT_ID); @@ -86,7 +74,7 @@ private Set getClientWhitelist(HttpServletRequest request) { ClientDetails client = clientDetailsService.loadClientByClientId(clientId, IdentityZoneHolder.get().getId()); redirectUris = client.getRegisteredRedirectUri(); } catch (NoSuchClientException x) { - logger.debug(String.format("Unable to find client with ID:%s for logout redirect", clientId)); + log.debug(String.format("Unable to find client with ID:%s for logout redirect", clientId)); } } return redirectUris; @@ -100,7 +88,7 @@ protected String determineTargetUrl(HttpServletRequest request, HttpServletRespo targetUrl = super.determineTargetUrl(request, response); } - if(isInternalRedirect(targetUrl, request)) { + if (isInternalRedirect(targetUrl, request)) { return targetUrl; } @@ -110,7 +98,11 @@ protected String determineTargetUrl(HttpServletRequest request, HttpServletRespo } Set clientWhitelist = getClientWhitelist(request); - Set combinedWhitelist = combineSets(whitelist, clientWhitelist); + Set combinedWhitelist = Stream.of( + Optional.ofNullable(whitelist).orElse(List.of()), + Optional.ofNullable(clientWhitelist).orElse(Set.of())) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); return findMatchingRedirectUri(combinedWhitelist, targetUrl, defaultTargetUrl); } @@ -119,15 +111,4 @@ private boolean isInternalRedirect(String targetUrl, HttpServletRequest request) String serverUrl = request.getRequestURL().toString().replaceAll("/logout\\.do$", "/"); return targetUrl.startsWith(serverUrl); } - - private static Set combineSets(Collection... sets) { - Set combined = null; - for(Collection set : sets) { - if(set != null) { - if(combined == null) { combined = new HashSet<>(set); } - else { combined.addAll(set); } - } - } - return combined; - } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutSuccessHandler.java similarity index 82% rename from server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutSuccessHandler.java index 048857b2c42..a312c52f251 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutSuccessHandler.java @@ -14,14 +14,13 @@ package org.cloudfoundry.identity.uaa.authentication; - import org.cloudfoundry.identity.uaa.oauth.KeyInfoService; import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthLogoutHandler; -import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; +import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthLogoutSuccessHandler; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; @@ -31,14 +30,14 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; -public class ZoneAwareWhitelistLogoutHandler implements LogoutSuccessHandler { +public class ZoneAwareWhitelistLogoutSuccessHandler implements LogoutSuccessHandler { private final MultitenantClientServices clientDetailsService; - private final ExternalOAuthLogoutHandler externalOAuthLogoutHandler; + private final ExternalOAuthLogoutSuccessHandler externalOAuthLogoutHandler; private final KeyInfoService keyInfoService; - public ZoneAwareWhitelistLogoutHandler(MultitenantClientServices clientDetailsService, ExternalOAuthLogoutHandler externalOAuthLogoutHandler, - KeyInfoService keyInfoService) { + public ZoneAwareWhitelistLogoutSuccessHandler(MultitenantClientServices clientDetailsService, ExternalOAuthLogoutSuccessHandler externalOAuthLogoutHandler, + KeyInfoService keyInfoService) { this.clientDetailsService = clientDetailsService; this.externalOAuthLogoutHandler = externalOAuthLogoutHandler; this.keyInfoService = keyInfoService; @@ -59,7 +58,7 @@ public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse resp protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - AbstractExternalOAuthIdentityProviderDefinition oauthConfig = externalOAuthLogoutHandler.getOAuthProviderForAuthentication(authentication); + AbstractExternalOAuthIdentityProviderDefinition oauthConfig = externalOAuthLogoutHandler.getOAuthProviderForAuthentication(authentication); String logoutUrl = externalOAuthLogoutHandler.getLogoutUrl(oauthConfig); if (logoutUrl == null) { @@ -69,12 +68,12 @@ protected String determineTargetUrl(HttpServletRequest request, HttpServletRespo } } - protected WhitelistLogoutHandler getZoneHandler() { + protected WhitelistLogoutSuccessHandler getZoneHandler() { IdentityZoneConfiguration config = IdentityZoneHolder.get().getConfig(); - if (config==null) { + if (config == null) { config = new IdentityZoneConfiguration(); } - WhitelistLogoutHandler handler = new WhitelistLogoutHandler(config.getLinks().getLogout().getWhitelist()); + WhitelistLogoutSuccessHandler handler = new WhitelistLogoutSuccessHandler(config.getLinks().getLogout().getWhitelist()); handler.setTargetUrlParameter(config.getLinks().getLogout().getRedirectParameterName()); handler.setDefaultTargetUrl(config.getLinks().getLogout().getRedirectUrl()); handler.setAlwaysUseDefaultTargetUrl(config.getLinks().getLogout().isDisableRedirectParameter()); @@ -82,5 +81,4 @@ protected WhitelistLogoutHandler getZoneHandler() { handler.setKeyInfoService(keyInfoService); return handler; } - } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java index 34b73f79526..7c79f56cb4e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java @@ -22,7 +22,6 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/UserLockoutPolicyRetriever.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/UserLockoutPolicyRetriever.java index 8fb45462635..6d2a0e65b86 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/UserLockoutPolicyRetriever.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/UserLockoutPolicyRetriever.java @@ -16,7 +16,6 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.ObjectUtils; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/cache/StaleUrlCache.java b/server/src/main/java/org/cloudfoundry/identity/uaa/cache/StaleUrlCache.java index 090857dd853..3ebb80e8306 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/cache/StaleUrlCache.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/cache/StaleUrlCache.java @@ -7,12 +7,10 @@ import com.google.common.util.concurrent.UncheckedExecutionException; import lombok.extern.slf4j.Slf4j; import org.cloudfoundry.identity.uaa.util.TimeService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; @@ -22,13 +20,11 @@ import java.time.Instant; @Slf4j -@Component public class StaleUrlCache implements UrlContentCache { private static final int DEFAULT_MAX_ENTRIES = 10_000; private final LoadingCache cache; - @Autowired public StaleUrlCache(final TimeService timeService) { this(timeService, Ticker.systemTicker()); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java index cb78f6498d4..0a7c54d3732 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java @@ -1,20 +1,8 @@ package org.cloudfoundry.identity.uaa.home; -import static java.util.Objects.nonNull; -import static org.cloudfoundry.identity.uaa.ratelimiting.RateLimitingFilter.RATE_LIMIT_ERROR_ATTRIBUTE; -import static org.springframework.util.StringUtils.hasText; - -import javax.servlet.RequestDispatcher; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.Principal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; import org.cloudfoundry.identity.uaa.client.ClientMetadata; import org.cloudfoundry.identity.uaa.client.JdbcClientMetadataProvisioning; import org.cloudfoundry.identity.uaa.util.SessionUtils; @@ -23,29 +11,35 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.Links; -import org.opensaml.common.SAMLException; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.Getter; +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static java.util.Objects.nonNull; +import static org.cloudfoundry.identity.uaa.ratelimiting.RateLimitingFilter.RATE_LIMIT_ERROR_ATTRIBUTE; +import static org.springframework.util.StringUtils.hasText; @SuppressWarnings("SpringMVCViewInspection") @Controller @@ -69,7 +63,7 @@ public HomeController( } private void populateBuildAndLinkInfo(Model model) { - model.addAllAttributes( new HashMap<>() ); + model.addAllAttributes(new HashMap<>()); } @RequestMapping(value = {"/", "/home"}) @@ -81,14 +75,14 @@ public String home(Model model, Principal principal) { globalLinks != null && globalLinks.getHomeRedirect() != null ? globalLinks.getHomeRedirect() : null; if (homePage != null && !"/".equals(homePage) && !"/home".equals(homePage)) { - homePage = UaaStringUtils.replaceZoneVariables( homePage, identityZone ); + homePage = UaaStringUtils.replaceZoneVariables(homePage, identityZone); return "redirect:" + homePage; } model.addAttribute("principal", principal); List tiles = new ArrayList<>(); - List clientMetadataList = clientMetadataProvisioning.retrieveAll( identityZone.getId()); + List clientMetadataList = clientMetadataProvisioning.retrieveAll(identityZone.getId()); clientMetadataList.stream() .filter(this::shouldShowClient) @@ -112,10 +106,10 @@ private TileData tileDataForClient(ClientMetadata clientMetadata) { } return TileData.builder() - .clientId( clientMetadata.getClientId() ) - .appLaunchUrl( clientMetadata.getAppLaunchUrl().toString() ) - .appIcon( "data:image/png;base64," + clientMetadata.getAppIcon() ) - .clientName( clientName ) + .clientId(clientMetadata.getClientId()) + .appLaunchUrl(clientMetadata.getAppLaunchUrl().toString()) + .appIcon("data:image/png;base64," + clientMetadata.getAppIcon()) + .clientName(clientName) .build(); } @@ -130,8 +124,7 @@ public String error500(Model model, HttpServletRequest request, HttpServletRespo // check for common SAML related exceptions and redirect these to bad_request if (nonNull(genericException) && - (genericException.getCause() instanceof SAMLException || genericException.getCause() instanceof MetadataProviderException)) { - Exception samlException = (Exception) genericException.getCause(); + (genericException.getCause() instanceof Saml2Exception samlException)) { model.addAttribute("saml_error", samlException.getMessage()); response.setStatus(400); return EXTERNAL_AUTH_ERROR; @@ -141,18 +134,20 @@ public String error500(Model model, HttpServletRequest request, HttpServletRespo return ERROR; } - @RequestMapping(path = "/error429", method = { RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT, RequestMethod.PATCH }) //NOSONAR + @SuppressWarnings("java:S3752") + @RequestMapping(path = "/error429", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT, RequestMethod.PATCH}) @ResponseStatus(HttpStatus.TOO_MANY_REQUESTS) @ResponseBody public JsonError error429Json(HttpServletRequest request) { - if (request.getAttribute(RATE_LIMIT_ERROR_ATTRIBUTE) instanceof String) { - return new JsonError((String) request.getAttribute(RATE_LIMIT_ERROR_ATTRIBUTE)); + if (request.getAttribute(RATE_LIMIT_ERROR_ATTRIBUTE) instanceof String rateLimitErrorString) { + return new JsonError(rateLimitErrorString); } else { return new JsonError("Too Many Requests"); } } - @RequestMapping(path="/error429", produces = MediaType.TEXT_HTML_VALUE, method = { RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT, RequestMethod.PATCH }) //NOSONAR + @SuppressWarnings("java:S3752") + @RequestMapping(path = "/error429", produces = MediaType.TEXT_HTML_VALUE, method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT, RequestMethod.PATCH}) public String error429(Model model, HttpServletRequest request) { model.addAttribute(RATE_LIMIT_ERROR_ATTRIBUTE, request.getAttribute(RATE_LIMIT_ERROR_ATTRIBUTE)); return "error429"; @@ -188,8 +183,8 @@ public String error_oauth(Model model, HttpServletRequest request) { @RequestMapping("/rejected") @ResponseStatus(HttpStatus.BAD_REQUEST) public String handleRequestRejected(Model model, - @RequestAttribute(RequestDispatcher.ERROR_EXCEPTION) RequestRejectedException ex, - @RequestAttribute(RequestDispatcher.ERROR_REQUEST_URI) String uri) { + @RequestAttribute(RequestDispatcher.ERROR_EXCEPTION) RequestRejectedException ex, + @RequestAttribute(RequestDispatcher.ERROR_REQUEST_URI) String uri) { logger.error("Request with encoded URI [{}] rejected. {}", URLEncoder.encode(uri, StandardCharsets.UTF_8), ex.getMessage()); model.addAttribute("oauth_error", "The request was rejected because it contained a potentially malicious character."); @@ -212,9 +207,6 @@ private static class TileData { private final String clientName; } - @Data - @AllArgsConstructor - private static class JsonError { - private final String error; + public record JsonError(String error) { } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java index b7b1adb29ac..07df69142fe 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java @@ -13,10 +13,9 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.impl.config; - -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; @@ -32,6 +31,7 @@ import org.cloudfoundry.identity.uaa.util.LdapUtils; import org.cloudfoundry.identity.uaa.util.UaaMapUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; @@ -51,35 +51,41 @@ import static java.util.Collections.emptyList; import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toList; import static org.cloudfoundry.identity.uaa.authentication.SystemAuthentication.SYSTEM_AUTHENTICATION; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition.LDAP_PROPERTY_NAMES; import static org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition.LDAP_PROPERTY_TYPES; +@Slf4j public class IdentityProviderBootstrap - implements InitializingBean, ApplicationListener, ApplicationEventPublisherAware { - private static Logger logger = LoggerFactory.getLogger(IdentityProviderBootstrap.class); + implements InitializingBean, ApplicationListener, ApplicationEventPublisherAware { - private IdentityProviderProvisioning provisioning; - private List providers = new LinkedList<>(); + private final IdentityProviderProvisioning provisioning; + private final List providers = new LinkedList<>(); + private final Environment environment; private BootstrapSamlIdentityProviderData configurator; private List oauthIdpDefintions; + @Setter private Map ldapConfig; + @Setter private Map keystoneConfig; - private Environment environment; + @Setter private PasswordPolicy defaultPasswordPolicy; + @Setter private LockoutPolicy defaultLockoutPolicy; + @Getter + @Setter private boolean disableInternalUserManagement; + @Setter private List originsToDelete = null; private ApplicationEventPublisher publisher; public IdentityProviderBootstrap( final @Qualifier("identityProviderProvisioning") IdentityProviderProvisioning provisioning, Environment environment) { - if (provisioning==null) { + if (provisioning == null) { throw new NullPointerException("Constructor argument can't be null."); } this.provisioning = provisioning; @@ -98,7 +104,7 @@ private void addOauthProviders() { } public void validateDuplicateAlias(String originKey) { - for (IdentityProvider provider: providers.stream().map(IdentityProviderWrapper::getProvider).collect(toList())) { + for (IdentityProvider provider : providers.stream().map(IdentityProviderWrapper::getProvider).toList()) { if (provider.getOriginKey().equals(originKey)) { throw new IllegalArgumentException("Provider alias " + originKey + " is not unique."); } @@ -108,8 +114,9 @@ public void validateDuplicateAlias(String originKey) { public void setSamlProviders(BootstrapSamlIdentityProviderData configurator) { this.configurator = configurator; } + protected void addSamlProviders() { - if (configurator==null) { + if (configurator == null) { return; } for (IdentityProviderWrapper wrapper : configurator.getSamlProviders()) { @@ -119,20 +126,16 @@ protected void addSamlProviders() { } - public void setLdapConfig(HashMap ldapConfig) { - this.ldapConfig = ldapConfig; - } - protected void addLdapProvider() { boolean ldapProfile = Arrays.asList(environment.getActiveProfiles()).contains(LDAP); //the LDAP provider has to be there //and we activate, deactivate based on the `ldap` profile presence - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setActive(ldapProfile); provider.setOriginKey(LDAP); provider.setType(LDAP); provider.setName("UAA LDAP Provider"); - Map ldap = new HashMap<>(); + Map ldap = new HashMap<>(); ldap.put(LdapIdentityProviderDefinition.LDAP, ldapConfig); LdapIdentityProviderDefinition json = getLdapConfigAsDefinition(ldap); provider.setConfig(json); @@ -141,18 +144,16 @@ protected void addLdapProvider() { LDAP is a bit tricky. We have a Flyway conversion (2.0.2) that always adds an LDAP provider. So we have to assume that if LDAP config == null, then we should override it */ - boolean override = ldapConfig == null || ldapConfig.get("override") == null ? true : (boolean) ldapConfig.get("override"); + boolean override = ldapConfig == null || ldapConfig.get("override") == null || (boolean) ldapConfig.get("override"); if (!override) { IdentityProvider existing = getProviderByOriginIgnoreActiveFlag(LDAP, IdentityZone.getUaaZoneId()); override = existing == null || existing.getConfig() == null; } - IdentityProviderWrapper wrapper = new IdentityProviderWrapper(provider); + IdentityProviderWrapper wrapper = new IdentityProviderWrapper<>(provider); wrapper.setOverride(override); providers.add(wrapper); } - - protected LdapIdentityProviderDefinition getLdapConfigAsDefinition(Map ldapConfig) { ldapConfig = UaaMapUtils.flatten(ldapConfig); populateLdapEnvironment(ldapConfig); @@ -164,16 +165,16 @@ protected LdapIdentityProviderDefinition getLdapConfigAsDefinition(Map ldapConfig) { //this method reads the environment and overwrites values (needed by LdapMockMvcTests that overrides properties through env) - AbstractEnvironment env = (AbstractEnvironment)environment; + AbstractEnvironment env = (AbstractEnvironment) environment; //these are our known complex data structures in the properties for (String property : LDAP_PROPERTY_NAMES) { - if (env.containsProperty(property) && LDAP_PROPERTY_TYPES.get(property)!=null) { + if (env.containsProperty(property) && LDAP_PROPERTY_TYPES.get(property) != null) { ldapConfig.put(property, env.getProperty(property, LDAP_PROPERTY_TYPES.get(property))); } } //but we can also have string properties like ldap.attributeMappings.user.attribute.mapToAttributeName=mapFromAttributeName - Map stringProperties = UaaMapUtils.getPropertiesStartingWith(env, "ldap."); + Map stringProperties = UaaMapUtils.getPropertiesStartingWith(env, "ldap."); for (Map.Entry entry : stringProperties.entrySet()) { if (!LDAP_PROPERTY_NAMES.contains(entry.getKey())) { ldapConfig.put(entry.getKey(), entry.getValue()); @@ -181,10 +182,6 @@ protected void populateLdapEnvironment(Map ldapConfig) { } } - public void setKeystoneConfig(HashMap keystoneConfig) { - this.keystoneConfig = keystoneConfig; - } - protected AbstractIdentityProviderDefinition getKeystoneDefinition(Map config) { return new KeystoneIdentityProviderDefinition(config); } @@ -192,14 +189,14 @@ protected AbstractIdentityProviderDefinition getKeystoneDefinition(Map provider = new IdentityProvider<>(); provider.setOriginKey(OriginKeys.KEYSTONE); provider.setType(OriginKeys.KEYSTONE); provider.setName("UAA Keystone Provider"); provider.setActive(active); provider.setConfig(getKeystoneDefinition(keystoneConfig)); - providers.add(new IdentityProviderWrapper(provider)); + providers.add(new IdentityProviderWrapper<>(provider)); } } @@ -224,7 +221,7 @@ public void afterPropertiesSet() throws Exception { String zoneId = IdentityZone.getUaaZoneId(); - for (IdentityProviderWrapper wrapper: providers) { + for (IdentityProviderWrapper wrapper : providers) { IdentityProvider provider = wrapper.getProvider(); if (getOriginsToDelete().contains(provider.getOriginKey())) { //dont process origins slated for deletion @@ -232,7 +229,7 @@ public void afterPropertiesSet() throws Exception { } IdentityProvider existing = getProviderByOriginIgnoreActiveFlag(provider.getOriginKey(), zoneId); provider.setIdentityZoneId(zoneId); - if (existing==null) { + if (existing == null) { provisioning.create(provider, zoneId); } else if (wrapper.isOverride()) { provider.setId(existing.getId()); @@ -248,7 +245,7 @@ public void afterPropertiesSet() throws Exception { public IdentityProvider getProviderByOriginIgnoreActiveFlag(String origin, String zoneId) { try { return provisioning.retrieveByOriginIgnoreActiveFlag(origin, zoneId); - }catch (EmptyResultDataAccessException ignored){ + } catch (EmptyResultDataAccessException ignored) { } return null; @@ -257,19 +254,16 @@ public IdentityProvider getProviderByOriginIgnoreActiveFlag(String origin, Strin private void deleteIdentityProviders(String zoneId) { for (String origin : getOriginsToDelete()) { if (!UAA.equals(origin) && !LDAP.equals(origin)) { - logger.debug("Attempting to deactivating identity provider:"+origin); + log.debug("Attempting to deactivating identity provider: {}", origin); IdentityProvider provider = getProviderByOriginIgnoreActiveFlag(origin, zoneId); //delete provider if (provider != null) { EntityDeletedEvent event = new EntityDeletedEvent<>(provider, SYSTEM_AUTHENTICATION, IdentityZoneHolder.getCurrentZoneId()); if (this.publisher != null) { publisher.publishEvent(event); - logger.debug("Identity provider deactivated:" + origin); + log.debug("Identity provider deactivated: {}", origin); } else { - logger.warn( - String.format("Unable to delete identity provider with origin '%s', no application publisher", - origin) - ); + log.warn("Unable to delete identity provider with origin '{}', no application publisher", origin); } } } @@ -294,32 +288,11 @@ protected boolean getBooleanValue(String s, boolean defaultValue) { } } - public void setDefaultPasswordPolicy(PasswordPolicy defaultPasswordPolicy) { - this.defaultPasswordPolicy = defaultPasswordPolicy; - } - - public void setDefaultLockoutPolicy(LockoutPolicy defaultLockoutPolicy) { - this.defaultLockoutPolicy = defaultLockoutPolicy; - } - - public boolean isDisableInternalUserManagement() { - return disableInternalUserManagement; - } - - public void setDisableInternalUserManagement(boolean disableInternalUserManagement) { - this.disableInternalUserManagement = disableInternalUserManagement; - } - public void setOauthIdpDefinitions(List oauthIdpDefintions) { this.oauthIdpDefintions = oauthIdpDefintions; } - public void setOriginsToDelete(List originsToDelete) { - this.originsToDelete = originsToDelete; - } - public List getOriginsToDelete() { return ofNullable(originsToDelete).orElse(emptyList()); } - } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index c324cea4c0e..3e1be1e2b0f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -13,6 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.impl.config; +import lombok.Data; import org.cloudfoundry.identity.uaa.login.Prompt; import org.cloudfoundry.identity.uaa.saml.SamlKey; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -31,19 +32,20 @@ import java.util.Locale; import java.util.Map; -import static java.util.Collections.EMPTY_MAP; import static java.util.Objects.nonNull; import static java.util.Optional.ofNullable; import static org.springframework.util.StringUtils.hasText; +@Data public class IdentityZoneConfigurationBootstrap implements InitializingBean { private ClientSecretPolicy clientSecretPolicy; private TokenPolicy tokenPolicy; - private IdentityZoneProvisioning provisioning; + + private final IdentityZoneProvisioning provisioning; private boolean selfServiceLinksEnabled = true; private String homeRedirect = null; - private Map selfServiceLinks; + private Map selfServiceLinks; private List logoutRedirectWhitelist; private String logoutRedirectParameterName; private String logoutDefaultRedirectUrl; @@ -55,12 +57,13 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean { private String samlSpPrivateKeyPassphrase; private String samlSpCertificate; private boolean disableSamlInResponseToCheck = false; + private boolean samlWantAssertionSigned = true; + private boolean samlRequestSigned = true; private Map> samlKeys; private String activeKeyId; private boolean idpDiscoveryEnabled = false; - private boolean accountChooserEnabled; private UserConfig defaultUserConfig; @@ -68,10 +71,6 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean { private IdentityZoneValidator validator = (config, mode) -> config; private Map branding; - public void setValidator(IdentityZoneValidator validator) { - this.validator = validator; - } - public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) { this.provisioning = provisioning; } @@ -87,21 +86,23 @@ public void afterPropertiesSet() throws InvalidIdentityZoneDetailsException { definition.getSamlConfig().setPrivateKey(samlSpPrivateKey); definition.getSamlConfig().setPrivateKeyPassword(samlSpPrivateKeyPassphrase); definition.getSamlConfig().setDisableInResponseToCheck(disableSamlInResponseToCheck); + definition.getSamlConfig().setWantAssertionSigned(samlWantAssertionSigned); + definition.getSamlConfig().setRequestSigned(samlRequestSigned); definition.setIdpDiscoveryEnabled(idpDiscoveryEnabled); definition.setAccountChooserEnabled(accountChooserEnabled); definition.setDefaultIdentityProvider(defaultIdentityProvider); definition.setUserConfig(defaultUserConfig); - samlKeys = ofNullable(samlKeys).orElse(EMPTY_MAP); - for (Map.Entry> entry : samlKeys.entrySet()) { + samlKeys = ofNullable(samlKeys).orElse(Map.of()); + for (Map.Entry> entry : samlKeys.entrySet()) { SamlKey samlKey = new SamlKey(entry.getValue().get("key"), entry.getValue().get("passphrase"), entry.getValue().get("certificate")); definition.getSamlConfig().addKey(ofNullable(entry.getKey()).orElseThrow(() -> new InvalidIdentityZoneDetailsException("SAML key id must not be null.", null)).toLowerCase(Locale.ROOT), samlKey); } definition.getSamlConfig().setActiveKeyId(this.activeKeyId); - if (selfServiceLinks!=null) { - String signup = (String)selfServiceLinks.get("signup"); - String passwd = (String)selfServiceLinks.get("passwd"); + if (selfServiceLinks != null) { + String signup = (String) selfServiceLinks.get("signup"); + String passwd = (String) selfServiceLinks.get("passwd"); if (hasText(signup)) { definition.getLinks().getSelfService().setSignup(signup); } @@ -132,10 +133,6 @@ public void afterPropertiesSet() throws InvalidIdentityZoneDetailsException { provisioning.update(identityZone); } - public void setClientSecretPolicy(ClientSecretPolicy clientSecretPolicy) { - this.clientSecretPolicy = clientSecretPolicy; - } - public IdentityZoneConfigurationBootstrap setSamlKeys(Map> samlKeys) { this.samlKeys = samlKeys; return this; @@ -145,96 +142,4 @@ public IdentityZoneConfigurationBootstrap setActiveKeyId(String activeKeyId) { this.activeKeyId = activeKeyId != null ? activeKeyId.toLowerCase(Locale.ROOT) : null; return this; } - - public void setTokenPolicy(TokenPolicy tokenPolicy) { - this.tokenPolicy = tokenPolicy; - } - - public void setSelfServiceLinksEnabled(boolean selfServiceLinksEnabled) { - this.selfServiceLinksEnabled = selfServiceLinksEnabled; - } - - public void setHomeRedirect(String homeRedirect) { - this.homeRedirect = homeRedirect; - } - - public String getHomeRedirect() { - return homeRedirect; - } - - public void setSelfServiceLinks(Map links) { - this.selfServiceLinks = links; - } - - public void setLogoutDefaultRedirectUrl(String logoutDefaultRedirectUrl) { - this.logoutDefaultRedirectUrl = logoutDefaultRedirectUrl; - } - - public void setLogoutDisableRedirectParameter(boolean logoutDisableRedirectParameter) { - this.logoutDisableRedirectParameter = logoutDisableRedirectParameter; - } - - public void setLogoutRedirectParameterName(String logoutRedirectParameterName) { - this.logoutRedirectParameterName = logoutRedirectParameterName; - } - - public void setLogoutRedirectWhitelist(List logoutRedirectWhitelist) { - this.logoutRedirectWhitelist = logoutRedirectWhitelist; - } - - public void setPrompts(List prompts) { - this.prompts = prompts; - } - - public void setDefaultIdentityProvider(String defaultIdentityProvider) { - this.defaultIdentityProvider = defaultIdentityProvider; - } - - public void setSamlSpCertificate(String samlSpCertificate) { - this.samlSpCertificate = samlSpCertificate; - } - - public void setSamlSpPrivateKey(String samlSpPrivateKey) { - this.samlSpPrivateKey = samlSpPrivateKey; - } - - public void setSamlSpPrivateKeyPassphrase(String samlSpPrivateKeyPassphrase) { - this.samlSpPrivateKeyPassphrase = samlSpPrivateKeyPassphrase; - } - - public boolean isIdpDiscoveryEnabled() { - return idpDiscoveryEnabled; - } - - public void setIdpDiscoveryEnabled(boolean idpDiscoveryEnabled) { - this.idpDiscoveryEnabled = idpDiscoveryEnabled; - } - - public boolean isAccountChooserEnabled() { - return accountChooserEnabled; - } - - public void setAccountChooserEnabled(boolean accountChooserEnabled) { - this.accountChooserEnabled = accountChooserEnabled; - } - - public void setBranding(Map branding) { - this.branding = branding; - } - - public Map getBranding() { - return branding; - } - - public boolean isDisableSamlInResponseToCheck() { - return disableSamlInResponseToCheck; - } - - public void setDisableSamlInResponseToCheck(boolean disableSamlInResponseToCheck) { - this.disableSamlInResponseToCheck = disableSamlInResponseToCheck; - } - - public void setDefaultUserConfig(final UserConfig defaultUserConfig) { - this.defaultUserConfig = defaultUserConfig; - } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java index 8df3b94b410..26051f21bfe 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java @@ -144,7 +144,7 @@ public String acceptInvitePage(@RequestParam String code, Model model, HttpServl SamlIdentityProviderDefinition definition = ObjectUtils.castInstance(provider.getConfig(), SamlIdentityProviderDefinition.class); - String redirect = "redirect:/" + SamlRedirectUtils.getIdpRedirectUrl(definition, spEntityID, IdentityZoneHolder.get()); + String redirect = "redirect:/" + SamlRedirectUtils.getIdpRedirectUrl(definition); logger.debug(String.format("Redirecting invitation for email:%s, id:%s single SAML IDP URL:%s", codeData.get("email"), codeData.get("user_id"), redirect)); return redirect; } else if (OIDC10.equals(provider.getType()) || OAUTH20.equals(provider.getType())) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index b0c617589ad..e09dedc4422 100755 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -1,57 +1,6 @@ package org.cloudfoundry.identity.uaa.login; -import java.awt.Color; -import java.io.IOException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.security.Principal; -import java.sql.Timestamp; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import org.cloudfoundry.identity.uaa.provider.NoSuchClientException; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.support.PropertiesLoaderUtils; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetails; -import org.springframework.security.web.savedrequest.SavedRequest; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.util.StringUtils; -import org.springframework.web.HttpMediaTypeNotAcceptableException; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.util.UriComponentsBuilder; - +import lombok.extern.slf4j.Slf4j; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaLoginHint; @@ -61,10 +10,12 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetails; import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.NoSuchClientException; import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; @@ -84,8 +35,57 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.Links; import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.savedrequest.SavedRequest; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.util.StringUtils; +import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.awt.*; +import java.io.IOException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.security.Principal; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Base64.getDecoder; @@ -95,17 +95,15 @@ import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.addSubdomainToUrl; import static org.springframework.util.StringUtils.hasText; -import static org.springframework.web.bind.annotation.RequestMethod.GET; /** * Controller that sends login info (e.g. prompts) to clients wishing to * authenticate. */ @Controller +@Slf4j public class LoginInfoEndpoint { - private static Logger logger = LoggerFactory.getLogger(LoginInfoEndpoint.class); - private static final String CREATE_ACCOUNT_LINK = "createAccountLink"; private static final String FORGOT_PASSWORD_LINK = "forgotPasswordLink"; private static final String LINK_CREATE_ACCOUNT_SHOW = "linkCreateAccountShow"; @@ -119,7 +117,23 @@ public class LoginInfoEndpoint { private static final String ENTITY_ID = "entityID"; private static final String IDP_DEFINITIONS = "idpDefinitions"; private static final String OAUTH_LINKS = "oauthLinks"; - + private static final String LOGIN_HINT_ATTRIBUTE = "login_hint"; + private static final String EMAIL_ATTRIBUTE = "email"; + private static final String ERROR_ATTRIBUTE = "error"; + private static final String USERNAME_PARAMETER = "username"; + private static final String CLIENT_ID_PARAMETER = "client_id"; + private static final String LOGIN = "login"; + private static final String REDIRECT = "redirect:"; + private static final MapCollector idpsMapCollector = + new MapCollector<>( + IdentityProvider::getOriginKey, + idp -> (AbstractExternalOAuthIdentityProviderDefinition) idp.getConfig() + ); + //http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string + // Pattern for recognizing a URL, based off RFC 3986 + private static final Pattern urlPattern = Pattern.compile( + "((https?|ftp|gopher|telnet|file):((//)|(\\\\))+[\\w\\d:#@%/;$()~_?\\+-=\\\\\\.&]*)", + Pattern.CASE_INSENSITIVE); private final Properties gitProperties; private final Properties buildProperties; private final String baseUrl; @@ -133,12 +147,6 @@ public class LoginInfoEndpoint { private final Links globalLinks; private final String entityID; - private static final MapCollector idpsMapCollector = - new MapCollector<>( - IdentityProvider::getOriginKey, - idp -> (AbstractExternalOAuthIdentityProviderDefinition) idp.getConfig() - ); - public LoginInfoEndpoint( final @Qualifier("zoneAwareAuthzAuthenticationManager") AuthenticationManager authenticationManager, final @Qualifier("codeStore") ExpiringCodeStore expiringCodeStore, @@ -172,6 +180,35 @@ private static Properties tryLoadAllProperties(final String fileName) { } } + private static List getSavedAccounts(Cookie[] cookies, Class clazz) { + return Arrays.stream(ofNullable(cookies).orElse(new Cookie[]{})) + .filter(c -> c.getName().startsWith("Saved-Account")) + .map(c -> { + try { + return JsonUtils.readValue(decodeCookieValue(c.getValue()), clazz); + } catch (JsonUtilException e) { + return null; + } + }) + .filter(Objects::nonNull) + .toList(); + } + + private static String decodeCookieValue(String inValue) { + try { + return URLDecoder.decode(inValue, UTF_8); + } catch (Exception e) { + log.debug("URLDecoder.decode failed for {}", inValue, e); + return ""; + } + } + + private static Map concatenateMaps(Map samlIdentityProviders, Map oauthIdentityProviders) { + Map allIdentityProviders = new HashMap<>(samlIdentityProviders); + allIdentityProviders.putAll(oauthIdentityProviders); + return allIdentityProviders; + } + @RequestMapping(value = {"/login"}, headers = "Accept=application/json") public String infoForLoginJson(Model model, Principal principal, HttpServletRequest request) { return login(model, principal, Collections.emptyList(), true, request); @@ -182,19 +219,6 @@ public String infoForJson(Model model, Principal principal, HttpServletRequest r return login(model, principal, Collections.emptyList(), true, request); } - static class SavedAccountOptionModel extends SavedAccountOption { - /** - * These must be public. They are accessed in templates. - */ - public int red, green, blue; - - void assignColors(Color color) { - red = color.getRed(); - blue = color.getBlue(); - green = color.getGreen(); - } - } - @RequestMapping(value = {"/login"}, headers = "Accept=text/html, */*") public String loginForHtml(Model model, Principal principal, @@ -217,30 +241,7 @@ public String loginForHtml(Model model, model.addAttribute("savedAccounts", savedAccounts); - return login(model, principal, Arrays.asList(PASSCODE), false, request); - } - - private static List getSavedAccounts(Cookie[] cookies, Class clazz) { - return Arrays.stream(ofNullable(cookies).orElse(new Cookie[]{})) - .filter(c -> c.getName().startsWith("Saved-Account")) - .map(c -> { - try { - return JsonUtils.readValue(decodeCookieValue(c.getValue()), clazz); - } catch (JsonUtilException e) { - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - private static String decodeCookieValue(String inValue) { - try { - return URLDecoder.decode(inValue, UTF_8.name()); - } catch (Exception e) { - logger.debug("URLDecoder.decode failed for " + inValue, e); - return ""; - } + return login(model, principal, List.of(PASSCODE), false, request); } @RequestMapping(value = {"/invalid_request"}) @@ -253,7 +254,7 @@ protected String getZonifiedEntityId() { } private String login(Model model, Principal principal, List excludedPrompts, boolean jsonResponse, HttpServletRequest request) { - if (principal instanceof UaaAuthentication && ((UaaAuthentication) principal).isAuthenticated()) { + if (principal instanceof UaaAuthentication uaaPrincipal && uaaPrincipal.isAuthenticated()) { return "redirect:/home"; } @@ -287,41 +288,40 @@ private String login(Model model, Principal principal, List excludedProm Map samlIdentityProviders; Map oauthIdentityProviders; - Map allIdentityProviders = Collections.emptyMap(); - Map loginHintProviders = Collections.emptyMap(); + Map allIdentityProviders = Map.of(); + Map loginHintProviders = Map.of(); if (uaaLoginHint != null && (allowedIdentityProviderKeys == null || allowedIdentityProviderKeys.contains(uaaLoginHint.getOrigin()))) { - // Login hint: Only try to read the hinted IdP from database + // Login hint: Only try to read the hinted IdP from the database if (!(OriginKeys.UAA.equals(uaaLoginHint.getOrigin()) || OriginKeys.LDAP.equals(uaaLoginHint.getOrigin()))) { try { IdentityProvider loginHintProvider = externalOAuthProviderConfigurator .retrieveByOrigin(uaaLoginHint.getOrigin(), IdentityZoneHolder.get().getId()); - loginHintProviders = Collections.singletonList(loginHintProvider).stream().collect( + loginHintProviders = Stream.of(loginHintProvider).collect( new MapCollector( IdentityProvider::getOriginKey, IdentityProvider::getConfig)); } catch (EmptyResultDataAccessException ignored) { + // ignore } } if (!loginHintProviders.isEmpty()) { - oauthIdentityProviders = Collections.emptyMap(); - samlIdentityProviders = Collections.emptyMap(); + oauthIdentityProviders = Map.of(); + samlIdentityProviders = Map.of(); } else { accountChooserNeeded = false; samlIdentityProviders = getSamlIdentityProviderDefinitions(allowedIdentityProviderKeys); oauthIdentityProviders = getOauthIdentityProviderDefinitions(allowedIdentityProviderKeys); - allIdentityProviders = new HashMap<>(); - allIdentityProviders.putAll(samlIdentityProviders); - allIdentityProviders.putAll(oauthIdentityProviders); + allIdentityProviders = concatenateMaps(samlIdentityProviders, oauthIdentityProviders); } } else if (!jsonResponse && (accountChooserNeeded || (accountChooserEnabled && !discoveryEnabled && !discoveryPerformed))) { // when `/login` is requested to return html response (as opposed to json response) //Account and origin chooser do not need idp information - oauthIdentityProviders = Collections.emptyMap(); - samlIdentityProviders = Collections.emptyMap(); + oauthIdentityProviders = Map.of(); + samlIdentityProviders = Map.of(); } else { samlIdentityProviders = getSamlIdentityProviderDefinitions(allowedIdentityProviderKeys); oauthIdentityProviders = getOauthIdentityProviderDefinitions(allowedIdentityProviderKeys); - allIdentityProviders = new HashMap<>() {{putAll(samlIdentityProviders);putAll(oauthIdentityProviders);}}; + allIdentityProviders = concatenateMaps(samlIdentityProviders, oauthIdentityProviders); } boolean fieldUsernameShow = true; @@ -332,15 +332,14 @@ private String login(Model model, Principal principal, List excludedProm OriginKeys.LDAP, IdentityZoneHolder.get().getId() ); } catch (EmptyResultDataAccessException ignored) { + // ignore } IdentityProvider uaaIdentityProvider = providerProvisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, IdentityZoneHolder.get().getId()); // ldap and uaa disabled removes username/password input boxes - if (!uaaIdentityProvider.isActive()) { - if (ldapIdentityProvider == null || !ldapIdentityProvider.isActive()) { - fieldUsernameShow = false; - returnLoginPrompts = false; - } + if (!uaaIdentityProvider.isActive() && (ldapIdentityProvider == null || !ldapIdentityProvider.isActive())) { + fieldUsernameShow = false; + returnLoginPrompts = false; } // ldap or uaa not part of allowedIdentityProviderKeys @@ -358,14 +357,14 @@ private String login(Model model, Principal principal, List excludedProm idpForRedirect = evaluateIdpDiscovery(model, samlIdentityProviders, oauthIdentityProviders, allIdentityProviders, allowedIdentityProviderKeys, idpForRedirect, discoveryPerformed, newLoginPageEnabled, defaultIdentityProviderName); if (idpForRedirect == null && !jsonResponse && !fieldUsernameShow && allIdentityProviders.size() == 1) { - idpForRedirect = allIdentityProviders.entrySet().stream().findAny().get(); + idpForRedirect = allIdentityProviders.entrySet().stream().findFirst().orElse(null); } if (idpForRedirect != null) { String externalRedirect = redirectToExternalProvider( idpForRedirect.getValue(), idpForRedirect.getKey(), request ); if (externalRedirect != null && !jsonResponse) { - logger.debug("Following external redirect : " + externalRedirect); + log.debug("Following external redirect : {}", externalRedirect); return externalRedirect; } } @@ -374,16 +373,16 @@ private String login(Model model, Principal principal, List excludedProm if (fieldUsernameShow && (allowedIdentityProviderKeys != null) && ((!discoveryEnabled && !accountChooserEnabled) || discoveryPerformed)) { if (!allowedIdentityProviderKeys.contains(OriginKeys.UAA)) { linkCreateAccountShow = false; - model.addAttribute("login_hint", new UaaLoginHint(OriginKeys.LDAP).toString()); + model.addAttribute(LOGIN_HINT_ATTRIBUTE, new UaaLoginHint(OriginKeys.LDAP).toString()); } else if (!allowedIdentityProviderKeys.contains(OriginKeys.LDAP)) { - model.addAttribute("login_hint", new UaaLoginHint(OriginKeys.UAA).toString()); + model.addAttribute(LOGIN_HINT_ATTRIBUTE, new UaaLoginHint(OriginKeys.UAA).toString()); } } String zonifiedEntityID = getZonifiedEntityId(); - Map links = getLinksInfo(); + Map links = getLinksInfo(); if (jsonResponse) { - setJsonInfo(model, samlIdentityProviders, zonifiedEntityID, links); + setJsonInfo(model, samlIdentityProviders, links); } else { updateLoginPageModel(model, request, clientName, samlIdentityProviders, oauthIdentityProviders, fieldUsernameShow, linkCreateAccountShow); @@ -396,12 +395,12 @@ private String login(Model model, Principal principal, List excludedProm model.addAttribute(ENTITY_ID, zonifiedEntityID); excludedPrompts = new LinkedList<>(excludedPrompts); - String origin = request != null ? request.getParameter("origin") : null; + String origin = request.getParameter("origin"); populatePrompts(model, excludedPrompts, origin, samlIdentityProviders, oauthIdentityProviders, excludedPrompts, returnLoginPrompts); if (principal == null) { - return getUnauthenticatedRedirect(model, request, discoveryEnabled, discoveryPerformed, accountChooserNeeded ,accountChooserEnabled); + return getUnauthenticatedRedirect(model, request, discoveryEnabled, discoveryPerformed, accountChooserNeeded, accountChooserEnabled); } return "home"; } @@ -425,21 +424,21 @@ private String getUnauthenticatedRedirect( if (!discoveryPerformed) { return "idp_discovery/email"; } - return goToPasswordPage(request.getParameter("email"), model); + return goToPasswordPage(request.getParameter(EMAIL_ATTRIBUTE), model); } if (accountChooserEnabled) { - if (model.containsAttribute("login_hint")) { - return goToPasswordPage(request.getParameter("email"), model); + if (model.containsAttribute(LOGIN_HINT_ATTRIBUTE)) { + return goToPasswordPage(request.getParameter(EMAIL_ATTRIBUTE), model); } - if (model.containsAttribute("error")) { + if (model.containsAttribute(ERROR_ATTRIBUTE)) { return "idp_discovery/account_chooser"; } if (discoveryPerformed) { - return goToPasswordPage(request.getParameter("email"), model); + return goToPasswordPage(request.getParameter(EMAIL_ATTRIBUTE), model); } return "idp_discovery/origin"; } - return "login"; + return LOGIN; } private void updateLoginPageModel( @@ -474,8 +473,7 @@ private void updateLoginPageModel( private void setJsonInfo( Model model, Map samlIdentityProviders, - String zonifiedEntityID, - Map links + Map links ) { for (String attribute : UI_ONLY_ATTRIBUTES) { links.remove(attribute); @@ -483,10 +481,7 @@ private void setJsonInfo( Map idpDefinitionsForJson = new HashMap<>(); if (samlIdentityProviders != null) { for (SamlIdentityProviderDefinition def : samlIdentityProviders.values()) { - String idpUrl = links.get("login") + - String.format("/saml/discovery?returnIDParam=idp&entityID=%s&idp=%s&isPassive=true", - zonifiedEntityID, - def.getIdpEntityAlias()); + String idpUrl = "%s/saml2/authenticate/%s".formatted(links.get(LOGIN), def.getIdpEntityAlias()); idpDefinitionsForJson.put(def.getIdpEntityAlias(), idpUrl); } model.addAttribute(IDP_DEFINITIONS, idpDefinitionsForJson); @@ -504,7 +499,8 @@ private Map.Entry evaluateIdpDiscove boolean newLoginPageEnabled, String defaultIdentityProviderName ) { - if (idpForRedirect == null && (discoveryPerformed || !newLoginPageEnabled) && defaultIdentityProviderName != null && !model.containsAttribute("login_hint") && !model.containsAttribute("error")) { //Default set, no login_hint given, no error, discovery performed + // Default set, no login_hint given, no error, discovery performed + if (idpForRedirect == null && (discoveryPerformed || !newLoginPageEnabled) && defaultIdentityProviderName != null && !model.containsAttribute(LOGIN_HINT_ATTRIBUTE) && !model.containsAttribute(ERROR_ATTRIBUTE)) { if (!OriginKeys.UAA.equals(defaultIdentityProviderName) && !OriginKeys.LDAP.equals(defaultIdentityProviderName)) { if (allIdentityProviders.containsKey(defaultIdentityProviderName)) { idpForRedirect = @@ -512,7 +508,7 @@ private Map.Entry evaluateIdpDiscove } } else if (allowedIdentityProviderKeys == null || allowedIdentityProviderKeys.contains(defaultIdentityProviderName)) { UaaLoginHint loginHint = new UaaLoginHint(defaultIdentityProviderName); - model.addAttribute("login_hint", loginHint.toString()); + model.addAttribute(LOGIN_HINT_ATTRIBUTE, loginHint.toString()); samlIdentityProviders.clear(); oauthIdentityProviders.clear(); } @@ -521,13 +517,11 @@ private Map.Entry evaluateIdpDiscove } private String extractLoginHintParam(HttpSession session, HttpServletRequest request) { - String loginHintParam = - ofNullable(session) - .flatMap(s -> ofNullable(SessionUtils.getSavedRequestSession(s))) - .flatMap(sr -> ofNullable(sr.getParameterValues("login_hint"))) - .flatMap(lhValues -> Arrays.stream(lhValues).findFirst()) - .orElse(request.getParameter("login_hint")); - return loginHintParam; + return ofNullable(session) + .flatMap(s -> ofNullable(SessionUtils.getSavedRequestSession(s))) + .flatMap(sr -> ofNullable(sr.getParameterValues(LOGIN_HINT_ATTRIBUTE))) + .flatMap(lhValues -> Arrays.stream(lhValues).findFirst()) + .orElse(request.getParameter(LOGIN_HINT_ATTRIBUTE)); } private Map.Entry evaluateLoginHint( @@ -544,16 +538,16 @@ private Map.Entry evaluateLoginHint( if (loginHintParam != null) { // parse login_hint in JSON format if (uaaLoginHint != null) { - logger.debug("Received login hint: {}", UaaStringUtils.getCleanedUserControlString(loginHintParam)); - logger.debug("Received login hint with origin: " + uaaLoginHint.getOrigin()); + log.debug("Received login hint: {}", UaaStringUtils.getCleanedUserControlString(loginHintParam)); + log.debug("Received login hint with origin: {}", uaaLoginHint.getOrigin()); if (OriginKeys.UAA.equals(uaaLoginHint.getOrigin()) || OriginKeys.LDAP.equals(uaaLoginHint.getOrigin())) { if (allowedIdentityProviderKeys == null || allowedIdentityProviderKeys.contains(uaaLoginHint.getOrigin())) { // in case of uaa/ldap, pass value to login page - model.addAttribute("login_hint", loginHintParam); + model.addAttribute(LOGIN_HINT_ATTRIBUTE, loginHintParam); samlIdentityProviders.clear(); oauthIdentityProviders.clear(); } else { - model.addAttribute("error", "invalid_login_hint"); + model.addAttribute(ERROR_ATTRIBUTE, "invalid_login_hint"); } } else { // for oidc/saml, trigger the redirect @@ -564,11 +558,11 @@ private Map.Entry evaluateLoginHint( } if (loginHintProviders.size() == 1) { idpForRedirect = new ArrayList<>(loginHintProviders.entrySet()).get(0); - logger.debug("Setting redirect from origin login_hint to: " + idpForRedirect); + log.debug("Setting redirect from origin login_hint to: {}", idpForRedirect); } else { - logger.debug("Client does not allow provider for login_hint with origin key: " - + uaaLoginHint.getOrigin()); - model.addAttribute("error", "invalid_login_hint"); + log.debug("Client does not allow provider for login_hint with origin key: {}", + uaaLoginHint.getOrigin()); + model.addAttribute(ERROR_ATTRIBUTE, "invalid_login_hint"); } } } else { @@ -577,14 +571,14 @@ private Map.Entry evaluateLoginHint( allIdentityProviders.entrySet().stream().filter( idp -> ofNullable(idp.getValue().getEmailDomain()).orElse(Collections.emptyList()).contains( loginHintParam) - ).collect(Collectors.toList()); + ).toList(); if (matchingIdentityProviders.size() > 1) { throw new IllegalStateException( "There is a misconfiguration with the identity provider(s). Please contact your system administrator." ); } else if (matchingIdentityProviders.size() == 1) { idpForRedirect = matchingIdentityProviders.get(0); - logger.debug("Setting redirect from email domain login hint to: " + idpForRedirect); + log.debug("Setting redirect from email domain login hint to: {}", idpForRedirect); } } } @@ -600,15 +594,14 @@ public String deleteSavedAccount(HttpServletRequest request, HttpServletResponse return "redirect:/login"; } - private String redirectToExternalProvider(AbstractIdentityProviderDefinition idpForRedirect, String idpOriginKey, HttpServletRequest request) { if (idpForRedirect != null) { - if (idpForRedirect instanceof SamlIdentityProviderDefinition) { - String url = SamlRedirectUtils.getIdpRedirectUrl((SamlIdentityProviderDefinition) idpForRedirect, entityID, IdentityZoneHolder.get()); + if (idpForRedirect instanceof SamlIdentityProviderDefinition samlIdentityProviderDefinition) { + String url = SamlRedirectUtils.getIdpRedirectUrl(samlIdentityProviderDefinition); return "redirect:/" + url; - } else if (idpForRedirect instanceof AbstractExternalOAuthIdentityProviderDefinition) { - String redirectUrl = getRedirectUrlForExternalOAuthIDP(request, idpOriginKey, (AbstractExternalOAuthIdentityProviderDefinition) idpForRedirect); - return "redirect:" + redirectUrl; + } else if (idpForRedirect instanceof AbstractExternalOAuthIdentityProviderDefinition providerDefinition) { + String redirectUrl = getRedirectUrlForExternalOAuthIDP(request, idpOriginKey, providerDefinition); + return REDIRECT + redirectUrl; } } return null; @@ -616,8 +609,8 @@ private String redirectToExternalProvider(AbstractIdentityProviderDefinition idp private String getRedirectUrlForExternalOAuthIDP(HttpServletRequest request, String idpOriginKey, AbstractExternalOAuthIdentityProviderDefinition definition) { String idpAuthenticationUrl = externalOAuthProviderConfigurator.getIdpAuthenticationUrl(definition, idpOriginKey, request); - if (request.getParameter("username") != null && definition.getUserPropagationParameter() != null) { - idpAuthenticationUrl = UriComponentsBuilder.fromUriString(idpAuthenticationUrl).queryParam(definition.getUserPropagationParameter(), request.getParameter("username")).build().toUriString(); + if (request.getParameter(USERNAME_PARAMETER) != null && definition.getUserPropagationParameter() != null) { + idpAuthenticationUrl = UriComponentsBuilder.fromUriString(idpAuthenticationUrl).queryParam(definition.getUserPropagationParameter(), request.getParameter(USERNAME_PARAMETER)).build().toUriString(); } return idpAuthenticationUrl; } @@ -643,8 +636,8 @@ private boolean hasSavedOauthAuthorizeRequest(HttpSession session) { } SavedRequest savedRequest = SessionUtils.getSavedRequestSession(session); String redirectUrl = savedRequest.getRedirectUrl(); - String[] client_ids = savedRequest.getParameterValues("client_id"); - return redirectUrl != null && redirectUrl.contains("/oauth/authorize") && client_ids != null && client_ids.length != 0; + String[] clientIds = savedRequest.getParameterValues(CLIENT_ID_PARAMETER); + return redirectUrl != null && redirectUrl.contains("/oauth/authorize") && clientIds != null && clientIds.length != 0; } private Map getClientInfo(HttpSession session) { @@ -652,9 +645,9 @@ private Map getClientInfo(HttpSession session) { return null; } SavedRequest savedRequest = SessionUtils.getSavedRequestSession(session); - String[] client_ids = savedRequest.getParameterValues("client_id"); + String[] clientIds = savedRequest.getParameterValues(CLIENT_ID_PARAMETER); try { - ClientDetails clientDetails = clientDetailsService.loadClientByClientId(client_ids[0], IdentityZoneHolder.get().getId()); + ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientIds[0], IdentityZoneHolder.get().getId()); return clientDetails.getAdditionalInformation(); } catch (NoSuchClientException x) { return null; @@ -699,7 +692,7 @@ private void populatePrompts( excludedPrompts.add(PASSCODE); } if (!returnLoginPrompts) { - excludedPrompts.add("username"); + excludedPrompts.add(USERNAME_PARAMETER); excludedPrompts.add("password"); } @@ -714,14 +707,12 @@ private void populatePrompts( try { providerForOrigin = providerProvisioning.retrieveByOrigin(origin, IdentityZoneHolder.get().getId()); } catch (DataAccessException ignored) { + // ignore } - if (providerForOrigin != null) { - if (providerForOrigin.getConfig() instanceof OIDCIdentityProviderDefinition) { - OIDCIdentityProviderDefinition oidcConfig = (OIDCIdentityProviderDefinition) providerForOrigin.getConfig(); - List providerPrompts = oidcConfig.getPrompts(); - if (providerPrompts != null) { - prompts = providerPrompts; - } + if (providerForOrigin != null && providerForOrigin.getConfig() instanceof OIDCIdentityProviderDefinition oidcConfig) { + List providerPrompts = oidcConfig.getPrompts(); + if (providerPrompts != null) { + prompts = providerPrompts; } } } @@ -745,12 +736,6 @@ private void populatePrompts( model.addAttribute("prompts", map); } - //http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string - // Pattern for recognizing a URL, based off RFC 3986 - private static final Pattern urlPattern = Pattern.compile( - "((https?|ftp|gopher|telnet|file):((//)|(\\\\))+[\\w\\d:#@%/;$()~_?\\+-=\\\\\\.&]*)", - Pattern.CASE_INSENSITIVE); - private String extractUrlFromString(String s) { Matcher matcher = urlPattern.matcher(s); if (matcher.find()) { @@ -762,8 +747,8 @@ private String extractUrlFromString(String s) { return null; } - @RequestMapping(value = "/origin-chooser", method = RequestMethod.POST) - public String loginUsingOrigin(@RequestParam(required = false, name = "login_hint") String loginHint, Model model, HttpSession session, HttpServletRequest request) { + @PostMapping(value = "/origin-chooser") + public String loginUsingOrigin(@RequestParam(required = false, name = LOGIN_HINT_ATTRIBUTE) String loginHint) { if (!StringUtils.hasText(loginHint)) { return "redirect:/login?discoveryPerformed=true"; } @@ -771,27 +756,27 @@ public String loginUsingOrigin(@RequestParam(required = false, name = "login_hin return "redirect:/login?discoveryPerformed=true&login_hint=" + URLEncoder.encode(uaaLoginHint.toString(), UTF_8); } - - @RequestMapping(value = "/login/idp_discovery", method = RequestMethod.POST) - public String discoverIdentityProvider(@RequestParam String email, @RequestParam(required = false) String skipDiscovery, @RequestParam(required = false, name = "login_hint") String loginHint, @RequestParam(required = false, name = "username") String username,Model model, HttpSession session, HttpServletRequest request) { + @PostMapping(value = "/login/idp_discovery") + public String discoverIdentityProvider(@RequestParam String email, @RequestParam(required = false) String skipDiscovery, @RequestParam(required = false, name = LOGIN_HINT_ATTRIBUTE) String loginHint, @RequestParam(required = false, name = USERNAME_PARAMETER) String username, Model model, HttpSession session, HttpServletRequest request) { ClientDetails clientDetails = null; if (hasSavedOauthAuthorizeRequest(session)) { SavedRequest savedRequest = SessionUtils.getSavedRequestSession(session); - String[] client_ids = savedRequest.getParameterValues("client_id"); + String[] clientIds = savedRequest.getParameterValues(CLIENT_ID_PARAMETER); try { - clientDetails = clientDetailsService.loadClientByClientId(client_ids[0], IdentityZoneHolder.get().getId()); + clientDetails = clientDetailsService.loadClientByClientId(clientIds[0], IdentityZoneHolder.get().getId()); } catch (NoSuchClientException ignored) { + // ignore } } if (StringUtils.hasText(loginHint)) { - model.addAttribute("login_hint", loginHint); + model.addAttribute(LOGIN_HINT_ATTRIBUTE, loginHint); } List identityProviders = DomainFilter.filter(providerProvisioning.retrieveActive(IdentityZoneHolder.get().getId()), clientDetails, email, false); if (!StringUtils.hasText(skipDiscovery) && identityProviders.size() == 1) { IdentityProvider matchedIdp = identityProviders.get(0); if (matchedIdp.getType().equals(UAA)) { - model.addAttribute("login_hint", new UaaLoginHint("uaa").toString()); + model.addAttribute(LOGIN_HINT_ATTRIBUTE, new UaaLoginHint("uaa").toString()); return goToPasswordPage(email, model); } else { String redirectUrl; @@ -802,17 +787,17 @@ public String discoverIdentityProvider(@RequestParam String email, @RequestParam } if (StringUtils.hasText(email)) { - model.addAttribute("email", email); + model.addAttribute(EMAIL_ATTRIBUTE, email); } if (StringUtils.hasText(username)) { - model.addAttribute("username", username); + model.addAttribute(USERNAME_PARAMETER, username); } return "redirect:/login?discoveryPerformed=true"; } private String goToPasswordPage(String email, Model model) { model.addAttribute(ZONE_NAME, IdentityZoneHolder.get().getName()); - model.addAttribute("email", email); + model.addAttribute(EMAIL_ATTRIBUTE, email); String forgotPasswordLink; if ((forgotPasswordLink = getSelfServiceLinks().get(FORGOT_PASSWORD_LINK)) != null) { model.addAttribute(FORGOT_PASSWORD_LINK, forgotPasswordLink); @@ -820,10 +805,10 @@ private String goToPasswordPage(String email, Model model) { return "idp_discovery/password"; } - @RequestMapping(value = "/autologin", method = RequestMethod.POST) + @PostMapping(value = "/autologin") @ResponseBody public AutologinResponse generateAutologinCode(@RequestBody AutologinRequest request, - @RequestHeader(value = "Authorization", required = false) String auth) throws Exception { + @RequestHeader(value = "Authorization", required = false) String auth) { if (auth == null || (!auth.startsWith("Basic"))) { throw new BadCredentialsException("No basic authorization client information in request"); @@ -843,29 +828,26 @@ public AutologinResponse generateAutologinCode(@RequestBody AutologinRequest req } String base64Credentials = auth.substring("Basic".length()).trim(); - String credentials = new String(getDecoder().decode(base64Credentials.getBytes()), UTF_8.name()); + String credentials = new String(getDecoder().decode(base64Credentials.getBytes()), UTF_8); // credentials = username:password final String[] values = credentials.split(":", 2); - if (values == null || values.length == 0) { + if (values.length == 0) { throw new BadCredentialsException("Invalid authorization header."); } String clientId = values[0]; Map codeData = new HashMap<>(); - codeData.put("client_id", clientId); - codeData.put("username", username); - if (userAuthentication != null && userAuthentication.getPrincipal() instanceof UaaPrincipal) { - UaaPrincipal p = (UaaPrincipal) userAuthentication.getPrincipal(); - if (p != null) { - codeData.put("user_id", p.getId()); - codeData.put(OriginKeys.ORIGIN, p.getOrigin()); - } + codeData.put(CLIENT_ID_PARAMETER, clientId); + codeData.put(USERNAME_PARAMETER, username); + if (userAuthentication != null && userAuthentication.getPrincipal() instanceof UaaPrincipal p) { + codeData.put("user_id", p.getId()); + codeData.put(OriginKeys.ORIGIN, p.getOrigin()); } ExpiringCode expiringCode = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), ExpiringCodeType.AUTOLOGIN.name(), IdentityZoneHolder.get().getId()); return new AutologinResponse(expiringCode.getCode()); } - @RequestMapping(value = "/autologin", method = GET) + @GetMapping(value = "/autologin") public String performAutologin(HttpSession session) { String redirectLocation = "home"; SavedRequest savedRequest = SessionUtils.getSavedRequestSession(session); @@ -873,23 +855,23 @@ public String performAutologin(HttpSession session) { redirectLocation = savedRequest.getRedirectUrl(); } - return "redirect:" + redirectLocation; + return REDIRECT + redirectLocation; } - @RequestMapping(value = "/login_implicit", method = GET) + @GetMapping(value = "/login_implicit") public String captureImplicitValuesUsingJavascript() { return "login_implicit"; } - @RequestMapping(value = "/login/callback/{origin}") - public String handleExternalOAuthCallback(final HttpSession session) { + @GetMapping(value = "/login/callback/{origin}") + public String handleExternalOAuthCallback(final HttpSession session, @PathVariable String origin) { String redirectLocation = "/home"; SavedRequest savedRequest = SessionUtils.getSavedRequestSession(session); if (savedRequest != null && savedRequest.getRedirectUrl() != null) { redirectLocation = savedRequest.getRedirectUrl(); } - return "redirect:" + redirectLocation; + return REDIRECT + redirectLocation; } private Map getLinksInfo() { @@ -897,11 +879,11 @@ public String handleExternalOAuthCallback(final HttpSession session) { Map model = new HashMap<>(); model.put(OriginKeys.UAA, addSubdomainToUrl(baseUrl, IdentityZoneHolder.get().getSubdomain())); if (baseUrl.contains("localhost:")) { - model.put("login", addSubdomainToUrl(baseUrl, IdentityZoneHolder.get().getSubdomain())); + model.put(LOGIN, addSubdomainToUrl(baseUrl, IdentityZoneHolder.get().getSubdomain())); } else if (hasText(externalLoginUrl)) { - model.put("login", externalLoginUrl); + model.put(LOGIN, externalLoginUrl); } else { - model.put("login", addSubdomainToUrl(baseUrl.replaceAll(OriginKeys.UAA, "login"), IdentityZoneHolder.get().getSubdomain())); + model.put(LOGIN, addSubdomainToUrl(baseUrl.replaceAll(OriginKeys.UAA, LOGIN), IdentityZoneHolder.get().getSubdomain())); } model.putAll(getSelfServiceLinks()); return model; @@ -911,9 +893,9 @@ protected Map getSelfServiceLinks() { Map selfServiceLinks = new HashMap<>(); IdentityZone zone = IdentityZoneHolder.get(); IdentityProvider uaaIdp = providerProvisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, IdentityZoneHolder.get().getId()); - boolean disableInternalUserManagement = (uaaIdp.getConfig() != null) ? uaaIdp.getConfig().isDisableInternalUserManagement() : false; + boolean disableInternalUserManagement = uaaIdp.getConfig() != null && uaaIdp.getConfig().isDisableInternalUserManagement(); - boolean selfServiceLinksEnabled = (zone.getConfig() != null) ? zone.getConfig().getLinks().getSelfService().isSelfServiceLinksEnabled() : true; + boolean selfServiceLinksEnabled = zone.getConfig() == null || zone.getConfig().getLinks().getSelfService().isSelfServiceLinksEnabled(); final String defaultSignup = "/create_account"; final String defaultPasswd = "/forgot_password"; @@ -942,4 +924,19 @@ protected Map getSelfServiceLinks() { } return selfServiceLinks; } + + static class SavedAccountOptionModel extends SavedAccountOption { + /** + * These must be public. They are accessed in templates. + */ + public int red; + public int green; + public int blue; + + void assignColors(Color color) { + red = color.getRed(); + blue = color.getBlue(); + green = color.getGreen(); + } + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/UaaAuthenticationFailureHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/UaaAuthenticationFailureHandler.java index ce5e1957f76..755e084f549 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/UaaAuthenticationFailureHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/UaaAuthenticationFailureHandler.java @@ -28,8 +28,8 @@ import java.io.IOException; public class UaaAuthenticationFailureHandler implements AuthenticationFailureHandler, LogoutHandler { - private ExceptionMappingAuthenticationFailureHandler delegate; - private CurrentUserCookieFactory currentUserCookieFactory; + private final ExceptionMappingAuthenticationFailureHandler delegate; + private final CurrentUserCookieFactory currentUserCookieFactory; public UaaAuthenticationFailureHandler(ExceptionMappingAuthenticationFailureHandler delegate, CurrentUserCookieFactory currentUserCookieFactory) { this.delegate = delegate; @@ -39,13 +39,12 @@ public UaaAuthenticationFailureHandler(ExceptionMappingAuthenticationFailureHand @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { addCookie(response); - if(exception != null) { - if (exception instanceof PasswordChangeRequiredException) { - SessionUtils.setForcePasswordExpiredUser(request.getSession(), - ((PasswordChangeRequiredException) exception).getAuthentication()); - } + if (exception instanceof PasswordChangeRequiredException passwordChangeRequiredException) { + SessionUtils.setForcePasswordExpiredUser(request.getSession(), + passwordChangeRequiredException.getAuthentication()); } - if (delegate!=null) { + + if (delegate != null) { delegate.onAuthenticationFailure(request, response, exception); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/config/xml/OAuth2FilterConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/config/xml/OAuth2FilterConfig.java new file mode 100644 index 00000000000..a444b56f7e1 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/config/xml/OAuth2FilterConfig.java @@ -0,0 +1,101 @@ +package org.cloudfoundry.identity.uaa.oauth.provider.config.xml; + +import org.cloudfoundry.identity.uaa.authentication.BackwardsCompatibleTokenEndpointAuthenticationFilter; +import org.cloudfoundry.identity.uaa.authentication.manager.PasswordGrantAuthenticationManager; +import org.cloudfoundry.identity.uaa.oauth.UaaAuthorizationRequestManager; +import org.cloudfoundry.identity.uaa.oauth.pkce.PkceValidationService; +import org.cloudfoundry.identity.uaa.oauth.provider.CompositeTokenGranter; +import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2RequestFactory; +import org.cloudfoundry.identity.uaa.oauth.provider.code.AuthorizationCodeServices; +import org.cloudfoundry.identity.uaa.oauth.provider.token.AuthorizationServerTokenServices; +import org.cloudfoundry.identity.uaa.oauth.token.JwtTokenGranter; +import org.cloudfoundry.identity.uaa.oauth.token.PkceEnhancedAuthorizationCodeTokenGranter; +import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning; +import org.cloudfoundry.identity.uaa.oauth.token.Saml2TokenGranter; +import org.cloudfoundry.identity.uaa.oauth.token.UserTokenGranter; +import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthAuthenticationManager; +import org.cloudfoundry.identity.uaa.provider.saml.Saml2BearerGrantAuthenticationConverter; +import org.cloudfoundry.identity.uaa.security.beans.SecurityContextAccessor; +import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.web.AuthenticationEntryPoint; + +@Configuration +public class OAuth2FilterConfig { + + @Autowired + @Bean + BackwardsCompatibleTokenEndpointAuthenticationFilter tokenEndpointAuthenticationFilter(PasswordGrantAuthenticationManager passwordGrantAuthenticationManager, + UaaAuthorizationRequestManager authorizationRequestManager, + Saml2BearerGrantAuthenticationConverter samlBearerGrantAuthenticationProvider, + ExternalOAuthAuthenticationManager externalOAuthAuthenticationManager, + AuthenticationDetailsSource authenticationDetailsSource, + AuthenticationEntryPoint basicAuthenticationEntryPoint) { + + BackwardsCompatibleTokenEndpointAuthenticationFilter authenticationFilter = + new BackwardsCompatibleTokenEndpointAuthenticationFilter("/oauth/token/alias/{registrationId}", + passwordGrantAuthenticationManager, authorizationRequestManager, samlBearerGrantAuthenticationProvider, + externalOAuthAuthenticationManager); + authenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource); + authenticationFilter.setAuthenticationEntryPoint(basicAuthenticationEntryPoint); + + return authenticationFilter; + } + + @Autowired + @Bean + public PkceEnhancedAuthorizationCodeTokenGranter pkceEnhancedAuthorizationCodeTokenGranter(@Qualifier("oauth2TokenGranter") CompositeTokenGranter compositeTokenGranter, + @Qualifier("tokenServices") AuthorizationServerTokenServices tokenServices, + @Qualifier("authorizationCodeServices") AuthorizationCodeServices authorizationCodeServices, + @Qualifier("jdbcClientDetailsService") MultitenantClientServices clientDetailsService, + @Qualifier("authorizationRequestManager") OAuth2RequestFactory requestFactory, + @Qualifier("pkceValidationServices") PkceValidationService pkceValidationServices) { + PkceEnhancedAuthorizationCodeTokenGranter tokenGranter = new PkceEnhancedAuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory); + tokenGranter.setPkceValidationService(pkceValidationServices); + compositeTokenGranter.addTokenGranter(tokenGranter); + + return tokenGranter; + } + + @Autowired + @Bean + public UserTokenGranter userTokenGranter(@Qualifier("oauth2TokenGranter") CompositeTokenGranter compositeTokenGranter, + @Qualifier("tokenServices") AuthorizationServerTokenServices tokenServices, + @Qualifier("jdbcClientDetailsService") MultitenantClientServices clientDetailsService, + @Qualifier("authorizationRequestManager") OAuth2RequestFactory requestFactory, + @Qualifier("revocableTokenProvisioning") RevocableTokenProvisioning tokenStore) { + UserTokenGranter tokenGranter = new UserTokenGranter(tokenServices, clientDetailsService, requestFactory, tokenStore); + compositeTokenGranter.addTokenGranter(tokenGranter); + + return tokenGranter; + } + + @Autowired + @Bean + public JwtTokenGranter jwtTokenGranter(@Qualifier("oauth2TokenGranter") CompositeTokenGranter compositeTokenGranter, + @Qualifier("tokenServices") AuthorizationServerTokenServices tokenServices, + @Qualifier("jdbcClientDetailsService") MultitenantClientServices clientDetailsService, + @Qualifier("authorizationRequestManager") OAuth2RequestFactory requestFactory) { + JwtTokenGranter tokenGranter = new JwtTokenGranter(tokenServices, clientDetailsService, requestFactory); + compositeTokenGranter.addTokenGranter(tokenGranter); + + return tokenGranter; + } + + @Autowired + @Bean + public Saml2TokenGranter samlTokenGranter(@Qualifier("oauth2TokenGranter") CompositeTokenGranter compositeTokenGranter, + @Qualifier("tokenServices") AuthorizationServerTokenServices tokenServices, + @Qualifier("jdbcClientDetailsService") MultitenantClientServices clientDetailsService, + @Qualifier("authorizationRequestManager") OAuth2RequestFactory requestFactory, + SecurityContextAccessor securityContextAccessor) { + Saml2TokenGranter tokenGranter = new Saml2TokenGranter(tokenServices, clientDetailsService, requestFactory, securityContextAccessor); + compositeTokenGranter.addTokenGranter(tokenGranter); + + return tokenGranter; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/endpoint/TokenEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/endpoint/TokenEndpoint.java index f5ecb8899dd..8058684375c 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/endpoint/TokenEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/endpoint/TokenEndpoint.java @@ -65,7 +65,7 @@ public ResponseEntity getAccessToken( public ResponseEntity postAccessToken( Principal principal, @RequestParam Map parameters) { - if (!(principal instanceof Authentication)) { + if (!(principal instanceof Authentication)) { throw new InsufficientAuthenticationException( "There is no client authentication. Try adding an appropriate authentication filter."); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/AddTokenGranter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/AddTokenGranter.java deleted file mode 100644 index a97a6baf171..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/AddTokenGranter.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cloudfoundry.identity.uaa.oauth.token; - -import org.cloudfoundry.identity.uaa.oauth.provider.CompositeTokenGranter; -import org.cloudfoundry.identity.uaa.oauth.provider.TokenGranter; - -/** - * This class just adds custom token granters to the - * {@link CompositeTokenGranter} object that is created by the - *

<oauth:authorization-server>
element - */ -public class AddTokenGranter { - - - private final TokenGranter userTokenGranter; - private final TokenGranter compositeTokenGranter; - - public AddTokenGranter(TokenGranter userTokenGranter, TokenGranter compositeTokenGranter) { - this.userTokenGranter = userTokenGranter; - this.compositeTokenGranter = compositeTokenGranter; - if (compositeTokenGranter == null) { - throw new NullPointerException("Expected non null "+CompositeTokenGranter.class.getName()); - } else if (compositeTokenGranter instanceof CompositeTokenGranter) { - CompositeTokenGranter cg = (CompositeTokenGranter)compositeTokenGranter; - cg.addTokenGranter(userTokenGranter); - } else { - throw new IllegalArgumentException( - "Expected "+CompositeTokenGranter.class.getName()+ - " but received "+ - compositeTokenGranter.getClass().getName() - ); - } - } - -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/JwtTokenGranter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/JwtTokenGranter.java index 3ea7fc24e50..bbdf87a9b92 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/JwtTokenGranter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/JwtTokenGranter.java @@ -18,7 +18,7 @@ public class JwtTokenGranter extends AbstractTokenGranter { final DefaultSecurityContextAccessor defaultSecurityContextAccessor; - protected JwtTokenGranter(AuthorizationServerTokenServices tokenServices, + public JwtTokenGranter(AuthorizationServerTokenServices tokenServices, MultitenantClientServices clientDetailsService, OAuth2RequestFactory requestFactory) { super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE_JWT_BEARER); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/passcode/PasscodeInformation.java b/server/src/main/java/org/cloudfoundry/identity/uaa/passcode/PasscodeInformation.java index baee6a4c567..0c130727a04 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/passcode/PasscodeInformation.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/passcode/PasscodeInformation.java @@ -16,10 +16,11 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.provider.saml.SamlUserAuthority; +import org.springframework.security.core.Authentication; import java.security.Principal; import java.util.ArrayList; @@ -29,24 +30,24 @@ import java.util.Map; import java.util.Set; -import org.springframework.security.core.Authentication; - +@Data public class PasscodeInformation { private static final String AUTHORITIES_KEY = "authorities"; private String userId; private String username; private String passcode; + @JsonIgnore private Map authorizationParameters; private String origin; @JsonCreator public PasscodeInformation( - @JsonProperty("userId") String userId, - @JsonProperty("username") String username, - @JsonProperty("passcode") String passcode, - @JsonProperty("origin") String origin, - @JsonProperty("samlAuthorities") List authorities) { + @JsonProperty("userId") String userId, + @JsonProperty("username") String username, + @JsonProperty("passcode") String passcode, + @JsonProperty("origin") String origin, + @JsonProperty("samlAuthorities") List authorities) { setUserId(userId); setUsername(username); @@ -62,11 +63,9 @@ public PasscodeInformation(Principal principal, Map authorizatio uaaPrincipal = getUaaPrincipal(castUaaPrincipal); } else if (principal instanceof UaaAuthentication castUaaAuthentication) { uaaPrincipal = getUaaPrincipal(castUaaAuthentication.getPrincipal()); - } else if (principal instanceof final LoginSamlAuthenticationToken samlTokenPrincipal) { - uaaPrincipal = getUaaPrincipal(samlTokenPrincipal.getUaaPrincipal()); } else if ( principal instanceof Authentication castAuthentication && - castAuthentication.getPrincipal() instanceof UaaPrincipal castUaaPrincipal + castAuthentication.getPrincipal() instanceof UaaPrincipal castUaaPrincipal ) { uaaPrincipal = getUaaPrincipal(castUaaPrincipal); } else { @@ -84,14 +83,6 @@ private UaaPrincipal getUaaPrincipal(UaaPrincipal castUaaPrincipal) { return castUaaPrincipal; } - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - @JsonProperty("samlAuthorities") public List getSamlAuthorities() { ArrayList list = new ArrayList<>(); @@ -106,37 +97,4 @@ public void setSamlAuthorities(List authorities) { Set set = new HashSet<>(authorities); authorizationParameters.put(AUTHORITIES_KEY, set); } - - @JsonIgnore - public Map getAuthorizationParameters() { - return authorizationParameters; - } - - public void setAuthorizationParameters(Map authorizationParameters) { - this.authorizationParameters = authorizationParameters; - } - - public String getPasscode() { - return passcode; - } - - public void setPasscode(String passcode) { - this.passcode = passcode; - } - - public String getOrigin() { - return origin; - } - - public void setOrigin(String origin) { - this.origin = origin; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java index 31ff0869d90..c7d8de2cd74 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java @@ -13,28 +13,6 @@ */ package org.cloudfoundry.identity.uaa.provider; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.SAML; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; -import static org.cloudfoundry.identity.uaa.util.UaaStringUtils.getCleanedUserControlString; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.CONFLICT; -import static org.springframework.http.HttpStatus.CREATED; -import static org.springframework.http.HttpStatus.EXPECTATION_FAILED; -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; -import static org.springframework.http.HttpStatus.OK; -import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; -import static org.springframework.util.StringUtils.hasText; -import static org.springframework.web.bind.annotation.RequestMethod.POST; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Date; -import java.util.List; -import java.util.Optional; - import org.cloudfoundry.identity.uaa.alias.EntityAliasFailedException; import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicLdapAuthenticationManager; @@ -48,7 +26,6 @@ import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; @@ -77,6 +54,27 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.SAML; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; +import static org.cloudfoundry.identity.uaa.util.UaaStringUtils.getCleanedUserControlString; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CONFLICT; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.EXPECTATION_FAILED; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; +import static org.springframework.util.StringUtils.hasText; + @RequestMapping("/identity-providers") @RestController public class IdentityProviderEndpoints implements ApplicationEventPublisherAware { @@ -124,7 +122,7 @@ public IdentityProviderEndpoints( } @PostMapping() - public ResponseEntity createIdentityProvider(@RequestBody IdentityProvider body, @RequestParam(required = false, defaultValue = "false") boolean rawConfig) throws MetadataProviderException{ + public ResponseEntity createIdentityProvider(@RequestBody IdentityProvider body, @RequestParam(required = false, defaultValue = "false") boolean rawConfig) { body.setSerializeConfigRaw(rawConfig); String zoneId = identityZoneManager.getCurrentIdentityZoneId(); body.setIdentityZoneId(zoneId); @@ -196,7 +194,7 @@ public ResponseEntity deleteIdentityProvider(@PathVariable Str } @PutMapping(value = "{id}") - public ResponseEntity updateIdentityProvider(@PathVariable String id, @RequestBody IdentityProvider body, @RequestParam(required = false, defaultValue = "false") boolean rawConfig) throws MetadataProviderException { + public ResponseEntity updateIdentityProvider(@PathVariable String id, @RequestBody IdentityProvider body, @RequestParam(required = false, defaultValue = "false") boolean rawConfig) { body.setSerializeConfigRaw(rawConfig); String zoneId = identityZoneManager.getCurrentIdentityZoneId(); IdentityProvider existing = identityProviderProvisioning.retrieve(id, zoneId); @@ -213,7 +211,7 @@ public ResponseEntity updateIdentityProvider(@PathVariable Str if (!idpAliasHandler.aliasPropertiesAreValid(body, existing)) { if (logger.isWarnEnabled()) { logger.warn("IdentityProvider[origin={}; zone={}] - Alias ID and/or ZID changed during update of IdP with alias.", - getCleanedUserControlString(body.getOriginKey()), getCleanedUserControlString(body.getIdentityZoneId())); + getCleanedUserControlString(body.getOriginKey()), getCleanedUserControlString(body.getIdentityZoneId())); } return new ResponseEntity<>(body, UNPROCESSABLE_ENTITY); } @@ -231,7 +229,7 @@ public ResponseEntity updateIdentityProvider(@PathVariable Str } private ResponseEntity persistIdentityProviderChange(IdentityProvider body, boolean rawConfig, String zoneId, - IdentityProvider existing, HttpStatus status) { + IdentityProvider existing, HttpStatus status) { final IdentityProvider updatedIdp; try { updatedIdp = transactionTemplate.execute(txStatus -> { @@ -246,15 +244,15 @@ private ResponseEntity persistIdentityProviderChange(IdentityP return new ResponseEntity<>(body, responseCode); } catch (final Exception e) { logger.warn(String.format("Unable to %s IdentityProvider[origin=%s; zone=%s]", - status == CREATED ? "create" : "update", body.getOriginKey(), body.getIdentityZoneId()), e); + status == CREATED ? "create" : "update", body.getOriginKey(), body.getIdentityZoneId()), e); return new ResponseEntity<>(body, INTERNAL_SERVER_ERROR); } if (updatedIdp == null) { if (logger.isWarnEnabled()) { logger.warn( - "IdentityProvider[origin={}; zone={}] - Transaction {} IdP (and alias IdP, if applicable) was not successful, but no exception was thrown.", - getCleanedUserControlString(body.getOriginKey()), getCleanedUserControlString(body.getIdentityZoneId()), - status == CREATED ? "creating" : "updating"); + "IdentityProvider[origin={}; zone={}] - Transaction {} IdP (and alias IdP, if applicable) was not successful, but no exception was thrown.", + getCleanedUserControlString(body.getOriginKey()), getCleanedUserControlString(body.getIdentityZoneId()), + status == CREATED ? "creating" : "updating"); } return new ResponseEntity<>(body, UNPROCESSABLE_ENTITY); } @@ -269,16 +267,16 @@ private ResponseEntity persistIdentityProviderChange(IdentityP public ResponseEntity updateIdentityProviderStatus(@PathVariable String id, @RequestBody IdentityProviderStatus body) { String zoneId = identityZoneManager.getCurrentIdentityZoneId(); IdentityProvider existing = identityProviderProvisioning.retrieve(id, zoneId); - if(body.getRequirePasswordChange() == null || !body.getRequirePasswordChange()) { + if (body.getRequirePasswordChange() == null || !body.getRequirePasswordChange()) { logger.debug("Invalid payload. The property requirePasswordChangeRequired needs to be set"); return new ResponseEntity<>(body, UNPROCESSABLE_ENTITY); } - if(!UAA.equals(existing.getType())) { + if (!UAA.equals(existing.getType())) { logger.debug("Invalid operation. This operation is not supported on external IDP"); return new ResponseEntity<>(body, UNPROCESSABLE_ENTITY); } UaaIdentityProviderDefinition uaaIdentityProviderDefinition = ObjectUtils.castInstance(existing.getConfig(), UaaIdentityProviderDefinition.class); - if(uaaIdentityProviderDefinition == null || uaaIdentityProviderDefinition.getPasswordPolicy() == null) { + if (uaaIdentityProviderDefinition == null || uaaIdentityProviderDefinition.getPasswordPolicy() == null) { logger.debug("IDP does not have an existing PasswordPolicy. Operation not supported"); return new ResponseEntity<>(body, UNPROCESSABLE_ENTITY); } @@ -290,15 +288,14 @@ public ResponseEntity updateIdentityProviderStatus(@Path * we do not need to propagate the changes to an alias IdP here. */ logger.info("PasswordChangeRequired property set for Identity Provider: {}", existing.getId()); - return new ResponseEntity<>(body, OK); + return new ResponseEntity<>(body, OK); } @GetMapping() public ResponseEntity> retrieveIdentityProviders( - @RequestParam(value = "active_only", required = false) String activeOnly, - @RequestParam(required = false, defaultValue = "false") boolean rawConfig, - @RequestParam(required = false, defaultValue = "") String originKey) - { + @RequestParam(value = "active_only", required = false) String activeOnly, + @RequestParam(required = false, defaultValue = "false") boolean rawConfig, + @RequestParam(required = false, defaultValue = "") String originKey) { boolean retrieveActiveOnly = Boolean.parseBoolean(activeOnly); List identityProviderList; if (UaaStringUtils.isNotEmpty(originKey)) { @@ -306,7 +303,7 @@ public ResponseEntity> retrieveIdentityProviders( } else { identityProviderList = identityProviderProvisioning.retrieveAll(retrieveActiveOnly, identityZoneManager.getCurrentIdentityZoneId()); } - for(IdentityProvider idp : identityProviderList) { + for (IdentityProvider idp : identityProviderList) { idp.setSerializeConfigRaw(rawConfig); setAuthMethod(idp); redactSensitiveData(idp); @@ -323,21 +320,21 @@ public ResponseEntity retrieveIdentityProvider(@PathVariable S return new ResponseEntity<>(identityProvider, OK); } - @RequestMapping(value = "test", method = POST) + @PostMapping(value = "test") public ResponseEntity testIdentityProvider(@RequestBody IdentityProviderValidationRequest body) { String exception = "ok"; HttpStatus status = OK; //create the LDAP IDP DynamicLdapAuthenticationManager manager = new DynamicLdapAuthenticationManager( - ObjectUtils.castInstance(body.getProvider().getConfig(),LdapIdentityProviderDefinition.class), - scimGroupExternalMembershipManager, - scimGroupProvisioning, - noOpManager + ObjectUtils.castInstance(body.getProvider().getConfig(), LdapIdentityProviderDefinition.class), + scimGroupExternalMembershipManager, + scimGroupProvisioning, + noOpManager ); try { //attempt authentication Authentication result = manager.authenticate(body.getCredentials()); - if ((result == null) || (result != null && !result.isAuthenticated())) { + if ((result == null) || (!result.isAuthenticated())) { status = EXPECTATION_FAILED; } } catch (BadCredentialsException x) { @@ -350,7 +347,7 @@ public ResponseEntity testIdentityProvider(@RequestBody IdentityProvider logger.error("Identity provider validation failed.", x); status = INTERNAL_SERVER_ERROR; exception = "check server logs"; - }finally { + } finally { //destroy IDP manager.destroy(); } @@ -358,13 +355,9 @@ public ResponseEntity testIdentityProvider(@RequestBody IdentityProvider return new ResponseEntity<>(JsonUtils.writeValueAsString(exception), status); } - @ExceptionHandler(MetadataProviderException.class) - public ResponseEntity handleMetadataProviderException(MetadataProviderException e) { - if (e.getMessage().contains("Duplicate")) { - return new ResponseEntity<>(e.getMessage(), CONFLICT); - } else { - return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); - } + @ExceptionHandler(IdpAlreadyExistsException.class) + public ResponseEntity handleDuplicateEntry(IdpAlreadyExistsException e) { + return new ResponseEntity<>(e.getMessage(), CONFLICT); } @ExceptionHandler(JsonUtils.JsonUtilException.class) @@ -377,7 +370,6 @@ public ResponseEntity handleProviderNotFoundException() { return new ResponseEntity<>("Provider not found.", HttpStatus.NOT_FOUND); } - protected String getExceptionString(Exception x) { StringWriter writer = new StringWriter(); x.printStackTrace(new PrintWriter(writer)); @@ -404,22 +396,22 @@ protected void patchSensitiveData(String id, IdentityProvider provider) { case LDAP: { if (provider.getConfig() instanceof LdapIdentityProviderDefinition definition && definition.getBindPassword() == null) { IdentityProvider existing = identityProviderProvisioning.retrieve(id, zoneId); - if (existing!=null && - existing.getConfig()!=null && - existing.getConfig() instanceof LdapIdentityProviderDefinition existingDefinition) { + if (existing != null && + existing.getConfig() != null && + existing.getConfig() instanceof LdapIdentityProviderDefinition existingDefinition) { definition.setBindPassword(existingDefinition.getBindPassword()); } } break; } - case OAUTH20, OIDC10 : { + case OAUTH20, OIDC10: { if (provider.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition definition && - definition.getRelyingPartySecret() == null && - secretNeeded(definition)) { + definition.getRelyingPartySecret() == null && + secretNeeded(definition)) { IdentityProvider existing = identityProviderProvisioning.retrieve(id, zoneId); - if (existing!=null && - existing.getConfig()!=null && - existing.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition existingDefinition) { + if (existing != null && + existing.getConfig() != null && + existing.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition existingDefinition) { definition.setRelyingPartySecret(existingDefinition.getRelyingPartySecret()); } } @@ -443,7 +435,7 @@ protected void redactSensitiveData(IdentityProvider provider) { } break; } - case OAUTH20, OIDC10 : { + case OAUTH20, OIDC10: { if (provider.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition definition) { logger.debug("Removing relying secret from OAuth/OIDC provider id: {}", provider.getId()); definition.setRelyingPartySecret(null); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandler.java deleted file mode 100644 index 75a86c7e694..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandler.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.oauth; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; -import org.springframework.util.StringUtils; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Set; - -public class ExternalOAuthLogoutHandler extends SimpleUrlLogoutSuccessHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(ExternalOAuthLogoutHandler.class); - - private final IdentityProviderProvisioning providerProvisioning; - private final OidcMetadataFetcher oidcMetadataFetcher; - private final IdentityZoneManager identityZoneManager; - private final Set defaultOrigin = Set.of(OriginKeys.UAA, OriginKeys.LDAP); - - public ExternalOAuthLogoutHandler(final IdentityProviderProvisioning providerProvisioning, final OidcMetadataFetcher oidcMetadataFetcher, - IdentityZoneManager identityZoneManager) { - this.providerProvisioning = providerProvisioning; - this.oidcMetadataFetcher = oidcMetadataFetcher; - this.identityZoneManager = identityZoneManager; - } - - @Override - protected String determineTargetUrl(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) { - final AbstractExternalOAuthIdentityProviderDefinition oauthConfig = - this.getOAuthProviderForAuthentication(authentication); - final String logoutUrl = this.getLogoutUrl(oauthConfig); - - if (logoutUrl == null) { - final String defaultUrl = getZoneDefaultUrl(); - if (LOGGER.isWarnEnabled()) { - LOGGER.warn(String.format("OAuth logout null, use default: %s", defaultUrl)); - } - return defaultUrl; - } - - return this.constructOAuthProviderLogoutUrl(request, logoutUrl, oauthConfig, authentication); - } - - public String constructOAuthProviderLogoutUrl(final HttpServletRequest request, final String logoutUrl, - final AbstractExternalOAuthIdentityProviderDefinition oauthConfig, - final Authentication authentication) { - final StringBuilder oauthLogoutUriBuilder = new StringBuilder(request.getRequestURL()); - if (StringUtils.hasText(request.getQueryString())) { - oauthLogoutUriBuilder.append("?"); - oauthLogoutUriBuilder.append(request.getQueryString()); - } - final String oauthLogoutUri = URLEncoder.encode(oauthLogoutUriBuilder.toString(), StandardCharsets.UTF_8); - final StringBuilder sb = new StringBuilder(logoutUrl); - sb.append("?post_logout_redirect_uri="); - sb.append(oauthLogoutUri); - sb.append("&client_id="); - sb.append(oauthConfig.getRelyingPartyId()); - - if (authentication instanceof UaaAuthentication uaaAuthentication && uaaAuthentication.getIdpIdToken() != null) { - sb.append("&id_token_hint="); - sb.append(uaaAuthentication.getIdpIdToken()); - } - return sb.toString(); - } - - public String getLogoutUrl(final AbstractExternalOAuthIdentityProviderDefinition oAuthIdentityProviderDefinition) { - String logoutUrl = null; - if (oAuthIdentityProviderDefinition != null && oAuthIdentityProviderDefinition.getLogoutUrl() != null) { - logoutUrl = oAuthIdentityProviderDefinition.getLogoutUrl().toString(); - } else { - if (oAuthIdentityProviderDefinition instanceof OIDCIdentityProviderDefinition) { - final OIDCIdentityProviderDefinition oidcIdentityProviderDefinition = (OIDCIdentityProviderDefinition) oAuthIdentityProviderDefinition; - try { - this.oidcMetadataFetcher.fetchMetadataAndUpdateDefinition(oidcIdentityProviderDefinition); - } catch (final OidcMetadataFetchingException e) { - LOGGER.warn(e.getLocalizedMessage(), e); - } - if (oidcIdentityProviderDefinition.getLogoutUrl() != null) { - logoutUrl = oidcIdentityProviderDefinition.getLogoutUrl().toString(); - } - } - } - return logoutUrl; - } - - public AbstractExternalOAuthIdentityProviderDefinition getOAuthProviderForAuthentication(final Authentication authentication) { - if (this.isExternalOAuthAuthentication(authentication)) { - final UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); - final IdentityProvider identityProvider = - this.providerProvisioning.retrieveByOrigin(principal.getOrigin(), principal.getZoneId()); - if (identityProvider != null && identityProvider.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition && ( - OriginKeys.OIDC10.equals(identityProvider.getType()) || OriginKeys.OAUTH20.equals(identityProvider.getType()))) { - return (AbstractExternalOAuthIdentityProviderDefinition) identityProvider.getConfig(); - } - } - return null; - } - - private boolean isExternalOAuthAuthentication(final Authentication authentication) { - if (authentication instanceof UaaAuthentication && authentication.getPrincipal() instanceof UaaPrincipal) { - final UaaAuthentication uaaAuthentication = (UaaAuthentication) authentication; - final UaaPrincipal principal = uaaAuthentication.getPrincipal(); - final String origin = principal.getOrigin(); - return !this.defaultOrigin.contains(origin) && - uaaAuthentication.getAuthenticationMethods() != null && - uaaAuthentication.getAuthenticationMethods().contains("oauth"); - } - return false; - } - - private String getZoneDefaultUrl() { - IdentityZoneConfiguration config = identityZoneManager.getCurrentIdentityZone().getConfig(); - if (config == null) { - config = new IdentityZoneConfiguration(); - } - return config.getLinks().getLogout().getRedirectUrl(); - } - - public boolean getPerformRpInitiatedLogout(AbstractExternalOAuthIdentityProviderDefinition oauthConfig) { - if (oauthConfig == null) { - return false; - } - return oauthConfig.isPerformRpInitiatedLogout(); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutSuccessHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutSuccessHandler.java new file mode 100644 index 00000000000..4b41621dc3e --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutSuccessHandler.java @@ -0,0 +1,132 @@ +package org.cloudfoundry.identity.uaa.provider.oauth; + +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Set; + +public class ExternalOAuthLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExternalOAuthLogoutSuccessHandler.class); + + private final IdentityProviderProvisioning providerProvisioning; + private final OidcMetadataFetcher oidcMetadataFetcher; + private final IdentityZoneManager identityZoneManager; + private final Set defaultOrigin = Set.of(OriginKeys.UAA, OriginKeys.LDAP); + + public ExternalOAuthLogoutSuccessHandler(final IdentityProviderProvisioning providerProvisioning, final OidcMetadataFetcher oidcMetadataFetcher, + IdentityZoneManager identityZoneManager) { + this.providerProvisioning = providerProvisioning; + this.oidcMetadataFetcher = oidcMetadataFetcher; + this.identityZoneManager = identityZoneManager; + } + + @Override + protected String determineTargetUrl(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) { + final AbstractExternalOAuthIdentityProviderDefinition oauthConfig = + this.getOAuthProviderForAuthentication(authentication); + final String logoutUrl = this.getLogoutUrl(oauthConfig); + + if (logoutUrl == null) { + final String defaultUrl = getZoneDefaultUrl(); + if (LOGGER.isWarnEnabled()) { + LOGGER.warn(String.format("OAuth logout null, use default: %s", defaultUrl)); + } + return defaultUrl; + } + + return this.constructOAuthProviderLogoutUrl(request, logoutUrl, oauthConfig, authentication); + } + + public String constructOAuthProviderLogoutUrl(final HttpServletRequest request, final String logoutUrl, + final AbstractExternalOAuthIdentityProviderDefinition oauthConfig, + final Authentication authentication) { + final StringBuilder oauthLogoutUriBuilder = new StringBuilder(request.getRequestURL()); + if (StringUtils.hasText(request.getQueryString())) { + oauthLogoutUriBuilder.append("?"); + oauthLogoutUriBuilder.append(request.getQueryString()); + } + final String oauthLogoutUri = URLEncoder.encode(oauthLogoutUriBuilder.toString(), StandardCharsets.UTF_8); + + String idTokenHint = ""; + if (authentication instanceof UaaAuthentication uaaAuthentication && uaaAuthentication.getIdpIdToken() != null) { + idTokenHint = "&id_token_hint=%s".formatted(uaaAuthentication.getIdpIdToken()); + } + + return "%s?post_logout_redirect_uri=%s&client_id=%s%s".formatted(logoutUrl, oauthLogoutUri, oauthConfig.getRelyingPartyId(), idTokenHint); + } + + public String getLogoutUrl(final AbstractExternalOAuthIdentityProviderDefinition oAuthIdentityProviderDefinition) { + String logoutUrl = null; + if (oAuthIdentityProviderDefinition != null && oAuthIdentityProviderDefinition.getLogoutUrl() != null) { + logoutUrl = oAuthIdentityProviderDefinition.getLogoutUrl().toString(); + } else { + if (oAuthIdentityProviderDefinition instanceof OIDCIdentityProviderDefinition oidcIdentityProviderDefinition) { + try { + this.oidcMetadataFetcher.fetchMetadataAndUpdateDefinition(oidcIdentityProviderDefinition); + } catch (final OidcMetadataFetchingException e) { + LOGGER.warn(e.getLocalizedMessage(), e); + } + if (oidcIdentityProviderDefinition.getLogoutUrl() != null) { + logoutUrl = oidcIdentityProviderDefinition.getLogoutUrl().toString(); + } + } + } + return logoutUrl; + } + + public AbstractExternalOAuthIdentityProviderDefinition getOAuthProviderForAuthentication(final Authentication authentication) { + if (this.isExternalOAuthAuthentication(authentication)) { + final UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); + final IdentityProvider identityProvider = + providerProvisioning.retrieveByOrigin(principal.getOrigin(), principal.getZoneId()); + if (identityProvider != null && identityProvider.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition && ( + OriginKeys.OIDC10.equals(identityProvider.getType()) || OriginKeys.OAUTH20.equals(identityProvider.getType()))) { + return (AbstractExternalOAuthIdentityProviderDefinition) identityProvider.getConfig(); + } + } + return null; + } + + private boolean isExternalOAuthAuthentication(final Authentication authentication) { + if (authentication instanceof UaaAuthentication uaaAuthentication && authentication.getPrincipal() instanceof UaaPrincipal principal) { + final String origin = principal.getOrigin(); + return !this.defaultOrigin.contains(origin) && + uaaAuthentication.getAuthenticationMethods() != null && + uaaAuthentication.getAuthenticationMethods().contains("oauth"); + } + return false; + } + + private String getZoneDefaultUrl() { + IdentityZoneConfiguration config = identityZoneManager.getCurrentIdentityZone().getConfig(); + if (config == null) { + config = new IdentityZoneConfiguration(); + } + return config.getLinks().getLogout().getRedirectUrl(); + } + + public boolean getPerformRpInitiatedLogout(AbstractExternalOAuthIdentityProviderDefinition oauthConfig) { + if (oauthConfig == null) { + return false; + } + return oauthConfig.isPerformRpInitiatedLogout(); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OauthIDPWrapperFactoryBean.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OauthIDPWrapperFactoryBean.java index aab85181025..d376b8de69d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OauthIDPWrapperFactoryBean.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OauthIDPWrapperFactoryBean.java @@ -50,15 +50,14 @@ public OauthIDPWrapperFactoryBean(Map definitions) { try { IdentityProvider provider = new IdentityProvider(); String type = (String) idpDefinitionMap.get("type"); - if(OAUTH20.equalsIgnoreCase(type)) { + if (OAUTH20.equalsIgnoreCase(type)) { RawExternalOAuthIdentityProviderDefinition oauthIdentityProviderDefinition = new RawExternalOAuthIdentityProviderDefinition(); oauthIdentityProviderDefinition.setCheckTokenUrl(idpDefinitionMap.get("checkTokenUrl") == null ? null : new URL((String) idpDefinitionMap.get("checkTokenUrl"))); setCommonProperties(idpDefinitionMap, oauthIdentityProviderDefinition); oauthIdpDefinitions.put(alias, oauthIdentityProviderDefinition); rawDef = oauthIdentityProviderDefinition; provider.setType(OriginKeys.OAUTH20); - } - else if(OIDC10.equalsIgnoreCase(type)) { + } else if (OIDC10.equalsIgnoreCase(type)) { rawDef = getExternalOIDCIdentityProviderDefinition(alias, idpDefinitionMap, provider); } else { throw new IllegalArgumentException("Unknown type for provider. Type must be oauth2.0 or oidc1.0. (Was " + type + ")"); @@ -77,18 +76,17 @@ else if(OIDC10.equalsIgnoreCase(type)) { } - } } private AbstractExternalOAuthIdentityProviderDefinition getExternalOIDCIdentityProviderDefinition(String alias, - Map idpDefinitionMap, IdentityProvider provider) throws MalformedURLException { + Map idpDefinitionMap, IdentityProvider provider) throws MalformedURLException { AbstractExternalOAuthIdentityProviderDefinition rawDef; OIDCIdentityProviderDefinition oidcIdentityProviderDefinition = new OIDCIdentityProviderDefinition(); setCommonProperties(idpDefinitionMap, oidcIdentityProviderDefinition); oidcIdentityProviderDefinition.setUserInfoUrl(idpDefinitionMap.get("userInfoUrl") == null ? null : new URL((String) idpDefinitionMap.get("userInfoUrl"))); oidcIdentityProviderDefinition.setPasswordGrantEnabled( - idpDefinitionMap.get("passwordGrantEnabled") == null ? false : (boolean) idpDefinitionMap.get("passwordGrantEnabled")); + idpDefinitionMap.get("passwordGrantEnabled") == null ? false : (boolean) idpDefinitionMap.get("passwordGrantEnabled")); oidcIdentityProviderDefinition.setSetForwardHeader(idpDefinitionMap.get("setForwardHeader") == null ? false : (boolean) idpDefinitionMap.get("setForwardHeader")); oidcIdentityProviderDefinition.setPrompts((List) idpDefinitionMap.get("prompts")); setJwtClientAuthentication(idpDefinitionMap, oidcIdentityProviderDefinition); @@ -117,7 +115,7 @@ private static Object getJwtClientAuthenticationDetails(Map uaaY public static IdentityProviderWrapper getIdentityProviderWrapper(String origin, AbstractExternalOAuthIdentityProviderDefinition rawDef, IdentityProvider provider, boolean override) { provider.setOriginKey(origin); - provider.setName("UAA Oauth Identity Provider["+provider.getOriginKey()+"]"); + provider.setName("UAA Oauth Identity Provider[" + provider.getOriginKey() + "]"); provider.setActive(true); try { provider.setConfig(rawDef); @@ -130,7 +128,7 @@ public static IdentityProviderWrapper getIdentityProviderWrapper(String origin, } protected void setCommonProperties(Map idpDefinitionMap, AbstractExternalOAuthIdentityProviderDefinition idpDefinition) { - idpDefinition.setLinkText((String)idpDefinitionMap.get("linkText")); + idpDefinition.setLinkText((String) idpDefinitionMap.get("linkText")); idpDefinition.setRelyingPartyId((String) idpDefinitionMap.get("relyingPartyId")); idpDefinition.setRelyingPartySecret((String) idpDefinitionMap.get("relyingPartySecret")); idpDefinition.setEmailDomain((List) idpDefinitionMap.get("emailDomain")); @@ -171,13 +169,13 @@ protected void setCommonProperties(Map idpDefinitionMap, Abstrac throw new IllegalArgumentException("URL is malformed.", e); } if (idpDefinitionMap.get("clientAuthInBody") instanceof Boolean) { - idpDefinition.setClientAuthInBody((boolean)idpDefinitionMap.get("clientAuthInBody")); + idpDefinition.setClientAuthInBody((boolean) idpDefinitionMap.get("clientAuthInBody")); } if (idpDefinitionMap.get("performRpInitiatedLogout") instanceof Boolean) { - idpDefinition.setPerformRpInitiatedLogout((boolean)idpDefinitionMap.get("performRpInitiatedLogout")); + idpDefinition.setPerformRpInitiatedLogout((boolean) idpDefinitionMap.get("performRpInitiatedLogout")); } if (idpDefinitionMap.get("cacheJwks") instanceof Boolean) { - idpDefinition.setCacheJwks((boolean)idpDefinitionMap.get("cacheJwks")); + idpDefinition.setCacheJwks((boolean) idpDefinitionMap.get("cacheJwks")); } if (idpDefinitionMap.get("authMethod") instanceof String definedAuthMethod) { if (ClientAuthentication.isMethodSupported(definedAuthMethod)) { @@ -191,7 +189,7 @@ protected void setCommonProperties(Map idpDefinitionMap, Abstrac private static Map parseAdditionalParameters(Map idpDefinitionMap) { Map additionalParameters = (Map) idpDefinitionMap.get("additionalAuthzParameters"); if (additionalParameters != null) { - Map additionalQueryParameters = new HashMap<>(); + Map additionalQueryParameters = new HashMap<>(); for (Map.Entry entry : additionalParameters.entrySet()) { String keyEntry = entry.getKey().toLowerCase(Locale.ROOT); String value = null; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BaseUaaRelyingPartyRegistrationRepository.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BaseUaaRelyingPartyRegistrationRepository.java new file mode 100644 index 00000000000..8bc4be77e79 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BaseUaaRelyingPartyRegistrationRepository.java @@ -0,0 +1,54 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.cloudfoundry.identity.uaa.zone.ZoneAware; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; + +import java.util.List; +import java.util.Optional; + +@Slf4j +public abstract class BaseUaaRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository, ZoneAware { + protected final String uaaWideSamlEntityID; + protected final String uaaWideSamlEntityIDAlias; + protected final String uaaWideSamlNameId; + protected final List signatureAlgorithms; + + protected BaseUaaRelyingPartyRegistrationRepository(String uaaWideSamlEntityID, String uaaWideSamlEntityIDAlias, + List signatureAlgorithms, + String uaaWideSamlNameId) { + this.uaaWideSamlEntityID = uaaWideSamlEntityID; + this.uaaWideSamlEntityIDAlias = uaaWideSamlEntityIDAlias; + this.signatureAlgorithms = signatureAlgorithms; + this.uaaWideSamlNameId = uaaWideSamlNameId; + } + + String getZoneEntityId(IdentityZone currentZone) { + // for default zone, use the samlEntityID + if (currentZone.isUaa()) { + return uaaWideSamlEntityID; + } + + // for non-default zone, use the zone specific entityID, if it exists + return Optional.ofNullable(currentZone.getConfig()) + .map(IdentityZoneConfiguration::getSamlConfig) + .map(SamlConfig::getEntityID) + // otherwise use the zone subdomain + default entityID + .orElseGet(() -> "%s.%s".formatted(currentZone.getSubdomain(), uaaWideSamlEntityID)); + } + + String getZoneEntityIdAlias(IdentityZone currentZone) { + String alias = Optional.ofNullable(uaaWideSamlEntityIDAlias) + .orElse(uaaWideSamlEntityID); + + // for default zone, use the samlEntityIDAlias if it exists, otherwise samlEntityID + if (currentZone.isUaa()) { + return alias; + } + // for non-default zone, use the "zone subdomain+.+alias" + return "%s.%s".formatted(currentZone.getSubdomain(), alias); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderData.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderData.java index d7ee75c7cd3..1a1151471ee 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderData.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderData.java @@ -13,13 +13,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - +import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderWrapper; @@ -27,13 +22,15 @@ import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition.ExternalGroupMappingMode; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; - -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; import static java.util.Collections.emptyList; import static java.util.Optional.ofNullable; @@ -44,8 +41,9 @@ import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.STORE_CUSTOM_ATTRIBUTES_NAME; import static org.springframework.util.StringUtils.hasText; +@Data +@Slf4j public class BootstrapSamlIdentityProviderData implements InitializingBean { - private static Logger logger = LoggerFactory.getLogger(BootstrapSamlIdentityProviderData.class); private String legacyIdpIdentityAlias; private volatile String legacyIdpMetaData; private String legacyNameId; @@ -56,43 +54,57 @@ public class BootstrapSamlIdentityProviderData implements InitializingBean { private Map> providers = null; private final SamlIdentityProviderConfigurator samlConfigurator; - public BootstrapSamlIdentityProviderData( - final @Qualifier("metaDataProviders") SamlIdentityProviderConfigurator samlConfigurator + public BootstrapSamlIdentityProviderData(final @Qualifier("metaDataProviders") SamlIdentityProviderConfigurator samlConfigurator ) { this.samlConfigurator = samlConfigurator; } + public static IdentityProvider parseSamlProvider(SamlIdentityProviderDefinition def) { + IdentityProvider provider = new IdentityProvider<>(); + provider.setType(OriginKeys.SAML); + provider.setOriginKey(def.getIdpEntityAlias()); + provider.setName("UAA SAML Identity Provider[" + provider.getOriginKey() + "]"); + provider.setActive(true); + try { + provider.setConfig(def); + } catch (JsonUtils.JsonUtilException x) { + throw new RuntimeException("Non serializable SAML config"); + } + return provider; + } + public List getIdentityProviderDefinitions() { return samlProviders .stream() - .map(p -> p.getProvider().getConfig()).collect(Collectors.toUnmodifiableList()); + .map(p -> p.getProvider().getConfig()) + .toList(); } protected void parseIdentityProviderDefinitions() { - if (getLegacyIdpMetaData()!=null) { + if (getLegacyIdpMetaData() != null) { SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); def.setMetaDataLocation(getLegacyIdpMetaData()); def.setMetadataTrustCheck(isLegacyMetadataTrustCheck()); def.setNameID(getLegacyNameId()); def.setAssertionConsumerIndex(getLegacyAssertionConsumerIndex()); String alias = getLegacyIdpIdentityAlias(); - if (alias==null) { + if (alias == null) { throw new IllegalArgumentException("Invalid IDP - Alias must be not null for deprecated IDP."); } def.setIdpEntityAlias(alias); def.setShowSamlLink(isLegacyShowSamlLink()); def.setLinkText("Use your corporate credentials"); def.setZoneId(IdentityZone.getUaaZoneId()); //legacy only has UAA zone - logger.debug("Legacy SAML provider configured with alias: "+alias); - IdentityProviderWrapper wrapper = new IdentityProviderWrapper(parseSamlProvider(def)); + log.debug("Legacy SAML provider configured with alias: " + alias); + IdentityProviderWrapper wrapper = new IdentityProviderWrapper<>(parseSamlProvider(def)); wrapper.setOverride(true); samlProviders.add(wrapper); } Set uniqueAlias = new HashSet<>(); - for (IdentityProviderWrapper wrapper : samlProviders) { - String alias = getUniqueAlias((SamlIdentityProviderDefinition) wrapper.getProvider().getConfig()); + for (IdentityProviderWrapper wrapper : samlProviders) { + String alias = getUniqueAlias(wrapper.getProvider().getConfig()); if (uniqueAlias.contains(alias)) { - throw new IllegalStateException("Duplicate IDP alias found:"+alias); + throw new IllegalStateException("Duplicate IDP alias found:" + alias); } uniqueAlias.add(alias); } @@ -106,32 +118,33 @@ public void setIdentityProviders(Map> providers) { if (providers == null) { return; } + this.providers = providers; for (Map.Entry entry : providers.entrySet()) { - String alias = (String)entry.getKey(); - Map saml = (Map)entry.getValue(); - String metaDataLocation = (String)saml.get("idpMetadata"); - String nameID = (String)saml.get("nameID"); - Integer assertionIndex = (Integer)saml.get("assertionConsumerIndex"); - Boolean trustCheck = (Boolean)saml.get("metadataTrustCheck"); - Boolean showLink = (Boolean)((Map)entry.getValue()).get("showSamlLoginLink"); - String socketFactoryClassName = (String)saml.get("socketFactoryClassName"); - String linkText = (String)((Map)entry.getValue()).get("linkText"); - String iconUrl = (String)((Map)entry.getValue()).get("iconUrl"); - String zoneId = (String)((Map)entry.getValue()).get("zoneId"); - String groupMappingMode = (String)((Map)entry.getValue()).get("groupMappingMode"); - String providerDescription = (String)((Map)entry.getValue()).get(PROVIDER_DESCRIPTION); - Boolean addShadowUserOnLogin = (Boolean)((Map)entry.getValue()).get("addShadowUserOnLogin"); - Boolean skipSslValidation = (Boolean)((Map)entry.getValue()).get("skipSslValidation"); - Boolean storeCustomAttributes = (Boolean)((Map)entry.getValue()).get(STORE_CUSTOM_ATTRIBUTES_NAME); - Boolean override = (Boolean)((Map)entry.getValue()).get("override"); + String alias = (String) entry.getKey(); + Map saml = (Map) entry.getValue(); + String metaDataLocation = (String) saml.get("idpMetadata"); + String nameID = (String) saml.get("nameID"); + Integer assertionIndex = (Integer) saml.get("assertionConsumerIndex"); + Boolean trustCheck = (Boolean) saml.get("metadataTrustCheck"); + Boolean showLink = (Boolean) ((Map) entry.getValue()).get("showSamlLoginLink"); + String socketFactoryClassName = (String) saml.get("socketFactoryClassName"); + String linkText = (String) ((Map) entry.getValue()).get("linkText"); + String iconUrl = (String) ((Map) entry.getValue()).get("iconUrl"); + String zoneId = (String) ((Map) entry.getValue()).get("zoneId"); + String groupMappingMode = (String) ((Map) entry.getValue()).get("groupMappingMode"); + String providerDescription = (String) ((Map) entry.getValue()).get(PROVIDER_DESCRIPTION); + Boolean addShadowUserOnLogin = (Boolean) ((Map) entry.getValue()).get("addShadowUserOnLogin"); + Boolean skipSslValidation = (Boolean) ((Map) entry.getValue()).get("skipSslValidation"); + Boolean storeCustomAttributes = (Boolean) ((Map) entry.getValue()).get(STORE_CUSTOM_ATTRIBUTES_NAME); + Boolean override = (Boolean) ((Map) entry.getValue()).get("override"); List authnContext = (List) saml.get("authnContext"); if (storeCustomAttributes == null) { storeCustomAttributes = true; //default value } - if (skipSslValidation==null) { + if (skipSslValidation == null) { skipSslValidation = socketFactoryClassName == null; } @@ -143,19 +156,21 @@ public void setIdentityProviders(Map> providers) { if (hasText(providerDescription)) { def.setProviderDescription(providerDescription); } - if (alias==null) { - throw new IllegalArgumentException("Invalid IDP - alias must not be null ["+metaDataLocation+"]"); + if (alias == null) { + throw new IllegalArgumentException("Invalid IDP - alias must not be null [" + metaDataLocation + "]"); } - if (metaDataLocation==null) { - throw new IllegalArgumentException("Invalid IDP - metaDataLocation must not be null ["+alias+"]"); + if (metaDataLocation == null) { + throw new IllegalArgumentException("Invalid IDP - metaDataLocation must not be null [" + alias + "]"); } def.setIdpEntityAlias(alias); - def.setAssertionConsumerIndex(assertionIndex== null ? 0 :assertionIndex); + def.setAssertionConsumerIndex(assertionIndex == null ? 0 : assertionIndex); def.setMetaDataLocation(metaDataLocation); def.setNameID(nameID); - def.setMetadataTrustCheck(trustCheck==null?true:trustCheck); - if(hasText(groupMappingMode)) { def.setGroupMappingMode(ExternalGroupMappingMode.valueOf(groupMappingMode)); } - def.setShowSamlLink(showLink==null?true: showLink); + def.setMetadataTrustCheck(trustCheck == null || trustCheck); + if (hasText(groupMappingMode)) { + def.setGroupMappingMode(ExternalGroupMappingMode.valueOf(groupMappingMode)); + } + def.setShowSamlLink(showLink == null || showLink); def.setSocketFactoryClassName(socketFactoryClassName); def.setLinkText(linkText); def.setIconUrl(iconUrl); @@ -163,45 +178,21 @@ public void setIdentityProviders(Map> providers) { def.setExternalGroupsWhitelist(externalGroupsWhitelist); def.setAttributeMappings(attributeMappings); def.setZoneId(hasText(zoneId) ? zoneId : IdentityZone.getUaaZoneId()); - def.setAddShadowUserOnLogin(addShadowUserOnLogin==null?true:addShadowUserOnLogin); + def.setAddShadowUserOnLogin(addShadowUserOnLogin == null || addShadowUserOnLogin); def.setSkipSslValidation(skipSslValidation); def.setAuthnContext(authnContext); - - IdentityProvider provider = parseSamlProvider(def); - try { - if (def.getType() == SamlIdentityProviderDefinition.MetadataLocation.DATA) { - ExtendedMetadataDelegate metadataDelegate = samlConfigurator.getExtendedMetadataDelegate(def); - def.setIdpEntityId(((ConfigMetadataProvider) metadataDelegate.getDelegate()).getEntityID()); - } - } catch (MetadataProviderException e) { - throw new IllegalArgumentException(e.getMessage(), e); + IdentityProvider provider = parseSamlProvider(def); + if (def.getType() == SamlIdentityProviderDefinition.MetadataLocation.DATA) { + RelyingPartyRegistration metadataDelegate = samlConfigurator.getExtendedMetadataDelegate(def); + def.setIdpEntityId(metadataDelegate.getAssertingPartyDetails().getEntityId()); } - IdentityProviderWrapper wrapper = new IdentityProviderWrapper(provider); - wrapper.setOverride(override == null ? true : override); + IdentityProviderWrapper wrapper = new IdentityProviderWrapper<>(provider); + wrapper.setOverride(override == null || override); samlProviders.add(wrapper); - } } - public static IdentityProvider parseSamlProvider(SamlIdentityProviderDefinition def) { - IdentityProvider provider = new IdentityProvider(); - provider.setType(OriginKeys.SAML); - provider.setOriginKey(def.getIdpEntityAlias()); - provider.setName("UAA SAML Identity Provider["+provider.getOriginKey()+"]"); - provider.setActive(true); - try { - provider.setConfig(def); - } catch (JsonUtils.JsonUtilException x) { - throw new RuntimeException("Non serializable SAML config"); - } - return provider; - } - - public String getLegacyIdpIdentityAlias() { - return legacyIdpIdentityAlias; - } - public void setLegacyIdpIdentityAlias(String legacyIdpIdentityAlias) { if ("null".equals(legacyIdpIdentityAlias)) { this.legacyIdpIdentityAlias = null; @@ -210,10 +201,6 @@ public void setLegacyIdpIdentityAlias(String legacyIdpIdentityAlias) { } } - public String getLegacyIdpMetaData() { - return legacyIdpMetaData; - } - public void setLegacyIdpMetaData(String legacyIdpMetaData) { if ("null".equals(legacyIdpMetaData)) { this.legacyIdpMetaData = null; @@ -222,38 +209,6 @@ public void setLegacyIdpMetaData(String legacyIdpMetaData) { } } - public String getLegacyNameId() { - return legacyNameId; - } - - public void setLegacyNameId(String legacyNameId) { - this.legacyNameId = legacyNameId; - } - - public int getLegacyAssertionConsumerIndex() { - return legacyAssertionConsumerIndex; - } - - public void setLegacyAssertionConsumerIndex(int legacyAssertionConsumerIndex) { - this.legacyAssertionConsumerIndex = legacyAssertionConsumerIndex; - } - - public boolean isLegacyMetadataTrustCheck() { - return legacyMetadataTrustCheck; - } - - public void setLegacyMetadataTrustCheck(boolean legacyMetadataTrustCheck) { - this.legacyMetadataTrustCheck = legacyMetadataTrustCheck; - } - - public boolean isLegacyShowSamlLink() { - return legacyShowSamlLink; - } - - public void setLegacyShowSamlLink(boolean legacyShowSamlLink) { - this.legacyShowSamlLink = legacyShowSamlLink; - } - @Override public void afterPropertiesSet() { parseIdentityProviderDefinitions(); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/CertificateRuntimeException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/CertificateRuntimeException.java new file mode 100644 index 00000000000..7a1d1621fd1 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/CertificateRuntimeException.java @@ -0,0 +1,9 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import java.security.cert.CertificateException; + +public class CertificateRuntimeException extends RuntimeException { + public CertificateRuntimeException(CertificateException e) { + super(e); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java deleted file mode 100644 index 22d26fb17c6..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * ***************************************************************************** - */ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.opensaml.saml2.metadata.EntitiesDescriptor; -import org.opensaml.saml2.metadata.EntityDescriptor; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.opensaml.xml.XMLObject; - -public interface ComparableProvider extends Comparable { - - String getAlias(); - String getZoneId(); - - XMLObject doGetMetadata() throws MetadataProviderException; - byte[] fetchMetadata(); - - default String getEntityID() throws MetadataProviderException { - fetchMetadata(); - XMLObject metadata = doGetMetadata(); - if (metadata instanceof EntityDescriptor) { - EntityDescriptor entityDescriptor = (EntityDescriptor) metadata; - return entityDescriptor.getEntityID(); - } else if (metadata instanceof EntitiesDescriptor) { - EntitiesDescriptor desc = (EntitiesDescriptor)metadata; - if (desc.getEntityDescriptors().size()!=1) { - throw new MetadataProviderException("Invalid metadata. Number of descriptors must be 1, but is "+desc.getEntityDescriptors().size()); - } else { - return desc.getEntityDescriptors().get(0).getEntityID(); - } - } else { - throw new MetadataProviderException("Unknown descriptor class:"+metadata.getClass().getName()); - } - } - - default int compareTo(ComparableProvider that) { - int result = 0; - - if (this == that) return 0; - - if (this.getAlias() == null) { - if(that.getAlias() != null) { - return -1; - } - } else { - if(that.getAlias() == null) { - return 1; - } - result = this.getAlias().compareTo(that.getAlias()); - if(0!=result) return result; - } - - if (this.getZoneId() == null) { - if(that.getZoneId() != null) { - return -1; - } - } else { - if(that.getZoneId() == null) { - return 1; - } - result = this.getZoneId().compareTo(that.getZoneId()); - } - return result; - } - - default int getHashCode() { - int result = getZoneId() !=null ? getZoneId().hashCode():0; - result = 31 * result + (getAlias() != null ? getAlias().hashCode():0); - return result; - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProvider.java deleted file mode 100644 index e1f31ba9314..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.opensaml.saml2.metadata.provider.AbstractMetadataProvider; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.opensaml.xml.XMLObject; -import org.opensaml.xml.io.UnmarshallingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -public class ConfigMetadataProvider extends AbstractMetadataProvider implements ComparableProvider { - - private final Logger log = LoggerFactory.getLogger(ConfigMetadataProvider.class); - - private final String metadata; - private final String zoneId; - private final String alias; - - public ConfigMetadataProvider(String zoneId, String alias, String metadata) { - this.metadata = metadata; - this.alias = alias; - this.zoneId = zoneId; - } - - public byte[] fetchMetadata() { - return metadata.getBytes(StandardCharsets.UTF_8); - } - - @Override - public XMLObject doGetMetadata() throws MetadataProviderException { - - InputStream stream = new ByteArrayInputStream(metadata.getBytes(StandardCharsets.UTF_8)); - - try { - return unmarshallMetadata(stream); - } catch (UnmarshallingException e) { - log.error("Unable to unmarshall metadata", e); - throw new MetadataProviderException(e); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || !(o instanceof ComparableProvider)) return false; - return this.compareTo((ComparableProvider)o) == 0; - } - - @Override - public int hashCode() { - return getHashCode(); - } - - @Override - public String getAlias() { - return alias; - } - - @Override - public String getZoneId() { - return zoneId; - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepository.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepository.java new file mode 100644 index 00000000000..83353de423c --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepository.java @@ -0,0 +1,78 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.util.KeyWithCert; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.ZoneAware; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.Optional; + +@Slf4j +public class ConfiguratorRelyingPartyRegistrationRepository extends BaseUaaRelyingPartyRegistrationRepository + implements RelyingPartyRegistrationRepository, ZoneAware { + + private final SamlIdentityProviderConfigurator configurator; + + public ConfiguratorRelyingPartyRegistrationRepository(String uaaWideSamlEntityID, + String uaaWideSamlEntityIDAlias, + SamlIdentityProviderConfigurator configurator, + List signatureAlgorithms, + String uaaWideSamlNameId) { + super(uaaWideSamlEntityID, uaaWideSamlEntityIDAlias, signatureAlgorithms, uaaWideSamlNameId); + Assert.notNull(configurator, "configurator cannot be null"); + this.configurator = configurator; + } + + /** + * Returns the relying party registration identified by the provided + * {@code registrationId}, or {@code null} if not found. + * + * @param registrationId the registration identifier + * @return the {@link RelyingPartyRegistration} if found, otherwise {@code null} + */ + @Override + public RelyingPartyRegistration findByRegistrationId(String registrationId) { + IdentityZone currentZone = retrieveZone(); + List identityProviderDefinitions = configurator.getIdentityProviderDefinitionsForZone(currentZone); + + for (SamlIdentityProviderDefinition identityProviderDefinition : identityProviderDefinitions) { + if (identityProviderDefinition.getIdpEntityAlias().equals(registrationId)) { + return createRelyingPartyRegistration(registrationId, identityProviderDefinition, currentZone); + } + } + + if (!identityProviderDefinitions.isEmpty()) { + SamlIdentityProviderDefinition identityProviderDefinition = identityProviderDefinitions.get(0); + return createRelyingPartyRegistration(identityProviderDefinition.getIdpEntityAlias(), identityProviderDefinition, currentZone); + } + + return null; + } + + private RelyingPartyRegistration createRelyingPartyRegistration(String registrationId, SamlIdentityProviderDefinition identityProviderDefinition, IdentityZone currentZone) { + SamlKeyManager samlKeyManager = retrieveKeyManager(); + List keyWithCerts = samlKeyManager.getAvailableCredentials(); + + String zonedSamlEntityID = getZoneEntityId(currentZone); + String zonedSamlEntityIDAlias = getZoneEntityIdAlias(currentZone); + boolean requestSigned = currentZone.getConfig().getSamlConfig().isRequestSigned(); + String nameID = Optional.ofNullable(identityProviderDefinition.getNameID()).orElse(uaaWideSamlNameId); + + RelyingPartyRegistrationBuilder.Params params = RelyingPartyRegistrationBuilder.Params.builder() + .samlEntityID(zonedSamlEntityID) + .samlSpNameId(nameID) + .keys(keyWithCerts) + .metadataLocation(identityProviderDefinition.getMetaDataLocation()) + .rpRegistrationId(registrationId) + .samlSpAlias(zonedSamlEntityIDAlias) + .requestSigned(requestSigned) + .signatureAlgorithms(signatureAlgorithms) + .build(); + return RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(params); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepository.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepository.java new file mode 100644 index 00000000000..07a2c79b587 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepository.java @@ -0,0 +1,61 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.util.KeyWithCert; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; + +import java.util.List; + +/** + * A ZoneAware {@link RelyingPartyRegistrationRepository} that always returns a default + * {@link RelyingPartyRegistrationRepository}. The default {@link RelyingPartyRegistration} in the + * {@link SamlRelyingPartyRegistrationRepositoryConfig} is configured to use a dummy SAML IdP metadata + * for the default zone (named example), this class also provides a dummy SAML IdP RelyingPartyRegistration + * but for non-default zones. + */ +public class DefaultRelyingPartyRegistrationRepository extends BaseUaaRelyingPartyRegistrationRepository { + public static final String CLASSPATH_DUMMY_SAML_IDP_METADATA_XML = "classpath:dummy-saml-idp-metadata.xml"; + + public DefaultRelyingPartyRegistrationRepository(String uaaWideSamlEntityID, + String uaaWideSamlEntityIDAlias, + List signatureAlgorithms, + String uaaWideSamlNameId) { + super(uaaWideSamlEntityID, uaaWideSamlEntityIDAlias, signatureAlgorithms, uaaWideSamlNameId); + } + + /** + * Returns the relying party registration identified by the provided + * {@code registrationId}, or {@code null} if not found. + * + * @param registrationId the registration identifier + * @return the {@link RelyingPartyRegistration} if found, otherwise {@code null} + */ + @Override + public RelyingPartyRegistration findByRegistrationId(String registrationId) { + IdentityZone currentZone = retrieveZone(); + + boolean requestSigned = true; + if (currentZone.getConfig() != null && currentZone.getConfig().getSamlConfig() != null) { + requestSigned = currentZone.getConfig().getSamlConfig().isRequestSigned(); + } + + SamlKeyManager samlKeyManager = retrieveKeyManager(); + List keyWithCerts = samlKeyManager.getAvailableCredentials(); + + String zonedSamlEntityID = getZoneEntityId(currentZone); + String zonedSamlEntityIDAlias = getZoneEntityIdAlias(currentZone); + + RelyingPartyRegistrationBuilder.Params params = RelyingPartyRegistrationBuilder.Params.builder() + .samlEntityID(zonedSamlEntityID) + .samlSpNameId(uaaWideSamlNameId) + .keys(keyWithCerts) + .metadataLocation(CLASSPATH_DUMMY_SAML_IDP_METADATA_XML) + .rpRegistrationId(registrationId) + .samlSpAlias(zonedSamlEntityIDAlias) + .requestSigned(requestSigned) + .signatureAlgorithms(signatureAlgorithms) + .build(); + return RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(params); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepository.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepository.java new file mode 100644 index 00000000000..d63a0bc71f0 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepository.java @@ -0,0 +1,49 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.zone.ZoneAware; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.util.Assert; + +import java.util.Arrays; +import java.util.List; + +/** + * A {@link RelyingPartyRegistrationRepository} that delegates to a list of other {@link RelyingPartyRegistrationRepository} + * instances. + */ +public class DelegatingRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository, ZoneAware { + + private final List delegates; + + public DelegatingRelyingPartyRegistrationRepository(List delegates) { + Assert.notEmpty(delegates, "delegates cannot be empty"); + this.delegates = delegates; + } + + public DelegatingRelyingPartyRegistrationRepository(RelyingPartyRegistrationRepository... delegates) { + Assert.notEmpty(delegates, "delegates cannot be empty"); + this.delegates = Arrays.asList(delegates); + } + + /** + * Returns the relying party registration identified by the provided + * {@code registrationId}, or {@code null} if not found. + * + * @param registrationId the registration identifier + * @return the {@link RelyingPartyRegistration} if found, otherwise {@code null} + */ + @Override + public RelyingPartyRegistration findByRegistrationId(String registrationId) { + boolean isDefaultZone = retrieveZone().isUaa(); + for (RelyingPartyRegistrationRepository repository : this.delegates) { + if (isDefaultZone || repository instanceof ZoneAware) { + RelyingPartyRegistration registration = repository.findByRegistrationId(registrationId); + if (registration != null) { + return registration; + } + } + } + return null; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FilesystemMetadataProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FilesystemMetadataProvider.java deleted file mode 100644 index 834229534ca..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FilesystemMetadataProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.provider.saml; - - -import org.opensaml.saml2.metadata.provider.MetadataProviderException; - -import java.io.File; -import java.util.Timer; - -public class FilesystemMetadataProvider extends org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider { - - public FilesystemMetadataProvider(Timer backgroundTaskTimer, File metadata) throws MetadataProviderException { - super(backgroundTaskTimer, metadata); - } - - @Override - public byte[] fetchMetadata() throws MetadataProviderException { - return super.fetchMetadata(); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FixedHttpMetaDataProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FixedHttpMetaDataProvider.java index 77ca1a0a039..2f3ba854f42 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FixedHttpMetaDataProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FixedHttpMetaDataProvider.java @@ -1,7 +1,6 @@ package org.cloudfoundry.identity.uaa.provider.saml; import org.cloudfoundry.identity.uaa.cache.UrlContentCache; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.springframework.web.client.RestTemplate; import java.net.URI; @@ -22,21 +21,19 @@ public FixedHttpMetaDataProvider( this.cache = cache; } - public byte[] fetchMetadata(String metadataURL, boolean isSkipSSLValidation) throws MetadataProviderException { + public byte[] fetchMetadata(String metadataURL, boolean isSkipSSLValidation) throws MetadataProviderNotFoundException { validateMetadataURL(metadataURL); - if (isSkipSSLValidation) { return cache.getUrlContent(metadataURL, trustingRestTemplate); } return cache.getUrlContent(metadataURL, nonTrustingRestTemplate); } - private void validateMetadataURL(String metadataURL) throws MetadataProviderException { + private void validateMetadataURL(String metadataURL) throws MetadataProviderNotFoundException { try { new URI(metadataURL); } catch (URISyntaxException e) { - throw new MetadataProviderException("Illegal URL syntax", e); + throw new MetadataProviderNotFoundException("Illegal URL syntax", e); } } - } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityZoneConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityZoneConfig.java new file mode 100644 index 00000000000..634b2fbb5a7 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityZoneConfig.java @@ -0,0 +1,42 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + +import java.security.Security; + +@Configuration +public class IdentityZoneConfig { + + @Bean + public BouncyCastleFipsProvider setUpBouncyCastle() { + BouncyCastleFipsProvider provider = new BouncyCastleFipsProvider(); + Security.addProvider(provider); + return provider; + } + + @Bean + public ZoneAwareKeyManager zoneAwareSamlSpKeyManager() { + return new ZoneAwareKeyManager(); + } + + @Autowired + @Bean + SamlKeyManagerFactory samlKeyManagerFactory(SamlConfigProps samlConfigProps) { + return new SamlKeyManagerFactory(samlConfigProps); + } + + @Autowired + @DependsOn({"identityZoneConfigurationBootstrap", "setUpBouncyCastle"}) + @Bean(destroyMethod = "reset") + public IdentityZoneHolder.Initializer identityZoneHolderInitializer(IdentityZoneProvisioning provisioning, + SamlKeyManagerFactory samlKeyManagerFactory) { + + return new IdentityZoneHolder.Initializer(provisioning, samlKeyManagerFactory); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandler.java deleted file mode 100644 index 0334fac8833..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandler.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.apache.http.client.utils.URIBuilder; -import org.cloudfoundry.identity.uaa.util.SessionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import org.springframework.security.web.savedrequest.DefaultSavedRequest; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; -import java.net.URI; - -/** - * This class is used to provide OAuth error redirects when SAML login fails - * with LoginSAMLException. Currently, the only scenario for this is when a - * shadow account does not exist for the user and the IdP configuration does not - * allow automatic creation of the shadow account. - * - */ -public class LoginSAMLAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { - private static final Logger LOG = LoggerFactory.getLogger(LoginSAMLAuthenticationFailureHandler.class); - - @Override - public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response, - final AuthenticationException exception) throws IOException, ServletException { - - String redirectTo = null; - - if (exception instanceof LoginSAMLException) { - - HttpSession session = request.getSession(); - if (session != null) { - DefaultSavedRequest savedRequest = - (DefaultSavedRequest) SessionUtils.getSavedRequestSession(session); - if (savedRequest != null) { - String[] redirectURI = savedRequest.getParameterMap().get("redirect_uri"); - - if (redirectURI != null && redirectURI.length > 0) { - URI uri = URI.create(redirectURI[0]); - URIBuilder uriBuilder = new URIBuilder(uri); - uriBuilder.addParameter("error", "access_denied"); - uriBuilder.addParameter("error_description", exception.getMessage()); - redirectTo = uriBuilder.toString(); - - if (LOG.isDebugEnabled()) { - LOG.debug("Error redirect to: " + redirectTo); - } - - getRedirectStrategy().sendRedirect(request, response, redirectTo); - } - } - } - } - - if (redirectTo == null) { - Throwable cause = exception.getCause(); - if (cause != null) { - AuthenticationException e = new AuthenticationServiceException(cause.getMessage(), cause.getCause()); - logger.debug(cause); - super.onAuthenticationFailure(request, response, e); - } - else { - logger.debug(exception); - super.onAuthenticationFailure(request, response, exception); - } - - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java deleted file mode 100644 index 376db3356e1..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java +++ /dev/null @@ -1,433 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; -import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; -import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; -import org.cloudfoundry.identity.uaa.user.UserInfo; -import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; -import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; -import org.joda.time.DateTime; -import org.opensaml.saml2.core.AuthnStatement; -import org.opensaml.xml.XMLObject; -import org.opensaml.xml.schema.XSAny; -import org.opensaml.xml.schema.XSBase64Binary; -import org.opensaml.xml.schema.XSBoolean; -import org.opensaml.xml.schema.XSBooleanValue; -import org.opensaml.xml.schema.XSDateTime; -import org.opensaml.xml.schema.XSInteger; -import org.opensaml.xml.schema.XSQName; -import org.opensaml.xml.schema.XSString; -import org.opensaml.xml.schema.XSURI; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.ProviderNotFoundException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.providers.ExpiringUsernameAuthenticationToken; -import org.springframework.security.saml.SAMLAuthenticationProvider; -import org.springframework.security.saml.SAMLAuthenticationToken; -import org.springframework.security.saml.SAMLCredential; -import org.springframework.security.saml.context.SAMLMessageContext; -import org.springframework.security.saml.userdetails.SAMLUserDetailsService; -import org.springframework.stereotype.Component; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; - -import javax.xml.namespace.QName; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; - -import static java.util.Optional.of; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.NotANumber; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_VERIFIED_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken.AUTHENTICATION_CONTEXT_CLASS_REFERENCE; -import static org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils.isAcceptedInvitationAuthentication; -import static org.cloudfoundry.identity.uaa.util.UaaStringUtils.retainAllMatches; - -/** - * SAML Authentication Provider responsible for validating of received SAML messages - */ -@Component("samlAuthenticationProvider") -public class LoginSamlAuthenticationProvider extends SAMLAuthenticationProvider implements ApplicationEventPublisherAware { - - private final static Logger logger = LoggerFactory.getLogger(LoginSamlAuthenticationProvider.class); - - private final IdentityZoneManager identityZoneManager; - private final UaaUserDatabase userDatabase; - private final IdentityProviderProvisioning identityProviderProvisioning; - private final ScimGroupExternalMembershipManager externalMembershipManager; - private ApplicationEventPublisher eventPublisher; - - public LoginSamlAuthenticationProvider( - final IdentityZoneManager identityZoneManager, - final UaaUserDatabase userDatabase, - final JdbcIdentityProviderProvisioning identityProviderProvisioning, - final ScimGroupExternalMembershipManager externalMembershipManager) { - this.identityZoneManager = identityZoneManager; - this.userDatabase = userDatabase; - this.identityProviderProvisioning = identityProviderProvisioning; - this.externalMembershipManager = externalMembershipManager; - } - - @Override - public void setUserDetails(SAMLUserDetailsService userDetails) { - super.setUserDetails(userDetails); - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { - this.eventPublisher = eventPublisher; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - if (!supports(authentication.getClass())) { - throw new IllegalArgumentException("Only SAMLAuthenticationToken is supported, " + authentication.getClass() + " was attempted"); - } - - IdentityZone zone = identityZoneManager.getCurrentIdentityZone(); - logger.debug(String.format("Initiating SAML authentication in zone '%s' domain '%s'", zone.getId(), zone.getSubdomain())); - SAMLAuthenticationToken token = (SAMLAuthenticationToken) authentication; - SAMLMessageContext context = token.getCredentials(); - String alias = context.getPeerExtendedMetadata().getAlias(); - String relayState = context.getRelayState(); - boolean addNew; - IdentityProvider idp; - SamlIdentityProviderDefinition samlConfig; - try { - idp = identityProviderProvisioning.retrieveByOrigin(alias, identityZoneManager.getCurrentIdentityZoneId()); - samlConfig = idp.getConfig(); - addNew = samlConfig.isAddShadowUserOnLogin(); - if (!idp.isActive()) { - throw new ProviderNotFoundException("Identity Provider has been disabled by administrator for alias:" + alias); - } - } catch (EmptyResultDataAccessException x) { - throw new ProviderNotFoundException("No SAML identity provider found in zone for alias:" + alias); - } - - ExpiringUsernameAuthenticationToken result = getExpiringUsernameAuthenticationToken(authentication); - UaaPrincipal samlPrincipal = new UaaPrincipal(NotANumber, result.getName(), result.getName(), alias, result.getName(), zone.getId()); - logger.debug( - String.format( - "Mapped SAML authentication to IDP with origin '%s' and username '%s'", - idp.getOriginKey(), - samlPrincipal.getName() - ) - ); - - Collection samlAuthorities = retrieveSamlAuthorities(samlConfig, (SAMLCredential) result.getCredentials()); - - Collection authorities = null; - SamlIdentityProviderDefinition.ExternalGroupMappingMode groupMappingMode = idp.getConfig().getGroupMappingMode(); - switch (groupMappingMode) { - case EXPLICITLY_MAPPED: - authorities = mapAuthorities(idp.getOriginKey(), samlAuthorities); - break; - case AS_SCOPES: - authorities = new LinkedList<>(samlAuthorities); - break; - } - - Set filteredExternalGroups = filterSamlAuthorities(samlConfig, samlAuthorities); - MultiValueMap userAttributes = retrieveUserAttributes(samlConfig, (SAMLCredential) result.getCredentials()); - - if (samlConfig.getAuthnContext() != null) { - if (Collections.disjoint(userAttributes.get(AUTHENTICATION_CONTEXT_CLASS_REFERENCE), samlConfig.getAuthnContext())) { - throw new BadCredentialsException("Identity Provider did not authenticate with the requested AuthnContext."); - } - } - - UaaUser user = createIfMissing(samlPrincipal, addNew, authorities, userAttributes); - UaaPrincipal principal = new UaaPrincipal(user); - UaaAuthentication resultUaaAuthentication = new LoginSamlAuthenticationToken(principal, result).getUaaAuthentication(user.getAuthorities(), filteredExternalGroups, userAttributes); - publish(new IdentityProviderAuthenticationSuccessEvent(user, resultUaaAuthentication, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZoneId())); - if (samlConfig.isStoreCustomAttributes()) { - userDatabase.storeUserInfo(user.getId(), - new UserInfo() - .setUserAttributes(resultUaaAuthentication.getUserAttributes()) - .setRoles(new LinkedList(resultUaaAuthentication.getExternalGroups())) - ); - } - configureRelayRedirect(relayState); - - return resultUaaAuthentication; - } - - public void configureRelayRedirect(String relayState) { - //configure relay state - if (UaaUrlUtils.isUrl(relayState)) { - RequestContextHolder.currentRequestAttributes() - .setAttribute( - UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, - relayState, - RequestAttributes.SCOPE_REQUEST - ); - } - } - - protected ExpiringUsernameAuthenticationToken getExpiringUsernameAuthenticationToken(Authentication authentication) { - return (ExpiringUsernameAuthenticationToken) super.authenticate(authentication); - } - - protected void publish(ApplicationEvent event) { - if (eventPublisher != null) { - eventPublisher.publishEvent(event); - } - } - - protected Set filterSamlAuthorities(SamlIdentityProviderDefinition definition, Collection samlAuthorities) { - List whiteList = of(definition.getExternalGroupsWhitelist()).orElse(Collections.EMPTY_LIST); - Set authorities = samlAuthorities.stream().map(s -> s.getAuthority()).collect(Collectors.toSet()); - if (whiteList.isEmpty()) { - return authorities; - } - Set result = retainAllMatches(authorities, whiteList); - logger.debug(String.format("White listed external SAML groups:'%s'", result)); - return result; - } - - protected Collection mapAuthorities(String origin, Collection authorities) { - Collection result = new LinkedList<>(); - logger.debug("Mapping SAML authorities:" + authorities); - for (GrantedAuthority authority : authorities) { - String externalGroup = authority.getAuthority(); - logger.debug("Attempting to map external group: " + externalGroup); - for (ScimGroupExternalMember internalGroup : externalMembershipManager.getExternalGroupMapsByExternalGroup(externalGroup, origin, identityZoneManager.getCurrentIdentityZoneId())) { - String internalName = internalGroup.getDisplayName(); - logger.debug(String.format("Mapped external: '%s' to internal: '%s'", externalGroup, internalName)); - result.add(new SimpleGrantedAuthority(internalName)); - } - } - return result; - } - - private Collection retrieveSamlAuthorities(SamlIdentityProviderDefinition definition, SAMLCredential credential) { - if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) != null) { - List groupAttributeNames = getGroupAttributeNames(definition); - - Collection authorities = new ArrayList<>(); - credential.getAttributes().stream() - .filter(attribute -> groupAttributeNames.contains(attribute.getName()) || groupAttributeNames.contains(attribute.getFriendlyName())) - .filter(attribute -> attribute.getAttributeValues() != null) - .filter(attribute -> attribute.getAttributeValues().size() > 0) - .forEach(attribute -> { - for (XMLObject group : attribute.getAttributeValues()) { - authorities.add(new SamlUserAuthority(getStringValue(attribute.getName(), - definition, - group))); - } - }); - - return authorities; - } - return new ArrayList<>(); - } - - private List getGroupAttributeNames(SamlIdentityProviderDefinition definition) { - List attributeNames = new LinkedList<>(); - - if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) instanceof String) { - attributeNames.add((String) definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME)); - } else if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) instanceof Collection) { - attributeNames.addAll((Collection) definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME)); - } - return attributeNames; - } - - public MultiValueMap retrieveUserAttributes(SamlIdentityProviderDefinition definition, SAMLCredential credential) { - logger.debug(String.format("Retrieving SAML user attributes [zone:%s, origin:%s]", definition.getZoneId(), definition.getIdpEntityAlias())); - MultiValueMap userAttributes = new LinkedMultiValueMap<>(); - if (definition != null && definition.getAttributeMappings() != null) { - for (Entry attributeMapping : definition.getAttributeMappings().entrySet()) { - if (attributeMapping.getValue() instanceof String) { - if (credential.getAttribute((String) attributeMapping.getValue()) != null) { - String key = attributeMapping.getKey(); - for (XMLObject xmlObject : credential.getAttribute((String) attributeMapping.getValue()).getAttributeValues()) { - String value = getStringValue(key, definition, xmlObject); - if (value != null) { - userAttributes.add(key, value); - } - } - } - } - } - } - if (credential.getAuthenticationAssertion() != null && credential.getAuthenticationAssertion().getAuthnStatements() != null) { - for (AuthnStatement statement : credential.getAuthenticationAssertion().getAuthnStatements()) { - if (statement.getAuthnContext() != null && statement.getAuthnContext().getAuthnContextClassRef() != null) { - userAttributes.add(AUTHENTICATION_CONTEXT_CLASS_REFERENCE, statement.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef()); - } - } - } - return userAttributes; - } - - protected String getStringValue(String key, SamlIdentityProviderDefinition definition, XMLObject xmlObject) { - String value = null; - if (xmlObject instanceof XSString) { - value = ((XSString) xmlObject).getValue(); - } else if (xmlObject instanceof XSAny) { - value = ((XSAny) xmlObject).getTextContent(); - } else if (xmlObject instanceof XSInteger) { - Integer i = ((XSInteger) xmlObject).getValue(); - value = i != null ? i.toString() : null; - } else if (xmlObject instanceof XSBoolean) { - XSBooleanValue b = ((XSBoolean) xmlObject).getValue(); - value = b != null && b.getValue() != null ? b.getValue().toString() : null; - } else if (xmlObject instanceof XSDateTime) { - DateTime d = ((XSDateTime) xmlObject).getValue(); - value = d != null ? d.toString() : null; - } else if (xmlObject instanceof XSQName) { - QName name = ((XSQName) xmlObject).getValue(); - value = name != null ? name.toString() : null; - } else if (xmlObject instanceof XSURI) { - value = ((XSURI) xmlObject).getValue(); - } else if (xmlObject instanceof XSBase64Binary) { - value = ((XSBase64Binary) xmlObject).getValue(); - } - - if (value != null) { - logger.debug(String.format("Found SAML user attribute %s of value %s [zone:%s, origin:%s]", key, value, definition.getZoneId(), definition.getIdpEntityAlias())); - return value; - } else if (xmlObject != null) { - logger.debug(String.format("SAML user attribute %s at is not of type XSString or other recognizable type, %s [zone:%s, origin:%s]", key, xmlObject.getClass().getName(), definition.getZoneId(), definition.getIdpEntityAlias())); - } - return null; - } - - protected UaaUser createIfMissing(UaaPrincipal samlPrincipal, boolean addNew, Collection authorities, MultiValueMap userAttributes) { - UaaUser user = null; - String invitedUserId = null; - boolean is_invitation_acceptance = isAcceptedInvitationAuthentication(); - if (is_invitation_acceptance) { - invitedUserId = (String) RequestContextHolder.currentRequestAttributes().getAttribute("user_id", RequestAttributes.SCOPE_SESSION); - user = userDatabase.retrieveUserById(invitedUserId); - if (userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) != null) { - if (!userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME).equalsIgnoreCase(user.getEmail())) { - throw new BadCredentialsException("SAML User email mismatch. Authenticated email doesn't match invited email."); - } - } else { - userAttributes = new LinkedMultiValueMap<>(userAttributes); - userAttributes.add(EMAIL_ATTRIBUTE_NAME, user.getEmail()); - } - addNew = false; - if (user.getUsername().equals(user.getEmail()) && !user.getUsername().equals(samlPrincipal.getName())) { - user = user.modifyUsername(samlPrincipal.getName()); - } - publish(new InvitedUserAuthenticatedEvent(user)); - user = userDatabase.retrieveUserById(invitedUserId); - } - - boolean userModified = false; - UaaUser userWithSamlAttributes = getUser(samlPrincipal, userAttributes); - try { - if (user == null) { - user = userDatabase.retrieveUserByName(samlPrincipal.getName(), samlPrincipal.getOrigin()); - } - } catch (UsernameNotFoundException e) { - UaaUserPrototype uaaUser = userDatabase.retrieveUserPrototypeByEmail(userWithSamlAttributes.getEmail(), samlPrincipal.getOrigin()); - if (uaaUser != null) { - userModified = true; - user = new UaaUser(uaaUser.withUsername(samlPrincipal.getName())); - } else { - if (!addNew) { - throw new LoginSAMLException("SAML user does not exist. " - + "You can correct this by creating a shadow user for the SAML user.", e); - } - publish(new NewUserAuthenticatedEvent(userWithSamlAttributes)); - try { - user = new UaaUser(userDatabase.retrieveUserPrototypeByName(samlPrincipal.getName(), samlPrincipal.getOrigin())); - } catch (UsernameNotFoundException ex) { - throw new BadCredentialsException("Unable to establish shadow user for SAML user:" + samlPrincipal.getName()); - } - } - } - if (haveUserAttributesChanged(user, userWithSamlAttributes)) { - userModified = true; - user = user.modifyAttributes(userWithSamlAttributes.getEmail(), - userWithSamlAttributes.getGivenName(), - userWithSamlAttributes.getFamilyName(), - userWithSamlAttributes.getPhoneNumber(), - userWithSamlAttributes.getExternalId(), - user.isVerified() || userWithSamlAttributes.isVerified()); - } - publish( - new ExternalGroupAuthorizationEvent( - user, - userModified, - authorities, - true - ) - ); - user = userDatabase.retrieveUserById(user.getId()); - return user; - } - - protected UaaUser getUser(UaaPrincipal principal, MultiValueMap userAttributes) { - if (principal.getName() == null && userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) == null) { - throw new BadCredentialsException("Cannot determine username from credentials supplied"); - } - - String name = principal.getName(); - return UaaUser.createWithDefaults(u -> - u.withId(OriginKeys.NotANumber) - .withUsername(name) - .withEmail(userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME)) - .withPhoneNumber(userAttributes.getFirst(PHONE_NUMBER_ATTRIBUTE_NAME)) - .withPassword("") - .withGivenName(userAttributes.getFirst(GIVEN_NAME_ATTRIBUTE_NAME)) - .withFamilyName(userAttributes.getFirst(FAMILY_NAME_ATTRIBUTE_NAME)) - .withAuthorities(Collections.emptyList()) - .withVerified(Boolean.valueOf(userAttributes.getFirst(EMAIL_VERIFIED_ATTRIBUTE_NAME))) - .withOrigin(principal.getOrigin() != null ? principal.getOrigin() : OriginKeys.LOGIN_SERVER) - .withExternalId(name) - .withZoneId(principal.getZoneId()) - ); - } - - protected boolean haveUserAttributesChanged(UaaUser existingUser, UaaUser user) { - return existingUser.isVerified() != user.isVerified() || - !StringUtils.equals(existingUser.getGivenName(), user.getGivenName()) || - !StringUtils.equals(existingUser.getFamilyName(), user.getFamilyName()) || - !StringUtils.equals(existingUser.getPhoneNumber(), user.getPhoneNumber()) || - !StringUtils.equals(existingUser.getEmail(), user.getEmail())|| - !StringUtils.equals(existingUser.getExternalId(), user.getExternalId()); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationToken.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationToken.java deleted file mode 100644 index 36854f7dee7..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationToken.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.providers.ExpiringUsernameAuthenticationToken; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; - - -public class LoginSamlAuthenticationToken extends ExpiringUsernameAuthenticationToken { - - public static final String AUTHENTICATION_CONTEXT_CLASS_REFERENCE = "acr"; - - private final UaaPrincipal uaaPrincipal; - - public LoginSamlAuthenticationToken(UaaPrincipal uaaPrincipal, ExpiringUsernameAuthenticationToken token) { - super(token.getTokenExpiration(), uaaPrincipal, token.getCredentials(), token.getAuthorities()); - this.uaaPrincipal = uaaPrincipal; - - } - - public UaaPrincipal getUaaPrincipal() { - return uaaPrincipal; - } - - public UaaAuthentication getUaaAuthentication(List uaaAuthorityList, - Set externalGroups, - MultiValueMap userAttributes) { - LinkedMultiValueMap customAttributes = new LinkedMultiValueMap<>(); - for (Map.Entry> entry : userAttributes.entrySet()) { - if (entry.getKey().startsWith(USER_ATTRIBUTE_PREFIX)) { - customAttributes.put(entry.getKey().substring(USER_ATTRIBUTE_PREFIX.length()), entry.getValue()); - } - } - UaaAuthentication authentication = new UaaAuthentication(getUaaPrincipal(), getCredentials(), uaaAuthorityList, externalGroups, customAttributes, null, isAuthenticated(), System.currentTimeMillis(), getTokenExpiration()==null ? -1l : getTokenExpiration().getTime()); - authentication.setAuthenticationMethods(Collections.singleton("ext")); - List acrValues = userAttributes.get(AUTHENTICATION_CONTEXT_CLASS_REFERENCE); - if (acrValues !=null) { - authentication.setAuthContextClassRef(new HashSet<>(acrValues)); - } - return authentication; - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlDiscovery.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlDiscovery.java deleted file mode 100644 index 520a207e997..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlDiscovery.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.provider.saml; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.saml.SAMLDiscovery; -import org.springframework.security.saml.SAMLEntryPoint; -import org.springframework.security.saml.context.SAMLContextProvider; -import org.springframework.security.saml.metadata.ExtendedMetadata; -import org.springframework.security.saml.metadata.MetadataManager; - -public class LoginSamlDiscovery extends SAMLDiscovery { - - private static final Logger logger = LoggerFactory.getLogger(LoginSamlDiscovery.class); - - private MetadataManager metadata; - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - try { - super.doFilter(request, response, chain); - } catch (UnableToFindSamlIDPException x) { - logger.warn("Unable to find SAML IDP", x); - HttpServletResponse httpServletResponse = (HttpServletResponse)response; - HttpServletRequest httpServletRequest = (HttpServletRequest)request; - httpServletResponse.sendRedirect( - httpServletResponse.encodeRedirectURL(httpServletRequest.getContextPath() + "/login?error=idp_not_found") - ); - } catch (Exception allOtherException) { - logger.warn("SAML Discovery exception", allOtherException); - HttpServletResponse httpServletResponse = (HttpServletResponse)response; - HttpServletRequest httpServletRequest = (HttpServletRequest)request; - httpServletRequest.getSession(true).setAttribute("oauth_error", allOtherException.getMessage()); - httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/oauth_error"); - } - } - - - @Override - protected String getPassiveIDP(HttpServletRequest request) { - String paramName = request.getParameter(RETURN_ID_PARAM); - //we have received the alias in our request - //so we need to translate that into an entityID - String idpAlias = request.getParameter(paramName==null?"idp":paramName); - if ( idpAlias!=null ) { - Set idps = metadata.getIDPEntityNames(); - for (String idp : idps) { - try { - ExtendedMetadata emd = metadata.getExtendedMetadata(idp); - if (emd!=null && idpAlias.equals(emd.getAlias())) { - return idp; - } - } catch (MetadataProviderException e) { - String message = "Unable to read extended metadata for alias["+idpAlias+"] IDP["+idp+"]"; - throw new UnableToFindSamlIDPException(message, e); - } - } - } - throw new UnableToFindSamlIDPException("Unable to locate IDP provider for alias:"+idpAlias); - //return super.getPassiveIDP(request); - } - - @Override - @Autowired - public void setMetadata(MetadataManager metadata) { - super.setMetadata(metadata); - this.metadata = metadata; - } - - @Override - @Autowired(required = false) - public void setSamlEntryPoint(SAMLEntryPoint samlEntryPoint) { - super.setSamlEntryPoint(samlEntryPoint); - } - - @Override - @Autowired - public void setContextProvider(SAMLContextProvider contextProvider) { - super.setContextProvider(contextProvider); - } - - public static class UnableToFindSamlIDPException extends RuntimeException { - public UnableToFindSamlIDPException(String message) { - super(message); - } - - public UnableToFindSamlIDPException(String message, Throwable cause) { - super(message, cause); - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlEntryPoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlEntryPoint.java deleted file mode 100644 index db0b4858138..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlEntryPoint.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.provider.saml; - - -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.opensaml.common.SAMLException; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.opensaml.ws.message.encoder.MessageEncodingException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.saml.SAMLEntryPoint; -import org.springframework.security.saml.context.SAMLMessageContext; -import org.springframework.security.saml.metadata.ExtendedMetadata; -import org.springframework.security.saml.websso.WebSSOProfileOptions; -import org.springframework.security.web.WebAttributes; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -public class LoginSamlEntryPoint extends SAMLEntryPoint { - - - private SamlIdentityProviderConfigurator providerDefinitionList; - - public SamlIdentityProviderConfigurator getProviderDefinitionList() { - return providerDefinitionList; - } - - public void setProviderDefinitionList(SamlIdentityProviderConfigurator providerDefinitionList) { - this.providerDefinitionList = providerDefinitionList; - } - - public WebSSOProfileOptions getDefaultProfileOptions() { - return defaultOptions; - } - - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { - try { - - SAMLMessageContext context = contextProvider.getLocalAndPeerEntity(request, response); - - if (isECP(context)) { - initializeECP(context, e); - } else if (isDiscovery(context)) { - initializeDiscovery(context); - } else { - initializeSSO(context, e); - } - } catch (SamlBindingNotSupportedException e1) { - request.setAttribute("error_message_code", "error.sso.supported.binding"); - request.getSession(true).setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, e1); - response.setStatus(400); - request.getRequestDispatcher("/saml_error").include(request, response); - } catch (SAMLException | MessageEncodingException | MetadataProviderException e1) { - logger.debug("Error initializing entry point", e1); - throw new ServletException(e1); - } - } - - @Override - protected WebSSOProfileOptions getProfileOptions(SAMLMessageContext context, AuthenticationException exception) throws MetadataProviderException { - WebSSOProfileOptions options = super.getProfileOptions(context, exception); - String idpEntityId = context.getPeerEntityId(); - if (idpEntityId!=null) { - ExtendedMetadata extendedMetadata = this.metadata.getExtendedMetadata(idpEntityId); - if (extendedMetadata!=null) { - String alias = extendedMetadata.getAlias(); - SamlIdentityProviderDefinition def = getIDPDefinition(alias); - if (def.getNameID()!=null) { - options.setNameID(def.getNameID()); - } - if (def.getAssertionConsumerIndex()>=0) { - options.setAssertionConsumerIndex(def.getAssertionConsumerIndex()); - } - - if (def.getAuthnContext() != null) { - options.setAuthnContexts(def.getAuthnContext()); - } - } - } - return options; - } - - private SamlIdentityProviderDefinition getIDPDefinition(String alias) throws MetadataProviderException { - if (alias!=null) { - for (SamlIdentityProviderDefinition def : getProviderDefinitionList().getIdentityProviderDefinitions()) { - if (alias.equals(def.getIdpEntityAlias()) && IdentityZoneHolder.get().getId().equals(def.getZoneId())) { - return def; - } - } - } - throw new MetadataProviderNotFoundException("Unable to find SAML provider for alias:"+alias); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java index fd9f94c3636..358a3a581b3 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java @@ -14,21 +14,10 @@ package org.cloudfoundry.identity.uaa.provider.saml; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; - -public class MetadataProviderNotFoundException extends MetadataProviderException { - public MetadataProviderNotFoundException() { - } - - public MetadataProviderNotFoundException(String message) { - super(message); - } +import org.springframework.security.saml2.Saml2Exception; +public class MetadataProviderNotFoundException extends Saml2Exception { public MetadataProviderNotFoundException(String message, Exception wrappedException) { super(message, wrappedException); } - - public MetadataProviderNotFoundException(Exception wrappedException) { - super(wrappedException); - } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/NonCachingMetadataCredentialResolver.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/NonCachingMetadataCredentialResolver.java deleted file mode 100644 index 22ddbfd2ac7..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/NonCachingMetadataCredentialResolver.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.opensaml.xml.security.credential.Credential; -import org.springframework.security.saml.key.KeyManager; -import org.springframework.security.saml.metadata.MetadataManager; -import org.springframework.security.saml.trust.MetadataCredentialResolver; - -import java.util.Collection; - - -public class NonCachingMetadataCredentialResolver extends MetadataCredentialResolver { - - public NonCachingMetadataCredentialResolver(MetadataManager metadataProvider, KeyManager keyManager) { - super(metadataProvider, keyManager); - } - - @Override - protected void cacheCredentials(MetadataCacheKey cacheKey, Collection credentials) { - //no op - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/NonSnarlMetadataManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/NonSnarlMetadataManager.java deleted file mode 100644 index bc1817c6b66..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/NonSnarlMetadataManager.java +++ /dev/null @@ -1,935 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * ***************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.joda.time.DateTime; -import org.opensaml.common.xml.SAMLConstants; -import org.opensaml.saml2.common.Extensions; -import org.opensaml.saml2.metadata.EntitiesDescriptor; -import org.opensaml.saml2.metadata.EntityDescriptor; -import org.opensaml.saml2.metadata.IDPSSODescriptor; -import org.opensaml.saml2.metadata.RoleDescriptor; -import org.opensaml.saml2.metadata.SPSSODescriptor; -import org.opensaml.saml2.metadata.provider.MetadataFilter; -import org.opensaml.saml2.metadata.provider.MetadataFilterChain; -import org.opensaml.saml2.metadata.provider.MetadataProvider; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.opensaml.saml2.metadata.provider.SignatureValidationFilter; -import org.opensaml.xml.Configuration; -import org.opensaml.xml.Namespace; -import org.opensaml.xml.NamespaceManager; -import org.opensaml.xml.XMLObject; -import org.opensaml.xml.schema.XSBooleanValue; -import org.opensaml.xml.security.x509.BasicPKIXValidationInformation; -import org.opensaml.xml.security.x509.BasicX509CredentialNameEvaluator; -import org.opensaml.xml.security.x509.CertPathPKIXValidationOptions; -import org.opensaml.xml.security.x509.PKIXValidationInformation; -import org.opensaml.xml.security.x509.PKIXValidationInformationResolver; -import org.opensaml.xml.security.x509.StaticPKIXValidationInformationResolver; -import org.opensaml.xml.signature.Signature; -import org.opensaml.xml.signature.SignatureTrustEngine; -import org.opensaml.xml.signature.impl.PKIXSignatureTrustEngine; -import org.opensaml.xml.util.IDIndex; -import org.opensaml.xml.util.LazySet; -import org.opensaml.xml.validation.ValidationException; -import org.opensaml.xml.validation.Validator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.saml.key.KeyManager; -import org.springframework.security.saml.metadata.ExtendedMetadata; -import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; -import org.springframework.security.saml.metadata.ExtendedMetadataProvider; -import org.springframework.security.saml.metadata.MetadataManager; -import org.springframework.security.saml.metadata.MetadataMemoryProvider; -import org.springframework.security.saml.trust.AllowAllSignatureTrustEngine; -import org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer; -import org.springframework.security.saml.util.SAMLUtil; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestClientException; -import org.w3c.dom.Element; - -import javax.xml.namespace.QName; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - - -public class NonSnarlMetadataManager extends MetadataManager implements ExtendedMetadataProvider, InitializingBean, DisposableBean { - - // Class logger - protected final Logger log = LoggerFactory.getLogger(NonSnarlMetadataManager.class); - - private ExtendedMetadata defaultExtendedMetadata; - - // Storage for cryptographic data used to verify metadata signatures - protected KeyManager keyManager; - - private final SamlIdentityProviderConfigurator configurator; - private ZoneAwareMetadataGenerator generator; - - public NonSnarlMetadataManager(SamlIdentityProviderConfigurator configurator) throws MetadataProviderException { - super(Collections.EMPTY_LIST); - this.configurator = configurator; - this.defaultExtendedMetadata = new ExtendedMetadata(); - super.setRefreshCheckInterval(0); - } - - @Override - public void destroy() { - - } - - @Override - public void setProviders(List newProviders) { - } - - @Override - public void refreshMetadata() { - } - - public ExtendedMetadataDelegate getLocalServiceProvider() throws MetadataProviderException { - EntityDescriptor descriptor = generator.generateMetadata(); - ExtendedMetadata extendedMetadata = generator.generateExtendedMetadata(); - log.info("Initialized local service provider for entityID: " + descriptor.getEntityID()); - MetadataMemoryProvider memoryProvider = new MetadataMemoryProvider(descriptor); - memoryProvider.initialize(); - return new ExtendedMetadataDelegate(memoryProvider, extendedMetadata); - } - - @Override - public void addMetadataProvider(MetadataProvider newProvider) { - //no op - } - - @Override - public void removeMetadataProvider(MetadataProvider provider) { - //no op - } - - public List getProviders() { - return new ArrayList<>(getAvailableProviders()); - } - - public List getAvailableProviders() { - IdentityZone zone = IdentityZoneHolder.get(); - List result = new ArrayList<>(); - try { - result.add(getLocalServiceProvider()); - } catch (MetadataProviderException e) { - throw new IllegalStateException(e); - } - for (SamlIdentityProviderDefinition definition : configurator.getIdentityProviderDefinitions()) { - log.info("Adding SAML IDP zone[" + zone.getId() + "] alias[" + definition.getIdpEntityAlias() + "]"); - try { - ExtendedMetadataDelegate delegate = configurator.getExtendedMetadataDelegate(definition); - initializeProvider(delegate); - initializeProviderData(delegate); - initializeProviderFilters(delegate); - result.add(delegate); - } catch (RestClientException | MetadataProviderException e) { - log.error("Invalid SAML IDP zone[" + zone.getId() + "] alias[" + definition.getIdpEntityAlias() + "]", e); - } - } - return result; - } - - @Override - protected void initializeProvider(ExtendedMetadataDelegate provider) throws MetadataProviderException { - // Initialize provider and perform signature verification - log.debug("Initializing extendedMetadataDelegate {}", provider); - provider.initialize(); - - } - - protected String getProviderIdpAlias(ExtendedMetadataDelegate provider) throws MetadataProviderException { - List stringSet = parseProvider(provider); - for (String key : stringSet) { - RoleDescriptor idpRoleDescriptor = provider.getRole(key, IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS); - if (idpRoleDescriptor != null) { - return key; - } - } - return null; - } - - protected String getProviderSpAlias(ExtendedMetadataDelegate provider) throws MetadataProviderException { - List stringSet = parseProvider(provider); - for (String key : stringSet) { - RoleDescriptor spRoleDescriptor = provider.getRole(key, SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS); - if (spRoleDescriptor != null) { - return key; - } - } - return null; - } - - protected String getHostedSpName(ExtendedMetadataDelegate provider) throws MetadataProviderException { - List stringSet = parseProvider(provider); - for (String key : stringSet) { - RoleDescriptor spRoleDescriptor = provider.getRole(key, SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS); - if (spRoleDescriptor != null) { - ExtendedMetadata extendedMetadata = getExtendedMetadata(key, provider); - if (extendedMetadata != null) { - if (extendedMetadata.isLocal()) { - return key; - } - } - } - } - return null; - } - - protected String getProviderAlias(ExtendedMetadataDelegate provider) throws MetadataProviderException { - List stringSet = parseProvider(provider); - for (String key : stringSet) { - // Verify extended metadata - ExtendedMetadata extendedMetadata = getExtendedMetadata(key, provider); - if (extendedMetadata != null) { - if (extendedMetadata.isLocal()) { - // Parse alias - String alias = extendedMetadata.getAlias(); - if (alias != null) { - // Verify alias is valid - SAMLUtil.verifyAlias(alias, key); - return alias; - } else { - log.debug("Local entity {} doesn't have an alias", key); - - } - } else { - log.debug("Remote entity {} available", key); - } - } else { - log.debug("No extended metadata available for entity {}", key); - } - } - return null; - } - /** - * Method populates local storage of IDP and SP names and verifies any name conflicts which might arise. - * - * @param provider provider to initialize - */ - protected void initializeProviderData(ExtendedMetadataDelegate provider) { - } - - @Override - protected void initializeProviderFilters(ExtendedMetadataDelegate provider) throws MetadataProviderException { - boolean requireSignature = provider.isMetadataRequireSignature(); - SignatureTrustEngine trustEngine = getTrustEngine(provider); - SignatureValidationFilter filter = new SignatureValidationFilter(trustEngine); - filter.setRequireSignature(requireSignature); - - log.debug("Created new trust manager for metadata provider {}", provider); - - // Combine any existing filters with the signature verification - MetadataFilter currentFilter = provider.getMetadataFilter(); - if (currentFilter != null) { - if (currentFilter instanceof MetadataFilterChain) { - log.debug("Adding signature filter into existing chain"); - MetadataFilterChain chain = (MetadataFilterChain) currentFilter; - chain.getFilters().add(filter); - } else { - log.debug("Combining signature filter with the existing in a new chain"); - MetadataFilterChain chain = new MetadataFilterChain(); - chain.getFilters().add(currentFilter); - chain.getFilters().add(filter); - } - } else { - log.debug("Adding signature filter"); - provider.setMetadataFilter(filter); - } - } - - @Override - protected SignatureTrustEngine getTrustEngine(MetadataProvider provider) { - - Set trustedKeys = null; - boolean verifyTrust = true; - boolean forceRevocationCheck = false; - - if (provider instanceof ExtendedMetadataDelegate) { - ExtendedMetadataDelegate metadata = (ExtendedMetadataDelegate) provider; - trustedKeys = metadata.getMetadataTrustedKeys(); - verifyTrust = metadata.isMetadataTrustCheck(); - forceRevocationCheck = metadata.isForceMetadataRevocationCheck(); - } - - if (verifyTrust) { - - log.debug("Setting trust verification for metadata provider {}", provider); - - CertPathPKIXValidationOptions pkixOptions = new CertPathPKIXValidationOptions(); - - if (forceRevocationCheck) { - log.debug("Revocation checking forced to true"); - pkixOptions.setForceRevocationEnabled(true); - } else { - log.debug("Revocation checking not forced"); - pkixOptions.setForceRevocationEnabled(false); - } - - return new PKIXSignatureTrustEngine( - getPKIXResolver(provider, trustedKeys, null), - Configuration.getGlobalSecurityConfiguration().getDefaultKeyInfoCredentialResolver(), - new org.springframework.security.saml.trust.CertPathPKIXTrustEvaluator(pkixOptions), - new BasicX509CredentialNameEvaluator()); - - } else { - - log.debug("Trust verification skipped for metadata provider {}", provider); - return new AllowAllSignatureTrustEngine(Configuration.getGlobalSecurityConfiguration().getDefaultKeyInfoCredentialResolver()); - - } - - } - - @Override - protected PKIXValidationInformationResolver getPKIXResolver(MetadataProvider provider, Set trustedKeys, Set trustedNames) { - - // Use all available keys - if (trustedKeys == null) { - trustedKeys = keyManager.getAvailableCredentials(); - } - - // Resolve allowed certificates to build the anchors - List certificates = new LinkedList(); - for (String key : trustedKeys) { - log.debug("Adding PKIX trust anchor {} for metadata verification of provider {}", key, provider); - X509Certificate certificate = keyManager.getCertificate(key); - if (certificate != null) { - certificates.add(certificate); - } else { - log.warn("Cannot construct PKIX trust anchor for key with alias {} for provider {}, key isn't included in the keystore", key, provider); - } - } - - List info = new LinkedList(); - info.add(new BasicPKIXValidationInformation(certificates, null, 4)); - return new StaticPKIXValidationInformationResolver(info, trustedNames); - - } - - @Override - protected List parseProvider(MetadataProvider provider) throws MetadataProviderException { - - List result = new LinkedList(); - - XMLObject object = provider.getMetadata(); - if (object instanceof EntityDescriptor) { - addDescriptor(result, (EntityDescriptor) object); - } else if (object instanceof EntitiesDescriptor) { - addDescriptors(result, (EntitiesDescriptor) object); - } - - return result; - - } - - private void addDescriptors(List result, EntitiesDescriptor descriptors) throws MetadataProviderException { - - log.debug("Found metadata EntitiesDescriptor with ID", descriptors.getID()); - - if (descriptors.getEntitiesDescriptors() != null) { - for (EntitiesDescriptor descriptor : descriptors.getEntitiesDescriptors()) { - addDescriptors(result, descriptor); - } - } - if (descriptors.getEntityDescriptors() != null) { - for (EntityDescriptor descriptor : descriptors.getEntityDescriptors()) { - addDescriptor(result, descriptor); - } - } - - } - - /** - * Parses entityID from the descriptor and adds it to the result set. Signatures on all found entities - * are verified using the given policy and trust engine. - * - * @param result result set - * @param descriptor descriptor to parse - */ - private void addDescriptor(List result, EntityDescriptor descriptor) { - - String entityID = descriptor.getEntityID(); - log.debug("Found metadata EntityDescriptor with ID", entityID); - result.add(entityID); - - } - - @Override - public Set getIDPEntityNames() { - Set result = new HashSet<>(); - for (ExtendedMetadataDelegate delegate : getAvailableProviders()) { - try { - String idp = getProviderIdpAlias(delegate); - if (StringUtils.hasText(idp)) { - result.add(idp); - } - } catch (MetadataProviderException e) { - log.error("Unable to get IDP alias for:"+delegate, e); - } - } - return result; - } - - @Override - public Set getSPEntityNames() { - Set result = new HashSet<>(); - for (ExtendedMetadataDelegate delegate : getAvailableProviders()) { - try { - String sp = getHostedSpName(delegate); - if (StringUtils.hasText(sp)) { - result.add(sp); - } - } catch (MetadataProviderException e) { - log.error("Unable to get IDP alias for:"+delegate, e); - } - } - return result; - } - - @Override - public boolean isIDPValid(String idpID) { - return getIDPEntityNames().contains(idpID); - } - - @Override - public boolean isSPValid(String spID) { - return getIDPEntityNames().contains(spID); - } - - @Override - public String getHostedSPName() { - for (ExtendedMetadataDelegate delegate : getAvailableProviders()) { - try { - String spName = getHostedSpName(delegate); - if (StringUtils.hasText(spName)) { - return spName; - } - } catch (MetadataProviderException e) { - log.error("Unable to find hosted SP name:"+delegate, e); - } - } - return null; - } - - @Override - public void setHostedSPName(String hostedSPName) { - - } - - @Override - public String getDefaultIDP() throws MetadataProviderException { - Iterator iterator = getIDPEntityNames().iterator(); - if (iterator.hasNext()) { - return iterator.next(); - } else { - throw new MetadataProviderException("No IDP was configured, please update included metadata with at least one IDP"); - } - } - - @Override - public void setDefaultIDP(String defaultIDP) { - //no op - } - - @Override - public ExtendedMetadata getExtendedMetadata(String entityID) throws MetadataProviderException { - for (MetadataProvider provider : getProviders()) { - ExtendedMetadata extendedMetadata = getExtendedMetadata(entityID, provider); - if (extendedMetadata != null) { - return extendedMetadata; - } - } - return getDefaultExtendedMetadata().clone(); - } - - private ExtendedMetadata getExtendedMetadata(String entityID, MetadataProvider provider) throws MetadataProviderException { - if (provider instanceof ExtendedMetadataProvider) { - ExtendedMetadataProvider extendedProvider = (ExtendedMetadataProvider) provider; - ExtendedMetadata extendedMetadata = extendedProvider.getExtendedMetadata(entityID); - if (extendedMetadata != null) { - return extendedMetadata.clone(); - } - } - return null; - } - - @Override - public EntityDescriptor getEntityDescriptor(byte[] hash) throws MetadataProviderException { - for (String idp : getIDPEntityNames()) { - if (SAMLUtil.compare(hash, idp)) { - return getEntityDescriptor(idp); - } - } - - for (String sp : getSPEntityNames()) { - if (SAMLUtil.compare(hash, sp)) { - return getEntityDescriptor(sp); - } - } - - return null; - } - - @Override - public String getEntityIdForAlias(String entityAlias) throws MetadataProviderException { - if (entityAlias == null) { - return null; - } - - String entityId = null; - - for (String idp : getIDPEntityNames()) { - ExtendedMetadata extendedMetadata = getExtendedMetadata(idp); - if (extendedMetadata.isLocal() && entityAlias.equals(extendedMetadata.getAlias())) { - if (entityId != null && !entityId.equals(idp)) { - throw new MetadataProviderException("Alias " + entityAlias + " is used both for entity " + entityId + " and " + idp); - } else { - entityId = idp; - } - } - } - - for (String sp : getSPEntityNames()) { - ExtendedMetadata extendedMetadata = getExtendedMetadata(sp); - if (extendedMetadata.isLocal() && entityAlias.equals(extendedMetadata.getAlias())) { - if (entityId != null && !entityId.equals(sp)) { - throw new MetadataProviderException("Alias " + entityAlias + " is used both for entity " + entityId + " and " + sp); - } else { - entityId = sp; - } - } - } - - return entityId; - } - - @Override - public ExtendedMetadata getDefaultExtendedMetadata() { - return defaultExtendedMetadata; - } - - @Override - public void setDefaultExtendedMetadata(ExtendedMetadata defaultExtendedMetadata) { - this.defaultExtendedMetadata = defaultExtendedMetadata; - } - - @Override - public boolean isRefreshRequired() { - return false; - } - - @Override - public void setRefreshRequired(boolean refreshRequired) { - //no op - } - - - @Override - public void setRefreshCheckInterval(long refreshCheckInterval) { - super.setRefreshCheckInterval(0); - } - - public void setKeyManager(KeyManager keyManager) { - this.keyManager = keyManager; - super.setKeyManager(keyManager); - } - - @Autowired(required = false) - public void setTLSConfigurer(TLSProtocolConfigurer configurer) { - // Only explicit dependency - } - - public EntitiesDescriptor getEntitiesDescriptor(String name) { - EntitiesDescriptor descriptor = null; - for (MetadataProvider provider : getProviders()) { - log.debug("Checking child metadata provider for entities descriptor with name: {}", name); - try { - descriptor = provider.getEntitiesDescriptor(name); - if (descriptor != null) { - break; - } - } catch (MetadataProviderException e) { - log.warn("Error retrieving metadata from provider of type {}, proceeding to next provider", - provider.getClass().getName(), e); - continue; - } - } - return descriptor; - } - - /** {@inheritDoc} */ - public EntityDescriptor getEntityDescriptor(String entityID) { - EntityDescriptor descriptor = null; - for (MetadataProvider provider : getProviders()) { - log.debug("Checking child metadata provider for entity descriptor with entity ID: {}", entityID); - try { - descriptor = provider.getEntityDescriptor(entityID); - if (descriptor != null) { - break; - } - } catch (MetadataProviderException e) { - log.warn("Error retrieving metadata from provider of type {}, proceeding to next provider", - provider.getClass().getName(), e); - continue; - } - } - return descriptor; - } - - /** {@inheritDoc} */ - public List getRole(String entityID, QName roleName) { - List roleDescriptors = null; - for (MetadataProvider provider : getProviders()) { - log.debug("Checking child metadata provider for entity descriptor with entity ID: {}", entityID); - try { - roleDescriptors = provider.getRole(entityID, roleName); - if (roleDescriptors != null && !roleDescriptors.isEmpty()) { - break; - } - } catch (MetadataProviderException e) { - log.warn("Error retrieving metadata from provider of type {}, proceeding to next provider", - provider.getClass().getName(), e); - continue; - } - } - return roleDescriptors; - } - - /** {@inheritDoc} */ - public RoleDescriptor getRole(String entityID, QName roleName, String supportedProtocol) { - RoleDescriptor roleDescriptor = null; - for (MetadataProvider provider : getProviders()) { - log.debug("Checking child metadata provider for entity descriptor with entity ID: {}", entityID); - try { - roleDescriptor = provider.getRole(entityID, roleName, supportedProtocol); - if (roleDescriptor != null) { - break; - } - } catch (MetadataProviderException e) { - log.warn("Error retrieving metadata from provider of type {}, proceeding to next provider", - provider.getClass().getName(), e); - continue; - } - } - return roleDescriptor; - } - - @Override - public XMLObject getMetadata() throws MetadataProviderException { - return new ChainingEntitiesDescriptor(); - } - - public void setMetadataGenerator(ZoneAwareMetadataGenerator generator) throws BeansException { - this.generator = generator; - } - - public class ChainingEntitiesDescriptor implements EntitiesDescriptor { - - /** Metadata from the child metadata providers. */ - private ArrayList childDescriptors; - - /** Constructor. */ - public ChainingEntitiesDescriptor() throws MetadataProviderException { - childDescriptors = new ArrayList(); - for (MetadataProvider provider : getProviders()) { - childDescriptors.add(provider.getMetadata()); - } - } - - /** {@inheritDoc} */ - public List getEntitiesDescriptors() { - ArrayList descriptors = new ArrayList<>(); - for (XMLObject descriptor : childDescriptors) { - if (descriptor instanceof EntitiesDescriptor) { - descriptors.add((EntitiesDescriptor) descriptor); - } - } - - return descriptors; - } - - /** {@inheritDoc} */ - public List getEntityDescriptors() { - ArrayList descriptors = new ArrayList<>(); - for (XMLObject descriptor : childDescriptors) { - if (descriptor instanceof EntityDescriptor) { - descriptors.add((EntityDescriptor) descriptor); - } - } - - return descriptors; - } - - /** {@inheritDoc} */ - public Extensions getExtensions() { - return null; - } - - /** {@inheritDoc} */ - public String getID() { - return null; - } - - /** {@inheritDoc} */ - public String getName() { - return null; - } - - /** {@inheritDoc} */ - public void setExtensions(Extensions extensions) { - - } - - /** {@inheritDoc} */ - public void setID(String newID) { - - } - - /** {@inheritDoc} */ - public void setName(String name) { - - } - - /** {@inheritDoc} */ - public String getSignatureReferenceID() { - return null; - } - - /** {@inheritDoc} */ - public Signature getSignature() { - return null; - } - - /** {@inheritDoc} */ - public boolean isSigned() { - return false; - } - - /** {@inheritDoc} */ - public void setSignature(Signature newSignature) { - - } - - /** {@inheritDoc} */ - public void addNamespace(Namespace namespace) { - - } - - /** {@inheritDoc} */ - public void detach() { - - } - - /** {@inheritDoc} */ - public Element getDOM() { - return null; - } - - /** {@inheritDoc} */ - public QName getElementQName() { - return EntitiesDescriptor.DEFAULT_ELEMENT_NAME; - } - - /** {@inheritDoc} */ - public IDIndex getIDIndex() { - return null; - } - - /** {@inheritDoc} */ - public NamespaceManager getNamespaceManager() { - return null; - } - - /** {@inheritDoc} */ - public Set getNamespaces() { - return new LazySet<>(); - } - - /** {@inheritDoc} */ - public String getNoNamespaceSchemaLocation() { - return null; - } - - /** {@inheritDoc} */ - public List getOrderedChildren() { - ArrayList descriptors = new ArrayList<>(); - try { - for (MetadataProvider provider : getProviders()) { - descriptors.add(provider.getMetadata()); - } - } catch (MetadataProviderException e) { - log.error("Unable to generate list of child descriptors", e); - } - - return descriptors; - } - - /** {@inheritDoc} */ - public XMLObject getParent() { - return null; - } - - /** {@inheritDoc} */ - public String getSchemaLocation() { - return null; - } - - /** {@inheritDoc} */ - public QName getSchemaType() { - return EntitiesDescriptor.TYPE_NAME; - } - - /** {@inheritDoc} */ - public boolean hasChildren() { - return !getOrderedChildren().isEmpty(); - } - - /** {@inheritDoc} */ - public boolean hasParent() { - return false; - } - - /** {@inheritDoc} */ - public void releaseChildrenDOM(boolean propagateRelease) { - - } - - /** {@inheritDoc} */ - public void releaseDOM() { - - } - - /** {@inheritDoc} */ - public void releaseParentDOM(boolean propagateRelease) { - - } - - /** {@inheritDoc} */ - public void removeNamespace(Namespace namespace) { - - } - - /** {@inheritDoc} */ - public XMLObject resolveID(String id) { - return null; - } - - /** {@inheritDoc} */ - public XMLObject resolveIDFromRoot(String id) { - return null; - } - - /** {@inheritDoc} */ - public void setDOM(Element dom) { - - } - - /** {@inheritDoc} */ - public void setNoNamespaceSchemaLocation(String location) { - - } - - /** {@inheritDoc} */ - public void setParent(XMLObject parent) { - - } - - /** {@inheritDoc} */ - public void setSchemaLocation(String location) { - - } - - /** {@inheritDoc} */ - public void deregisterValidator(Validator validator) { - - } - - /** {@inheritDoc} */ - public List getValidators() { - return new ArrayList(); - } - - /** {@inheritDoc} */ - public void registerValidator(Validator validator) { - } - - /** {@inheritDoc} */ - public void validate(boolean validateDescendants) { - } - - /** {@inheritDoc} */ - public DateTime getValidUntil() { - return null; - } - - /** {@inheritDoc} */ - public boolean isValid() { - return true; - } - - /** {@inheritDoc} */ - public void setValidUntil(DateTime validUntil) { - - } - - /** {@inheritDoc} */ - public Long getCacheDuration() { - return null; - } - - /** {@inheritDoc} */ - public void setCacheDuration(Long duration) { - - } - - /** {@inheritDoc} */ - public Boolean isNil() { - return Boolean.FALSE; - } - - /** {@inheritDoc} */ - public XSBooleanValue isNilXSBoolean() { - return new XSBooleanValue(Boolean.FALSE, false); - } - - /** {@inheritDoc} */ - public void setNil(Boolean arg0) { - // do nothing - } - - /** {@inheritDoc} */ - public void setNil(XSBooleanValue arg0) { - // do nothing - } - - } -} \ No newline at end of file diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProvider.java new file mode 100644 index 00000000000..e769d656b9d --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProvider.java @@ -0,0 +1,750 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Getter; +import net.shibboleth.utilities.java.support.xml.ParserPool; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.opensaml.core.config.ConfigurationService; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistry; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.schema.XSAny; +import org.opensaml.core.xml.schema.XSBoolean; +import org.opensaml.core.xml.schema.XSBooleanValue; +import org.opensaml.core.xml.schema.XSDateTime; +import org.opensaml.core.xml.schema.XSInteger; +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.core.xml.schema.XSURI; +import org.opensaml.saml.common.assertion.ValidationContext; +import org.opensaml.saml.common.assertion.ValidationResult; +import org.opensaml.saml.saml2.assertion.ConditionValidator; +import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator; +import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters; +import org.opensaml.saml.saml2.assertion.StatementValidator; +import org.opensaml.saml.saml2.assertion.SubjectConfirmationValidator; +import org.opensaml.saml.saml2.assertion.impl.AudienceRestrictionConditionValidator; +import org.opensaml.saml.saml2.assertion.impl.BearerSubjectConfirmationValidator; +import org.opensaml.saml.saml2.assertion.impl.DelegationRestrictionConditionValidator; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.AuthnRequest; +import org.opensaml.saml.saml2.core.AuthnStatement; +import org.opensaml.saml.saml2.core.Condition; +import org.opensaml.saml.saml2.core.OneTimeUse; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.saml.saml2.core.SubjectConfirmation; +import org.opensaml.saml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml.saml2.core.impl.AuthnRequestUnmarshaller; +import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller; +import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; +import org.opensaml.xmlsec.signature.support.SignaturePrevalidator; +import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.log.LogMessage; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.OpenSamlInitializationService; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; +import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; +import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.annotation.Nonnull; +import javax.xml.namespace.QName; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * This was copied from Spring Security, and modified to work with Open SAML 4.0.x + * The original class only works with Open SAML 4.1.x+ + *

+ * Once we can move to the spring-security version of OpenSaml4AuthenticationProvider, + * this class should be removed, along with OpenSamlDecryptionUtils and OpenSamlVerificationUtils. + */ +public final class OpenSaml4AuthenticationProvider implements AuthenticationProvider { + + static { + OpenSamlInitializationService.initialize(); + } + + private final Log logger = LogFactory.getLog(this.getClass()); + + private final ResponseUnmarshaller responseUnmarshaller; + + private static final AuthnRequestUnmarshaller authnRequestUnmarshaller; + + static { + XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); + authnRequestUnmarshaller = (AuthnRequestUnmarshaller) registry.getUnmarshallerFactory() + .getUnmarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME); + } + + private final ParserPool parserPool; + + private final Converter responseSignatureValidator = createDefaultResponseSignatureValidator(); + + private final Consumer responseElementsDecrypter = createDefaultResponseElementsDecrypter(); + + private Converter responseValidator = createDefaultResponseValidator(); + + private final Converter assertionSignatureValidator = createDefaultAssertionSignatureValidator(); + + private final Consumer assertionElementsDecrypter = createDefaultAssertionElementsDecrypter(); + + private final Converter assertionValidator = createDefaultAssertionValidator(); + + private Converter responseAuthenticationConverter = createDefaultResponseAuthenticationConverter(); + + /** + * Creates an {@link OpenSaml4AuthenticationProvider} + */ + public OpenSaml4AuthenticationProvider() { + XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); + this.responseUnmarshaller = (ResponseUnmarshaller) registry.getUnmarshallerFactory() + .getUnmarshaller(Response.DEFAULT_ELEMENT_NAME); + this.parserPool = registry.getParserPool(); + } + + /** + * Set the {@link Converter} to use for validating the SAML 2.0 Response. + *

+ * You can still invoke the default validator by delegating to + * {@link #createDefaultResponseValidator()}, like so: + * + *

+     * OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
+     * provider.setResponseValidator(responseToken -> {
+     * 		Saml2ResponseValidatorResult result = createDefaultResponseValidator()
+     * 			.convert(responseToken)
+     * 		return result.concat(myCustomValidator.convert(responseToken));
+     * });
+     * 
+ * + * @param responseValidator the {@link Converter} to use + * @since 5.6 + */ + public void setResponseValidator(Converter responseValidator) { + Assert.notNull(responseValidator, "responseValidator cannot be null"); + this.responseValidator = responseValidator; + } + + /** + * Set the {@link Converter} to use for converting a validated {@link Response} into + * an {@link AbstractAuthenticationToken}. + *

+ * You can delegate to the default behavior by calling + * {@link #createDefaultResponseAuthenticationConverter()} like so: + * + *

+     * 	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
+     * 	Converter<ResponseToken, Saml2Authentication> authenticationConverter =
+     * 			createDefaultResponseAuthenticationConverter();
+     * 	provider.setResponseAuthenticationConverter(responseToken -> {
+     * 		Saml2Authentication authentication = authenticationConverter.convert(responseToken);
+     * 		User user = myUserRepository.findByUsername(authentication.getName());
+     * 		return new MyAuthentication(authentication, user);
+     *    });
+     * 
+ * + * @param responseAuthenticationConverter the {@link Converter} to use + * @since 5.4 + */ + public void setResponseAuthenticationConverter( + Converter responseAuthenticationConverter) { + Assert.notNull(responseAuthenticationConverter, "responseAuthenticationConverter cannot be null"); + this.responseAuthenticationConverter = responseAuthenticationConverter; + } + + /** + * Construct a default strategy for validating the SAML 2.0 Response + * + * @return the default response validator strategy + * @since 5.6 + */ + public static Converter createDefaultResponseValidator() { + return responseToken -> { + Response response = responseToken.getResponse(); + Saml2AuthenticationToken token = responseToken.getToken(); + Saml2ResponseValidatorResult result = Saml2ResponseValidatorResult.success(); + String statusCode = getStatusCode(response); + if (!StatusCode.SUCCESS.equals(statusCode)) { + String message = String.format("Invalid status [%s] for SAML response [%s]", statusCode, + response.getID()); + result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, message)); + } + + String inResponseTo = response.getInResponseTo(); + result = result.concat(validateInResponseTo(token.getAuthenticationRequest(), inResponseTo)); + + String issuer = response.getIssuer().getValue(); + String destination = response.getDestination(); + String location = token.getRelyingPartyRegistration().getAssertionConsumerServiceLocation(); + if (StringUtils.hasText(destination) && !destination.equals(location)) { + String message = "Invalid destination [" + destination + "] for SAML response [" + response.getID() + + "]"; + result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, message)); + } + String assertingPartyEntityId = token.getRelyingPartyRegistration() + .getAssertingPartyDetails() + .getEntityId(); + if (!StringUtils.hasText(issuer) || !issuer.equals(assertingPartyEntityId)) { + String message = String.format("Invalid issuer [%s] for SAML response [%s]", issuer, response.getID()); + result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, message)); + } + if (response.getAssertions().isEmpty()) { + result = result.concat( + new Saml2Error(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, "No assertions found in response.")); + } + return result; + }; + } + + private static Saml2ResponseValidatorResult validateInResponseTo(AbstractSaml2AuthenticationRequest storedRequest, + String inResponseTo) { + if (!StringUtils.hasText(inResponseTo)) { + return Saml2ResponseValidatorResult.success(); + } + AuthnRequest request = parseRequest(storedRequest); + if (request == null) { + String message = "The response contained an InResponseTo attribute [" + inResponseTo + "]" + + " but no saved authentication request was found"; + return Saml2ResponseValidatorResult + .failure(new Saml2Error(Saml2ErrorCodes.INVALID_IN_RESPONSE_TO, message)); + } + if (!inResponseTo.equals(request.getID())) { + String message = "The InResponseTo attribute [" + inResponseTo + "] does not match the ID of the " + + "authentication request [" + request.getID() + "]"; + return Saml2ResponseValidatorResult + .failure(new Saml2Error(Saml2ErrorCodes.INVALID_IN_RESPONSE_TO, message)); + } + return Saml2ResponseValidatorResult.success(); + } + + /** + * Construct a default strategy for validating each SAML 2.0 Assertion and associated + * {@link Authentication} token + * + * @return the default assertion validator strategy + */ + public static Converter createDefaultAssertionValidator() { + + return createDefaultAssertionValidatorWithParameters( + params -> params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, Duration.ofMinutes(5)), false); + } + + /** + * Construct a default strategy for validating each SAML 2.0 Assertion and associated + * {@link Authentication} token + * + * @param validationContextParameters a consumer for editing the values passed to the + * {@link ValidationContext} for each assertion being validated + * @return the default assertion validator strategy + * @since 5.8 + */ + public static Converter createDefaultAssertionValidatorWithParameters( + Consumer> validationContextParameters, boolean saml2bearer) { + return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION, + assertionToken -> SAML20AssertionValidators.attributeValidator, + assertionToken -> createValidationContext(assertionToken, validationContextParameters, saml2bearer)); + } + + /** + * Construct a default strategy for converting a SAML 2.0 Response and + * {@link Authentication} token into a {@link Saml2Authentication} + * + * @return the default response authentication converter strategy + */ + public static Converter createDefaultResponseAuthenticationConverter() { + return responseToken -> { + Response response = responseToken.response; + Saml2AuthenticationToken token = responseToken.token; + Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); + String username = assertion.getSubject().getNameID().getValue(); + Map> attributes = getAssertionAttributes(assertion); + List sessionIndexes = getSessionIndexes(assertion); + DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes, + sessionIndexes); + String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId(); + principal.setRelyingPartyRegistrationId(registrationId); + return new Saml2Authentication(principal, token.getSaml2Response(), + AuthorityUtils.createAuthorityList("ROLE_USER")); + }; + } + + /** + * @param authentication the authentication request object, must be of type + * {@link Saml2AuthenticationToken} + * @return {@link Saml2Authentication} if the assertion is valid + * @throws AuthenticationException if a validation exception occurs + */ + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + try { + Saml2AuthenticationToken token = (Saml2AuthenticationToken) authentication; + String serializedResponse = token.getSaml2Response(); + Response response = parseResponse(serializedResponse); + process(token, response); + AbstractAuthenticationToken authenticationResponse = this.responseAuthenticationConverter + .convert(new ResponseToken(response, token)); + if (authenticationResponse != null) { + authenticationResponse.setDetails(authentication.getDetails()); + } + return authenticationResponse; + } catch (Saml2AuthenticationException ex) { + throw ex; + } catch (Exception ex) { + throw createAuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex); + } + } + + @Override + public boolean supports(Class authentication) { + return authentication != null && Saml2AuthenticationToken.class.isAssignableFrom(authentication); + } + + private Response parseResponse(String response) throws Saml2Exception, Saml2AuthenticationException { + try { + Document document = this.parserPool + .parse(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8))); + Element element = document.getDocumentElement(); + return (Response) this.responseUnmarshaller.unmarshall(element); + } catch (Exception ex) { + throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, ex.getMessage(), ex); + } + } + + private void process(Saml2AuthenticationToken token, Response response) { + String issuer = response.getIssuer().getValue(); + this.logger.debug(LogMessage.format("Processing SAML response from %s", issuer)); + boolean responseSigned = response.isSigned(); + + ResponseToken responseToken = new ResponseToken(response, token); + Saml2ResponseValidatorResult result = this.responseSignatureValidator.convert(responseToken); + if (responseSigned) { + this.responseElementsDecrypter.accept(responseToken); + } else if (!response.getEncryptedAssertions().isEmpty()) { + result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, + "Did not decrypt response [" + response.getID() + "] since it is not signed")); + } + result = result.concat(this.responseValidator.convert(responseToken)); + boolean allAssertionsSigned = true; + for (Assertion assertion : response.getAssertions()) { + AssertionToken assertionToken = new AssertionToken(assertion, token); + result = result.concat(this.assertionSignatureValidator.convert(assertionToken)); + allAssertionsSigned = allAssertionsSigned && assertion.isSigned(); + if (responseSigned || assertion.isSigned()) { + this.assertionElementsDecrypter.accept(new AssertionToken(assertion, token)); + } + result = result.concat(this.assertionValidator.convert(assertionToken)); + } + if (!responseSigned && !allAssertionsSigned) { + String description = "Either the response or one of the assertions is unsigned. " + + "Please either sign the response or all of the assertions."; + result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, description)); + } + Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions()); + if (firstAssertion != null && !hasName(firstAssertion)) { + Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, + "Assertion [" + firstAssertion.getID() + "] is missing a subject"); + result = result.concat(error); + } + + if (result.hasErrors()) { + Collection errors = result.getErrors(); + if (this.logger.isTraceEnabled()) { + this.logger.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() + + "]: " + errors); + } else if (this.logger.isDebugEnabled()) { + this.logger + .debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]"); + } + Saml2Error first = errors.iterator().next(); + throw createAuthenticationException(first.getErrorCode(), first.getDescription(), null); + } else { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Successfully processed SAML Response [" + response.getID() + "]"); + } + } + } + + private Converter createDefaultResponseSignatureValidator() { + return responseToken -> { + Response response = responseToken.getResponse(); + RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration(); + if (response.isSigned()) { + return OpenSamlVerificationUtils.verifySignature(response, registration).post(response.getSignature()); + } + return Saml2ResponseValidatorResult.success(); + }; + } + + private Consumer createDefaultResponseElementsDecrypter() { + return responseToken -> { + Response response = responseToken.getResponse(); + RelyingPartyRegistration registration = responseToken.getToken().getRelyingPartyRegistration(); + try { + OpenSamlDecryptionUtils.decryptResponseElements(response, registration); + } catch (Exception ex) { + throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); + } + }; + } + + private static String getStatusCode(Response response) { + if (response.getStatus() == null) { + return StatusCode.SUCCESS; + } + if (response.getStatus().getStatusCode() == null) { + return StatusCode.SUCCESS; + } + return response.getStatus().getStatusCode().getValue(); + } + + public static Converter createDefaultAssertionSignatureValidator() { + return createAssertionValidator(Saml2ErrorCodes.INVALID_SIGNATURE, assertionToken -> { + RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration(); + SignatureTrustEngine engine = OpenSamlVerificationUtils.trustEngine(registration); + return SAML20AssertionValidators.createSignatureValidator(engine); + }, assertionToken -> new ValidationContext( + Collections.singletonMap(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false))); + } + + public static Consumer createDefaultAssertionElementsDecrypter() { + return assertionToken -> { + Assertion assertion = assertionToken.getAssertion(); + RelyingPartyRegistration registration = assertionToken.getToken().getRelyingPartyRegistration(); + try { + OpenSamlDecryptionUtils.decryptAssertionElements(assertion, registration); + } catch (Exception ex) { + throw createAuthenticationException(Saml2ErrorCodes.DECRYPTION_ERROR, ex.getMessage(), ex); + } + }; + } + + public static boolean hasName(Assertion assertion) { + if (assertion == null) { + return false; + } + if (assertion.getSubject() == null) { + return false; + } + if (assertion.getSubject().getNameID() == null) { + return false; + } + return assertion.getSubject().getNameID().getValue() != null; + } + + public static Map> getAssertionAttributes(Assertion assertion) { + MultiValueMap attributeMap = new LinkedMultiValueMap<>(); + for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) { + for (Attribute attribute : attributeStatement.getAttributes()) { + List attributeValues = new ArrayList<>(); + for (XMLObject xmlObject : attribute.getAttributeValues()) { + Object attributeValue = getXmlObjectValue(xmlObject); + if (attributeValue != null) { + attributeValues.add(attributeValue); + } + } + attributeMap.addAll(attribute.getName(), attributeValues); + } + } + return new LinkedHashMap<>(attributeMap); // gh-11785 + } + + public static List getSessionIndexes(Assertion assertion) { + List sessionIndexes = new ArrayList<>(); + for (AuthnStatement statement : assertion.getAuthnStatements()) { + sessionIndexes.add(statement.getSessionIndex()); + } + return sessionIndexes; + } + + public static Object getXmlObjectValue(XMLObject xmlObject) { + if (xmlObject instanceof XSAny xsAny) { + return xsAny.getTextContent(); + } + if (xmlObject instanceof XSString xsString) { + return xsString.getValue(); + } + if (xmlObject instanceof XSInteger xsInteger) { + return xsInteger.getValue(); + } + if (xmlObject instanceof XSURI xsUri) { + return xsUri.getURI(); + } + if (xmlObject instanceof XSBoolean xsBoolean) { + XSBooleanValue xsBooleanValue = xsBoolean.getValue(); + return (xsBooleanValue != null) ? xsBooleanValue.getValue() : null; + } + if (xmlObject instanceof XSDateTime xsDateTime) { + return xsDateTime.getValue(); + } + return xmlObject; + } + + public static Saml2AuthenticationException createAuthenticationException(String code, String message, + Exception cause) { + return new Saml2AuthenticationException(new Saml2Error(code, message), cause); + } + + private static Converter createAssertionValidator(String errorCode, + Converter validatorConverter, + Converter contextConverter) { + + return assertionToken -> { + Assertion assertion = assertionToken.assertion; + SAML20AssertionValidator validator = validatorConverter.convert(assertionToken); + ValidationContext context = contextConverter.convert(assertionToken); + try { + ValidationResult result = validator.validate(assertion, context); + if (result == ValidationResult.VALID) { + return Saml2ResponseValidatorResult.success(); + } + } catch (Exception ex) { + String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), + assertion.getParent() != null ? ((Response) assertion.getParent()).getID() : assertion.getID(), + ex.getMessage()); + return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); + } + String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), + assertion.getParent() != null ? ((Response) assertion.getParent()).getID() : assertion.getID(), + context.getValidationFailureMessage()); + return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); + }; + } + + private static ValidationContext createValidationContext(AssertionToken assertionToken, + Consumer> paramsConsumer, + boolean saml2Bearer) { + Saml2AuthenticationToken token = assertionToken.token; + RelyingPartyRegistration relyingPartyRegistration = token.getRelyingPartyRegistration(); + String audience = relyingPartyRegistration.getEntityId(); + String recipient; + if (saml2Bearer) { + recipient = relyingPartyRegistration.getAssertionConsumerServiceLocation().replace("/saml/SSO/alias/", "/oauth/token/alias/"); + } else { + recipient = relyingPartyRegistration.getAssertionConsumerServiceLocation(); + } + String assertingPartyEntityId = relyingPartyRegistration.getAssertingPartyDetails().getEntityId(); + Map params = new HashMap<>(); + Assertion assertion = assertionToken.getAssertion(); + if (!saml2Bearer && assertionContainsInResponseTo(assertion)) { + String requestId = getAuthnRequestId(token.getAuthenticationRequest()); + params.put(SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO, requestId); + } + params.put(SAML2AssertionValidationParameters.COND_VALID_AUDIENCES, Collections.singleton(audience)); + params.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton(recipient)); + params.put(SAML2AssertionValidationParameters.VALID_ISSUERS, Collections.singleton(assertingPartyEntityId)); + paramsConsumer.accept(params); + return new ValidationContext(params); + } + + private static boolean assertionContainsInResponseTo(Assertion assertion) { + if (assertion.getSubject() == null) { + return false; + } + for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) { + SubjectConfirmationData confirmationData = confirmation.getSubjectConfirmationData(); + if (confirmationData == null) { + continue; + } + if (StringUtils.hasText(confirmationData.getInResponseTo())) { + return true; + } + } + return false; + } + + private static String getAuthnRequestId(AbstractSaml2AuthenticationRequest serialized) { + AuthnRequest request = parseRequest(serialized); + if (request == null) { + return null; + } + return request.getID(); + } + + private static AuthnRequest parseRequest(AbstractSaml2AuthenticationRequest request) { + if (request == null) { + return null; + } + String samlRequest = request.getSamlRequest(); + if (!StringUtils.hasText(samlRequest)) { + return null; + } + if (request.getBinding() == Saml2MessageBinding.REDIRECT) { + samlRequest = Saml2Utils.samlInflate(Saml2Utils.samlDecode(samlRequest)); + } else { + samlRequest = new String(Saml2Utils.samlDecode(samlRequest), StandardCharsets.UTF_8); + } + try { + Document document = XMLObjectProviderRegistrySupport.getParserPool() + .parse(new ByteArrayInputStream(samlRequest.getBytes(StandardCharsets.UTF_8))); + Element element = document.getDocumentElement(); + return (AuthnRequest) authnRequestUnmarshaller.unmarshall(element); + } catch (Exception ex) { + String message = "Failed to deserialize associated authentication request [" + ex.getMessage() + "]"; + throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_REQUEST_DATA, message, ex); + } + } + + private static class SAML20AssertionValidators { + + private static final Collection conditions = new ArrayList<>(); + + private static final Collection subjects = new ArrayList<>(); + + private static final Collection statements = new ArrayList<>(); + + private static final SignaturePrevalidator validator = new SAMLSignatureProfileValidator(); + + static { + conditions.add(new AudienceRestrictionConditionValidator()); + conditions.add(new DelegationRestrictionConditionValidator()); + conditions.add(new ConditionValidator() { + @Nonnull + @Override + public QName getServicedCondition() { + return OneTimeUse.DEFAULT_ELEMENT_NAME; + } + + @Nonnull + @Override + public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) { + // applications should validate their own OneTimeUse conditions + return ValidationResult.VALID; + } + }); + subjects.add(new BearerSubjectConfirmationValidator() { + @Override + protected ValidationResult validateAddress(SubjectConfirmation confirmation, Assertion assertion, + ValidationContext context, boolean required) { + // applications should validate their own addresses - gh-7514 + return ValidationResult.VALID; + } + }); + } + + private static final SAML20AssertionValidator attributeValidator = new SAML20AssertionValidator(conditions, + subjects, statements, null, null) { + @Nonnull + @Override + protected ValidationResult validateSignature(Assertion token, ValidationContext context) { + return ValidationResult.VALID; + } + }; + + static SAML20AssertionValidator createSignatureValidator(SignatureTrustEngine engine) { + return new SAML20AssertionValidator(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), engine, + validator) { + @Nonnull + @Override + protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) { + return ValidationResult.VALID; + } + + @Nonnull + @Override + protected ValidationResult validateSubjectConfirmation(Assertion assertion, ValidationContext context) { + return ValidationResult.VALID; + } + + @Nonnull + @Override + protected ValidationResult validateStatements(Assertion assertion, ValidationContext context) { + return ValidationResult.VALID; + } + + @Override + protected ValidationResult validateIssuer(Assertion assertion, ValidationContext context) { + return ValidationResult.VALID; + } + }; + + } + + } + + /** + * A tuple containing an OpenSAML {@link Response} and its associated authentication + * token. + * + * @since 5.4 + */ + @Getter + public static class ResponseToken { + + private final Saml2AuthenticationToken token; + + private final Response response; + + public ResponseToken(Response response, Saml2AuthenticationToken token) { + this.token = token; + this.response = response; + } + } + + /** + * A tuple containing an OpenSAML {@link Assertion} and its associated authentication + * token. + * + * @since 5.4 + */ + @Getter + public static class AssertionToken { + + private final Saml2AuthenticationToken token; + + private final Assertion assertion; + + AssertionToken(Assertion assertion, Saml2AuthenticationToken token) { + this.token = token; + this.assertion = assertion; + } + + public Assertion getAssertion() { return this.assertion; } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlDecryptionUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlDecryptionUtils.java new file mode 100644 index 00000000000..8b73308ec3d --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlDecryptionUtils.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.EncryptedAssertion; +import org.opensaml.saml.saml2.core.EncryptedAttribute; +import org.opensaml.saml.saml2.core.NameID; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.encryption.Decrypter; +import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.CredentialSupport; +import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; +import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; +import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +/** + * This class was copied from Spring Security 5.6.0 to get the OpenSaml4AuthenticationProvider to work. + * It should be removed once we are able to more to the spring-security version of OpenSaml4AuthenticationProvider. + *

+ * Utility methods for decrypting SAML components with OpenSAML + * + * For internal use only. + * + * @author Josh Cummings + */ +final class OpenSamlDecryptionUtils { + + private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( + Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), + new SimpleRetrievalMethodEncryptedKeyResolver())); + + static void decryptResponseElements(Response response, RelyingPartyRegistration registration) { + Decrypter decrypter = decrypter(registration); + for (EncryptedAssertion encryptedAssertion : response.getEncryptedAssertions()) { + try { + Assertion assertion = decrypter.decrypt(encryptedAssertion); + response.getAssertions().add(assertion); + } + catch (Exception ex) { + throw new Saml2Exception(ex); + } + } + } + + static void decryptAssertionElements(Assertion assertion, RelyingPartyRegistration registration) { + Decrypter decrypter = decrypter(registration); + for (AttributeStatement statement : assertion.getAttributeStatements()) { + for (EncryptedAttribute encryptedAttribute : statement.getEncryptedAttributes()) { + try { + Attribute attribute = decrypter.decrypt(encryptedAttribute); + statement.getAttributes().add(attribute); + } + catch (Exception ex) { + throw new Saml2Exception(ex); + } + } + } + if (assertion.getSubject() == null) { + return; + } + if (assertion.getSubject().getEncryptedID() == null) { + return; + } + try { + assertion.getSubject().setNameID((NameID) decrypter.decrypt(assertion.getSubject().getEncryptedID())); + } + catch (Exception ex) { + throw new Saml2Exception(ex); + } + } + + private static Decrypter decrypter(RelyingPartyRegistration registration) { + Collection credentials = new ArrayList<>(); + for (Saml2X509Credential key : registration.getDecryptionX509Credentials()) { + Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey()); + credentials.add(cred); + } + KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); + Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver); + decrypter.setRootInNewDocument(true); + return decrypter; + } + + private OpenSamlDecryptionUtils() { + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlVerificationUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlVerificationUtils.java new file mode 100644 index 00000000000..a103945bd06 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlVerificationUtils.java @@ -0,0 +1,218 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import net.shibboleth.utilities.java.support.resolver.CriteriaSet; +import org.opensaml.core.criterion.EntityIdCriterion; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.criterion.ProtocolCriterion; +import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; +import org.opensaml.saml.saml2.core.Issuer; +import org.opensaml.saml.saml2.core.RequestAbstractType; +import org.opensaml.saml.saml2.core.StatusResponseType; +import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.CredentialResolver; +import org.opensaml.security.credential.UsageType; +import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; +import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; +import org.opensaml.security.credential.impl.CollectionCredentialResolver; +import org.opensaml.security.criteria.UsageCriterion; +import org.opensaml.security.x509.BasicX509Credential; +import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; +import org.opensaml.xmlsec.signature.Signature; +import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; +import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; +import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.web.util.UriUtils; + +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * This class was copied from Spring Security 5.6.0 to get the OpenSaml4AuthenticationProvider to work. + * It should be removed once we are able to more to the spring-security version of OpenSaml4AuthenticationProvider. + *

+ * Utility methods for verifying SAML component signatures with OpenSAML + *

+ * For internal use only. + * + * @author Josh Cummings + */ +final class OpenSamlVerificationUtils { + + static VerifierPartial verifySignature(StatusResponseType object, RelyingPartyRegistration registration) { + return new VerifierPartial(object, registration); + } + + static VerifierPartial verifySignature(RequestAbstractType object, RelyingPartyRegistration registration) { + return new VerifierPartial(object, registration); + } + + static SignatureTrustEngine trustEngine(RelyingPartyRegistration registration) { + Set credentials = new HashSet<>(); + Collection keys = registration.getAssertingPartyDetails().getVerificationX509Credentials(); + for (Saml2X509Credential key : keys) { + BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); + cred.setUsageType(UsageType.SIGNING); + cred.setEntityId(registration.getAssertingPartyDetails().getEntityId()); + credentials.add(cred); + } + CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); + return new ExplicitKeySignatureTrustEngine(credentialsResolver, + DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); + } + + private OpenSamlVerificationUtils() { + + } + + static class VerifierPartial { + + private static final String INVALID_SIGNATURE_FOR_OBJECT = "Invalid signature for object [%s]"; + private static final String INVALID_SIGNATURE_FOR_OBJECT_COLON = "Invalid signature for object [%s]: "; + + private final String id; + + private final CriteriaSet criteria; + + private final SignatureTrustEngine trustEngine; + + VerifierPartial(StatusResponseType object, RelyingPartyRegistration registration) { + this.id = object.getID(); + this.criteria = verificationCriteria(object.getIssuer()); + this.trustEngine = trustEngine(registration); + } + + VerifierPartial(RequestAbstractType object, RelyingPartyRegistration registration) { + this.id = object.getID(); + this.criteria = verificationCriteria(object.getIssuer()); + this.trustEngine = trustEngine(registration); + } + + Saml2ResponseValidatorResult redirect(HttpServletRequest request, String objectParameterName) { + RedirectSignature signature = new RedirectSignature(request, objectParameterName); + if (signature.getAlgorithm() == null) { + return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, + "Missing signature algorithm for object [" + this.id + "]")); + } + if (!signature.hasSignature()) { + return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, + "Missing signature for object [" + this.id + "]")); + } + Collection errors = new ArrayList<>(); + String algorithmUri = signature.getAlgorithm(); + try { + if (!this.trustEngine.validate(signature.getSignature(), signature.getContent(), algorithmUri, + this.criteria, null)) { + errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, + INVALID_SIGNATURE_FOR_OBJECT.formatted(this.id))); + } + } catch (Exception ex) { + errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, + INVALID_SIGNATURE_FOR_OBJECT_COLON.formatted(this.id))); + } + return Saml2ResponseValidatorResult.failure(errors); + } + + Saml2ResponseValidatorResult post(Signature signature) { + Collection errors = new ArrayList<>(); + SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); + try { + profileValidator.validate(signature); + } catch (Exception ex) { + errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, + INVALID_SIGNATURE_FOR_OBJECT_COLON.formatted(this.id))); + } + + try { + if (!this.trustEngine.validate(signature, this.criteria)) { + errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, + INVALID_SIGNATURE_FOR_OBJECT.formatted(this.id))); + } + } catch (Exception ex) { + errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, + INVALID_SIGNATURE_FOR_OBJECT_COLON.formatted(this.id))); + } + + return Saml2ResponseValidatorResult.failure(errors); + } + + private CriteriaSet verificationCriteria(Issuer issuer) { + CriteriaSet criteriaSet = new CriteriaSet(); + criteriaSet.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue()))); + criteriaSet.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS))); + criteriaSet.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); + return criteriaSet; + } + + private static class RedirectSignature { + + private final HttpServletRequest request; + + private final String objectParameterName; + + RedirectSignature(HttpServletRequest request, String objectParameterName) { + this.request = request; + this.objectParameterName = objectParameterName; + } + + String getAlgorithm() { + return this.request.getParameter(Saml2ParameterNames.SIG_ALG); + } + + byte[] getContent() { + if (this.request.getParameter(Saml2ParameterNames.RELAY_STATE) != null) { + return String + .format("%s=%s&%s=%s&%s=%s", this.objectParameterName, UriUtils + .encode(this.request.getParameter(this.objectParameterName), StandardCharsets.ISO_8859_1), + Saml2ParameterNames.RELAY_STATE, + UriUtils.encode(this.request.getParameter(Saml2ParameterNames.RELAY_STATE), + StandardCharsets.ISO_8859_1), + Saml2ParameterNames.SIG_ALG, + UriUtils.encode(getAlgorithm(), StandardCharsets.ISO_8859_1)) + .getBytes(StandardCharsets.UTF_8); + } else { + return String + .format("%s=%s&%s=%s", this.objectParameterName, + UriUtils.encode(this.request.getParameter(this.objectParameterName), + StandardCharsets.ISO_8859_1), + Saml2ParameterNames.SIG_ALG, + UriUtils.encode(getAlgorithm(), StandardCharsets.ISO_8859_1)) + .getBytes(StandardCharsets.UTF_8); + } + } + + byte[] getSignature() { + return Saml2Utils.samlDecode(this.request.getParameter(Saml2ParameterNames.SIGNATURE)); + } + + boolean hasSignature() { + return this.request.getParameter(Saml2ParameterNames.SIGNATURE) != null; + } + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlXmlUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlXmlUtils.java new file mode 100644 index 00000000000..978ec9bc20c --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlXmlUtils.java @@ -0,0 +1,58 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.schema.XSAny; +import org.opensaml.core.xml.schema.XSBase64Binary; +import org.opensaml.core.xml.schema.XSBoolean; +import org.opensaml.core.xml.schema.XSBooleanValue; +import org.opensaml.core.xml.schema.XSDateTime; +import org.opensaml.core.xml.schema.XSInteger; +import org.opensaml.core.xml.schema.XSQName; +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.core.xml.schema.XSURI; + +import javax.xml.namespace.QName; +import java.time.Instant; + +@Slf4j +public class OpenSamlXmlUtils { + + private OpenSamlXmlUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static String getStringValue(String key, SamlIdentityProviderDefinition definition, XMLObject xmlObject) { + String value = null; + if (xmlObject instanceof XSString xsString) { + value = xsString.getValue(); + } else if (xmlObject instanceof XSAny xsAny) { + value = xsAny.getTextContent(); + } else if (xmlObject instanceof XSInteger xsInteger) { + Integer i = xsInteger.getValue(); + value = i != null ? i.toString() : null; + } else if (xmlObject instanceof XSBoolean xsBoolean) { + XSBooleanValue b = xsBoolean.getValue(); + value = b != null && b.getValue() != null ? b.getValue().toString() : null; + } else if (xmlObject instanceof XSDateTime xsDateTime) { + Instant d = xsDateTime.getValue(); + value = d != null ? d.toString() : null; + } else if (xmlObject instanceof XSQName xsQName) { + QName name = xsQName.getValue(); + value = name != null ? name.toString() : null; + } else if (xmlObject instanceof XSURI xsUri) { + value = xsUri.getURI(); + } else if (xmlObject instanceof XSBase64Binary xsBase64Binary) { + value = xsBase64Binary.getValue(); + } + + if (value != null) { + log.debug("Found SAML user attribute {} of value {} [zone:{}, origin:{}]", key, value, definition.getZoneId(), definition.getIdpEntityAlias()); + return value; + } else if (xmlObject != null) { + log.debug("SAML user attribute {} at is not of type XSString or other recognizable type, {} [zone:{}, origin:{}]", key, xmlObject.getClass().getName(), definition.getZoneId(), definition.getIdpEntityAlias()); + } + return null; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilder.java new file mode 100644 index 00000000000..01b40172150 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilder.java @@ -0,0 +1,140 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Builder; +import lombok.Value; +import lombok.With; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.util.KeyWithCert; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.List; +import java.util.function.UnaryOperator; + +/** + * A utility class to build a {@link RelyingPartyRegistration} object from the given parameters + */ +@Slf4j +public class RelyingPartyRegistrationBuilder { + public static final String CLASSPATH_DUMMY_SAML_IDP_METADATA_XML = "classpath:dummy-saml-idp-metadata.xml"; + + private static final UnaryOperator assertionConsumerServiceLocationFunction = "{baseUrl}/saml/SSO/alias/%s"::formatted; + private static final UnaryOperator singleLogoutServiceResponseLocationFunction = "{baseUrl}/saml/SingleLogout/alias/%s"::formatted; + private static final UnaryOperator singleLogoutServiceLocationFunction = "{baseUrl}/saml/SingleLogout/alias/%s"::formatted; + + private RelyingPartyRegistrationBuilder() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + /** + * Build a RelyingPartyRegistration object from the given parameters + * + * @param params the params object used to build the RelyingPartyRegistration object + * @return a RelyingPartyRegistration object + */ + public static RelyingPartyRegistration buildRelyingPartyRegistration(Params builderParams) { + final Params params; + if (StringUtils.isEmpty(builderParams.getMetadataLocation())) { + params = builderParams.withMetadataLocation(CLASSPATH_DUMMY_SAML_IDP_METADATA_XML); + } else { + params = builderParams; + } + + SamlIdentityProviderDefinition.MetadataLocation type = SamlIdentityProviderDefinition.getType(params.metadataLocation); + RelyingPartyRegistration.Builder builder; + if (type == SamlIdentityProviderDefinition.MetadataLocation.DATA) { + try (InputStream stringInputStream = new ByteArrayInputStream(params.metadataLocation.getBytes())) { + builder = RelyingPartyRegistrations.fromMetadata(stringInputStream); + } catch (Exception e) { + log.error("Error reading metadata from string: {}", params.metadataLocation, e); + throw new Saml2Exception(e); + } + } else { + builder = RelyingPartyRegistrations.fromMetadataLocation(params.metadataLocation); + } + + builder.entityId(params.samlEntityID); + if (params.rpRegistrationId != null) builder.registrationId(params.rpRegistrationId); + if (params.samlSpNameId != null) builder.nameIdFormat(params.samlSpNameId); + + return builder + .signingX509Credentials(cred -> params.keys.stream() + .map(k -> Saml2X509Credential.signing(k.getPrivateKey(), k.getCertificate())) + .forEach(cred::add)) + .decryptionX509Credentials(cred -> params.keys.stream().findFirst() + .map(k -> Saml2X509Credential.decryption(k.getPrivateKey(), k.getCertificate())) + .ifPresent(cred::add)) + .assertionConsumerServiceLocation(assertionConsumerServiceLocationFunction.apply(params.samlSpAlias)) + .assertionConsumerServiceBinding(Saml2MessageBinding.POST) + .singleLogoutServiceLocation(singleLogoutServiceLocationFunction.apply(params.samlSpAlias)) + .singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocationFunction.apply(params.samlSpAlias)) + // Accept both POST and REDIRECT bindings + .singleLogoutServiceBindings(c -> { + c.add(Saml2MessageBinding.POST); + c.add(Saml2MessageBinding.REDIRECT); + }) + // alter the default value of the APs wantAuthnRequestsSigned, + // to reflect the UAA configured desire to always sign/or-not the AuthnRequest + .assertingPartyDetails(details -> { + details.wantAuthnRequestsSigned(params.requestSigned); + details.signingAlgorithms(alg -> alg.addAll(params.signatureAlgorithms.stream().map(SignatureAlgorithm::getSignatureAlgorithmURI).toList())); + }).build(); + } + + /** + * Parameters for building a {@link RelyingPartyRegistration} using {@link RelyingPartyRegistrationBuilder} + */ + @Value + @Builder + @With + public static class Params { + /** + * the entityId of the relying party + */ + String samlEntityID; + + /** + * the nameIdFormat of the relying party + */ + String samlSpNameId; + + /** + * A list of KeyWithCert objects, with the first key in the list being the active key, all keys in the + * list will be added for signing. Although it is possible to have multiple decryption keys, + * only the first one will be used to maintain parity with existing UAA + */ + List keys; + + /** + * the location or XML data of the metadata + */ + String metadataLocation; + + /** + * the registrationId of the relying party + */ + String rpRegistrationId; + + /** + * the alias of the relying party for the SAML endpoints + */ + String samlSpAlias; + + /** + * whether the AuthnRequest should be signed + */ + boolean requestSigned; + + /** + * the list of signature algorithms to use for signing + */ + List signatureAlgorithms; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SPWebSSOProfileImpl.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SPWebSSOProfileImpl.java deleted file mode 100644 index b22f938b22a..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SPWebSSOProfileImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.opensaml.saml2.metadata.IDPSSODescriptor; -import org.opensaml.saml2.metadata.SPSSODescriptor; -import org.opensaml.saml2.metadata.SingleSignOnService; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.springframework.security.saml.metadata.MetadataManager; -import org.springframework.security.saml.processor.SAMLProcessor; -import org.springframework.security.saml.websso.WebSSOProfileImpl; -import org.springframework.security.saml.websso.WebSSOProfileOptions; - -import static org.opensaml.common.xml.SAMLConstants.SAML2_POST_BINDING_URI; -import static org.opensaml.common.xml.SAMLConstants.SAML2_REDIRECT_BINDING_URI; - -public class SPWebSSOProfileImpl extends WebSSOProfileImpl { - public SPWebSSOProfileImpl () {} - - public SPWebSSOProfileImpl(SAMLProcessor processor, MetadataManager manager) { - super(processor, manager); - } - - /** - * Determines whether given SingleSignOn service can be used together with this profile. Bindings POST, Artifact - * and Redirect are supported for WebSSO. - * - * @param endpoint endpoint - * @return true if endpoint is supported - */ - @Override - protected boolean isEndpointSupported(SingleSignOnService endpoint) { - return - SAML2_POST_BINDING_URI.equals(endpoint.getBinding()) || - SAML2_REDIRECT_BINDING_URI.equals(endpoint.getBinding()); - } - - @Override - protected SingleSignOnService getSingleSignOnService(WebSSOProfileOptions options, IDPSSODescriptor idpssoDescriptor, SPSSODescriptor spDescriptor) throws MetadataProviderException { - try { - return super.getSingleSignOnService(options, idpssoDescriptor, spDescriptor); - } catch (MetadataProviderException e) { - throw new SamlBindingNotSupportedException(e.getMessage(), e); - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverter.java new file mode 100644 index 00000000000..0f50076378e --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverter.java @@ -0,0 +1,347 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.xml.ParserPool; +import org.cloudfoundry.identity.uaa.authentication.BackwardsCompatibleTokenEndpointAuthenticationFilter; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.authentication.UaaSamlPrincipal; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; +import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.opensaml.core.config.ConfigurationService; +import org.opensaml.core.xml.config.XMLObjectProviderRegistry; +import org.opensaml.saml.common.assertion.ValidationContext; +import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.impl.AssertionUnmarshaller; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.core.convert.converter.Converter; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.ProviderNotFoundException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.OpenSamlInitializationService; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; +import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; +import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; +import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.servlet.http.HttpServletRequest; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.NotANumber; +import static org.cloudfoundry.identity.uaa.provider.saml.OpenSaml4AuthenticationProvider.createDefaultAssertionValidatorWithParameters; + +/** + * This {@link AuthenticationConverter} is used in the SAML2 Bearer Grant exchange in {@link BackwardsCompatibleTokenEndpointAuthenticationFilter} + * + * @see RFC 7522 + */ +@Slf4j +public final class Saml2BearerGrantAuthenticationConverter implements AuthenticationConverter, + ApplicationEventPublisherAware { + + static { + OpenSamlInitializationService.initialize(); + } + + private static final AssertionUnmarshaller assertionUnmarshaller; + + private static final ParserPool parserPool; + + static { + XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); + assertionUnmarshaller = (AssertionUnmarshaller) registry.getUnmarshallerFactory() + .getUnmarshaller(Assertion.DEFAULT_ELEMENT_NAME); + parserPool = registry.getParserPool(); + } + + private final Converter assertionSignatureValidator = OpenSaml4AuthenticationProvider.createDefaultAssertionSignatureValidator(); + + private final Consumer assertionElementsDecrypter = OpenSaml4AuthenticationProvider.createDefaultAssertionElementsDecrypter(); + + private final Converter assertionValidator = createDefaultAssertionValidator(); + + private final Converter assertionTokenAuthenticationConverter = createDefaultAssertionAuthenticationConverter(); + + private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver; + private final IdentityZoneManager identityZoneManager; + private final IdentityProviderProvisioning identityProviderProvisioning; + private final SamlUaaAuthenticationUserManager userManager; + private ApplicationEventPublisher eventPublisher; + + /** + * Creates an {@link Saml2BearerGrantAuthenticationConverter} + */ + public Saml2BearerGrantAuthenticationConverter(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver, + IdentityZoneManager identityZoneManager, + IdentityProviderProvisioning identityProviderProvisioning, + SamlUaaAuthenticationUserManager userManager, + ApplicationEventPublisher eventPublisher) { + + Assert.notNull(relyingPartyRegistrationResolver, "relyingPartyRegistrationResolver cannot be null"); + this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver; + this.identityZoneManager = identityZoneManager; + this.identityProviderProvisioning = identityProviderProvisioning; + this.userManager = userManager; + this.eventPublisher = eventPublisher; + } + + /** + * Construct a default strategy for validating each SAML 2.0 Assertion and associated + * {@link Authentication} token + * + * @return the default assertion validator strategy + */ + public static Converter createDefaultAssertionValidator() { + + return createDefaultAssertionValidatorWithParameters( + params -> params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, Duration.ofMinutes(5)), true); + } + + /** + * Construct a default strategy for converting a SAML 2.0 Assertion and + * {@link Authentication} token into a {@link Saml2Authentication} + * + * @return the default response authentication converter strategy + */ + static Converter createDefaultAssertionAuthenticationConverter() { + return assertionToken -> { + Assertion assertion = assertionToken.getAssertion(); + Saml2AuthenticationToken token = assertionToken.getToken(); + String username = assertion.getSubject().getNameID().getValue(); + Map> attributes = OpenSaml4AuthenticationProvider.getAssertionAttributes(assertion); + List sessionIndexes = OpenSaml4AuthenticationProvider.getSessionIndexes(assertion); + DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes, + sessionIndexes); + String registrationId = token.getRelyingPartyRegistration().getRegistrationId(); + principal.setRelyingPartyRegistrationId(registrationId); + return new Saml2Authentication(principal, token.getSaml2Response(), + AuthorityUtils.createAuthorityList("ROLE_USER")); + }; + } + + /** + * Construct a default strategy for validating each SAML 2.0 Assertion and associated + * {@link Authentication} token + * + * @param validationContextParameters a consumer for editing the values passed to the + * {@link ValidationContext} for each assertion being validated + * @return the default assertion validator strategy + * @since 5.8 + */ + + @Override + public Authentication convert(HttpServletRequest request) throws AuthenticationException { + RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationResolver.resolve(request, null); + + String serializedAssertion = request.getParameter("assertion"); + byte[] decodedAssertion = Saml2Utils.samlDecode(serializedAssertion); + String assertionXml = new String(decodedAssertion, StandardCharsets.UTF_8); + + Assertion assertion = parseAssertion(assertionXml); + Saml2AuthenticationToken authenticationToken = new Saml2AuthenticationToken(relyingPartyRegistration, assertionXml); + process(authenticationToken, assertion); + + String subjectName = assertion.getSubject().getNameID().getValue(); + String alias = relyingPartyRegistration.getRegistrationId(); + IdentityZone zone = identityZoneManager.getCurrentIdentityZone(); + + UaaPrincipal initialPrincipal = new UaaPrincipal(NotANumber, subjectName, subjectName, + alias, subjectName, zone.getId()); + + boolean addNew; + IdentityProvider idp; + SamlIdentityProviderDefinition samlConfig; + try { + idp = identityProviderProvisioning.retrieveByOrigin(alias, identityZoneManager.getCurrentIdentityZoneId()); + samlConfig = idp.getConfig(); + addNew = samlConfig.isAddShadowUserOnLogin(); + if (!idp.isActive()) { + throw new ProviderNotFoundException("Identity Provider has been disabled by administrator for alias:" + alias); + } + } catch (EmptyResultDataAccessException x) { + throw new ProviderNotFoundException("No SAML identity provider found in zone for alias:" + alias); + } + + MultiValueMap userAttributes = new LinkedMultiValueMap<>(); + + log.debug("Mapped SAML authentication to IDP with origin '{}' and username '{}'", + idp.getOriginKey(), initialPrincipal.getName()); + + UaaUser user = userManager.createIfMissing(initialPrincipal, addNew, List.of(), userAttributes); + UaaAuthentication authentication = new UaaAuthentication( + new UaaSamlPrincipal(user), + authenticationToken.getCredentials(), + user.getAuthorities(), + Set.of(), + userAttributes, + null, + true, System.currentTimeMillis(), + -1); + authentication.setAuthenticationMethods(Set.of("ext")); + setAuthContextClassRefs(assertion, authentication); + + publish(new IdentityProviderAuthenticationSuccessEvent(user, authentication, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZoneId())); + + AbstractSaml2AuthenticationRequest authenticationRequest = authenticationToken.getAuthenticationRequest(); + if (authenticationRequest != null) { + String relayState = authenticationRequest.getRelayState(); + configureRelayRedirect(relayState); + } + + return authentication; + } + + private static void setAuthContextClassRefs(Assertion assertion, UaaAuthentication authentication) { + Set authContextClassRef = new HashSet<>(); + assertion.getAuthnStatements().forEach(authnStatement -> { + if (authnStatement.getAuthnContext() != null) { + authContextClassRef.add(authnStatement.getAuthnContext().getAuthnContextClassRef().getURI()); + } + }); + authentication.setAuthContextClassRef(authContextClassRef); + } + + public void configureRelayRedirect(String relayState) { + //configure relay state + if (UaaUrlUtils.isUrl(relayState)) { + RequestContextHolder.currentRequestAttributes() + .setAttribute( + UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, + relayState, + RequestAttributes.SCOPE_REQUEST + ); + } + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.eventPublisher = applicationEventPublisher; + } + + private void publish(ApplicationEvent event) { + if (eventPublisher != null) { + eventPublisher.publishEvent(event); + } + } + + /** + * @param authentication the authentication request object must be of type + * {@link Saml2AuthenticationToken} + * @return {@link Saml2Authentication} if the assertion is valid + * @throws AuthenticationException if a validation exception occurs + */ + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + try { + Saml2AuthenticationToken token = (Saml2AuthenticationToken) authentication; + String serializedAssertion = token.getSaml2Response(); + Assertion assertion = parseAssertion(serializedAssertion); + process(token, assertion); + AbstractAuthenticationToken authenticationResponse = this.assertionTokenAuthenticationConverter + .convert(new OpenSaml4AuthenticationProvider.AssertionToken(assertion, token)); + if (authenticationResponse != null) { + authenticationResponse.setDetails(authentication.getDetails()); + } + return authenticationResponse; + } catch (Saml2AuthenticationException ex) { + throw ex; + } catch (Exception ex) { + throw OpenSaml4AuthenticationProvider.createAuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex); + } + } + + private static Assertion parseAssertion(String assertion) throws Saml2Exception, Saml2AuthenticationException { + try { + Document document = parserPool + .parse(new ByteArrayInputStream(assertion.getBytes(StandardCharsets.UTF_8))); + Element element = document.getDocumentElement(); + return (Assertion) assertionUnmarshaller.unmarshall(element); + } catch (Exception ex) { + throw OpenSaml4AuthenticationProvider.createAuthenticationException(Saml2ErrorCodes.INVALID_ASSERTION, ex.getMessage(), ex); + } + } + + private void process(Saml2AuthenticationToken token, Assertion assertion) { + String issuer = assertion.getIssuer().getValue(); + log.debug("Processing SAML response from {}", issuer); + + OpenSaml4AuthenticationProvider.AssertionToken assertionToken = new OpenSaml4AuthenticationProvider.AssertionToken(assertion, token); + Saml2ResponseValidatorResult result = this.assertionSignatureValidator.convert(assertionToken); + if (assertion.isSigned()) { + this.assertionElementsDecrypter.accept(new OpenSaml4AuthenticationProvider.AssertionToken(assertion, token)); + } + result = result.concat(this.assertionValidator.convert(assertionToken)); + + if (!OpenSaml4AuthenticationProvider.hasName(assertion)) { + Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, + "Assertion [" + assertion.getID() + "] is missing a subject"); + result = result.concat(error); + } + + if (result.hasErrors()) { + Collection errors = result.getErrors(); + if (log.isTraceEnabled()) { + log.trace("Found {} validation errors in SAML assertion [{}}]: {}", errors.size(), assertion.getID(), errors); + } else if (log.isDebugEnabled()) { + log.debug("Found {} validation errors in SAML assertion [{}}]", errors.size(), assertion.getID()); + } + Saml2Error first = errors.iterator().next(); + throw OpenSaml4AuthenticationProvider.createAuthenticationException(first.getErrorCode(), first.getDescription(), null); + } else { + log.debug("Successfully processed SAML Assertion [{}]", assertion.getID()); + } + } + +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2Utils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2Utils.java new file mode 100644 index 00000000000..32b4298320c --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2Utils.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.springframework.security.saml2.Saml2Exception; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterOutputStream; + +/** + * This class contains functions to Encode, Decode, Deflate and Inflate SAML messages. + *

+ * It was copied from Spring-Security + * org.springframework.security.saml2.core.Saml2Utils + *

+ * There are multiple copies of this class in the Spring-Security code, this particular one exposes functionality publicly. + * Others are only used internally. + */ +public final class Saml2Utils { + + private Saml2Utils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static String samlEncode(byte[] b) { + return Base64.getEncoder().encodeToString(b); + } + + public static byte[] samlDecode(String s) { + return Base64.getMimeDecoder().decode(s); + } + + public static byte[] samlDeflate(String s) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(out, + new Deflater(Deflater.DEFLATED, true)); + deflaterOutputStream.write(s.getBytes(StandardCharsets.UTF_8)); + deflaterOutputStream.finish(); + return out.toByteArray(); + } catch (IOException ex) { + throw new Saml2Exception("Unable to deflate string", ex); + } + } + + public static String samlInflate(byte[] b) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + InflaterOutputStream inflaterOutputStream = new InflaterOutputStream(out, new Inflater(true)); + inflaterOutputStream.write(b); + inflaterOutputStream.finish(); + return out.toString(StandardCharsets.UTF_8); + } catch (IOException ex) { + throw new Saml2Exception("Unable to inflate string", ex); + } + } + + /***************************************************************************** + * Below are convenience methods not originally in the Spring-Security class + *****************************************************************************/ + + public static String samlEncode(String s) { + return samlEncode(s.getBytes(StandardCharsets.UTF_8)); + } + + public static String samlDeflateAndEncode(String s) { + return samlEncode(samlDeflate(s)); + } + + public static String samlDecodeAndInflate(String s) { + return samlInflate(samlDecode(s)); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlAuthenticationFilterConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlAuthenticationFilterConfig.java new file mode 100644 index 00000000000..855bb1312b0 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlAuthenticationFilterConfig.java @@ -0,0 +1,262 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.authentication.SamlLogoutRequestValidator; +import org.cloudfoundry.identity.uaa.authentication.SamlLogoutResponseValidator; +import org.cloudfoundry.identity.uaa.authentication.ZoneAwareWhitelistLogoutSuccessHandler; +import org.cloudfoundry.identity.uaa.login.UaaAuthenticationFailureHandler; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthLogoutSuccessHandler; +import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; +import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; +import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter; +import org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter; +import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; +import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter; +import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver; +import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler; +import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.context.SecurityContextRepository; +import org.springframework.security.web.csrf.CsrfLogoutHandler; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import javax.servlet.Filter; + +/** + * Configuration for SAML Filters and Authentication Providers for SAML Authentication. + */ +@Configuration +public class SamlAuthenticationFilterConfig { + + public static final String BACKWARD_COMPATIBLE_ASSERTION_CONSUMER_FILTER_PROCESSES_URI = "/saml/SSO/alias/{registrationId}"; + + /** + * Handles building and forwarding the SAML2 Authentication Request to the IDP. + */ + @Autowired + @Bean + Filter saml2WebSsoAuthenticationRequestFilter(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { + OpenSaml4AuthenticationRequestResolver openSaml4AuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationResolver); + + return new Saml2WebSsoAuthenticationRequestFilter(openSaml4AuthenticationRequestResolver); + } + + @Bean + SecurityContextRepository securityContextRepository() { + return new HttpSessionSecurityContextRepository(); + } + + @Autowired + @Bean + SamlUaaAuthenticationUserManager samlUaaAuthenticationUserManager(final UaaUserDatabase userDatabase, + ApplicationEventPublisher applicationEventPublisher) { + + SamlUaaAuthenticationUserManager samlUaaAuthenticationUserManager = new SamlUaaAuthenticationUserManager(userDatabase); + samlUaaAuthenticationUserManager.setApplicationEventPublisher(applicationEventPublisher); + + return samlUaaAuthenticationUserManager; + } + + @Autowired + @Bean + AuthenticationProvider samlAuthenticationProvider(IdentityZoneManager identityZoneManager, + final JdbcIdentityProviderProvisioning identityProviderProvisioning, + ScimGroupExternalMembershipManager externalMembershipManager, + SamlUaaAuthenticationUserManager samlUaaAuthenticationUserManager, + ApplicationEventPublisher applicationEventPublisher, + SamlConfigProps samlConfigProps) { + + SamlUaaAuthenticationAttributesConverter attributesConverter = new SamlUaaAuthenticationAttributesConverter(); + SamlUaaAuthenticationAuthoritiesConverter authoritiesConverter = new SamlUaaAuthenticationAuthoritiesConverter(externalMembershipManager); + + SamlUaaResponseAuthenticationConverter samlResponseAuthenticationConverter = + new SamlUaaResponseAuthenticationConverter(identityZoneManager, identityProviderProvisioning, + samlUaaAuthenticationUserManager, attributesConverter, authoritiesConverter); + samlResponseAuthenticationConverter.setApplicationEventPublisher(applicationEventPublisher); + + OpenSaml4AuthenticationProvider samlResponseAuthenticationProvider = new OpenSaml4AuthenticationProvider(); + samlResponseAuthenticationProvider.setResponseAuthenticationConverter(samlResponseAuthenticationConverter); + + // This validator ignores wraps the default validator and ignores InResponseTo errors, if configured + UaaInResponseToHandlingResponseValidator uaaInResponseToHandlingResponseValidator = + new UaaInResponseToHandlingResponseValidator(OpenSaml4AuthenticationProvider.createDefaultResponseValidator(), samlConfigProps.getDisableInResponseToCheck()); + samlResponseAuthenticationProvider.setResponseValidator(uaaInResponseToHandlingResponseValidator); + + return samlResponseAuthenticationProvider; + } + + /** + * Handles the return SAML2 Authentication Response from the IDP and creates the Authentication object. + */ + @Autowired + @Bean + Filter saml2WebSsoAuthenticationFilter(AuthenticationProvider samlAuthenticationProvider, + RelyingPartyRegistrationRepository relyingPartyRegistrationRepository, + SecurityContextRepository securityContextRepository, + SamlLoginAuthenticationFailureHandler samlLoginAuthenticationFailureHandler, + UaaSavedRequestAwareAuthenticationSuccessHandler samlLoginAuthenticationSuccessHandler) { + + RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository); + Saml2AuthenticationTokenConverter saml2AuthenticationTokenConverter = new Saml2AuthenticationTokenConverter(relyingPartyRegistrationResolver); + Saml2WebSsoAuthenticationFilter saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(saml2AuthenticationTokenConverter, BACKWARD_COMPATIBLE_ASSERTION_CONSUMER_FILTER_PROCESSES_URI); + + ProviderManager authenticationManager = new ProviderManager(samlAuthenticationProvider); + saml2WebSsoAuthenticationFilter.setAuthenticationManager(authenticationManager); + saml2WebSsoAuthenticationFilter.setSecurityContextRepository(securityContextRepository); + saml2WebSsoAuthenticationFilter.setFilterProcessesUrl(BACKWARD_COMPATIBLE_ASSERTION_CONSUMER_FILTER_PROCESSES_URI); + saml2WebSsoAuthenticationFilter.setAuthenticationFailureHandler(samlLoginAuthenticationFailureHandler); + saml2WebSsoAuthenticationFilter.setAuthenticationSuccessHandler(samlLoginAuthenticationSuccessHandler); + + return saml2WebSsoAuthenticationFilter; + } + + /** + * Handler deciding where to redirect user after unsuccessful login + */ + @Bean + public SamlLoginAuthenticationFailureHandler samlLoginAuthenticationFailureHandler() { + SamlLoginAuthenticationFailureHandler handler = new SamlLoginAuthenticationFailureHandler(); + handler.setDefaultFailureUrl("/saml_error"); + return handler; + } + + /** + * Handler deciding where to redirect user after successful login + */ + @Bean + public UaaSavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() { + return new UaaSavedRequestAwareAuthenticationSuccessHandler(); + } + + @Autowired + @Bean + Saml2LogoutRequestResolver saml2LogoutRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { + return new OpenSaml4LogoutRequestResolver(relyingPartyRegistrationResolver); + } + + /** + * Handles a Relying Party Initiated Logout + * and forwards a Saml2LogoutRequest to IDP/asserting party if configured. + */ + @Autowired + @Bean + Saml2RelyingPartyInitiatedLogoutSuccessHandler saml2RelyingPartyInitiatedLogoutSuccessHandler(Saml2LogoutRequestResolver logoutRequestResolver) { + return new Saml2RelyingPartyInitiatedLogoutSuccessHandler(logoutRequestResolver); + } + + @Autowired + @Bean + UaaDelegatingLogoutSuccessHandler uaaDelegatingLogoutSuccessHandler(ZoneAwareWhitelistLogoutSuccessHandler zoneAwareWhitelistLogoutHandler, + Saml2RelyingPartyInitiatedLogoutSuccessHandler saml2RelyingPartyInitiatedLogoutSuccessHandler, + ExternalOAuthLogoutSuccessHandler externalOAuthLogoutHandler, + RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { + + return new UaaDelegatingLogoutSuccessHandler(zoneAwareWhitelistLogoutHandler, + saml2RelyingPartyInitiatedLogoutSuccessHandler, + externalOAuthLogoutHandler, + relyingPartyRegistrationResolver); + } + + /** + * Handles a Logout click from the user, removes the Authentication object, + * and determines if an OAuth2 or SAML2 Logout should be performed. + * If Saml, it forwards a Saml2LogoutRequest to IDP/asserting party if configured. + */ + @Autowired + @Bean + LogoutFilter logoutFilter(UaaDelegatingLogoutSuccessHandler delegatingLogoutSuccessHandler, + UaaAuthenticationFailureHandler authenticationFailureHandler, + CookieBasedCsrfTokenRepository loginCookieCsrfRepository) { + + SecurityContextLogoutHandler securityContextLogoutHandlerWithHandler = new SecurityContextLogoutHandler(); + CsrfLogoutHandler csrfLogoutHandler = new CsrfLogoutHandler(loginCookieCsrfRepository); + CookieClearingLogoutHandler cookieClearingLogoutHandlerWithHandler = new CookieClearingLogoutHandler("JSESSIONID"); + + LogoutFilter logoutFilter = new LogoutFilter(delegatingLogoutSuccessHandler, + authenticationFailureHandler, securityContextLogoutHandlerWithHandler, csrfLogoutHandler, + cookieClearingLogoutHandlerWithHandler); + logoutFilter.setLogoutRequestMatcher(new AntPathRequestMatcher("/logout.do")); + + return logoutFilter; + } + + /** + * Handles a return SAML2LogoutResponse from IDP/asserting party in response to a Saml2LogoutRequest from UAA. + */ + @Autowired + @Bean + Saml2LogoutResponseFilter saml2LogoutResponseFilter(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver, + UaaDelegatingLogoutSuccessHandler successHandler) { + + // This validator ignores missing signatures in the SAML2 Logout Response + Saml2LogoutResponseValidator openSamlLogoutResponseValidator = new SamlLogoutResponseValidator(); + + Saml2LogoutResponseFilter saml2LogoutResponseFilter = new Saml2LogoutResponseFilter(relyingPartyRegistrationResolver, openSamlLogoutResponseValidator, successHandler); + saml2LogoutResponseFilter.setLogoutRequestMatcher(new AntPathRequestMatcher("/saml/SingleLogout/alias/{registrationId}")); + + return saml2LogoutResponseFilter; + } + + /** + * Handles an incoming Saml2LogoutRequest from an Asserting Party Initiated Logout + */ + @Autowired + @Bean + Saml2LogoutRequestFilter saml2LogoutRequestFilter(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository, + UaaAuthenticationFailureHandler authenticationFailureHandler, + CookieBasedCsrfTokenRepository loginCookieCsrfRepository) { + RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository); + + // This validator ignores missing signatures in the SAML2 Logout Response + Saml2LogoutRequestValidator logoutRequestValidator = new SamlLogoutRequestValidator(); + Saml2LogoutResponseResolver logoutResponseResolver = new OpenSaml4LogoutResponseResolver(relyingPartyRegistrationResolver); + + SecurityContextLogoutHandler securityContextLogoutHandlerWithHandler = new SecurityContextLogoutHandler(); + CsrfLogoutHandler csrfLogoutHandler = new CsrfLogoutHandler(loginCookieCsrfRepository); + CookieClearingLogoutHandler cookieClearingLogoutHandlerWithHandler = new CookieClearingLogoutHandler("JSESSIONID"); + + Saml2LogoutRequestFilter saml2LogoutRequestFilter = new Saml2LogoutRequestFilter(relyingPartyRegistrationResolver, + logoutRequestValidator, logoutResponseResolver, + authenticationFailureHandler, securityContextLogoutHandlerWithHandler, csrfLogoutHandler, + cookieClearingLogoutHandlerWithHandler); + saml2LogoutRequestFilter.setLogoutRequestMatcher(new AntPathRequestMatcher("/saml/SingleLogout/alias/{registrationId}")); + return saml2LogoutRequestFilter; + } + + /** + * Handles Authentication for the Saml2 Bearer Grant. + */ + @Autowired + @Bean + Saml2BearerGrantAuthenticationConverter samlBearerGrantAuthenticationProvider(IdentityZoneManager identityZoneManager, + final JdbcIdentityProviderProvisioning identityProviderProvisioning, + SamlUaaAuthenticationUserManager samlUaaAuthenticationUserManager, + ApplicationEventPublisher applicationEventPublisher, + RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) { + + RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository); + return new Saml2BearerGrantAuthenticationConverter(relyingPartyRegistrationResolver, identityZoneManager, + identityProviderProvisioning, samlUaaAuthenticationUserManager, applicationEventPublisher); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlBindingNotSupportedException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlBindingNotSupportedException.java deleted file mode 100644 index 91e03c24437..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlBindingNotSupportedException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.opensaml.saml2.metadata.provider.MetadataProviderException; - -public class SamlBindingNotSupportedException extends MetadataProviderException { - public SamlBindingNotSupportedException() { - } - - public SamlBindingNotSupportedException(String message) { - super(message); - } - - public SamlBindingNotSupportedException(Exception wrappedException) { - super(wrappedException); - } - - public SamlBindingNotSupportedException(String message, Exception wrappedException) { - super(message, wrappedException); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfigProps.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfigProps.java new file mode 100644 index 00000000000..5ac2c995a15 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfigProps.java @@ -0,0 +1,98 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.saml.SamlKey; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.lang.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Configuration properties for SAML + * Loaded from the 'login.saml' section of the UAA configuration YAML file + */ +@Slf4j +@Data +@ConfigurationProperties(prefix = "login.saml") +public class SamlConfigProps { + + /** + * Map of provider IDs to provider configuration + */ + private Map> providers; + + /** + * Entity ID Alias to login at /saml/SSO/alias/{login.saml.entityIDAlias}; + * both SAML SP metadata and SAML Authn Request will include this as part of various SAML URLs + * (such as the AssertionConsumerService URL); + * if not set, UAA will fall back to login.entityID + */ + private String entityIDAlias; + + /** + * Default nameID if IDP nameID is not set. + * Defaults to urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + * Used in SAML Authn Request: + * + */ + private String nameID = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; + + /** + * Default assertionConsumerIndex if IDP value is not set + * Defaults to 0 + */ + private int assertionConsumerIndex = 0; + + /** + * The activeKeyId in the keys map + */ + private String activeKeyId; + + /** + * Map of key IDs to SamlKey objects + */ + private Map keys = new HashMap<>(); + + /** + * Local/SP metadata - want incoming assertions signed + * Defaults to true + */ + private Boolean wantAssertionSigned = true; + + /** + * When login.saml.signMetaData is true or not set, the SAML SP metadata has a Signature section; + * when it's false, there is no Signature. This applies to both default and non-default zones. + * Defaults to true + */ + private Boolean signMetaData = true; + + /** + * Local/SP metadata - requests signed + * Defaults to true + */ + private Boolean signRequest = true; + + /** + * Algorithm for SAML signatures. + * Accepts: SHA1, SHA256, SHA512 + * Defaults to SHA256. + */ + private String signatureAlgorithm = "SHA256"; + + /** + * If true, do not validate the InResponseToField part of an incoming IDP assertion + * Defaults to false + */ + private Boolean disableInResponseToCheck = false; + + /** + * Get the active key + * @return the active SamlKey, if available or null + */ + @Nullable + public SamlKey getActiveSamlKey() { + return keys != null ? keys.get(activeKeyId) : null; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfiguration.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfiguration.java new file mode 100644 index 00000000000..cefb6f9a338 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfiguration.java @@ -0,0 +1,163 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.TrustStrategy; +import org.cloudfoundry.identity.uaa.cache.StaleUrlCache; +import org.cloudfoundry.identity.uaa.cache.UrlContentCache; +import org.cloudfoundry.identity.uaa.util.TimeService; +import org.cloudfoundry.identity.uaa.util.TimeServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import javax.net.ssl.SSLContext; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; + +@Slf4j +@EnableConfigurationProperties({SamlConfigProps.class}) +@Configuration +@Data +public class SamlConfiguration { + + @Value("${login.entityID:unit-test-sp}") + private String samlEntityID = "unit-test-sp"; + @Value("${login.idpMetadataURL:null}") + private String metaDataUrl; + @Value("${login.idpMetadata:null}") + private String metaData; + @Value("${login.idpEntityAlias:null}") + private String legacyIdpIdentityAlias; + @SuppressWarnings("java:S6857") // Properly formatted default + @Value("${login.saml.nameID:'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'}") + private String legacyNameId = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; + @Value("${login.saml.assertionConsumerIndex:0}") + private int legacyAssertionConsumerIndex = 0; + @Value("${login.saml.metadataTrustCheck:true}") + private boolean legacyMetadataTrustCheck = true; + @Value("${login.showSamlLoginLink:true}") + private boolean legacyShowSamlLink = true; + + /** + * Sets the timeout in milliseconds retrieving an HTTP connection, used when fetching URL metadata + * Defaults to 10,000ms (10 seconds) + */ + @Value("${login.saml.socket.connectionManagerTimeout:10000}") + private int socketConnectionTimeout = 10_000; + + /** + * Sets the timeout in milliseconds reading data from an HTTP connection, used when fetching URL metadata + * Defaults to 10,000ms (10 seconds) + */ + @Value("${login.saml.socket.soTimeout:10000}") + private int socketReadTimeout = 10_000; + + @Bean + public String samlEntityID() { + return samlEntityID; + } + + @Autowired + @Bean + public BootstrapSamlIdentityProviderData bootstrapMetaDataProviders(SamlConfigProps samlConfigProps, + final @Qualifier("metaDataProviders") SamlIdentityProviderConfigurator metaDataProviders) { + BootstrapSamlIdentityProviderData idpData = new BootstrapSamlIdentityProviderData(metaDataProviders); + idpData.setIdentityProviders(samlConfigProps.getProviders()); + if (isNotNull(metaData)) { + idpData.setLegacyIdpMetaData(metaData); + } else if (isNotNull(metaDataUrl)) { + idpData.setLegacyIdpMetaData(metaDataUrl); + } + idpData.setLegacyIdpIdentityAlias(legacyIdpIdentityAlias); + idpData.setLegacyNameId(legacyNameId); + idpData.setLegacyAssertionConsumerIndex(legacyAssertionConsumerIndex); + idpData.setLegacyMetadataTrustCheck(legacyMetadataTrustCheck); + idpData.setLegacyShowSamlLink(legacyShowSamlLink); + return idpData; + } + + private boolean isNotNull(String value) { + if (value == null) { + return false; + } + return !value.isEmpty() && !value.equals("null"); + } + + @Autowired + @Bean + public SignatureAlgorithm getSignatureAlgorithm(SamlConfigProps samlConfigProps) { + try { + return SignatureAlgorithm.valueOf(samlConfigProps.getSignatureAlgorithm()); + } catch (IllegalArgumentException e) { + // default to INVALID (SHA256), if the signature algorithm is not valid + SignatureAlgorithm defaultSignatureAlgorithm = SignatureAlgorithm.INVALID; + log.error("Invalid signature algorithm: '{}', defaulting to {}", samlConfigProps.getSignatureAlgorithm(), defaultSignatureAlgorithm, e); + return defaultSignatureAlgorithm; + } + } + + @Autowired + @Bean + public boolean signSamlMetaData(SamlConfigProps samlConfigProps) { + return samlConfigProps.getSignMetaData(); + } + + @Bean + public TimeService timeService() { + return new TimeServiceImpl(); + } + + @Autowired + @Bean + public UrlContentCache urlContentCache(TimeService timeService) { + return new StaleUrlCache(timeService); + } + + @Bean + public RestTemplate trustingRestTemplate() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + // skip ssl validation + TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true; + SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); + SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build(); + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); + requestFactory.setHttpClient(httpClient); + + RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); + return restTemplateBuilder + .setConnectTimeout(Duration.ofMillis(socketConnectionTimeout)) + .setReadTimeout(Duration.ofMillis(socketReadTimeout)) + .requestFactory(() -> requestFactory) + .build(); + } + + @Bean + public RestTemplate nonTrustingRestTemplate() { + RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); + return restTemplateBuilder + .setConnectTimeout(Duration.ofMillis(socketConnectionTimeout)) + .setReadTimeout(Duration.ofMillis(socketReadTimeout)) + .build(); + } + + @Autowired + @Bean + public FixedHttpMetaDataProvider fixedHttpMetaDataProvider(@Qualifier("trustingRestTemplate") RestTemplate trustingRestTemplate, + @Qualifier("nonTrustingRestTemplate") RestTemplate nonTrustingRestTemplate, + UrlContentCache urlContentCache) { + return new FixedHttpMetaDataProvider(trustingRestTemplate, nonTrustingRestTemplate, urlContentCache); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfigurationBean.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfigurationBean.java deleted file mode 100644 index 56bfd7679b2..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfigurationBean.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.opensaml.xml.Configuration; -import org.opensaml.xml.security.BasicSecurityConfiguration; -import org.opensaml.xml.signature.SignatureConstants; -import org.springframework.beans.factory.InitializingBean; - - -public class SamlConfigurationBean implements InitializingBean { - private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.SHA1; - - public void setSignatureAlgorithm(SignatureAlgorithm s) { - signatureAlgorithm = s; - } - - public SignatureAlgorithm getSignatureAlgorithm() { - return signatureAlgorithm; - } - - @Override - public void afterPropertiesSet() { - BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration(); - switch (signatureAlgorithm) { - case SHA1: - config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); - config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA1); - break; - case SHA256: - config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256); - break; - case SHA512: - config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512); - config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA512); - break; - } - } - - public enum SignatureAlgorithm { - SHA1, - SHA256, - SHA512 - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java index 556113ab95b..2eb204b6ef0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java @@ -1,20 +1,19 @@ package org.cloudfoundry.identity.uaa.provider.saml; +import org.apache.commons.io.IOUtils; import org.apache.http.client.utils.URIBuilder; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdpAlreadyExistsException; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.opensaml.xml.parse.BasicParserPool; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.security.saml.metadata.ExtendedMetadata; -import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import java.net.URI; import java.net.URISyntaxException; @@ -26,28 +25,28 @@ @Component("metaDataProviders") public class SamlIdentityProviderConfigurator { - private final BasicParserPool parserPool; private final IdentityProviderProvisioning providerProvisioning; + private final IdentityZoneManager identityZoneManager; private final FixedHttpMetaDataProvider fixedHttpMetaDataProvider; public SamlIdentityProviderConfigurator( - final BasicParserPool parserPool, final @Qualifier("identityProviderProvisioning") IdentityProviderProvisioning providerProvisioning, - final FixedHttpMetaDataProvider fixedHttpMetaDataProvider) { - this.parserPool = parserPool; + final @Qualifier("identityZoneManager") IdentityZoneManager identityZoneManager, + final @Qualifier("fixedHttpMetaDataProvider") FixedHttpMetaDataProvider fixedHttpMetaDataProvider) { this.providerProvisioning = providerProvisioning; + this.identityZoneManager = identityZoneManager; this.fixedHttpMetaDataProvider = fixedHttpMetaDataProvider; } public List getIdentityProviderDefinitions() { - return getIdentityProviderDefinitionsForZone(IdentityZoneHolder.get()); + return getIdentityProviderDefinitionsForZone(identityZoneManager.getCurrentIdentityZone()); } public List getIdentityProviderDefinitionsForZone(IdentityZone zone) { List result = new LinkedList<>(); - for (IdentityProvider provider : providerProvisioning.retrieveActive(zone.getId())) { + for (IdentityProvider provider : providerProvisioning.retrieveActive(zone.getId())) { if (OriginKeys.SAML.equals(provider.getType())) { - result.add((SamlIdentityProviderDefinition) provider.getConfig()); + result.add(provider.getConfig()); } } return result; @@ -71,11 +70,10 @@ public List getIdentityProviderDefinitions(List< * adds or replaces a SAML identity proviider * * @param providerDefinition - the provider to be added - * @param creation - check new created config - * @throws MetadataProviderException if the system fails to fetch meta data for this provider + * @param creation - check new created config */ - public synchronized String validateSamlIdentityProviderDefinition(SamlIdentityProviderDefinition providerDefinition, boolean creation) throws MetadataProviderException { - ExtendedMetadataDelegate added, deleted = null; + public synchronized String validateSamlIdentityProviderDefinition(SamlIdentityProviderDefinition providerDefinition, boolean creation) { + RelyingPartyRegistration added; if (providerDefinition == null) { throw new NullPointerException(); } @@ -87,17 +85,20 @@ public synchronized String validateSamlIdentityProviderDefinition(SamlIdentityPr } SamlIdentityProviderDefinition clone = providerDefinition.clone(); added = getExtendedMetadataDelegate(clone); - String entityIDToBeAdded = ((ConfigMetadataProvider) added.getDelegate()).getEntityID(); - if (!StringUtils.hasText(entityIDToBeAdded)) { - throw new MetadataProviderException("Emtpy entityID for SAML provider with zoneId:" + providerDefinition.getZoneId() + " and origin:" + providerDefinition.getIdpEntityAlias()); + String entityIDToBeAdded = added.getAssertingPartyDetails().getEntityId(); + if (!hasText(entityIDToBeAdded)) { + throw new IllegalStateException("Emtpy entityID for SAML provider with zoneId:" + providerDefinition.getZoneId() + " and origin:" + providerDefinition.getIdpEntityAlias()); } boolean entityIDexists = creation && entityIdExists(entityIDToBeAdded, providerDefinition.getZoneId()); if (!entityIDexists) { for (SamlIdentityProviderDefinition existing : getIdentityProviderDefinitions()) { - ConfigMetadataProvider existingProvider = (ConfigMetadataProvider) getExtendedMetadataDelegate(existing).getDelegate(); - if (entityIDToBeAdded.equals(existingProvider.getEntityID()) && !(existing.getUniqueAlias().equals(clone.getUniqueAlias()))) { + if (existing.getType() != SamlIdentityProviderDefinition.MetadataLocation.DATA) { + continue; + } + RelyingPartyRegistration existingProvider = getExtendedMetadataDelegate(existing); + if (entityIDToBeAdded.equals(existingProvider.getAssertingPartyDetails().getEntityId()) && !(existing.getUniqueAlias().equals(clone.getUniqueAlias()))) { entityIDexists = true; break; } @@ -105,15 +106,11 @@ public synchronized String validateSamlIdentityProviderDefinition(SamlIdentityPr } if (entityIDexists) { - throw new MetadataProviderException("Duplicate entity ID:" + entityIDToBeAdded); + throw new IdpAlreadyExistsException("Duplicate entity ID:" + entityIDToBeAdded); } return entityIDToBeAdded; } - public ExtendedMetadataDelegate getExtendedMetadataDelegateFromCache(SamlIdentityProviderDefinition def) throws MetadataProviderException { - return getExtendedMetadataDelegate(def); - } - private boolean entityIdExists(String entityId, String zoneId) { try { return providerProvisioning.retrieveByExternId(entityId, OriginKeys.SAML, zoneId) != null; @@ -122,63 +119,41 @@ private boolean entityIdExists(String entityId, String zoneId) { } } - public ExtendedMetadataDelegate getExtendedMetadataDelegate(SamlIdentityProviderDefinition def) throws MetadataProviderException { - ExtendedMetadataDelegate metadata; - switch (def.getType()) { - case DATA: { - metadata = configureXMLMetadata(def); - break; - } - case URL: { - metadata = configureURLMetadata(def); - break; - } - default: { - throw new MetadataProviderException("Invalid metadata type for alias[" + def.getIdpEntityAlias() + "]:" + def.getMetaDataLocation()); - } - } - return metadata; + public RelyingPartyRegistration getExtendedMetadataDelegate(SamlIdentityProviderDefinition def) { + return switch (def.getType()) { + case DATA -> configureXMLMetadata(def); + case URL -> configureURLMetadata(def); + default -> + throw new IllegalStateException("Invalid metadata type for alias[" + def.getIdpEntityAlias() + "]:" + def.getMetaDataLocation()); + }; } - protected ExtendedMetadataDelegate configureXMLMetadata(SamlIdentityProviderDefinition def) { - ConfigMetadataProvider configMetadataProvider = new ConfigMetadataProvider(def.getZoneId(), def.getIdpEntityAlias(), def.getMetaDataLocation()); - configMetadataProvider.setParserPool(parserPool); - ExtendedMetadata extendedMetadata = new ExtendedMetadata(); - extendedMetadata.setLocal(false); - extendedMetadata.setAlias(def.getIdpEntityAlias()); - ExtendedMetadataDelegate delegate = new ExtendedMetadataDelegate(configMetadataProvider, extendedMetadata); - delegate.setMetadataTrustCheck(def.isMetadataTrustCheck()); - - return delegate; + protected RelyingPartyRegistration configureXMLMetadata(SamlIdentityProviderDefinition def) { + return RelyingPartyRegistrations.fromMetadata(IOUtils.toInputStream(def.getMetaDataLocation(), StandardCharsets.UTF_8)).build(); } - protected String adjustURIForPort(String uri) throws URISyntaxException { URI metadataURI = new URI(uri); if (metadataURI.getPort() < 0) { - switch (metadataURI.getScheme()) { - case "https": - return new URIBuilder(uri).setPort(443).build().toString(); - case "http": - return new URIBuilder(uri).setPort(80).build().toString(); - default: - return uri; - } + return switch (metadataURI.getScheme()) { + case "https" -> new URIBuilder(uri).setPort(443).build().toString(); + case "http" -> new URIBuilder(uri).setPort(80).build().toString(); + default -> uri; + }; } return uri; } - protected ExtendedMetadataDelegate configureURLMetadata(SamlIdentityProviderDefinition def) throws MetadataProviderException { + protected RelyingPartyRegistration configureURLMetadata(SamlIdentityProviderDefinition def) { try { def = def.clone(); - String adjustedMetatadataURIForPort = adjustURIForPort(def.getMetaDataLocation()); - - byte[] metadata = fixedHttpMetaDataProvider.fetchMetadata(adjustedMetatadataURIForPort, def.isSkipSslValidation()); + String adjustedMetadataURIForPort = adjustURIForPort(def.getMetaDataLocation()); + byte[] metadata = fixedHttpMetaDataProvider.fetchMetadata(adjustedMetadataURIForPort, def.isSkipSslValidation()); def.setMetaDataLocation(new String(metadata, StandardCharsets.UTF_8)); return configureXMLMetadata(def); } catch (URISyntaxException e) { - throw new MetadataProviderException("Invalid socket factory(invalid URI):" + def.getMetaDataLocation(), e); + throw new IllegalStateException("Invalid socket factory(invalid URI):" + def.getMetaDataLocation(), e); } } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManager.java new file mode 100644 index 00000000000..20d35188600 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManager.java @@ -0,0 +1,13 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.util.KeyWithCert; + +import java.util.List; + +public interface SamlKeyManager { + KeyWithCert getCredential(String keyName); + KeyWithCert getDefaultCredential(); + String getDefaultCredentialName(); + List getAvailableCredentials(); + List getAvailableCredentialIds(); +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManagerFactory.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManagerFactory.java index a300df4954b..3c0b488cda9 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManagerFactory.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManagerFactory.java @@ -13,74 +13,161 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml; +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; import org.cloudfoundry.identity.uaa.saml.SamlKey; import org.cloudfoundry.identity.uaa.util.KeyWithCert; import org.cloudfoundry.identity.uaa.zone.SamlConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.saml.key.JKSKeyManager; -import org.springframework.security.saml.key.KeyManager; - -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; -import static java.util.Optional.ofNullable; +import java.security.Security; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +@Slf4j public final class SamlKeyManagerFactory { + private final SamlConfigProps samlConfigProps; - protected final static Logger logger = LoggerFactory.getLogger(SamlKeyManagerFactory.class); + public SamlKeyManagerFactory(SamlConfigProps samlConfigProps) { + this.samlConfigProps = samlConfigProps; + } - public SamlKeyManagerFactory() { + public SamlKeyManager getKeyManager(SamlConfig config) { + boolean hasKeys = Optional.ofNullable(config) + .map(SamlConfig::getKeys) + .map(k -> !k.isEmpty()) + .orElse(false); + + if (hasKeys) { + return new SamlConfigSamlKeyManagerImpl(config); + } + // fall back to default keys in samlConfigProps + return new SamlConfigPropsSamlKeyManagerImpl(samlConfigProps); } - public KeyManager getKeyManager(SamlConfig config) { - return getKeyManager(config.getKeys(), config.getActiveKeyId()); + //***************************** + // Key Manager Implementations + //***************************** + + abstract static class BaseSamlKeyManagerImpl implements SamlKeyManager { + + static { + Security.addProvider(new BouncyCastleFipsProvider()); + } + + protected List convertList(List samlKeys) { + List result = new ArrayList<>(); + for (SamlKey k : samlKeys) { + try { + result.add(convertKey(k)); + } catch (CertificateRuntimeException e) { + // already logged in convertKey + } + } + + return result; + } + + protected static KeyWithCert convertKey(SamlKey k) { + try { + return KeyWithCert.fromSamlKey(k); + } catch (CertificateException e) { + log.error("Error converting key with cert", e); + throw new CertificateRuntimeException(e); + } + } } - private KeyManager getKeyManager(Map keys, String activeKeyId) { - SamlKey activeKey = keys.get(activeKeyId); + static class SamlConfigSamlKeyManagerImpl extends BaseSamlKeyManagerImpl { + + private final SamlConfig samlConfig; + + SamlConfigSamlKeyManagerImpl(SamlConfig samlConfig) { + this.samlConfig = samlConfig; + } + + @Override + public KeyWithCert getCredential(String keyName) { + return convertKey(samlConfig.getKeys().get(keyName)); + } - if (activeKey == null) { - return null; + @Override + public KeyWithCert getDefaultCredential() { + return convertKey(samlConfig.getActiveKey()); } - try { - KeyStore keystore = KeyStore.getInstance("JKS"); - keystore.load(null); - Map aliasPasswordMap = new HashMap<>(); - for (Map.Entry entry : keys.entrySet()) { - Supplier passProvider = () -> ofNullable(entry.getValue().getPassphrase()).orElse(""); - KeyWithCert keyWithCert = entry.getValue().getKey() == null ? - new KeyWithCert(entry.getValue().getCertificate()) : - new KeyWithCert(entry.getValue().getKey(), passProvider.get(), entry.getValue().getCertificate()); + @Override + public String getDefaultCredentialName() { + return samlConfig.getActiveKeyId(); + } - X509Certificate certificate = keyWithCert.getCertificate(); + @Override + public List getAvailableCredentials() { + return convertList(samlConfig.getKeyList()); + } - String alias = entry.getKey(); - keystore.setCertificateEntry(alias, certificate); + @Override + public List getAvailableCredentialIds() { + List keyList = new ArrayList<>(); + String activeKeyId = getDefaultCredentialName(); + Optional.ofNullable(activeKeyId).ifPresent(keyList::add); + keyList.addAll(samlConfig.getKeys().keySet().stream() + .filter(k -> !k.equals(activeKeyId)) + .toList()); - PrivateKey privateKey = keyWithCert.getPrivateKey(); - if (privateKey != null) { - keystore.setKeyEntry(alias, privateKey, passProvider.get().toCharArray(), new Certificate[]{certificate}); - aliasPasswordMap.put(alias, passProvider.get()); - } - } + return Collections.unmodifiableList(keyList); + } + } + + static class SamlConfigPropsSamlKeyManagerImpl extends BaseSamlKeyManagerImpl { + + private final SamlConfigProps samlConfigProps; - JKSKeyManager keyManager = new JKSKeyManager(keystore, aliasPasswordMap, activeKeyId); + SamlConfigPropsSamlKeyManagerImpl(SamlConfigProps samlConfigProps) { + this.samlConfigProps = samlConfigProps; + } + + @Override + public KeyWithCert getCredential(String keyName) { + return convertKey(samlConfigProps.getKeys().get(keyName)); + } + + @Override + public KeyWithCert getDefaultCredential() { + return convertKey(samlConfigProps.getActiveSamlKey()); + } + + @Override + public String getDefaultCredentialName() { + return samlConfigProps.getActiveKeyId(); + } + + @Override + public List getAvailableCredentials() { + List keyList = new ArrayList<>(); + String activeKeyId = getDefaultCredentialName(); + Optional.ofNullable(samlConfigProps.getActiveSamlKey()).ifPresent(keyList::add); + keyList.addAll(samlConfigProps.getKeys().entrySet().stream() + .filter(e -> !e.getKey().equals(activeKeyId)) + .map(Map.Entry::getValue) + .toList()); + + return convertList(keyList); + } - logger.info("Loaded service provider certificate " + keyManager.getDefaultCredentialName()); + @Override + public List getAvailableCredentialIds() { + List keyList = new ArrayList<>(); + String activeKeyId = samlConfigProps.getActiveKeyId(); + Optional.ofNullable(activeKeyId).ifPresent(keyList::add); + keyList.addAll(samlConfigProps.getKeys().keySet().stream() + .filter(k -> !k.equals(activeKeyId)) + .toList()); - return keyManager; - } catch (Throwable t) { - logger.error("Could not load certificate", t); - throw new IllegalArgumentException( - "Could not load service provider certificate. Check serviceProviderKey and certificate parameters", - t); + return Collections.unmodifiableList(keyList); } } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationFailureHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationFailureHandler.java new file mode 100644 index 00000000000..9246a3d5da4 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationFailureHandler.java @@ -0,0 +1,80 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.extern.slf4j.Slf4j; +import org.apache.http.client.utils.URIBuilder; +import org.cloudfoundry.identity.uaa.authentication.MalformedSamlResponseLogger; +import org.cloudfoundry.identity.uaa.util.SessionUtils; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.savedrequest.DefaultSavedRequest; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.net.URI; + +/** + * This class is used to provide OAuth error redirects when SAML login fails + * with LoginSAMLException. Currently, the only scenario for this is when a + * shadow account does not exist for the user and the IdP configuration does not + * allow automatic creation of the shadow account. + */ +@Slf4j +public class SamlLoginAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + MalformedSamlResponseLogger malformedLogger = new MalformedSamlResponseLogger(); + + @Override + public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response, + final AuthenticationException exception) throws IOException, ServletException { + + String redirectTo = null; + if (exception instanceof SamlLoginException) { + redirectTo = handleSamlLoginException(request, response, exception); + } else if (exception instanceof Saml2AuthenticationException) { + malformedLogger.logMalformedResponse(request); + redirectTo = handleSamlLoginException(request, response, exception); + } + + if (redirectTo == null) { + Throwable cause = exception.getCause(); + if (cause != null) { + AuthenticationException e = new AuthenticationServiceException(cause.getMessage(), cause.getCause()); + logger.debug(cause); + super.onAuthenticationFailure(request, response, e); + } else { + logger.debug(exception); + super.onAuthenticationFailure(request, response, exception); + } + } + } + + private String handleSamlLoginException(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { + String redirectTo = null; + + HttpSession session = request.getSession(); + if (session != null) { + DefaultSavedRequest savedRequest = + (DefaultSavedRequest) SessionUtils.getSavedRequestSession(session); + if (savedRequest != null) { + String[] redirectURI = savedRequest.getParameterMap().get("redirect_uri"); + + if (redirectURI != null && redirectURI.length > 0) { + URI uri = URI.create(redirectURI[0]); + URIBuilder uriBuilder = new URIBuilder(uri); + uriBuilder.addParameter("error", "access_denied"); + uriBuilder.addParameter("error_description", exception.getMessage()); + redirectTo = uriBuilder.toString(); + + log.debug("Error redirect to: {}", redirectTo); + getRedirectStrategy().sendRedirect(request, response, redirectTo); + } + } + } + + return redirectTo; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginException.java similarity index 70% rename from server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginException.java index 6c78c2f5652..0ab482d0bd1 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginException.java @@ -2,10 +2,13 @@ import org.springframework.security.authentication.BadCredentialsException; -public class LoginSAMLException extends BadCredentialsException { +import java.io.Serial; + +public class SamlLoginException extends BadCredentialsException { /** * Generated serialization id. */ + @Serial private static final long serialVersionUID = 9115629621572693116L; /** @@ -15,11 +18,11 @@ public class LoginSAMLException extends BadCredentialsException { * @param msg * the detail message */ - public LoginSAMLException(final String msg) { + public SamlLoginException(final String msg) { super(msg); } - public LoginSAMLException(final String msg, final Throwable e) { + public SamlLoginException(final String msg, final Throwable e) { super(msg, e); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpoint.java new file mode 100644 index 00000000000..9c963b1b3c4 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpoint.java @@ -0,0 +1,83 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.ZoneAware; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver; +import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResolver; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +@RestController +public class SamlMetadataEndpoint implements ZoneAware { + public static final String DEFAULT_REGISTRATION_ID = "example"; + private static final String APPLICATION_XML_CHARSET_UTF_8 = "application/xml; charset=UTF-8"; + + private final Saml2MetadataResolver saml2MetadataResolver; + + private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver; + + public SamlMetadataEndpoint(RelyingPartyRegistrationResolver registrationResolver, + IdentityZoneManager identityZoneManager, SignatureAlgorithm signatureAlgorithms, + @Qualifier("signSamlMetaData") boolean signMetaData) { + Assert.notNull(registrationResolver, "registrationResolver cannot be null"); + relyingPartyRegistrationResolver = registrationResolver; + OpenSamlMetadataResolver metadataResolver = new OpenSamlMetadataResolver(); + saml2MetadataResolver = metadataResolver; + metadataResolver.setEntityDescriptorCustomizer( + new SamlMetadataEntityDescriptorCustomizer(identityZoneManager, signatureAlgorithms, signMetaData)); + } + + @GetMapping(value = "/saml/metadata", produces = APPLICATION_XML_CHARSET_UTF_8) + public ResponseEntity legacyMetadataEndpoint(HttpServletRequest request) { + return metadataEndpoint(request, DEFAULT_REGISTRATION_ID); + } + + @GetMapping(value = "/saml/metadata/{registrationId}", produces = APPLICATION_XML_CHARSET_UTF_8) + public ResponseEntity metadataEndpoint(HttpServletRequest request, @PathVariable String registrationId) { + RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationResolver.resolve(request, registrationId); + if (relyingPartyRegistration == null) { + return ResponseEntity.status(HttpServletResponse.SC_UNAUTHORIZED).build(); + } + String metadata = saml2MetadataResolver.resolve(relyingPartyRegistration); + + String contentDisposition = ContentDispositionFilename.getContentDisposition(retrieveZone()); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) + .body(metadata); + } +} + +record ContentDispositionFilename(String fileName) { + private static final String CONTENT_DISPOSITION_FORMAT = "attachment; filename=\"%s\"; filename*=UTF-8''%s"; + private static final String DEFAULT_FILE_NAME = "saml-sp.xml"; + + static ContentDispositionFilename retrieveZoneAwareContentDispositionFilename(IdentityZone zone) { + if (zone.isUaa()) { + return new ContentDispositionFilename(DEFAULT_FILE_NAME); + } + String filename = "saml-%s-sp.xml".formatted(zone.getSubdomain()); + return new ContentDispositionFilename(filename); + } + + static String getContentDisposition(IdentityZone zone) { + return retrieveZoneAwareContentDispositionFilename(zone).getContentDisposition(); + } + + String getContentDisposition() { + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8); + return CONTENT_DISPOSITION_FORMAT.formatted(fileName, encodedFileName); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEntityDescriptorCustomizer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEntityDescriptorCustomizer.java new file mode 100644 index 00000000000..162985c26af --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEntityDescriptorCustomizer.java @@ -0,0 +1,212 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Value; +import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.resolver.CriteriaSet; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.opensaml.core.xml.XMLObjectBuilder; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.saml2.metadata.NameIDFormat; +import org.opensaml.saml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; +import org.opensaml.security.SecurityException; +import org.opensaml.security.credential.BasicCredential; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.CredentialSupport; +import org.opensaml.security.credential.UsageType; +import org.opensaml.xmlsec.SignatureSigningParameters; +import org.opensaml.xmlsec.SignatureSigningParametersResolver; +import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; +import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; +import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; +import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; +import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.opensaml.xmlsec.signature.support.SignatureException; +import org.opensaml.xmlsec.signature.support.SignatureSupport; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.util.Assert; + +import javax.xml.namespace.QName; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_EMAIL; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_PERSISTENT; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_TRANSIENT; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_UNSPECIFIED; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_X509SUBJECT; + +/** + * This class is used to customize the EntityDescriptor used in the Metadata call, + * it is called as part of the {@link OpenSamlMetadataResolver} after basic creation is completed. + */ +@Slf4j +@Value +public class SamlMetadataEntityDescriptorCustomizer implements Consumer { + private static final Set NAME_ID_FORMATS = new HashSet<>(); + private static final String URI_BINDING = "urn:oasis:names:tc:SAML:2.0:bindings:URI"; + private static final UnaryOperator assertionConsumerServiceLocationMutationFunction = o -> o.replace("/saml/SSO/alias/", "/oauth/token/alias/"); + + static { + NAME_ID_FORMATS.add(NAMEID_FORMAT_EMAIL); + NAME_ID_FORMATS.add(NAMEID_FORMAT_TRANSIENT); + NAME_ID_FORMATS.add(NAMEID_FORMAT_PERSISTENT); + NAME_ID_FORMATS.add(NAMEID_FORMAT_UNSPECIFIED); + NAME_ID_FORMATS.add(NAMEID_FORMAT_X509SUBJECT); + } + + IdentityZoneManager identityZoneManager; + SignatureAlgorithm signatureAlgorithm; + boolean signMetaData; + + @Override + public void accept(OpenSamlMetadataResolver.EntityDescriptorParameters entityDescriptorParameters) { + SamlConfig samlConfig = identityZoneManager.getCurrentIdentityZone().getConfig().getSamlConfig(); + + EntityDescriptor entityDescriptor = entityDescriptorParameters.getEntityDescriptor(); + entityDescriptor.setID(entityDescriptor.getEntityID()); + updateSpSsoDescriptor(entityDescriptor, samlConfig); + + // Signature has to be last, as it will sign the whole entity descriptor + if (signMetaData && signatureAlgorithm != null) { + signMetadata(entityDescriptorParameters); + } + } + + private void updateSpSsoDescriptor(EntityDescriptor entityDescriptor, SamlConfig samlConfig) { + SPSSODescriptor spSsoDescriptor = entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS); + spSsoDescriptor.setWantAssertionsSigned(samlConfig.isWantAssertionSigned()); + spSsoDescriptor.setAuthnRequestsSigned(samlConfig.isRequestSigned()); + updateNameIdFormats(spSsoDescriptor); + updateAssertionConsumerServices(spSsoDescriptor); + } + + /** + * Update the assertion consumer services. + * The first existing assertion consumer service is used as the default, + * and the second is added for the oauth token endpoint. + * + * @param spSsoDescriptor the SP SSO descriptor to update + */ + private void updateAssertionConsumerServices(SPSSODescriptor spSsoDescriptor) { + List assertionConsumerServices = spSsoDescriptor.getAssertionConsumerServices(); + + AssertionConsumerService existingService = assertionConsumerServices.get(0); + existingService.setIndex(0); + existingService.setIsDefault(true); + String existingUrl = existingService.getLocation(); + + AssertionConsumerService additionalService = build(AssertionConsumerService.DEFAULT_ELEMENT_NAME); + additionalService.setBinding(URI_BINDING); + additionalService.setLocation(assertionConsumerServiceLocationMutationFunction.apply(existingUrl)); + additionalService.setIndex(1); + assertionConsumerServices.add(additionalService); + } + + /** + * Add a signature element to the entity descriptor. + * The signature contains the active key's certificate. + * + * @param entityDescriptorParameters the entity descriptor parameters + */ + private void signMetadata(OpenSamlMetadataResolver.EntityDescriptorParameters entityDescriptorParameters) { + + EntityDescriptor entityDescriptor = entityDescriptorParameters.getEntityDescriptor(); + RelyingPartyRegistration registration = entityDescriptorParameters.getRelyingPartyRegistration(); + SignatureSigningParameters parameters = resolveSigningParameters(registration); + try { + SignatureSupport.signObject(entityDescriptor, parameters); + } catch (SecurityException | SignatureException | MarshallingException e) { + log.error("Error signing entity descriptor", e); + } + } + + private void updateNameIdFormats(SPSSODescriptor spSsoDescriptor) { + // OpenSamlMetadataResolver adds the item from the relyingPartyRegistration, + // Create a set to be used to ignore adding duplicates + Set existingNameIDFormats = spSsoDescriptor.getNameIDFormats().stream().map(NameIDFormat::getURI).collect(Collectors.toSet()); + spSsoDescriptor.getNameIDFormats().addAll(NAME_ID_FORMATS.stream().filter(Predicate.not(existingNameIDFormats::contains)).map(this::buildNameIDFormat).collect(Collectors.toSet())); + } + + private NameIDFormat buildNameIDFormat(String value) { + NameIDFormat nameIdFormat = build(NameIDFormat.DEFAULT_ELEMENT_NAME); + nameIdFormat.setURI(value); + return nameIdFormat; + } + + private T build(QName elementName) { + XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); + if (builder == null) { + throw new Saml2Exception("Unable to resolve Builder for " + elementName); + } + //noinspection unchecked + return (T) builder.buildObject(elementName); + } + + private SignatureSigningParameters resolveSigningParameters(RelyingPartyRegistration relyingPartyRegistration) { + + List credentials = resolveSigningCredentials(relyingPartyRegistration); + SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); + BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); + signingConfiguration.setSigningCredentials(credentials); + signingConfiguration.setSignatureAlgorithms(List.of(signatureAlgorithm.getSignatureAlgorithmURI())); + signingConfiguration.setSignatureReferenceDigestMethods(List.of(signatureAlgorithm.getDigestAlgorithmURI())); + signingConfiguration.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager()); + + CriteriaSet criteria = new CriteriaSet(); + criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration)); + try { + SignatureSigningParameters parameters = resolver.resolveSingle(criteria); + Assert.notNull(parameters, "Failed to resolve any signing credential"); + return parameters; + } catch (Exception ex) { + throw new Saml2Exception(ex); + } + } + + private static List resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) { + List credentials = new ArrayList<>(); + for (Saml2X509Credential x509Credential : relyingPartyRegistration.getSigningX509Credentials()) { + java.security.cert.X509Certificate certificate = x509Credential.getCertificate(); + PrivateKey privateKey = x509Credential.getPrivateKey(); + BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); + credential.setEntityId(relyingPartyRegistration.getEntityId()); + credential.setUsageType(UsageType.SIGNING); + credentials.add(credential); + } + return credentials; + } + + private static NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() { + final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); + + namedManager.setUseDefaultManager(true); + final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager(); + + // Generator for X509Credentials + final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); + x509Factory.setEmitEntityCertificate(true); + x509Factory.setEmitEntityCertificateChain(true); + + defaultManager.registerFactory(x509Factory); + + return namedManager; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlNameIdFormats.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlNameIdFormats.java new file mode 100644 index 00000000000..0d5fff1e4d8 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlNameIdFormats.java @@ -0,0 +1,151 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +/** + * This class contains NameID format constants for SAML 1.1 and SAML 2.0. + * + * @see Saml 2.0 Doc + * Section 8.3 - Name Identifier Format Identifiers + */ +public final class SamlNameIdFormats { + + private static final String NAMEID_FORMAT_BASE = "urn:oasis:names:tc:SAML:%s:nameid-format:%s"; + + /*************************************************************************** + * SAML 1.1 NameID Formats + */ + private static final String NAMEID_VERSION_1_1 = "1.1"; + + /** + * URI: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + *

+ * Indicates that the content of the element is in the form of an email address, specifically "addr-spec" as + * defined in IETF RFC 2822 [RFC 2822] Section 3.4.1. An addr-spec has the form local-part@domain. Note + * that an addr-spec has no phrase (such as a common name) before it, has no comment (text surrounded + * in parentheses) after it, and is not surrounded by "<" and ">". + */ + public static final String NAMEID_FORMAT_EMAIL = NAMEID_FORMAT_BASE.formatted(NAMEID_VERSION_1_1, "emailAddress"); + + /** + * URI: urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + *

+ * The interpretation of the content of the element is left to individual implementations. + */ + public static final String NAMEID_FORMAT_UNSPECIFIED = NAMEID_FORMAT_BASE.formatted(NAMEID_VERSION_1_1, "unspecified"); + + /** + * URI: urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName + *

+ * Indicates that the content of the element is in the form specified for the contents of the + * element in the XML Signature Recommendation [XMLSig]. Implementors + *

+ * should note that the XML Signature specification specifies encoding rules for X.509 subject names that + * differ from the rules given in IETF RFC 2253 [RFC 2253]. + */ + public static final String NAMEID_FORMAT_X509SUBJECT = NAMEID_FORMAT_BASE.formatted(NAMEID_VERSION_1_1, "X509SubjectName"); + + /** + * URI: urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName + *

+ * Indicates that the content of the element is a Windows domain qualified name. A Windows domain + * qualified user name is a string of the form "DomainName\UserName". The domain name and "\" separator + * MAY be omitted. + */ + public static final String NAMEID_FORMAT_WINDOWS_DQN = NAMEID_FORMAT_BASE.formatted(NAMEID_VERSION_1_1, "WindowsDomainQualifiedName"); + + /*************************************************************************** + * SAML 2.0 NameID Formats + */ + private static final String NAMEID_VERSION_2_0 = "2.0"; + + /** + * URI: urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + *

+ * Indicates that the content of the element is a persistent opaque identifier for a principal that is specific to + * an identity provider and a service provider or affiliation of service providers. Persistent name identifiers + * generated by identity providers MUST be constructed using pseudo-random values that have no + * discernible correspondence with the subject's actual identifier (for example, username). The intent is to + * create a non-public, pair-wise pseudonym to prevent the discovery of the subject's identity or activities. + * Persistent name identifier values MUST NOT exceed a length of 256 characters. + *

+ * The element's NameQualifier attribute, if present, MUST contain the unique identifier of the identity + * provider that generated the identifier (see Section 8.3.6). It MAY be omitted if the value can be derived + * from the context of the message containing the element, such as the issuer of a protocol message or an + * assertion containing the identifier in its subject. Note that a different system entity might later issue its own + * protocol message or assertion containing the identifier; the NameQualifier attribute does not change in + * this case, but MUST continue to identify the entity that originally created the identifier (and MUST NOT be + * omitted in such a case). + *

+ * The element's SPNameQualifier attribute, if present, MUST contain the unique identifier of the service + * provider or affiliation of providers for whom the identifier was generated (see Section 8.3.6). It MAY be + * omitted if the element is contained in a message intended only for consumption directly by the service + * provider, and the value would be the unique identifier of that service provider. + * The element's SPProvidedID attribute MUST contain the alternative identifier of the principal most + * recently set by the service provider or affiliation, if any (see Section 3.6). If no such identifier has been + * established, then the attribute MUST be omitted. + *

+ * Persistent identifiers are intended as a privacy protection mechanism; as such they MUST NOT be shared + * in clear text with providers other than the providers that have established the shared identifier. + * Furthermore, they MUST NOT appear in log files or similar locations without appropriate controls and + * protections. Deployments without such requirements are free to use other kinds of identifiers in their + * SAML exchanges, but MUST NOT overload this format with persistent but non-opaque values + *

+ * Note also that while persistent identifiers are typically used to reflect an account linking relationship + * between a pair of providers, a service provider is not obligated to recognize or make use of the long term + * nature of the persistent identifier or establish such a link. Such a "one-sided" relationship is not discernibly + * different and does not affect the behavior of the identity provider or any processing rules specific to + * persistent identifiers in the protocols defined in this specification. + *

+ * Finally, note that the NameQualifier and SPNameQualifier attributes indicate directionality of + * creation, but not of use. If a persistent identifier is created by a particular identity provider, the + * NameQualifier attribute value is permanently established at that time. If a service provider that receives + * such an identifier takes on the role of an identity provider and issues its own assertion containing that + * identifier, the NameQualifier attribute value does not change (and would of course not be omitted). It + * might alternatively choose to create its own persistent identifier to represent the principal and link the two + * values. This is a deployment decision. + */ + public static final String NAMEID_FORMAT_PERSISTENT = NAMEID_FORMAT_BASE.formatted(NAMEID_VERSION_2_0, "persistent"); + + /** + * URI: urn:oasis:names:tc:SAML:2.0:nameid-format:transient + *

+ * Indicates that the content of the element is an identifier with transient semantics and SHOULD be treated + * as an opaque and temporary value by the relying party. Transient identifier values MUST be generated in + * accordance with the rules for SAML identifiers (see Section 1.3.4), and MUST NOT exceed a length of + * 256 characters. + *

+ * The NameQualifier and SPNameQualifier attributes MAY be used to signify that the identifier + * represents a transient and temporary pair-wise identifier. In such a case, they MAY be omitted in + * accordance with the rules specified in Section 8.3.7. + */ + public static final String NAMEID_FORMAT_TRANSIENT = NAMEID_FORMAT_BASE.formatted(NAMEID_VERSION_2_0, "transient"); + + /** + * URI: urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos + *

+ * Indicates that the content of the element is in the form of a Kerberos principal name using the format + * name[/instance]@REALM. The syntax, format and characters allowed for the name, instance, and + * realm are described in IETF RFC 1510 [RFC 1510]. + */ + public static final String NAMEID_FORMAT_KERBEROS = NAMEID_FORMAT_BASE.formatted(NAMEID_VERSION_2_0, "kerberos"); + + /** + * URI: urn:oasis:names:tc:SAML:2.0:nameid-format:entity + *

+ * Indicates that the content of the element is the identifier of an entity that provides SAML-based services + * (such as a SAML authority, requester, or responder) or is a participant in SAML profiles (such as a service + * provider supporting the browser SSO profile). Such an identifier can be used in the element to + * identify the issuer of a SAML request, response, or assertion, or within the element to make + * assertions about system entities that can issue SAML requests, responses, and assertions. It can also be + * used in other elements and attributes whose purpose is to identify a system entity in various protocol + * exchanges. + *

+ * The syntax of such an identifier is a URI of not more than 1024 characters in length. It is + * RECOMMENDED that a system entity use a URL containing its own domain name to identify itself. + * The NameQualifier, SPNameQualifier, and SPProvidedID attributes MUST be omitted. + */ + public static final String NAMEID_FORMAT_ENTITY = NAMEID_FORMAT_BASE.formatted(NAMEID_VERSION_2_0, "entity"); + + private SamlNameIdFormats() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java index b2f84de179f..389ce5b6b74 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java @@ -17,41 +17,31 @@ import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.joda.time.DateTime; -import org.opensaml.common.SAMLVersion; -import org.opensaml.saml2.core.Assertion; -import org.opensaml.saml2.core.Issuer; -import org.opensaml.saml2.core.Response; -import org.opensaml.saml2.core.Status; -import org.opensaml.saml2.core.StatusCode; -import org.opensaml.saml2.core.StatusMessage; -import org.opensaml.saml2.core.impl.IssuerBuilder; -import org.opensaml.saml2.core.impl.ResponseBuilder; -import org.opensaml.saml2.core.impl.StatusBuilder; -import org.opensaml.saml2.core.impl.StatusCodeBuilder; -import org.opensaml.saml2.core.impl.StatusMessageBuilder; import org.springframework.web.util.UriComponentsBuilder; public class SamlRedirectUtils { - public static String getIdpRedirectUrl(SamlIdentityProviderDefinition definition, String entityId, IdentityZone identityZone) { - UriComponentsBuilder builder = UriComponentsBuilder.fromPath("saml/discovery"); - builder.queryParam("returnIDParam", "idp"); - builder.queryParam("entityID", getZonifiedEntityId(entityId, identityZone)); - builder.queryParam("idp", definition.getIdpEntityAlias()); - builder.queryParam("isPassive", "true"); + private SamlRedirectUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static String getIdpRedirectUrl(SamlIdentityProviderDefinition definition) { + String entityIdAlias = definition.getIdpEntityAlias(); + UriComponentsBuilder builder = UriComponentsBuilder.fromPath("saml2/authenticate/%s".formatted(entityIdAlias)); return builder.build().toUriString(); } public static String getZonifiedEntityId(String entityID, IdentityZone identityZone) { - try{ + try { if (!identityZone.isUaa()) { String url = identityZone.getConfig().getSamlConfig().getEntityID(); if (url != null) { return url; } } - } catch (Exception ignored) {} + } catch (Exception ignored) { + // ignore + } if (UaaUrlUtils.isUrl(entityID)) { return UaaUrlUtils.addSubdomainToUrl(entityID, identityZone.getSubdomain()); @@ -59,28 +49,4 @@ public static String getZonifiedEntityId(String entityID, IdentityZone identityZ return UaaUrlUtils.getSubdomain(identityZone.getSubdomain()) + entityID; } } - - public static Response wrapAssertionIntoResponse(Assertion assertion, String assertionIssuer) { - Response response = new ResponseBuilder().buildObject(); - Issuer issuer = new IssuerBuilder().buildObject(); - issuer.setValue(assertionIssuer); - response.setIssuer(issuer); - response.setID("id-" + System.currentTimeMillis()); - Status stat = new StatusBuilder().buildObject(); - // Set the status code - StatusCode statCode = new StatusCodeBuilder().buildObject(); - statCode.setValue("urn:oasis:names:tc:SAML:2.0:status:Success"); - stat.setStatusCode(statCode); - // Set the status Message - StatusMessage statMesssage = new StatusMessageBuilder().buildObject(); - statMesssage.setMessage(null); - stat.setStatusMessage(statMesssage); - response.setStatus(stat); - response.setVersion(SAMLVersion.VERSION_20); - response.setIssueInstant(new DateTime()); - response.getAssertions().add(assertion); - //XMLHelper.adoptElement(assertion.getDOM(), assertion.getDOM().getOwnerDocument()); - return response; - } - } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfig.java new file mode 100644 index 00000000000..6bbff77c601 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfig.java @@ -0,0 +1,109 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.util.KeyWithCert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; + +import java.util.ArrayList; +import java.util.List; + +import static org.cloudfoundry.identity.uaa.provider.saml.SamlMetadataEndpoint.DEFAULT_REGISTRATION_ID; + +@Configuration +@Slf4j +public class SamlRelyingPartyRegistrationRepositoryConfig { + + public static final String CLASSPATH_DUMMY_SAML_IDP_METADATA_XML = "classpath:dummy-saml-idp-metadata.xml"; + + private final String samlEntityID; + private final SamlConfigProps samlConfigProps; + private final BootstrapSamlIdentityProviderData bootstrapSamlIdentityProviderData; + private final String samlSpNameID; + private final List signatureAlgorithms; + + public SamlRelyingPartyRegistrationRepositoryConfig(@Qualifier("samlEntityID") String samlEntityID, + SamlConfigProps samlConfigProps, + BootstrapSamlIdentityProviderData bootstrapSamlIdentityProviderData, + @Value("${login.saml.nameID:urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified}") + String samlSpNameID, List signatureAlgorithms + ) { + this.samlEntityID = samlEntityID; + this.samlConfigProps = samlConfigProps; + this.bootstrapSamlIdentityProviderData = bootstrapSamlIdentityProviderData; + this.samlSpNameID = samlSpNameID; + this.signatureAlgorithms = signatureAlgorithms; + } + + @Autowired + @Bean + RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(SamlIdentityProviderConfigurator samlIdentityProviderConfigurator) { + SamlKeyManagerFactory.SamlConfigPropsSamlKeyManagerImpl samlKeyManager = new SamlKeyManagerFactory.SamlConfigPropsSamlKeyManagerImpl(samlConfigProps); + List defaultKeysWithCerts = samlKeyManager.getAvailableCredentials(); + + List relyingPartyRegistrations = new ArrayList<>(); + String uaaWideSamlEntityIDAlias = samlConfigProps.getEntityIDAlias() != null ? samlConfigProps.getEntityIDAlias() : samlEntityID; + + @SuppressWarnings("java:S125") + // Spring Security requires at least one relyingPartyRegistration before SAML SP metadata generation; + // and each relyingPartyRegistration needs to contain the SAML IDP metadata. + // However, in the context of UAA external SAML IDP login, UAA does not know what the SAML IDP + // metadata is until the operator configures the SAML IDP(s). + // Also, some SAML IDPs might require you to supply the SAML SP metadata first before you can get the + // SAML IDP metadata. + // Hence, create a default relyingPartyRegistration with a hardcoded stub SAML IDP metadata + // here to ensure that the SAML SP metadata will always be present, + // even when there are no SAML IDPs configured. + // See relevant issue: https://github.com/spring-projects/spring-security/issues/11369 + RelyingPartyRegistrationBuilder.Params exampleParams = RelyingPartyRegistrationBuilder.Params.builder() + .samlEntityID(samlEntityID) + .samlSpNameId(samlSpNameID) + .keys(defaultKeysWithCerts) + .metadataLocation(CLASSPATH_DUMMY_SAML_IDP_METADATA_XML) + .rpRegistrationId(DEFAULT_REGISTRATION_ID) + .samlSpAlias(uaaWideSamlEntityIDAlias) + .requestSigned(samlConfigProps.getSignRequest()) + .signatureAlgorithms(signatureAlgorithms) + .build(); + RelyingPartyRegistration exampleRelyingPartyRegistration = RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(exampleParams); + relyingPartyRegistrations.add(exampleRelyingPartyRegistration); + + for (SamlIdentityProviderDefinition samlIdentityProviderDefinition : bootstrapSamlIdentityProviderData.getIdentityProviderDefinitions()) { + RelyingPartyRegistrationBuilder.Params params = RelyingPartyRegistrationBuilder.Params.builder() + .samlEntityID(samlEntityID) + .samlSpNameId(samlSpNameID) + .keys(defaultKeysWithCerts) + .metadataLocation(samlIdentityProviderDefinition.getMetaDataLocation()) + .rpRegistrationId(samlIdentityProviderDefinition.getIdpEntityAlias()) + .samlSpAlias(uaaWideSamlEntityIDAlias) + .requestSigned(samlConfigProps.getSignRequest()) + .signatureAlgorithms(signatureAlgorithms) + .build(); + relyingPartyRegistrations.add(RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(params)); + } + + InMemoryRelyingPartyRegistrationRepository bootstrapRepo = new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistrations); + ConfiguratorRelyingPartyRegistrationRepository configuratorRepo = + new ConfiguratorRelyingPartyRegistrationRepository(samlEntityID, uaaWideSamlEntityIDAlias, + samlIdentityProviderConfigurator, signatureAlgorithms, samlSpNameID); + DefaultRelyingPartyRegistrationRepository defaultRepo = + new DefaultRelyingPartyRegistrationRepository(samlEntityID, uaaWideSamlEntityIDAlias, signatureAlgorithms, samlSpNameID); + + return new DelegatingRelyingPartyRegistrationRepository(bootstrapRepo, configuratorRepo, defaultRepo); + } + + @Autowired + @Bean + RelyingPartyRegistrationResolver relyingPartyRegistrationResolver(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) { + return new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlSessionStorageFactory.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlSessionStorageFactory.java deleted file mode 100644 index faac61fefad..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlSessionStorageFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.springframework.security.saml.storage.HttpSessionStorage; -import org.springframework.security.saml.storage.SAMLMessageStorage; -import org.springframework.security.saml.storage.SAMLMessageStorageFactory; - -import javax.servlet.http.HttpServletRequest; - -public class SamlSessionStorageFactory implements SAMLMessageStorageFactory { - - @Override - public synchronized SAMLMessageStorage getMessageStorage(HttpServletRequest request) { - if (IdentityZoneHolder.get().getConfig().getSamlConfig().isDisableInResponseToCheck()) { - //add the ability to disable inResponseTo check - //https://docs.spring.io/spring-security-saml/docs/current/reference/html/chapter-troubleshooting.html - return null; - } - return new HttpSessionStorage(request); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAttributesConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAttributesConverter.java new file mode 100644 index 00000000000..4ead95c34e3 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAttributesConverter.java @@ -0,0 +1,63 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Response; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.List; +import java.util.Map; + +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; + +/** + * Part of the AuthenticationConverter used during SAML login flow. + * This handles the conversion of SAML Attributes to User Attributes + */ +@Slf4j +public class SamlUaaAuthenticationAttributesConverter { + + public MultiValueMap retrieveUserAttributes(SamlIdentityProviderDefinition definition, Response response) { + log.debug("Retrieving SAML user attributes [zone:{}, origin:{}}]", definition.getZoneId(), definition.getIdpEntityAlias()); + MultiValueMap userAttributes = new LinkedMultiValueMap<>(); + List assertions = response.getAssertions(); + if (assertions.isEmpty()) { + return userAttributes; + } + + assertions.stream().flatMap(assertion -> assertion.getAttributeStatements().stream()) + .flatMap(statement -> statement.getAttributes().stream()) + .forEach(attribute -> { + String key = attribute.getName(); + attribute.getAttributeValues().forEach(xmlObject -> { + String value = OpenSamlXmlUtils.getStringValue(key, definition, xmlObject); + if (value != null) { + userAttributes.add(key, value); + } + }); + }); + + if (definition != null && definition.getAttributeMappings() != null) { + definition.getAttributeMappings().forEach((key, attributeKey) -> { + if (attributeKey instanceof String && userAttributes.get(attributeKey) != null) { + userAttributes.addAll(key, userAttributes.get(attributeKey)); + } + }); + } + + return userAttributes; + } + + public MultiValueMap retrieveCustomUserAttributes(MultiValueMap userAttributes) { + MultiValueMap customAttributes = new LinkedMultiValueMap<>(); + for (Map.Entry> entry : userAttributes.entrySet()) { + if (entry.getKey().startsWith(USER_ATTRIBUTE_PREFIX)) { + customAttributes.put(entry.getKey().substring(USER_ATTRIBUTE_PREFIX.length()), entry.getValue()); + } + } + return customAttributes; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAuthoritiesConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAuthoritiesConverter.java new file mode 100644 index 00000000000..206f40602ce --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAuthoritiesConverter.java @@ -0,0 +1,100 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; +import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.saml.saml2.core.Response; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.Optional.of; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.saml.OpenSamlXmlUtils.getStringValue; +import static org.cloudfoundry.identity.uaa.util.UaaStringUtils.retainAllMatches; + +/** + * Part of the AuthenticationConverter used during SAML login flow. + * This handles the conversion of SAML Authorities to UAA Authorities. + */ +@Slf4j +@Getter +public class SamlUaaAuthenticationAuthoritiesConverter { + + private final ScimGroupExternalMembershipManager externalMembershipManager; + + public SamlUaaAuthenticationAuthoritiesConverter( + ScimGroupExternalMembershipManager externalMembershipManager) { + this.externalMembershipManager = externalMembershipManager; + } + + protected Set filterSamlAuthorities(SamlIdentityProviderDefinition definition, Collection samlAuthorities) { + List whiteList = of(definition.getExternalGroupsWhitelist()).orElse(List.of()); + Set authorities = samlAuthorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); + if (whiteList.isEmpty()) { + return authorities; + } + Set result = retainAllMatches(authorities, whiteList); + log.debug("White listed external SAML groups:'{}'", result); + return result; + } + + protected Collection mapAuthorities(String origin, Collection authorities, String identityZoneId) { + Collection result = new LinkedList<>(); + log.debug("Mapping SAML authorities:" + authorities); + for (GrantedAuthority authority : authorities) { + String externalGroup = authority.getAuthority(); + log.debug("Attempting to map external group: {}", externalGroup); + for (ScimGroupExternalMember internalGroup : externalMembershipManager.getExternalGroupMapsByExternalGroup(externalGroup, origin, identityZoneId)) { + String internalName = internalGroup.getDisplayName(); + log.debug("Mapped external: '{}' to internal: '{}'", externalGroup, internalName); + result.add(new SimpleGrantedAuthority(internalName)); + } + } + return result; + } + + protected List retrieveSamlAuthorities(SamlIdentityProviderDefinition definition, Response response) { + if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) != null) { + List groupAttributeNames = getGroupAttributeNames(definition); + + List authorities = new ArrayList<>(); + response.getAssertions().stream().flatMap(assertion -> assertion.getAttributeStatements().stream()) + .flatMap(attributeStatement -> attributeStatement.getAttributes().stream()) + .filter(attribute -> groupAttributeNames.contains(attribute.getName()) || groupAttributeNames.contains(attribute.getFriendlyName())) + .filter(attribute -> attribute.getAttributeValues() != null) + .filter(attribute -> !attribute.getAttributeValues().isEmpty()) + .forEach(attribute -> { + for (XMLObject group : attribute.getAttributeValues()) { + authorities.add(new SamlUserAuthority(getStringValue(attribute.getName(), + definition, + group))); + } + }); + + return authorities; + } + return new ArrayList<>(); + } + + private List getGroupAttributeNames(SamlIdentityProviderDefinition definition) { + List attributeNames = new LinkedList<>(); + + if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) instanceof String value) { + attributeNames.add(value); + } else if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) instanceof Collection value) { + attributeNames.addAll(value); + } + return attributeNames; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationUserManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationUserManager.java new file mode 100644 index 00000000000..1636b2b094a --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationUserManager.java @@ -0,0 +1,193 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; +import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; +import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; +import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; +import org.cloudfoundry.identity.uaa.user.UserInfo; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; + +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_VERIFIED_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils.isAcceptedInvitationAuthentication; + +/** + * Part of the AuthenticationConverter used during SAML login flow. + * This handles User creation and storage in the database. + */ +public class SamlUaaAuthenticationUserManager implements ApplicationEventPublisherAware { + + ApplicationEventPublisher eventPublisher; + + public SamlUaaAuthenticationUserManager(UaaUserDatabase userDatabase) { + this.userDatabase = userDatabase; + } + + private final UaaUserDatabase userDatabase; + + protected UaaUser createIfMissing(UaaPrincipal samlPrincipal, + boolean addNew, + Collection authorities, + MultiValueMap userAttributes) { + + CreateIfMissingContext context = new CreateIfMissingContext(addNew, false, new LinkedMultiValueMap<>(userAttributes)); + UaaUser user = getAcceptedInvitationUser(samlPrincipal, context); + UaaUser userWithSamlAttributes = getUser(samlPrincipal, context.getUserAttributes()); + + try { + if (user == null) { + user = userDatabase.retrieveUserByName(samlPrincipal.getName(), samlPrincipal.getOrigin()); + } + } catch (UsernameNotFoundException e) { + UaaUserPrototype uaaUser = userDatabase.retrieveUserPrototypeByEmail(userWithSamlAttributes.getEmail(), samlPrincipal.getOrigin()); + if (uaaUser != null) { + context.setUserModified(true); + user = new UaaUser(uaaUser.withUsername(samlPrincipal.getName())); + } else { + if (!context.isAddNew()) { + throw new SamlLoginException("SAML user does not exist. " + + "You can correct this by creating a shadow user for the SAML user.", e); + } + publish(new NewUserAuthenticatedEvent(userWithSamlAttributes)); + try { + user = new UaaUser(userDatabase.retrieveUserPrototypeByName(samlPrincipal.getName(), samlPrincipal.getOrigin())); + } catch (UsernameNotFoundException ex) { + throw new BadCredentialsException("Unable to establish shadow user for SAML user:" + samlPrincipal.getName(), ex); + } + } + } + + if (haveUserAttributesChanged(user, userWithSamlAttributes)) { + context.setUserModified(true); + user = user.modifyAttributes(userWithSamlAttributes.getEmail(), + userWithSamlAttributes.getGivenName(), + userWithSamlAttributes.getFamilyName(), + userWithSamlAttributes.getPhoneNumber(), + userWithSamlAttributes.getExternalId(), + user.isVerified() || userWithSamlAttributes.isVerified()); + } + + publish(new ExternalGroupAuthorizationEvent(user, context.isUserModified(), authorities, true)); + + user = userDatabase.retrieveUserById(user.getId()); + return user; + } + + private UaaUser getAcceptedInvitationUser(UaaPrincipal samlPrincipal, CreateIfMissingContext context) { + if (!isAcceptedInvitationAuthentication()) { + return null; + } + + context.setAddNew(false); + String invitedUserId = (String) RequestContextHolder.currentRequestAttributes().getAttribute("user_id", RequestAttributes.SCOPE_SESSION); + UaaUser user = userDatabase.retrieveUserById(invitedUserId); + if (context.hasEmailAttribute()) { + if (!context.getEmailAttribute().equalsIgnoreCase(user.getEmail())) { + throw new BadCredentialsException("SAML User email mismatch. Authenticated email doesn't match invited email."); + } + } else { + context.addEmailAttribute(user.getEmail()); + } + + if (user.getUsername().equals(user.getEmail()) && !user.getUsername().equals(samlPrincipal.getName())) { + user = user.modifyUsername(samlPrincipal.getName()); + } + + publish(new InvitedUserAuthenticatedEvent(user)); + return userDatabase.retrieveUserById(invitedUserId); + } + + protected UaaUser getUser(UaaPrincipal principal, MultiValueMap userAttributes) { + if (principal.getName() == null && userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) == null) { + throw new BadCredentialsException("Cannot determine username from credentials supplied"); + } + + String name = principal.getName(); + return UaaUser.createWithDefaults(u -> + u.withId(OriginKeys.NotANumber) + .withUsername(name) + .withEmail(userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME)) + .withPhoneNumber(userAttributes.getFirst(PHONE_NUMBER_ATTRIBUTE_NAME)) + .withPassword("") + .withGivenName(userAttributes.getFirst(GIVEN_NAME_ATTRIBUTE_NAME)) + .withFamilyName(userAttributes.getFirst(FAMILY_NAME_ATTRIBUTE_NAME)) + .withAuthorities(Collections.emptyList()) + .withVerified(Boolean.parseBoolean(userAttributes.getFirst(EMAIL_VERIFIED_ATTRIBUTE_NAME))) + .withOrigin(principal.getOrigin() != null ? principal.getOrigin() : OriginKeys.LOGIN_SERVER) + .withExternalId(name) + .withZoneId(principal.getZoneId()) + ); + } + + protected void storeCustomAttributesAndRoles(UaaUser user, UaaAuthentication authentication) { + userDatabase.storeUserInfo(user.getId(), + new UserInfo() + .setUserAttributes(authentication.getUserAttributes()) + .setRoles(new LinkedList<>(authentication.getExternalGroups())) + ); + } + + protected static boolean haveUserAttributesChanged(UaaUser existingUser, UaaUser user) { + return existingUser.isVerified() != user.isVerified() || + !StringUtils.equals(existingUser.getGivenName(), user.getGivenName()) || + !StringUtils.equals(existingUser.getFamilyName(), user.getFamilyName()) || + !StringUtils.equals(existingUser.getPhoneNumber(), user.getPhoneNumber()) || + !StringUtils.equals(existingUser.getEmail(), user.getEmail()) || + !StringUtils.equals(existingUser.getExternalId(), user.getExternalId()); + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.eventPublisher = applicationEventPublisher; + } + + protected void publish(ApplicationEvent event) { + if (eventPublisher != null) { + eventPublisher.publishEvent(event); + } + } + + @Data + @AllArgsConstructor + public static class CreateIfMissingContext{ + boolean addNew; + boolean userModified; + MultiValueMap userAttributes; + + public String getEmailAttribute() { + return userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME); + } + + public boolean hasEmailAttribute() { + return userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) != null; + } + + public void addEmailAttribute(String value) { + userAttributes.add(EMAIL_ATTRIBUTE_NAME, value); + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaResponseAuthenticationConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaResponseAuthenticationConverter.java new file mode 100644 index 00000000000..0b6af42cd95 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaResponseAuthenticationConverter.java @@ -0,0 +1,197 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.authentication.UaaSamlPrincipal; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; +import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Response; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.core.convert.converter.Converter; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.ProviderNotFoundException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.util.MultiValueMap; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.NotANumber; + +/** + * AuthenticationConverter used during SAML login flow to convert a SAML response token to a UaaAuthentication. + */ +@Slf4j +@Getter +public class SamlUaaResponseAuthenticationConverter + implements Converter, + ApplicationEventPublisherAware { + + public static final String AUTHENTICATION_CONTEXT_CLASS_REFERENCE = "acr"; + + private final IdentityZoneManager identityZoneManager; + + private final IdentityProviderProvisioning identityProviderProvisioning; + + private ApplicationEventPublisher eventPublisher; + + private final SamlUaaAuthenticationUserManager userManager; + private final SamlUaaAuthenticationAttributesConverter attributesConverter; + private final SamlUaaAuthenticationAuthoritiesConverter authoritiesConverter; + + public SamlUaaResponseAuthenticationConverter(IdentityZoneManager identityZoneManager, + final JdbcIdentityProviderProvisioning identityProviderProvisioning, + SamlUaaAuthenticationUserManager userManager, + SamlUaaAuthenticationAttributesConverter attributesConverter, + SamlUaaAuthenticationAuthoritiesConverter authoritiesConverter) { + this.identityZoneManager = identityZoneManager; + this.identityProviderProvisioning = identityProviderProvisioning; + this.userManager = userManager; + this.attributesConverter = attributesConverter; + this.authoritiesConverter = authoritiesConverter; + } + + @Override + public UaaAuthentication convert(OpenSaml4AuthenticationProvider.ResponseToken responseToken) { + Saml2AuthenticationToken authenticationToken = responseToken.getToken(); + Response response = responseToken.getResponse(); + List assertions = response.getAssertions(); + String subjectName = assertions.get(0).getSubject().getNameID().getValue(); + + IdentityZone zone = identityZoneManager.getCurrentIdentityZone(); + log.debug("Initiating SAML authentication in zone '{}' domain '{}'", + zone.getId(), zone.getSubdomain()); + + RelyingPartyRegistration relyingPartyRegistration = authenticationToken.getRelyingPartyRegistration(); + String alias = relyingPartyRegistration.getRegistrationId(); + UaaPrincipal initialPrincipal = new UaaPrincipal(NotANumber, subjectName, authenticationToken.getName(), + alias, authenticationToken.getName(), zone.getId()); + log.debug("Mapped SAML authentication to IDP with origin '{}' and username '{}'", + alias, initialPrincipal.getName()); + + boolean addNew; + IdentityProvider idp; + SamlIdentityProviderDefinition samlConfig; + try { + idp = identityProviderProvisioning.retrieveByOrigin(alias, zone.getId()); + samlConfig = idp.getConfig(); + addNew = samlConfig.isAddShadowUserOnLogin(); + if (!idp.isActive()) { + throw new ProviderNotFoundException("Identity Provider has been disabled by administrator for alias:" + alias); + } + } catch (EmptyResultDataAccessException x) { + throw new ProviderNotFoundException("No SAML identity provider found in zone for alias:" + alias); + } + + MultiValueMap userAttributes = attributesConverter.retrieveUserAttributes(samlConfig, response); + List samlAuthorities = authoritiesConverter.retrieveSamlAuthorities(samlConfig, response); + + log.debug("Mapped SAML authentication to IDP with origin '{}' and username '{}'", + idp.getOriginKey(), initialPrincipal.getName()); + + UaaUser user = userManager.createIfMissing(initialPrincipal, addNew, getMappedAuthorities( + idp, samlAuthorities), userAttributes); + + UaaAuthentication authentication = new UaaAuthentication( + new UaaSamlPrincipal(user), + authenticationToken.getCredentials(), + user.getAuthorities(), + authoritiesConverter.filterSamlAuthorities(samlConfig, samlAuthorities), + attributesConverter.retrieveCustomUserAttributes(userAttributes), + null, + true, System.currentTimeMillis(), + -1); + + authentication.setAuthenticationMethods(Set.of("ext")); + setAuthContextClassRef(userAttributes, authentication, samlConfig); + + publish(new IdentityProviderAuthenticationSuccessEvent(user, authentication, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZoneId())); + + if (samlConfig.isStoreCustomAttributes()) { + userManager.storeCustomAttributesAndRoles(user, authentication); + } + + AbstractSaml2AuthenticationRequest authenticationRequest = authenticationToken.getAuthenticationRequest(); + if (authenticationRequest != null) { + String relayState = authenticationRequest.getRelayState(); + configureRelayRedirect(relayState); + } + + return authentication; + } + + private static void setAuthContextClassRef(MultiValueMap userAttributes, + UaaAuthentication authentication, SamlIdentityProviderDefinition samlConfig) { + + List acrValues = userAttributes.get(AUTHENTICATION_CONTEXT_CLASS_REFERENCE); + if (acrValues != null) { + authentication.setAuthContextClassRef(Set.copyOf(acrValues)); + } + + if (samlConfig.getAuthnContext() != null) { + assert acrValues != null; + if (Collections.disjoint(acrValues, samlConfig.getAuthnContext())) { + throw new BadCredentialsException( + "Identity Provider did not authenticate with the requested AuthnContext."); + } + } + } + + private Collection getMappedAuthorities( + IdentityProvider idp, + List samlAuthorities) { + Collection authorities; + SamlIdentityProviderDefinition.ExternalGroupMappingMode groupMappingMode = idp.getConfig().getGroupMappingMode(); + authorities = switch (groupMappingMode) { + case EXPLICITLY_MAPPED -> authoritiesConverter.mapAuthorities(idp.getOriginKey(), + samlAuthorities, identityZoneManager.getCurrentIdentityZoneId()); + case AS_SCOPES -> List.copyOf(samlAuthorities); + }; + return authorities; + } + + public void configureRelayRedirect(String relayState) { + //configure relay state + if (UaaUrlUtils.isUrl(relayState)) { + RequestContextHolder.currentRequestAttributes() + .setAttribute( + UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, + relayState, + RequestAttributes.SCOPE_REQUEST + ); + } + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.eventPublisher = applicationEventPublisher; + } + + protected void publish(ApplicationEvent event) { + if (eventPublisher != null) { + eventPublisher.publishEvent(event); + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SignatureAlgorithm.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SignatureAlgorithm.java new file mode 100644 index 00000000000..cc46c9e3938 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SignatureAlgorithm.java @@ -0,0 +1,25 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_DIGEST_SHA1; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_DIGEST_SHA256; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_DIGEST_SHA512; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512; + +@Getter +@AllArgsConstructor +public enum SignatureAlgorithm { + SHA1(ALGO_ID_DIGEST_SHA1, ALGO_ID_SIGNATURE_RSA_SHA1), + SHA256(ALGO_ID_DIGEST_SHA256, ALGO_ID_SIGNATURE_RSA_SHA256), + SHA512(ALGO_ID_DIGEST_SHA512, ALGO_ID_SIGNATURE_RSA_SHA512), + + // Default to SHA256 when the algorithm is not recognized, but allow it to be checked as invalid + INVALID(ALGO_ID_DIGEST_SHA256, ALGO_ID_SIGNATURE_RSA_SHA256); + + private final String digestAlgorithmURI; + private final String signatureAlgorithmURI; +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandler.java new file mode 100644 index 00000000000..84f5bfd5435 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandler.java @@ -0,0 +1,98 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.authentication.ZoneAwareWhitelistLogoutSuccessHandler; +import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthLogoutSuccessHandler; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; + +/** + * UaaDelegatingLogoutSuccessHandler is a {@link LogoutSuccessHandler} that delegates to the appropriate + * logout handler based on the authentication. + *

+ *

  • If we have a valid SAML2 {@link Saml2AuthenticatedPrincipal} in the authentication, and have a + * SingleLogoutServiceLocation set, then we will delegate to the {@link Saml2RelyingPartyInitiatedLogoutSuccessHandler}. + *
  • If we have a valid OAuth2 {@link AbstractExternalOAuthIdentityProviderDefinition} in the authentication, + * then we will delegate to the {@link ExternalOAuthLogoutSuccessHandler}. + *
  • Otherwise, we will delegate to the {@link ZoneAwareWhitelistLogoutSuccessHandler}. + *

    + * On the LogoutResponse side, there is no Authentication available at that point, so will + * always delegate to the {@link ZoneAwareWhitelistLogoutSuccessHandler}. + */ +public class UaaDelegatingLogoutSuccessHandler implements LogoutSuccessHandler { + private final ZoneAwareWhitelistLogoutSuccessHandler zoneAwareWhitelistLogoutHandler; + private final Saml2RelyingPartyInitiatedLogoutSuccessHandler saml2RelyingPartyInitiatedLogoutSuccessHandler; + private final ExternalOAuthLogoutSuccessHandler externalOAuthLogoutHandler; + private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver; + + public UaaDelegatingLogoutSuccessHandler(ZoneAwareWhitelistLogoutSuccessHandler zoneAwareWhitelistLogoutHandler, + Saml2RelyingPartyInitiatedLogoutSuccessHandler saml2RelyingPartyInitiatedLogoutSuccessHandler, + ExternalOAuthLogoutSuccessHandler externalOAuthLogoutHandler, + RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { + this.zoneAwareWhitelistLogoutHandler = zoneAwareWhitelistLogoutHandler; + this.saml2RelyingPartyInitiatedLogoutSuccessHandler = saml2RelyingPartyInitiatedLogoutSuccessHandler; + this.externalOAuthLogoutHandler = externalOAuthLogoutHandler; + this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver; + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + if (shouldPerformSamlRelyingPartyLogout(request, authentication)) { + saml2RelyingPartyInitiatedLogoutSuccessHandler.onLogoutSuccess(request, response, authentication); + return; + } + + if (shouldPerformOAuthRpInitiatedLogout(authentication)) { + externalOAuthLogoutHandler.onLogoutSuccess(request, response, authentication); + return; + } + + zoneAwareWhitelistLogoutHandler.onLogoutSuccess(request, response, authentication); + } + + private boolean shouldPerformOAuthRpInitiatedLogout(Authentication authentication) { + + AbstractExternalOAuthIdentityProviderDefinition oauthConfig = externalOAuthLogoutHandler.getOAuthProviderForAuthentication(authentication); + String logoutUrl = externalOAuthLogoutHandler.getLogoutUrl(oauthConfig); + boolean shouldPerformRpInitiatedLogout = externalOAuthLogoutHandler.getPerformRpInitiatedLogout(oauthConfig); + return shouldPerformRpInitiatedLogout && logoutUrl != null; + } + + /** + * Determines if the logout should follow the SAML protocol to the Asserting Party. + */ + private boolean shouldPerformSamlRelyingPartyLogout(HttpServletRequest request, Authentication authentication) { + if (authentication == null) { + return false; + } + + Object principal = authentication.getPrincipal(); + if (!(principal instanceof Saml2AuthenticatedPrincipal samlPrincipal)) { + return false; + } + + String registrationId = samlPrincipal.getRelyingPartyRegistrationId(); + if (registrationId == null) { + return false; + } + + RelyingPartyRegistration registration = relyingPartyRegistrationResolver.resolve(request, registrationId); + if (registration == null) { + return false; + } + + String singleLogoutServiceLocation = Optional.ofNullable(registration.getAssertingPartyDetails()).map(RelyingPartyRegistration.AssertingPartyDetails::getSingleLogoutServiceLocation).orElse(null); + return singleLogoutServiceLocation != null; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaInResponseToHandlingResponseValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaInResponseToHandlingResponseValidator.java new file mode 100644 index 00000000000..825a66c9f8e --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaInResponseToHandlingResponseValidator.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import groovy.util.logging.Slf4j; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.NonNull; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; + +/** + * Strategy for validating the SAML 2.0 Response used with + * {@link org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider} + * Handles the property `login.saml.disableInResponseToCheck` when set to true + * we will ignore errors on the InResponseTo check of the SAML Response. + *

    + * The InResponseTo attribute is optional, but if it is present, the default validator checks against the ID of the request. + */ +@Slf4j +public final class UaaInResponseToHandlingResponseValidator implements Converter { + + private final boolean uaaWideDisableInResponseToCheck; + private final Converter delegate; + + public UaaInResponseToHandlingResponseValidator(Converter delegate, + boolean uaaWideDisableInResponseToCheck) { + this.delegate = delegate; + this.uaaWideDisableInResponseToCheck = uaaWideDisableInResponseToCheck; + } + + @Override + public Saml2ResponseValidatorResult convert(@NonNull OpenSaml4AuthenticationProvider.ResponseToken source) { + Saml2ResponseValidatorResult result = delegate.convert(source); + // if the result is successful, return it + if (result == null || !result.hasErrors()) { + return result; + } + + // in case of error, check if we should ignore the InResponseTo check, and remove + IdentityZone identityZone = IdentityZoneHolder.get(); + if (identityZone != null) { + boolean removeInResponseToErrors = false; + + // samlConfig does not have correct values for UAA zone + if (identityZone.isUaa() && uaaWideDisableInResponseToCheck) { + removeInResponseToErrors = true; + } else { + removeInResponseToErrors = Optional.of(identityZone) + .map(IdentityZone::getConfig) + .map(IdentityZoneConfiguration::getSamlConfig) + .map(SamlConfig::isDisableInResponseToCheck) + .orElse(false); + } + + if (removeInResponseToErrors) { + result = removeInResponseToErrors(result); + } + } + + return result; + } + + private Saml2ResponseValidatorResult removeInResponseToErrors(Saml2ResponseValidatorResult result) { + + Collection errors = new ArrayList<>(result.getErrors()); + errors.removeIf(error -> error.getErrorCode().contains("invalid_in_response_to")); + + if (errors.isEmpty()) { + return Saml2ResponseValidatorResult.success(); + } + return Saml2ResponseValidatorResult.failure(errors); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareKeyManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareKeyManager.java index f7b729525d9..bc9bc798088 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareKeyManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareKeyManager.java @@ -13,52 +13,38 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml; +import org.cloudfoundry.identity.uaa.util.KeyWithCert; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.opensaml.xml.security.CriteriaSet; -import org.opensaml.xml.security.SecurityException; -import org.opensaml.xml.security.credential.Credential; import org.springframework.context.annotation.DependsOn; -import org.springframework.security.saml.key.KeyManager; import org.springframework.stereotype.Component; -import java.security.cert.X509Certificate; -import java.util.Set; +import java.util.List; @Component("zoneAwareSamlSpKeyManager") @DependsOn("identityZoneHolderInitializer") -public class ZoneAwareKeyManager implements KeyManager { +public class ZoneAwareKeyManager implements SamlKeyManager { @Override - public Credential getCredential(String keyName) { - return IdentityZoneHolder.getSamlSPKeyManager().getCredential(keyName); + public KeyWithCert getCredential(String keyName) { + return IdentityZoneHolder.getSamlKeyManager().getCredential(keyName); } @Override - public Credential getDefaultCredential() { - return IdentityZoneHolder.getSamlSPKeyManager().getDefaultCredential(); + public KeyWithCert getDefaultCredential() { + return IdentityZoneHolder.getSamlKeyManager().getDefaultCredential(); } @Override public String getDefaultCredentialName() { - return IdentityZoneHolder.getSamlSPKeyManager().getDefaultCredentialName(); + return IdentityZoneHolder.getSamlKeyManager().getDefaultCredentialName(); } @Override - public Set getAvailableCredentials() { - return IdentityZoneHolder.getSamlSPKeyManager().getAvailableCredentials(); + public List getAvailableCredentials() { + return IdentityZoneHolder.getSamlKeyManager().getAvailableCredentials(); } @Override - public X509Certificate getCertificate(String alias) { - return IdentityZoneHolder.getSamlSPKeyManager().getCertificate(alias); - } - - @Override - public Iterable resolve(CriteriaSet criteria) throws SecurityException { - return IdentityZoneHolder.getSamlSPKeyManager().resolve(criteria); - } - - @Override - public Credential resolveSingle(CriteriaSet criteria) throws SecurityException { - return IdentityZoneHolder.getSamlSPKeyManager().resolveSingle(criteria); + public List getAvailableCredentialIds() { + return IdentityZoneHolder.getSamlKeyManager().getAvailableCredentialIds(); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataDisplayFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataDisplayFilter.java deleted file mode 100644 index 81cdc225236..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataDisplayFilter.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * ***************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.opensaml.saml2.metadata.EntityDescriptor; -import org.opensaml.xml.io.MarshallingException; -import org.springframework.security.saml.metadata.MetadataDisplayFilter; -import org.springframework.security.saml.metadata.MetadataGenerator; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; - -public class ZoneAwareMetadataDisplayFilter extends MetadataDisplayFilter { - - protected final MetadataGenerator generator; - - public ZoneAwareMetadataDisplayFilter(MetadataGenerator generator) { - this.generator = generator; - } - - public MetadataGenerator getGenerator() { - return generator; - } - - @Override - protected void processMetadataDisplay(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - super.processMetadataDisplay(request, response); - response.setHeader("Content-Disposition", String.format("attachment; filename=\"saml-%ssp.xml\"", - !IdentityZoneHolder.isUaa() ? IdentityZoneHolder.get().getSubdomain() + "-" : "")); - } - - @Override - protected void displayMetadata(String spEntityName, PrintWriter writer) throws ServletException { - try { - EntityDescriptor descriptor = getGenerator().generateMetadata(); - if (descriptor == null) { - throw new ServletException("Metadata entity with ID " + manager.getHostedSPName() + " wasn't found"); - } else { - writer.print(getMetadataAsString(descriptor)); - } - } catch (MarshallingException e) { - log.error("Error marshalling entity descriptor", e); - throw new ServletException(e); - } catch (Exception e) { - log.error("Error retrieving metadata", e); - throw new ServletException("Error retrieving metadata", e); - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java deleted file mode 100644 index b27cc165470..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * ***************************************************************************** - */ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.opensaml.saml2.metadata.EntityDescriptor; -import org.opensaml.saml2.metadata.SPSSODescriptor; -import org.opensaml.xml.security.credential.UsageType; -import org.springframework.security.saml.key.KeyManager; -import org.springframework.security.saml.metadata.ExtendedMetadata; -import org.springframework.security.saml.metadata.MetadataGenerator; -import org.springframework.security.saml.util.SAMLUtil; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -public class ZoneAwareMetadataGenerator extends MetadataGenerator { - - @Override - public ExtendedMetadata generateExtendedMetadata() { - ExtendedMetadata metadata = super.generateExtendedMetadata(); - metadata.setAlias(UaaUrlUtils.getSubdomain(IdentityZoneHolder.get().getSubdomain())+metadata.getAlias()); - return metadata; - } - - @Override - public String getEntityId() { - if (!IdentityZoneHolder.isUaa()) { - String url = getZoneDefinition().getSamlConfig().getEntityID(); - if (url != null) { - return url; - } - } - - String entityId = super.getEntityId(); - - if (UaaUrlUtils.isUrl(entityId)) { - return UaaUrlUtils.addSubdomainToUrl(entityId, IdentityZoneHolder.get().getSubdomain()); - } else { - return UaaUrlUtils.getSubdomain(IdentityZoneHolder.get().getSubdomain()) + entityId; - } - } - - @Override - public String getEntityBaseURL() { - return UaaUrlUtils.addSubdomainToUrl(super.getEntityBaseURL(), IdentityZoneHolder.get().getSubdomain()); - } - - @Override - protected String getEntityAlias() { - return UaaUrlUtils.getSubdomain(IdentityZoneHolder.get().getSubdomain()) + super.getEntityAlias(); - } - - @Override - public boolean isRequestSigned() { - if (!IdentityZoneHolder.isUaa()) { - return getZoneDefinition().getSamlConfig().isRequestSigned(); - } - return super.isRequestSigned(); - } - - @Override - public boolean isWantAssertionSigned() { - if (!IdentityZoneHolder.isUaa()) { - return getZoneDefinition().getSamlConfig().isWantAssertionSigned(); - } - return super.isWantAssertionSigned(); - } - - protected IdentityZoneConfiguration getZoneDefinition() { - IdentityZone zone = IdentityZoneHolder.get(); - IdentityZoneConfiguration definition = zone.getConfig(); - return definition!=null ? definition : new IdentityZoneConfiguration(); - } - - @Override - public EntityDescriptor generateMetadata() { - EntityDescriptor result = super.generateMetadata(); - result.setID(SAMLUtil.getNCNameString(result.getEntityID())); - return result; - } - - @Override - protected SPSSODescriptor buildSPSSODescriptor(String entityBaseURL, String entityAlias, boolean requestSigned, boolean wantAssertionSigned, Collection includedNameID) { - SPSSODescriptor result = super.buildSPSSODescriptor(entityBaseURL, entityAlias, requestSigned, wantAssertionSigned, includedNameID); - - //metadata should not contain inactive keys - KeyManager samlSPKeyManager = IdentityZoneHolder.getSamlSPKeyManager(); - if (samlSPKeyManager != null && samlSPKeyManager.getAvailableCredentials()!=null) { - Set allKeyAliases = new HashSet(samlSPKeyManager.getAvailableCredentials()); - String activeKeyAlias = samlSPKeyManager.getDefaultCredentialName(); - allKeyAliases.remove(activeKeyAlias); - for (String keyAlias : allKeyAliases) { - result.getKeyDescriptors().add(getKeyDescriptor(UsageType.SIGNING, getServerKeyInfo(keyAlias))); - } - }//add inactive keys as signing verification keys - - int index = result.getAssertionConsumerServices().size(); - result.getAssertionConsumerServices() - .add( - getAssertionConsumerService( - getEntityBaseURL(), - getEntityAlias(), - false, - index, - "/oauth/token", - "urn:oasis:names:tc:SAML:2.0:bindings:URI" - )); - return result; - } - - @Override - public Collection getBindingsSSO() { - return Collections.singleton("post"); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/ContentSecurityPolicyFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/ContentSecurityPolicyFilter.java index f78bc784f9a..d40a78d8ec8 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/ContentSecurityPolicyFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/ContentSecurityPolicyFilter.java @@ -20,7 +20,7 @@ public class ContentSecurityPolicyFilter extends OncePerRequestFilter { private final String cspHeader; public ContentSecurityPolicyFilter(List allowedScriptSrc) { - this.allowedScriptSrc = unmodifiableSet(new HashSet(allowedScriptSrc)); + this.allowedScriptSrc = unmodifiableSet(new HashSet<>(allowedScriptSrc)); this.cspHeader = cspHeaderValue(); } @@ -38,6 +38,7 @@ protected boolean shouldNotFilter(HttpServletRequest request) { final String requestPath = UaaUrlUtils.getRequestPath(request); final List pathsWithHtmlInlineScripts = Arrays.asList( "/saml/", + "/saml2/", "/login_implicit"); return pathsWithHtmlInlineScripts.stream() diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java index 814fb2df29e..c311bcdcc2a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java @@ -45,7 +45,7 @@ /** * Post processor which injects an additional filter at the head * of each security filter chain. - * + *

    * If the requireHttps property is set, and a non HTTP request is received (as * determined by the absence of the httpsHeader) the filter will either * redirect with a 301 or send an error code to the client. @@ -54,14 +54,13 @@ * those serving browser clients). Clients in this list will also receive an * HSTS response header, as defined in * http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14. - * + *

    * HTTP requests from any other clients will receive a JSON error message. - * + *

    * The filter also wraps calls to the getRemoteAddr to give a more * accurate value for the remote client IP, * making use of the clientAddrHeader if available in the request. * - * * @author Luke Taylor */ @ManagedResource( @@ -107,13 +106,9 @@ public Map, ReasonPhrase> getErrorMap() { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof SecurityFilterChain && !ignore.contains(beanName)) { + if (bean instanceof SecurityFilterChain fc && !ignore.contains(beanName)) { logger.info("Processing security filter chain " + beanName); - SecurityFilterChain fc = (SecurityFilterChain) bean; - - Filter uaaFilter = new HttpsEnforcementFilter(beanName, redirectToHttps.contains(beanName)); - fc.getFilters().add(0, uaaFilter); if (additionalFilters != null) { for (Entry entry : additionalFilters.entrySet()) { int position = entry.getKey().getPosition(fc); @@ -124,6 +119,9 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw } } } + + Filter uaaFilter = new HttpsEnforcementFilter(beanName, redirectToHttps.contains(beanName)); + fc.getFilters().add(0, uaaFilter); } return bean; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java index a4a91eaa67f..f9125dae8f3 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java @@ -14,6 +14,7 @@ package org.cloudfoundry.identity.uaa.user; import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.Getter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -31,7 +32,10 @@ */ public enum UaaAuthority implements GrantedAuthority { - UAA_INVITED("uaa.invited", 1), UAA_ADMIN("uaa.admin", 1), UAA_USER("uaa.user", 0), UAA_NONE("uaa.none", -1); + UAA_INVITED("uaa.invited", 1), + UAA_ADMIN("uaa.admin", 1), + UAA_USER("uaa.user", 0), + UAA_NONE("uaa.none", -1); public static final List ADMIN_AUTHORITIES = List.of(UAA_ADMIN, UAA_USER); @@ -41,6 +45,10 @@ public enum UaaAuthority implements GrantedAuthority { private final int value; + /** + * The name of the type of user, either "uaa.admin" or "uaa.user". + */ + @Getter private final String userType; UaaAuthority(String userType, int value) { @@ -52,15 +60,6 @@ public int value() { return value; } - /** - * The name of the type of user, either "uaa.admin" or "uaa.user". - * - * @return a user type name - */ - public String getUserType() { - return userType; - } - /** * The authority granted by this value (same as user type). * @@ -85,6 +84,6 @@ public static UaaAuthority fromAuthorities(String authorities) { public static GrantedAuthority authority(String value) { return value.equals("uaa.admin") ? UAA_ADMIN : value.contains("uaa.user") ? UAA_USER - : new SimpleGrantedAuthority(value); + : new SimpleGrantedAuthority(value); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java index 2f9a51226b5..806664df65a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import lombok.Setter; import org.cloudfoundry.identity.uaa.authentication.NonStringPassword; import org.springframework.security.core.GrantedAuthority; import org.springframework.util.Assert; @@ -20,6 +22,7 @@ * @author Dave Syer * @author Joel D'sa */ +@Getter @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class UaaUser { @@ -95,22 +98,22 @@ public static UaaUser createWithDefaults(Consumer config) { private final String phoneNumber; + @Setter private Long lastLogonTime; + @Setter private Long previousLogonTime; - public String getZoneId() { - return zoneId; - } - private final String zoneId; private final List authorities; + @Setter private boolean verified = false; private boolean legacyVerificationBehavior = false; + @Setter private boolean passwordChangeRequired; public UaaUser(String username, String password, String email, String givenName, String familyName) { @@ -173,42 +176,10 @@ public UaaUser(UaaUserPrototype prototype) { this.previousLogonTime = prototype.getPreviousLogonTime(); } - public String getId() { - return id; - } - - public String getUsername() { - return username; - } - public String getPassword() { return password.getPassword(); } - public String getEmail() { - return email; - } - - public String getGivenName() { - return givenName; - } - - public String getFamilyName() { - return familyName; - } - - public String getOrigin() { - return origin; - } - - public String getExternalId() { - return externalId; - } - - public String getSalt() { - return salt; - } - public List getAuthorities() { return Optional.ofNullable(authorities).orElseThrow(); } @@ -238,121 +209,109 @@ public String toString() { + ", familyName=" + familyName + "}]"; } - public Date getModified() { - return modified; - } - - public Date getCreated() { - return created; - } - - public Date getPasswordLastModified() { - return passwordLastModified; - } - public UaaUser modifySource(String origin, String externalId) { return new UaaUser( - new UaaUserPrototype() - .withEmail(email) - .withGivenName(givenName) - .withFamilyName(familyName) - .withPhoneNumber(phoneNumber) - .withModified(modified) - .withId(id) - .withUsername(username) - .withPassword(password) - .withAuthorities(authorities) - .withCreated(created) - .withOrigin(origin) - .withExternalId(externalId) - .withVerified(verified) - .withZoneId(zoneId) - .withSalt(salt) - .withPasswordLastModified(passwordLastModified)); + new UaaUserPrototype() + .withEmail(email) + .withGivenName(givenName) + .withFamilyName(familyName) + .withPhoneNumber(phoneNumber) + .withModified(modified) + .withId(id) + .withUsername(username) + .withPassword(password) + .withAuthorities(authorities) + .withCreated(created) + .withOrigin(origin) + .withExternalId(externalId) + .withVerified(verified) + .withZoneId(zoneId) + .withSalt(salt) + .withPasswordLastModified(passwordLastModified)); } public UaaUser modifyEmail(String email) { return new UaaUser( - new UaaUserPrototype() - .withEmail(email) - .withGivenName(givenName) - .withFamilyName(familyName) - .withPhoneNumber(phoneNumber) - .withModified(modified) - .withId(id) - .withUsername(username) - .withPassword(password) - .withAuthorities(authorities) - .withCreated(created) - .withOrigin(origin) - .withExternalId(externalId) - .withVerified(verified) - .withZoneId(zoneId) - .withSalt(salt) - .withPasswordLastModified(passwordLastModified)); + new UaaUserPrototype() + .withEmail(email) + .withGivenName(givenName) + .withFamilyName(familyName) + .withPhoneNumber(phoneNumber) + .withModified(modified) + .withId(id) + .withUsername(username) + .withPassword(password) + .withAuthorities(authorities) + .withCreated(created) + .withOrigin(origin) + .withExternalId(externalId) + .withVerified(verified) + .withZoneId(zoneId) + .withSalt(salt) + .withPasswordLastModified(passwordLastModified)); } public UaaUser modifyOrigin(String origin) { return new UaaUser( - new UaaUserPrototype() - .withEmail(email) - .withGivenName(givenName) - .withFamilyName(familyName) - .withPhoneNumber(phoneNumber) - .withModified(modified) - .withId(id) - .withUsername(username) - .withPassword(password) - .withAuthorities(authorities) - .withCreated(created) - .withOrigin(origin) - .withExternalId(externalId) - .withVerified(verified) - .withZoneId(zoneId) - .withSalt(salt) - .withPasswordLastModified(passwordLastModified)); + new UaaUserPrototype() + .withEmail(email) + .withGivenName(givenName) + .withFamilyName(familyName) + .withPhoneNumber(phoneNumber) + .withModified(modified) + .withId(id) + .withUsername(username) + .withPassword(password) + .withAuthorities(authorities) + .withCreated(created) + .withOrigin(origin) + .withExternalId(externalId) + .withVerified(verified) + .withZoneId(zoneId) + .withSalt(salt) + .withPasswordLastModified(passwordLastModified)); } public UaaUser modifyId(String id) { return new UaaUser( - new UaaUserPrototype() - .withEmail(email) - .withGivenName(givenName) - .withFamilyName(familyName) - .withPhoneNumber(phoneNumber) - .withModified(modified) - .withId(id) - .withUsername(username) - .withPassword(password) - .withAuthorities(authorities) - .withCreated(created) - .withOrigin(origin) - .withExternalId(externalId) - .withVerified(verified) - .withZoneId(zoneId) - .withSalt(salt) - .withPasswordLastModified(passwordLastModified)); + new UaaUserPrototype() + .withEmail(email) + .withGivenName(givenName) + .withFamilyName(familyName) + .withPhoneNumber(phoneNumber) + .withModified(modified) + .withId(id) + .withUsername(username) + .withPassword(password) + .withAuthorities(authorities) + .withCreated(created) + .withOrigin(origin) + .withExternalId(externalId) + .withVerified(verified) + .withZoneId(zoneId) + .withSalt(salt) + .withPasswordLastModified(passwordLastModified)); } public UaaUser modifyUsername(String username) { return new UaaUser( - new UaaUserPrototype() - .withEmail(email) - .withGivenName(givenName) - .withFamilyName(familyName) - .withPhoneNumber(phoneNumber) - .withModified(modified) - .withId(id) - .withUsername(username) - .withPassword(password) - .withAuthorities(authorities) - .withCreated(created) - .withOrigin(origin) - .withExternalId(externalId) - .withVerified(verified) - .withZoneId(zoneId) - .withSalt(salt) - .withPasswordLastModified(passwordLastModified)); + new UaaUserPrototype() + .withEmail(email) + .withGivenName(givenName) + .withFamilyName(familyName) + .withPhoneNumber(phoneNumber) + .withModified(modified) + .withId(id) + .withUsername(username) + .withPassword(password) + .withAuthorities(authorities) + .withCreated(created) + .withOrigin(origin) + .withExternalId(externalId) + .withVerified(verified) + .withZoneId(zoneId) + .withSalt(salt) + .withPasswordLastModified(passwordLastModified)); } public UaaUser modifyAttributes(String email, @@ -379,44 +338,4 @@ public UaaUser modifyAttributes(String email, .withSalt(salt) .withPasswordLastModified(passwordLastModified)); } - - public boolean isVerified() { - return verified; - } - - public void setVerified(boolean verified) { - this.verified = verified; - } - - public String getPhoneNumber() { - return phoneNumber; - } - - public boolean isLegacyVerificationBehavior() { - return legacyVerificationBehavior; - } - - public boolean isPasswordChangeRequired() { - return passwordChangeRequired; - } - - public void setPasswordChangeRequired(boolean passwordChangeRequired) { - this.passwordChangeRequired = passwordChangeRequired; - } - - public Long getLastLogonTime() { - return lastLogonTime; - } - - public void setLastLogonTime(Long lastLogonTime) { - this.lastLogonTime = lastLogonTime; - } - - public Long getPreviousLogonTime() { - return previousLogonTime; - } - - public void setPreviousLogonTime(Long previousLogonTime) { - this.previousLogonTime = previousLogonTime; - } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java index f14f6ba9af6..95314bc35cd 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java @@ -13,12 +13,14 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.user; +import lombok.Getter; import org.cloudfoundry.identity.uaa.authentication.NonStringPassword; import org.springframework.security.core.GrantedAuthority; import java.util.Date; import java.util.List; +@Getter public final class UaaUserPrototype { private String id = "NaN"; @@ -66,42 +68,32 @@ public UaaUserPrototype() { public UaaUserPrototype(UaaUser user) { withVerified(user.isVerified()) - .withLegacyVerificationBehavior(user.isLegacyVerificationBehavior()) - .withEmail(user.getEmail()) - .withUsername(user.getUsername()) - .withPhoneNumber(user.getPhoneNumber()) - .withId(user.getId()) - .withOrigin(user.getOrigin()) - .withZoneId(user.getZoneId()) - .withAuthorities(user.getAuthorities()) - .withPassword(user.getPassword()) - .withFamilyName(user.getFamilyName()) - .withGivenName(user.getGivenName()) - .withExternalId(user.getExternalId()) - .withPasswordLastModified(user.getPasswordLastModified()) - .withLastLogonSuccess(user.getLastLogonTime()) - .withPreviousLogonSuccess(user.getPreviousLogonTime()) - .withSalt(user.getSalt()) - .withCreated(user.getCreated()) - .withModified(user.getModified()) - .withPasswordChangeRequired(user.isPasswordChangeRequired()); - - } - - public String getId() { - return id; + .withLegacyVerificationBehavior(user.isLegacyVerificationBehavior()) + .withEmail(user.getEmail()) + .withUsername(user.getUsername()) + .withPhoneNumber(user.getPhoneNumber()) + .withId(user.getId()) + .withOrigin(user.getOrigin()) + .withZoneId(user.getZoneId()) + .withAuthorities(user.getAuthorities()) + .withPassword(user.getPassword()) + .withFamilyName(user.getFamilyName()) + .withGivenName(user.getGivenName()) + .withExternalId(user.getExternalId()) + .withPasswordLastModified(user.getPasswordLastModified()) + .withLastLogonSuccess(user.getLastLogonTime()) + .withPreviousLogonSuccess(user.getPreviousLogonTime()) + .withSalt(user.getSalt()) + .withCreated(user.getCreated()) + .withModified(user.getModified()) + .withPasswordChangeRequired(user.isPasswordChangeRequired()); } - public UaaUserPrototype withId(String id) { this.id = id; return this; } - public String getUsername() { - return username; - } - public UaaUserPrototype withUsername(String username) { this.username = username; return this; @@ -119,148 +111,87 @@ UaaUserPrototype withPassword(NonStringPassword password) { this.password = password; return this; } + public UaaUserPrototype withPassword(String password) { this.password = new NonStringPassword(password); return this; } - public String getEmail() { - return email; - } - public UaaUserPrototype withEmail(String email) { this.email = email; return this; } - public String getGivenName() { - return givenName; - } - public UaaUserPrototype withGivenName(String givenName) { this.givenName = givenName; return this; } - public String getFamilyName() { - return familyName; - } - public UaaUserPrototype withFamilyName(String familyName) { this.familyName = familyName; return this; } - public String getPhoneNumber() { - return phoneNumber; - } - public UaaUserPrototype withPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; return this; } - public Date getCreated() { - return created; - } - public UaaUserPrototype withCreated(Date created) { this.created = created; return this; } - public Date getModified() { - return modified; - } - public UaaUserPrototype withModified(Date modified) { this.modified = modified; return this; } - public String getOrigin() { - return origin; - } - public UaaUserPrototype withOrigin(String origin) { this.origin = origin; return this; } - public String getExternalId() { - return externalId; - } - public UaaUserPrototype withExternalId(String externalId) { this.externalId = externalId; return this; } - public String getSalt() { - return salt; - } - public UaaUserPrototype withSalt(String salt) { this.salt = salt; return this; } - public Date getPasswordLastModified() { - return passwordLastModified; - } - public UaaUserPrototype withPasswordLastModified(Date passwordLastModified) { this.passwordLastModified = passwordLastModified; return this; } - public String getZoneId() { - return zoneId; - } - public UaaUserPrototype withZoneId(String zoneId) { this.zoneId = zoneId; return this; } - public List getAuthorities() { - return authorities; - } - public UaaUserPrototype withAuthorities(List authorities) { this.authorities = authorities; return this; } - public boolean isVerified() { - return verified; - } - public UaaUserPrototype withVerified(boolean verified) { this.verified = verified; return this; } - public boolean isLegacyVerificationBehavior() { return legacyVerificationBehavior; } - public UaaUserPrototype withLegacyVerificationBehavior(boolean legacyVerificationBehavior) { this.legacyVerificationBehavior = legacyVerificationBehavior; return this; } - public boolean isPasswordChangeRequired() { - return passwordChangeRequired; - } - public UaaUserPrototype withPasswordChangeRequired(boolean requiresPasswordChange) { this.passwordChangeRequired = requiresPasswordChange; return this; } - public Long getLastLogonTime() { - return lastLogonTime; - } - public UaaUserPrototype withLastLogonSuccess(Long lastLogonTime) { this.lastLogonTime = lastLogonTime; return this; @@ -270,8 +201,4 @@ public UaaUserPrototype withPreviousLogonSuccess(Long previousLogonTime) { this.previousLogonTime = previousLogonTime; return this; } - - public Long getPreviousLogonTime() { - return previousLogonTime; - } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java index 8914ec68ab1..7618f46e188 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/KeyWithCert.java @@ -1,5 +1,6 @@ package org.cloudfoundry.identity.uaa.util; +import lombok.Getter; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; @@ -10,6 +11,7 @@ import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; +import org.cloudfoundry.identity.uaa.saml.SamlKey; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -18,17 +20,21 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.Base64; import static org.cloudfoundry.identity.uaa.oauth.jwt.JwtAlgorithms.DEFAULT_RSA; +@Getter public class KeyWithCert { - private X509Certificate certificate; - private PrivateKey privateKey; + private final X509Certificate certificate; + private final PrivateKey privateKey; public KeyWithCert(String encodedCertificate) throws CertificateException { certificate = loadCertificate(encodedCertificate); + privateKey = null; } public KeyWithCert(String encodedPrivateKey, String passphrase, String encodedCertificate) throws CertificateException { @@ -37,7 +43,6 @@ public KeyWithCert(String encodedPrivateKey, String passphrase, String encodedCe } privateKey = loadPrivateKey(encodedPrivateKey, passphrase); - certificate = loadCertificate(encodedCertificate); if (!keysMatch(certificate.getPublicKey(), privateKey)) { @@ -45,12 +50,16 @@ public KeyWithCert(String encodedPrivateKey, String passphrase, String encodedCe } } - public X509Certificate getCertificate() { - return certificate; - } + public static KeyWithCert fromSamlKey(SamlKey samlKey) throws CertificateException { + if (samlKey == null) { + return null; + } - public PrivateKey getPrivateKey() { - return privateKey; + if (samlKey.getKey() == null) { + return new KeyWithCert(samlKey.getCertificate()); + } + + return new KeyWithCert(samlKey.getKey(), samlKey.getPassphrase(), samlKey.getCertificate()); } private boolean keysMatch(PublicKey publicKey, PrivateKey privateKey) { @@ -85,22 +94,21 @@ private static String getJavaAlgorithm(String publicKeyAlgorithm) { return publicKeyAlgorithm; } - private PrivateKey loadPrivateKey(String encodedPrivateKey, String passphrase) throws CertificateException { + private static PrivateKey loadPrivateKey(String encodedPrivateKey, String passphrase) throws CertificateException { PrivateKey privateKey = null; try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(encodedPrivateKey.getBytes())))) { JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleFipsProvider.PROVIDER_NAME); Object object = pemParser.readObject(); - if (object instanceof PEMEncryptedKeyPair) { + if (object instanceof PEMEncryptedKeyPair pemEncryptedKeyPair) { PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(passphrase.toCharArray()); - KeyPair keyPair = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)); + KeyPair keyPair = converter.getKeyPair(pemEncryptedKeyPair.decryptKeyPair(decProv)); privateKey = keyPair.getPrivate(); - } else if (object instanceof PEMKeyPair) { - KeyPair keyPair = converter.getKeyPair((PEMKeyPair) object); + } else if (object instanceof PEMKeyPair pemKeyPair) { + KeyPair keyPair = converter.getKeyPair(pemKeyPair); privateKey = keyPair.getPrivate(); - } else if (object instanceof PrivateKeyInfo) { - PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) object; + } else if (object instanceof PrivateKeyInfo privateKeyInfo) { privateKey = converter.getPrivateKey(privateKeyInfo); } } catch (IOException ex) { @@ -114,13 +122,13 @@ private PrivateKey loadPrivateKey(String encodedPrivateKey, String passphrase) t return privateKey; } - private X509Certificate loadCertificate(String encodedCertificate) throws CertificateException { + private static X509Certificate loadCertificate(String encodedCertificate) throws CertificateException { X509Certificate certificate; try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(encodedCertificate.getBytes())))) { Object object = pemParser.readObject(); - if (object instanceof X509CertificateHolder) { - certificate = new JcaX509CertificateConverter().setProvider(BouncyCastleFipsProvider.PROVIDER_NAME).getCertificate((X509CertificateHolder) object); + if (object instanceof X509CertificateHolder x509CertificateHolder) { + certificate = new JcaX509CertificateConverter().setProvider(BouncyCastleFipsProvider.PROVIDER_NAME).getCertificate(x509CertificateHolder); } else { throw new CertificateException("Unsupported certificate type, not an X509CertificateHolder."); } @@ -134,4 +142,8 @@ private X509Certificate loadCertificate(String encodedCertificate) throws Certif return certificate; } + + public String getEncodedCertificate() throws CertificateEncodingException { + return new String(Base64.getEncoder().encode(certificate.getEncoded())); + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/SessionUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/SessionUtils.java index 86e93be742e..a40069295a4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/SessionUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/SessionUtils.java @@ -16,6 +16,7 @@ public final class SessionUtils { // org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache.DEFAULT_SAVED_REQUEST_ATTR // public static final String SAVED_REQUEST_SESSION_ATTRIBUTE = "SPRING_SECURITY_SAVED_REQUEST"; + // shadows org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY // org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME // org.springframework.session.jdbc.JdbcIndexedSessionRepository.SPRING_SECURITY_CONTEXT diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/velocity/VelocityFactory.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/velocity/VelocityFactory.java deleted file mode 100644 index 2262fd6e95d..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/velocity/VelocityFactory.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.util.velocity; - -import org.apache.velocity.app.VelocityEngine; -import org.apache.velocity.runtime.RuntimeConstants; - -public class VelocityFactory { - - public static VelocityEngine getEngine() { - - try { - VelocityEngine velocityEngine = new VelocityEngine(); - velocityEngine.setProperty(RuntimeConstants.ENCODING_DEFAULT, "UTF-8"); - //velocityEngine.setProperty(RuntimeConstants.OUTPUT_ENCODING, "UTF-8"); - velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); - velocityEngine.setProperty("classpath.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); - //velocityEngine.setProperty(VelocityEngine.RUNTIME_LOG_LOGSYSTEM, new SLF4JLogChute()); - - velocityEngine.init(); - return velocityEngine; - } catch (Exception e) { - throw new RuntimeException("Error configuring velocity", e); - } - - } - -} \ No newline at end of file diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandler.java index 17e87a7f092..b44bec90422 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandler.java @@ -15,27 +15,58 @@ package org.cloudfoundry.identity.uaa.web; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.savedrequest.SavedRequest; +import org.springframework.util.StringUtils; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +@Slf4j public class UaaSavedRequestAwareAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { public static final String URI_OVERRIDE_ATTRIBUTE = "override.redirect_uri"; - public static final String FORM_REDIRECT_PARAMETER = "form_redirect_uri"; - private static Logger logger = LoggerFactory.getLogger(UaaSavedRequestAwareAuthenticationSuccessHandler.class); + private final RequestCache requestCache = new HttpSessionRequestCache(); + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { + SavedRequest savedRequest = this.requestCache.getRequest(request, response); + if (savedRequest == null) { + String relayState = request.getParameter(Saml2ParameterNames.RELAY_STATE); + if (relayState != null && UaaUrlUtils.isUrl(relayState)) { + log.debug("Redirecting to relayState URI: {}", relayState); + this.getRedirectStrategy().sendRedirect(request, response, relayState); + } else { + super.onAuthenticationSuccess(request, response, authentication); + } + } else { + String targetUrlParameter = this.getTargetUrlParameter(); + if (!this.isAlwaysUseDefaultTargetUrl() && (targetUrlParameter == null || !StringUtils.hasText(request.getParameter(targetUrlParameter)))) { + this.clearAuthenticationAttributes(request); + String targetUrl = savedRequest.getRedirectUrl(); + this.getRedirectStrategy().sendRedirect(request, response, targetUrl); + } else { + this.requestCache.removeRequest(request, response); + super.onAuthenticationSuccess(request, response, authentication); + } + } + } @Override public String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) { Object redirectAttribute = request.getAttribute(URI_OVERRIDE_ATTRIBUTE); String redirectFormParam = request.getParameter(FORM_REDIRECT_PARAMETER); - if (redirectAttribute !=null) { - logger.debug("Returning redirectAttribute saved URI:"+redirectAttribute); + if (redirectAttribute != null) { + log.debug("Returning redirectAttribute saved URI: {}", redirectAttribute); return (String) redirectAttribute; } else if (UaaUrlUtils.uriHasMatchingHost(redirectFormParam, request.getServerName())) { return redirectFormParam; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolder.java index 5efed55ac6b..9036ae6c291 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolder.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolder.java @@ -1,48 +1,40 @@ package org.cloudfoundry.identity.uaa.zone; +import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManager; import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManagerFactory; -import org.springframework.security.saml.key.KeyManager; + +import java.util.Optional; /** - * @Deprecated Use {@link org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager} instead + * Handles getting and caching of the current IdentityZone and its SamlKeyManager within ThreadLocal storage. + * + * @deprecated Use {@link org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager} instead, which still uses this class as a utility. */ -@Deprecated +@Deprecated(since = "4.29.0") public class IdentityZoneHolder { + private IdentityZoneHolder() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + private static IdentityZoneProvisioning provisioning; + private static SamlKeyManagerFactory samlKeyManagerFactory; public static void setProvisioning(IdentityZoneProvisioning provisioning) { IdentityZoneHolder.provisioning = provisioning; } - private static SamlKeyManagerFactory samlKeyManagerFactory = new SamlKeyManagerFactory(); + public static void setSamlKeyManagerFactory(SamlKeyManagerFactory samlKeyManagerFactory) { + IdentityZoneHolder.samlKeyManagerFactory = samlKeyManagerFactory; + } - private static final ThreadLocal IDENTITY_ZONE_THREAD_LOCAL = InheritableThreadLocal - .withInitial(() -> getUaaZone(provisioning)); + private static final ThreadLocal IDENTITY_ZONE_THREAD_LOCAL = + ThreadLocal.withInitial(() -> getUaaZone(provisioning)); public static IdentityZone get() { return IDENTITY_ZONE_THREAD_LOCAL.get(); } - private static final ThreadLocal KEY_MANAGER_THREAD_LOCAL = InheritableThreadLocal.withInitial(() -> null); - - public static KeyManager getSamlSPKeyManager() { - KeyManager keyManager = KEY_MANAGER_THREAD_LOCAL.get(); - if (keyManager != null) { - return keyManager; - } - - keyManager = samlKeyManagerFactory.getKeyManager(IDENTITY_ZONE_THREAD_LOCAL.get().getConfig().getSamlConfig()); - if (keyManager != null) { - KEY_MANAGER_THREAD_LOCAL.set(keyManager); - return keyManager; - } - - keyManager = samlKeyManagerFactory.getKeyManager(getUaaZone(provisioning).getConfig().getSamlConfig()); - KEY_MANAGER_THREAD_LOCAL.set(keyManager); - return keyManager; - } - public static IdentityZone getUaaZone() { return getUaaZone(provisioning); } @@ -56,7 +48,7 @@ private static IdentityZone getUaaZone(IdentityZoneProvisioning provisioning) { public static void set(IdentityZone zone) { IDENTITY_ZONE_THREAD_LOCAL.set(zone); - KEY_MANAGER_THREAD_LOCAL.set(null); + KEY_MANAGER_THREAD_LOCAL.remove(); } public static void clear() { @@ -72,13 +64,55 @@ public static String getCurrentZoneId() { return IDENTITY_ZONE_THREAD_LOCAL.get().getId(); } + private static final ThreadLocal KEY_MANAGER_THREAD_LOCAL = + ThreadLocal.withInitial(() -> null); + + public static SamlKeyManager getSamlKeyManager() { + SamlKeyManager keyManager = KEY_MANAGER_THREAD_LOCAL.get(); + if (keyManager != null) { + return keyManager; + } + + var optionalZoneSamlConfig = Optional.ofNullable(get()) + .map(IdentityZone::getConfig) + .map(IdentityZoneConfiguration::getSamlConfig); + boolean zoneHasKeys = optionalZoneSamlConfig.map(SamlConfig::getKeys) + .map(k -> !k.isEmpty()) + .orElse(false); + + if (zoneHasKeys) { + keyManager = samlKeyManagerFactory.getKeyManager(optionalZoneSamlConfig.orElse(null)); + setSamlKeyManager(keyManager); + return keyManager; + } + + var optionalUaaSamlConfig = Optional.ofNullable(getUaaZone(provisioning)) + .map(IdentityZone::getConfig) + .map(IdentityZoneConfiguration::getSamlConfig) + .orElse(null); + + keyManager = samlKeyManagerFactory.getKeyManager(optionalUaaSamlConfig); + setSamlKeyManager(keyManager); + return keyManager; + } + + private static void setSamlKeyManager(SamlKeyManager keyManager) { + KEY_MANAGER_THREAD_LOCAL.set(keyManager); + } + + /** + * Utility class to initialize the IdentityZoneHolder with the necessary dependencies. + * Work around for the fact that IdentityZoneHolder is a static utility class and cannot be instantiated. + */ public static class Initializer { - public Initializer(IdentityZoneProvisioning provisioning) { + public Initializer(IdentityZoneProvisioning provisioning, SamlKeyManagerFactory samlKeyManagerFactory) { IdentityZoneHolder.setProvisioning(provisioning); + IdentityZoneHolder.setSamlKeyManagerFactory(samlKeyManagerFactory); } public void reset() { IdentityZoneHolder.setProvisioning(null); + IdentityZoneHolder.setSamlKeyManagerFactory(null); } } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java index 02d6ca7d093..38890eb1dec 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java @@ -30,7 +30,6 @@ * If the X-Identity-Zone-Id header is set and the user has a scope * of zones.<id>.admin, this filter switches the IdentityZone in the IdentityZoneHolder * to the one in the header. - * */ public class IdentityZoneSwitchingFilter extends OncePerRequestFilter { @@ -47,10 +46,9 @@ public IdentityZoneSwitchingFilter(IdentityZoneProvisioning dao) { protected OAuth2Authentication getAuthenticationForZone(String identityZoneId, HttpServletRequest servletRequest) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if(!(authentication instanceof OAuth2Authentication)) { + if (!(authentication instanceof OAuth2Authentication oa)) { return null; } - OAuth2Authentication oa = (OAuth2Authentication) authentication; Object oaDetails = oa.getDetails(); @@ -69,28 +67,27 @@ protected OAuth2Authentication getAuthenticationForZone(String identityZoneId, H } } request = new OAuth2Request( - request.getRequestParameters(), - request.getClientId(), - UaaStringUtils.getAuthoritiesFromStrings(clientAuthorities), - request.isApproved(), - clientScopes, - request.getResourceIds(), - request.getRedirectUri(), - request.getResponseTypes(), - request.getExtensions() - ); - - - UaaAuthentication userAuthentication = (UaaAuthentication)oa.getUserAuthentication(); - if (userAuthentication!=null) { + request.getRequestParameters(), + request.getClientId(), + UaaStringUtils.getAuthoritiesFromStrings(clientAuthorities), + request.isApproved(), + clientScopes, + request.getResourceIds(), + request.getRedirectUri(), + request.getResponseTypes(), + request.getExtensions() + ); + + UaaAuthentication userAuthentication = (UaaAuthentication) oa.getUserAuthentication(); + if (userAuthentication != null) { userAuthentication = new UaaAuthentication( - userAuthentication.getPrincipal(), - null, - UaaStringUtils.getAuthoritiesFromStrings(clientScopes), - new UaaAuthenticationDetails(servletRequest), - true, userAuthentication.getAuthenticatedTime()); + userAuthentication.getPrincipal(), + null, + UaaStringUtils.getAuthoritiesFromStrings(clientScopes), + new UaaAuthenticationDetails(servletRequest), + true, userAuthentication.getAuthenticatedTime()); } - oa = new UaaOauth2Authentication(((UaaOauth2Authentication)oa).getTokenValue(), IdentityZoneHolder.get().getId(), request, userAuthentication); + oa = new UaaOauth2Authentication(((UaaOauth2Authentication) oa).getTokenValue(), IdentityZoneHolder.get().getId(), request, userAuthentication); oa.setDetails(oaDetails); return oa; } @@ -100,7 +97,7 @@ protected String stripPrefix(String s, String identityZoneId) { return s; } //dont touch the zones.{zone.id}.admin scope - String replace = ZONES_ZONE_ID_PREFIX+identityZoneId+"."; + String replace = ZONES_ZONE_ID_PREFIX + identityZoneId + "."; for (String scope : zoneScopestoNotStripPrefix) { if (s.equals(replace + scope)) { return s; @@ -108,20 +105,16 @@ protected String stripPrefix(String s, String identityZoneId) { } //replace zones.. - if (s.startsWith(replace)) { return s.substring(replace.length()); } return s; } - - @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String identityZoneIdFromHeader = request.getHeader(HEADER); String identityZoneSubDomainFromHeader = request.getHeader(SUBDOMAIN_HEADER); @@ -141,7 +134,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (IdentityZoneHolder.isUaa() && oAuth2Authentication != null && !oAuth2Authentication.getOAuth2Request().getScope().isEmpty()) { SecurityContextHolder.getContext().setAuthentication(oAuth2Authentication); } else { - response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not authorized to switch to IdentityZone with id "+identityZoneId); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not authorized to switch to IdentityZone with id " + identityZoneId); return; } @@ -167,5 +160,4 @@ private IdentityZone validateIdentityZone(String identityZoneId, String identity } return identityZone; } - } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAware.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAware.java new file mode 100644 index 00000000000..8a4326f0708 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAware.java @@ -0,0 +1,13 @@ +package org.cloudfoundry.identity.uaa.zone; + +import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManager; + +public interface ZoneAware { + default IdentityZone retrieveZone() { + return IdentityZoneHolder.get(); + } + + default SamlKeyManager retrieveKeyManager() { + return IdentityZoneHolder.getSamlKeyManager(); + } +} diff --git a/server/src/main/resources/dummy-saml-idp-metadata.xml b/server/src/main/resources/dummy-saml-idp-metadata.xml new file mode 100644 index 00000000000..064c6fd6e36 --- /dev/null +++ b/server/src/main/resources/dummy-saml-idp-metadata.xml @@ -0,0 +1,16 @@ + + + + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + + + diff --git a/server/src/main/resources/spring/login-ui.xml b/server/src/main/resources/spring/login-ui.xml index 744205f1c15..e6644b71263 100644 --- a/server/src/main/resources/spring/login-ui.xml +++ b/server/src/main/resources/spring/login-ui.xml @@ -131,23 +131,10 @@ - - - - - - - - - - - - - @@ -164,27 +151,6 @@ - - - - - - - - - - - - - JSESSIONID - - - - - - - - @@ -224,14 +190,16 @@ + httpsHeaderFilter"/> + + + - + @@ -255,12 +223,13 @@ + - + - + - + @@ -305,7 +274,6 @@ - @@ -318,15 +286,17 @@ + class="org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthLogoutSuccessHandler"> + - - - - + + + + @@ -416,10 +386,6 @@ - - - @@ -497,7 +463,7 @@ - + diff --git a/server/src/main/resources/templates/web/login.html b/server/src/main/resources/templates/web/login.html index 1227703d992..59accc0c710 100644 --- a/server/src/main/resources/templates/web/login.html +++ b/server/src/main/resources/templates/web/login.html @@ -55,7 +55,7 @@

    or sign in with:

    diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/TestClassNullifier.java b/server/src/test/java/org/cloudfoundry/identity/uaa/TestClassNullifier.java index e536cfd9a35..3c641f36335 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/TestClassNullifier.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/TestClassNullifier.java @@ -15,26 +15,26 @@ package org.cloudfoundry.identity.uaa; import org.cloudfoundry.identity.uaa.util.NullifyFields; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.springframework.web.servlet.view.InternalResourceViewResolver; public class TestClassNullifier { private volatile static Class clazz; - @Before + @BeforeEach public void trackClass() { clazz = this.getClass(); } - @After + @AfterEach public void nullifyInstanceFields() throws Exception { NullifyFields.nullifyFields(this.getClass(), this, false); } - @AfterClass + @AfterAll public static void nullifyClassFields() throws Exception { NullifyFields.nullifyFields(clazz, null, true); clazz = null; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/BackwardsCompatibleTokenEndpointAuthenticationFilterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/BackwardsCompatibleTokenEndpointAuthenticationFilterTest.java index 3f26efd228b..b1fee35681f 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/BackwardsCompatibleTokenEndpointAuthenticationFilterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/BackwardsCompatibleTokenEndpointAuthenticationFilterTest.java @@ -23,12 +23,16 @@ import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthAuthenticationManager; import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthCodeToken; +import org.cloudfoundry.identity.uaa.provider.saml.Saml2BearerGrantAuthenticationConverter; import org.cloudfoundry.identity.uaa.util.SessionUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; @@ -36,7 +40,6 @@ import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.saml.SAMLProcessingFilter; import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.FilterChain; @@ -44,15 +47,12 @@ import java.util.Map; import static java.util.Optional.ofNullable; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.oauth.TokenTestSupport.OPENID; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GRANT_TYPE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.CLIENT_AUTH_NONE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_JWT_BEARER; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_SAML2_BEARER; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.same; @@ -65,46 +65,45 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -public class BackwardsCompatibleTokenEndpointAuthenticationFilterTest { - +@ExtendWith(MockitoExtension.class) +class BackwardsCompatibleTokenEndpointAuthenticationFilterTest { + @Mock private AuthenticationManager passwordAuthManager; + @Mock private OAuth2RequestFactory requestFactory; - private SAMLProcessingFilter samlAuthFilter; + @Mock + private Saml2BearerGrantAuthenticationConverter saml2BearerGrantAuthenticationConverter; + @Mock private ExternalOAuthAuthenticationManager externalOAuthAuthenticationManager; + + @Mock + private FilterChain chain; + @Mock + private AuthenticationEntryPoint entryPoint; + private BackwardsCompatibleTokenEndpointAuthenticationFilter filter; private MockHttpServletRequest request; private MockHttpServletResponse response; - private FilterChain chain; - private AuthenticationEntryPoint entryPoint; private TokenTestSupport support; - @Before + @BeforeEach public void setUp() { - - passwordAuthManager = mock(AuthenticationManager.class); - requestFactory = mock(OAuth2RequestFactory.class); - samlAuthFilter = mock(SAMLProcessingFilter.class); - externalOAuthAuthenticationManager = mock(ExternalOAuthAuthenticationManager.class); - filter = spy( - new BackwardsCompatibleTokenEndpointAuthenticationFilter( - passwordAuthManager, - requestFactory, - samlAuthFilter, - externalOAuthAuthenticationManager - ) + new BackwardsCompatibleTokenEndpointAuthenticationFilter( + passwordAuthManager, + requestFactory, + saml2BearerGrantAuthenticationConverter, + externalOAuthAuthenticationManager + ) ); - entryPoint = mock(AuthenticationEntryPoint.class); filter.setAuthenticationEntryPoint(entryPoint); - request = new MockHttpServletRequest("POST", "/oauth/token"); response = new MockHttpServletResponse(); - chain = mock(FilterChain.class); } - @After + @AfterEach public void tearDown() { SecurityContextHolder.clearContext(); IdentityZoneHolder.clear(); @@ -112,7 +111,7 @@ public void tearDown() { } @Test - public void password_expired() throws Exception { + void passwordExpired() throws Exception { UaaAuthentication uaaAuthentication = mock(UaaAuthentication.class); when(uaaAuthentication.isAuthenticated()).thenReturn(true); MockHttpSession httpSession = new MockHttpSession(); @@ -124,11 +123,10 @@ public void password_expired() throws Exception { request.addParameter("password", "koala"); filter.doFilter(request, response, chain); verify(entryPoint, times(1)).commence(same(request), same(response), any(PasswordChangeRequiredException.class)); - } @Test - public void attempt_password_authentication() throws Exception { + void attemptPasswordAuthentication() throws Exception { request.addParameter(GRANT_TYPE, "password"); request.addParameter("username", "marissa"); request.addParameter("password", "koala"); @@ -150,7 +148,7 @@ public void attempt_password_authentication() throws Exception { } @Test - public void attempt_password_authentication_with_details() throws Exception { + void attemptPasswordAuthenticationWithDetails() throws Exception { request.addParameter(GRANT_TYPE, "password"); request.addParameter("username", "marissa"); request.addParameter("password", "koala"); @@ -172,33 +170,38 @@ public void attempt_password_authentication_with_details() throws Exception { } @Test - public void attempt_saml_assertion_authentication() throws Exception { + void attemptSamlAssertionAuthentication() throws Exception { request.addParameter(GRANT_TYPE, GRANT_TYPE_SAML2_BEARER); request.addParameter("assertion", "saml-assertion-value-here"); filter.doFilter(request, response, chain); + verify(filter, times(1)).attemptTokenAuthentication(same(request), same(response)); - verify(samlAuthFilter, times(1)).attemptAuthentication(same(request), same(response)); + verify(saml2BearerGrantAuthenticationConverter, times(1)).convert(same(request)); verifyNoInteractions(passwordAuthManager); verifyNoInteractions(externalOAuthAuthenticationManager); + verify(saml2BearerGrantAuthenticationConverter, times(1)).convert(same(request)); } @Test - public void saml_assertion_missing() throws Exception { + void samlAssertionMissing() throws Exception { request.addParameter(GRANT_TYPE, GRANT_TYPE_SAML2_BEARER); filter.doFilter(request, response, chain); + verify(filter, times(1)).attemptTokenAuthentication(same(request), same(response)); verifyNoInteractions(externalOAuthAuthenticationManager); verifyNoInteractions(passwordAuthManager); verifyNoInteractions(externalOAuthAuthenticationManager); + verifyNoInteractions(saml2BearerGrantAuthenticationConverter); + ArgumentCaptor exceptionArgumentCaptor = ArgumentCaptor.forClass(AuthenticationException.class); verify(entryPoint, times(1)).commence(same(request), same(response), exceptionArgumentCaptor.capture()); - assertNotNull(exceptionArgumentCaptor.getValue()); - assertEquals("SAML Assertion is missing", exceptionArgumentCaptor.getValue().getMessage()); - assertTrue(exceptionArgumentCaptor.getValue() instanceof InsufficientAuthenticationException); + assertThat(exceptionArgumentCaptor.getValue()) + .hasMessage("SAML Assertion is missing") + .isInstanceOf(InsufficientAuthenticationException.class); } @Test - public void attempt_jwt_token_authentication() throws Exception { + void attemptJwtTokenAuthentication() throws Exception { support = new TokenTestSupport(null, null); String idToken = support.getIdTokenAsString(Collections.singletonList(OPENID)); request.addParameter(GRANT_TYPE, GRANT_TYPE_JWT_BEARER); @@ -209,12 +212,12 @@ public void attempt_jwt_token_authentication() throws Exception { verify(externalOAuthAuthenticationManager, times(1)).authenticate(authenticateData.capture()); verifyNoInteractions(passwordAuthManager); verifyNoMoreInteractions(externalOAuthAuthenticationManager); - assertEquals(idToken, authenticateData.getValue().getIdToken()); - assertNull(authenticateData.getValue().getOrigin()); + assertThat(authenticateData.getValue().getIdToken()).isEqualTo(idToken); + assertThat(authenticateData.getValue().getOrigin()).isNull(); } @Test - public void jwt_assertion_missing() throws Exception { + void jwtAssertionMissing() throws Exception { request.addParameter(GRANT_TYPE, GRANT_TYPE_JWT_BEARER); filter.doFilter(request, response, chain); verify(filter, times(1)).attemptTokenAuthentication(same(request), same(response)); @@ -223,9 +226,7 @@ public void jwt_assertion_missing() throws Exception { verifyNoInteractions(externalOAuthAuthenticationManager); ArgumentCaptor exceptionArgumentCaptor = ArgumentCaptor.forClass(AuthenticationException.class); verify(entryPoint, times(1)).commence(same(request), same(response), exceptionArgumentCaptor.capture()); - assertNotNull(exceptionArgumentCaptor.getValue()); - assertEquals("Assertion is missing", exceptionArgumentCaptor.getValue().getMessage()); - assertTrue(exceptionArgumentCaptor.getValue() instanceof InsufficientAuthenticationException); + assertThat(exceptionArgumentCaptor.getValue()).isInstanceOf(InsufficientAuthenticationException.class); + assertThat(exceptionArgumentCaptor.getValue().getMessage()).isEqualTo("Assertion is missing"); } - -} \ No newline at end of file +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/MalformedSamlResponseLoggerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/MalformedSamlResponseLoggerTest.java new file mode 100644 index 00000000000..07495d8308e --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/MalformedSamlResponseLoggerTest.java @@ -0,0 +1,131 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.logging.log4j.Level.DEBUG; +import static org.apache.logging.log4j.Level.INFO; +import static org.apache.logging.log4j.Level.WARN; +import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.authentication.MalformedSamlResponseLogger.X_VCAP_REQUEST_ID_HEADER; + +class MalformedSamlResponseLoggerTest { + + private static final String MALFORMED_MESSAGE = "Malformed SAML response. More details at log level DEBUG."; + + private MalformedSamlResponseLogger malformedSamlResponseLogger; + + private static final String LOGGER_NAME = "org.cloudfoundry.identity.uaa.authentication.SamlResponseLoggerBinding"; + private static Level originalLevel; + private static List logEvents; + private static AbstractAppender appender; + + MockHttpServletRequest mockHttpServletRequest; + + @BeforeAll + static void setupLogger() { + logEvents = new ArrayList<>(); + appender = new AbstractAppender("", null, null, true, null) { + @Override + public void append(LogEvent event) { + if (LOGGER_NAME.equals(event.getLoggerName())) { + logEvents.add(event); + } + } + }; + appender.start(); + + LoggerContext context = (LoggerContext) LogManager.getContext(false); + originalLevel = context.getRootLogger().getLevel(); + Configurator.setRootLevel(DEBUG); + context.getRootLogger().addAppender(appender); + } + + @BeforeEach + void setUp() { + logEvents.clear(); + mockHttpServletRequest = new MockHttpServletRequest("GET", "/test"); + mockHttpServletRequest.setContentType("application/json"); + malformedSamlResponseLogger = new MalformedSamlResponseLogger(); + LoggerContext context = (LoggerContext) LogManager.getContext(false); + originalLevel = context.getRootLogger().getLevel(); + } + + @AfterAll + static void removeAppender() { + LoggerContext context = (LoggerContext) LogManager.getContext(false); + context.getRootLogger().removeAppender(appender); + Configurator.setRootLevel(originalLevel); + } + + @Test + void doesNotFailWithNullParameterMap() { + Configurator.setRootLevel(DEBUG); + malformedSamlResponseLogger.logMalformedResponse(mockHttpServletRequest); + assertThat(logEvents).hasSize(2); + assertThatMessageWasLogged(logEvents, WARN, MALFORMED_MESSAGE); + } + + @Test + void doesNotFailWithNullParameter() { + mockHttpServletRequest.addHeader(X_VCAP_REQUEST_ID_HEADER, "1234"); + mockHttpServletRequest.setParameter("", new String[]{null}); + mockHttpServletRequest.setParameter("key1", new String[]{null}); + mockHttpServletRequest.setParameter("key2", null, ""); + mockHttpServletRequest.setParameter("key3", "value", null); + + Configurator.setRootLevel(DEBUG); + malformedSamlResponseLogger.logMalformedResponse(mockHttpServletRequest); + assertThat(logEvents).hasSize(2); + assertThatMessageWasLogged(logEvents, WARN, MALFORMED_MESSAGE); + assertThatMessageWasLogged(logEvents, DEBUG, "Method: GET, Params (name/size): (/0) (key1/0) (key2/0) (key2/0) (key3/5) (key3/0), Content-type: application/json, Request-size: -1, X-Vcap-Request-Id: 1234"); + } + + @Test + void logsDetailsAtDebugLevel() { + mockHttpServletRequest.setMethod("POST"); + mockHttpServletRequest.addHeader(X_VCAP_REQUEST_ID_HEADER, "12345"); + mockHttpServletRequest.setParameter("key1", new String[]{"value"}); + mockHttpServletRequest.setParameter("key2", new String[]{"value2"}); + mockHttpServletRequest.setContentType("application/xml"); + mockHttpServletRequest.setContent("data".getBytes(StandardCharsets.UTF_8)); + + Configurator.setRootLevel(DEBUG); + malformedSamlResponseLogger.logMalformedResponse(mockHttpServletRequest); + assertThat(logEvents).hasSize(2); + assertThatMessageWasLogged(logEvents, WARN, MALFORMED_MESSAGE); + assertThatMessageWasLogged(logEvents, DEBUG, "Method: POST, Params (name/size): (key1/5) (key2/6), Content-type: application/xml, Request-size: 4, X-Vcap-Request-Id: 12345"); + } + + @Test + void noDetailsAtInfoLevel() { + mockHttpServletRequest.setParameter("key1", new String[]{null}); + Configurator.setRootLevel(INFO); + malformedSamlResponseLogger.logMalformedResponse(mockHttpServletRequest); + assertThat(logEvents).hasSize(1); + assertThatMessageWasLogged(logEvents, WARN, MALFORMED_MESSAGE); + } + + private void assertThatMessageWasLogged( + final List logEvents, + final Level expectedLevel, + final String expectedMessage) { + + assertThat(logEvents).filteredOn(l -> l.getLevel().equals(expectedLevel)) + .first() + .returns(expectedMessage, l -> l.getMessage().getFormattedMessage()); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlAssertionBindingTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlAssertionBindingTests.java deleted file mode 100644 index d36f3a4ce60..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlAssertionBindingTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * ***************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.authentication; - -import org.junit.Before; -import org.junit.Test; -import org.opensaml.ws.transport.http.HTTPInTransport; -import org.opensaml.xml.parse.BasicParserPool; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Created by fhanik on 12/22/16. - */ -public class SamlAssertionBindingTests { - - private SamlAssertionBinding binding; - - @Before - public void setUp() { - binding = new SamlAssertionBinding(new BasicParserPool()); - } - - @Test - public void supports() { - HTTPInTransport transport = mock(HTTPInTransport.class); - assertFalse(binding.supports(transport)); - - when(transport.getHTTPMethod()).thenReturn("POST"); - assertFalse(binding.supports(transport)); - - when(transport.getParameterValue("assertion")).thenReturn("some assertion"); - assertTrue(binding.supports(transport)); - } - - @Test - public void getBindingURI() { - assertEquals("urn:oasis:names:tc:SAML:2.0:bindings:URI", binding.getBindingURI()); - } - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutRequestValidatorTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutRequestValidatorTest.java new file mode 100644 index 00000000000..ce065169036 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutRequestValidatorTest.java @@ -0,0 +1,52 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SamlLogoutRequestValidatorTest { + + @Mock + private Saml2LogoutRequestValidator delegate; + private SamlLogoutRequestValidator validator; + + @BeforeEach + void setUp() { + validator = new SamlLogoutRequestValidator(delegate); + } + + @Test + void validatePassesThruSuccess() { + Saml2LogoutValidatorResult success = Saml2LogoutValidatorResult.success(); + when(delegate.validate(any())).thenReturn(success); + Saml2LogoutValidatorResult result = validator.validate(null); + assertThat(result.hasErrors()).isFalse(); + } + + @Test + void validateRemovesMissingSignatureError() { + Saml2Error signatureError = new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object"); + when(delegate.validate(any())).thenReturn(Saml2LogoutValidatorResult.withErrors(signatureError).build()); + Saml2LogoutValidatorResult result = validator.validate(null); + assertThat(result.hasErrors()).isFalse(); + } + + @Test + void validateDifferentErrorIsPassedThru() { + Saml2Error signatureError = new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Failed to match issuer to configured issuer"); + when(delegate.validate(any())).thenReturn(Saml2LogoutValidatorResult.withErrors(signatureError).build()); + Saml2LogoutValidatorResult result = validator.validate(null); + assertThat(result.hasErrors()).isTrue(); + } +} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutResponseValidatorTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutResponseValidatorTest.java new file mode 100644 index 00000000000..3aab59044cf --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlLogoutResponseValidatorTest.java @@ -0,0 +1,52 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator; +import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SamlLogoutResponseValidatorTest { + + @Mock + private Saml2LogoutResponseValidator delegate; + private SamlLogoutResponseValidator validator; + + @BeforeEach + void setUp() { + validator = new SamlLogoutResponseValidator(delegate); + } + + @Test + void validatePassesThruSuccess() { + Saml2LogoutValidatorResult success = Saml2LogoutValidatorResult.success(); + when(delegate.validate(any())).thenReturn(success); + Saml2LogoutValidatorResult result = validator.validate(null); + assertThat(result.hasErrors()).isFalse(); + } + + @Test + void validateRemovesMissingSignatureErrors() { + Saml2Error signatureError = new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object"); + when(delegate.validate(any())).thenReturn(Saml2LogoutValidatorResult.withErrors(signatureError).build()); + Saml2LogoutValidatorResult result = validator.validate(null); + assertThat(result.hasErrors()).isFalse(); + } + + @Test + void validateDifferentErrorIsPassedThru() { + Saml2Error signatureError = new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Failed to match issuer to configured issuer"); + when(delegate.validate(any())).thenReturn(Saml2LogoutValidatorResult.withErrors(signatureError).build()); + Saml2LogoutValidatorResult result = validator.validate(null); + assertThat(result.hasErrors()).isTrue(); + } +} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlResponseLoggerBindingTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlResponseLoggerBindingTest.java deleted file mode 100644 index 323f048ddc5..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SamlResponseLoggerBindingTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configurator; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.opensaml.ws.transport.InputStreamInTransportAdapter; -import org.opensaml.ws.transport.http.HttpServletRequestAdapter; -import org.slf4j.LoggerFactory; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; - -import java.util.HashMap; -import java.util.Map; - -import static org.apache.logging.log4j.Level.DEBUG; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class SamlResponseLoggerBindingTest { - - private SamlResponseLoggerBinding samlResponseLoggerBinding; - - private Level originalLevel; - - @BeforeEach - void setUp() { - samlResponseLoggerBinding = new SamlResponseLoggerBinding(); - - LoggerContext context = (LoggerContext) LogManager.getContext(false); - originalLevel = context.getRootLogger().getLevel(); - } - - @AfterEach - void tearDown() { - Configurator.setRootLevel(originalLevel); - } - - @Test - void xVcapRequestId() { - assertThat(SamlResponseLoggerBinding.X_VCAP_REQUEST_ID_HEADER, is("X-Vcap-Request-Id")); - } - - @Test - void doesNotFailWithSomethingOtherThanHttpServletRequestAdapter() { - InputStreamInTransportAdapter inputStreamInTransportAdapter = new InputStreamInTransportAdapter(null); - - assertDoesNotThrow(() -> samlResponseLoggerBinding.supports(inputStreamInTransportAdapter)); - } - - @Test - void doesNotFailWithNullServletRequest() { - HttpServletRequestAdapter httpServletRequestAdapter = new HttpServletRequestAdapter(null); - - Configurator.setRootLevel(DEBUG); - - assertDoesNotThrow(() -> samlResponseLoggerBinding.supports(httpServletRequestAdapter)); - } - - @Test - void doesNotFailWithNullParameterMap() { - HttpServletRequest mockHttpServletRequest = mock(HttpServletRequest.class); - when(mockHttpServletRequest.getParameterMap()).thenReturn(null); - HttpServletRequestAdapter httpServletRequestAdapter = new HttpServletRequestAdapter(mockHttpServletRequest); - - Configurator.setRootLevel(DEBUG); - - assertDoesNotThrow(() -> samlResponseLoggerBinding.supports(httpServletRequestAdapter)); - } - - @Test - void doesNotFailWithNullParameter() { - HttpServletRequest mockHttpServletRequest = mock(HttpServletRequest.class); - Map parameters = new HashMap<>(); - parameters.put(null, null); - parameters.put("key1", null); - parameters.put("key2", new String[]{null}); - parameters.put("key3", new String[]{"value", null}); - when(mockHttpServletRequest.getParameterMap()).thenReturn(parameters); - HttpServletRequestAdapter httpServletRequestAdapter = new HttpServletRequestAdapter(mockHttpServletRequest); - - Configurator.setRootLevel(DEBUG); - - assertDoesNotThrow(() -> samlResponseLoggerBinding.supports(httpServletRequestAdapter)); - } -} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UTF8ConversionFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UTF8ConversionFilterTests.java index b93519ac0a6..4059e0d6c4c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UTF8ConversionFilterTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UTF8ConversionFilterTests.java @@ -13,61 +13,62 @@ */ package org.cloudfoundry.identity.uaa.authentication; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import javax.servlet.FilterChain; import javax.servlet.ServletException; - import java.io.IOException; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class UTF8ConversionFilterTests { +@ExtendWith(MockitoExtension.class) +class UTF8ConversionFilterTests { private MockHttpServletResponse response; private MockHttpServletRequest request; - private FilterChain chain; private UTF8ConversionFilter filter; - @Before - public void setup() { + @Mock + private FilterChain chain; + + @BeforeEach + void setup() { request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); - chain = mock(FilterChain.class); filter = new UTF8ConversionFilter(); } - public void verifyChain(int count) throws IOException, ServletException { + private void verifyChain(int count) throws IOException, ServletException { filter.doFilter(request, response, chain); verify(chain, times(count)).doFilter(any(), any()); - if (count==0) { - assertEquals(400, response.getStatus()); + if (count == 0) { + assertThat(response.getStatus()).isEqualTo(400); } } - - @Test - public void validateParamsAndContinue() throws Exception { + void validateParamsAndContinue() throws Exception { verifyChain(1); } @Test - public void nullCharactersInSingleValueParams_1() throws Exception { - request.setParameter("test", new String(new char[] {'a','b','\u0000'})); + void nullCharactersInSingleValueParams_1() throws Exception { + request.setParameter("test", new String(new char[]{'a', 'b', '\u0000'})); verifyChain(0); } @Test - public void nullCharactersInSingleValueParams_2() throws Exception { - request.setParameter("test", new String(new char[] {'a','b',(char)0})); + void nullCharactersInSingleValueParams_2() throws Exception { + request.setParameter("test", new String(new char[]{'a', 'b', (char) 0})); verifyChain(0); } -} \ No newline at end of file +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java index 2f6e0fcc5c8..05b892786a4 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java @@ -16,8 +16,7 @@ package org.cloudfoundry.identity.uaa.authentication; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.hamcrest.Matchers; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.util.LinkedMultiValueMap; @@ -32,29 +31,21 @@ import java.util.Map; import java.util.Set; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.everyItem; -import static org.hamcrest.Matchers.isIn; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class UaaAuthenticationSerializationTests { +class UaaAuthenticationSerializationTests { - public static final String COST_CENTER = "costCenter"; - public static final String DENVER_CO = "Denver,CO"; - public static final String MANAGER = "manager"; - public static final String JOHN_THE_SLOTH = "John the Sloth"; - public static final String KARI_THE_ANT_EATER = "Kari the Ant Eater"; + private static final String COST_CENTER = "costCenter"; + private static final String DENVER_CO = "Denver,CO"; + private static final String MANAGER = "manager"; + private static final String JOHN_THE_SLOTH = "John the Sloth"; + private static final String KARI_THE_ANT_EATER = "Kari the Ant Eater"; @Test - public void test_serialization() { - UaaPrincipal principal = new UaaPrincipal("id","username","email","origin","externalId","zoneId"); + void test_serialization() { + UaaPrincipal principal = new UaaPrincipal("id", "username", "email", "origin", "externalId", "zoneId"); HttpSession session = mock(HttpSession.class); when(session.getId()).thenReturn("id"); HttpServletRequest request = mock(HttpServletRequest.class); @@ -67,121 +58,121 @@ public void test_serialization() { List authorities = Arrays.asList(new SimpleGrantedAuthority("role1"), new SimpleGrantedAuthority("role2")); String credentials = "credentials"; - Map> userAttributes = new HashMap<>(); - userAttributes.put("atest", Arrays.asList("test1","test2","test3")); + Map> userAttributes = new HashMap<>(); + userAttributes.put("atest", Arrays.asList("test1", "test2", "test3")); userAttributes.put("btest", Arrays.asList("test1", "test2", "test3")); - Set externalGroups = new HashSet<>(Arrays.asList("group1","group2","group3")); + Set externalGroups = new HashSet<>(Arrays.asList("group1", "group2", "group3")); boolean authenticated = true; long authenticatedTime = System.currentTimeMillis(); long expiresAt = Long.MAX_VALUE; - UaaAuthentication expected = new UaaAuthentication(principal,credentials, authorities, externalGroups,userAttributes, details, authenticated, authenticatedTime, expiresAt); + UaaAuthentication expected = new UaaAuthentication(principal, credentials, authorities, externalGroups, userAttributes, details, authenticated, authenticatedTime, expiresAt); String authenticationAsJson = JsonUtils.writeValueAsString(expected); UaaAuthentication actual = JsonUtils.readValue(authenticationAsJson, UaaAuthentication.class); //validate authentication details - UaaAuthenticationDetails actualDetails = (UaaAuthenticationDetails)actual.getDetails(); - assertNotNull(actualDetails); - assertEquals("remoteAddr", actualDetails.getOrigin()); - assertEquals("id", actualDetails.getSessionId()); - assertEquals("clientId", actualDetails.getClientId()); - assertTrue(actualDetails.isAddNew()); + UaaAuthenticationDetails actualDetails = actual.getUaaAuthenticationDetails(); + assertThat(actualDetails).isNotNull().isSameAs(actual.getDetails()); + assertThat(actualDetails.getOrigin()).isEqualTo("remoteAddr"); + assertThat(actualDetails.getSessionId()).isEqualTo("id"); + assertThat(actualDetails.getClientId()).isEqualTo("clientId"); + assertThat(actualDetails.isAddNew()).isTrue(); //validate principal UaaPrincipal actualPrincipal = actual.getPrincipal(); - assertEquals("id",actualPrincipal.getId()); - assertEquals("username",actualPrincipal.getName()); - assertEquals("email",actualPrincipal.getEmail()); - assertEquals("origin",actualPrincipal.getOrigin()); - assertEquals("externalId",actualPrincipal.getExternalId()); - assertEquals("zoneId", actualPrincipal.getZoneId()); + assertThat(actualPrincipal.getId()).isEqualTo("id"); + assertThat(actualPrincipal.getName()).isEqualTo("username"); + assertThat(actualPrincipal.getEmail()).isEqualTo("email"); + assertThat(actualPrincipal.getOrigin()).isEqualTo("origin"); + assertThat(actualPrincipal.getExternalId()).isEqualTo("externalId"); + assertThat(actualPrincipal.getZoneId()).isEqualTo("zoneId"); //validate authorities - assertThat(actual.getAuthorities(), containsInAnyOrder(new SimpleGrantedAuthority("role1"), new SimpleGrantedAuthority("role2"))); + assertThat(actual.getAuthorities()).contains(new SimpleGrantedAuthority("role1"), new SimpleGrantedAuthority("role2")); //validate external groups - assertThat(actual.getExternalGroups(), containsInAnyOrder("group1","group2","group3")); + assertThat(actual.getExternalGroups()).contains("group1", "group2", "group3"); //validate user attributes - assertEquals(2, actual.getUserAttributes().size()); - assertThat(actual.getUserAttributes().get("atest"),containsInAnyOrder("test1","test2","test3")); - assertThat(actual.getUserAttributes().get("btest"),containsInAnyOrder("test1","test2","test3")); + assertThat(actual.getUserAttributes()).hasSize(2); + assertThat(actual.getUserAttributes().get("atest")).contains("test1", "test2", "test3"); + assertThat(actual.getUserAttributes().get("btest")).contains("test1", "test2", "test3"); //validate authenticated - assertEquals(authenticated, actual.isAuthenticated()); + assertThat(actual.isAuthenticated()).isEqualTo(authenticated); //validate authenticated time - assertEquals(authenticatedTime, actual.getAuthenticatedTime()); + assertThat(actual.getAuthenticatedTime()).isEqualTo(authenticatedTime); //validate expires at time - assertEquals(expiresAt, actual.getExpiresAt()); - + assertThat(actual.getExpiresAt()).isEqualTo(expiresAt); } @Test - public void testDeserializationWithoutAuthenticatedTime() { - String data ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"authenticatedTime\":1438649464353,\"name\":\"username\"}"; + void testDeserializationWithoutAuthenticatedTime() { + String data = "{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"authenticatedTime\":1438649464353,\"name\":\"username\"}"; UaaAuthentication authentication1 = JsonUtils.readValue(data, UaaAuthentication.class); - assertEquals(1438649464353l, authentication1.getAuthenticatedTime()); - assertEquals(-1l, authentication1.getExpiresAt()); - assertTrue(authentication1.isAuthenticated()); - assertNull(authentication1.getAuthContextClassRef()); - String dataWithoutTime ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"name\":\"username\"}"; + assertThat(authentication1.getAuthenticatedTime()).isEqualTo(1438649464353L); + assertThat(authentication1.getExpiresAt()).isEqualTo(-1); + assertThat(authentication1.isAuthenticated()).isTrue(); + assertThat(authentication1.getAuthContextClassRef()).isNull(); + String dataWithoutTime = "{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"name\":\"username\"}"; UaaAuthentication authentication2 = JsonUtils.readValue(dataWithoutTime, UaaAuthentication.class); - assertEquals(-1, authentication2.getAuthenticatedTime()); - + assertThat(authentication2.getAuthenticatedTime()).isEqualTo(-1); - long inThePast = System.currentTimeMillis() - 1000l * 60l; - data ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"authenticatedTime\":1438649464353,\"name\":\"username\", \"expiresAt\":"+inThePast+"}"; + long inThePast = System.currentTimeMillis() - 1000L * 60L; + data = "{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"authenticatedTime\":1438649464353,\"name\":\"username\", \"expiresAt\":" + inThePast + "}"; UaaAuthentication authentication3 = JsonUtils.readValue(data, UaaAuthentication.class); - assertEquals(1438649464353l, authentication3.getAuthenticatedTime()); - assertEquals(inThePast, authentication3.getExpiresAt()); - assertFalse(authentication3.isAuthenticated()); + assertThat(authentication3.getAuthenticatedTime()).isEqualTo(1438649464353L); + assertThat(authentication3.getExpiresAt()).isEqualTo(inThePast); + assertThat(authentication3.isAuthenticated()).isFalse(); - long inTheFuture = System.currentTimeMillis() + 1000l * 60l; - data ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"authenticatedTime\":1438649464353,\"name\":\"username\", \"expiresAt\":"+inTheFuture+"}"; + long inTheFuture = System.currentTimeMillis() + 1000L * 60L; + data = "{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"authenticatedTime\":1438649464353,\"name\":\"username\", \"expiresAt\":" + inTheFuture + "}"; UaaAuthentication authentication4 = JsonUtils.readValue(data, UaaAuthentication.class); - assertEquals(1438649464353l, authentication4.getAuthenticatedTime()); - assertEquals(inTheFuture, authentication4.getExpiresAt()); - assertTrue(authentication4.isAuthenticated()); + assertThat(authentication4.getAuthenticatedTime()).isEqualTo(1438649464353L); + assertThat(authentication4.getExpiresAt()).isEqualTo(inTheFuture); + assertThat(authentication4.isAuthenticated()).isTrue(); } @Test - public void deserialization_with_external_groups() { - String dataWithExternalGroups ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"externalGroups\":[\"something\",\"or\",\"other\",\"something\"],\"details\":null,\"authenticated\":true,\"authenticatedTime\":null,\"name\":\"username\"}"; + void deserialization_with_external_groups() { + String dataWithExternalGroups = "{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"externalGroups\":[\"something\",\"or\",\"other\",\"something\"],\"details\":null,\"authenticated\":true,\"authenticatedTime\":null,\"name\":\"username\"}"; UaaAuthentication authentication = JsonUtils.readValue(dataWithExternalGroups, UaaAuthentication.class); - assertEquals(3, authentication.getExternalGroups().size()); - assertThat(authentication.getExternalGroups(), Matchers.containsInAnyOrder("something", "or", "other")); - assertTrue(authentication.isAuthenticated()); + assertThat(authentication.getExternalGroups()) + .hasSize(3) + .contains("something", "or", "other"); + assertThat(authentication.isAuthenticated()).isTrue(); } @Test - public void deserialization_with_user_attributes() { - String dataWithoutUserAttributes ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"externalGroups\":[\"something\",\"or\",\"other\",\"something\"],\"details\":null,\"authenticated\":true,\"authenticatedTime\":null,\"name\":\"username\", \"previousLoginSuccessTime\":1485305759347}"; + void deserialization_with_user_attributes() { + String dataWithoutUserAttributes = "{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"externalGroups\":[\"something\",\"or\",\"other\",\"something\"],\"details\":null,\"authenticated\":true,\"authenticatedTime\":null,\"name\":\"username\", \"previousLoginSuccessTime\":1485305759347}"; UaaAuthentication authentication = JsonUtils.readValue(dataWithoutUserAttributes, UaaAuthentication.class); - assertEquals(3, authentication.getExternalGroups().size()); - assertThat(authentication.getExternalGroups(), Matchers.containsInAnyOrder("something", "or", "other")); - assertTrue(authentication.isAuthenticated()); + assertThat(authentication.getExternalGroups()) + .hasSize(3) + .contains("something", "or", "other"); + assertThat(authentication.isAuthenticated()).isTrue(); - MultiValueMap userAttributes = new LinkedMultiValueMap<>(); + MultiValueMap userAttributes = new LinkedMultiValueMap<>(); userAttributes.add(COST_CENTER, DENVER_CO); userAttributes.add(MANAGER, JOHN_THE_SLOTH); userAttributes.add(MANAGER, KARI_THE_ANT_EATER); authentication.setUserAttributes(userAttributes); String dataWithUserAttributes = JsonUtils.writeValueAsString(authentication); - assertTrue("userAttributes should be part of the JSON", dataWithUserAttributes.contains("userAttributes")); + assertThat(dataWithUserAttributes).as("userAttributes should be part of the JSON").contains("userAttributes"); UaaAuthentication authWithUserData = JsonUtils.readValue(dataWithUserAttributes, UaaAuthentication.class); - assertNotNull(authWithUserData.getUserAttributes()); - assertThat(authWithUserData.getUserAttributes().entrySet(), everyItem(isIn(userAttributes.entrySet()))); - assertThat(userAttributes.entrySet(), everyItem(isIn(authWithUserData.getUserAttributes().entrySet()))); - - assertEquals(3, authentication.getExternalGroups().size()); - assertThat(authentication.getExternalGroups(), Matchers.containsInAnyOrder("something", "or", "other")); - assertTrue(authentication.isAuthenticated()); - assertEquals((Long) 1485305759347L, authentication.getLastLoginSuccessTime()); + assertThat(authWithUserData.getUserAttributes()).isNotNull(); + assertThat(authWithUserData.getUserAttributes().entrySet()).containsExactlyInAnyOrderElementsOf(userAttributes.entrySet()); + assertThat(userAttributes.entrySet()).containsExactlyInAnyOrderElementsOf(authWithUserData.getUserAttributes().entrySet()); + + assertThat(authentication.getExternalGroups()) + .hasSize(3) + .contains("something", "or", "other"); + assertThat(authentication.isAuthenticated()).isTrue(); + assertThat(authentication.getLastLoginSuccessTime()).isEqualTo((Long) 1485305759347L); } - } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializerDeserializerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializerDeserializerTest.java index fc1637b2554..b95b53141de 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializerDeserializerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializerDeserializerTest.java @@ -7,16 +7,16 @@ import org.junit.Test; import java.util.Collections; -import java.util.LinkedList; +import java.util.Objects; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class UaaAuthenticationSerializerDeserializerTest { @Test public void serializeUaaAuthentication() { UaaPrincipal p = new UaaPrincipal("user-id", "username", "user@example.com", OriginKeys.UAA, "", IdentityZoneHolder.get().getId()); - UaaAuthentication auth = new UaaAuthentication(p, UaaAuthority.USER_AUTHORITIES, new UaaAuthenticationDetails(false, "clientId", OriginKeys.ORIGIN,"sessionId")); + UaaAuthentication auth = new UaaAuthentication(p, UaaAuthority.USER_AUTHORITIES, new UaaAuthenticationDetails(false, "clientId", OriginKeys.ORIGIN, "sessionId")); auth.setAuthenticationMethods(Collections.singleton("pwd")); auth.setAuthContextClassRef(Collections.singleton("test:uri")); auth.setAuthenticatedTime(1485314434675L); @@ -25,17 +25,23 @@ public void serializeUaaAuthentication() { UaaAuthentication deserializedUaaAuthentication = JsonUtils.readValue(JsonUtils.writeValueAsString(auth), UaaAuthentication.class); - assertEquals(auth.getDetails(), deserializedUaaAuthentication.getDetails()); - assertEquals(auth.getPrincipal(), deserializedUaaAuthentication.getPrincipal()); - assertEquals("uaa.user", ((LinkedList) deserializedUaaAuthentication.getAuthorities()).get(0).toString()); - assertEquals(Collections.EMPTY_SET, deserializedUaaAuthentication.getExternalGroups()); - assertEquals(auth.getExpiresAt(), deserializedUaaAuthentication.getExpiresAt()); - assertEquals(auth.getAuthenticatedTime(), deserializedUaaAuthentication.getAuthenticatedTime()); - assertEquals(auth.isAuthenticated(), deserializedUaaAuthentication.isAuthenticated()); - assertEquals(auth.getUserAttributesAsMap(), deserializedUaaAuthentication.getUserAttributesAsMap()); - assertEquals(auth.getAuthenticationMethods(), deserializedUaaAuthentication.getAuthenticationMethods()); - assertEquals(auth.getAuthContextClassRef(), deserializedUaaAuthentication.getAuthContextClassRef()); - assertEquals(auth.getLastLoginSuccessTime(), deserializedUaaAuthentication.getLastLoginSuccessTime()); - assertEquals(auth.getIdpIdToken(), deserializedUaaAuthentication.getIdpIdToken()); + assertThat(deserializedUaaAuthentication) + .returns(auth.getDetails(), UaaAuthentication::getDetails) + .returns(auth.getPrincipal(), UaaAuthentication::getPrincipal) + .returns(auth.getExpiresAt(), UaaAuthentication::getExpiresAt) + .returns(auth.getAuthenticatedTime(), UaaAuthentication::getAuthenticatedTime) + .returns(auth.isAuthenticated(), UaaAuthentication::isAuthenticated) + .returns(auth.getUserAttributesAsMap(), UaaAuthentication::getUserAttributesAsMap) + .returns(auth.getAuthenticationMethods(), UaaAuthentication::getAuthenticationMethods) + .returns(auth.getAuthContextClassRef(), UaaAuthentication::getAuthContextClassRef) + .returns(auth.getLastLoginSuccessTime(), UaaAuthentication::getLastLoginSuccessTime) + .returns(auth.getIdpIdToken(), UaaAuthentication::getIdpIdToken); + + assertThat(deserializedUaaAuthentication.getAuthorities()) + .extracting(Objects::toString) + .containsExactly("uaa.user"); + + assertThat(deserializedUaaAuthentication.getExternalGroups()) + .isEmpty(); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlPrincipalTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlPrincipalTest.java new file mode 100644 index 00000000000..478d08b3d7f --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaSamlPrincipalTest.java @@ -0,0 +1,19 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class UaaSamlPrincipalTest { + @Test + void testUaaSamlPrincipal() { + UaaSamlPrincipal uaaSamlPrincipal = new UaaSamlPrincipal("id", "name", "email", "origin", "externalId", "zoneId"); + assertThat(uaaSamlPrincipal).returns("id", UaaSamlPrincipal::getId) + .returns("name", UaaSamlPrincipal::getName) + .returns("email", UaaSamlPrincipal::getEmail) + .returns("origin", UaaSamlPrincipal::getOrigin) + .returns("origin", UaaSamlPrincipal::getRelyingPartyRegistrationId) + .returns("externalId", UaaSamlPrincipal::getExternalId) + .returns("zoneId", UaaSamlPrincipal::getZoneId); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutSuccessHandlerTest.java similarity index 76% rename from server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutSuccessHandlerTest.java index d27eb597d65..765c3b74025 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutSuccessHandlerTest.java @@ -16,41 +16,40 @@ import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; +import org.cloudfoundry.identity.uaa.provider.NoSuchClientException; import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.cloudfoundry.identity.uaa.provider.NoSuchClientException; import java.util.Collections; import static java.util.Collections.EMPTY_LIST; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; @ExtendWith(PollutionPreventionExtension.class) -class WhitelistLogoutHandlerTest { +class WhitelistLogoutSuccessHandlerTest { - private WhitelistLogoutHandler handler; + private WhitelistLogoutSuccessHandler handler; private MockHttpServletRequest request; private MockHttpServletResponse response; - private UaaClientDetails client; private MultitenantClientServices clientDetailsService; @BeforeEach void setUp() { request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); - client = new UaaClientDetails(CLIENT_ID,"","","","","http://*.testing.com,http://testing.com"); - clientDetailsService = mock(MultitenantClientServices.class); - handler = new WhitelistLogoutHandler(EMPTY_LIST); + UaaClientDetails client = new UaaClientDetails(CLIENT_ID, "", "", "", "", "http://*.testing.com,http://testing.com"); + clientDetailsService = mock(MultitenantClientServices.class); + handler = new WhitelistLogoutSuccessHandler(EMPTY_LIST); handler.setDefaultTargetUrl("/login"); handler.setAlwaysUseDefaultTargetUrl(true); handler.setTargetUrlParameter("redirect"); @@ -60,10 +59,9 @@ void setUp() { @Test void test_default_redirect_uri() { - assertEquals("/login", handler.determineTargetUrl(request, response)); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); handler.setAlwaysUseDefaultTargetUrl(false); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); } @Test @@ -71,9 +69,9 @@ void test_whitelist_reject() { handler.setWhitelist(Collections.singletonList("http://testing.com")); handler.setAlwaysUseDefaultTargetUrl(false); request.setParameter("redirect", "http://testing.com"); - assertEquals("http://testing.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://testing.com"); request.setParameter("redirect", "http://www.testing.com"); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); } @Test @@ -82,9 +80,9 @@ void test_open_redirect_no_longer_allowed() { handler.setAlwaysUseDefaultTargetUrl(false); handler.setDefaultTargetUrl("/login"); request.setParameter("redirect", "http://testing.com"); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); request.setParameter("redirect", "http://www.testing.com"); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); } @Test @@ -92,7 +90,7 @@ void test_whitelist_redirect() { handler.setWhitelist(Collections.singletonList("http://somethingelse.com")); handler.setAlwaysUseDefaultTargetUrl(false); request.setParameter("redirect", "http://somethingelse.com"); - assertEquals("http://somethingelse.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://somethingelse.com"); } @Test @@ -100,7 +98,7 @@ void test_whitelist_redirect_with_wildcard() { handler.setWhitelist(Collections.singletonList("http://*.somethingelse.com")); handler.setAlwaysUseDefaultTargetUrl(false); request.setParameter("redirect", "http://www.somethingelse.com"); - assertEquals("http://www.somethingelse.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://www.somethingelse.com"); } @Test @@ -109,7 +107,7 @@ void test_client_redirect() { handler.setAlwaysUseDefaultTargetUrl(false); request.setParameter("redirect", "http://testing.com"); request.setParameter(CLIENT_ID, CLIENT_ID); - assertEquals("http://testing.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://testing.com"); } @Test @@ -119,7 +117,7 @@ void client_not_found_exception() { handler.setAlwaysUseDefaultTargetUrl(false); request.setParameter("redirect", "http://notwhitelisted.com"); request.setParameter(CLIENT_ID, "test"); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); verify(clientDetailsService).loadClientByClientId("test", "uaa"); } @@ -129,7 +127,6 @@ void test_client_redirect_using_wildcard() { handler.setAlwaysUseDefaultTargetUrl(false); request.setParameter(CLIENT_ID, CLIENT_ID); request.setParameter("redirect", "http://www.testing.com"); - assertEquals("http://www.testing.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://www.testing.com"); } - } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutSuccessHandlerTests.java similarity index 58% rename from server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutSuccessHandlerTests.java index 4b7754e07fc..4885001e78d 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutHandlerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/ZoneAwareWhitelistLogoutSuccessHandlerTests.java @@ -16,178 +16,178 @@ import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.oauth.KeyInfoService; -import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthLogoutHandler; -import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; +import org.cloudfoundry.identity.uaa.provider.NoSuchClientException; +import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthLogoutSuccessHandler; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.cloudfoundry.identity.uaa.provider.NoSuchClientException; import javax.servlet.ServletException; import java.io.IOException; import java.util.Collections; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; import static org.mockito.Mockito.times; -import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; - +import static org.mockito.Mockito.when; -public class ZoneAwareWhitelistLogoutHandlerTests { +@ExtendWith(MockitoExtension.class) +class ZoneAwareWhitelistLogoutSuccessHandlerTests { - private MockHttpServletRequest request = new MockHttpServletRequest(); - private MockHttpServletResponse response = new MockHttpServletResponse(); - private UaaClientDetails client = new UaaClientDetails(CLIENT_ID, "", "", "", "", "http://*.testing.com,http://testing.com"); - private MultitenantClientServices clientDetailsService = mock(MultitenantClientServices.class); - private ExternalOAuthLogoutHandler oAuthLogoutHandler = mock(ExternalOAuthLogoutHandler.class); - private KeyInfoService keyInfoService = mock(KeyInfoService.class); - private ZoneAwareWhitelistLogoutHandler handler; + private final MockHttpServletRequest request = new MockHttpServletRequest(); + private final MockHttpServletResponse response = new MockHttpServletResponse(); + private final UaaClientDetails client = new UaaClientDetails(CLIENT_ID, "", "", "", "", "http://*.testing.com,http://testing.com"); + private ZoneAwareWhitelistLogoutSuccessHandler handler; IdentityZoneConfiguration configuration = new IdentityZoneConfiguration(); IdentityZoneConfiguration original; + @Mock + private MultitenantClientServices clientDetailsService; + @Mock + private ExternalOAuthLogoutSuccessHandler oAuthLogoutHandler; + @Mock + private KeyInfoService keyInfoService; - @Before - public void setUp() { + @BeforeEach + void setUp() { original = IdentityZone.getUaa().getConfig(); configuration.getLinks().getLogout() - .setRedirectUrl("/login") - .setDisableRedirectParameter(true) - .setRedirectParameterName("redirect"); - when(clientDetailsService.loadClientByClientId(CLIENT_ID, "uaa")).thenReturn(client); - handler = new ZoneAwareWhitelistLogoutHandler(clientDetailsService, oAuthLogoutHandler, keyInfoService); + .setRedirectUrl("/login") + .setDisableRedirectParameter(true) + .setRedirectParameterName("redirect"); + handler = new ZoneAwareWhitelistLogoutSuccessHandler(clientDetailsService, oAuthLogoutHandler, keyInfoService); IdentityZoneHolder.get().setConfig(configuration); } - @After - public void tearDown() { + @AfterEach + void tearDown() { IdentityZoneHolder.clear(); IdentityZone.getUaa().setConfig(original); } @Test - public void test_null_config_defaults() throws Exception { + void null_config_defaults() { IdentityZoneHolder.get().setConfig(null); - test_default_redirect_uri(); + default_redirect_uri(); } - @Test - public void test_default_redirect_uri() { - assertEquals("/login", handler.determineTargetUrl(request, response)); - assertEquals("/login", handler.determineTargetUrl(request, response)); + void default_redirect_uri() { + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); configuration.getLinks().getLogout().setDisableRedirectParameter(false); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); } @Test - public void test_whitelist_reject() { + void whitelist_reject() { configuration.getLinks().getLogout().setWhitelist(Collections.singletonList("http://testing.com")); configuration.getLinks().getLogout().setDisableRedirectParameter(false); request.setParameter("redirect", "http://testing.com"); - assertEquals("http://testing.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://testing.com"); request.setParameter("redirect", "http://www.testing.com"); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); } @Test - public void test_open_redirect_no_longer_allowed() { + void open_redirect_no_longer_allowed() { configuration.getLinks().getLogout().setWhitelist(null); configuration.getLinks().getLogout().setRedirectUrl("/login"); configuration.getLinks().getLogout().setDisableRedirectParameter(false); request.setParameter("redirect", "http://testing.com"); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); request.setParameter("redirect", "http://www.testing.com"); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); } @Test - public void test_whitelist_redirect() { + void whitelist_redirect() { configuration.getLinks().getLogout().setWhitelist(Collections.singletonList("http://somethingelse.com")); configuration.getLinks().getLogout().setDisableRedirectParameter(false); request.setParameter("redirect", "http://somethingelse.com"); - assertEquals("http://somethingelse.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://somethingelse.com"); } @Test - public void test_whitelist_redirect_with_wildcard() { + void whitelist_redirect_with_wildcard() { configuration.getLinks().getLogout().setWhitelist(Collections.singletonList("http://*.somethingelse.com")); configuration.getLinks().getLogout().setDisableRedirectParameter(false); request.setParameter("redirect", "http://www.somethingelse.com"); - assertEquals("http://www.somethingelse.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://www.somethingelse.com"); } @Test - public void test_client_redirect() { + void client_redirect() { + when(clientDetailsService.loadClientByClientId(CLIENT_ID, "uaa")).thenReturn(client); configuration.getLinks().getLogout().setWhitelist(Collections.singletonList("http://somethingelse.com")); configuration.getLinks().getLogout().setDisableRedirectParameter(false); request.setParameter("redirect", "http://testing.com"); request.setParameter(CLIENT_ID, CLIENT_ID); - assertEquals("http://testing.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://testing.com"); } @Test - public void client_not_found_exception() { + void client_not_found_exception() { when(clientDetailsService.loadClientByClientId("test", "uaa")).thenThrow(new NoSuchClientException("test")); configuration.getLinks().getLogout().setWhitelist(Collections.singletonList("http://testing.com")); configuration.getLinks().getLogout().setDisableRedirectParameter(false); request.setParameter("redirect", "http://notwhitelisted.com"); request.setParameter(CLIENT_ID, "test"); - assertEquals("/login", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); } @Test - public void test_client_redirect_using_wildcard() { + void client_redirect_using_wildcard() { + when(clientDetailsService.loadClientByClientId(CLIENT_ID, "uaa")).thenReturn(client); configuration.getLinks().getLogout().setWhitelist(Collections.singletonList("http://testing.com")); configuration.getLinks().getLogout().setDisableRedirectParameter(false); request.setParameter(CLIENT_ID, CLIENT_ID); request.setParameter("redirect", "http://www.testing.com"); - assertEquals("http://www.testing.com", handler.determineTargetUrl(request, response)); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("http://www.testing.com"); } @Test - public void test_external_client_redirect() { + void external_client_redirect() { configuration.getLinks().getLogout().setWhitelist(Collections.singletonList("http://somethingelse.com")); configuration.getLinks().getLogout().setDisableRedirectParameter(false); when(oAuthLogoutHandler.getLogoutUrl(null)).thenReturn(""); when(oAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, "", null, null)).thenReturn("/login"); request.setParameter("redirect", "http://testing.com"); request.setParameter(CLIENT_ID, CLIENT_ID); - assertEquals("/login", handler.determineTargetUrl(request, response)); - } - - @Test - public void test_external_logout() throws ServletException, IOException { - when(oAuthLogoutHandler.getLogoutUrl(null)).thenReturn(""); - when(oAuthLogoutHandler.getPerformRpInitiatedLogout(null)).thenReturn(true); - handler.onLogoutSuccess(request, response, null); - verify(oAuthLogoutHandler, times(1)).onLogoutSuccess(request, response, null); - } - - @Test - public void test_does_not_external_logout() throws ServletException, IOException { - when(oAuthLogoutHandler.getLogoutUrl(null)).thenReturn(""); - when(oAuthLogoutHandler.getPerformRpInitiatedLogout(null)).thenReturn(false); + assertThat(handler.determineTargetUrl(request, response)).isEqualTo("/login"); + } + + /* + * Parameterized Test replaces the following tests: + * - external_logout + * - does_not_external_logout + * - does_not_external_logout_when_logout_url_is_null + */ + @ParameterizedTest + @CsvSource({ + "'',true,1", + "'',false,0", + ",true,0"}) + void external_logout(String url, boolean rpInitiated, int onSuccessCalls) throws ServletException, IOException { + when(oAuthLogoutHandler.getLogoutUrl(null)).thenReturn(url); + when(oAuthLogoutHandler.getPerformRpInitiatedLogout(null)).thenReturn(rpInitiated); handler.onLogoutSuccess(request, response, null); - verify(oAuthLogoutHandler, times(0)).onLogoutSuccess(request, response, null); - } - - @Test - public void test_does_not_external_logout_when_logout_url_is_null() throws ServletException, IOException { - when(oAuthLogoutHandler.getLogoutUrl(null)).thenReturn(null); - when(oAuthLogoutHandler.getPerformRpInitiatedLogout(null)).thenReturn(true); - handler.onLogoutSuccess(request, response, null); - verify(oAuthLogoutHandler, times(0)).onLogoutSuccess(request, response, null); + verify(oAuthLogoutHandler, times(onSuccessCalls)).onLogoutSuccess(request, response, null); } @Test - public void test_logout() throws ServletException, IOException { + void logout() throws ServletException, IOException { handler.onLogoutSuccess(request, response, null); verify(oAuthLogoutHandler, times(0)).onLogoutSuccess(request, response, null); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java index 2c1bfc2d46f..0438d39ab42 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java @@ -34,9 +34,7 @@ import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationFailure; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationSuccess; -import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/cache/StaleUrlCacheTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/cache/StaleUrlCacheTests.java index 686e99a9679..3897117e99c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/cache/StaleUrlCacheTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/cache/StaleUrlCacheTests.java @@ -25,18 +25,17 @@ import java.net.URISyntaxException; import java.time.Duration; import java.util.Arrays; +import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTimeout; +import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,24 +44,12 @@ class StaleUrlCacheTests { private static final Duration CACHE_EXPIRATION = Duration.ofMinutes(10); - private static final Duration CACHE_EXPIRED = CACHE_EXPIRATION.plusMinutes(1); - private static final String URL = "http://localhost:8080/uaa/.well-known/openid-configuration"; + private static final Duration CACHE_EXPIRED = CACHE_EXPIRATION.multipliedBy(2).plusMinutes(1); + private static final String URI = "http://localhost:8080/uaa/.well-known/openid-configuration"; private static final byte[] content1; private static final byte[] content2; private static final byte[] content3; - private StaleUrlCache cache; - @Mock - private TimeService mockTimeService; - @Mock - private RestTemplate mockRestTemplate; - @Mock - HttpEntity httpEntity; - @Mock - ResponseEntity responseEntity; - - private TestTicker ticker; - static { content1 = new byte[8]; Arrays.fill(content1, (byte) 1); @@ -72,6 +59,17 @@ class StaleUrlCacheTests { Arrays.fill(content3, (byte) 3); } + @Mock + HttpEntity httpEntity; + @Mock + ResponseEntity responseEntity; + private StaleUrlCache cache; + @Mock + private TimeService mockTimeService; + @Mock + private RestTemplate mockRestTemplate; + private TestTicker ticker; + @BeforeEach void setup() { ticker = new TestTicker(System.nanoTime()); @@ -81,8 +79,8 @@ void setup() { @Test void correct_method_invoked_on_rest_template() throws URISyntaxException { - cache.getUrlContent(URL, mockRestTemplate); - verify(mockRestTemplate, times(1)).getForObject(eq(new URI(URL)), same(byte[].class)); + cache.getUrlContent(URI, mockRestTemplate); + verify(mockRestTemplate, times(1)).getForObject(eq(new URI(URI)), same(byte[].class)); } @Test @@ -93,37 +91,35 @@ void incorrect_uri_throws_illegal_argument_exception() { @Test void rest_client_exception_is_propagated() { when(mockRestTemplate.getForObject(any(URI.class), any())).thenThrow(new RestClientException("mock")); - assertThatExceptionOfType(RestClientException.class).isThrownBy(() -> cache.getUrlContent(URL, mockRestTemplate)); + assertThatExceptionOfType(RestClientException.class).isThrownBy(() -> cache.getUrlContent(URI, mockRestTemplate)); } @Test void calling_twice_uses_cache() throws Exception { - byte[] c1 = cache.getUrlContent(URL, mockRestTemplate); - byte[] c2 = cache.getUrlContent(URL, mockRestTemplate); - verify(mockRestTemplate, times(1)).getForObject(eq(new URI(URL)), same(byte[].class)); + byte[] c1 = cache.getUrlContent(URI, mockRestTemplate); + byte[] c2 = cache.getUrlContent(URI, mockRestTemplate); + verify(mockRestTemplate, times(1)).getForObject(eq(new URI(URI)), same(byte[].class)); assertThat(c2).isSameAs(c1); assertThat(cache.size()).isOne(); } @Test - void entry_refreshes_after_time() throws Exception { + void entry_refreshes_after_time() { when(mockTimeService.getCurrentTimeMillis()).thenAnswer(e -> System.currentTimeMillis()); when(mockRestTemplate.getForObject(any(URI.class), any())).thenReturn(content1, content2, content3); // populate the cache - byte[] c1 = cache.getUrlContent(URL, mockRestTemplate); + byte[] c1 = cache.getUrlContent(URI, mockRestTemplate); ticker.advance(CACHE_EXPIRED); // next call after timeout, should force async refresh - byte[] c2 = cache.getUrlContent(URL, mockRestTemplate); + byte[] c2 = cache.getUrlContent(URI, mockRestTemplate); assertThat(c2).isSameAs(c1); - // allow the async refresh to complete - verify(mockRestTemplate, timeout(1000).times(2)).getForObject(eq(new URI(URL)), same(byte[].class)); - - // the next call should return the new content - byte[] c3 = cache.getUrlContent(URL, mockRestTemplate); - assertThat(c3).isNotSameAs(c1); + // Allow time for the async getUrlContent to be called + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> verify(mockRestTemplate, times(2)).getForObject(eq(new URI(URI)), same(byte[].class))); + // Allow time for the async update to caffeine's cache. + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> assertThat(cache.getUrlContent(URI, mockRestTemplate)).isNotSameAs(c1)); } @Test @@ -154,23 +150,22 @@ void max_entries_is_respected() throws URISyntaxException { } @Test - void stale_entry_returned_on_failure() throws Exception { + void stale_entry_returned_on_failure() { when(mockRestTemplate.getForObject(any(URI.class), any())).thenReturn(content3).thenThrow(new RestClientException("mock")); // populate the cache - byte[] c1 = cache.getUrlContent(URL, mockRestTemplate); + byte[] c1 = cache.getUrlContent(URI, mockRestTemplate); ticker.advance(CACHE_EXPIRED); // next call after timeout, should force async refresh - byte[] c2 = cache.getUrlContent(URL, mockRestTemplate); + byte[] c2 = cache.getUrlContent(URI, mockRestTemplate); assertThat(c2).isSameAs(c1); - // allow the async refresh to complete - verify(mockRestTemplate, timeout(1000).times(2)).getForObject(eq(new URI(URL)), same(byte[].class)); - - // the next call would normally return the new content, in this case it should return the stale content - byte[] c3 = cache.getUrlContent(URL, mockRestTemplate); - assertThat(c3).isSameAs(c1); + // Allow time for the async getUrlContent to be called + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> verify(mockRestTemplate, times(2)).getForObject(eq(new URI(URI)), same(byte[].class))); + // Allow time for the async update to caffeine's cache. + // It should continue returning the stale content due to the exception + await().during(200, TimeUnit.MILLISECONDS).untilAsserted(() -> assertThat(cache.getUrlContent(URI, mockRestTemplate)).isSameAs(c1)); } @Test @@ -178,24 +173,24 @@ void extended_method_invoked_on_rest_template() throws URISyntaxException { when(mockRestTemplate.exchange(any(URI.class), any(HttpMethod.class), any(HttpEntity.class), any(Class.class))).thenReturn(responseEntity); when(responseEntity.getStatusCode()).thenReturn(HttpStatus.OK); when(responseEntity.getBody()).thenReturn(new byte[1]); - cache.getUrlContent(URL, mockRestTemplate, HttpMethod.GET, httpEntity); - verify(mockRestTemplate, times(1)).exchange(eq(new URI(URL)), + cache.getUrlContent(URI, mockRestTemplate, HttpMethod.GET, httpEntity); + verify(mockRestTemplate, times(1)).exchange(eq(new URI(URI)), eq(HttpMethod.GET), any(HttpEntity.class), same(byte[].class)); } @Test void exception_invoked_on_rest_template() { when(mockRestTemplate.exchange(any(URI.class), any(HttpMethod.class), any(HttpEntity.class), any(Class.class))).thenThrow(new UncheckedExecutionException(new IllegalArgumentException("illegal"))); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> cache.getUrlContent(URL, mockRestTemplate, HttpMethod.GET, httpEntity)); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> cache.getUrlContent(URI, mockRestTemplate, HttpMethod.GET, httpEntity)); } @Test void test_equal() { - StaleUrlCache.UriRequest uriRequest = new StaleUrlCache.UriRequest(URL, mockRestTemplate, HttpMethod.GET, responseEntity); - assertEquals(uriRequest, uriRequest); + StaleUrlCache.UriRequest uriRequest = new StaleUrlCache.UriRequest(URI, mockRestTemplate, HttpMethod.GET, responseEntity); + assertThat(uriRequest.equals(uriRequest)).isTrue(); assertThat(uriRequest.equals(null)).isFalse(); - assertThat(uriRequest.equals(URL)).isFalse(); - assertThat(new StaleUrlCache.UriRequest(URL, mockRestTemplate, HttpMethod.GET, responseEntity).equals(uriRequest)).isTrue(); + assertThat(uriRequest.equals(URI)).isFalse(); + assertThat(new StaleUrlCache.UriRequest(URI, mockRestTemplate, HttpMethod.GET, responseEntity).equals(uriRequest)).isTrue(); assertThat(new StaleUrlCache.UriRequest(null, mockRestTemplate, HttpMethod.GET, responseEntity).equals(uriRequest)).isFalse(); } @@ -203,7 +198,8 @@ void test_equal() { void extended_method_invoked_on_rest_template_invalid_http_response() { when(mockRestTemplate.exchange(any(URI.class), any(HttpMethod.class), any(HttpEntity.class), any(Class.class))).thenReturn(responseEntity); when(responseEntity.getStatusCode()).thenReturn(HttpStatus.TEMPORARY_REDIRECT); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> cache.getUrlContent(URL, mockRestTemplate, HttpMethod.GET, httpEntity)); + assertThatThrownBy(() -> cache.getUrlContent(URI, mockRestTemplate, HttpMethod.GET, httpEntity)) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -215,6 +211,23 @@ void constructor_executed() { assertThat(urlCache.size()).isZero(); } + static class TestTicker implements Ticker { + long nanos; + + public TestTicker(long initialNanos) { + nanos = initialNanos; + } + + @Override + public long read() { + return nanos; + } + + public void advance(Duration duration) { + nanos += duration.toNanos(); + } + } + @Nested @DisplayName("When a http server never returns a http response") class DeadHttpServer { @@ -238,26 +251,9 @@ void throwUnavailableIdpWhenServerMetadataDoesNotReply() { RestTemplate restTemplate = restTemplateConfig.trustingRestTemplate(); String url = slowHttpServer.getUrl(); - assertTimeout(Duration.ofSeconds(60), () -> assertThatThrownBy(() -> cache.getUrlContent(url, restTemplate)) - .isInstanceOf(ResourceAccessException.class) - ); - } - } - - static class TestTicker implements Ticker { - long nanos; - - public TestTicker(long initialNanos) { - nanos = initialNanos; - } - - @Override - public long read() { - return nanos; - } - - public void advance(Duration duration) { - nanos += duration.toNanos(); + await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> + assertThatThrownBy(() -> cache.getUrlContent(url, restTemplate)) + .isInstanceOf(ResourceAccessException.class)); } } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java index 06320f27730..55726b43e45 100755 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java @@ -20,7 +20,6 @@ import org.cloudfoundry.identity.uaa.provider.oauth.OauthIDPWrapperFactoryBean; import org.cloudfoundry.identity.uaa.provider.saml.BootstrapSamlIdentityProviderData; import org.cloudfoundry.identity.uaa.test.TestUtils; -import org.cloudfoundry.identity.uaa.util.PredicateMatcher; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,7 +42,9 @@ import java.util.List; import java.util.Map; -import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.fail; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.KEYSTONE; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20; @@ -54,15 +55,6 @@ import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EXTERNAL_GROUPS_WHITELIST; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.STORE_CUSTOM_ATTRIBUTES_NAME; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; @@ -79,8 +71,8 @@ class IdentityProviderBootstrapTest { private IdentityProviderProvisioning provisioning; private IdentityProviderBootstrap bootstrap; private MockEnvironment environment; - private AbstractExternalOAuthIdentityProviderDefinition oauthProvider; - private AbstractExternalOAuthIdentityProviderDefinition oidcProvider; + private RawExternalOAuthIdentityProviderDefinition oauthProvider; + private OIDCIdentityProviderDefinition oidcProvider; private HashMap oauthProviderConfig; @Autowired @@ -136,18 +128,17 @@ void ldapProfileBootstrap() throws Exception { bootstrap.afterPropertiesSet(); IdentityProvider ldapProvider = provisioning.retrieveByOriginIgnoreActiveFlag(LDAP, IdentityZone.getUaaZoneId()); - assertNotNull(ldapProvider); - assertNotNull(ldapProvider.getCreated()); - assertNotNull(ldapProvider.getLastModified()); - assertEquals(LDAP, ldapProvider.getType()); + assertThat(ldapProvider).isNotNull(); + assertThat(ldapProvider.getCreated()).isNotNull(); + assertThat(ldapProvider.getLastModified()).isNotNull(); + assertThat(ldapProvider.getType()).isEqualTo(LDAP); LdapIdentityProviderDefinition definition = ldapProvider.getConfig(); - assertNotNull(definition); - assertFalse(definition.isConfigured()); + assertThat(definition).isNotNull(); + assertThat(definition.isConfigured()).isFalse(); } @Test void ldapBootstrap() throws Exception { - final String idpDescription = "Test LDAP Provider Description"; HashMap ldapConfig = getGenericLdapConfig(); bootstrap.setLdapConfig(ldapConfig); @@ -159,15 +150,15 @@ void ldapBootstrap() throws Exception { private static void validateGenericLdapProvider( IdentityProvider ldapProvider) { - assertNotNull(ldapProvider); - assertNotNull(ldapProvider.getCreated()); - assertNotNull(ldapProvider.getLastModified()); - assertEquals(LDAP, ldapProvider.getType()); - assertThat(ldapProvider.getConfig().getEmailDomain(), containsInAnyOrder("test.domain")); - assertEquals(Collections.singletonList("value"), ldapProvider.getConfig().getExternalGroupsWhitelist()); - assertEquals("first_name", ldapProvider.getConfig().getAttributeMappings().get("given_name")); - assertEquals("Test LDAP Provider Description", ldapProvider.getConfig().getProviderDescription()); - assertFalse(ldapProvider.getConfig().isStoreCustomAttributes()); + assertThat(ldapProvider).isNotNull(); + assertThat(ldapProvider.getCreated()).isNotNull(); + assertThat(ldapProvider.getLastModified()).isNotNull(); + assertThat(ldapProvider.getType()).isEqualTo(LDAP); + assertThat(ldapProvider.getConfig().getEmailDomain()).contains("test.domain"); + assertThat(ldapProvider.getConfig().getExternalGroupsWhitelist()).isEqualTo(Collections.singletonList("value")); + assertThat(ldapProvider.getConfig().getAttributeMappings()).containsEntry("given_name", "first_name"); + assertThat(ldapProvider.getConfig().getProviderDescription()).isEqualTo("Test LDAP Provider Description"); + assertThat(ldapProvider.getConfig().isStoreCustomAttributes()).isFalse(); } private static HashMap getGenericLdapConfig() { @@ -216,38 +207,38 @@ void removedLdapBootstrapRemainsActive() throws Exception { bootstrap.afterPropertiesSet(); IdentityProvider ldapProvider = provisioning.retrieveByOriginIgnoreActiveFlag(LDAP, IdentityZone.getUaaZoneId()); - assertNotNull(ldapProvider); - assertNotNull(ldapProvider.getCreated()); - assertNotNull(ldapProvider.getLastModified()); - assertEquals(LDAP, ldapProvider.getType()); - assertTrue(ldapProvider.isActive()); + assertThat(ldapProvider).isNotNull(); + assertThat(ldapProvider.getCreated()).isNotNull(); + assertThat(ldapProvider.getLastModified()).isNotNull(); + assertThat(ldapProvider.getType()).isEqualTo(LDAP); + assertThat(ldapProvider.isActive()).isTrue(); bootstrap.setLdapConfig(null); bootstrap.afterPropertiesSet(); ldapProvider = provisioning.retrieveByOriginIgnoreActiveFlag(LDAP, IdentityZone.getUaaZoneId()); - assertNotNull(ldapProvider); - assertNotNull(ldapProvider.getCreated()); - assertNotNull(ldapProvider.getLastModified()); - assertEquals(LDAP, ldapProvider.getType()); - assertFalse(ldapProvider.isActive()); + assertThat(ldapProvider).isNotNull(); + assertThat(ldapProvider.getCreated()).isNotNull(); + assertThat(ldapProvider.getLastModified()).isNotNull(); + assertThat(ldapProvider.getType()).isEqualTo(LDAP); + assertThat(ldapProvider.isActive()).isFalse(); bootstrap.setLdapConfig(ldapConfig); bootstrap.afterPropertiesSet(); ldapProvider = provisioning.retrieveByOriginIgnoreActiveFlag(LDAP, IdentityZone.getUaaZoneId()); - assertNotNull(ldapProvider); - assertNotNull(ldapProvider.getCreated()); - assertNotNull(ldapProvider.getLastModified()); - assertEquals(LDAP, ldapProvider.getType()); - assertTrue(ldapProvider.isActive()); + assertThat(ldapProvider).isNotNull(); + assertThat(ldapProvider.getCreated()).isNotNull(); + assertThat(ldapProvider.getLastModified()).isNotNull(); + assertThat(ldapProvider.getType()).isEqualTo(LDAP); + assertThat(ldapProvider.isActive()).isTrue(); environment.setActiveProfiles("default"); bootstrap.afterPropertiesSet(); ldapProvider = provisioning.retrieveByOriginIgnoreActiveFlag(LDAP, IdentityZone.getUaaZoneId()); - assertNotNull(ldapProvider); - assertNotNull(ldapProvider.getCreated()); - assertNotNull(ldapProvider.getLastModified()); - assertEquals(LDAP, ldapProvider.getType()); - assertFalse(ldapProvider.isActive()); + assertThat(ldapProvider).isNotNull(); + assertThat(ldapProvider.getCreated()).isNotNull(); + assertThat(ldapProvider.getLastModified()).isNotNull(); + assertThat(ldapProvider.getType()).isEqualTo(LDAP); + assertThat(ldapProvider.isActive()).isFalse(); } @Test @@ -256,13 +247,13 @@ void keystoneProfileBootstrap() throws Exception { bootstrap.afterPropertiesSet(); IdentityProvider keystoneProvider = provisioning.retrieveByOriginIgnoreActiveFlag(KEYSTONE, IdentityZone.getUaaZoneId()); - assertNotNull(keystoneProvider); - assertEquals(new KeystoneIdentityProviderDefinition(), keystoneProvider.getConfig()); - assertNotNull(keystoneProvider.getCreated()); - assertNotNull(keystoneProvider.getLastModified()); - assertEquals(KEYSTONE, keystoneProvider.getType()); - assertNotNull(keystoneProvider.getConfig()); - assertNull(keystoneProvider.getConfig().getAdditionalConfiguration()); + assertThat(keystoneProvider).isNotNull(); + assertThat(keystoneProvider.getConfig()).isEqualTo(new KeystoneIdentityProviderDefinition()); + assertThat(keystoneProvider.getCreated()).isNotNull(); + assertThat(keystoneProvider.getLastModified()).isNotNull(); + assertThat(keystoneProvider.getType()).isEqualTo(KEYSTONE); + assertThat(keystoneProvider.getConfig()).isNotNull(); + assertThat(keystoneProvider.getConfig().getAdditionalConfiguration()).isNull(); } @Test @@ -273,11 +264,11 @@ void keystoneBootstrap() throws Exception { bootstrap.afterPropertiesSet(); IdentityProvider keystoneProvider = provisioning.retrieveByOriginIgnoreActiveFlag(KEYSTONE, IdentityZone.getUaaZoneId()); - assertNotNull(keystoneProvider); - assertEquals(new KeystoneIdentityProviderDefinition(keystoneConfig), keystoneProvider.getConfig()); - assertNotNull(keystoneProvider.getCreated()); - assertNotNull(keystoneProvider.getLastModified()); - assertEquals(KEYSTONE, keystoneProvider.getType()); + assertThat(keystoneProvider).isNotNull(); + assertThat(keystoneProvider.getConfig()).isEqualTo(new KeystoneIdentityProviderDefinition(keystoneConfig)); + assertThat(keystoneProvider.getCreated()).isNotNull(); + assertThat(keystoneProvider.getLastModified()).isNotNull(); + assertThat(keystoneProvider.getType()).isEqualTo(KEYSTONE); } @Test @@ -289,38 +280,38 @@ void removedKeystoneBootstrapIsInactive() throws Exception { bootstrap.afterPropertiesSet(); IdentityProvider keystoneProvider = provisioning.retrieveByOriginIgnoreActiveFlag(KEYSTONE, IdentityZone.getUaaZoneId()); - assertNotNull(keystoneProvider); - assertEquals(new KeystoneIdentityProviderDefinition(keystoneConfig), keystoneProvider.getConfig()); - assertNotNull(keystoneProvider.getCreated()); - assertNotNull(keystoneProvider.getLastModified()); - assertEquals(KEYSTONE, keystoneProvider.getType()); - assertTrue(keystoneProvider.isActive()); + assertThat(keystoneProvider).isNotNull(); + assertThat(keystoneProvider.getConfig()).isEqualTo(new KeystoneIdentityProviderDefinition(keystoneConfig)); + assertThat(keystoneProvider.getCreated()).isNotNull(); + assertThat(keystoneProvider.getLastModified()).isNotNull(); + assertThat(keystoneProvider.getType()).isEqualTo(KEYSTONE); + assertThat(keystoneProvider.isActive()).isTrue(); bootstrap.setKeystoneConfig(null); bootstrap.afterPropertiesSet(); keystoneProvider = provisioning.retrieveByOriginIgnoreActiveFlag(KEYSTONE, IdentityZone.getUaaZoneId()); - assertNotNull(keystoneProvider); - assertNotNull(keystoneProvider.getCreated()); - assertNotNull(keystoneProvider.getLastModified()); - assertEquals(KEYSTONE, keystoneProvider.getType()); - assertFalse(keystoneProvider.isActive()); + assertThat(keystoneProvider).isNotNull(); + assertThat(keystoneProvider.getCreated()).isNotNull(); + assertThat(keystoneProvider.getLastModified()).isNotNull(); + assertThat(keystoneProvider.getType()).isEqualTo(KEYSTONE); + assertThat(keystoneProvider.isActive()).isFalse(); bootstrap.setKeystoneConfig(keystoneConfig); bootstrap.afterPropertiesSet(); keystoneProvider = provisioning.retrieveByOriginIgnoreActiveFlag(KEYSTONE, IdentityZone.getUaaZoneId()); - assertNotNull(keystoneProvider); - assertEquals(new KeystoneIdentityProviderDefinition(keystoneConfig), keystoneProvider.getConfig()); - assertNotNull(keystoneProvider.getCreated()); - assertNotNull(keystoneProvider.getLastModified()); - assertEquals(KEYSTONE, keystoneProvider.getType()); - assertTrue(keystoneProvider.isActive()); + assertThat(keystoneProvider).isNotNull(); + assertThat(keystoneProvider.getConfig()).isEqualTo(new KeystoneIdentityProviderDefinition(keystoneConfig)); + assertThat(keystoneProvider.getCreated()).isNotNull(); + assertThat(keystoneProvider.getLastModified()).isNotNull(); + assertThat(keystoneProvider.getType()).isEqualTo(KEYSTONE); + assertThat(keystoneProvider.isActive()).isTrue(); } @Test void oauthAndOidcProviderDeletion() throws Exception { TestUtils.cleanAndSeedDb(jdbcTemplate); setOauthIDPWrappers(); - bootstrap.setOriginsToDelete(new LinkedList(oauthProviderConfig.keySet())); + bootstrap.setOriginsToDelete(new LinkedList<>(oauthProviderConfig.keySet())); bootstrap.afterPropertiesSet(); for (Map.Entry provider : oauthProviderConfig.entrySet()) { try { @@ -333,27 +324,22 @@ void oauthAndOidcProviderDeletion() throws Exception { } private void setOauthIDPWrappers() { - List wrappers = new LinkedList<>(); - oauthProviderConfig - .entrySet() - .forEach( - p -> { - IdentityProvider provider = new IdentityProvider(); - if (p.getValue() instanceof OIDCIdentityProviderDefinition) { - provider.setType(OIDC10); - } else if (p.getValue() instanceof RawExternalOAuthIdentityProviderDefinition) { - provider.setType(OAUTH20); - } - wrappers.add( - OauthIDPWrapperFactoryBean.getIdentityProviderWrapper( - p.getKey(), - p.getValue(), - provider, - true - ) + List wrappers = oauthProviderConfig.entrySet().stream() + .map(e -> { + IdentityProvider provider = new IdentityProvider<>(); + if (e.getValue() instanceof OIDCIdentityProviderDefinition) { + provider.setType(OIDC10); + } else if (e.getValue() instanceof RawExternalOAuthIdentityProviderDefinition) { + provider.setType(OAUTH20); + } + return + OauthIDPWrapperFactoryBean.getIdentityProviderWrapper( + e.getKey(), + e.getValue(), + provider, + true ); - } - ); + }).toList(); bootstrap.setOauthIdpDefinitions(wrappers); } @@ -372,28 +358,27 @@ void oauthAndOidcProviderActivation() throws Exception { bootstrap.afterPropertiesSet(); for (Map.Entry provider : oauthProviderConfig.entrySet()) { IdentityProvider bootstrapOauthProvider = provisioning.retrieveByOriginIgnoreActiveFlag(provider.getKey(), IdentityZone.getUaaZoneId()); - assertNotNull(bootstrapOauthProvider); - assertThat(oauthProviderConfig.values(), PredicateMatcher.has(c -> c.equals(bootstrapOauthProvider.getConfig()))); - assertNotNull(bootstrapOauthProvider.getCreated()); - assertNotNull(bootstrapOauthProvider.getLastModified()); - assertEquals(provider.getKey(), bootstrapOauthProvider.getType()); - assertTrue(bootstrapOauthProvider.isActive()); + assertThat(bootstrapOauthProvider).isNotNull(); + assertThat(oauthProviderConfig).containsValue(bootstrapOauthProvider.getConfig()); + assertThat(bootstrapOauthProvider.getCreated()).isNotNull(); + assertThat(bootstrapOauthProvider.getLastModified()).isNotNull(); + assertThat(bootstrapOauthProvider.getType()).isEqualTo(provider.getKey()); + assertThat(bootstrapOauthProvider.isActive()).isTrue(); } - } private void validateOauthOidcProvider(Map.Entry provider, IdentityProvider bootstrapOauthProvider) { - assertNotNull(bootstrapOauthProvider); - assertThat(oauthProviderConfig.values(), PredicateMatcher.has(c -> c.equals(bootstrapOauthProvider.getConfig()))); - assertNotNull(bootstrapOauthProvider.getCreated()); - assertNotNull(bootstrapOauthProvider.getLastModified()); - assertEquals(provider.getKey(), bootstrapOauthProvider.getType()); - assertTrue(bootstrapOauthProvider.isActive()); - assertTrue(bootstrapOauthProvider.getConfig().isStoreCustomAttributes()); //default + assertThat(bootstrapOauthProvider).isNotNull(); + assertThat(oauthProviderConfig).containsValue(bootstrapOauthProvider.getConfig()); + assertThat(bootstrapOauthProvider.getCreated()).isNotNull(); + assertThat(bootstrapOauthProvider.getLastModified()).isNotNull(); + assertThat(bootstrapOauthProvider.getType()).isEqualTo(provider.getKey()); + assertThat(bootstrapOauthProvider.isActive()).isTrue(); + assertThat(bootstrapOauthProvider.getConfig().isStoreCustomAttributes()).isTrue(); //default if (OIDC10.equals(provider.getKey())) { - assertEquals("code id_token", bootstrapOauthProvider.getConfig().getResponseType()); + assertThat(bootstrapOauthProvider.getConfig().getResponseType()).isEqualTo("code id_token"); } else { - assertEquals("code", bootstrapOauthProvider.getConfig().getResponseType()); + assertThat(bootstrapOauthProvider.getConfig().getResponseType()).isEqualTo("code"); } } @@ -419,7 +404,7 @@ void bootstrapFailsIfSamlAndOauthHaveTheSameAlias() throws Exception { setOauthIDPWrappers(); bootstrap.setSamlProviders(configurator); - assertThrows(IllegalArgumentException.class, () -> bootstrap.afterPropertiesSet()); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> bootstrap.afterPropertiesSet()); } private AbstractExternalOAuthIdentityProviderDefinition setCommonProperties(AbstractExternalOAuthIdentityProviderDefinition definition) throws MalformedURLException { @@ -442,12 +427,12 @@ void samlBootstrap() throws Exception { bootstrap.afterPropertiesSet(); IdentityProvider samlProvider = provisioning.retrieveByExternId(samlIdentityProviderDefinition.getIdpEntityAlias(), SAML, IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider); + assertThat(samlProvider).isNotNull(); samlIdentityProviderDefinition.setZoneId(IdentityZone.getUaaZoneId()); - assertEquals(samlIdentityProviderDefinition, samlProvider.getConfig()); - assertNotNull(samlProvider.getCreated()); - assertNotNull(samlProvider.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider.getType()); + assertThat(samlProvider.getConfig()).isEqualTo(samlIdentityProviderDefinition); + assertThat(samlProvider.getCreated()).isNotNull(); + assertThat(samlProvider.getLastModified()).isNotNull(); + assertThat(samlProvider.getType()).isEqualTo(OriginKeys.SAML); } @Test @@ -469,16 +454,13 @@ void providersDeletedAndNotCreated() throws Exception { ArgumentCaptor> captor = ArgumentCaptor.forClass(EntityDeletedEvent.class); verify(publisher, times(2)).publishEvent(captor.capture()); - assertThat( - captor - .getAllValues() - .stream() - .map( - p -> p.getDeleted().getOriginKey() - ).collect(toList() - ), - containsInAnyOrder(originsToDelete.toArray()) - ); + assertThat(captor + .getAllValues() + .stream() + .map( + p -> p.getDeleted().getOriginKey() + ).toList()) + .containsAll(originsToDelete); } private void configureSamlProviders(boolean override, SamlIdentityProviderDefinition... definitions) { @@ -504,10 +486,10 @@ void samlProviderOverrideFalse() throws Exception { IdentityProvider samlProvider = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); IdentityProvider samlProvider2 = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition1.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider); - assertNotNull(samlProvider2); - assertEquals("http://location", samlProvider.getConfig().getMetaDataLocation()); - assertEquals("http://location2", samlProvider2.getConfig().getMetaDataLocation()); + assertThat(samlProvider).isNotNull(); + assertThat(samlProvider2).isNotNull(); + assertThat(samlProvider.getConfig().getMetaDataLocation()).isEqualTo("http://location"); + assertThat(samlProvider2.getConfig().getMetaDataLocation()).isEqualTo("http://location2"); samlIdentityProviderDefinition.setMetaDataLocation("http://some.other.location"); samlIdentityProviderDefinition1.setMetaDataLocation("http://some.other.location"); @@ -517,10 +499,10 @@ void samlProviderOverrideFalse() throws Exception { samlProvider = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); samlProvider2 = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition1.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider); - assertNotNull(samlProvider2); - assertEquals("http://location", samlProvider.getConfig().getMetaDataLocation()); - assertEquals("http://location2", samlProvider2.getConfig().getMetaDataLocation()); + assertThat(samlProvider).isNotNull(); + assertThat(samlProvider.getConfig().getMetaDataLocation()).isEqualTo("http://location"); + assertThat(samlProvider2).isNotNull(); + assertThat(samlProvider2.getConfig().getMetaDataLocation()).isEqualTo("http://location2"); } @@ -532,62 +514,62 @@ void samlProviderNotDeactivated() throws Exception { bootstrap.afterPropertiesSet(); IdentityProvider samlProvider = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider); + assertThat(samlProvider).isNotNull(); samlIdentityProviderDefinition.setZoneId(IdentityZone.getUaaZoneId()); - assertEquals(samlIdentityProviderDefinition, samlProvider.getConfig()); - assertNotNull(samlProvider.getCreated()); - assertNotNull(samlProvider.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider.getType()); - assertTrue(samlProvider.isActive()); + assertThat(samlProvider.getConfig()).isEqualTo(samlIdentityProviderDefinition); + assertThat(samlProvider.getCreated()).isNotNull(); + assertThat(samlProvider.getLastModified()).isNotNull(); + assertThat(samlProvider.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider.isActive()).isTrue(); IdentityProvider samlProvider2 = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition1.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider2); + assertThat(samlProvider2).isNotNull(); samlIdentityProviderDefinition1.setZoneId(IdentityZone.getUaaZoneId()); - assertEquals(samlIdentityProviderDefinition1, samlProvider2.getConfig()); - assertNotNull(samlProvider2.getCreated()); - assertNotNull(samlProvider2.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider2.getType()); - assertTrue(samlProvider2.isActive()); + assertThat(samlProvider2.getConfig()).isEqualTo(samlIdentityProviderDefinition1); + assertThat(samlProvider2.getCreated()).isNotNull(); + assertThat(samlProvider2.getLastModified()).isNotNull(); + assertThat(samlProvider2.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider2.isActive()).isTrue(); configureSamlProviders(true, samlIdentityProviderDefinition); bootstrap.setSamlProviders(configurator); bootstrap.afterPropertiesSet(); samlProvider = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider); - assertEquals(samlIdentityProviderDefinition, samlProvider.getConfig()); - assertNotNull(samlProvider.getCreated()); - assertNotNull(samlProvider.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider.getType()); - assertTrue(samlProvider.isActive()); + assertThat(samlProvider).isNotNull(); + assertThat(samlProvider.getConfig()).isEqualTo(samlIdentityProviderDefinition); + assertThat(samlProvider.getCreated()).isNotNull(); + assertThat(samlProvider.getLastModified()).isNotNull(); + assertThat(samlProvider.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider.isActive()).isTrue(); samlProvider2 = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition1.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider2); - assertEquals(samlIdentityProviderDefinition1, samlProvider2.getConfig()); - assertNotNull(samlProvider2.getCreated()); - assertNotNull(samlProvider2.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider2.getType()); - assertTrue(samlProvider2.isActive()); + assertThat(samlProvider2).isNotNull(); + assertThat(samlProvider2.getConfig()).isEqualTo(samlIdentityProviderDefinition1); + assertThat(samlProvider2.getCreated()).isNotNull(); + assertThat(samlProvider2.getLastModified()).isNotNull(); + assertThat(samlProvider2.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider2.isActive()).isTrue(); configureSamlProviders(true, samlIdentityProviderDefinition1); bootstrap.setSamlProviders(configurator); bootstrap.afterPropertiesSet(); samlProvider = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider); - assertEquals(samlIdentityProviderDefinition, samlProvider.getConfig()); - assertNotNull(samlProvider.getCreated()); - assertNotNull(samlProvider.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider.getType()); - assertTrue(samlProvider.isActive()); + assertThat(samlProvider).isNotNull(); + assertThat(samlProvider.getConfig()).isEqualTo(samlIdentityProviderDefinition); + assertThat(samlProvider.getCreated()).isNotNull(); + assertThat(samlProvider.getLastModified()).isNotNull(); + assertThat(samlProvider.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider.isActive()).isTrue(); samlProvider2 = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition1.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider2); - assertEquals(samlIdentityProviderDefinition1, samlProvider2.getConfig()); - assertNotNull(samlProvider2.getCreated()); - assertNotNull(samlProvider2.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider2.getType()); - assertTrue(samlProvider2.isActive()); + assertThat(samlProvider2).isNotNull(); + assertThat(samlProvider2.getConfig()).isEqualTo(samlIdentityProviderDefinition1); + assertThat(samlProvider2.getCreated()).isNotNull(); + assertThat(samlProvider2.getLastModified()).isNotNull(); + assertThat(samlProvider2.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider2.isActive()).isTrue(); configurator = mock(BootstrapSamlIdentityProviderData.class); when(configurator.getIdentityProviderDefinitions()).thenReturn(new LinkedList<>()); @@ -595,20 +577,20 @@ void samlProviderNotDeactivated() throws Exception { bootstrap.afterPropertiesSet(); samlProvider = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider); - assertEquals(samlIdentityProviderDefinition, samlProvider.getConfig()); - assertNotNull(samlProvider.getCreated()); - assertNotNull(samlProvider.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider.getType()); - assertTrue(samlProvider.isActive()); + assertThat(samlProvider).isNotNull(); + assertThat(samlProvider.getConfig()).isEqualTo(samlIdentityProviderDefinition); + assertThat(samlProvider.getCreated()).isNotNull(); + assertThat(samlProvider.getLastModified()).isNotNull(); + assertThat(samlProvider.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider.isActive()).isTrue(); samlProvider2 = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition1.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider2); - assertEquals(samlIdentityProviderDefinition1, samlProvider2.getConfig()); - assertNotNull(samlProvider2.getCreated()); - assertNotNull(samlProvider2.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider2.getType()); - assertTrue(samlProvider2.isActive()); + assertThat(samlProvider2).isNotNull(); + assertThat(samlProvider2.getConfig()).isEqualTo(samlIdentityProviderDefinition1); + assertThat(samlProvider2.getCreated()).isNotNull(); + assertThat(samlProvider2.getLastModified()).isNotNull(); + assertThat(samlProvider2.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider2.isActive()).isTrue(); configurator = mock(BootstrapSamlIdentityProviderData.class); when(configurator.getIdentityProviderDefinitions()).thenReturn(Arrays.asList(samlIdentityProviderDefinition1, samlIdentityProviderDefinition)); @@ -616,20 +598,20 @@ void samlProviderNotDeactivated() throws Exception { bootstrap.afterPropertiesSet(); samlProvider = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider); - assertEquals(samlIdentityProviderDefinition, samlProvider.getConfig()); - assertNotNull(samlProvider.getCreated()); - assertNotNull(samlProvider.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider.getType()); - assertTrue(samlProvider.isActive()); + assertThat(samlProvider).isNotNull(); + assertThat(samlProvider.getConfig()).isEqualTo(samlIdentityProviderDefinition); + assertThat(samlProvider.getCreated()).isNotNull(); + assertThat(samlProvider.getLastModified()).isNotNull(); + assertThat(samlProvider.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider.isActive()).isTrue(); samlProvider2 = provisioning.retrieveByOriginIgnoreActiveFlag(samlIdentityProviderDefinition1.getIdpEntityAlias(), IdentityZone.getUaaZoneId()); - assertNotNull(samlProvider2); - assertEquals(samlIdentityProviderDefinition1, samlProvider2.getConfig()); - assertNotNull(samlProvider2.getCreated()); - assertNotNull(samlProvider2.getLastModified()); - assertEquals(OriginKeys.SAML, samlProvider2.getType()); - assertTrue(samlProvider2.isActive()); + assertThat(samlProvider2).isNotNull(); + assertThat(samlProvider2.getConfig()).isEqualTo(samlIdentityProviderDefinition1); + assertThat(samlProvider2.getCreated()).isNotNull(); + assertThat(samlProvider2.getLastModified()).isNotNull(); + assertThat(samlProvider2.getType()).isEqualTo(OriginKeys.SAML); + assertThat(samlProvider2.isActive()).isTrue(); } @Test @@ -655,7 +637,7 @@ private void setDisableInternalUserManagement(String expectedValue) throws Excep if (expectedValue == null) { expectedValue = "false"; } - assertEquals(Boolean.valueOf(expectedValue), internalIDP.getConfig().isDisableInternalUserManagement()); + assertThat(internalIDP.getConfig().isDisableInternalUserManagement()).isEqualTo(Boolean.valueOf(expectedValue)); } @Test @@ -665,13 +647,13 @@ void setPasswordPolicyToInternalIDP() throws Exception { IdentityProvider internalIDP = provisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, IdentityZone.getUaaZoneId()); PasswordPolicy passwordPolicy = internalIDP.getConfig().getPasswordPolicy(); - assertEquals(123, passwordPolicy.getMinLength()); - assertEquals(4567, passwordPolicy.getMaxLength()); - assertEquals(1, passwordPolicy.getRequireUpperCaseCharacter()); - assertEquals(0, passwordPolicy.getRequireLowerCaseCharacter()); - assertEquals(1, passwordPolicy.getRequireDigit()); - assertEquals(0, passwordPolicy.getRequireSpecialCharacter()); - assertEquals(6, passwordPolicy.getExpirePasswordInMonths()); + assertThat(passwordPolicy.getMinLength()).isEqualTo(123); + assertThat(passwordPolicy.getMaxLength()).isEqualTo(4567); + assertThat(passwordPolicy.getRequireUpperCaseCharacter()).isOne(); + assertThat(passwordPolicy.getRequireLowerCaseCharacter()).isZero(); + assertThat(passwordPolicy.getRequireDigit()).isOne(); + assertThat(passwordPolicy.getRequireSpecialCharacter()).isZero(); + assertThat(passwordPolicy.getExpirePasswordInMonths()).isEqualTo(6); } @Test @@ -686,9 +668,9 @@ void setLockoutPolicyToInternalIDP() throws Exception { IdentityProvider internalIDP = provisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, IdentityZone.getUaaZoneId()); lockoutPolicy = internalIDP.getConfig().getLockoutPolicy(); - assertEquals(123, lockoutPolicy.getLockoutPeriodSeconds()); - assertEquals(3, lockoutPolicy.getLockoutAfterFailures()); - assertEquals(343, lockoutPolicy.getCountFailuresWithin()); + assertThat(lockoutPolicy.getLockoutPeriodSeconds()).isEqualTo(123); + assertThat(lockoutPolicy.getLockoutAfterFailures()).isEqualTo(3); + assertThat(lockoutPolicy.getCountFailuresWithin()).isEqualTo(343); } @Test @@ -697,19 +679,19 @@ void deactivateAndActivateInternalIDP() throws Exception { bootstrap.afterPropertiesSet(); IdentityProvider internalIdp = provisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, IdentityZone.getUaaZoneId()); - assertFalse(internalIdp.isActive()); + assertThat(internalIdp.isActive()).isFalse(); environment.setProperty("disableInternalAuth", "false"); bootstrap.afterPropertiesSet(); internalIdp = provisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, IdentityZone.getUaaZoneId()); - assertTrue(internalIdp.isActive()); + assertThat(internalIdp.isActive()).isTrue(); } @Test void defaultActiveFlagOnInternalIDP() throws Exception { bootstrap.afterPropertiesSet(); IdentityProvider internalIdp = provisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, IdentityZone.getUaaZoneId()); - assertTrue(internalIdp.isActive()); + assertThat(internalIdp.isActive()).isTrue(); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index faf0506a61d..42783edc4f1 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -4,7 +4,6 @@ import org.cloudfoundry.identity.uaa.annotations.WithDatabaseContext; import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; import org.cloudfoundry.identity.uaa.login.Prompt; -import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils; import org.cloudfoundry.identity.uaa.test.TestUtils; import org.cloudfoundry.identity.uaa.zone.ClientSecretPolicy; import org.cloudfoundry.identity.uaa.zone.GeneralIdentityZoneConfigurationValidator; @@ -30,40 +29,37 @@ import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat.JWT; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.certificate1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.key1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.passphrase1; @WithDatabaseContext public class IdentityZoneConfigurationBootstrapTests { - public static final String PRIVATE_KEY = - "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXAIBAAKBgQDErZsZY70QAa7WdDD6eOv3RLBA4I5J0zZOiXMzoFB5yh64q0sm\n" + - "ESNtV4payOYE5TnHxWjMo0y7gDsGjI1omAG6wgfyp63I9WcLX7FDLyee43fG5+b9\n" + - "roofosL+OzJSXESSulsT9Y1XxSFFM5RMu4Ie9uM4/izKLCsAKiggMhnAmQIDAQAB\n" + - "AoGAAs2OllALk7zSZxAE2qz6f+2krWgF3xt5fKkM0UGJpBKzWWJnkcVQwfArcpvG\n" + - "W2+A4U347mGtaEatkKxUH5d6/s37jfRI7++HFXcLf6QJPmuE3+FtB2mX0lVJoaJb\n" + - "RLh+tOtt4ZJRAt/u6RjUCVNpDnJB6NZ032bpL3DijfNkRuECQQDkJR+JJPUpQGoI\n" + - "voPqcLl0i1tLX93XE7nu1YuwdQ5SmRaS0IJMozoBLBfFNmCWlSHaQpBORc38+eGC\n" + - "J9xsOrBNAkEA3LD1JoNI+wPSo/o71TED7BoVdwCXLKPqm0TnTr2EybCUPLNoff8r\n" + - "Ngm51jXc8mNvUkBtYiPfMKzpdqqFBWXXfQJAQ7D0E2gAybWQAHouf7/kdrzmYI3Y\n" + - "L3lt4HxBzyBcGIvNk9AD6SNBEZn4j44byHIFMlIvqNmzTY0CqPCUyRP8vQJBALXm\n" + - "ANmygferKfXP7XsFwGbdBO4mBXRc0qURwNkMqiMXMMdrVGftZq9Oiua9VJRQUtPn\n" + - "mIC4cmCLVI5jc+qEC30CQE+eOXomzxNNPxVnIp5k5f+savOWBBu83J2IoT2znnGb\n" + - "wTKZHjWybPHsW2q8Z6Moz5dvE+XMd11c5NtIG2/L97I=\n" + - "-----END RSA PRIVATE KEY-----"; + public static final String PRIVATE_KEY = """ + -----BEGIN RSA PRIVATE KEY----- + MIICXAIBAAKBgQDErZsZY70QAa7WdDD6eOv3RLBA4I5J0zZOiXMzoFB5yh64q0sm + ESNtV4payOYE5TnHxWjMo0y7gDsGjI1omAG6wgfyp63I9WcLX7FDLyee43fG5+b9 + roofosL+OzJSXESSulsT9Y1XxSFFM5RMu4Ie9uM4/izKLCsAKiggMhnAmQIDAQAB + AoGAAs2OllALk7zSZxAE2qz6f+2krWgF3xt5fKkM0UGJpBKzWWJnkcVQwfArcpvG + W2+A4U347mGtaEatkKxUH5d6/s37jfRI7++HFXcLf6QJPmuE3+FtB2mX0lVJoaJb + RLh+tOtt4ZJRAt/u6RjUCVNpDnJB6NZ032bpL3DijfNkRuECQQDkJR+JJPUpQGoI + voPqcLl0i1tLX93XE7nu1YuwdQ5SmRaS0IJMozoBLBfFNmCWlSHaQpBORc38+eGC + J9xsOrBNAkEA3LD1JoNI+wPSo/o71TED7BoVdwCXLKPqm0TnTr2EybCUPLNoff8r + Ngm51jXc8mNvUkBtYiPfMKzpdqqFBWXXfQJAQ7D0E2gAybWQAHouf7/kdrzmYI3Y + L3lt4HxBzyBcGIvNk9AD6SNBEZn4j44byHIFMlIvqNmzTY0CqPCUyRP8vQJBALXm + ANmygferKfXP7XsFwGbdBO4mBXRc0qURwNkMqiMXMMdrVGftZq9Oiua9VJRQUtPn + mIC4cmCLVI5jc+qEC30CQE+eOXomzxNNPxVnIp5k5f+savOWBBu83J2IoT2znnGb + wTKZHjWybPHsW2q8Z6Moz5dvE+XMd11c5NtIG2/L97I= + -----END RSA PRIVATE KEY-----"""; private static final String ID = "id"; private IdentityZoneProvisioning provisioning; private IdentityZoneConfigurationBootstrap bootstrap; - private Map links = new HashMap<>(); - private GeneralIdentityZoneValidator validator; + private final Map links = new HashMap<>(); @BeforeEach void configureProvisioning(@Autowired JdbcTemplate jdbcTemplate) throws SQLException { @@ -73,7 +69,7 @@ void configureProvisioning(@Autowired JdbcTemplate jdbcTemplate) throws SQLExcep GeneralIdentityZoneConfigurationValidator configValidator = new GeneralIdentityZoneConfigurationValidator(); - validator = new GeneralIdentityZoneValidator(configValidator); + GeneralIdentityZoneValidator validator = new GeneralIdentityZoneValidator(configValidator); bootstrap.setValidator(validator); //For the SamlTestUtils keys we are using. @@ -81,104 +77,109 @@ void configureProvisioning(@Autowired JdbcTemplate jdbcTemplate) throws SQLExcep } @Test - void testClientSecretPolicy() throws Exception { + void clientSecretPolicy() throws Exception { bootstrap.setClientSecretPolicy(new ClientSecretPolicy(0, 255, 0, 1, 1, 1, 6)); bootstrap.afterPropertiesSet(); IdentityZone uaa = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertEquals(0, uaa.getConfig().getClientSecretPolicy().getMinLength()); - assertEquals(255, uaa.getConfig().getClientSecretPolicy().getMaxLength()); - assertEquals(0, uaa.getConfig().getClientSecretPolicy().getRequireUpperCaseCharacter()); - assertEquals(1, uaa.getConfig().getClientSecretPolicy().getRequireLowerCaseCharacter()); - assertEquals(1, uaa.getConfig().getClientSecretPolicy().getRequireDigit()); - assertEquals(1, uaa.getConfig().getClientSecretPolicy().getRequireSpecialCharacter()); - assertEquals(-1, uaa.getConfig().getClientSecretPolicy().getExpireSecretInMonths()); + assertThat(uaa.getConfig().getClientSecretPolicy().getMinLength()).isZero(); + assertThat(uaa.getConfig().getClientSecretPolicy().getMaxLength()).isEqualTo(255); + assertThat(uaa.getConfig().getClientSecretPolicy().getRequireUpperCaseCharacter()).isZero(); + assertThat(uaa.getConfig().getClientSecretPolicy().getRequireLowerCaseCharacter()).isOne(); + assertThat(uaa.getConfig().getClientSecretPolicy().getRequireDigit()).isOne(); + assertThat(uaa.getConfig().getClientSecretPolicy().getRequireSpecialCharacter()).isOne(); + assertThat(uaa.getConfig().getClientSecretPolicy().getExpireSecretInMonths()).isEqualTo(-1); } @Test - void test_multiple_keys() throws InvalidIdentityZoneDetailsException { - bootstrap.setSamlSpPrivateKey(SamlTestUtils.PROVIDER_PRIVATE_KEY); - bootstrap.setSamlSpCertificate(SamlTestUtils.PROVIDER_CERTIFICATE); - bootstrap.setSamlSpPrivateKeyPassphrase(SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD); + void multipleKeys() throws InvalidIdentityZoneDetailsException { + bootstrap.setSamlSpPrivateKey(key1()); + bootstrap.setSamlSpCertificate(certificate1()); + bootstrap.setSamlSpPrivateKeyPassphrase(passphrase1()); Map> keys = new HashMap<>(); Map key1 = new HashMap<>(); - key1.put("key", SamlTestUtils.PROVIDER_PRIVATE_KEY); - key1.put("passphrase", SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD); - key1.put("certificate", SamlTestUtils.PROVIDER_CERTIFICATE); + key1.put("key", key1()); + key1.put("passphrase", passphrase1()); + key1.put("certificate", certificate1()); keys.put("Key1", key1); bootstrap.setActiveKeyId("KEY1"); bootstrap.setSamlKeys(keys); bootstrap.afterPropertiesSet(); IdentityZone uaa = provisioning.retrieve(IdentityZone.getUaaZoneId()); SamlConfig config = uaa.getConfig().getSamlConfig(); - assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY, config.getPrivateKey()); - assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD, config.getPrivateKeyPassword()); - assertEquals(SamlTestUtils.PROVIDER_CERTIFICATE, config.getCertificate()); + assertThat(config.getPrivateKey()).isEqualTo(key1()); + assertThat(config.getPrivateKeyPassword()).isEqualTo(passphrase1()); + assertThat(config.getCertificate()).isEqualTo(certificate1()); - assertEquals("key1", config.getActiveKeyId()); - assertEquals(2, config.getKeys().size()); + assertThat(config.getActiveKeyId()).isEqualTo("key1"); + assertThat(config.getKeys()).hasSize(2); - assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY, config.getKeys().get("key1").getKey()); - assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD, config.getKeys().get("key1").getPassphrase()); - assertEquals(SamlTestUtils.PROVIDER_CERTIFICATE, config.getKeys().get("key1").getCertificate()); + assertThat(config.getKeys().get("key1").getKey()).isEqualTo(key1()); + assertThat(config.getKeys().get("key1").getPassphrase()).isEqualTo(passphrase1()); + assertThat(config.getKeys().get("key1").getCertificate()).isEqualTo(certificate1()); } @Test - void test_keyId_null_exception() { - bootstrap.setSamlSpPrivateKey(SamlTestUtils.PROVIDER_PRIVATE_KEY); - bootstrap.setSamlSpCertificate(SamlTestUtils.PROVIDER_CERTIFICATE); - bootstrap.setSamlSpPrivateKeyPassphrase(SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD); + void keyIdNullException() { + bootstrap.setSamlSpPrivateKey(key1()); + bootstrap.setSamlSpCertificate(certificate1()); + bootstrap.setSamlSpPrivateKeyPassphrase(passphrase1()); Map> keys = new HashMap<>(); Map key1 = new HashMap<>(); - key1.put("key", SamlTestUtils.PROVIDER_PRIVATE_KEY); - key1.put("passphrase", SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD); - key1.put("certificate", SamlTestUtils.PROVIDER_CERTIFICATE); + key1.put("key", key1()); + key1.put("passphrase", passphrase1()); + key1.put("certificate", certificate1()); keys.put(null, key1); bootstrap.setActiveKeyId(null); bootstrap.setSamlKeys(keys); - assertThrows(InvalidIdentityZoneDetailsException.class, () -> bootstrap.afterPropertiesSet()); + assertThatExceptionOfType(InvalidIdentityZoneDetailsException.class).isThrownBy(() -> bootstrap.afterPropertiesSet()); } @Test - void testDefaultSamlKeys() throws Exception { - bootstrap.setSamlSpPrivateKey(SamlTestUtils.PROVIDER_PRIVATE_KEY); - bootstrap.setSamlSpCertificate(SamlTestUtils.PROVIDER_CERTIFICATE); - bootstrap.setSamlSpPrivateKeyPassphrase(SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD); + void samlKeysAndSigningConfigs() throws Exception { + bootstrap.setSamlSpPrivateKey(key1()); + bootstrap.setSamlSpCertificate(certificate1()); + bootstrap.setSamlSpPrivateKeyPassphrase(passphrase1()); + bootstrap.setSamlWantAssertionSigned(false); + bootstrap.setSamlRequestSigned(false); bootstrap.afterPropertiesSet(); + IdentityZone uaa = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY, uaa.getConfig().getSamlConfig().getPrivateKey()); - assertEquals(SamlTestUtils.PROVIDER_PRIVATE_KEY_PASSWORD, uaa.getConfig().getSamlConfig().getPrivateKeyPassword()); - assertEquals(SamlTestUtils.PROVIDER_CERTIFICATE, uaa.getConfig().getSamlConfig().getCertificate()); + assertThat(uaa.getConfig().getSamlConfig().getPrivateKey()).isEqualTo(key1()); + assertThat(uaa.getConfig().getSamlConfig().getPrivateKeyPassword()).isEqualTo(passphrase1()); + assertThat(uaa.getConfig().getSamlConfig().getCertificate()).isEqualTo(certificate1()); + assertThat(uaa.getConfig().getSamlConfig().isWantAssertionSigned()).isFalse(); + assertThat(uaa.getConfig().getSamlConfig().isRequestSigned()).isFalse(); } @Test - void enable_in_response_to() throws Exception { + void enableInResponseTo() throws Exception { bootstrap.setDisableSamlInResponseToCheck(false); bootstrap.afterPropertiesSet(); IdentityZone uaa = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertFalse(uaa.getConfig().getSamlConfig().isDisableInResponseToCheck()); + assertThat(uaa.getConfig().getSamlConfig().isDisableInResponseToCheck()).isFalse(); } @Test - void saml_disable_in_response_to() throws Exception { + void disableInResponseTo() throws Exception { bootstrap.setDisableSamlInResponseToCheck(true); bootstrap.afterPropertiesSet(); IdentityZone uaa = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertTrue(uaa.getConfig().getSamlConfig().isDisableInResponseToCheck()); + assertThat(uaa.getConfig().getSamlConfig().isDisableInResponseToCheck()).isTrue(); } @Test - void testDefaultGroups() throws Exception { + void defaultGroups() throws Exception { UserConfig defaultUserConfig = new UserConfig(); String[] groups = {"group1", "group2", "group3"}; defaultUserConfig.setDefaultGroups(Arrays.asList(groups)); bootstrap.setDefaultUserConfig(defaultUserConfig); bootstrap.afterPropertiesSet(); IdentityZone uaa = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertThat(uaa.getConfig().getUserConfig().getDefaultGroups(), containsInAnyOrder(groups)); + assertThat(uaa.getConfig().getUserConfig().getDefaultGroups()).contains(groups); } @Test - void testAllowedGroups() throws Exception { + void allowedGroups() throws Exception { UserConfig defaultUserConfig = new UserConfig(); String[] groups = {"group1", "group2", "group3"}; defaultUserConfig.setDefaultGroups(Arrays.asList(groups)); @@ -186,11 +187,11 @@ void testAllowedGroups() throws Exception { bootstrap.setDefaultUserConfig(defaultUserConfig); bootstrap.afterPropertiesSet(); IdentityZone uaa = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertThat(uaa.getConfig().getUserConfig().resultingAllowedGroups(), containsInAnyOrder(groups)); + assertThat(uaa.getConfig().getUserConfig().resultingAllowedGroups()).contains(groups); } @Test - void tokenPolicy_configured_fromValuesInYaml() throws Exception { + void tokenPolicyConfiguredFromValuesInYaml() throws Exception { TokenPolicy tokenPolicy = new TokenPolicy(); Map keys = new HashMap<>(); keys.put(ID, PRIVATE_KEY); @@ -204,69 +205,68 @@ void tokenPolicy_configured_fromValuesInYaml() throws Exception { IdentityZone zone = provisioning.retrieve(IdentityZone.getUaaZoneId()); IdentityZoneConfiguration definition = zone.getConfig(); - assertEquals(3600, definition.getTokenPolicy().getAccessTokenValidity()); - assertFalse(definition.getTokenPolicy().isRefreshTokenUnique()); - assertEquals(JWT.getStringValue(), definition.getTokenPolicy().getRefreshTokenFormat()); - assertEquals(PRIVATE_KEY, definition.getTokenPolicy().getKeys().get(ID).getSigningKey()); + assertThat(definition.getTokenPolicy().getAccessTokenValidity()).isEqualTo(3600); + assertThat(definition.getTokenPolicy().isRefreshTokenUnique()).isFalse(); + assertThat(definition.getTokenPolicy().getRefreshTokenFormat()).isEqualTo(JWT.getStringValue()); + assertThat(definition.getTokenPolicy().getKeys().get(ID).getSigningKey()).isEqualTo(PRIVATE_KEY); } @Test - void disable_self_service_links() throws Exception { + void disableSelfServiceLinks() throws Exception { bootstrap.setSelfServiceLinksEnabled(false); bootstrap.afterPropertiesSet(); IdentityZone zone = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertFalse(zone.getConfig().getLinks().getSelfService().isSelfServiceLinksEnabled()); + assertThat(zone.getConfig().getLinks().getSelfService().isSelfServiceLinksEnabled()).isFalse(); } @Test - void set_home_redirect() throws Exception { - bootstrap.setHomeRedirect("http://some.redirect.com/redirect"); + void setHomeRedirect() throws Exception { + bootstrap.setHomeRedirect("https://some.redirect.com/redirect"); bootstrap.afterPropertiesSet(); IdentityZone zone = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertEquals("http://some.redirect.com/redirect", zone.getConfig().getLinks().getHomeRedirect()); + assertThat(zone.getConfig().getLinks().getHomeRedirect()).isEqualTo("https://some.redirect.com/redirect"); } @Test - void signup_link_configured() throws Exception { + void signupLinkConfigured() throws Exception { links.put("signup", "/configured_signup"); bootstrap.setSelfServiceLinks(links); bootstrap.afterPropertiesSet(); IdentityZone zone = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertEquals("/configured_signup", zone.getConfig().getLinks().getSelfService().getSignup()); - assertNull(zone.getConfig().getLinks().getSelfService().getPasswd()); + assertThat(zone.getConfig().getLinks().getSelfService().getSignup()).isEqualTo("/configured_signup"); + assertThat(zone.getConfig().getLinks().getSelfService().getPasswd()).isNull(); } @Test - void passwd_link_configured() throws Exception { + void passwdLinkConfigured() throws Exception { links.put("passwd", "/configured_passwd"); bootstrap.setSelfServiceLinks(links); bootstrap.afterPropertiesSet(); IdentityZone zone = provisioning.retrieve(IdentityZone.getUaaZoneId()); - assertNull(zone.getConfig().getLinks().getSelfService().getSignup()); - assertEquals("/configured_passwd", zone.getConfig().getLinks().getSelfService().getPasswd()); + assertThat(zone.getConfig().getLinks().getSelfService().getSignup()).isNull(); + assertThat(zone.getConfig().getLinks().getSelfService().getPasswd()).isEqualTo("/configured_passwd"); } @Test - void test_logout_redirect() throws Exception { + void logoutRedirect() throws Exception { bootstrap.setLogoutDefaultRedirectUrl("/configured_login"); bootstrap.setLogoutDisableRedirectParameter(false); bootstrap.setLogoutRedirectParameterName("test"); bootstrap.setLogoutRedirectWhitelist(Collections.singletonList("http://single-url")); bootstrap.afterPropertiesSet(); IdentityZoneConfiguration config = provisioning.retrieve(IdentityZone.getUaaZoneId()).getConfig(); - assertEquals("/configured_login", config.getLinks().getLogout().getRedirectUrl()); - assertEquals("test", config.getLinks().getLogout().getRedirectParameterName()); - assertEquals(Collections.singletonList("http://single-url"), config.getLinks().getLogout().getWhitelist()); - assertFalse(config.getLinks().getLogout().isDisableRedirectParameter()); + assertThat(config.getLinks().getLogout().getRedirectUrl()).isEqualTo("/configured_login"); + assertThat(config.getLinks().getLogout().getRedirectParameterName()).isEqualTo("test"); + assertThat(config.getLinks().getLogout().getWhitelist()).isEqualTo(Collections.singletonList("http://single-url")); + assertThat(config.getLinks().getLogout().isDisableRedirectParameter()).isFalse(); } - @Test - void test_prompts() throws Exception { + void testPrompts() throws Exception { List prompts = Arrays.asList( new Prompt("name1", "type1", "text1"), new Prompt("name2", "type2", "text2") @@ -274,7 +274,7 @@ void test_prompts() throws Exception { bootstrap.setPrompts(prompts); bootstrap.afterPropertiesSet(); IdentityZoneConfiguration config = provisioning.retrieve(IdentityZone.getUaaZoneId()).getConfig(); - assertEquals(prompts, config.getPrompts()); + assertThat(config.getPrompts()).isEqualTo(prompts); } @Test @@ -282,6 +282,6 @@ void idpDiscoveryEnabled() throws Exception { bootstrap.setIdpDiscoveryEnabled(true); bootstrap.afterPropertiesSet(); IdentityZoneConfiguration config = provisioning.retrieve(IdentityZone.getUaaZoneId()).getConfig(); - assertTrue(config.isIdpDiscoveryEnabled()); + assertThat(config.isIdpDiscoveryEnabled()).isTrue(); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java index b9ffe2e779c..61d16f1666d 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java @@ -33,15 +33,7 @@ import java.util.Arrays; import java.util.Collections; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.HttpHeaders.ACCEPT; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; @@ -61,23 +53,14 @@ public void configure() { public void default_user_groups_when_json_is_deserialized() { definition.setUserConfig(null); String s = JsonUtils.writeValueAsString(definition); - assertThat(s, not(containsString("userConfig"))); + assertThat(s).doesNotContain("userConfig"); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); - assertNotNull(definition.getUserConfig()); - assertThat(definition.getUserConfig().getDefaultGroups(), containsInAnyOrder( - "openid", - "password.write", - "uaa.user", - "approvals.me", - "profile", - "roles", - "user_attributes", - "uaa.offline_token" - )); - assertNull(definition.getUserConfig().resultingAllowedGroups()); + assertThat(definition.getUserConfig()).isNotNull(); + assertThat(definition.getUserConfig().getDefaultGroups()).contains("openid", "password.write", "uaa.user", "approvals.me", "profile", "roles", "user_attributes", "uaa.offline_token"); + assertThat(definition.getUserConfig().resultingAllowedGroups()).isNull(); s = JsonUtils.writeValueAsString(definition); - assertThat(s, containsString("userConfig")); - assertThat(s, containsString("uaa.offline_token")); + assertThat(s).contains("userConfig") + .contains("uaa.offline_token"); } @Test @@ -154,27 +137,27 @@ public void deserializeZmsJSON_withUnknownProperties_doesNotFail() { @Test public void test_want_assertion_signed_setters() { - assertTrue(definition.getSamlConfig().isRequestSigned()); + assertThat(definition.getSamlConfig().isRequestSigned()).isTrue(); definition = JsonUtils.readValue(JsonUtils.writeValueAsString(definition), IdentityZoneConfiguration.class); - assertTrue(definition.getSamlConfig().isRequestSigned()); + assertThat(definition.getSamlConfig().isRequestSigned()).isTrue(); definition.getSamlConfig().setRequestSigned(false); - assertFalse(definition.getSamlConfig().isRequestSigned()); + assertThat(definition.getSamlConfig().isRequestSigned()).isFalse(); } @Test public void test_disable_redirect_flag_vestigial() { definition.getLinks().getLogout().setDisableRedirectParameter(true); - assertFalse("setting disableRedirectParameter should not have worked.", definition.getLinks().getLogout().isDisableRedirectParameter()); + assertThat(definition.getLinks().getLogout().isDisableRedirectParameter()).as("setting disableRedirectParameter should not have worked.").isFalse(); } @Test public void test_request_signed_setters() { - assertTrue(definition.getSamlConfig().isWantAssertionSigned()); + assertThat(definition.getSamlConfig().isWantAssertionSigned()).isTrue(); definition = JsonUtils.readValue(JsonUtils.writeValueAsString(definition), IdentityZoneConfiguration.class); - assertTrue(definition.getSamlConfig().isWantAssertionSigned()); + assertThat(definition.getSamlConfig().isWantAssertionSigned()).isTrue(); definition.getSamlConfig().setWantAssertionSigned(false); - assertFalse(definition.getSamlConfig().isWantAssertionSigned()); + assertThat(definition.getSamlConfig().isWantAssertionSigned()).isFalse(); } @Test @@ -182,47 +165,47 @@ public void testDeserialize_Without_SamlConfig() { String s = JsonUtils.writeValueAsString(definition); s = s.replace(",\"samlConfig\":{\"requestSigned\":false,\"wantAssertionSigned\":true}",""); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); - assertTrue(definition.getSamlConfig().isRequestSigned()); - assertTrue(definition.getSamlConfig().isWantAssertionSigned()); + assertThat(definition.getSamlConfig().isRequestSigned()).isTrue(); + assertThat(definition.getSamlConfig().isWantAssertionSigned()).isTrue(); definition.getSamlConfig().setWantAssertionSigned(true); definition.getSamlConfig().setRequestSigned(true); s = JsonUtils.writeValueAsString(definition); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); - assertTrue(definition.getSamlConfig().isRequestSigned()); - assertTrue(definition.getSamlConfig().isWantAssertionSigned()); + assertThat(definition.getSamlConfig().isRequestSigned()).isTrue(); + assertThat(definition.getSamlConfig().isWantAssertionSigned()).isTrue(); definition.getSamlConfig().setWantAssertionSigned(false); definition.getSamlConfig().setRequestSigned(false); s = JsonUtils.writeValueAsString(definition); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); - assertFalse(definition.getSamlConfig().isRequestSigned()); - assertFalse(definition.getSamlConfig().isWantAssertionSigned()); + assertThat(definition.getSamlConfig().isRequestSigned()).isFalse(); + assertThat(definition.getSamlConfig().isWantAssertionSigned()).isFalse(); } @Test public void testDeserialize_With_SamlConfig() { - assertFalse(definition.getSamlConfig().isDisableInResponseToCheck()); + assertThat(definition.getSamlConfig().isDisableInResponseToCheck()).isFalse(); String s = JsonUtils.writeValueAsString(definition); s = s.replace("\"wantAssertionSigned\":true","\"wantAssertionSigned\":false"); s = s.replace("\"disableInResponseToCheck\":false","\"disableInResponseToCheck\":true"); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); - assertTrue(definition.getSamlConfig().isRequestSigned()); - assertFalse(definition.getSamlConfig().isWantAssertionSigned()); - assertTrue(definition.getSamlConfig().isDisableInResponseToCheck()); + assertThat(definition.getSamlConfig().isRequestSigned()).isTrue(); + assertThat(definition.getSamlConfig().isWantAssertionSigned()).isFalse(); + assertThat(definition.getSamlConfig().isDisableInResponseToCheck()).isTrue(); s = s.replace("\"disableInResponseToCheck\":true,",""); s = s.replace(",\"disableInResponseToCheck\":true",""); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); - assertFalse(definition.getSamlConfig().isDisableInResponseToCheck()); + assertThat(definition.getSamlConfig().isDisableInResponseToCheck()).isFalse(); } @Test public void testDefaultCorsConfiguration() { - assertEquals(Arrays.asList(new String[] {ACCEPT, AUTHORIZATION, CONTENT_TYPE}), definition.getCorsPolicy().getDefaultConfiguration().getAllowedHeaders()); - assertEquals(Collections.singletonList(GET.toString()), definition.getCorsPolicy().getDefaultConfiguration().getAllowedMethods()); - assertEquals(Collections.singletonList(".*"), definition.getCorsPolicy().getDefaultConfiguration().getAllowedUris()); - assertEquals(Collections.EMPTY_LIST, definition.getCorsPolicy().getDefaultConfiguration().getAllowedUriPatterns()); - assertEquals(Collections.singletonList(".*"), definition.getCorsPolicy().getDefaultConfiguration().getAllowedOrigins()); - assertEquals(Collections.EMPTY_LIST, definition.getCorsPolicy().getDefaultConfiguration().getAllowedOriginPatterns()); - assertEquals(1728000, definition.getCorsPolicy().getDefaultConfiguration().getMaxAge()); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedHeaders()).isEqualTo(Arrays.asList(new String[]{ACCEPT, AUTHORIZATION, CONTENT_TYPE})); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedMethods()).isEqualTo(Collections.singletonList(GET.toString())); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedUris()).isEqualTo(Collections.singletonList(".*")); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedUriPatterns()).isEqualTo(Collections.EMPTY_LIST); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedOrigins()).isEqualTo(Collections.singletonList(".*")); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedOriginPatterns()).isEqualTo(Collections.EMPTY_LIST); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getMaxAge()).isEqualTo(1728000); } @Test @@ -234,13 +217,13 @@ public void testDeserialize_DefaultCorsConfiguration() { s = s.replace("\"allowedUris\":[\".*\"]", "\"allowedUris\":[\"^/uaa/userinfo$\",\"^/uaa/logout\\\\.do$\"]"); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); - assertEquals(Arrays.asList(new String[] {ACCEPT}), definition.getCorsPolicy().getDefaultConfiguration().getAllowedHeaders()); - assertEquals(Arrays.asList(new String[] {GET.toString(), POST.toString()}), definition.getCorsPolicy().getDefaultConfiguration().getAllowedMethods()); - assertEquals(Arrays.asList(new String[] {"^/uaa/userinfo$", "^/uaa/logout\\.do$"}), definition.getCorsPolicy().getDefaultConfiguration().getAllowedUris()); - assertEquals(Collections.EMPTY_LIST, definition.getCorsPolicy().getDefaultConfiguration().getAllowedUriPatterns()); - assertEquals(Arrays.asList(new String[] {"^localhost$", "^.*\\.localhost$"}), definition.getCorsPolicy().getDefaultConfiguration().getAllowedOrigins()); - assertEquals(Collections.EMPTY_LIST, definition.getCorsPolicy().getDefaultConfiguration().getAllowedOriginPatterns()); - assertEquals(1728000, definition.getCorsPolicy().getDefaultConfiguration().getMaxAge()); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedHeaders()).isEqualTo(Arrays.asList(new String[]{ACCEPT})); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedMethods()).isEqualTo(Arrays.asList(new String[]{GET.toString(), POST.toString()})); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedUris()).isEqualTo(Arrays.asList(new String[]{"^/uaa/userinfo$", "^/uaa/logout\\.do$"})); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedUriPatterns()).isEqualTo(Collections.EMPTY_LIST); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedOrigins()).isEqualTo(Arrays.asList(new String[]{"^localhost$", "^.*\\.localhost$"})); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getAllowedOriginPatterns()).isEqualTo(Collections.EMPTY_LIST); + assertThat(definition.getCorsPolicy().getDefaultConfiguration().getMaxAge()).isEqualTo(1728000); } @Test @@ -249,10 +232,10 @@ public void testSerializeDefaultIdentityProvider() { config.setDefaultIdentityProvider("originkey"); String configString = JsonUtils.writeValueAsString(config); - assertThat(configString, containsString("\"defaultIdentityProvider\"")); - assertThat(configString, containsString("\"originkey\"")); + assertThat(configString).contains("\"defaultIdentityProvider\"") + .contains("\"originkey\""); IdentityZoneConfiguration deserializedConfig = JsonUtils.readValue(configString, IdentityZoneConfiguration.class); - assertEquals(config.getDefaultIdentityProvider(), deserializedConfig.getDefaultIdentityProvider()); + assertThat(deserializedConfig.getDefaultIdentityProvider()).isEqualTo(config.getDefaultIdentityProvider()); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/extensions/SystemPropertiesCleanupExtension.java b/server/src/test/java/org/cloudfoundry/identity/uaa/extensions/SystemPropertiesCleanupExtension.java new file mode 100644 index 00000000000..793abe232cc --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/extensions/SystemPropertiesCleanupExtension.java @@ -0,0 +1,38 @@ +package org.cloudfoundry.identity.uaa.extensions; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.util.Set; + +public class SystemPropertiesCleanupExtension implements BeforeAllCallback, AfterAllCallback { + + private final Set properties; + + public SystemPropertiesCleanupExtension(String... props) { + this.properties = Set.of(props); + } + + @Override + public void beforeAll(ExtensionContext context) { + ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create(context.getRequiredTestClass())); + + properties.forEach(s -> store.put(s, System.getProperty(s))); + } + + @Override + public void afterAll(ExtensionContext context) { + ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create(context.getRequiredTestClass())); + + properties.forEach(key -> { + String value = store.get(key, String.class); + if (value == null) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + } + ); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java index f37277f7e3f..bb0e7576c8a 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java @@ -8,6 +8,7 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.home.BuildInfo; import org.cloudfoundry.identity.uaa.login.ThymeleafConfig; +import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetailsService; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; @@ -27,10 +28,10 @@ import org.cloudfoundry.identity.uaa.zone.Consent; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -42,10 +43,9 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetailsService; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -63,14 +63,11 @@ import java.util.HashMap; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType.INVITATION; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -90,11 +87,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @WebAppConfiguration @ContextConfiguration(classes = InvitationsControllerTest.ContextConfiguration.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -public class InvitationsControllerTest { +class InvitationsControllerTest { private MockMvc mockMvc; @@ -131,54 +128,54 @@ public class InvitationsControllerTest { @Autowired ExternalOAuthProviderConfigurator externalOAuthProviderConfigurator; - @Before + @BeforeEach public void setUp() { IdentityZoneHolder.clear(); SecurityContextHolder.clearContext(); mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) - .build(); + .build(); } - @After + @AfterEach public void tearDown() { SecurityContextHolder.clearContext(); } @Test - public void testAcceptInvitationsPage() throws Exception { + void testAcceptInvitationsPage() throws Exception { String zoneId = IdentityZoneHolder.get().getId(); - Map codeData = new HashMap<>(); + Map codeData = new HashMap<>(); codeData.put("user_id", "user-id-001"); codeData.put("email", "user@example.com"); codeData.put("client_id", "client-id"); codeData.put("redirect_uri", "blah.test.com"); when(expiringCodeStore.peekCode("code", zoneId)).thenReturn(createCode(codeData), null); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(any(), any())).thenReturn(provider); mockMvc.perform(get("/invitations/accept").param("code", "code")) - .andExpect(status().isOk()) - .andExpect(model().attribute("email", "user@example.com")) - .andExpect(model().attribute("code", "code")) - .andExpect(view().name("invitations/accept_invite")); + .andExpect(status().isOk()) + .andExpect(model().attribute("email", "user@example.com")) + .andExpect(model().attribute("code", "code")) + .andExpect(view().name("invitations/accept_invite")); UaaPrincipal principal = ((UaaPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal()); - assertTrue(SecurityContextHolder.getContext().getAuthentication() instanceof AnonymousAuthenticationToken); - assertEquals("user-id-001", principal.getId()); - assertEquals("user@example.com", principal.getName()); - assertEquals("user@example.com", principal.getEmail()); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isInstanceOf(AnonymousAuthenticationToken.class); + assertThat(principal.getId()).isEqualTo("user-id-001"); + assertThat(principal.getName()).isEqualTo("user@example.com"); + assertThat(principal.getEmail()).isEqualTo("user@example.com"); mockMvc.perform(get("/invitations/accept").param("code", "code")) - .andExpect(status().isUnprocessableEntity()) - .andExpect(view().name("invitations/accept_invite")) - .andExpect(model().attribute("error_message_code", "code_expired")); + .andExpect(status().isUnprocessableEntity()) + .andExpect(view().name("invitations/accept_invite")) + .andExpect(model().attribute("error_message_code", "code_expired")); } @Test - public void incorrectCodeIntent() throws Exception { + void incorrectCodeIntent() throws Exception { String zoneId = IdentityZoneHolder.get().getId(); - Map codeData = new HashMap<>(); + Map codeData = new HashMap<>(); codeData.put("user_id", "user-id-001"); codeData.put("email", "user@example.com"); codeData.put("client_id", "client-id"); @@ -186,25 +183,25 @@ public void incorrectCodeIntent() throws Exception { when(expiringCodeStore.retrieveCode("the_secret_code", zoneId)).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), "incorrect-code-intent")); MockHttpServletRequestBuilder get = get("/invitations/accept") - .param("code", "the_secret_code"); + .param("code", "the_secret_code"); mockMvc.perform(get).andExpect(status().isUnprocessableEntity()); } @Test - public void acceptInvitePage_for_unverifiedSamlUser() throws Exception { - Map codeData = getInvitationsCode("test-saml"); + void acceptInvitePage_for_unverifiedSamlUser() throws Exception { + Map codeData = getInvitationsCode("test-saml"); String zoneId = IdentityZoneHolder.get().getId(); when(expiringCodeStore.peekCode("the_secret_code", zoneId)).thenReturn(createCode(codeData)); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); SamlIdentityProviderDefinition definition = new SamlIdentityProviderDefinition() - .setMetaDataLocation("http://test.saml.com") - .setIdpEntityAlias("test-saml") - .setNameID("test") - .setLinkText("testsaml") - .setIconUrl("test.com") - .setZoneId(zoneId); + .setMetaDataLocation("http://test.saml.com") + .setIdpEntityAlias("test-saml") + .setNameID("test") + .setLinkText("testsaml") + .setIconUrl("test.com") + .setZoneId(zoneId); provider.setConfig(definition); provider.setType(OriginKeys.SAML); when(providerProvisioning.retrieveByOrigin(eq("test-saml"), anyString())).thenReturn(provider); @@ -212,23 +209,23 @@ public void acceptInvitePage_for_unverifiedSamlUser() throws Exception { .param("code", "the_secret_code"); MvcResult result = mockMvc.perform(get) - .andExpect(redirectedUrl("/saml/discovery?returnIDParam=idp&entityID=sp-entity-id&idp=test-saml&isPassive=true")) + .andExpect(redirectedUrl("/saml2/authenticate/test-saml")) .andReturn(); - assertEquals(true, result.getRequest().getSession().getAttribute("IS_INVITE_ACCEPTANCE")); - assertEquals("user-id-001", result.getRequest().getSession().getAttribute("user_id")); + assertThat(result.getRequest().getSession().getAttribute("IS_INVITE_ACCEPTANCE")).isEqualTo(true); + assertThat(result.getRequest().getSession().getAttribute("user_id")).isEqualTo("user-id-001"); } @Test - public void acceptInvitePage_for_unverifiedOIDCUser() throws Exception { - Map codeData = getInvitationsCode("test-oidc"); + void acceptInvitePage_for_unverifiedOIDCUser() throws Exception { + Map codeData = getInvitationsCode("test-oidc"); String zoneId = IdentityZoneHolder.get().getId(); when(expiringCodeStore.peekCode("the_secret_code", zoneId)).thenReturn(createCode(codeData)); OIDCIdentityProviderDefinition definition = new OIDCIdentityProviderDefinition(); definition.setAuthUrl(new URL("https://oidc10.auth.url")); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setConfig(definition); provider.setType(OriginKeys.OIDC10); when(providerProvisioning.retrieveByOrigin(eq("test-oidc"), anyString())).thenReturn(provider); @@ -242,17 +239,17 @@ public void acceptInvitePage_for_unverifiedOIDCUser() throws Exception { .andExpect(redirectedUrl("http://example.com")) .andReturn(); - assertEquals(true, result.getRequest().getSession().getAttribute("IS_INVITE_ACCEPTANCE")); - assertEquals("user-id-001", result.getRequest().getSession().getAttribute("user_id")); + assertThat(result.getRequest().getSession().getAttribute("IS_INVITE_ACCEPTANCE")).isEqualTo(true); + assertThat(result.getRequest().getSession().getAttribute("user_id")).isEqualTo("user-id-001"); } @Test - public void acceptInvitePage_for_unverifiedLdapUser() throws Exception { + void acceptInvitePage_for_unverifiedLdapUser() throws Exception { Map codeData = getInvitationsCode(LDAP); String zoneId = IdentityZoneHolder.get().getId(); when(expiringCodeStore.peekCode("the_secret_code", zoneId)).thenReturn(createCode(codeData)); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setType(LDAP); when(providerProvisioning.retrieveByOrigin(eq(LDAP), anyString())).thenReturn(provider); @@ -280,7 +277,7 @@ private Map getInvitationsCode(String origin) { } @Test - public void unverifiedLdapUser_acceptsInvite_byLoggingIn() throws Exception { + void unverifiedLdapUser_acceptsInvite_byLoggingIn() throws Exception { Map codeData = getInvitationsCode(LDAP); String zoneId = IdentityZoneHolder.get().getId(); when(expiringCodeStore.retrieveCode("the_secret_code", zoneId)).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); @@ -309,10 +306,10 @@ public void unverifiedLdapUser_acceptsInvite_byLoggingIn() throws Exception { when(expiringCodeStore.generateCode(anyString(), any(), eq(null), eq(zoneId))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); mockMvc.perform(post("/invitations/accept_enterprise.do") - .param("enterprise_username", "test-ldap-user") - .param("enterprise_password", "password") - .param("enterprise_email", "email") - .param("code", "the_secret_code")) + .param("enterprise_username", "test-ldap-user") + .param("enterprise_password", "password") + .param("enterprise_email", "email") + .param("code", "the_secret_code")) .andExpect(redirectedUrl("/login?success=invite_accepted&form_redirect_uri=blah.test.com")) .andReturn(); @@ -320,13 +317,13 @@ public void unverifiedLdapUser_acceptsInvite_byLoggingIn() throws Exception { ArgumentCaptor userArgumentCaptor = ArgumentCaptor.forClass(ScimUser.class); verify(scimUserProvisioning).update(anyString(), userArgumentCaptor.capture(), eq(zoneId)); ScimUser value = userArgumentCaptor.getValue(); - assertEquals("test-ldap-user", value.getUserName()); - assertEquals("user@example.com", value.getPrimaryEmail()); + assertThat(value.getUserName()).isEqualTo("test-ldap-user"); + assertThat(value.getPrimaryEmail()).isEqualTo("user@example.com"); verify(ldapAuthenticationManager).authenticate(any()); } @Test - public void unverifiedLdapUser_acceptsInvite_byLoggingIn_bad_credentials() throws Exception { + void unverifiedLdapUser_acceptsInvite_byLoggingIn_bad_credentials() throws Exception { Map codeData = getInvitationsCode("ldap"); String zoneId = IdentityZoneHolder.get().getId(); when(expiringCodeStore.retrieveCode("the_secret_code", zoneId)).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); @@ -342,18 +339,18 @@ public void unverifiedLdapUser_acceptsInvite_byLoggingIn_bad_credentials() throw when(ldapActual.authenticate(any())).thenThrow(new BadCredentialsException("bad creds")); mockMvc.perform(post("/invitations/accept_enterprise.do") - .param("enterprise_username", "test-ldap-user") - .param("enterprise_password", "password") - .param("enterprise_email", "email") - .param("code", "the_secret_code")) - .andExpect(model().attribute("ldap", true)) - .andExpect(model().attribute("email", "email")) - .andExpect(model().attribute("error_message", "bad_credentials")) - .andReturn(); + .param("enterprise_username", "test-ldap-user") + .param("enterprise_password", "password") + .param("enterprise_email", "email") + .param("code", "the_secret_code")) + .andExpect(model().attribute("ldap", true)) + .andExpect(model().attribute("email", "email")) + .andExpect(model().attribute("error_message", "bad_credentials")) + .andReturn(); } @Test - public void unverifiedLdapUser_acceptsInvite_byLoggingIn_whereEmailDoesNotMatchAuthenticatedEmail() throws Exception { + void unverifiedLdapUser_acceptsInvite_byLoggingIn_whereEmailDoesNotMatchAuthenticatedEmail() throws Exception { Map codeData = getInvitationsCode(LDAP); String zoneId = IdentityZoneHolder.get().getId(); when(expiringCodeStore.retrieveCode("the_secret_code", zoneId)).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); @@ -375,10 +372,10 @@ public void unverifiedLdapUser_acceptsInvite_byLoggingIn_whereEmailDoesNotMatchA when(expiringCodeStore.generateCode(anyString(), any(), eq(null), eq(zoneId))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); mockMvc.perform(post("/invitations/accept_enterprise.do") - .param("enterprise_username", "test-ldap-user") - .param("enterprise_password", "password") - .param("enterprise_email", "email") - .param("code", "the_secret_code")) + .param("enterprise_username", "test-ldap-user") + .param("enterprise_password", "password") + .param("enterprise_email", "email") + .param("code", "the_secret_code")) .andExpect(status().isUnprocessableEntity()) .andExpect(view().name("invitations/accept_invite")) .andExpect(content().string(containsString("Email: " + "user@example.com"))) @@ -392,20 +389,20 @@ public void unverifiedLdapUser_acceptsInvite_byLoggingIn_whereEmailDoesNotMatchA } @Test - public void acceptInvitePage_for_verifiedUser() throws Exception { + void acceptInvitePage_for_verifiedUser() throws Exception { String zoneId = IdentityZoneHolder.get().getId(); UaaUser user = new UaaUser("user@example.com", "", "user@example.com", "Given", "family"); user.modifyId("verified-user"); user.setVerified(true); when(userDatabase.retrieveUserById("verified-user")).thenReturn(user); - Map codeData = new HashMap<>(); + Map codeData = new HashMap<>(); codeData.put("user_id", "verified-user"); codeData.put("email", "user@example.com"); codeData.put("origin", "some-origin"); when(expiringCodeStore.peekCode("the_secret_code", zoneId)).thenReturn(createCode(codeData), null); when(invitationsService.acceptInvitation(anyString(), eq(""))).thenReturn(new AcceptedInvitation("blah.test.com", new ScimUser())); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") @@ -420,119 +417,117 @@ private ExpiringCode createCode(Map codeData) { } @Test - public void incorrectGeneratedCodeIntent_for_verifiedUser() throws Exception { + void incorrectGeneratedCodeIntent_for_verifiedUser() throws Exception { String zoneId = IdentityZoneHolder.get().getId(); UaaUser user = new UaaUser("user@example.com", "", "user@example.com", "Given", "family"); user.modifyId("verified-user"); user.setVerified(true); when(userDatabase.retrieveUserById("verified-user")).thenReturn(user); - Map codeData = new HashMap<>(); + Map codeData = new HashMap<>(); codeData.put("user_id", "verified-user"); codeData.put("email", "user@example.com"); when(expiringCodeStore.retrieveCode("the_secret_code", zoneId)).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), "incorrect-code-intent")); when(expiringCodeStore.generateCode(anyString(), any(), eq(null), eq(zoneId))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), "incorrect-code-intent")); - doThrow(new HttpClientErrorException(BAD_REQUEST)).when(invitationsService).acceptInvitation(eq("incorrect-code-intent"), eq("")); + when(invitationsService.acceptInvitation("incorrect-code-intent", "")).thenThrow(new HttpClientErrorException(BAD_REQUEST)); MockHttpServletRequestBuilder get = get("/invitations/accept") - .param("code", "the_secret_code"); + .param("code", "the_secret_code"); mockMvc.perform(get).andExpect(status().isUnprocessableEntity()); } @Test - public void testAcceptInvitePageWithExpiredCode() throws Exception { + void testAcceptInvitePageWithExpiredCode() throws Exception { String zoneId = IdentityZoneHolder.get().getId(); when(expiringCodeStore.retrieveCode(anyString(), eq(zoneId))).thenReturn(null); MockHttpServletRequestBuilder get = get("/invitations/accept").param("code", "the_secret_code"); mockMvc.perform(get) - .andExpect(status().isUnprocessableEntity()) - .andExpect(model().attribute("error_message_code", "code_expired")) - .andExpect(view().name("invitations/accept_invite")) - .andExpect(xpath("//*[@class='email-display']").doesNotExist()) - .andExpect(xpath("//form").doesNotExist()); - assertNull(SecurityContextHolder.getContext().getAuthentication()); + .andExpect(status().isUnprocessableEntity()) + .andExpect(model().attribute("error_message_code", "code_expired")) + .andExpect(view().name("invitations/accept_invite")) + .andExpect(xpath("//*[@class='email-display']").doesNotExist()) + .andExpect(xpath("//form").doesNotExist()); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); } @Test - public void missing_code() throws Exception { + void missing_code() throws Exception { MockHttpServletRequestBuilder post = startAcceptInviteFlow("a", "a"); String zoneId = IdentityZoneHolder.get().getId(); when(expiringCodeStore.retrieveCode("thecode", zoneId)).thenReturn(null); - IdentityProvider identityProvider = new IdentityProvider(); + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); mockMvc.perform(post) - .andExpect(status().isUnprocessableEntity()) - .andExpect(model().attribute("error_message_code", "code_expired")) - .andExpect(view().name("invitations/accept_invite")); + .andExpect(status().isUnprocessableEntity()) + .andExpect(model().attribute("error_message_code", "code_expired")) + .andExpect(view().name("invitations/accept_invite")); verify(expiringCodeStore).retrieveCode("thecode", zoneId); verify(expiringCodeStore, never()).generateCode(anyString(), any(), anyString(), eq(zoneId)); verify(invitationsService, never()).acceptInvitation(anyString(), anyString()); - } @Test - public void invalid_principal_id() throws Exception { + void invalid_principal_id() throws Exception { MockHttpServletRequestBuilder post = startAcceptInviteFlow("a", "a"); String zoneId = IdentityZoneHolder.get().getId(); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); codeData.put("user_id", "invalid id"); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", zoneId)).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); - IdentityProvider identityProvider = new IdentityProvider(); + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); mockMvc.perform(post) - .andExpect(status().isUnprocessableEntity()) - .andExpect(model().attribute("error_message_code", "code_expired")) - .andExpect(view().name("invitations/accept_invite")); + .andExpect(status().isUnprocessableEntity()) + .andExpect(model().attribute("error_message_code", "code_expired")) + .andExpect(view().name("invitations/accept_invite")); verify(expiringCodeStore).retrieveCode("thecode", zoneId); verify(expiringCodeStore, never()).generateCode(anyString(), any(), anyString(), eq(zoneId)); verify(invitationsService, never()).acceptInvitation(anyString(), anyString()); - } @Test - public void testAcceptInviteWithContraveningPassword() throws Exception { + void testAcceptInviteWithContraveningPassword() throws Exception { doThrow(new InvalidPasswordException(Arrays.asList("Msg 2c", "Msg 1c"))).when(passwordValidator).validate("a"); MockHttpServletRequestBuilder post = startAcceptInviteFlow("a", "a"); String zoneId = IdentityZoneHolder.get().getId(); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", zoneId)).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.retrieveCode("thenewcode", zoneId)).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(zoneId))).thenReturn( - new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), - new ExpiringCode("thenewcode2", new Timestamp(1), codeDataString, INVITATION.name()) + new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), + new ExpiringCode("thenewcode2", new Timestamp(1), codeDataString, INVITATION.name()) ); - IdentityProvider identityProvider = new IdentityProvider(); + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); mockMvc.perform(post) - .andExpect(status().isFound()) - .andExpect(model().attribute("error_message", "Msg 1c Msg 2c")) - .andExpect(model().attribute("code", "thenewcode2")) - .andExpect(view().name("redirect:accept")); + .andExpect(status().isFound()) + .andExpect(model().attribute("error_message", "Msg 1c Msg 2c")) + .andExpect(model().attribute("code", "thenewcode2")) + .andExpect(view().name("redirect:accept")); verify(expiringCodeStore).retrieveCode("thecode", zoneId); verify(expiringCodeStore, times(2)).generateCode(anyString(), any(), anyString(), eq(zoneId)); verify(invitationsService, never()).acceptInvitation(anyString(), anyString()); } @Test - public void testAcceptInvite() throws Exception { - ScimUser user = new ScimUser("user-id-001", "user@example.com","fname", "lname"); + void testAcceptInvite() throws Exception { + ScimUser user = new ScimUser("user-id-001", "user@example.com", "fname", "lname"); user.setPrimaryEmail(user.getUserName()); - MockHttpServletRequestBuilder post = startAcceptInviteFlow("passw0rd","passw0rd"); + MockHttpServletRequestBuilder post = startAcceptInviteFlow("passw0rd", "passw0rd"); String zoneId = IdentityZoneHolder.get().getId(); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); ExpiringCode thecode = new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()); ExpiringCode thenewcode = new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()); @@ -540,14 +535,14 @@ public void testAcceptInvite() throws Exception { when(expiringCodeStore.retrieveCode("thecode", zoneId)).thenReturn(thecode, null); when(expiringCodeStore.retrieveCode("thenewcode", zoneId)).thenReturn(thenewcode, null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(zoneId))) - .thenReturn(thenewcode) - .thenReturn(thenewcode2); + .thenReturn(thenewcode) + .thenReturn(thenewcode2); when(invitationsService.acceptInvitation(anyString(), eq("passw0rd"))).thenReturn(new AcceptedInvitation("/home", user)); - MvcResult res = mockMvc.perform(post) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?success=invite_accepted")).andReturn(); + mockMvc.perform(post) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?success=invite_accepted")).andReturn(); verify(invitationsService).acceptInvitation(anyString(), eq("passw0rd")); } @@ -559,48 +554,48 @@ private MockHttpServletRequestBuilder startAcceptInviteFlow(String password, Str SecurityContextHolder.getContext().setAuthentication(token); return post("/invitations/accept.do") - .param("code","thecode") - .param("password", password) - .param("password_confirmation", passwordConfirmation); + .param("code", "thecode") + .param("password", password) + .param("password_confirmation", passwordConfirmation); } @Test - public void acceptInviteWithValidClientRedirect() throws Exception { + void acceptInviteWithValidClientRedirect() throws Exception { String zoneId = IdentityZoneHolder.get().getId(); - UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null,zoneId); - ScimUser user = new ScimUser(uaaPrincipal.getId(), uaaPrincipal.getName(),"fname", "lname"); + UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null, zoneId); + ScimUser user = new ScimUser(uaaPrincipal.getId(), uaaPrincipal.getName(), "fname", "lname"); user.setPrimaryEmail(user.getUserName()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", zoneId)).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(zoneId))).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name())); when(invitationsService.acceptInvitation(anyString(), eq("password"))).thenReturn(new AcceptedInvitation("valid.redirect.com", user)); MockHttpServletRequestBuilder post = post("/invitations/accept.do") - .param("password", "password") - .param("password_confirmation", "password") - .param("code", "thecode"); + .param("password", "password") + .param("password_confirmation", "password") + .param("code", "thecode"); mockMvc.perform(post) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?success=invite_accepted&form_redirect_uri=valid.redirect.com")); + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?success=invite_accepted&form_redirect_uri=valid.redirect.com")); } @Test - public void acceptInviteWithInvalidClientRedirect() throws Exception { + void acceptInviteWithInvalidClientRedirect() throws Exception { String zoneId = IdentityZoneHolder.get().getId(); - UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null,zoneId); + UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null, zoneId); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); - ScimUser user = new ScimUser(uaaPrincipal.getId(), uaaPrincipal.getName(),"fname", "lname"); + ScimUser user = new ScimUser(uaaPrincipal.getId(), uaaPrincipal.getName(), "fname", "lname"); user.setPrimaryEmail(user.getUserName()); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", zoneId)).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(zoneId))).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name())); @@ -608,23 +603,23 @@ public void acceptInviteWithInvalidClientRedirect() throws Exception { when(invitationsService.acceptInvitation(anyString(), eq("password"))).thenReturn(new AcceptedInvitation("/home", user)); MockHttpServletRequestBuilder post = post("/invitations/accept.do") - .param("code","thecode") - .param("password", "password") - .param("password_confirmation", "password"); + .param("code", "thecode") + .param("password", "password") + .param("password_confirmation", "password"); mockMvc.perform(post) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?success=invite_accepted")); + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?success=invite_accepted")); } @Test - public void invalidCodeOnAcceptPost() throws Exception { + void invalidCodeOnAcceptPost() throws Exception { String zoneId = IdentityZoneHolder.get().getId(); - UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null,zoneId); + UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null, zoneId); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", zoneId)).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(zoneId))).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name())); @@ -632,149 +627,148 @@ public void invalidCodeOnAcceptPost() throws Exception { doThrow(new HttpClientErrorException(BAD_REQUEST)).when(invitationsService).acceptInvitation(anyString(), anyString()); MockHttpServletRequestBuilder post = post("/invitations/accept.do") - .param("code","thecode") - .param("password", "password") - .param("password_confirmation", "password"); + .param("code", "thecode") + .param("password", "password") + .param("password_confirmation", "password"); mockMvc.perform(post) - .andExpect(status().isUnprocessableEntity()) - .andExpect(model().attribute("error_message_code", "code_expired")) - .andExpect(view().name("invitations/accept_invite")); + .andExpect(status().isUnprocessableEntity()) + .andExpect(model().attribute("error_message_code", "code_expired")) + .andExpect(view().name("invitations/accept_invite")); } @Test - public void testAcceptInviteWithoutMatchingPasswords() throws Exception { - MockHttpServletRequestBuilder post = startAcceptInviteFlow("a","b"); + void testAcceptInviteWithoutMatchingPasswords() throws Exception { + MockHttpServletRequestBuilder post = startAcceptInviteFlow("a", "b"); String zoneId = IdentityZoneHolder.get().getId(); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", zoneId)).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.retrieveCode("thenewcode", zoneId)).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(zoneId))).thenReturn( - new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), - new ExpiringCode("thenewcode2", new Timestamp(1), codeDataString, INVITATION.name()) + new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), + new ExpiringCode("thenewcode2", new Timestamp(1), codeDataString, INVITATION.name()) ); - - IdentityProvider identityProvider = new IdentityProvider(); + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); mockMvc.perform(post) - .andExpect(status().isFound()) - .andExpect(model().attribute("error_message_code", "form_error")) - .andExpect(model().attribute("code", "thenewcode2")) - .andExpect(view().name("redirect:accept")); + .andExpect(status().isFound()) + .andExpect(model().attribute("error_message_code", "form_error")) + .andExpect(model().attribute("code", "thenewcode2")) + .andExpect(view().name("redirect:accept")); verify(expiringCodeStore).retrieveCode("thecode", zoneId); verify(expiringCodeStore, times(2)).generateCode(anyString(), any(), anyString(), eq(zoneId)); verify(invitationsService, never()).acceptInvitation(anyString(), anyString()); } @Test - public void testAcceptInvite_displaysConsentText() throws Exception { + void testAcceptInvite_displaysConsentText() throws Exception { IdentityZone defaultZone = IdentityZoneHolder.get(); String zoneId = IdentityZoneHolder.get().getId(); BrandingInformation branding = new BrandingInformation(); branding.setConsent(new Consent("paying Jaskanwal Pawar & Jennifer Hamon each a million dollars", null)); defaultZone.getConfig().setBranding(branding); - IdentityProvider identityProvider = new IdentityProvider(); + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(identityProvider); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); ExpiringCode expiringCode = new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()); when(expiringCodeStore.peekCode("thecode", zoneId)) - .thenReturn(expiringCode, null); + .thenReturn(expiringCode, null); mockMvc.perform(get("/invitations/accept") - .param("code", "thecode")) - .andExpect(content().string(containsString("Jaskanwal"))); + .param("code", "thecode")) + .andExpect(content().string(containsString("Jaskanwal"))); // cleanup changes to default zone defaultZone.getConfig().setBranding(null); } @Test - public void testAcceptInvite_doesNotDisplayConsentCheckboxWhenNotConfiguredForZone() throws Exception { - IdentityProvider identityProvider = new IdentityProvider(); + void testAcceptInvite_doesNotDisplayConsentCheckboxWhenNotConfiguredForZone() throws Exception { + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(identityProvider); String zoneId = IdentityZoneHolder.get().getId(); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); ExpiringCode expiringCode = new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()); when(expiringCodeStore.retrieveCode("thecode", zoneId)) - .thenReturn(expiringCode, null); + .thenReturn(expiringCode, null); when(expiringCodeStore.generateCode(anyString(), any(), eq(INVITATION.name()), eq(zoneId))) - .thenReturn(expiringCode); + .thenReturn(expiringCode); mockMvc.perform(get("/invitations/accept") - .param("code", "thecode")) - .andExpect(content().string(not(containsString("I agree")))); + .param("code", "thecode")) + .andExpect(content().string(not(containsString("I agree")))); } @Test - public void testAcceptInvite_displaysErrorMessageIfConsentNotChecked() throws Exception { + void testAcceptInvite_displaysErrorMessageIfConsentNotChecked() throws Exception { IdentityZone defaultZone = IdentityZoneHolder.get(); String zoneId = IdentityZoneHolder.get().getId(); BrandingInformation branding = new BrandingInformation(); branding.setConsent(new Consent("paying Jaskanwal Pawar & Jennifer Hamon each a million dollars", null)); defaultZone.getConfig().setBranding(branding); - IdentityProvider identityProvider = new IdentityProvider(); + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(identityProvider); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); ExpiringCode expiringCode = new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()); when(expiringCodeStore.peekCode(anyString(), eq(zoneId))) - .thenReturn(expiringCode); + .thenReturn(expiringCode); when(expiringCodeStore.retrieveCode(anyString(), eq(zoneId))) - .thenReturn(expiringCode); + .thenReturn(expiringCode); when(expiringCodeStore.generateCode(anyString(), any(), eq(INVITATION.name()), eq(zoneId))) - .thenReturn(expiringCode); + .thenReturn(expiringCode); MvcResult mvcResult = mockMvc.perform(startAcceptInviteFlow("password", "password")) - .andReturn(); + .andReturn(); mockMvc.perform(get("/invitations/" + mvcResult.getResponse().getHeader("Location"))) - .andExpect(model().attribute("error_message_code", "missing_consent")); + .andExpect(model().attribute("error_message_code", "missing_consent")); // cleanup changes to default zone defaultZone.getConfig().setBranding(null); } @Test - public void testAcceptInvite_worksWithConsentProvided() throws Exception { + void testAcceptInvite_worksWithConsentProvided() throws Exception { IdentityZone defaultZone = IdentityZoneHolder.get(); String zoneId = IdentityZoneHolder.get().getId(); BrandingInformation branding = new BrandingInformation(); branding.setConsent(new Consent("paying Jaskanwal Pawar & Jennifer Hamon each a million dollars", null)); defaultZone.getConfig().setBranding(branding); - IdentityProvider identityProvider = new IdentityProvider(); + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(identityProvider); - Map codeData = getInvitationsCode(OriginKeys.UAA); + Map codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); ExpiringCode expiringCode = new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()); when(expiringCodeStore.retrieveCode(anyString(), eq(zoneId))) - .thenReturn(expiringCode); + .thenReturn(expiringCode); when(expiringCodeStore.generateCode(anyString(), any(), eq(INVITATION.name()), eq(zoneId))) - .thenReturn(expiringCode); + .thenReturn(expiringCode); when(invitationsService.acceptInvitation(anyString(), anyString())) - .thenReturn(new AcceptedInvitation(codeData.get("redirect_uri"), null)); + .thenReturn(new AcceptedInvitation(codeData.get("redirect_uri"), null)); MvcResult mvcResult = mockMvc.perform(startAcceptInviteFlow("password", "password") - .param("does_user_consent", "true")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeader("Location"), containsString(codeData.get("redirect_uri"))); + .param("does_user_consent", "true")) + .andReturn(); + assertThat(mvcResult.getResponse().getHeader("Location")).contains(codeData.get("redirect_uri")); // cleanup changes to default zone defaultZone.getConfig().setBranding(null); @@ -797,9 +791,9 @@ BuildInfo buildInfo() { @Bean public UaaUserDatabase userDatabase() { UaaUserDatabase userDatabase = mock(UaaUserDatabase.class); - UaaUser user = new UaaUser("user@example.com","","user@example.com","Given","family"); + UaaUser user = new UaaUser("user@example.com", "", "user@example.com", "Given", "family"); user = user.modifyId("user-id-001"); - when (userDatabase.retrieveUserById(user.getId())).thenReturn(user); + when(userDatabase.retrieveUserById(user.getId())).thenReturn(user); return userDatabase; } @@ -852,12 +846,14 @@ ExpiringCodeStore expiringCodeStore() { } @Bean - PasswordValidator uaaPasswordValidator() { return mock(PasswordValidator.class); } + PasswordValidator uaaPasswordValidator() { + return mock(PasswordValidator.class); + } @Bean IdentityProviderProvisioning providerProvisioning() { - return mock (IdentityProviderProvisioning.class); + return mock(IdentityProviderProvisioning.class); } @Bean diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/AddBcProvider.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/AddBcProvider.java deleted file mode 100644 index cf125e6e867..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/AddBcProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. - *

    - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - *

    - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.login; - -import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; - -import java.security.Security; - -public class AddBcProvider { - - static { - Security.addProvider(new BouncyCastleFipsProvider()); - } - - public static void noop() { - } - - -} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerTest.java index ba4a43775c2..69fecc3362f 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerTest.java @@ -5,7 +5,6 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,10 +16,16 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; @ExtendWith(SpringExtension.class) @ExtendWith(PollutionPreventionExtension.class) @@ -32,7 +37,7 @@ class ForcePasswordChangeControllerTest extends TestClassNullifier { private UaaAuthentication mockUaaAuthentication; @BeforeEach - void setUp() { + void beforeEach() { mockResourcePropertySource = mock(ResourcePropertySource.class); ForcePasswordChangeController controller = new ForcePasswordChangeController( mockResourcePropertySource, @@ -68,10 +73,10 @@ void redirectToLogInIfPasswordIsNotExpired() throws Exception { @Test void handleForcePasswordChange() throws Exception { mockMvc.perform( - post("/uaa/force_password_change") - .param("password", "pwd") - .param("password_confirmation", "pwd") - .contextPath("/uaa")) + post("/uaa/force_password_change") + .param("password", "pwd") + .param("password_confirmation", "pwd") + .contextPath("/uaa")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/uaa/force_password_change_completed")); verify(mockUaaAuthentication, times(1)).setAuthenticatedTime(anyLong()); @@ -80,9 +85,9 @@ void handleForcePasswordChange() throws Exception { @Test void handleForcePasswordChangeWithRedirect() throws Exception { mockMvc.perform( - post("/force_password_change") - .param("password", "pwd") - .param("password_confirmation", "pwd")) + post("/force_password_change") + .param("password", "pwd") + .param("password_confirmation", "pwd")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/force_password_change_completed")); } @@ -91,9 +96,9 @@ void handleForcePasswordChangeWithRedirect() throws Exception { void passwordAndConfirmAreDifferent() throws Exception { when(mockResourcePropertySource.getProperty("force_password_change.form_error")).thenReturn("Passwords must match and not be empty."); mockMvc.perform( - post("/force_password_change") - .param("password", "pwd") - .param("password_confirmation", "nopwd")) + post("/force_password_change") + .param("password", "pwd") + .param("password_confirmation", "nopwd")) .andExpect(status().isUnprocessableEntity()); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java index 574a06577be..f733990c0db 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java @@ -3,23 +3,28 @@ import org.cloudfoundry.identity.uaa.TestClassNullifier; import org.cloudfoundry.identity.uaa.client.ClientMetadata; import org.cloudfoundry.identity.uaa.client.JdbcClientMetadataProvisioning; +import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; import org.cloudfoundry.identity.uaa.home.BuildInfo; import org.cloudfoundry.identity.uaa.home.HomeController; -import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; -import org.cloudfoundry.identity.uaa.zone.*; +import org.cloudfoundry.identity.uaa.provider.saml.MetadataProviderNotFoundException; +import org.cloudfoundry.identity.uaa.zone.BrandingInformation; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.Links; +import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.opensaml.common.SAMLException; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.web.WebAttributes; import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.test.annotation.DirtiesContext; @@ -46,7 +51,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; @ExtendWith(SpringExtension.class) @ExtendWith(PollutionPreventionExtension.class) @@ -69,7 +77,7 @@ class HomeControllerViewTests extends TestClassNullifier { private HomeController homeController; @BeforeEach - void setUp() { + void beforeEach() { SecurityContextHolder.clearContext(); IdentityZoneHolder.clear(); mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) @@ -83,7 +91,7 @@ void setUp() { } @AfterEach - void tearDown() { + void afterEach() { SecurityContextHolder.clearContext(); IdentityZoneHolder.clear(); IdentityZoneHolder.get().setConfig(originalConfiguration); @@ -158,9 +166,9 @@ void errorBranding(final String errorUrl) throws Exception { @Test void errorOauthWithExceptionString() throws Exception { mockMvc.perform(get("/oauth_error").sessionAttr("oauth_error", "auth error")) - .andExpect(status().isOk()) - .andExpect(content().string(containsString(customFooterText))) - .andExpect(content().string(containsString(base64ProductLogo))); + .andExpect(status().isOk()) + .andExpect(content().string(containsString(customFooterText))) + .andExpect(content().string(containsString(base64ProductLogo))); } @Test @@ -173,27 +181,27 @@ void error500WithGenericException() throws Exception { @Test void error500WithSAMLExceptionAsCause() throws Exception { - mockMvc.perform(get("/error500").requestAttr("javax.servlet.error.exception", new Exception(new SAMLException("bad")))) - .andExpect(status().isBadRequest()) - .andExpect(content().string(containsString(customFooterText))) - .andExpect(content().string(containsString(base64ProductLogo))); + mockMvc.perform(get("/error500").requestAttr("javax.servlet.error.exception", new Exception(new Saml2Exception("bad")))) + .andExpect(status().isBadRequest()) + .andExpect(content().string(containsString(customFooterText))) + .andExpect(content().string(containsString(base64ProductLogo))); } @Test - void error500WithMetadataProviderExceptionCause() throws Exception { - mockMvc.perform(get("/error500").requestAttr("javax.servlet.error.exception", new Exception(new MetadataProviderException("bad")))) - .andExpect(status().isBadRequest()) - .andExpect(content().string(containsString(customFooterText))) - .andExpect(content().string(containsString(base64ProductLogo))); + void error500WithMetadataProviderNotFoundExceptionCause() throws Exception { + mockMvc.perform(get("/error500").requestAttr("javax.servlet.error.exception", new Exception(new MetadataProviderNotFoundException("bad", new RuntimeException())))) + .andExpect(status().isBadRequest()) + .andExpect(content().string(containsString(customFooterText))) + .andExpect(content().string(containsString(base64ProductLogo))); } @ParameterizedTest @ValueSource(strings = { - "/rejected" + "/rejected" }) void errorRejection(final String errorUrl) throws Exception { mockMvc.perform(get(errorUrl)) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()); } @Test diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index 986cb508593..f676034118b 100755 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -1,7 +1,6 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.codestore.InMemoryExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; @@ -18,7 +17,6 @@ import org.cloudfoundry.identity.uaa.provider.oauth.OidcMetadataFetcher; import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.util.PredicateMatcher; import org.cloudfoundry.identity.uaa.util.SessionUtils; import org.cloudfoundry.identity.uaa.util.TimeServiceImpl; import org.cloudfoundry.identity.uaa.util.UaaRandomStringUtil; @@ -62,26 +60,14 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.addSubdomainToUrl; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -99,22 +85,18 @@ class LoginInfoEndpointTests { private static final String HTTP_LOCALHOST_8080_UAA = "http://localhost:8080/uaa"; private static final Links DEFAULT_GLOBAL_LINKS = new Links().setSelfService(new Links.SelfService().setPasswd(null).setSignup(null)); - private UaaPrincipal marissa; private List prompts; private ExtendedModelMap extendedModelMap; private SamlIdentityProviderConfigurator mockSamlIdentityProviderConfigurator; private List idps; private IdentityProviderProvisioning mockIdentityProviderProvisioning; - private IdentityProvider uaaIdentityProvider; + private IdentityProvider uaaIdentityProvider; private IdentityZoneConfiguration originalConfiguration; - private IdentityZoneProvisioning identityZoneProvisioning; - private IdentityZoneManager identityZoneManager; private ExternalOAuthProviderConfigurator configurator; @BeforeEach void setUp() { IdentityZoneHolder.clear(); - marissa = new UaaPrincipal("marissa-id", "marissa", "marissa@test.org", "origin", null, IdentityZoneHolder.get().getId()); prompts = new LinkedList<>(); prompts.add(new Prompt("username", "text", "Email")); prompts.add(new Prompt("password", "password", "Password")); @@ -123,9 +105,9 @@ void setUp() { when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions()).thenReturn(emptyList()); when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitionsForZone(any())).thenReturn(emptyList()); mockIdentityProviderProvisioning = mock(IdentityProviderProvisioning.class); - uaaIdentityProvider = new IdentityProvider(); - identityZoneProvisioning = mock(IdentityZoneProvisioning.class); - identityZoneManager = new IdentityZoneManagerImpl(); + uaaIdentityProvider = new IdentityProvider<>(); + IdentityZoneProvisioning identityZoneProvisioning = mock(IdentityZoneProvisioning.class); + IdentityZoneManager identityZoneManager = new IdentityZoneManagerImpl(); when(mockIdentityProviderProvisioning.retrieveByOriginIgnoreActiveFlag(eq(OriginKeys.UAA), anyString())).thenReturn(uaaIdentityProvider); when(mockIdentityProviderProvisioning.retrieveByOrigin(eq(OriginKeys.LDAP), anyString())).thenReturn(new IdentityProvider()); idps = getIdps(); @@ -147,9 +129,9 @@ void clearZoneHolder() { @Test void loginReturnsSystemZone() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - assertFalse(extendedModelMap.containsAttribute("zone_name")); + assertThat(extendedModelMap.containsAttribute("zone_name")).isFalse(); endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest(), singletonList(MediaType.TEXT_HTML)); - assertEquals(OriginKeys.UAA, extendedModelMap.asMap().get("zone_name")); + assertThat(extendedModelMap.asMap()).containsEntry("zone_name", OriginKeys.UAA); } @Test @@ -158,7 +140,7 @@ void alreadyLoggedInRedirectsToHome() throws Exception { UaaAuthentication authentication = mock(UaaAuthentication.class); when(authentication.isAuthenticated()).thenReturn(true); String result = endpoint.loginForHtml(extendedModelMap, authentication, new MockHttpServletRequest(), singletonList(MediaType.TEXT_HTML)); - assertEquals("redirect:/home", result); + assertThat(result).isEqualTo("redirect:/home"); } @Test @@ -169,16 +151,16 @@ void deleteSavedAccount() { String userId = "testUserId"; String result = endpoint.deleteSavedAccount(request, response, userId); Cookie[] cookies = response.getCookies(); - assertEquals(cookies.length, 1); - assertEquals(cookies[0].getName(), "Saved-Account-" + userId); - assertEquals(cookies[0].getMaxAge(), 0); - assertEquals("redirect:/login", result); + assertThat(cookies).hasSize(1); + assertThat("Saved-Account-" + userId).isEqualTo(cookies[0].getName()); + assertThat(cookies[0].getMaxAge()).isZero(); + assertThat(result).isEqualTo("redirect:/login"); } @Test void savedAccountsPopulatedOnModel() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - assertThat(extendedModelMap, not(hasKey("savedAccounts"))); + assertThat(extendedModelMap).doesNotContainKey("savedAccounts"); MockHttpServletRequest request = new MockHttpServletRequest(); SavedAccountOption savedAccount = new SavedAccountOption(); @@ -187,41 +169,41 @@ void savedAccountsPopulatedOnModel() throws Exception { savedAccount.setUserId("xxxx"); savedAccount.setOrigin("uaa"); - Cookie cookie1 = new Cookie("Saved-Account-xxxx", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8.name())); + Cookie cookie1 = new Cookie("Saved-Account-xxxx", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8)); savedAccount.setUsername("tim"); savedAccount.setEmail("tim@example.org"); savedAccount.setUserId("zzzz"); savedAccount.setOrigin("ldap"); - Cookie cookie2 = new Cookie("Saved-Account-zzzz", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8.name())); + Cookie cookie2 = new Cookie("Saved-Account-zzzz", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8)); request.setCookies(cookie1, cookie2); endpoint.loginForHtml(extendedModelMap, null, request, singletonList(MediaType.TEXT_HTML)); - assertThat(extendedModelMap, hasKey("savedAccounts")); - assertThat(extendedModelMap.get("savedAccounts"), instanceOf(List.class)); + assertThat(extendedModelMap).containsKey("savedAccounts"); + assertThat(extendedModelMap.get("savedAccounts")).isInstanceOf(List.class); List savedAccounts = (List) extendedModelMap.get("savedAccounts"); - assertThat(savedAccounts, hasSize(2)); + assertThat(savedAccounts).hasSize(2); SavedAccountOption savedAccount0 = savedAccounts.get(0); - assertThat(savedAccount0, notNullValue()); - assertEquals("bob", savedAccount0.getUsername()); - assertEquals("bob@example.com", savedAccount0.getEmail()); - assertEquals("uaa", savedAccount0.getOrigin()); - assertEquals("xxxx", savedAccount0.getUserId()); + assertThat(savedAccount0).isNotNull(); + assertThat(savedAccount0.getUsername()).isEqualTo("bob"); + assertThat(savedAccount0.getEmail()).isEqualTo("bob@example.com"); + assertThat(savedAccount0.getOrigin()).isEqualTo("uaa"); + assertThat(savedAccount0.getUserId()).isEqualTo("xxxx"); SavedAccountOption savedAccount1 = savedAccounts.get(1); - assertThat(savedAccount1, notNullValue()); - assertEquals("tim", savedAccount1.getUsername()); - assertEquals("tim@example.org", savedAccount1.getEmail()); - assertEquals("ldap", savedAccount1.getOrigin()); - assertEquals("zzzz", savedAccount1.getUserId()); + assertThat(savedAccount1).isNotNull(); + assertThat(savedAccount1.getUsername()).isEqualTo("tim"); + assertThat(savedAccount1.getEmail()).isEqualTo("tim@example.org"); + assertThat(savedAccount1.getOrigin()).isEqualTo("ldap"); + assertThat(savedAccount1.getUserId()).isEqualTo("zzzz"); } @Test void ignoresBadJsonSavedAccount() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - assertThat(extendedModelMap, not(hasKey("savedAccounts"))); + assertThat(extendedModelMap).doesNotContainKey("savedAccounts"); MockHttpServletRequest request = new MockHttpServletRequest(); SavedAccountOption savedAccount = new SavedAccountOption(); @@ -236,16 +218,16 @@ void ignoresBadJsonSavedAccount() throws Exception { request.setCookies(cookieGood, cookieBadJson); endpoint.loginForHtml(extendedModelMap, null, request, singletonList(MediaType.TEXT_HTML)); - assertThat(extendedModelMap, hasKey("savedAccounts")); - assertThat(extendedModelMap.get("savedAccounts"), instanceOf(List.class)); + assertThat(extendedModelMap).containsKey("savedAccounts"); + assertThat(extendedModelMap.get("savedAccounts")).isInstanceOf(List.class); List savedAccounts = (List) extendedModelMap.get("savedAccounts"); - assertThat(savedAccounts, hasSize(1)); + assertThat(savedAccounts).hasSize(1); } @Test void savedAccountsEncodedAndUnEncoded() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - assertThat(extendedModelMap, not(hasKey("savedAccounts"))); + assertThat(extendedModelMap).doesNotContainKey("savedAccounts"); MockHttpServletRequest request = new MockHttpServletRequest(); SavedAccountOption savedAccount = new SavedAccountOption(); @@ -261,35 +243,35 @@ void savedAccountsEncodedAndUnEncoded() throws Exception { savedAccount.setUserId("xxxx"); savedAccount.setOrigin("uaa"); // write Cookie2 with URLencode into value, situation after this correction - Cookie cookie2 = new Cookie("Saved-Account-zzzz", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8.name())); + Cookie cookie2 = new Cookie("Saved-Account-zzzz", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8)); request.setCookies(cookie1, cookie2); endpoint.loginForHtml(extendedModelMap, null, request, singletonList(MediaType.TEXT_HTML)); - assertThat(extendedModelMap, hasKey("savedAccounts")); - assertThat(extendedModelMap.get("savedAccounts"), instanceOf(List.class)); + assertThat(extendedModelMap).containsKey("savedAccounts"); + assertThat(extendedModelMap.get("savedAccounts")).isInstanceOf(List.class); List savedAccounts = (List) extendedModelMap.get("savedAccounts"); - assertThat(savedAccounts, hasSize(2)); + assertThat(savedAccounts).hasSize(2); // evaluate that both cookies can be parsed out has have same values SavedAccountOption savedAccount0 = savedAccounts.get(0); - assertThat(savedAccount0, notNullValue()); - assertEquals("bill", savedAccount0.getUsername()); - assertEquals("bill@example.com", savedAccount0.getEmail()); - assertEquals("uaa", savedAccount0.getOrigin()); - assertEquals("xxxx", savedAccount0.getUserId()); + assertThat(savedAccount0).isNotNull(); + assertThat(savedAccount0.getUsername()).isEqualTo("bill"); + assertThat(savedAccount0.getEmail()).isEqualTo("bill@example.com"); + assertThat(savedAccount0.getOrigin()).isEqualTo("uaa"); + assertThat(savedAccount0.getUserId()).isEqualTo("xxxx"); SavedAccountOption savedAccount1 = savedAccounts.get(1); - assertThat(savedAccount1, notNullValue()); - assertEquals("bill", savedAccount1.getUsername()); - assertEquals("bill@example.com", savedAccount1.getEmail()); - assertEquals("uaa", savedAccount1.getOrigin()); - assertEquals("xxxx", savedAccount1.getUserId()); + assertThat(savedAccount1).isNotNull(); + assertThat(savedAccount1.getUsername()).isEqualTo("bill"); + assertThat(savedAccount1.getEmail()).isEqualTo("bill@example.com"); + assertThat(savedAccount1.getOrigin()).isEqualTo("uaa"); + assertThat(savedAccount1.getUserId()).isEqualTo("xxxx"); } @Test void savedAccountsInvalidCookie() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - assertThat(extendedModelMap, not(hasKey("savedAccounts"))); + assertThat(extendedModelMap).doesNotContainKey("savedAccounts"); MockHttpServletRequest request = new MockHttpServletRequest(); SavedAccountOption savedAccount = new SavedAccountOption(); @@ -303,10 +285,10 @@ void savedAccountsInvalidCookie() throws Exception { request.setCookies(cookie1); endpoint.loginForHtml(extendedModelMap, null, request, singletonList(MediaType.TEXT_HTML)); - assertThat(extendedModelMap, hasKey("savedAccounts")); - assertThat(extendedModelMap.get("savedAccounts"), instanceOf(List.class)); + assertThat(extendedModelMap).containsKey("savedAccounts"); + assertThat(extendedModelMap.get("savedAccounts")).isInstanceOf(List.class); List savedAccounts = (List) extendedModelMap.get("savedAccounts"); - assertThat(savedAccounts, hasSize(0)); + assertThat(savedAccounts).isEmpty(); } @Test @@ -317,9 +299,9 @@ void loginReturnsOtherZone() throws Exception { zone.setSubdomain(zone.getName()); IdentityZoneHolder.set(zone); LoginInfoEndpoint endpoint = getEndpoint(zone); - assertFalse(extendedModelMap.containsAttribute("zone_name")); + assertThat(extendedModelMap.containsAttribute("zone_name")).isFalse(); endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest(), singletonList(MediaType.TEXT_HTML)); - assertEquals("some_other_zone", extendedModelMap.asMap().get("zone_name")); + assertThat(extendedModelMap.asMap()).containsEntry("zone_name", "some_other_zone"); } @Test @@ -376,11 +358,11 @@ private static void validateSelfServiceLinks( final String signup, final String passwd, final Map links) { - assertEquals(signup, links.get("createAccountLink")); - assertEquals(passwd, links.get("forgotPasswordLink")); - //json links - assertEquals(signup, links.get("register")); - assertEquals(passwd, links.get("passwd")); + assertThat(links).containsEntry("createAccountLink", signup) + .containsEntry("forgotPasswordLink", passwd) + //json links + .containsEntry("register", signup) + .containsEntry("passwd", passwd); } @Test @@ -388,9 +370,9 @@ void discoverIdentityProviderCarriesEmailIfProvided() { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpSession session = new MockHttpSession(); - endpoint.discoverIdentityProvider("testuser@fake.com", "true", null, null, extendedModelMap, session, request); + endpoint.discoverIdentityProvider("testuser@fake.com", "true", null, null, extendedModelMap, session, request); - assertEquals(extendedModelMap.get("email"), "testuser@fake.com"); + assertThat(extendedModelMap).containsEntry("email", "testuser@fake.com"); } @Test @@ -401,14 +383,14 @@ void discoverIdentityProviderCarriesLoginHintIfProvided() { String loginHint = "{\"origin\":\"my-OIDC-idp1\"}"; endpoint.discoverIdentityProvider("testuser@fake.com", "true", loginHint, null, extendedModelMap, session, request); - assertEquals(loginHint, extendedModelMap.get("login_hint")); + assertThat(extendedModelMap).containsEntry("login_hint", loginHint); } @Test void discoverIdentityProviderCarriesUsername() throws MalformedURLException { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); MockHttpServletRequest request = new MockHttpServletRequest(); - request.setParameter("username","testuser@fake.com"); + request.setParameter("username", "testuser@fake.com"); MockHttpSession session = new MockHttpSession(); String loginHint = "{\"origin\":\"my-OIDC-idp1\"}"; IdentityProvider idp = mock(IdentityProvider.class); @@ -426,7 +408,7 @@ void discoverIdentityProviderCarriesUsername() throws MalformedURLException { String redirect = endpoint.discoverIdentityProvider("testuser@fake.com", null, loginHint, "testuser@fake.com", extendedModelMap, session, request); - assertThat(redirect, containsString("username=testuser@fake.com")); + assertThat(redirect).contains("username=testuser@fake.com"); } @Test @@ -440,32 +422,28 @@ void discoverIdentityProviderWritesLoginHintIfOnlyUaa() { uaaIdentityProvider.setType(OriginKeys.UAA); when(mockIdentityProviderProvisioning.retrieveActive("uaa")).thenReturn(singletonList(uaaIdentityProvider)); - endpoint.discoverIdentityProvider("testuser@fake.com", null, null, null, extendedModelMap, session, request); + endpoint.discoverIdentityProvider("testuser@fake.com", null, null, null, extendedModelMap, session, request); String loginHint = "{\"origin\":\"uaa\"}"; - assertEquals(loginHint, extendedModelMap.get("login_hint")); + assertThat(extendedModelMap).containsEntry("login_hint", loginHint); } @Test void originChooserCarriesLoginHint() { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpSession session = new MockHttpSession(); - String redirect = endpoint.loginUsingOrigin("providedOrigin", extendedModelMap, session, request); + String redirect = endpoint.loginUsingOrigin("providedOrigin"); - assertThat(redirect, startsWith("redirect:/login?discoveryPerformed=true")); - assertThat(redirect, containsString("login_hint")); - assertThat(redirect, containsString("providedOrigin")); + assertThat(redirect).startsWith("redirect:/login?discoveryPerformed=true") + .contains("login_hint") + .contains("providedOrigin"); } @Test void originChooserDefaultsToNoLoginHint() { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpSession session = new MockHttpSession(); - String redirect = endpoint.loginUsingOrigin(null, extendedModelMap, session, request); + String redirect = endpoint.loginUsingOrigin(null); - assertEquals(redirect, "redirect:/login?discoveryPerformed=true"); + assertThat(redirect).isEqualTo("redirect:/login?discoveryPerformed=true"); } @Test @@ -484,20 +462,21 @@ private String check_links_urls(IdentityZone zone) { String baseUrl = "http://uaa.domain.com"; LoginInfoEndpoint endpoint = getEndpoint(zone, null, baseUrl); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", baseUrl)); - assertEquals(addSubdomainToUrl(baseUrl, IdentityZoneHolder.get().getSubdomain()), ((Map) extendedModelMap.asMap().get("links")).get("uaa")); - assertEquals(addSubdomainToUrl(baseUrl.replace("uaa", "login"), IdentityZoneHolder.get().getSubdomain()), ((Map) extendedModelMap.asMap().get("links")).get("login")); + assertThat(((Map) extendedModelMap.asMap().get("links"))).containsEntry("uaa", addSubdomainToUrl(baseUrl, IdentityZoneHolder.get().getSubdomain())) + .containsEntry("login", addSubdomainToUrl(baseUrl.replace("uaa", "login"), IdentityZoneHolder.get().getSubdomain())); String loginBaseUrl = "http://external-login.domain.com"; endpoint = getEndpoint(zone, loginBaseUrl, baseUrl); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", baseUrl)); - assertEquals(addSubdomainToUrl(baseUrl, IdentityZoneHolder.get().getSubdomain()), ((Map) extendedModelMap.asMap().get("links")).get("uaa")); - assertEquals(loginBaseUrl, ((Map) extendedModelMap.asMap().get("links")).get("login")); + assertThat(((Map) extendedModelMap.asMap().get("links"))) + .containsEntry("uaa", addSubdomainToUrl(baseUrl, IdentityZoneHolder.get().getSubdomain())) + .containsEntry("login", loginBaseUrl); - when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions((List) isNull(), eq(zone))).thenReturn(idps); + when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(isNull(), eq(zone))).thenReturn(idps); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", baseUrl)); - Map mapPrompts = (Map) extendedModelMap.get("prompts"); - assertNotNull(mapPrompts.get("passcode")); - assertEquals("Temporary Authentication Code ( Get one at " + addSubdomainToUrl(HTTP_LOCALHOST_8080_UAA, IdentityZoneHolder.get().getSubdomain()) + "/passcode )", ((String[]) mapPrompts.get("passcode"))[1]); + Map mapPrompts = (Map) extendedModelMap.get("prompts"); + assertThat(mapPrompts).containsKey("passcode"); + assertThat(((String[]) mapPrompts.get("passcode"))[1]).isEqualTo("Temporary Authentication Code ( Get one at " + addSubdomainToUrl(HTTP_LOCALHOST_8080_UAA, IdentityZoneHolder.get().getSubdomain()) + "/passcode )"); return baseUrl; } @@ -510,9 +489,9 @@ void no_self_service_links_if_self_service_disabled() { LoginInfoEndpoint endpoint = getEndpoint(zone); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); Map links = (Map) extendedModelMap.asMap().get("links"); - assertNotNull(links); - assertNull(links.get("register")); - assertNull(links.get("passwd")); + assertThat(links).isNotNull() + .doesNotContainKey("register") + .doesNotContainKey("passwd"); } @Test @@ -520,15 +499,15 @@ void no_ui_links_for_json() { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); Map links = (Map) extendedModelMap.asMap().get("links"); - assertNotNull(links); - assertNull(links.get("linkCreateAccountShow")); - assertNull(links.get("fieldUsernameShow")); - assertNull(links.get("forgotPasswordLink")); - assertNull(links.get("createAccountLink")); - assertEquals("http://someurl", links.get("login")); - assertEquals("http://someurl", links.get("uaa")); - assertEquals("/create_account", links.get("register")); - assertEquals("/forgot_password", links.get("passwd")); + assertThat(links).isNotNull() + .doesNotContainKey("linkCreateAccountShow") + .doesNotContainKey("fieldUsernameShow") + .doesNotContainKey("forgotPasswordLink") + .doesNotContainKey("createAccountLink") + .containsEntry("login", "http://someurl") + .containsEntry("uaa", "http://someurl") + .containsEntry("register", "/create_account") + .containsEntry("passwd", "/forgot_password"); } @Test @@ -537,15 +516,13 @@ void saml_links_for_json() { when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(any(), any())).thenReturn(idps); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); Map links = (Map) extendedModelMap.asMap().get("links"); - assertEquals("http://someurl", links.get("login")); - assertTrue(extendedModelMap.get("idpDefinitions") instanceof Map); + assertThat(links).containsEntry("login", "http://someurl"); + assertThat(extendedModelMap.get("idpDefinitions")).isInstanceOf(Map.class); Map idpDefinitions = (Map) extendedModelMap.get("idpDefinitions"); - for (SamlIdentityProviderDefinition def : idps) { - assertEquals( - "http://someurl/saml/discovery?returnIDParam=idp&entityID=" + endpoint.getZonifiedEntityId() + "&idp=" + def.getIdpEntityAlias() + "&isPassive=true", - idpDefinitions.get(def.getIdpEntityAlias()) - ); - } + + var defs = idps.stream().collect(Collectors.toMap(SamlIdentityProviderDefinition::getIdpEntityAlias, + def -> "http://someurl/saml2/authenticate/%s".formatted(def.getIdpEntityAlias()))); + assertThat(idpDefinitions).containsAllEntriesOf(defs); } @Test @@ -553,9 +530,9 @@ void saml_links_for_html() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl"), null); Map links = (Map) extendedModelMap.asMap().get("links"); - assertNotNull(links); - assertEquals("http://someurl", links.get("login")); - assertTrue(extendedModelMap.get("idpDefinitions") instanceof Collection); + assertThat(links).isNotNull() + .containsEntry("login", "http://someurl"); + assertThat(extendedModelMap.get("idpDefinitions")).isInstanceOf(Collection.class); } @Test @@ -566,13 +543,14 @@ void no_self_service_links_if_internal_user_management_disabled() { uaaIdentityProvider.setConfig(uaaIdentityProviderDefinition); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); Map links = (Map) extendedModelMap.asMap().get("links"); - assertNotNull(links); - assertNull(links.get("register")); - assertNull(links.get("passwd")); - assertNull(links.get("createAccountLink")); - assertNull(links.get("forgotPasswordLink")); - assertNull(extendedModelMap.asMap().get("createAccountLink")); - assertNull(extendedModelMap.asMap().get("forgotPasswordLink")); + assertThat(links).isNotNull() + .doesNotContainKey("register") + .doesNotContainKey("passwd") + .doesNotContainKey("createAccountLink") + .doesNotContainKey("forgotPasswordLink"); + assertThat(extendedModelMap.asMap()) + .doesNotContainKey("createAccountLink") + .doesNotContainKey("forgotPasswordLink"); } @Test @@ -584,66 +562,66 @@ void no_usernamePasswordBoxes_if_internalAuth_and_ldap_disabled() throws Excepti ldapIdentityProvider.setActive(false); when(mockIdentityProviderProvisioning.retrieveByOrigin(OriginKeys.LDAP, "uaa")).thenReturn(ldapIdentityProvider); - IdentityProvider uaaIdentityProvider = new IdentityProvider(); - uaaIdentityProvider.setActive(false); - when(mockIdentityProviderProvisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, "uaa")).thenReturn(uaaIdentityProvider); - + IdentityProvider idp = new IdentityProvider(); + idp.setActive(false); + when(mockIdentityProviderProvisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, "uaa")).thenReturn(idp); endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl"), null); - assertFalse((Boolean) extendedModelMap.get("fieldUsernameShow")); + assertThat((Boolean) extendedModelMap.get("fieldUsernameShow")).isFalse(); } @Test void promptLogic() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl"), singletonList(MediaType.TEXT_HTML)); - assertNotNull("prompts attribute should be present", extendedModelMap.get("prompts")); - assertTrue("prompts should be a Map for Html content", extendedModelMap.get("prompts") instanceof Map); - Map mapPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals("there should be two prompts for html", 2, mapPrompts.size()); - assertNotNull(mapPrompts.get("username")); - assertNotNull(mapPrompts.get("password")); - assertNull(mapPrompts.get("passcode")); + assertThat(extendedModelMap).as("prompts attribute should be present").containsKey("prompts"); + assertThat(extendedModelMap.get("prompts")).as("prompts should be a Map for Html content").isInstanceOf(Map.class); + Map mapPrompts = (Map) extendedModelMap.get("prompts"); + assertThat(mapPrompts).as("there should be two prompts for html") + .hasSize(2) + .containsKey("username") + .containsKey("password") + .doesNotContainKey("passcode"); extendedModelMap.clear(); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); - assertNotNull("prompts attribute should be present", extendedModelMap.get("prompts")); - assertTrue("prompts should be a Map for JSON content", extendedModelMap.get("prompts") instanceof Map); - mapPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals("there should be two prompts for html", 2, mapPrompts.size()); - assertNotNull(mapPrompts.get("username")); - assertNotNull(mapPrompts.get("password")); - assertNull(mapPrompts.get("passcode")); + assertThat(extendedModelMap).as("prompts attribute should be present").containsKey("prompts"); + assertThat(extendedModelMap.get("prompts")).as("prompts should be a Map for JSON content").isInstanceOf(Map.class); + mapPrompts = (Map) extendedModelMap.get("prompts"); + assertThat(mapPrompts).as("there should be two prompts for html").hasSize(2) + .containsKey("username") + .containsKey("password") + .doesNotContainKey("passcode"); //add a SAML IDP, should make the passcode prompt appear extendedModelMap.clear(); - when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions((List) isNull(), eq(IdentityZone.getUaa()))).thenReturn(idps); + when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(isNull(), eq(IdentityZone.getUaa()))).thenReturn(idps); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); - assertNotNull("prompts attribute should be present", extendedModelMap.get("prompts")); - assertTrue("prompts should be a Map for JSON content", extendedModelMap.get("prompts") instanceof Map); - mapPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals("there should be three prompts for html", 3, mapPrompts.size()); - assertNotNull(mapPrompts.get("username")); - assertNotNull(mapPrompts.get("password")); - assertNotNull(mapPrompts.get("passcode")); + assertThat(extendedModelMap).as("prompts attribute should be present").containsKey("prompts"); + assertThat(extendedModelMap.get("prompts")).as("prompts should be a Map for JSON content").isInstanceOf(Map.class); + mapPrompts = (Map) extendedModelMap.get("prompts"); + assertThat(mapPrompts).as("there should be three prompts for html").hasSize(3) + .containsKey("username") + .containsKey("password") + .containsKey("passcode"); - when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions((List) isNull(), eq(IdentityZone.getUaa()))).thenReturn(idps); + when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(isNull(), eq(IdentityZone.getUaa()))).thenReturn(idps); IdentityProvider ldapIdentityProvider = new IdentityProvider(); ldapIdentityProvider.setActive(false); when(mockIdentityProviderProvisioning.retrieveByOrigin(OriginKeys.LDAP, "uaa")).thenReturn(ldapIdentityProvider); - IdentityProvider uaaIdentityProvider = new IdentityProvider(); - uaaIdentityProvider.setActive(false); - when(mockIdentityProviderProvisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, "uaa")).thenReturn(uaaIdentityProvider); + IdentityProvider idp = new IdentityProvider(); + idp.setActive(false); + when(mockIdentityProviderProvisioning.retrieveByOriginIgnoreActiveFlag(OriginKeys.UAA, "uaa")).thenReturn(idp); extendedModelMap.clear(); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); - assertNotNull("prompts attribute should be present", extendedModelMap.get("prompts")); - mapPrompts = (Map) extendedModelMap.get("prompts"); - assertNull(mapPrompts.get("username")); - assertNull(mapPrompts.get("password")); - assertNotNull(mapPrompts.get("passcode")); + assertThat(extendedModelMap).as("prompts attribute should be present").containsKey("prompts"); + mapPrompts = (Map) extendedModelMap.get("prompts"); + assertThat(mapPrompts).doesNotContainKey("username") + .doesNotContainKey("password") + .containsKey("passcode"); } @Test @@ -658,46 +636,46 @@ void filterIdpsForDefaultZone() throws Exception { SessionUtils.setSavedRequestSession(session, savedRequest); request.setSession(session); // mock SamlIdentityProviderConfigurator - when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions((List) isNull(), eq(IdentityZone.getUaa()))).thenReturn(idps); + when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(isNull(), eq(IdentityZone.getUaa()))).thenReturn(idps); endpoint.loginForHtml(extendedModelMap, null, request, singletonList(MediaType.TEXT_HTML)); Collection idpDefinitions = (Collection) extendedModelMap.asMap().get("idpDefinitions"); - assertEquals(2, idpDefinitions.size()); + assertThat(idpDefinitions).hasSize(2); Iterator iterator = idpDefinitions.iterator(); SamlIdentityProviderDefinition clientIdp = iterator.next(); - assertEquals("awesome-idp", clientIdp.getIdpEntityAlias()); - assertTrue(clientIdp.isShowSamlLink()); + assertThat(clientIdp.getIdpEntityAlias()).isEqualTo("awesome-idp"); + assertThat(clientIdp.isShowSamlLink()).isTrue(); clientIdp = iterator.next(); - assertEquals("my-client-awesome-idp", clientIdp.getIdpEntityAlias()); - assertTrue(clientIdp.isShowSamlLink()); - assertEquals(true, extendedModelMap.asMap().get("fieldUsernameShow")); - assertEquals(true, extendedModelMap.asMap().get("linkCreateAccountShow")); + assertThat(clientIdp.getIdpEntityAlias()).isEqualTo("my-client-awesome-idp"); + assertThat(clientIdp.isShowSamlLink()).isTrue(); + assertThat(extendedModelMap.asMap()).containsEntry("fieldUsernameShow", true) + .containsEntry("linkCreateAccountShow", true); } @Test void filterIdpsWithNoSavedRequest() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions((List) isNull(), eq(IdentityZone.getUaa()))).thenReturn(idps); + when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(isNull(), eq(IdentityZone.getUaa()))).thenReturn(idps); endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest(), singletonList(MediaType.TEXT_HTML)); Collection idpDefinitions = (Collection) extendedModelMap.asMap().get("idpDefinitions"); - assertEquals(2, idpDefinitions.size()); + assertThat(idpDefinitions).hasSize(2); Iterator iterator = idpDefinitions.iterator(); SamlIdentityProviderDefinition clientIdp = iterator.next(); - assertEquals("awesome-idp", clientIdp.getIdpEntityAlias()); - assertTrue(clientIdp.isShowSamlLink()); + assertThat(clientIdp.getIdpEntityAlias()).isEqualTo("awesome-idp"); + assertThat(clientIdp.isShowSamlLink()).isTrue(); clientIdp = iterator.next(); - assertEquals("my-client-awesome-idp", clientIdp.getIdpEntityAlias()); - assertTrue(clientIdp.isShowSamlLink()); - assertEquals(true, extendedModelMap.asMap().get("fieldUsernameShow")); - assertEquals(true, extendedModelMap.asMap().get("linkCreateAccountShow")); + assertThat(clientIdp.getIdpEntityAlias()).isEqualTo("my-client-awesome-idp"); + assertThat(clientIdp.isShowSamlLink()).isTrue(); + assertThat(extendedModelMap.asMap()).containsEntry("fieldUsernameShow", true) + .containsEntry("linkCreateAccountShow", true); } @Test @@ -718,18 +696,18 @@ void filterIDPsForAuthcodeClientInDefaultZone() throws Exception { List clientIDPs = new LinkedList<>(); clientIDPs.add(createIdentityProviderDefinition("my-client-awesome-idp1", "uaa")); clientIDPs.add(createIdentityProviderDefinition("my-client-awesome-idp2", "uaa")); - when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(eq(allowedProviders), eq(IdentityZone.getUaa()))).thenReturn(clientIDPs); + when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(allowedProviders, IdentityZone.getUaa())).thenReturn(clientIDPs); LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); endpoint.loginForHtml(extendedModelMap, null, request, singletonList(MediaType.TEXT_HTML)); Collection idpDefinitions = (Collection) extendedModelMap.asMap().get("idpDefinitions"); - assertEquals(2, idpDefinitions.size()); + assertThat(idpDefinitions).hasSize(2); - assertThat(idpDefinitions, PredicateMatcher.has(c -> c.getIdpEntityAlias().equals("my-client-awesome-idp1"))); - assertThat(idpDefinitions, PredicateMatcher.has(c -> c.isShowSamlLink())); - assertEquals(true, extendedModelMap.asMap().get("fieldUsernameShow")); - assertEquals(false, extendedModelMap.asMap().get("linkCreateAccountShow")); + assertThat(idpDefinitions).extracting(SamlIdentityProviderDefinition::getIdpEntityAlias).contains("my-client-awesome-idp1"); + assertThat(idpDefinitions).extracting(SamlIdentityProviderDefinition::isShowSamlLink).contains(true); + assertThat(extendedModelMap.asMap()).containsEntry("fieldUsernameShow", true) + .containsEntry("linkCreateAccountShow", false); } @Test @@ -753,19 +731,18 @@ void filterIDPsForAuthcodeClientInOtherZone() throws Exception { List clientIDPs = new LinkedList<>(); clientIDPs.add(createIdentityProviderDefinition("my-client-awesome-idp1", "uaa")); clientIDPs.add(createIdentityProviderDefinition("my-client-awesome-idp2", "uaa")); - when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(eq(allowedProviders), eq(zone))).thenReturn(clientIDPs); - + when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions(allowedProviders, zone)).thenReturn(clientIDPs); LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); endpoint.loginForHtml(extendedModelMap, null, request, singletonList(MediaType.TEXT_HTML)); Collection idpDefinitions = (Collection) extendedModelMap.asMap().get("idpDefinitions"); - assertEquals(2, idpDefinitions.size()); + assertThat(idpDefinitions).hasSize(2); - assertThat(idpDefinitions, PredicateMatcher.has(c -> c.getIdpEntityAlias().equals("my-client-awesome-idp1"))); - assertThat(idpDefinitions, PredicateMatcher.has(SamlIdentityProviderDefinition::isShowSamlLink)); - assertEquals(false, extendedModelMap.asMap().get("fieldUsernameShow")); - assertEquals(false, extendedModelMap.asMap().get("linkCreateAccountShow")); + assertThat(idpDefinitions).extracting(SamlIdentityProviderDefinition::getIdpEntityAlias).contains("my-client-awesome-idp1"); + assertThat(idpDefinitions).extracting(SamlIdentityProviderDefinition::isShowSamlLink).contains(true); + assertThat(extendedModelMap.asMap()).containsEntry("fieldUsernameShow", false) + .containsEntry("linkCreateAccountShow", false); } @Test @@ -790,7 +767,7 @@ void authcodeWithAllowedProviderStillUsesAccountChooser() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); endpoint.loginForHtml(extendedModelMap, null, request, singletonList(MediaType.TEXT_HTML)); - assertNull(extendedModelMap.get("login_hint")); + assertThat(extendedModelMap).doesNotContainKey("login_hint"); } @Test @@ -838,9 +815,9 @@ void allowedIdpsforClientOIDCProvider() throws Exception { endpoint.loginForHtml(extendedModelMap, null, request, singletonList(MediaType.TEXT_HTML)); Collection> idpDefinitions = (Collection>) extendedModelMap.asMap().get("oauthLinks"); - assertEquals(2, idpDefinitions.size()); + assertThat(idpDefinitions).hasSize(2); // Expect this always on top of list because of sorting - assertEquals("my-OIDC-idp1", ((Map.Entry) idpDefinitions.iterator().next()).getValue()); + assertThat(((Map.Entry) idpDefinitions.iterator().next()).getValue()).isEqualTo("my-OIDC-idp1"); } @Test @@ -859,7 +836,7 @@ void oauth_provider_links_shown() throws Exception { when(mockIdentityProviderProvisioning.retrieveAll(anyBoolean(), anyString())).thenReturn(singletonList(identityProvider)); endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest(), singletonList(MediaType.TEXT_HTML)); - assertThat(extendedModelMap.get("showLoginLinks"), equalTo(true)); + assertThat((Boolean) extendedModelMap.get("showLoginLinks")).isTrue(); } @Test @@ -876,8 +853,8 @@ void passcode_prompt_present_whenThereIsAtleastOneActiveOauthProvider() throws E when(mockIdentityProviderProvisioning.retrieveAll(anyBoolean(), anyString())).thenReturn(singletonList(identityProvider)); endpoint.infoForLoginJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); - Map mapPrompts = (Map) extendedModelMap.get("prompts"); - assertNotNull(mapPrompts.get("passcode")); + Map mapPrompts = (Map) extendedModelMap.get("prompts"); + assertThat(mapPrompts).containsKey("passcode"); } @Test @@ -895,8 +872,8 @@ void passcode_prompt_present_whenThereIsAtleastOneActiveOauthProvider_stillWorks when(mockIdentityProviderProvisioning.retrieveAll(anyBoolean(), anyString())).thenReturn(singletonList(identityProvider)); endpoint.infoForLoginJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); - Map mapPrompts = (Map) extendedModelMap.get("prompts"); - assertNotNull(mapPrompts.get("passcode")); + Map mapPrompts = (Map) extendedModelMap.get("prompts"); + assertThat(mapPrompts).containsKey("passcode"); } @Test @@ -914,8 +891,8 @@ void passcode_prompt_present_whenThereIsAtleastOneActiveOauthProvider_stillWorks when(mockIdentityProviderProvisioning.retrieveAll(anyBoolean(), anyString())).thenReturn(singletonList(identityProvider)); endpoint.infoForLoginJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); - Map mapPrompts = (Map) extendedModelMap.get("prompts"); - assertNotNull(mapPrompts.get("passcode")); + Map mapPrompts = (Map) extendedModelMap.get("prompts"); + assertThat(mapPrompts).containsKey("passcode"); } @Test @@ -936,15 +913,15 @@ void we_return_both_oauth_and_oidc_providers() throws Exception { oidcProvider.setConfig(oidcDefinition); when(mockIdentityProviderProvisioning.retrieveAll(anyBoolean(), anyString())).thenReturn(Arrays.asList(oauthProvider, oidcProvider)); - assertEquals(2, endpoint.getOauthIdentityProviderDefinitions(null).size()); + assertThat(endpoint.getOauthIdentityProviderDefinitions(null)).hasSize(2); } @Test void externalOAuthCallback_redirectsToHomeIfNoSavedRequest() { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); HttpSession session = new MockHttpSession(); - String redirectUrl = endpoint.handleExternalOAuthCallback(session); - assertEquals("redirect:/home", redirectUrl); + String redirectUrl = endpoint.handleExternalOAuthCallback(session, "origin"); + assertThat(redirectUrl).isEqualTo("redirect:/home"); } @Test @@ -954,15 +931,14 @@ void externalOAuthCallback_redirectsToSavedRequestIfPresent() { DefaultSavedRequest savedRequest = mock(DefaultSavedRequest.class); when(savedRequest.getRedirectUrl()).thenReturn("/some.redirect.url"); SessionUtils.setSavedRequestSession(session, savedRequest); - String redirectUrl = endpoint.handleExternalOAuthCallback(session); - assertEquals("redirect:/some.redirect.url", redirectUrl); + String redirectUrl = endpoint.handleExternalOAuthCallback(session, "origin"); + assertThat(redirectUrl).isEqualTo("redirect:/some.redirect.url"); } @Test void loginWithInvalidMediaType() { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - assertThrows(HttpMediaTypeNotAcceptableException.class, - () -> endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest(), singletonList(MediaType.TEXT_XML))); + assertThatExceptionOfType(HttpMediaTypeNotAcceptableException.class).isThrownBy(() -> endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest(), singletonList(MediaType.TEXT_XML))); } @Test @@ -984,17 +960,13 @@ void loginHintEmailDomain() throws Exception { when(mockIdentityProviderProvisioning.retrieveAll(anyBoolean(), any())).thenReturn(singletonList(mockProvider)); LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - - SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"example.com"}); - - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertThat(redirect, startsWith("redirect:http://localhost:8080/uaa")); - assertThat(redirect, containsString("my-OIDC-idp1")); - assertNull(extendedModelMap.get("login_hint")); + assertThat(redirect).startsWith("redirect:http://localhost:8080/uaa") + .contains("my-OIDC-idp1"); + assertThat(extendedModelMap).doesNotContainKey("login_hint"); } @Test @@ -1011,7 +983,7 @@ void loginHintOriginUaa() throws Exception { endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("{\"origin\":\"uaa\"}", extendedModelMap.get("login_hint")); + assertThat(extendedModelMap).containsEntry("login_hint", "{\"origin\":\"uaa\"}"); } @Test @@ -1030,8 +1002,8 @@ void loginHintOriginUaa_onlyAccountChooser() throws Exception { String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("idp_discovery/password", redirect); - assertEquals("{\"origin\":\"uaa\"}", extendedModelMap.get("login_hint")); + assertThat(redirect).isEqualTo("idp_discovery/password"); + assertThat(extendedModelMap).containsEntry("login_hint", "{\"origin\":\"uaa\"}"); } @Test @@ -1040,29 +1012,22 @@ void loginHintOriginUaaDirectCall() throws Exception { mockHttpServletRequest.setParameter("login_hint", "{\"origin\":\"uaa\"}"); MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("{\"origin\":\"uaa\"}", extendedModelMap.get("login_hint")); + assertThat(extendedModelMap).containsEntry("login_hint", "{\"origin\":\"uaa\"}"); } @Test void loginHintOriginUaaDoubleEncoded() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); - MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{URLEncoder.encode("{\"origin\":\"uaa\"}", UTF_8)}); - - endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals(extendedModelMap.get("login_hint"), URLEncoder.encode("{\"origin\":\"uaa\"}", UTF_8)); + assertThat(URLEncoder.encode("{\"origin\":\"uaa\"}", UTF_8)).isEqualTo(extendedModelMap.get("login_hint")); } @Test @@ -1084,7 +1049,7 @@ void loginHintOriginUaaAllowedProvidersNull() throws Exception { endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("{\"origin\":\"uaa\"}", extendedModelMap.get("login_hint")); + assertThat(extendedModelMap).containsEntry("login_hint", "{\"origin\":\"uaa\"}"); } @Test @@ -1111,9 +1076,9 @@ void loginHintUaaNotAllowedLoginPageNotEmpty() throws Exception { endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertNull(extendedModelMap.get("login_hint")); - assertFalse((Boolean) extendedModelMap.get("fieldUsernameShow")); - assertEquals("invalid_login_hint", extendedModelMap.get("error")); + assertThat(extendedModelMap).doesNotContainKey("login_hint") + .containsEntry("error", "invalid_login_hint") + .containsEntry("fieldUsernameShow", false); } @Test @@ -1125,26 +1090,26 @@ void testNoLoginHintAccountChooser() throws Exception { savedAccount.setEmail("bob@example.com"); savedAccount.setUserId("xxxx"); savedAccount.setOrigin("uaa"); - Cookie cookie1 = new Cookie("Saved-Account-xxxx", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8.name())); + Cookie cookie1 = new Cookie("Saved-Account-xxxx", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8)); savedAccount.setUsername("tim"); savedAccount.setEmail("tim@example.org"); savedAccount.setUserId("zzzz"); savedAccount.setOrigin("ldap"); - Cookie cookie2 = new Cookie("Saved-Account-zzzz", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8.name())); + Cookie cookie2 = new Cookie("Saved-Account-zzzz", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8)); mockHttpServletRequest.setCookies(cookie1, cookie2); MultitenantClientServices clientDetailsService = mockClientService(); LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); + SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); IdentityZoneHolder.get().getConfig().setIdpDiscoveryEnabled(true); IdentityZoneHolder.get().getConfig().setAccountChooserEnabled(true); String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, Collections.singletonList(MediaType.TEXT_HTML)); - assertEquals("idp_discovery/account_chooser", redirect); + assertThat(redirect).isEqualTo("idp_discovery/account_chooser"); verify(mockIdentityProviderProvisioning, times(0)).retrieveAll(eq(true), anyString()); verify(mockSamlIdentityProviderConfigurator, times(0)).getIdentityProviderDefinitions(any(), any()); } @@ -1158,14 +1123,13 @@ void loginHintOriginUaaSkipAccountChooser() throws Exception { savedAccount.setEmail("bob@example.com"); savedAccount.setUserId("xxxx"); savedAccount.setOrigin("uaa"); - Cookie cookie1 = new Cookie("Saved-Account-xxxx", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8.name())); + Cookie cookie1 = new Cookie("Saved-Account-xxxx", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8)); savedAccount.setUsername("tim"); savedAccount.setEmail("tim@example.org"); savedAccount.setUserId("zzzz"); savedAccount.setOrigin("ldap"); - Cookie cookie2 = new Cookie("Saved-Account-zzzz", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8.name())); - + Cookie cookie2 = new Cookie("Saved-Account-zzzz", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount), UTF_8)); mockHttpServletRequest.setCookies(cookie1, cookie2); MultitenantClientServices clientDetailsService = mockClientService(); @@ -1176,56 +1140,47 @@ void loginHintOriginUaaSkipAccountChooser() throws Exception { IdentityZoneHolder.get().getConfig().setIdpDiscoveryEnabled(true); IdentityZoneHolder.get().getConfig().setAccountChooserEnabled(true); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("{\"origin\":\"uaa\"}", extendedModelMap.get("login_hint")); - assertEquals("idp_discovery/email", redirect); + assertThat(extendedModelMap).containsEntry("login_hint", "{\"origin\":\"uaa\"}"); + assertThat(redirect).isEqualTo("idp_discovery/email"); } @Test void invalidLoginHintErrorOnDiscoveryPage() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); - MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"{\"origin\":\"invalidorigin\"}"}); IdentityZoneHolder.get().getConfig().setIdpDiscoveryEnabled(true); IdentityZoneHolder.get().getConfig().setAccountChooserEnabled(false); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("idp_discovery/email", redirect); + assertThat(redirect).isEqualTo("idp_discovery/email"); } @Test void invalidLoginHintErrorOnAccountChooserPage() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); - MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"{\"origin\":\"invalidorigin\"}"}); IdentityZoneHolder.get().getConfig().setIdpDiscoveryEnabled(false); IdentityZoneHolder.get().getConfig().setAccountChooserEnabled(true); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("idp_discovery/account_chooser", redirect); - assertTrue(extendedModelMap.containsKey("error")); + assertThat(redirect).isEqualTo("idp_discovery/account_chooser"); + assertThat(extendedModelMap).containsKey("error"); } @Test - public void testInvalidLoginHintLoginPageReturnsList() throws Exception { + void testInvalidLoginHintLoginPageReturnsList() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); UaaClientDetails clientDetails = new UaaClientDetails(); @@ -1242,71 +1197,53 @@ public void testInvalidLoginHintLoginPageReturnsList() throws Exception { SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"{\"origin\":\"invalidorigin\"}"}); - endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, Collections.singletonList(MediaType.TEXT_HTML)); - - assertFalse(((Collection>)extendedModelMap.get("oauthLinks")).isEmpty()); + assertThat(((Collection>) extendedModelMap.get("oauthLinks"))).isNotEmpty(); } @Test void loginHintOriginOidc() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); - MultitenantClientServices clientDetailsService = mockClientService(); - mockLoginHintProvider(configurator); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"{\"origin\":\"my-OIDC-idp1\"}"}); - - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertThat(redirect, startsWith("redirect:http://localhost:8080/uaa")); - assertThat(redirect, containsString("my-OIDC-idp1")); - assertNull(extendedModelMap.get("login_hint")); + assertThat(redirect).startsWith("redirect:http://localhost:8080/uaa") + .contains("my-OIDC-idp1"); + assertThat(extendedModelMap).doesNotContainKey("login_hint"); } @Test void loginHintOriginOidcForJson() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); - MultitenantClientServices clientDetailsService = mockClientService(); - mockLoginHintProvider(configurator); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"{\"origin\":\"my-OIDC-idp1\"}"}); - - endpoint.infoForLoginJson(extendedModelMap, null, mockHttpServletRequest); - assertNotNull(extendedModelMap.get("prompts")); - assertTrue(extendedModelMap.get("prompts") instanceof Map); + assertThat(extendedModelMap).containsKey("prompts"); + assertThat(extendedModelMap.get("prompts")).isInstanceOf(Map.class); Map returnedPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals(2, returnedPrompts.size()); + assertThat(returnedPrompts).hasSize(2); } @Test void loginHintOriginInvalid() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); - MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"{\"origin\":\"my-OIDC-idp1\"}"}); when(configurator.retrieveByOrigin(eq("my-OIDC-idp1"), anyString())).thenThrow(new EmptyResultDataAccessException(0)); - endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - - assertEquals("invalid_login_hint", extendedModelMap.get("error")); + assertThat(extendedModelMap).containsEntry("error", "invalid_login_hint"); } @Test @@ -1326,20 +1263,17 @@ void getPromptsFromOIDCProvider() { when(mockIdentityProviderProvisioning.retrieveByOrigin("OIDC-without-prompts", "uaa")).thenReturn(provider); MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - endpoint.infoForLoginJson(extendedModelMap, null, mockHttpServletRequest); - assertNotNull(extendedModelMap.get("prompts")); - assertTrue(extendedModelMap.get("prompts") instanceof Map); + assertThat(extendedModelMap).containsKey("prompts"); + assertThat(extendedModelMap.get("prompts")).isInstanceOf(Map.class); Map returnedPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals(2, returnedPrompts.size()); - - assertNotNull(returnedPrompts.get("username")); - assertEquals("MyEmail", returnedPrompts.get("username")[1]); - assertNotNull(returnedPrompts.get("password")); - assertEquals("MyPassword", returnedPrompts.get("password")[1]); + assertThat(returnedPrompts).hasSize(2) + .containsKey("username"); + assertThat(returnedPrompts.get("username")[1]).isEqualTo("MyEmail"); + assertThat(returnedPrompts).containsKey("password"); + assertThat(returnedPrompts.get("password")[1]).isEqualTo("MyPassword"); } @Test @@ -1352,20 +1286,17 @@ void getPromptsFromNonOIDCProvider() { when(mockIdentityProviderProvisioning.retrieveByOrigin("non-OIDC", "uaa")).thenReturn(provider); MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - - endpoint.infoForLoginJson(extendedModelMap, null, mockHttpServletRequest); - assertNotNull(extendedModelMap.get("prompts")); - assertTrue(extendedModelMap.get("prompts") instanceof Map); + assertThat(extendedModelMap).containsKey("prompts"); + assertThat(extendedModelMap.get("prompts")).isInstanceOf(Map.class); Map returnedPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals(2, returnedPrompts.size()); - assertNotNull(returnedPrompts.get("username")); - assertEquals("Email", returnedPrompts.get("username")[1]); - assertNotNull(returnedPrompts.get("password")); - assertEquals("Password", returnedPrompts.get("password")[1]); + assertThat(returnedPrompts).hasSize(2) + .containsKey("username"); + assertThat(returnedPrompts.get("username")[1]).isEqualTo("Email"); + assertThat(returnedPrompts).containsKey("password"); + assertThat(returnedPrompts.get("password")[1]).isEqualTo("Password"); } @Test @@ -1375,20 +1306,17 @@ void getPromptsFromNonExistentProvider() { when(mockIdentityProviderProvisioning.retrieveByOrigin("non-OIDC", "uaa")).thenThrow(mock(DataAccessException.class)); MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - - endpoint.infoForLoginJson(extendedModelMap, null, mockHttpServletRequest); - assertNotNull(extendedModelMap.get("prompts")); - assertTrue(extendedModelMap.get("prompts") instanceof Map); + assertThat(extendedModelMap).containsKey("prompts"); + assertThat(extendedModelMap.get("prompts")).isInstanceOf(Map.class); Map returnedPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals(2, returnedPrompts.size()); - assertNotNull(returnedPrompts.get("username")); - assertEquals("Email", returnedPrompts.get("username")[1]); - assertNotNull(returnedPrompts.get("password")); - assertEquals("Password", returnedPrompts.get("password")[1]); + assertThat(returnedPrompts).hasSize(2) + .containsKey("username"); + assertThat(returnedPrompts.get("username")[1]).isEqualTo("Email"); + assertThat(returnedPrompts).containsKey("password"); + assertThat(returnedPrompts.get("password")[1]).isEqualTo("Password"); } @Test @@ -1407,15 +1335,14 @@ void getPromptsFromOIDCProviderWithoutPrompts() { endpoint.infoForLoginJson(extendedModelMap, null, mockHttpServletRequest); - assertNotNull(extendedModelMap.get("prompts")); - assertTrue(extendedModelMap.get("prompts") instanceof Map); + assertThat(extendedModelMap).containsKey("prompts"); + assertThat(extendedModelMap.get("prompts")).isInstanceOf(Map.class); Map returnedPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals(2, returnedPrompts.size()); - - assertNotNull(returnedPrompts.get("username")); - assertEquals("Email", returnedPrompts.get("username")[1]); - assertNotNull(returnedPrompts.get("password")); - assertEquals("Password", returnedPrompts.get("password")[1]); + assertThat(returnedPrompts).hasSize(2) + .containsKey("username"); + assertThat(returnedPrompts.get("username")[1]).isEqualTo("Email"); + assertThat(returnedPrompts).containsKey("password"); + assertThat(returnedPrompts.get("password")[1]).isEqualTo("Password"); } @Test @@ -1428,27 +1355,23 @@ void defaultProviderUaa() throws Exception { String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("login", redirect); - assertEquals("{\"origin\":\"uaa\"}", extendedModelMap.get("login_hint")); - assertEquals("uaa", extendedModelMap.get("defaultIdpName")); + assertThat(redirect).isEqualTo("login"); + assertThat(extendedModelMap).containsEntry("login_hint", "{\"origin\":\"uaa\"}") + .containsEntry("defaultIdpName", "uaa"); } @Test void defaultProviderOIDC() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); - MultitenantClientServices clientDetailsService = mockClientService(); mockOidcProvider(mockIdentityProviderProvisioning); IdentityZoneHolder.get().getConfig().setDefaultIdentityProvider("my-OIDC-idp1"); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertThat(redirect, startsWith("redirect:http://localhost:8080/uaa")); - assertThat(redirect, containsString("my-OIDC-idp1")); + assertThat(redirect).startsWith("redirect:http://localhost:8080/uaa") + .contains("my-OIDC-idp1"); } @Test @@ -1461,13 +1384,12 @@ void defaultProviderOIDCLoginForJson() throws Exception { IdentityZoneHolder.get().getConfig().setDefaultIdentityProvider("my-OIDC-idp1"); LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - endpoint.infoForLoginJson(extendedModelMap, null, mockHttpServletRequest); - assertNotNull(extendedModelMap.get("prompts")); - assertTrue(extendedModelMap.get("prompts") instanceof Map); + assertThat(extendedModelMap).containsKey("prompts"); + assertThat(extendedModelMap.get("prompts")).isInstanceOf(Map.class); Map returnedPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals(3, returnedPrompts.size()); + assertThat(returnedPrompts).hasSize(3); } @Test @@ -1487,8 +1409,8 @@ void defaultProviderBeforeDiscovery() throws Exception { String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertThat(redirect, startsWith("redirect:http://localhost:8080/uaa")); - assertThat(redirect, containsString("my-OIDC-idp1")); + assertThat(redirect).startsWith("redirect:http://localhost:8080/uaa") + .contains("my-OIDC-idp1"); } @Test @@ -1499,14 +1421,11 @@ void discoveryPerformedWithAccountChooserOnlyReturnsLoginPage() throws Exception IdentityZoneHolder.get().getConfig().setAccountChooserEnabled(true); MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - mockHttpServletRequest.setParameter("discoveryPerformed", "true"); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("idp_discovery/password", redirect); + assertThat(redirect).isEqualTo("idp_discovery/password"); } @Test @@ -1519,15 +1438,12 @@ void discoveryPerformedWithAccountChooserOnlyReturnsDefaultIdp() throws Exceptio IdentityZoneHolder.get().getConfig().setAccountChooserEnabled(true); MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - mockHttpServletRequest.setParameter("discoveryPerformed", "true"); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertThat(redirect, startsWith("redirect:http://localhost:8080/uaa")); - assertThat(redirect, containsString("my-OIDC-idp1")); + assertThat(redirect).startsWith("redirect:http://localhost:8080/uaa") + .contains("my-OIDC-idp1"); } @Test @@ -1542,15 +1458,15 @@ void accountChooserOnlyReturnsOriginChooser() throws Exception { String oidcOrigin1 = "my-OIDC-idp1"; String oidcOrigin2 = "my-OIDC-idp2"; //Test also non-default idp - List> idpCollections = Arrays.asList( - Arrays.asList(OriginKeys.UAA,OriginKeys.LDAP,oidcOrigin1,oidcOrigin2), - Arrays.asList(OriginKeys.UAA, oidcOrigin1,oidcOrigin2), - Arrays.asList( OriginKeys.LDAP,oidcOrigin1,oidcOrigin2), - Arrays.asList(OriginKeys.UAA,OriginKeys.LDAP,oidcOrigin1), - Arrays.asList(OriginKeys.UAA,OriginKeys.LDAP, oidcOrigin2), - Arrays.asList( oidcOrigin1,oidcOrigin2), - Arrays.asList( oidcOrigin1), - Arrays.asList( oidcOrigin2)); + List> idpCollections = List.of( + List.of(OriginKeys.UAA, OriginKeys.LDAP, oidcOrigin1, oidcOrigin2), + List.of(OriginKeys.UAA, oidcOrigin1, oidcOrigin2), + List.of(OriginKeys.LDAP, oidcOrigin1, oidcOrigin2), + List.of(OriginKeys.UAA, OriginKeys.LDAP, oidcOrigin1), + List.of(OriginKeys.UAA, OriginKeys.LDAP, oidcOrigin2), + List.of(oidcOrigin1, oidcOrigin2), + List.of(oidcOrigin1), + List.of(oidcOrigin2)); for (List idpCollection : idpCollections) { MultitenantClientServices clientDetailsService = mockClientService(idpCollection); @@ -1558,7 +1474,7 @@ void accountChooserOnlyReturnsOriginChooser() throws Exception { String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("idp_discovery/origin", redirect); + assertThat(redirect).isEqualTo("idp_discovery/origin"); verify(mockIdentityProviderProvisioning, times(0)).retrieveAll(eq(true), anyString()); verify(mockSamlIdentityProviderConfigurator, times(0)).getIdentityProviderDefinitions(any(), any()); } @@ -1574,12 +1490,10 @@ void accountChooserOnlyReturnsOriginChooser_whenUsingNoAllowedProviders() throws IdentityZoneHolder.get().getConfig().setAccountChooserEnabled(true); MultitenantClientServices clientDetailsService = mockClientService(null); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("idp_discovery/origin", redirect); + assertThat(redirect).isEqualTo("idp_discovery/origin"); verify(mockIdentityProviderProvisioning, times(0)).retrieveAll(eq(true), anyString()); verify(mockSamlIdentityProviderConfigurator, times(0)).getIdentityProviderDefinitions(any(), any()); } @@ -1588,40 +1502,30 @@ void accountChooserOnlyReturnsOriginChooser_whenUsingNoAllowedProviders() throws void loginHintOverridesDefaultProvider() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); IdentityZoneHolder.get().getConfig().setDefaultIdentityProvider("uaa"); - MultitenantClientServices clientDetailsService = mockClientService(); - mockLoginHintProvider(configurator); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"{\"origin\":\"my-OIDC-idp1\"}"}); - - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertThat(redirect, startsWith("redirect:http://localhost:8080/uaa")); - assertThat(redirect, containsString("my-OIDC-idp1")); - assertNull(extendedModelMap.get("login_hint")); + assertThat(redirect).startsWith("redirect:http://localhost:8080/uaa") + .contains("my-OIDC-idp1"); + assertThat(extendedModelMap).doesNotContainKey("login_hint"); } @Test void loginHintLdapOverridesDefaultProviderUaa() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); IdentityZoneHolder.get().getConfig().setDefaultIdentityProvider("uaa"); - MultitenantClientServices clientDetailsService = mockClientService(); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - SavedRequest savedRequest = SessionUtils.getSavedRequestSession(mockHttpServletRequest.getSession()); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"{\"origin\":\"ldap\"}"}); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("{\"origin\":\"ldap\"}", extendedModelMap.get("login_hint")); - assertEquals("login", redirect); + assertThat(extendedModelMap).containsEntry("login_hint", "{\"origin\":\"ldap\"}"); + assertThat(redirect).isEqualTo("login"); } @Test @@ -1631,16 +1535,14 @@ void defaultProviderInvalidFallback() throws Exception { MultitenantClientServices clientDetailsService = mockClientService(); LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("login", redirect); + assertThat(redirect).isEqualTo("login"); } @Test void defaultProviderLdapWithAllowedOnlyOIDC() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); - List allowedProviders = singletonList("my-OIDC-idp1"); // mock Client service UaaClientDetails clientDetails = new UaaClientDetails(); @@ -1651,15 +1553,12 @@ void defaultProviderLdapWithAllowedOnlyOIDC() throws Exception { mockOidcProvider(mockIdentityProviderProvisioning); IdentityZoneHolder.get().getConfig().setDefaultIdentityProvider("ldap"); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertThat(redirect, startsWith("redirect:http://localhost:8080/uaa")); - assertThat(redirect, containsString("my-OIDC-idp1")); - assertFalse(extendedModelMap.containsKey("login_hint")); + assertThat(redirect).startsWith("redirect:http://localhost:8080/uaa") + .contains("my-OIDC-idp1"); + assertThat(extendedModelMap).doesNotContainKey("login_hint"); } @Test @@ -1675,11 +1574,10 @@ void allowedProvidersOnlyLDAPDoesNotUseInternalUsers() throws Exception { when(clientDetailsService.loadClientByClientId("client-id", "uaa")).thenReturn(clientDetails); LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("{\"origin\":\"ldap\"}", extendedModelMap.get("login_hint")); - assertEquals("login", redirect); + assertThat(extendedModelMap).containsEntry("login_hint", "{\"origin\":\"ldap\"}"); + assertThat(redirect).isEqualTo("login"); } @Test @@ -1695,16 +1593,14 @@ void allowedProvidersLoginHintDoesKeepExternalProviders() throws Exception { when(clientDetailsService.loadClientByClientId("client-id", "uaa")).thenReturn(clientDetails); mockOidcProvider(mockIdentityProviderProvisioning); - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get(), clientDetailsService); - String redirect = endpoint.loginForHtml(extendedModelMap, null, mockHttpServletRequest, singletonList(MediaType.TEXT_HTML)); - assertEquals("{\"origin\":\"uaa\"}", extendedModelMap.get("login_hint")); - assertEquals("login", redirect); + assertThat(extendedModelMap).containsEntry("login_hint", "{\"origin\":\"uaa\"}"); + assertThat(redirect).isEqualTo("login"); Collection> oauthLinks = (Collection>) extendedModelMap.get("oauthLinks"); - assertEquals(1, oauthLinks.size()); + assertThat(oauthLinks).hasSize(1); } @Test @@ -1718,9 +1614,9 @@ void colorsMustBePublic() { } }; - assertEquals(Boolean.TRUE, isPublic.apply("red")); - assertEquals(Boolean.TRUE, isPublic.apply("green")); - assertEquals(Boolean.TRUE, isPublic.apply("blue")); + assertThat(isPublic.apply("red")).isTrue(); + assertThat(isPublic.apply("green")).isTrue(); + assertThat(isPublic.apply("blue")).isTrue(); } private MockHttpServletRequest getMockHttpServletRequest() { @@ -1752,7 +1648,7 @@ private LoginInfoEndpoint getEndpoint( globalLinks, clientDetailsService, mockSamlIdentityProviderConfigurator); - if(identityZone.getConfig() != null) { + if (identityZone.getConfig() != null) { identityZone.getConfig().setPrompts(prompts); } return endpoint; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlKeyManagerFactoryCertificateTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlKeyManagerFactoryCertificateTests.java new file mode 100644 index 00000000000..2d5468299cd --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlKeyManagerFactoryCertificateTests.java @@ -0,0 +1,302 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +package org.cloudfoundry.identity.uaa.login; + +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.identity.uaa.provider.saml.CertificateRuntimeException; +import org.cloudfoundry.identity.uaa.provider.saml.SamlConfigProps; +import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManager; +import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManagerFactory; +import org.cloudfoundry.identity.uaa.util.KeyWithCert; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.security.Security; +import java.security.cert.CertificateException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class SamlKeyManagerFactoryCertificateTests { + + public static final String KEY = """ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: DES-EDE3-CBC,5771044F3450A262 + + VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe + aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v + CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh + DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B + +KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3 + KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU + o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6 + NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi + 7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI + 0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu + h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9 + zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb + dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw== + -----END RSA PRIVATE KEY-----"""; + + public static final String CERTIFICATE = """ + -----BEGIN CERTIFICATE----- + MIIB1TCCAT4CCQCpQCfJYT8ZJTANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDFCRz + YW1sX2xvZ2luLE9VPXRlbXBlc3QsTz12bXdhcmUsTz1jb20wHhcNMTMwNzAyMDAw + MzM3WhcNMTQwNzAyMDAwMzM3WjAvMS0wKwYDVQQDFCRzYW1sX2xvZ2luLE9VPXRl + bXBlc3QsTz12bXdhcmUsTz1jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB + ANK8mv+mUzhPH/8iTdMsZ6mY4r4At/GZIFS34L+/I0V2g6PkZ84VBgodqqV6Z6NY + OSk0lcjrzU650zbES7yn4MjuvP0N5T9LydlvjOEzfA+uRETiy8d+DsS3rThRY+Ja + dvmS0PswJ8cvHAksYmGNUWfTU+Roxcv0ZDqD+cUNi1+NAgMBAAEwDQYJKoZIhvcN + AQEFBQADgYEAy54UVlZifk1PPdTg9OJuumdxgzZk3QEWZGjdJYEc134MeKKsIX50 + +6y5GDyXmxvJx33ySTZuRaaXClOuAtXRWpz0KlceujYuwboyUxhn46SUASD872nb + cN0E1UrhDloFcftXEXudDL2S2cSQjsyxLNbBop63xq+U6MYG/uFe7GQ= + -----END CERTIFICATE-----"""; + + public static final String PASSWORD = "password"; + + @BeforeAll + static void addBCProvider() { + Security.addProvider(new BouncyCastleFipsProvider()); + } + + @Test + void workingCertificate() { + SamlConfig config = new SamlConfig(); + config.setPrivateKey(KEY); + config.setPrivateKeyPassword(PASSWORD); + config.setCertificate(CERTIFICATE); + SamlKeyManager keyManager = new SamlKeyManagerFactory(new SamlConfigProps()).getKeyManager(config); + KeyWithCert credential = keyManager.getDefaultCredential(); + assertThat(credential).isNotNull(); + assertThat(credential.getPrivateKey()).isNotNull(); + assertThat(credential.getCertificate()).isNotNull(); + } + + @Test + void workingCertificateNullPassword() { + String key = """ + -----BEGIN RSA PRIVATE KEY----- + MIICXgIBAAKBgQDfTLadf6QgJeS2XXImEHMsa+1O7MmIt44xaL77N2K+J/JGpfV3 + AnkyB06wFZ02sBLB7hko42LIsVEOyTuUBird/3vlyHFKytG7UEt60Fl88SbAEfsU + JN1i1aSUlunPS/NCz+BKwwKFP9Ss3rNImE9Uc2LMvGy153LHFVW2zrjhTwIDAQAB + AoGBAJDh21LRcJITRBQ3CUs9PR1DYZPl+tUkE7RnPBMPWpf6ny3LnDp9dllJeHqz + a3ACSgleDSEEeCGzOt6XHnrqjYCKa42Z+Opnjx/OOpjyX1NAaswRtnb039jwv4gb + RlwT49Y17UAQpISOo7JFadCBoMG0ix8xr4ScY+zCSoG5v0BhAkEA8llNsiWBJF5r + LWQ6uimfdU2y1IPlkcGAvjekYDkdkHiRie725Dn4qRiXyABeaqNm2bpnD620Okwr + sf7LY+BMdwJBAOvgt/ZGwJrMOe/cHhbujtjBK/1CumJ4n2r5V1zPBFfLNXiKnpJ6 + J/sRwmjgg4u3Anu1ENF3YsxYabflBnvOP+kCQCQ8VBCp6OhOMcpErT8+j/gTGQUL + f5zOiPhoC2zTvWbnkCNGlqXDQTnPUop1+6gILI2rgFNozoTU9MeVaEXTuLsCQQDC + AGuNpReYucwVGYet+LuITyjs/krp3qfPhhByhtndk4cBA5H0i4ACodKyC6Zl7Tmf + oYaZoYWi6DzbQQUaIsKxAkEA2rXQjQFsfnSm+w/9067ChWg46p4lq5Na2NpcpFgH + waZKhM1W0oB8MX78M+0fG3xGUtywTx0D4N7pr1Tk2GTgNw== + -----END RSA PRIVATE KEY-----"""; + + String certificate = """ + -----BEGIN CERTIFICATE----- + MIIEJTCCA46gAwIBAgIJANIqfxWTfhpkMA0GCSqGSIb3DQEBBQUAMIG+MQswCQYD + VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j + aXNjbzEdMBsGA1UEChMUUGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Ns + b3VkIEZvdW5kcnkgSWRlbnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2Yt + YXBwLmNvbTEfMB0GCSqGSIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzAeFw0xNTA1 + MTQxNzE5MTBaFw0yNTA1MTExNzE5MTBaMIG+MQswCQYDVQQGEwJVUzETMBEGA1UE + CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEdMBsGA1UEChMU + UGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Nsb3VkIEZvdW5kcnkgSWRl + bnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2YtYXBwLmNvbTEfMB0GCSqG + SIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw + gYkCgYEA30y2nX+kICXktl1yJhBzLGvtTuzJiLeOMWi++zdivifyRqX1dwJ5MgdO + sBWdNrASwe4ZKONiyLFRDsk7lAYq3f975chxSsrRu1BLetBZfPEmwBH7FCTdYtWk + lJbpz0vzQs/gSsMChT/UrN6zSJhPVHNizLxstedyxxVVts644U8CAwEAAaOCAScw + ggEjMB0GA1UdDgQWBBSvWY/TyHysYGxKvII95wD/CzE1AzCB8wYDVR0jBIHrMIHo + gBSvWY/TyHysYGxKvII95wD/CzE1A6GBxKSBwTCBvjELMAkGA1UEBhMCVVMxEzAR + BgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHTAbBgNV + BAoTFFBpdm90YWwgU29mdHdhcmUgSW5jMSQwIgYDVQQLExtDbG91ZCBGb3VuZHJ5 + IElkZW50aXR5IFRlYW0xHDAaBgNVBAMTE2lkZW50aXR5LmNmLWFwcC5jb20xHzAd + BgkqhkiG9w0BCQEWEG1hcmlzc2FAdGVzdC5vcmeCCQDSKn8Vk34aZDAMBgNVHRME + BTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAL5j1JCN5EoXMOOBSBUL8KeVZFQD3Nfy + YkYKBatFEKdBFlAKLBdG+5KzE7sTYesn7EzBISHXFz3DhdK2tg+IF1DeSFVmFl2n + iVxQ1sYjo4kCugHBsWo+MpFH9VBLFzsMlP3eIDuVKe8aPXFKYCGhctZEJdQTKlja + lshe50nayKrT + -----END CERTIFICATE-----"""; + + SamlConfig config = new SamlConfig(); + config.setPrivateKey(key); + config.setPrivateKeyPassword(null); + config.setCertificate(certificate); + SamlKeyManager keyManager = new SamlKeyManagerFactory(new SamlConfigProps()).getKeyManager(config); + KeyWithCert credential = keyManager.getDefaultCredential(); + assertThat(credential).isNotNull(); + assertThat(credential.getPrivateKey()).isNotNull(); + assertThat(credential.getCertificate()).isNotNull(); + } + + @Test + void failsWithWorkingCertificateInvalidPassword() { + SamlConfig config = new SamlConfig(); + config.setPrivateKey(KEY); + config.setPrivateKeyPassword("anIncorrectPassword"); + config.setCertificate(CERTIFICATE); + SamlKeyManager keyManager = new SamlKeyManagerFactory(new SamlConfigProps()).getKeyManager(config); + assertThatThrownBy(keyManager::getDefaultCredential) + .isInstanceOf(CertificateRuntimeException.class) + .getCause() + .isInstanceOf(CertificateException.class) + .hasMessageContaining("Failed to read private key"); + } + + @Test + void failsWithWorkingCertificateIllegalKey() { + String key = """ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: DES-EDE3-CBC,5771044F3450A262 + + VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe + aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v + CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh + DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B + +KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3 + KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU + o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6 + 7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI + 0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu + h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9 + zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb + dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw== + -----END RSA PRIVATE KEY-----"""; + + String certificate = """ + -----BEGIN CERTIFICATE----- + MIIB1TCCAT4CCQCpQCfJYT8ZJTANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDFCRz + YW1sX2xvZ2luLE9VPXRlbXBlc3QsTz12bXdhcmUsTz1jb20wHhcNMTMwNzAyMDAw + MzM3WhcNMTQwNzAyMDAwMzM3WjAvMS0wKwYDVQQDFCRzYW1sX2xvZ2luLE9VPXRl + bXBlc3QsTz12bXdhcmUsTz1jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB + ANK8mv+mUzhPH/8iTdMsZ6mY4r4At/GZIFS34L+/I0V2g6PkZ84VBgodqqV6Z6NY + OSk0lcjrzU650zbES7yn4MjuvP0N5T9LydlvjOEzfA+uRETiy8d+DsS3rThRY+Ja + dvmS0PswJ8cvHAksYmGNUWfTU+Roxcv0ZDqD+cUNi1+NAgMBAAEwDQYJKoZIhvcN + AQEFBQADgYEAy54UVlZifk1PPdTg9OJuumdxgzZk3QEWZGjdJYEc134MeKKsIX50 + +6y5GDyXmxvJx33ySTZuRaaXClOuAtXRWpz0KlceujYuwboyUxhn46SUASD872nb + cN0E1UrhDloFcftXEXudDL2S2cSQjsyxLNbBop63xq+U6MYG/uFe7GQ= + -----END CERTIFICATE-----"""; + + readSamlConfig(key, certificate, "Failed to read private key"); + } + + @Test + void failsWithNonWorkingCertificate() { + String key = """ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: DES-EDE3-CBC,5771044F3450A262 + + VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe + aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v + CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh + DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B + +KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3 + KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU + o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6 + NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi + 7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI + 0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu + h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9 + zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb + dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw== + -----END RSA PRIVATE KEY-----"""; + + String certificate = """ + -----BEGIN CERTIFICATE----- + MIIB1TCCAT4CCQCpQCfJYT8ZJTANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDFCRz + YW1sX2xvZ2luLE9VPXRlbXBlc3QsTz12bXdhcmUsTz1jb20wHhcNMTMwNzAyMDAw + MzM3WhcNMTQwNzAyMDAwMzM3WjAvMS0wKwYDVQQDFCRzYW1sX2xvZ2luLE9VPXRl + bXBlc3QsTz12bXdhcmUsTz1jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB + OSk0lcjrzU650zbES7yn4MjuvP0N5T9LydlvjOEzfA+uRETiy8d+DsS3rThRY+Ja + dvmS0PswJ8cvHAksYmGNUWfTU+Roxcv0ZDqD+cUNi1+NAgMBAAEwDQYJKoZIhvcN + AQEFBQADgYEAy54UVlZifk1PPdTg9OJuumdxgzZk3QEWZGjdJYEc134MeKKsIX50 + +6y5GDyXmxvJx33ySTZuRaaXClOuAtXRWpz0KlceujYuwboyUxhn46SUASD872nb + cN0E1UrhDloFcftXEXudDL2S2cSQjsyxLNbBop63xq+U6MYG/uFe7GQ= + -----END CERTIFICATE-----"""; + + readSamlConfig(key, certificate, "Failed to read certificate"); + } + + @Test + void failsWithUnmatchedKeyPair() { + String key = """ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: DES-EDE3-CBC,5771044F3450A262 + + VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe + aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v + CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh + DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B + +KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3 + KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU + o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6 + NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi + 7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI + 0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu + h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9 + zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb + dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw== + -----END RSA PRIVATE KEY----- + """; + + String certificate = """ + -----BEGIN CERTIFICATE----- + MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2 + MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg + U1NMIENBMB4XDTEzMDczMDAwMDAwMFoXDTE2MDcyOTIzNTk1OVowPzEhMB8GA1UE + CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRowGAYDVQQDExFlZHVyb2FtLmJi + ay5hYy51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrSBWTl56O2 + VJbahURgPznums43Nnn/smJ6cGywPu4mtJHUHSmONlBDTAWFS1fLkh8YHIQmdwYg + FY4pHjZmKVtJ6ZOFhDNN1R2VMka4ZtREWn3XX8pUacol5KjEIh6U/FvMHyRv7sV5 + 9J6JUK+n5R7ZsSu7XRi6TrT3xhfu0KoWo8RM/salKo2theIcyqLPHiFLEtA7ISLV + q7I49uj9h9Hni/iCpBey+Gn5yDub4nrv81aDfD6zDoW/vXIOrcXFYRK3lXWOOFi4 + cfmu4SQQwMV1jBOer8JgfsQ3EQMgwauSMLUR31wPM83eMbOC72HhW9SJUtFDj42c + PIEWd+rTA8ECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFAy9k2gM896ro0lrKzdX + R+qQ47ntMB0GA1UdDgQWBBQgoU+Pbgk2MthczZt7TviUiIWyrjAOBgNVHQ8BAf8E + BAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH + AwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICHTAIBgZngQwBAgEwOgYDVR0fBDMw + MTAvoC2gK4YpaHR0cDovL2NybC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5j + cmwwbQYIKwYBBQUHAQEEYTBfMDUGCCsGAQUFBzAChilodHRwOi8vY3J0LnRjcy50 + ZXJlbmEub3JnL1RFUkVOQVNTTENBLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL29j + c3AudGNzLnRlcmVuYS5vcmcwHAYDVR0RBBUwE4IRZWR1cm9hbS5iYmsuYWMudWsw + DQYJKoZIhvcNAQEFBQADggEBAHTw5b1lrTBqnx/QSO50Mww+OPYgV4b4NSu2rqxG + I2hHLiD4l7Sk3WOdXPAQMmTlo6N10Lt6p8gLLxKsOAw+nK+z9aLcgKk9/kYoe4C8 + jHzwTy6eO+sCKnJfTqEX8p3b8l736lUWwPgMjjEN+d49ZegqCwH6SEz7h0+DwGmF + LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl + r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi + yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c= + -----END CERTIFICATE----- + """; + + readSamlConfig(key, certificate, "Certificate does not match private key"); + } + + private static void readSamlConfig(String key, String certificate, String expectedError) { + SamlConfig config = new SamlConfig(); + config.setPrivateKey(key); + config.setPrivateKeyPassword(PASSWORD); + config.setCertificate(certificate); + SamlKeyManager keyManager = new SamlKeyManagerFactory(new SamlConfigProps()).getKeyManager(config); + assertThatThrownBy(keyManager::getDefaultCredential) + .isInstanceOf(CertificateRuntimeException.class) + .getCause() + .isInstanceOf(CertificateException.class) + .hasMessageContaining(expectedError); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java deleted file mode 100644 index 8a791b4756c..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.login; - -import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManagerFactory; -import org.cloudfoundry.identity.uaa.zone.SamlConfig; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.opensaml.xml.security.credential.Credential; -import org.springframework.security.saml.key.KeyManager; - -import static org.junit.Assert.assertNotNull; - -public class SamlLoginServerKeyManagerTests { - - private KeyManager keyManager = null; - public static final String KEY = "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + - "\n" + - "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + - "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + - "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + - "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + - "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + - "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + - "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + - "NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi\n" + - "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + - "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + - "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + - "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + - "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + - "-----END RSA PRIVATE KEY-----"; - public static final String CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" + - "MIIB1TCCAT4CCQCpQCfJYT8ZJTANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDFCRz\n" + - "YW1sX2xvZ2luLE9VPXRlbXBlc3QsTz12bXdhcmUsTz1jb20wHhcNMTMwNzAyMDAw\n" + - "MzM3WhcNMTQwNzAyMDAwMzM3WjAvMS0wKwYDVQQDFCRzYW1sX2xvZ2luLE9VPXRl\n" + - "bXBlc3QsTz12bXdhcmUsTz1jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB\n" + - "ANK8mv+mUzhPH/8iTdMsZ6mY4r4At/GZIFS34L+/I0V2g6PkZ84VBgodqqV6Z6NY\n" + - "OSk0lcjrzU650zbES7yn4MjuvP0N5T9LydlvjOEzfA+uRETiy8d+DsS3rThRY+Ja\n" + - "dvmS0PswJ8cvHAksYmGNUWfTU+Roxcv0ZDqD+cUNi1+NAgMBAAEwDQYJKoZIhvcN\n" + - "AQEFBQADgYEAy54UVlZifk1PPdTg9OJuumdxgzZk3QEWZGjdJYEc134MeKKsIX50\n" + - "+6y5GDyXmxvJx33ySTZuRaaXClOuAtXRWpz0KlceujYuwboyUxhn46SUASD872nb\n" + - "cN0E1UrhDloFcftXEXudDL2S2cSQjsyxLNbBop63xq+U6MYG/uFe7GQ=\n" + - "-----END CERTIFICATE-----"; - public static final String PASSWORD = "password"; - - @BeforeClass - public static void setUpBC() { - AddBcProvider.noop(); - } - - @Test - public void testWithWorkingCertificate() { - - SamlConfig config = new SamlConfig(); - config.setPrivateKey(KEY); - config.setPrivateKeyPassword(PASSWORD); - config.setCertificate(CERTIFICATE); - keyManager = new SamlKeyManagerFactory().getKeyManager(config); - Credential credential = keyManager.getDefaultCredential(); - assertNotNull(credential.getPrivateKey()); - assertNotNull(credential.getPublicKey()); - assertNotNull(credential); - } - - @Test(expected = IllegalArgumentException.class) - public void testWithWorkingCertificateInvalidPassword() { - String key = "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + - "\n" + - "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + - "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + - "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + - "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + - "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + - "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + - "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + - "NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi\n" + - "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + - "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + - "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + - "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + - "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + - "-----END RSA PRIVATE KEY-----"; - String certificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIB1TCCAT4CCQCpQCfJYT8ZJTANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDFCRz\n" + - "YW1sX2xvZ2luLE9VPXRlbXBlc3QsTz12bXdhcmUsTz1jb20wHhcNMTMwNzAyMDAw\n" + - "MzM3WhcNMTQwNzAyMDAwMzM3WjAvMS0wKwYDVQQDFCRzYW1sX2xvZ2luLE9VPXRl\n" + - "bXBlc3QsTz12bXdhcmUsTz1jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB\n" + - "ANK8mv+mUzhPH/8iTdMsZ6mY4r4At/GZIFS34L+/I0V2g6PkZ84VBgodqqV6Z6NY\n" + - "OSk0lcjrzU650zbES7yn4MjuvP0N5T9LydlvjOEzfA+uRETiy8d+DsS3rThRY+Ja\n" + - "dvmS0PswJ8cvHAksYmGNUWfTU+Roxcv0ZDqD+cUNi1+NAgMBAAEwDQYJKoZIhvcN\n" + - "AQEFBQADgYEAy54UVlZifk1PPdTg9OJuumdxgzZk3QEWZGjdJYEc134MeKKsIX50\n" + - "+6y5GDyXmxvJx33ySTZuRaaXClOuAtXRWpz0KlceujYuwboyUxhn46SUASD872nb\n" + - "cN0E1UrhDloFcftXEXudDL2S2cSQjsyxLNbBop63xq+U6MYG/uFe7GQ=\n" + - "-----END CERTIFICATE-----"; - String password = "vmware"; - - try { - SamlConfig config = new SamlConfig(); - config.setPrivateKey(key); - config.setPrivateKeyPassword(password); - config.setCertificate(certificate); - keyManager = new SamlKeyManagerFactory().getKeyManager(config); - Assert.fail("Password invalid. Should not reach this line."); - } catch (Exception x) { - if (x.getClass().getName().equals("org.bouncycastle.openssl.EncryptionException")) { - throw new IllegalArgumentException(x); - } else if (x.getClass().equals(IllegalArgumentException.class)) { - throw x; - } - } - } - - @Test - public void testWithWorkingCertificateNullPassword() { - String key = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXgIBAAKBgQDfTLadf6QgJeS2XXImEHMsa+1O7MmIt44xaL77N2K+J/JGpfV3\n" + - "AnkyB06wFZ02sBLB7hko42LIsVEOyTuUBird/3vlyHFKytG7UEt60Fl88SbAEfsU\n" + - "JN1i1aSUlunPS/NCz+BKwwKFP9Ss3rNImE9Uc2LMvGy153LHFVW2zrjhTwIDAQAB\n" + - "AoGBAJDh21LRcJITRBQ3CUs9PR1DYZPl+tUkE7RnPBMPWpf6ny3LnDp9dllJeHqz\n" + - "a3ACSgleDSEEeCGzOt6XHnrqjYCKa42Z+Opnjx/OOpjyX1NAaswRtnb039jwv4gb\n" + - "RlwT49Y17UAQpISOo7JFadCBoMG0ix8xr4ScY+zCSoG5v0BhAkEA8llNsiWBJF5r\n" + - "LWQ6uimfdU2y1IPlkcGAvjekYDkdkHiRie725Dn4qRiXyABeaqNm2bpnD620Okwr\n" + - "sf7LY+BMdwJBAOvgt/ZGwJrMOe/cHhbujtjBK/1CumJ4n2r5V1zPBFfLNXiKnpJ6\n" + - "J/sRwmjgg4u3Anu1ENF3YsxYabflBnvOP+kCQCQ8VBCp6OhOMcpErT8+j/gTGQUL\n" + - "f5zOiPhoC2zTvWbnkCNGlqXDQTnPUop1+6gILI2rgFNozoTU9MeVaEXTuLsCQQDC\n" + - "AGuNpReYucwVGYet+LuITyjs/krp3qfPhhByhtndk4cBA5H0i4ACodKyC6Zl7Tmf\n" + - "oYaZoYWi6DzbQQUaIsKxAkEA2rXQjQFsfnSm+w/9067ChWg46p4lq5Na2NpcpFgH\n" + - "waZKhM1W0oB8MX78M+0fG3xGUtywTx0D4N7pr1Tk2GTgNw==\n" + - "-----END RSA PRIVATE KEY-----"; - String certificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIEJTCCA46gAwIBAgIJANIqfxWTfhpkMA0GCSqGSIb3DQEBBQUAMIG+MQswCQYD\n" + - "VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n" + - "aXNjbzEdMBsGA1UEChMUUGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Ns\n" + - "b3VkIEZvdW5kcnkgSWRlbnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2Yt\n" + - "YXBwLmNvbTEfMB0GCSqGSIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzAeFw0xNTA1\n" + - "MTQxNzE5MTBaFw0yNTA1MTExNzE5MTBaMIG+MQswCQYDVQQGEwJVUzETMBEGA1UE\n" + - "CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEdMBsGA1UEChMU\n" + - "UGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Nsb3VkIEZvdW5kcnkgSWRl\n" + - "bnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2YtYXBwLmNvbTEfMB0GCSqG\n" + - "SIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\n" + - "gYkCgYEA30y2nX+kICXktl1yJhBzLGvtTuzJiLeOMWi++zdivifyRqX1dwJ5MgdO\n" + - "sBWdNrASwe4ZKONiyLFRDsk7lAYq3f975chxSsrRu1BLetBZfPEmwBH7FCTdYtWk\n" + - "lJbpz0vzQs/gSsMChT/UrN6zSJhPVHNizLxstedyxxVVts644U8CAwEAAaOCAScw\n" + - "ggEjMB0GA1UdDgQWBBSvWY/TyHysYGxKvII95wD/CzE1AzCB8wYDVR0jBIHrMIHo\n" + - "gBSvWY/TyHysYGxKvII95wD/CzE1A6GBxKSBwTCBvjELMAkGA1UEBhMCVVMxEzAR\n" + - "BgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHTAbBgNV\n" + - "BAoTFFBpdm90YWwgU29mdHdhcmUgSW5jMSQwIgYDVQQLExtDbG91ZCBGb3VuZHJ5\n" + - "IElkZW50aXR5IFRlYW0xHDAaBgNVBAMTE2lkZW50aXR5LmNmLWFwcC5jb20xHzAd\n" + - "BgkqhkiG9w0BCQEWEG1hcmlzc2FAdGVzdC5vcmeCCQDSKn8Vk34aZDAMBgNVHRME\n" + - "BTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAL5j1JCN5EoXMOOBSBUL8KeVZFQD3Nfy\n" + - "YkYKBatFEKdBFlAKLBdG+5KzE7sTYesn7EzBISHXFz3DhdK2tg+IF1DeSFVmFl2n\n" + - "iVxQ1sYjo4kCugHBsWo+MpFH9VBLFzsMlP3eIDuVKe8aPXFKYCGhctZEJdQTKlja\n" + - "lshe50nayKrT\n" + - "-----END CERTIFICATE-----"; - String password = null; - - SamlConfig config = new SamlConfig(); - config.setPrivateKey(key); - config.setPrivateKeyPassword(password); - config.setCertificate(certificate); - keyManager = new SamlKeyManagerFactory().getKeyManager(config); - Credential credential = keyManager.getDefaultCredential(); - assertNotNull(credential.getPrivateKey()); - assertNotNull(credential.getPublicKey()); - assertNotNull(credential); - } - - @Test(expected = IllegalArgumentException.class) - public void testWithWorkingCertificateIllegalKey() { - String key = "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + - "\n" + - "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + - "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + - "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + - "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + - "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + - "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + - "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + - "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + - "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + - "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + - "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + - "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + - "-----END RSA PRIVATE KEY-----"; - String certificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIB1TCCAT4CCQCpQCfJYT8ZJTANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDFCRz\n" + - "YW1sX2xvZ2luLE9VPXRlbXBlc3QsTz12bXdhcmUsTz1jb20wHhcNMTMwNzAyMDAw\n" + - "MzM3WhcNMTQwNzAyMDAwMzM3WjAvMS0wKwYDVQQDFCRzYW1sX2xvZ2luLE9VPXRl\n" + - "bXBlc3QsTz12bXdhcmUsTz1jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB\n" + - "ANK8mv+mUzhPH/8iTdMsZ6mY4r4At/GZIFS34L+/I0V2g6PkZ84VBgodqqV6Z6NY\n" + - "OSk0lcjrzU650zbES7yn4MjuvP0N5T9LydlvjOEzfA+uRETiy8d+DsS3rThRY+Ja\n" + - "dvmS0PswJ8cvHAksYmGNUWfTU+Roxcv0ZDqD+cUNi1+NAgMBAAEwDQYJKoZIhvcN\n" + - "AQEFBQADgYEAy54UVlZifk1PPdTg9OJuumdxgzZk3QEWZGjdJYEc134MeKKsIX50\n" + - "+6y5GDyXmxvJx33ySTZuRaaXClOuAtXRWpz0KlceujYuwboyUxhn46SUASD872nb\n" + - "cN0E1UrhDloFcftXEXudDL2S2cSQjsyxLNbBop63xq+U6MYG/uFe7GQ=\n" + - "-----END CERTIFICATE-----"; - String password = "password"; - - SamlConfig config = new SamlConfig(); - config.setPrivateKey(key); - config.setPrivateKeyPassword(password); - config.setCertificate(certificate); - keyManager = new SamlKeyManagerFactory().getKeyManager(config); - - } - - @Test(expected = IllegalArgumentException.class) - public void testWithNonWorkingCertificate() { - String key = "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + - "\n" + - "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + - "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + - "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + - "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + - "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + - "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + - "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + - "NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi\n" + - "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + - "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + - "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + - "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + - "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + - "-----END RSA PRIVATE KEY-----"; - String certificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIB1TCCAT4CCQCpQCfJYT8ZJTANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDFCRz\n" + - "YW1sX2xvZ2luLE9VPXRlbXBlc3QsTz12bXdhcmUsTz1jb20wHhcNMTMwNzAyMDAw\n" + - "MzM3WhcNMTQwNzAyMDAwMzM3WjAvMS0wKwYDVQQDFCRzYW1sX2xvZ2luLE9VPXRl\n" + - "bXBlc3QsTz12bXdhcmUsTz1jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB\n" + - "OSk0lcjrzU650zbES7yn4MjuvP0N5T9LydlvjOEzfA+uRETiy8d+DsS3rThRY+Ja\n" + - "dvmS0PswJ8cvHAksYmGNUWfTU+Roxcv0ZDqD+cUNi1+NAgMBAAEwDQYJKoZIhvcN\n" + - "AQEFBQADgYEAy54UVlZifk1PPdTg9OJuumdxgzZk3QEWZGjdJYEc134MeKKsIX50\n" + - "+6y5GDyXmxvJx33ySTZuRaaXClOuAtXRWpz0KlceujYuwboyUxhn46SUASD872nb\n" + - "cN0E1UrhDloFcftXEXudDL2S2cSQjsyxLNbBop63xq+U6MYG/uFe7GQ=\n" + - "-----END CERTIFICATE-----"; - String password = "password"; - - try { - SamlConfig config = new SamlConfig(); - config.setPrivateKey(key); - config.setPrivateKeyPassword(password); - config.setCertificate(certificate); - keyManager = new SamlKeyManagerFactory().getKeyManager(config); - Assert.fail("Key/Cert pair is invalid. Should not reach this line."); - } catch (Exception x) { - if (x.getClass().getName().equals("org.bouncycastle.openssl.PEMException")) { - throw new IllegalArgumentException(x); - } else if (x.getClass().getName().equals("org.bouncycastle.openssl.EncryptionException")) { - throw new IllegalArgumentException(x); - } else if (x.getClass().equals(IllegalArgumentException.class)) { - throw x; - } - } - } - - @Test(expected = IllegalArgumentException.class) - public void testKeyPairValidated() { - String key = "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + - "\n" + - "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + - "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + - "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + - "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + - "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + - "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + - "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + - "NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi\n" + - "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + - "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + - "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + - "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + - "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + - "-----END RSA PRIVATE KEY-----\n"; - String certificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2\n" + - "MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg\n" + - "U1NMIENBMB4XDTEzMDczMDAwMDAwMFoXDTE2MDcyOTIzNTk1OVowPzEhMB8GA1UE\n" + - "CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRowGAYDVQQDExFlZHVyb2FtLmJi\n" + - "ay5hYy51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrSBWTl56O2\n" + - "VJbahURgPznums43Nnn/smJ6cGywPu4mtJHUHSmONlBDTAWFS1fLkh8YHIQmdwYg\n" + - "FY4pHjZmKVtJ6ZOFhDNN1R2VMka4ZtREWn3XX8pUacol5KjEIh6U/FvMHyRv7sV5\n" + - "9J6JUK+n5R7ZsSu7XRi6TrT3xhfu0KoWo8RM/salKo2theIcyqLPHiFLEtA7ISLV\n" + - "q7I49uj9h9Hni/iCpBey+Gn5yDub4nrv81aDfD6zDoW/vXIOrcXFYRK3lXWOOFi4\n" + - "cfmu4SQQwMV1jBOer8JgfsQ3EQMgwauSMLUR31wPM83eMbOC72HhW9SJUtFDj42c\n" + - "PIEWd+rTA8ECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFAy9k2gM896ro0lrKzdX\n" + - "R+qQ47ntMB0GA1UdDgQWBBQgoU+Pbgk2MthczZt7TviUiIWyrjAOBgNVHQ8BAf8E\n" + - "BAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH\n" + - "AwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICHTAIBgZngQwBAgEwOgYDVR0fBDMw\n" + - "MTAvoC2gK4YpaHR0cDovL2NybC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5j\n" + - "cmwwbQYIKwYBBQUHAQEEYTBfMDUGCCsGAQUFBzAChilodHRwOi8vY3J0LnRjcy50\n" + - "ZXJlbmEub3JnL1RFUkVOQVNTTENBLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL29j\n" + - "c3AudGNzLnRlcmVuYS5vcmcwHAYDVR0RBBUwE4IRZWR1cm9hbS5iYmsuYWMudWsw\n" + - "DQYJKoZIhvcNAQEFBQADggEBAHTw5b1lrTBqnx/QSO50Mww+OPYgV4b4NSu2rqxG\n" + - "I2hHLiD4l7Sk3WOdXPAQMmTlo6N10Lt6p8gLLxKsOAw+nK+z9aLcgKk9/kYoe4C8\n" + - "jHzwTy6eO+sCKnJfTqEX8p3b8l736lUWwPgMjjEN+d49ZegqCwH6SEz7h0+DwGmF\n" + - "LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl\n" + - "r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi\n" + - "yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c=\n" + - "-----END CERTIFICATE-----\n"; - - String password = "password"; - - SamlConfig config = new SamlConfig(); - config.setPrivateKey(key); - config.setPrivateKeyPassword(password); - config.setCertificate(certificate); - keyManager = new SamlKeyManagerFactory().getKeyManager(config); - - } -} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/TokenTestSupport.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/TokenTestSupport.java index 9e9de21db8a..8cea068fd08 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/TokenTestSupport.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/TokenTestSupport.java @@ -53,6 +53,7 @@ import org.cloudfoundry.identity.uaa.zone.InMemoryMultitenantClientServices; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -60,7 +61,6 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; -import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetailsService; import java.util.Arrays; import java.util.Collections; @@ -73,10 +73,10 @@ import java.util.Set; import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_PASSWORD; import static org.cloudfoundry.identity.uaa.user.UaaAuthority.USER_AUTHORITIES; -import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; @@ -89,12 +89,12 @@ public class TokenTestSupport { public static final String GRANT_TYPE = "grant_type"; public static final String CLIENT_AUTHORITIES = "read,update,write,openid"; public static final String ISSUER_URI = "http://localhost:8080/uaa/oauth/token"; - public static final String READ = "read"; - public static final String WRITE = "write"; - public static final String DELETE = "delete"; - public static final String ALL_GRANTS_CSV = "authorization_code,password,implicit,client_credentials,refresh_token"; - public static final String CLIENTS = "clients"; - public static final String SCIM = "scim"; + private static final String READ = "read"; + private static final String WRITE = "write"; + private static final String DELETE = "delete"; + private static final String ALL_GRANTS_CSV = "authorization_code,password,implicit,client_credentials,refresh_token"; + private static final String CLIENTS = "clients"; + private static final String SCIM = "scim"; public static final String OPENID = "openid"; public static final String ROLES = "roles"; public static final String PROFILE = "profile"; @@ -106,37 +106,37 @@ public class TokenTestSupport { final String externalId = "externalId"; List defaultUserAuthorities = Arrays.asList( - UaaAuthority.authority("space.123.developer"), - UaaAuthority.authority("uaa.user"), - UaaAuthority.authority("space.345.developer"), - UaaAuthority.authority("space.123.admin"), - UaaAuthority.authority(OPENID), - UaaAuthority.authority(READ), - UaaAuthority.authority(WRITE), - UaaAuthority.authority("uaa.offline_token")); - - UaaUser defaultUser = - new UaaUser( - new UaaUserPrototype() - .withId(userId) - .withUsername(username) - .withPassword(GRANT_TYPE_PASSWORD) - .withEmail(email) - .withAuthorities(defaultUserAuthorities) - .withGivenName("Marissa") - .withFamilyName("Bloggs") - .withPhoneNumber("1234567890") - .withCreated(new Date(System.currentTimeMillis() - 15000)) - .withModified(new Date(System.currentTimeMillis() - 15000)) - .withOrigin(OriginKeys.UAA) - .withExternalId(externalId) - .withVerified(false) - .withZoneId(IdentityZoneHolder.get().getId()) - .withSalt(userId) - .withPasswordLastModified(new Date(System.currentTimeMillis() - 15000)) - .withLastLogonSuccess(12345L) - .withPreviousLogonSuccess(12365L) - ); + UaaAuthority.authority("space.123.developer"), + UaaAuthority.authority("uaa.user"), + UaaAuthority.authority("space.345.developer"), + UaaAuthority.authority("space.123.admin"), + UaaAuthority.authority(OPENID), + UaaAuthority.authority(READ), + UaaAuthority.authority(WRITE), + UaaAuthority.authority("uaa.offline_token")); + + UaaUser defaultUser = + new UaaUser( + new UaaUserPrototype() + .withId(userId) + .withUsername(username) + .withPassword(GRANT_TYPE_PASSWORD) + .withEmail(email) + .withAuthorities(defaultUserAuthorities) + .withGivenName("Marissa") + .withFamilyName("Bloggs") + .withPhoneNumber("1234567890") + .withCreated(new Date(System.currentTimeMillis() - 15000)) + .withModified(new Date(System.currentTimeMillis() - 15000)) + .withOrigin(OriginKeys.UAA) + .withExternalId(externalId) + .withVerified(false) + .withZoneId(IdentityZoneHolder.get().getId()) + .withSalt(userId) + .withPasswordLastModified(new Date(System.currentTimeMillis() - 15000)) + .withLastLogonSuccess(12345L) + .withPreviousLogonSuccess(12365L) + ); UaaTokenServices tokenServices; @@ -146,8 +146,7 @@ public class TokenTestSupport { TestApplicationEventPublisher publisher; // Need to create a user with a modified time slightly in the past because - // the token IAT is in seconds and the token - // expiry + // the token IAT is in seconds, and the token expiry // skew will not be long enough InMemoryUaaUserDatabase userDatabase; @@ -170,11 +169,10 @@ public class TokenTestSupport { OAuth2RequestFactory requestFactory; TokenPolicy tokenPolicy; RevocableTokenProvisioning tokenProvisioning; - final Map tokens = new HashMap<>(); - private final RefreshTokenCreator refreshTokenCreator; + final Map tokens; public final TimeService timeService; public final TokenValidationService tokenValidationService; - private KeyInfoService keyInfoService; + private final KeyInfoService keyInfoService; public void clear() { tokens.clear(); @@ -182,7 +180,7 @@ public void clear() { } public TokenTestSupport(UaaTokenEnhancer tokenEnhancer, KeyInfoService keyInfo) throws Exception { - tokens.clear(); + tokens = new HashMap<>(); publisher = TestApplicationEventPublisher.forEventClass(TokenIssuedEvent.class); IdentityZoneHolder.clear(); IdentityZoneProvisioning provisioning = mock(IdentityZoneProvisioning.class); @@ -204,28 +202,27 @@ public TokenTestSupport(UaaTokenEnhancer tokenEnhancer, KeyInfoService keyInfo) mockAuthentication = new MockAuthentication(); SecurityContextHolder.getContext().setAuthentication(mockAuthentication); - requestedAuthScopes = Arrays.asList(READ, WRITE,OPENID); - clientScopes = Arrays.asList(READ, WRITE,OPENID); + requestedAuthScopes = Arrays.asList(READ, WRITE, OPENID); + clientScopes = Arrays.asList(READ, WRITE, OPENID); readScope = Collections.singletonList(READ); writeScope = Collections.singletonList(WRITE); - expandedScopes = Arrays.asList(READ, WRITE, DELETE,OPENID); + expandedScopes = Arrays.asList(READ, WRITE, DELETE, OPENID); resourceIds = Arrays.asList(SCIM, CLIENTS); - expectedJson = "[\""+READ+"\",\""+WRITE+"\",\""+OPENID+"\"]"; - + expectedJson = "[\"" + READ + "\",\"" + WRITE + "\",\"" + OPENID + "\"]"; defaultClient = new UaaClientDetails( - CLIENT_ID, - SCIM+","+CLIENTS, - READ+","+WRITE+","+OPENID+",uaa.offline_token", - ALL_GRANTS_CSV, - CLIENT_AUTHORITIES); + CLIENT_ID, + SCIM + "," + CLIENTS, + READ + "," + WRITE + "," + OPENID + ",uaa.offline_token", + ALL_GRANTS_CSV, + CLIENT_AUTHORITIES); clientWithoutRefreshToken = new UaaClientDetails( - CLIENT_ID_NO_REFRESH_TOKEN_GRANT, - SCIM+","+CLIENTS, - READ+","+WRITE+","+OPENID+",uaa.offline_token", + CLIENT_ID_NO_REFRESH_TOKEN_GRANT, + SCIM + "," + CLIENTS, + READ + "," + WRITE + "," + OPENID + ",uaa.offline_token", GRANT_TYPE_AUTHORIZATION_CODE, - CLIENT_AUTHORITIES); + CLIENT_AUTHORITIES); Map clientDetailsMap = new HashMap<>(); clientDetailsMap.put(CLIENT_ID, defaultClient); @@ -239,41 +236,40 @@ public TokenTestSupport(UaaTokenEnhancer tokenEnhancer, KeyInfoService keyInfo) clientDetailsService.setClientDetailsStore(IdentityZoneHolder.get().getId(), clientDetailsMap); tokenProvisioning = mock(RevocableTokenProvisioning.class); - doAnswer((Answer) invocation -> { - RevocableToken arg = (RevocableToken)invocation.getArguments()[1]; + Mockito.lenient().doAnswer((Answer) invocation -> { + RevocableToken arg = (RevocableToken) invocation.getArguments()[1]; tokens.put(arg.getTokenId(), arg); return null; }).when(tokenProvisioning).upsert(anyString(), any(), anyString()); doAnswer((Answer) invocation -> { - RevocableToken arg = (RevocableToken)invocation.getArguments()[0]; + RevocableToken arg = (RevocableToken) invocation.getArguments()[0]; tokens.put(arg.getTokenId(), arg); return null; }).when(tokenProvisioning).createIfNotExists(any(), anyString()); - when(tokenProvisioning.create(any(), anyString())).thenAnswer((Answer) invocation -> { - RevocableToken arg = (RevocableToken)invocation.getArguments()[0]; + Mockito.lenient().when(tokenProvisioning.create(any(), anyString())).thenAnswer((Answer) invocation -> { + RevocableToken arg = (RevocableToken) invocation.getArguments()[0]; tokens.put(arg.getTokenId(), arg); return arg; }); - when(tokenProvisioning.update(anyString(), any(), anyString())).thenAnswer((Answer) invocation -> { - String id = (String)invocation.getArguments()[0]; - RevocableToken arg = (RevocableToken)invocation.getArguments()[1]; + Mockito.lenient().when(tokenProvisioning.update(anyString(), any(), anyString())).thenAnswer((Answer) invocation -> { + String id = (String) invocation.getArguments()[0]; + RevocableToken arg = (RevocableToken) invocation.getArguments()[1]; arg.setTokenId(id); tokens.put(arg.getTokenId(), arg); return arg; }); - when(tokenProvisioning.retrieve(anyString(), anyString())).thenAnswer((Answer) invocation -> { - String id = (String)invocation.getArguments()[0]; + Mockito.lenient().when(tokenProvisioning.retrieve(anyString(), anyString())).thenAnswer((Answer) invocation -> { + String id = (String) invocation.getArguments()[0]; RevocableToken result = tokens.get(id); - if (result==null) { + if (result == null) { throw new EmptyResultDataAccessException(1); } return result; - }); AbstractOAuth2AccessTokenMatchers.revocableTokens.set(tokens); - requestFactory = new DefaultOAuth2RequestFactory((ClientDetailsService) clientDetailsService); + requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService); timeService = mock(TimeService.class); approvalService = new ApprovalService(timeService, approvalStore); when(timeService.getCurrentDate()).thenCallRealMethod(); @@ -283,7 +279,7 @@ public TokenTestSupport(UaaTokenEnhancer tokenEnhancer, KeyInfoService keyInfo) TokenValidityResolver refreshTokenValidityResolver = new TokenValidityResolver(new ClientRefreshTokenValidity(clientDetailsService, mockIdentityZoneManager), 12345, timeService); TokenValidityResolver accessTokenValidityResolver = new TokenValidityResolver(new ClientAccessTokenValidity(clientDetailsService, mockIdentityZoneManager), 1234, timeService); IdTokenCreator idTokenCreator = new IdTokenCreator(tokenEndpointBuilder, timeService, accessTokenValidityResolver, userDatabase, clientDetailsService, new HashSet<>(), mockIdentityZoneManager); - refreshTokenCreator = new RefreshTokenCreator(false, refreshTokenValidityResolver, tokenEndpointBuilder, timeService, keyInfoService); + RefreshTokenCreator refreshTokenCreator = new RefreshTokenCreator(false, refreshTokenValidityResolver, tokenEndpointBuilder, timeService, keyInfoService); tokenServices = new UaaTokenServices( idTokenCreator, tokenEndpointBuilder, @@ -304,10 +300,10 @@ public TokenTestSupport(UaaTokenEnhancer tokenEnhancer, KeyInfoService keyInfo) tokenServices.setUaaTokenEnhancer(tokenEnhancer); IdentityZoneHolder.get().getConfig().getUserConfig().setDefaultGroups( - new LinkedList<>(AuthorityUtils.authorityListToSet(USER_AUTHORITIES)) + new LinkedList<>(AuthorityUtils.authorityListToSet(USER_AUTHORITIES)) ); IdentityZoneHolder.get().getConfig().getUserConfig().setAllowedGroups( - new LinkedList<>(AuthorityUtils.authorityListToSet(USER_AUTHORITIES)) + new LinkedList<>(AuthorityUtils.authorityListToSet(USER_AUTHORITIES)) ); } @@ -320,8 +316,12 @@ public RevocableTokenProvisioning getTokenProvisioning() { } public CompositeToken getCompositeAccessToken(List scopes) { - UaaPrincipal uaaPrincipal = new UaaPrincipal(defaultUser.getId(), defaultUser.getUsername(), defaultUser.getEmail(), defaultUser.getOrigin(), defaultUser.getExternalId(), defaultUser.getZoneId()); - UaaAuthentication userAuthentication = new UaaAuthentication(uaaPrincipal, null, defaultUserAuthorities, new HashSet<>(Arrays.asList("group1", "group2")), Collections.EMPTY_MAP, null, true, System.currentTimeMillis(), System.currentTimeMillis() + 1000l * 60l); + UaaPrincipal uaaPrincipal = new UaaPrincipal(defaultUser.getId(), defaultUser.getUsername(), + defaultUser.getEmail(), defaultUser.getOrigin(), defaultUser.getExternalId(), defaultUser.getZoneId()); + UaaAuthentication userAuthentication = new UaaAuthentication(uaaPrincipal, null, + defaultUserAuthorities, new HashSet<>(Arrays.asList("group1", "group2")), Map.of(), + null, true, System.currentTimeMillis(), + System.currentTimeMillis() + 1000L * 60L); Set amr = new HashSet<>(Arrays.asList("ext", "mfa", "rba")); userAuthentication.setAuthenticationMethods(amr); userAuthentication.setAuthContextClassRef(new HashSet<>(Collections.singletonList("some test ACR"))); @@ -345,7 +345,7 @@ public Jwt getIdToken(List scopes) { Jwt tokenJwt = JwtHelper.decode(accessToken.getValue()); SignatureVerifier verifier = keyInfoService.getKey(tokenJwt.getHeader().getKid()).getVerifier(); tokenJwt.verifySignature(verifier); - assertNotNull("Token must not be null", tokenJwt); + assertThat(tokenJwt).as("Token must not be null").isNotNull(); Jwt idToken = JwtHelper.decode(accessToken.getIdTokenValue()); idToken.verifySignature(verifier); @@ -359,5 +359,4 @@ public InMemoryMultitenantClientServices getClientDetailsService() { public void copyClients(String fromZoneId, String toZoneId) { getClientDetailsService().setClientDetailsStore(toZoneId, getClientDetailsService().getInMemoryService(fromZoneId)); } - } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/AddTokenGranterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/AddTokenGranterTests.java deleted file mode 100644 index d21d3b6c1d7..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/AddTokenGranterTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * ***************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.oauth.token; - -import org.cloudfoundry.identity.uaa.oauth.provider.CompositeTokenGranter; -import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2RequestFactory; -import org.cloudfoundry.identity.uaa.oauth.provider.TokenGranter; -import org.cloudfoundry.identity.uaa.oauth.provider.token.AuthorizationServerTokenServices; -import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; -import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Test; -import org.springframework.test.util.ReflectionTestUtils; - -import java.util.List; - -import static java.util.Collections.emptyList; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; - -public class AddTokenGranterTests { - - - private CompositeTokenGranter compositeTokenGranter; - private UserTokenGranter userTokenGranter; - - @Before - public void setup() { - compositeTokenGranter = new CompositeTokenGranter(emptyList()); - userTokenGranter = new UserTokenGranter( - mock(AuthorizationServerTokenServices.class), - mock(MultitenantClientServices.class), - mock(OAuth2RequestFactory.class), - mock(RevocableTokenProvisioning.class) - ); - } - - - @Test - public void happy_day() { - new AddTokenGranter(userTokenGranter, compositeTokenGranter); - List granterList = (List) ReflectionTestUtils.getField(compositeTokenGranter, "tokenGranters"); - assertThat("User token compositeTokenGranter should have been added to the list.", granterList, Matchers.contains(userTokenGranter)); - } - - @Test(expected = IllegalArgumentException.class) - public void invalid_class_used() { - new AddTokenGranter(userTokenGranter, mock(TokenGranter.class)); - } -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/Saml2TokenGranterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/Saml2TokenGranterTest.java index 233edbaa60d..9df6569125d 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/Saml2TokenGranterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/Saml2TokenGranterTest.java @@ -20,47 +20,24 @@ import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.common.DefaultOAuth2AccessToken; import org.cloudfoundry.identity.uaa.oauth.common.DefaultOAuth2RefreshToken; +import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidGrantException; import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2Request; import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2RequestFactory; import org.cloudfoundry.identity.uaa.oauth.provider.TokenRequest; import org.cloudfoundry.identity.uaa.oauth.provider.token.AuthorizationServerTokenServices; import org.cloudfoundry.identity.uaa.security.beans.DefaultSecurityContextAccessor; -import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.opensaml.Configuration; -import org.opensaml.DefaultBootstrap; -import org.opensaml.saml2.core.Assertion; -import org.opensaml.saml2.core.impl.AssertionMarshaller; -import org.opensaml.saml2.metadata.EntityDescriptor; -import org.opensaml.xml.ConfigurationException; -import org.opensaml.xml.XMLObject; -import org.opensaml.xml.io.Unmarshaller; -import org.opensaml.xml.io.UnmarshallerFactory; -import org.opensaml.xml.io.UnmarshallingException; -import org.opensaml.xml.parse.BasicParserPool; -import org.opensaml.xml.parse.XMLParserException; -import org.opensaml.xml.util.XMLHelper; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; -import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidGrantException; -import org.springframework.security.saml.SAMLAuthenticationToken; -import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import java.io.ByteArrayInputStream; -import java.io.InputStreamReader; -import java.io.Reader; import java.security.Security; import java.util.Collection; import java.util.Date; @@ -70,7 +47,11 @@ import java.util.List; import java.util.Map; -import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.GRANT_TYPE; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.JTI; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_SAML2_BEARER; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.USER_TOKEN_REQUESTING_CLIENT_ID; @@ -78,222 +59,162 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.GRANT_TYPE; - -public class Saml2TokenGranterTest { - - @Rule - public ExpectedException exception = ExpectedException.none(); - - private Saml2TokenGranter granter; - private Saml2TokenGranter mockedgranter; - private DefaultSecurityContextAccessor mockSecurityAccessor; - private AuthorizationServerTokenServices tokenServices; - private MultitenantClientServices clientDetailsService; - private OAuth2RequestFactory requestFactory; - private UaaOauth2Authentication authentication; - private TokenRequest tokenRequest; - private UaaAuthentication userAuthentication; - private Map requestParameters; - private UaaClientDetails requestingClient; - private UaaClientDetails receivingClient; - private UaaClientDetails passwordClient; - private SAMLAuthenticationToken samltoken; - private SAMLMessageContext samlcontext; - private UaaUserDatabase uaaUserDatabase = mock(UaaUserDatabase.class); - - @Before - public void setup() { - try { DefaultBootstrap.bootstrap(); - } catch (ConfigurationException ignored) { } - tokenServices = mock(AuthorizationServerTokenServices.class); - clientDetailsService = mock(MultitenantClientServices.class); - requestFactory = mock(OAuth2RequestFactory.class); - authentication = mock(UaaOauth2Authentication.class); - samlcontext = mock(SAMLMessageContext.class); - mockSecurityAccessor = mock(DefaultSecurityContextAccessor.class); - MockHttpServletRequest request = new MockHttpServletRequest(); - ServletRequestAttributes attrs = new ServletRequestAttributes(request); - RequestContextHolder.setRequestAttributes(attrs); - Security.addProvider(new org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider()); - - userAuthentication = mock(UaaAuthentication.class); - granter = new Saml2TokenGranter( - tokenServices, - clientDetailsService, - requestFactory, - mockSecurityAccessor); - samltoken = new SAMLAuthenticationToken(samlcontext); - SecurityContextHolder.getContext().setAuthentication(authentication); - - requestingClient = new UaaClientDetails("requestingId",null,"uaa.user",GRANT_TYPE_SAML2_BEARER, null); - receivingClient = new UaaClientDetails("receivingId",null,"test.scope",GRANT_TYPE_SAML2_BEARER, null); - passwordClient = new UaaClientDetails("pwdId",null,"test.scope","password", null); - when(clientDetailsService.loadClientByClientId(eq(requestingClient.getClientId()), anyString())).thenReturn(requestingClient); - when(clientDetailsService.loadClientByClientId(eq(receivingClient.getClientId()), anyString())).thenReturn(receivingClient); - when(mockSecurityAccessor.isUser()).thenReturn(true); - requestParameters = new HashMap<>(); - requestParameters.put(USER_TOKEN_REQUESTING_CLIENT_ID, requestingClient.getClientId()); - requestParameters.put(GRANT_TYPE, GRANT_TYPE_SAML2_BEARER); - requestParameters.put(CLIENT_ID, receivingClient.getClientId()); - tokenRequest = new PublicTokenRequest(); - tokenRequest.setRequestParameters(requestParameters); - } - - @After - public void teardown() { - SecurityContextHolder.clearContext(); - } - @Test - public void test_not_authenticated() { - when(authentication.isAuthenticated()).thenReturn(false); - granter.validateRequest(tokenRequest); - } - - @Test - public void test_not_a_user_authentication() { - when(authentication.isAuthenticated()).thenReturn(true); - when(authentication.getUserAuthentication()).thenReturn(null); - granter.validateRequest(tokenRequest); - } - - @Test - public void invalid_grant_type() { - SecurityContextHolder.getContext().setAuthentication(authentication); - exception.expect(InvalidGrantException.class); - exception.expectMessage("Invalid grant type"); - requestParameters.put(GRANT_TYPE, "password"); - tokenRequest.setRequestParameters(requestParameters); - granter.validateRequest(tokenRequest); - } - - @Test - public void test_no_user_authentication() { - SecurityContextHolder.getContext().setAuthentication(authentication); - exception.expect(InvalidGrantException.class); - exception.expectMessage("User authentication not found"); - when(mockSecurityAccessor.isUser()).thenReturn(false); - granter.validateRequest(tokenRequest); - } +class Saml2TokenGranterTest { + + private Saml2TokenGranter granter; + private DefaultSecurityContextAccessor mockSecurityAccessor; + private OAuth2RequestFactory requestFactory; + private UaaOauth2Authentication authentication; + private TokenRequest tokenRequest; + private UaaAuthentication userAuthentication; + private Map requestParameters; + private UaaClientDetails requestingClient; + private UaaClientDetails receivingClient; + + @BeforeEach + void setup() { + AuthorizationServerTokenServices tokenServices = mock(AuthorizationServerTokenServices.class); + MultitenantClientServices clientDetailsService = mock(MultitenantClientServices.class); + requestFactory = mock(OAuth2RequestFactory.class); + authentication = mock(UaaOauth2Authentication.class); + mockSecurityAccessor = mock(DefaultSecurityContextAccessor.class); + MockHttpServletRequest request = new MockHttpServletRequest(); + ServletRequestAttributes attrs = new ServletRequestAttributes(request); + RequestContextHolder.setRequestAttributes(attrs); + Security.addProvider(new org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider()); + + userAuthentication = mock(UaaAuthentication.class); + granter = new Saml2TokenGranter( + tokenServices, + clientDetailsService, + requestFactory, + mockSecurityAccessor); + SecurityContextHolder.getContext().setAuthentication(authentication); + + requestingClient = new UaaClientDetails("requestingId", null, "uaa.user", GRANT_TYPE_SAML2_BEARER, null); + receivingClient = new UaaClientDetails("receivingId", null, "test.scope", GRANT_TYPE_SAML2_BEARER, null); + when(clientDetailsService.loadClientByClientId(eq(requestingClient.getClientId()), anyString())).thenReturn(requestingClient); + when(clientDetailsService.loadClientByClientId(eq(receivingClient.getClientId()), anyString())).thenReturn(receivingClient); + when(mockSecurityAccessor.isUser()).thenReturn(true); + requestParameters = new HashMap<>(); + requestParameters.put(USER_TOKEN_REQUESTING_CLIENT_ID, requestingClient.getClientId()); + requestParameters.put(GRANT_TYPE, GRANT_TYPE_SAML2_BEARER); + requestParameters.put(CLIENT_ID, receivingClient.getClientId()); + tokenRequest = new PublicTokenRequest(); + tokenRequest.setRequestParameters(requestParameters); + } - @Test(expected = InvalidGrantException.class) - public void test_no_grant_type() { - missing_parameter(GRANT_TYPE); - } + @AfterEach + void teardown() { + SecurityContextHolder.clearContext(); + } - @Test - public void test_ensure_that_access_token_is_deleted_and_modified() { - String tokenId = "access_token"; - DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(tokenId); - DefaultOAuth2RefreshToken refreshToken = new DefaultOAuth2RefreshToken("refresh_token"); - Map info = new HashMap(token.getAdditionalInformation()); - info.put(JTI, token.getValue()); - token.setAdditionalInformation(info); - token.setRefreshToken(refreshToken); - token.setExpiration(new Date()); - } + @Test + void notAuthenticated() { + when(authentication.isAuthenticated()).thenReturn(false); + assertThat(granter.validateRequest(tokenRequest)) + .isSameAs(authentication); + } - @Test - public void test_grant() { - tokenRequest.setGrantType(requestParameters.get(GRANT_TYPE)); - granter.grant(GRANT_TYPE, tokenRequest); - } + @Test + void notAUserAuthentication() { + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getUserAuthentication()).thenReturn(null); + assertThat(granter.validateRequest(tokenRequest)) + .isSameAs(authentication); + } - @Test - public void test_oauth2_authentication_with_empty_allowed() { - OAuth2Request myReq = new OAuth2Request(requestParameters, receivingClient.getClientId(), receivingClient.getAuthorities(), true, receivingClient.getScope(), receivingClient.getResourceIds(), null, null, null); - UaaClientDetails myClient = new UaaClientDetails(requestingClient); - List allowedProviders = new LinkedList(); - Map additionalInformation = new LinkedHashMap<>(); - Collection me = AuthorityUtils.commaSeparatedStringToAuthorityList("openid,foo.bar,uaa.user,one.read"); - mockedgranter = mock(Saml2TokenGranter.class); - when(mockedgranter.validateRequest(tokenRequest)).thenReturn(userAuthentication); - when(mockedgranter.getOAuth2Authentication(myClient, tokenRequest)).thenCallRealMethod(); - myClient.setScope(StringUtils.commaDelimitedListToSet("openid,foo.bar")); - additionalInformation.put(ClientConstants.ALLOWED_PROVIDERS, allowedProviders); - myClient.setAdditionalInformation(additionalInformation); - when(userAuthentication.getAuthorities()).thenReturn(me); - when(requestFactory.createOAuth2Request(receivingClient, tokenRequest)).thenReturn(myReq); - granter.getOAuth2Authentication(myClient, tokenRequest); - } + @Test + void invalidGrantType() { + SecurityContextHolder.getContext().setAuthentication(authentication); + requestParameters.put(GRANT_TYPE, "password"); + tokenRequest.setRequestParameters(requestParameters); - @Test(expected = InvalidGrantException.class) - public void test_missing_token_Request() { - granter.validateRequest(null); - } + assertThatThrownBy(() -> granter.validateRequest(tokenRequest)) + .isInstanceOf(InvalidGrantException.class) + .hasMessage("Invalid grant type"); + } - @Test - public void happy_day() { - missing_parameter("non existent"); - } + @Test + void noUserAuthentication() { + SecurityContextHolder.getContext().setAuthentication(authentication); + when(mockSecurityAccessor.isUser()).thenReturn(false); + assertThatThrownBy(() -> granter.validateRequest(tokenRequest)) + .isInstanceOf(InvalidGrantException.class) + .hasMessage("User authentication not found"); + } - protected void missing_parameter(String parameter) { - when(authentication.isAuthenticated()).thenReturn(true); - when(authentication.getUserAuthentication()).thenReturn(null); - when(authentication.getUserAuthentication()).thenReturn(userAuthentication); - when(userAuthentication.isAuthenticated()).thenReturn(true); - requestParameters.remove(parameter); - tokenRequest = new PublicTokenRequest(); - tokenRequest.setClientId(receivingClient.getClientId()); - tokenRequest.setRequestParameters(requestParameters); - tokenRequest.setGrantType(requestParameters.get(GRANT_TYPE)); - granter.validateRequest(tokenRequest); - } + @Test + void noGrantType() { + assertThatThrownBy(() -> missingParameter(GRANT_TYPE)) + .isInstanceOf(InvalidGrantException.class); + } - public static class PublicTokenRequest extends TokenRequest { - public PublicTokenRequest() { + @Test + void happyDay() { + assertThatNoException().isThrownBy(() -> missingParameter("non existent")); } - } - EntityDescriptor getMetadata(String xml) { - try { - return (EntityDescriptor)unmarshallObject(xml); - } catch(Exception ignored) { + @Test + void ensureThatAccessTokenIsDeletedAndModified() { + String tokenId = "access_token"; + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(tokenId); + DefaultOAuth2RefreshToken refreshToken = new DefaultOAuth2RefreshToken("refresh_token"); + + Map info = new HashMap<>(token.getAdditionalInformation()); + info.put(JTI, token.getValue()); + token.setAdditionalInformation(info); + token.setRefreshToken(refreshToken); + token.setExpiration(new Date()); } - return null; - } - Assertion getAssertion(String xml) { - try { - return (Assertion)unmarshallObject(xml); - } catch(Exception ignored) { + @Test + void grant() { + tokenRequest.setGrantType(requestParameters.get(GRANT_TYPE)); + assertThatNoException().isThrownBy(() -> granter.grant(GRANT_TYPE, tokenRequest)); } - return null; - } - String getAssertionXml(Assertion assertion) { - try { - AssertionMarshaller marshaller = new AssertionMarshaller(); - Element plaintextElement = marshaller.marshall(assertion); - return XMLHelper.nodeToString(plaintextElement); - } catch(Exception ignored) { + @Test + void oauth2AuthenticationWithEmptyAllowed() { + OAuth2Request myReq = new OAuth2Request(requestParameters, receivingClient.getClientId(), receivingClient.getAuthorities(), true, receivingClient.getScope(), receivingClient.getResourceIds(), null, null, null); + UaaClientDetails myClient = new UaaClientDetails(requestingClient); + List allowedProviders = new LinkedList<>(); + Map additionalInformation = new LinkedHashMap<>(); + Collection me = AuthorityUtils.commaSeparatedStringToAuthorityList("openid,foo.bar,uaa.user,one.read"); + Saml2TokenGranter mockedGranter = mock(Saml2TokenGranter.class); + when(mockedGranter.validateRequest(tokenRequest)).thenReturn(userAuthentication); + when(mockedGranter.getOAuth2Authentication(myClient, tokenRequest)).thenCallRealMethod(); + myClient.setScope(StringUtils.commaDelimitedListToSet("openid,foo.bar")); + additionalInformation.put(ClientConstants.ALLOWED_PROVIDERS, allowedProviders); + myClient.setAdditionalInformation(additionalInformation); + when(userAuthentication.getAuthorities()).thenReturn(me); + when(requestFactory.createOAuth2Request(receivingClient, tokenRequest)).thenReturn(myReq); + granter.getOAuth2Authentication(myClient, tokenRequest); } - return null; - } - /* - * Unmarshall XML string to OpenSAML XMLObject - */ - private XMLObject unmarshallObject(String xmlString) throws UnmarshallingException, XMLParserException { - BasicParserPool parser = new BasicParserPool(); - parser.setNamespaceAware(true); - /* Base64URL encoded */ - byte[] bytes = xmlString.getBytes(UTF_8); - if (bytes == null || bytes.length == 0) - throw new InsufficientAuthenticationException("Invalid assertion encoding"); - Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes)); - Document doc = parser.parse(reader); - Element samlElement = doc.getDocumentElement(); + @Test + void missingTokenRequest() { + assertThatThrownBy(() -> granter.validateRequest(null)) + .isInstanceOf(InvalidGrantException.class); + } - UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory(); - Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(samlElement); - if (unmarshaller == null) { - throw new InsufficientAuthenticationException("Unsuccessful to unmarshal assertion string"); + protected void missingParameter(String parameter) { + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getUserAuthentication()).thenReturn(null); + when(authentication.getUserAuthentication()).thenReturn(userAuthentication); + when(userAuthentication.isAuthenticated()).thenReturn(true); + requestParameters.remove(parameter); + tokenRequest = new PublicTokenRequest(); + tokenRequest.setClientId(receivingClient.getClientId()); + tokenRequest.setRequestParameters(requestParameters); + tokenRequest.setGrantType(requestParameters.get(GRANT_TYPE)); + granter.validateRequest(tokenRequest); } - return unmarshaller.unmarshall(samlElement); - } -} \ No newline at end of file + public static class PublicTokenRequest extends TokenRequest { + public PublicTokenRequest() { + } + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/passcode/PasscodeInformationTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/passcode/PasscodeInformationTest.java index 43842a1b158..18fd35a8baf 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/passcode/PasscodeInformationTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/passcode/PasscodeInformationTest.java @@ -1,22 +1,19 @@ package org.cloudfoundry.identity.uaa.passcode; -import java.security.Principal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Map; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.providers.ExpiringUsernameAuthenticationToken; - import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -38,11 +35,11 @@ public void before() { @Test void buildPasscodeInformationForUserAttributes() { final PasscodeInformation passcodeInformation = - new PasscodeInformation(uaaPrincipal.getId(), - uaaPrincipal.getName(), - null, - uaaPrincipal.getOrigin(), - Collections.emptyList()); + new PasscodeInformation(uaaPrincipal.getId(), + uaaPrincipal.getName(), + null, + uaaPrincipal.getOrigin(), + Collections.emptyList()); assertNull(passcodeInformation.getPasscode()); assertEquals(uaaPrincipal.getName(), passcodeInformation.getUsername()); @@ -79,39 +76,6 @@ void buildPasscodeInformationFromUaaAuthentication() { assertEquals(uaaPrincipal.getId(), passcodeInformation.getUserId()); } - @Test - void buildPasscodeFromExpiringToken() { - ExpiringUsernameAuthenticationToken expiringUsernameAuthenticationToken = - new ExpiringUsernameAuthenticationToken(uaaPrincipal, ""); - - final PasscodeInformation passcodeInformation = - new PasscodeInformation(expiringUsernameAuthenticationToken, authorizationParameters); - - assertNull(passcodeInformation.getPasscode()); - assertEquals(uaaPrincipal.getName(), passcodeInformation.getUsername()); - assertEquals(uaaPrincipal.getOrigin(), passcodeInformation.getOrigin()); - assertEquals(uaaPrincipal.getId(), passcodeInformation.getUserId()); - } - - @Test - void buildPasscodeInformationFromSamlToken() { - Principal principal = mock(Principal.class); - ExpiringUsernameAuthenticationToken expiringUsernameAuthenticationToken = - new ExpiringUsernameAuthenticationToken(principal, ""); - LoginSamlAuthenticationToken samlAuthenticationToken = new LoginSamlAuthenticationToken( - uaaPrincipal, - expiringUsernameAuthenticationToken - ); - - final PasscodeInformation passcodeInformation = - new PasscodeInformation(samlAuthenticationToken, authorizationParameters); - - assertNull(passcodeInformation.getPasscode()); - assertEquals(uaaPrincipal.getName(), passcodeInformation.getUsername()); - assertEquals(uaaPrincipal.getOrigin(), passcodeInformation.getOrigin()); - assertEquals(uaaPrincipal.getId(), passcodeInformation.getUserId()); - } - @Test void passcodeInformationThrowsExceptionOnUnknownPrincipal() { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("unknown principal type", ""); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpointsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpointsTest.java index 9c23277d414..ed099eb4bad 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpointsTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpointsTest.java @@ -1,44 +1,5 @@ package org.cloudfoundry.identity.uaa.provider; -import static org.assertj.core.api.Assertions.assertThat; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.SAML; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UNKNOWN; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_NAME; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.stream.Stream; - import org.apache.commons.lang3.tuple.Pair; import org.assertj.core.api.Assertions; import org.cloudfoundry.identity.uaa.alias.EntityAliasFailedException; @@ -63,13 +24,51 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.PlatformTransactionManager; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.SAML; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UNKNOWN; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_NAME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; + @ExtendWith(PollutionPreventionExtension.class) @ExtendWith(MockitoExtension.class) class IdentityProviderEndpointsTest { @@ -117,7 +116,7 @@ void setup() { } IdentityProvider getExternalOAuthProvider() { - IdentityProvider identityProvider = new IdentityProvider<>(); + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setName("my oidc provider"); identityProvider.setIdentityZoneId(OriginKeys.UAA); OIDCIdentityProviderDefinition config = new OIDCIdentityProviderDefinition(); @@ -183,7 +182,7 @@ void retrieve_oauth_provider_by_id_redacts_password() { } IdentityProvider retrieve_oauth_provider_by_id(String id, String type) { - IdentityProvider provider = getExternalOAuthProvider(); + IdentityProvider provider = getExternalOAuthProvider(); provider.setType(type); when(mockIdentityProviderProvisioning.retrieve(anyString(), anyString())).thenReturn(provider); ResponseEntity oauth = identityProviderEndpoints.retrieveIdentityProvider(id, true); @@ -191,7 +190,7 @@ IdentityProvider retrieve_oauth_provider_by_id(S assertEquals(200, oauth.getStatusCode().value()); assertNotNull(oauth.getBody()); assertNotNull(oauth.getBody().getConfig()); - assertTrue(oauth.getBody().getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition); + assertInstanceOf(AbstractExternalOAuthIdentityProviderDefinition.class, oauth.getBody().getConfig()); assertNull(((AbstractExternalOAuthIdentityProviderDefinition) oauth.getBody().getConfig()).getRelyingPartySecret()); return oauth.getBody(); } @@ -208,14 +207,14 @@ IdentityProvider retrieve_ldap_provider_by_id(St assertEquals(200, ldap.getStatusCode().value()); assertNotNull(ldap.getBody()); assertNotNull(ldap.getBody().getConfig()); - assertTrue(ldap.getBody().getConfig() instanceof LdapIdentityProviderDefinition); + assertInstanceOf(LdapIdentityProviderDefinition.class, ldap.getBody().getConfig()); assertNull(((LdapIdentityProviderDefinition) ldap.getBody().getConfig()).getBindPassword()); return ldap.getBody(); } @Test void remove_bind_password() { - remove_sensitive_data(() -> getLdapDefinition(), + remove_sensitive_data(this::getLdapDefinition, LDAP, spy -> verify((LdapIdentityProviderDefinition) spy, times(1)).setBindPassword(isNull())); } @@ -223,7 +222,7 @@ void remove_bind_password() { @Test void remove_client_secret() { for (String type : Arrays.asList(OIDC10, OAUTH20)) { - remove_sensitive_data(() -> getExternalOAuthProvider(), + remove_sensitive_data(this::getExternalOAuthProvider, type, spy -> verify((AbstractExternalOAuthIdentityProviderDefinition) spy, times(1)).setRelyingPartySecret(isNull())); } @@ -241,8 +240,8 @@ void remove_sensitive_data(Supplier getProvider, String type, @Test void remove_client_secret_wrong_origin() { - IdentityProvider provider = getExternalOAuthProvider(); - AbstractExternalOAuthIdentityProviderDefinition spy = Mockito.spy((AbstractExternalOAuthIdentityProviderDefinition) provider.getConfig()); + IdentityProvider provider = getExternalOAuthProvider(); + AbstractExternalOAuthIdentityProviderDefinition spy = Mockito.spy(provider.getConfig()); provider.setConfig(spy); provider.setType(UNKNOWN); identityProviderEndpoints.redactSensitiveData(provider); @@ -251,7 +250,7 @@ void remove_client_secret_wrong_origin() { @Test void remove_bind_password_non_ldap() { - IdentityProvider provider = getLdapDefinition(); + IdentityProvider provider = getLdapDefinition(); LdapIdentityProviderDefinition spy = Mockito.spy((LdapIdentityProviderDefinition) provider.getConfig()); provider.setConfig(spy); provider.setType(OriginKeys.UNKNOWN); @@ -261,16 +260,16 @@ void remove_bind_password_non_ldap() { @Test void patch_bind_password() { - IdentityProvider provider = getLdapDefinition(); - LdapIdentityProviderDefinition def = (LdapIdentityProviderDefinition) provider.getConfig(); + IdentityProvider provider = getLdapDefinition(); + LdapIdentityProviderDefinition def = provider.getConfig(); def.setBindPassword(null); LdapIdentityProviderDefinition spy = Mockito.spy(def); provider.setConfig(spy); reset(mockIdentityProviderProvisioning); String zoneId = IdentityZone.getUaaZoneId(); - when(mockIdentityProviderProvisioning.retrieve(eq(provider.getId()), eq(zoneId))).thenReturn(getLdapDefinition()); + when(mockIdentityProviderProvisioning.retrieve(provider.getId(), zoneId)).thenReturn(getLdapDefinition()); identityProviderEndpoints.patchSensitiveData(provider.getId(), provider); - verify(spy, times(1)).setBindPassword(eq(getLdapDefinition().getConfig().getBindPassword())); + verify(spy, times(1)).setBindPassword(getLdapDefinition().getConfig().getBindPassword()); } @Test @@ -284,16 +283,16 @@ void patch_client_secret() { provider.setType(type); reset(mockIdentityProviderProvisioning); String zoneId = IdentityZone.getUaaZoneId(); - when(mockIdentityProviderProvisioning.retrieve(eq(provider.getId()), eq(zoneId))).thenReturn(getExternalOAuthProvider()); + when(mockIdentityProviderProvisioning.retrieve(provider.getId(), zoneId)).thenReturn(getExternalOAuthProvider()); identityProviderEndpoints.patchSensitiveData(provider.getId(), provider); - verify(spy, times(1)).setRelyingPartySecret(eq(getExternalOAuthProvider().getConfig().getRelyingPartySecret())); + verify(spy, times(1)).setRelyingPartySecret(getExternalOAuthProvider().getConfig().getRelyingPartySecret()); } } @Test void patch_bind_password_non_ldap() { - IdentityProvider provider = getLdapDefinition(); - LdapIdentityProviderDefinition spy = Mockito.spy((LdapIdentityProviderDefinition) provider.getConfig()); + IdentityProvider provider = getLdapDefinition(); + LdapIdentityProviderDefinition spy = Mockito.spy(provider.getConfig()); provider.setConfig(spy); provider.setType(OriginKeys.UNKNOWN); identityProviderEndpoints.redactSensitiveData(provider); @@ -311,20 +310,20 @@ void retrieve_all_providers_redacts_data() { IdentityProvider ldap = ldapList.getBody().get(0); assertNotNull(ldap); assertNotNull(ldap.getConfig()); - assertTrue(ldap.getConfig() instanceof LdapIdentityProviderDefinition); + assertInstanceOf(LdapIdentityProviderDefinition.class, ldap.getConfig()); assertNull(ldap.getConfig().getBindPassword()); IdentityProvider oauth = ldapList.getBody().get(1); assertNotNull(oauth); assertNotNull(oauth.getConfig()); - assertTrue(oauth.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition); + assertInstanceOf(AbstractExternalOAuthIdentityProviderDefinition.class, oauth.getConfig()); assertNull(oauth.getConfig().getRelyingPartySecret()); } @Test void retrieve_by_origin_providers_redacts_data() { when(mockIdentityProviderProvisioning.retrieveByOrigin(anyString(), anyString())) - .thenReturn(getExternalOAuthProvider()); + .thenReturn(getExternalOAuthProvider()); ResponseEntity> puppyList = identityProviderEndpoints.retrieveIdentityProviders("false", true, "puppy"); assertNotNull(puppyList); assertNotNull(puppyList.getBody()); @@ -332,23 +331,23 @@ void retrieve_by_origin_providers_redacts_data() { IdentityProvider oidc = puppyList.getBody().get(0); assertNotNull(oidc); assertNotNull(oidc.getConfig()); - assertTrue(oidc.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition); + assertInstanceOf(AbstractExternalOAuthIdentityProviderDefinition.class, oidc.getConfig()); assertNull(oidc.getConfig().getRelyingPartySecret()); assertEquals(ClientAuthentication.CLIENT_SECRET_BASIC, oidc.getConfig().getAuthMethod()); } @Test - void update_ldap_provider_patches_password() throws Exception { + void update_ldap_provider_patches_password() { IdentityProvider provider = retrieve_ldap_provider_by_id("id"); provider.getConfig().setBindPassword(null); LdapIdentityProviderDefinition spy = Mockito.spy(provider.getConfig()); provider.setConfig(spy); reset(mockIdentityProviderProvisioning); String zoneId = IdentityZone.getUaaZoneId(); - when(mockIdentityProviderProvisioning.retrieve(eq(provider.getId()), eq(zoneId))).thenReturn(getLdapDefinition()); + when(mockIdentityProviderProvisioning.retrieve(provider.getId(), zoneId)).thenReturn(getLdapDefinition()); when(mockIdentityProviderProvisioning.update(any(), eq(zoneId))).thenReturn(getLdapDefinition()); ResponseEntity response = identityProviderEndpoints.updateIdentityProvider(provider.getId(), provider, true); - verify(spy, times(1)).setBindPassword(eq(getLdapDefinition().getConfig().getBindPassword())); + verify(spy, times(1)).setBindPassword(getLdapDefinition().getConfig().getBindPassword()); ArgumentCaptor captor = ArgumentCaptor.forClass(IdentityProvider.class); verify(mockIdentityProviderProvisioning, times(1)).update(captor.capture(), eq(zoneId)); assertNotNull(captor.getValue()); @@ -358,22 +357,22 @@ void update_ldap_provider_patches_password() throws Exception { assertEquals(200, response.getStatusCode().value()); assertNotNull(response.getBody()); assertNotNull(response.getBody().getConfig()); - assertTrue(response.getBody().getConfig() instanceof LdapIdentityProviderDefinition); + assertInstanceOf(LdapIdentityProviderDefinition.class, response.getBody().getConfig()); assertNull(((LdapIdentityProviderDefinition) response.getBody().getConfig()).getBindPassword()); } @Test - void update_ldap_provider_takes_new_password() throws Exception { + void update_ldap_provider_takes_new_password() { IdentityProvider provider = retrieve_ldap_provider_by_id("id"); LdapIdentityProviderDefinition spy = Mockito.spy(provider.getConfig()); provider.setConfig(spy); spy.setBindPassword("newpassword"); String zoneId = IdentityZone.getUaaZoneId(); reset(mockIdentityProviderProvisioning); - when(mockIdentityProviderProvisioning.retrieve(eq(provider.getId()), eq(zoneId))).thenReturn(getLdapDefinition()); + when(mockIdentityProviderProvisioning.retrieve(provider.getId(), zoneId)).thenReturn(getLdapDefinition()); when(mockIdentityProviderProvisioning.update(any(), eq(zoneId))).thenReturn(getLdapDefinition()); ResponseEntity response = identityProviderEndpoints.updateIdentityProvider(provider.getId(), provider, true); - verify(spy, times(1)).setBindPassword(eq("newpassword")); + verify(spy, times(1)).setBindPassword("newpassword"); ArgumentCaptor captor = ArgumentCaptor.forClass(IdentityProvider.class); verify(mockIdentityProviderProvisioning, times(1)).update(captor.capture(), eq(zoneId)); assertNotNull(captor.getValue()); @@ -384,12 +383,12 @@ void update_ldap_provider_takes_new_password() throws Exception { assertEquals(200, response.getStatusCode().value()); assertNotNull(response.getBody()); assertNotNull(response.getBody().getConfig()); - assertTrue(response.getBody().getConfig() instanceof LdapIdentityProviderDefinition); + assertInstanceOf(LdapIdentityProviderDefinition.class, response.getBody().getConfig()); assertNull(((LdapIdentityProviderDefinition) response.getBody().getConfig()).getBindPassword()); } @Test - void update_saml_provider_validator_failed() throws Exception { + void update_saml_provider_validator_failed() { IdentityProvider provider = new IdentityProvider<>(); String zoneId = IdentityZone.getUaaZoneId(); provider.setId("id"); @@ -408,7 +407,7 @@ void update_saml_provider_validator_failed() throws Exception { } @Test - void update_saml_provider_alias_failed() throws Exception { + void update_saml_provider_alias_failed() { IdentityProvider provider = new IdentityProvider<>(); String zoneId = IdentityZone.getUaaZoneId(); provider.setId("id"); @@ -426,8 +425,8 @@ void update_saml_provider_alias_failed() throws Exception { } @Test - void create_saml_provider_validator_failed() throws Exception { - IdentityProvider provider = new IdentityProvider<>(); + void create_saml_provider_validator_failed() { + IdentityProvider provider = new IdentityProvider<>(); String zoneId = IdentityZone.getUaaZoneId(); provider.setId("id"); provider.setType(SAML); @@ -443,8 +442,8 @@ void create_saml_provider_validator_failed() throws Exception { } @Test - void create_saml_provider_alias_failed() throws Exception { - IdentityProvider provider = new IdentityProvider<>(); + void create_saml_provider_alias_failed() { + IdentityProvider provider = new IdentityProvider<>(); String zoneId = IdentityZone.getUaaZoneId(); provider.setId("id"); provider.setType(SAML); @@ -460,7 +459,7 @@ void create_saml_provider_alias_failed() throws Exception { } @Test - void create_ldap_provider_removes_password() throws Exception { + void create_ldap_provider_removes_password() { String zoneId = IdentityZone.getUaaZoneId(); IdentityProvider ldapDefinition = getLdapDefinition(); assertNotNull(ldapDefinition.getConfig().getBindPassword()); @@ -470,7 +469,7 @@ void create_ldap_provider_removes_password() throws Exception { assertNotNull(created); assertEquals(LDAP, created.getType()); assertNotNull(created.getConfig()); - assertTrue(created.getConfig() instanceof LdapIdentityProviderDefinition); + assertInstanceOf(LdapIdentityProviderDefinition.class, created.getConfig()); assertNull(((LdapIdentityProviderDefinition) created.getConfig()).getBindPassword()); } @@ -499,7 +498,7 @@ private void arrangeAliasEntitiesEnabled(final boolean enabled) { @Nested class Create { @Test - void shouldReturnOriginalIdpWithAliasId_WhenAliasPropertiesAreValid() throws MetadataProviderException { + void shouldReturnOriginalIdpWithAliasId_WhenAliasPropertiesAreValid() { arrangeCurrentIdentityZone(UAA); final IdentityProvider requestBody = getExternalOAuthProvider(); @@ -534,7 +533,7 @@ void shouldReturnOriginalIdpWithAliasId_WhenAliasPropertiesAreValid() throws Met } @Test - void shouldRespondWith422_WhenAliasPropertiesAreNotValid() throws MetadataProviderException { + void shouldRespondWith422_WhenAliasPropertiesAreNotValid() { arrangeCurrentIdentityZone(UAA); final IdentityProvider requestBody = getExternalOAuthProvider(); @@ -559,7 +558,7 @@ void shouldRespondWith422_WhenAliasPropertiesAreNotValid() throws MetadataProvid void shouldRespondWithErrorCode_WhenExceptionIsThrownDuringAliasCreation( final Exception thrownException, final HttpStatus expectedStatusCode - ) throws MetadataProviderException { + ) { arrangeCurrentIdentityZone(UAA); final IdentityProvider requestBody = getExternalOAuthProvider(); @@ -601,7 +600,7 @@ private static Stream shouldRespondWithErrorCode_WhenExceptionIsThrow @Nested class Update { @Test - void shouldReturnOriginalIdpWithAliasId_WhenAliasPropertiesAreValid() throws MetadataProviderException { + void shouldReturnOriginalIdpWithAliasId_WhenAliasPropertiesAreValid() { arrangeCurrentIdentityZone(UAA); final String originalIdpId = UUID.randomUUID().toString(); @@ -643,7 +642,7 @@ void shouldReturnOriginalIdpWithAliasId_WhenAliasPropertiesAreValid() throws Met } @Test - void shouldRespondWith422_WhenAliasPropertiesAreNotValid() throws MetadataProviderException { + void shouldRespondWith422_WhenAliasPropertiesAreNotValid() { arrangeCurrentIdentityZone(UAA); final String originalIdpId = UUID.randomUUID().toString(); @@ -675,7 +674,7 @@ void shouldRespondWith422_WhenAliasPropertiesAreNotValid() throws MetadataProvid void shouldRespondWithErrorCode_WhenExceptionIsThrownDuringAliasCreation( final Exception thrownException, final HttpStatus expectedException - ) throws MetadataProviderException { + ) { arrangeCurrentIdentityZone(UAA); final String originalIdpId = UUID.randomUUID().toString(); @@ -853,7 +852,7 @@ private static IdentityProvider externalOAuthDefinition = getExternalOAuthProvider(); @@ -865,14 +864,14 @@ void create_oauth_provider_removes_password() throws Exception { assertNotNull(created); assertEquals(type, created.getType()); assertNotNull(created.getConfig()); - assertTrue(created.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition); + assertInstanceOf(AbstractExternalOAuthIdentityProviderDefinition.class, created.getConfig()); assertNull(((AbstractExternalOAuthIdentityProviderDefinition) created.getConfig()).getRelyingPartySecret()); - assertEquals(ClientAuthentication.CLIENT_SECRET_BASIC,((AbstractExternalOAuthIdentityProviderDefinition) created.getConfig()).getAuthMethod()); + assertEquals(ClientAuthentication.CLIENT_SECRET_BASIC, ((AbstractExternalOAuthIdentityProviderDefinition) created.getConfig()).getAuthMethod()); } } @Test - void create_oauth_provider_set_auth_method_none() throws Exception { + void create_oauth_provider_set_auth_method_none() { String zoneId = IdentityZone.getUaaZoneId(); for (String type : Arrays.asList(OIDC10, OAUTH20)) { IdentityProvider externalOAuthDefinition = getExternalOAuthProvider(); @@ -884,9 +883,9 @@ void create_oauth_provider_set_auth_method_none() throws Exception { assertNotNull(created); assertEquals(type, created.getType()); assertNotNull(created.getConfig()); - assertTrue(created.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition); + assertInstanceOf(AbstractExternalOAuthIdentityProviderDefinition.class, created.getConfig()); assertNull(((AbstractExternalOAuthIdentityProviderDefinition) created.getConfig()).getRelyingPartySecret()); - assertEquals(ClientAuthentication.CLIENT_SECRET_BASIC,((AbstractExternalOAuthIdentityProviderDefinition) created.getConfig()).getAuthMethod()); + assertEquals(ClientAuthentication.CLIENT_SECRET_BASIC, ((AbstractExternalOAuthIdentityProviderDefinition) created.getConfig()).getAuthMethod()); externalOAuthDefinition.getConfig().setRelyingPartySecret(null); externalOAuthDefinition.getConfig().setAuthMethod("none"); AbstractExternalOAuthIdentityProviderDefinition spy = Mockito.spy(externalOAuthDefinition.getConfig()); @@ -897,7 +896,7 @@ void create_oauth_provider_set_auth_method_none() throws Exception { assertEquals(type, upated.getType()); assertNotNull(upated.getConfig()); verify(spy, never()).setRelyingPartySecret(eq(getExternalOAuthProvider().getConfig().getRelyingPartySecret())); - assertEquals(ClientAuthentication.NONE,((AbstractExternalOAuthIdentityProviderDefinition) upated.getConfig()).getAuthMethod()); + assertEquals(ClientAuthentication.NONE, ((AbstractExternalOAuthIdentityProviderDefinition) upated.getConfig()).getAuthMethod()); } } @@ -913,7 +912,7 @@ void testPatchIdentityProviderStatusInvalidIDP() { String zoneId = IdentityZone.getUaaZoneId(); IdentityProviderStatus identityProviderStatus = new IdentityProviderStatus(); identityProviderStatus.setRequirePasswordChange(true); - IdentityProvider notUAAIDP = new IdentityProvider(); + IdentityProvider notUAAIDP = new IdentityProvider<>(); notUAAIDP.setType("NOT_UAA"); notUAAIDP.setConfig(new SamlIdentityProviderDefinition()); when(mockIdentityProviderProvisioning.retrieve(anyString(), eq(zoneId))).thenReturn(notUAAIDP); @@ -926,11 +925,11 @@ void testPatchIdentityProviderStatusWithNoIDPDefinition() { String zoneId = IdentityZone.getUaaZoneId(); IdentityProviderStatus identityProviderStatus = new IdentityProviderStatus(); identityProviderStatus.setRequirePasswordChange(true); - IdentityProvider invalidIDP = new IdentityProvider(); + IdentityProvider invalidIDP = new IdentityProvider<>(); invalidIDP.setConfig(null); invalidIDP.setType(OriginKeys.UAA); when(mockIdentityProviderProvisioning.retrieve(anyString(), eq(zoneId))).thenReturn(invalidIDP); - ResponseEntity responseEntity = identityProviderEndpoints.updateIdentityProviderStatus("123", identityProviderStatus); + ResponseEntity responseEntity = identityProviderEndpoints.updateIdentityProviderStatus("123", identityProviderStatus); assertEquals(UNPROCESSABLE_ENTITY, responseEntity.getStatusCode()); } @@ -939,11 +938,11 @@ void testPatchIdentityProviderStatusWithNoPasswordPolicy() { String zoneId = IdentityZone.getUaaZoneId(); IdentityProviderStatus identityProviderStatus = new IdentityProviderStatus(); identityProviderStatus.setRequirePasswordChange(true); - IdentityProvider invalidIDP = new IdentityProvider(); + IdentityProvider invalidIDP = new IdentityProvider<>(); invalidIDP.setType(OriginKeys.UAA); invalidIDP.setConfig(new UaaIdentityProviderDefinition(null, null)); when(mockIdentityProviderProvisioning.retrieve(anyString(), eq(zoneId))).thenReturn(invalidIDP); - ResponseEntity responseEntity = identityProviderEndpoints.updateIdentityProviderStatus("123", identityProviderStatus); + ResponseEntity responseEntity = identityProviderEndpoints.updateIdentityProviderStatus("123", identityProviderStatus); assertEquals(UNPROCESSABLE_ENTITY, responseEntity.getStatusCode()); } @@ -952,7 +951,7 @@ void testPatchIdentityProviderStatus() { String zoneId = IdentityZone.getUaaZoneId(); IdentityProviderStatus identityProviderStatus = new IdentityProviderStatus(); identityProviderStatus.setRequirePasswordChange(true); - IdentityProvider validIDP = new IdentityProvider(); + IdentityProvider validIDP = new IdentityProvider<>(); validIDP.setType(OriginKeys.UAA); validIDP.setConfig(new UaaIdentityProviderDefinition(new PasswordPolicy(), null)); when(mockIdentityProviderProvisioning.retrieve(anyString(), eq(zoneId))).thenReturn(validIDP); @@ -963,7 +962,7 @@ void testPatchIdentityProviderStatus() { @Test void testDeleteIdentityProviderExisting() { String zoneId = IdentityZone.getUaaZoneId(); - IdentityProvider validIDP = new IdentityProvider(); + IdentityProvider validIDP = new IdentityProvider<>(); validIDP.setType(OriginKeys.UAA); validIDP.setConfig(new UaaIdentityProviderDefinition( new PasswordPolicy(), null)); @@ -998,7 +997,7 @@ void testDeleteIdentityProviderNotExisting() { @Test void testDeleteIdentityProviderResponseNotContainingRelyingPartySecret() { String zoneId = IdentityZone.getUaaZoneId(); - IdentityProvider validIDP = new IdentityProvider(); + IdentityProvider validIDP = new IdentityProvider<>(); validIDP.setType(OIDC10); OIDCIdentityProviderDefinition identityProviderDefinition = new OIDCIdentityProviderDefinition(); @@ -1015,7 +1014,7 @@ void testDeleteIdentityProviderResponseNotContainingRelyingPartySecret() { identityProviderEndpoints.deleteIdentityProvider( identityProviderIdentifier, false); assertEquals(HttpStatus.OK, deleteResponse.getStatusCode()); - assertNull(((AbstractExternalOAuthIdentityProviderDefinition)deleteResponse + assertNull(((AbstractExternalOAuthIdentityProviderDefinition) deleteResponse .getBody().getConfig()).getRelyingPartySecret()); } @@ -1033,7 +1032,7 @@ void testDeleteIdentityProviderResponseNotContainingBindPassword() { identityProviderEndpoints.deleteIdentityProvider( identityProvider.getId(), false); assertEquals(HttpStatus.OK, deleteResponse.getStatusCode()); - assertNull(((LdapIdentityProviderDefinition)deleteResponse + assertNull(((LdapIdentityProviderDefinition) deleteResponse .getBody().getConfig()).getBindPassword()); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandlerTest.java deleted file mode 100644 index 9dbe1847a49..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutHandlerTest.java +++ /dev/null @@ -1,159 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.oauth; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class ExternalOAuthLogoutHandlerTest { - - private MockHttpServletRequest request = new MockHttpServletRequest(); - private MockHttpServletResponse response = new MockHttpServletResponse(); - private IdentityProvider identityProvider; - private OIDCIdentityProviderDefinition oAuthIdentityProviderDefinition; - private IdentityProviderProvisioning providerProvisioning = mock(IdentityProviderProvisioning.class); - private OidcMetadataFetcher oidcMetadataFetcher = mock(OidcMetadataFetcher.class); - private UaaAuthentication uaaAuthentication = mock(UaaAuthentication.class); - private UaaPrincipal uaaPrincipal = mock(UaaPrincipal.class); - private IdentityZoneManager identityZoneManager = mock(IdentityZoneManager.class); - - private ExternalOAuthLogoutHandler oAuthLogoutHandler = mock(ExternalOAuthLogoutHandler.class); - IdentityZoneConfiguration configuration = new IdentityZoneConfiguration(); - IdentityZoneConfiguration original; - private final String uaa_endsession_url = "http://localhost:8080/uaa/logout.do"; - - - @BeforeEach - public void setUp() throws MalformedURLException { - IdentityZone uaaZone = IdentityZone.getUaa(); - original = IdentityZone.getUaa().getConfig(); - configuration.getLinks().getLogout() - .setRedirectUrl("/login") - .setDisableRedirectParameter(true) - .setRedirectParameterName("redirect"); - uaaZone.setConfig(configuration); - identityProvider = new IdentityProvider(); - identityProvider.setType(OriginKeys.OIDC10); - identityProvider.setOriginKey("test"); - identityProvider.setId("id"); - identityProvider.setName("name"); - identityProvider.setActive(true); - oAuthIdentityProviderDefinition = new OIDCIdentityProviderDefinition(); - oAuthIdentityProviderDefinition.setLogoutUrl(new URL(uaa_endsession_url)); - oAuthIdentityProviderDefinition.setRelyingPartyId("id"); - identityProvider.setConfig(oAuthIdentityProviderDefinition); - when(providerProvisioning.retrieveByOrigin("test", "uaa")).thenReturn(identityProvider); - when(uaaAuthentication.getPrincipal()).thenReturn(uaaPrincipal); - when(uaaAuthentication.getAuthenticationMethods()).thenReturn(Set.of("ext", "oauth")); - when(uaaPrincipal.getOrigin()).thenReturn("test"); - when(uaaPrincipal.getZoneId()).thenReturn("uaa"); - when(identityZoneManager.getCurrentIdentityZone()).thenReturn(uaaZone); - oAuthLogoutHandler = new ExternalOAuthLogoutHandler(providerProvisioning, oidcMetadataFetcher, identityZoneManager); - IdentityZoneHolder.get().setConfig(configuration); - SecurityContextHolder.getContext().setAuthentication(uaaAuthentication); - } - - @AfterEach - public void tearDown() { - IdentityZoneHolder.clear(); - IdentityZone.getUaa().setConfig(original); - SecurityContextHolder.clearContext(); - request.setQueryString(null); - } - - @Test - void determineTargetUrl() { - request.setQueryString("parameter=value"); - assertEquals("http://localhost:8080/uaa/logout.do?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3Fparameter%3Dvalue&client_id=id", - oAuthLogoutHandler.determineTargetUrl(request, response, uaaAuthentication)); - } - - @Test - void determineTargetUrlWithIdTokenHint() { - request.setQueryString("parameter=value"); - when(uaaAuthentication.getIdpIdToken()).thenReturn("token"); - assertEquals("http://localhost:8080/uaa/logout.do?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3Fparameter%3Dvalue&client_id=id&id_token_hint=token", - oAuthLogoutHandler.determineTargetUrl(request, response, uaaAuthentication)); - } - - @Test - void determineDefaultTargetUrl() { - oAuthIdentityProviderDefinition.setLogoutUrl(null); - IdentityZoneHolder.get().setConfig(null); - assertEquals("/login", - oAuthLogoutHandler.determineTargetUrl(request, response, uaaAuthentication)); - } - - @Test - void constructOAuthProviderLogoutUrl() { - oAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, "", oAuthIdentityProviderDefinition, uaaAuthentication); - } - - @Test - void getLogoutUrl() throws OidcMetadataFetchingException { - assertEquals(uaa_endsession_url, oAuthLogoutHandler.getLogoutUrl(oAuthIdentityProviderDefinition)); - verify(oidcMetadataFetcher, times(0)).fetchMetadataAndUpdateDefinition(oAuthIdentityProviderDefinition); - } - - @Test - void getNewFetchedLogoutUrl() throws OidcMetadataFetchingException { - oAuthIdentityProviderDefinition.setLogoutUrl(null); - assertNull(oAuthLogoutHandler.getLogoutUrl(oAuthIdentityProviderDefinition)); - verify(oidcMetadataFetcher, times(1)).fetchMetadataAndUpdateDefinition(oAuthIdentityProviderDefinition); - } - - @Test - void getNewInvalidFetchedLogoutUrl() throws OidcMetadataFetchingException { - oAuthIdentityProviderDefinition.setLogoutUrl(null); - doThrow(new OidcMetadataFetchingException("")).when(oidcMetadataFetcher).fetchMetadataAndUpdateDefinition(oAuthIdentityProviderDefinition); - assertNull(oAuthLogoutHandler.getLogoutUrl(oAuthIdentityProviderDefinition)); - verify(oidcMetadataFetcher, times(1)).fetchMetadataAndUpdateDefinition(oAuthIdentityProviderDefinition); - } - - @Test - void getOAuthProviderForAuthentication() { - assertEquals(oAuthIdentityProviderDefinition, oAuthLogoutHandler.getOAuthProviderForAuthentication(uaaAuthentication)); - } - - @Test - void getNullOAuthProviderForAuthentication() { - assertNull(oAuthLogoutHandler.getOAuthProviderForAuthentication(null)); - } - - @Test - void getPerformRpInitiatedLogout() { - oAuthIdentityProviderDefinition.setPerformRpInitiatedLogout(true); - assertTrue(oAuthLogoutHandler.getPerformRpInitiatedLogout(oAuthIdentityProviderDefinition)); - - oAuthIdentityProviderDefinition.setPerformRpInitiatedLogout(false); - assertFalse(oAuthLogoutHandler.getPerformRpInitiatedLogout(oAuthIdentityProviderDefinition)); - - assertFalse(oAuthLogoutHandler.getPerformRpInitiatedLogout(null)); - } -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutSuccessHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutSuccessHandlerTest.java new file mode 100644 index 00000000000..4400eeea493 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthLogoutSuccessHandlerTest.java @@ -0,0 +1,167 @@ +package org.cloudfoundry.identity.uaa.provider.oauth; + +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ExternalOAuthLogoutSuccessHandlerTest { + private static final String UAA_ENDSESSION_URL = "http://localhost:8080/uaa/logout.do"; + + private final MockHttpServletRequest request = new MockHttpServletRequest(); + private final MockHttpServletResponse response = new MockHttpServletResponse(); + private OIDCIdentityProviderDefinition oAuthIdentityProviderDefinition; + + private ExternalOAuthLogoutSuccessHandler oAuthLogoutHandler = mock(ExternalOAuthLogoutSuccessHandler.class); + IdentityZoneConfiguration configuration = new IdentityZoneConfiguration(); + IdentityZoneConfiguration original; + + @Mock(lenient = true) + private IdentityProviderProvisioning providerProvisioning; + + @Mock + private OidcMetadataFetcher oidcMetadataFetcher; + + @Mock(lenient = true) + private UaaAuthentication uaaAuthentication; + + @Mock(lenient = true) + private UaaPrincipal uaaPrincipal; + + @Mock(lenient = true) + private IdentityZoneManager identityZoneManager; + + @BeforeEach + public void setUp() throws MalformedURLException { + IdentityZone uaaZone = IdentityZone.getUaa(); + original = IdentityZone.getUaa().getConfig(); + configuration.getLinks().getLogout() + .setRedirectUrl("/login") + .setDisableRedirectParameter(true) + .setRedirectParameterName("redirect"); + uaaZone.setConfig(configuration); + IdentityProvider identityProvider = new IdentityProvider(); + identityProvider.setType(OriginKeys.OIDC10); + identityProvider.setOriginKey("test"); + identityProvider.setId("id"); + identityProvider.setName("name"); + identityProvider.setActive(true); + oAuthIdentityProviderDefinition = new OIDCIdentityProviderDefinition(); + oAuthIdentityProviderDefinition.setLogoutUrl(new URL(UAA_ENDSESSION_URL)); + oAuthIdentityProviderDefinition.setRelyingPartyId("id"); + identityProvider.setConfig(oAuthIdentityProviderDefinition); + when(providerProvisioning.retrieveByOrigin("test", "uaa")).thenReturn(identityProvider); + when(uaaAuthentication.getPrincipal()).thenReturn(uaaPrincipal); + when(uaaAuthentication.getAuthenticationMethods()).thenReturn(Set.of("ext", "oauth")); + when(uaaPrincipal.getOrigin()).thenReturn("test"); + when(uaaPrincipal.getZoneId()).thenReturn("uaa"); + when(identityZoneManager.getCurrentIdentityZone()).thenReturn(uaaZone); + oAuthLogoutHandler = new ExternalOAuthLogoutSuccessHandler(providerProvisioning, oidcMetadataFetcher, identityZoneManager); + IdentityZoneHolder.get().setConfig(configuration); + SecurityContextHolder.getContext().setAuthentication(uaaAuthentication); + } + + @AfterEach + public void tearDown() { + IdentityZoneHolder.clear(); + IdentityZone.getUaa().setConfig(original); + SecurityContextHolder.clearContext(); + request.setQueryString(null); + } + + @Test + void determineTargetUrl() { + request.setQueryString("parameter=value"); + assertThat(oAuthLogoutHandler.determineTargetUrl(request, response, uaaAuthentication)).isEqualTo("http://localhost:8080/uaa/logout.do?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3Fparameter%3Dvalue&client_id=id"); + } + + @Test + void determineTargetUrlWithIdTokenHint() { + request.setQueryString("parameter=value"); + when(uaaAuthentication.getIdpIdToken()).thenReturn("token"); + assertThat(oAuthLogoutHandler.determineTargetUrl(request, response, uaaAuthentication)) + .isEqualTo("http://localhost:8080/uaa/logout.do?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3Fparameter%3Dvalue&client_id=id&id_token_hint=token"); + } + + @Test + void determineDefaultTargetUrl() { + oAuthIdentityProviderDefinition.setLogoutUrl(null); + IdentityZoneHolder.get().setConfig(null); + assertThat(oAuthLogoutHandler.determineTargetUrl(request, response, uaaAuthentication)).isEqualTo("/login"); + } + + @Test + void constructOAuthProviderLogoutUrl() { + String url = oAuthLogoutHandler.constructOAuthProviderLogoutUrl(request, "", oAuthIdentityProviderDefinition, uaaAuthentication); + assertThat(url).isEqualTo("?post_logout_redirect_uri=http%3A%2F%2Flocalhost&client_id=id"); + } + + @Test + void getLogoutUrl() throws OidcMetadataFetchingException { + assertThat(oAuthLogoutHandler.getLogoutUrl(oAuthIdentityProviderDefinition)).isEqualTo(UAA_ENDSESSION_URL); + verify(oidcMetadataFetcher, times(0)).fetchMetadataAndUpdateDefinition(oAuthIdentityProviderDefinition); + } + + @Test + void getNewFetchedLogoutUrl() throws OidcMetadataFetchingException { + oAuthIdentityProviderDefinition.setLogoutUrl(null); + assertThat(oAuthLogoutHandler.getLogoutUrl(oAuthIdentityProviderDefinition)).isNull(); + verify(oidcMetadataFetcher, times(1)).fetchMetadataAndUpdateDefinition(oAuthIdentityProviderDefinition); + } + + @Test + void getNewInvalidFetchedLogoutUrl() throws OidcMetadataFetchingException { + oAuthIdentityProviderDefinition.setLogoutUrl(null); + doThrow(new OidcMetadataFetchingException("")).when(oidcMetadataFetcher).fetchMetadataAndUpdateDefinition(oAuthIdentityProviderDefinition); + assertThat(oAuthLogoutHandler.getLogoutUrl(oAuthIdentityProviderDefinition)).isNull(); + verify(oidcMetadataFetcher, times(1)).fetchMetadataAndUpdateDefinition(oAuthIdentityProviderDefinition); + } + + @Test + void getOAuthProviderForAuthentication() { + assertThat(oAuthLogoutHandler.getOAuthProviderForAuthentication(uaaAuthentication)).isEqualTo(oAuthIdentityProviderDefinition); + } + + @Test + void getNullOAuthProviderForAuthentication() { + assertThat(oAuthLogoutHandler.getOAuthProviderForAuthentication(null)).isNull(); + } + + @Test + void getPerformRpInitiatedLogout() { + oAuthIdentityProviderDefinition.setPerformRpInitiatedLogout(true); + assertThat(oAuthLogoutHandler.getPerformRpInitiatedLogout(oAuthIdentityProviderDefinition)).isTrue(); + + oAuthIdentityProviderDefinition.setPerformRpInitiatedLogout(false); + assertThat(oAuthLogoutHandler.getPerformRpInitiatedLogout(oAuthIdentityProviderDefinition)).isFalse(); + + assertThat(oAuthLogoutHandler.getPerformRpInitiatedLogout(null)).isFalse(); + } +} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderDataTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderDataTests.java index ab2c0fd29f6..d5972b21dbf 100755 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderDataTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderDataTests.java @@ -13,160 +13,156 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.opensaml.DefaultBootstrap; -import org.opensaml.xml.parse.BasicParserPool; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.config.YamlMapFactoryBean; import org.springframework.beans.factory.config.YamlProcessor; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static java.util.Arrays.asList; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.mock; public class BootstrapSamlIdentityProviderDataTests { - public static final String testXmlFileData = "MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG\n" + - " A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\n" + - " MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu\n" + - " Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC\n" + - " VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM\n" + - " BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN\n" + - " AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU\n" + - " WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O\n" + - " Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL\n" + - " 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk\n" + - " vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6\n" + - " GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; + private static final String TEST_XML_FILE_DATA = """ + MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu + Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC + VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM + BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN + AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU + WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O + Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL + 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk + vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6 + GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"""; - public static final String testXmlFileData2 = "\n" + - "\n" + - "MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG\n" + - " A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\n" + - " MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu\n" + - " Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC\n" + - " VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM\n" + - " BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN\n" + - " AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU\n" + - " WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O\n" + - " Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL\n" + - " 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk\n" + - " vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6\n" + - " GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; + private static final String TEST_XML_FILE_DATA_2 = """ + - public static final String xmlWithoutID = - "MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG\n" + - "A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\n" + - "MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu\n" + - "Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC\n" + - "VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM\n" + - "BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN\n" + - "AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU\n" + - "WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O\n" + - "Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL\n" + - "3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk\n" + - "vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6\n" + - "GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n"; + MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu + Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC + VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM + BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN + AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU + WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O + Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL + 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk + vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6 + GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"""; - public static final String xml = String.format(xmlWithoutID, "http://www.okta.com/k2lw4l5bPODCMIIDBRYZ"); + public static final String XML_WITHOUT_ID = """ + MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu + Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC + VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM + BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN + AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU + WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O + Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL + 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk + vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6 + GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + """; - BootstrapSamlIdentityProviderData bootstrap = null; - SamlIdentityProviderDefinition singleAdd = null; - public static final String singleAddAlias = "sample-alias"; + private BootstrapSamlIdentityProviderData bootstrap = null; + private SamlIdentityProviderDefinition singleAdd = null; + private static final String SINGLE_ADD_ALIAS = "sample-alias"; public static String sampleYaml = " providers:\n" + - " okta-local:\n" + - " storeCustomAttributes: true\n" + - " idpMetadata: |\n" + - " " + testXmlFileData.replace("\n","\n ") + "\n"+ - " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " assertionConsumerIndex: 0\n" + - " metadataTrustCheck: true\n" + - " showSamlLoginLink: true\n" + - " linkText: 'Okta Preview 1'\n" + - " iconUrl: 'http://link.to/icon.jpg'\n" + - " "+ AbstractIdentityProviderDefinition.EMAIL_DOMAIN_ATTR+":\n" + - " - test.org\n" + - " - test.com\n" + - " externalGroupsWhitelist:\n" + - " - admin\n" + - " - user\n" + - " attributeMappings:\n" + - " given_name: first_name\n" + - " external_groups:\n" + - " - roles\n" + - " okta-local-2:\n" + - " idpMetadata: |\n" + - " MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG\n" + - " A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\n" + - " MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu\n" + - " Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC\n" + - " VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM\n" + - " BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN\n" + - " AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU\n" + - " WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O\n" + - " Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL\n" + - " 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk\n" + - " vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6\n" + - " GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n" + - " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " assertionConsumerIndex: 0\n" + - " metadataTrustCheck: true\n" + - " showSamlLoginLink: true\n" + - " linkText: 'Okta Preview 2'\n" + - " simplesamlphp-url:\n" + - " storeCustomAttributes: false\n" + - " assertionConsumerIndex: 0\n" + - " idpMetadata: http://simplesamlphp.com/saml2/idp/metadata.php\n" + - " metadataTrustCheck: false\n" + - " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " custom-authncontext:\n" + - " authnContext: [\"custom-context\", \"another-context\"]\n" + - " idpMetadata: |\n" + - " " + testXmlFileData.replace("\n","\n ") + "\n" - ; - - @BeforeClass - public static void initializeOpenSAML() throws Exception { - if (!org.apache.xml.security.Init.isInitialized()) { - DefaultBootstrap.bootstrap(); - } - } + " okta-local:\n" + + " storeCustomAttributes: true\n" + + " idpMetadata: |\n" + + " " + TEST_XML_FILE_DATA.replace("\n", "\n ") + "\n" + + " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + + " assertionConsumerIndex: 0\n" + + " metadataTrustCheck: true\n" + + " showSamlLoginLink: true\n" + + " linkText: 'Okta Preview 1'\n" + + " iconUrl: 'http://link.to/icon.jpg'\n" + + " " + AbstractIdentityProviderDefinition.EMAIL_DOMAIN_ATTR + ":\n" + + " - test.org\n" + + " - test.com\n" + + " externalGroupsWhitelist:\n" + + " - admin\n" + + " - user\n" + + " attributeMappings:\n" + + " given_name: first_name\n" + + " external_groups:\n" + + " - roles\n" + + " okta-local-2:\n" + + " idpMetadata: |\n" + + " MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG\n" + + " A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\n" + + " MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu\n" + + " Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC\n" + + " VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM\n" + + " BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN\n" + + " AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU\n" + + " WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O\n" + + " Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL\n" + + " 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk\n" + + " vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6\n" + + " GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n" + + " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + + " assertionConsumerIndex: 0\n" + + " metadataTrustCheck: true\n" + + " showSamlLoginLink: true\n" + + " linkText: 'Okta Preview 2'\n" + + " simplesamlphp-url:\n" + + " storeCustomAttributes: false\n" + + " assertionConsumerIndex: 0\n" + + " idpMetadata: http://simplesamlphp.com/saml2/idp/metadata.php\n" + + " metadataTrustCheck: false\n" + + " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + + " custom-authncontext:\n" + + " authnContext: [\"custom-context\", \"another-context\"]\n" + + " idpMetadata: |\n" + + " " + TEST_XML_FILE_DATA.replace("\n", "\n ") + "\n"; - @Before - public void setUp() { - bootstrap = new BootstrapSamlIdentityProviderData(new SamlIdentityProviderConfigurator(new BasicParserPool(), mock(JdbcIdentityProviderProvisioning.class), mock(FixedHttpMetaDataProvider.class))); + @BeforeEach + void beforeEach() { + bootstrap = new BootstrapSamlIdentityProviderData(new SamlIdentityProviderConfigurator(mock(JdbcIdentityProviderProvisioning.class), new IdentityZoneManagerImpl(), mock(FixedHttpMetaDataProvider.class))); singleAdd = new SamlIdentityProviderDefinition() - .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.xmlWithoutID, new RandomValueStringGenerator().generate())) - .setIdpEntityAlias(singleAddAlias) - .setNameID("sample-nameID") - .setAssertionConsumerIndex(1) - .setMetadataTrustCheck(true) - .setLinkText("sample-link-test") - .setIconUrl("sample-icon-url") - .setZoneId("uaa"); + .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID, new RandomValueStringGenerator().generate())) + .setIdpEntityAlias(SINGLE_ADD_ALIAS) + .setNameID("sample-nameID") + .setAssertionConsumerIndex(1) + .setMetadataTrustCheck(true) + .setLinkText("sample-link-test") + .setIconUrl("sample-icon-url") + .setZoneId("uaa"); } public static Map> parseYaml(String sampleYaml) { @@ -178,133 +174,127 @@ public static Map> parseYaml(String sampleYaml) { factory.setResources(resources.toArray(new Resource[0])); Map tmpdata = factory.getObject(); Map> dataMap = new HashMap<>(); - for (Map.Entry entry : ((Map)tmpdata.get("providers")).entrySet()) { - dataMap.put(entry.getKey(), (Map)entry.getValue()); + for (Map.Entry entry : ((Map) tmpdata.get("providers")).entrySet()) { + dataMap.put(entry.getKey(), (Map) entry.getValue()); } return Collections.unmodifiableMap(dataMap); } - private Map> sampleData = parseYaml(sampleYaml); + private final Map> sampleData = parseYaml(sampleYaml); @Test - public void testCloneIdentityProviderDefinition() { + void testCloneIdentityProviderDefinition() { SamlIdentityProviderDefinition clone = singleAdd.clone(); - assertEquals(singleAdd, clone); - assertNotSame(singleAdd, clone); + assertThat(clone).isEqualTo(singleAdd).isNotSameAs(singleAdd); } @Test - public void testAddProviderDefinition() throws Exception { + void testAddProviderDefinition() { bootstrap.setIdentityProviders(sampleData); bootstrap.afterPropertiesSet(); testGetIdentityProviderDefinitions(4, false); - bootstrap.getSamlProviders() - .forEach(p -> assertThat(p.isOverride(), is(true))); + assertThat(bootstrap.getSamlProviders()).allSatisfy(p -> assertThat(p.isOverride()).isTrue()); } @Test - public void test_override() throws Exception { + void test_override() { sampleData.get("okta-local").put("override", false); bootstrap.setIdentityProviders(sampleData); bootstrap.afterPropertiesSet(); testGetIdentityProviderDefinitions(4, false); - assertThat( - bootstrap + assertThat(bootstrap .getSamlProviders() .stream() .filter(p -> "okta-local".equals(p.getProvider().getOriginKey())) .findFirst() .get() - .isOverride(), - is(false) - ); + .isOverride()).isFalse(); } - @Test - public void testGetIdentityProviderDefinitions() throws Exception { + void testGetIdentityProviderDefinitions() { testGetIdentityProviderDefinitions(4); } - protected void testGetIdentityProviderDefinitions(int count) throws Exception { + private void testGetIdentityProviderDefinitions(int count) { testGetIdentityProviderDefinitions(count, true); } - protected void testGetIdentityProviderDefinitions(int count, boolean addData) { + + private void testGetIdentityProviderDefinitions(int count, boolean addData) { if (addData) { bootstrap.setIdentityProviders(sampleData); bootstrap.afterPropertiesSet(); } List idps = bootstrap.getIdentityProviderDefinitions(); - assertEquals(count, idps.size()); + assertThat(idps).hasSize(count); for (SamlIdentityProviderDefinition idp : idps) { switch (idp.getIdpEntityAlias()) { - case "okta-local" : { - assertEquals(SamlIdentityProviderDefinition.MetadataLocation.DATA, idp.getType()); - assertEquals(testXmlFileData.trim(), idp.getMetaDataLocation().trim()); - assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", idp.getNameID()); - assertEquals(0, idp.getAssertionConsumerIndex()); - assertEquals("Okta Preview 1", idp.getLinkText()); - assertEquals("http://link.to/icon.jpg", idp.getIconUrl()); + case "okta-local": { + assertThat(idp.getType()).isEqualTo(SamlIdentityProviderDefinition.MetadataLocation.DATA); + assertThat(idp.getMetaDataLocation().trim()).isEqualTo(TEST_XML_FILE_DATA.trim()); + assertThat(idp.getNameID()).isEqualTo("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"); + assertThat(idp.getAssertionConsumerIndex()).isZero(); + assertThat(idp.getLinkText()).isEqualTo("Okta Preview 1"); + assertThat(idp.getIconUrl()).isEqualTo("http://link.to/icon.jpg"); Map attributeMappings = new HashMap<>(); attributeMappings.put("given_name", "first_name"); attributeMappings.put("external_groups", Collections.singletonList("roles")); - assertEquals(attributeMappings, idp.getAttributeMappings()); - assertEquals(asList("admin", "user"), idp.getExternalGroupsWhitelist()); - assertTrue(idp.isShowSamlLink()); - assertTrue(idp.isMetadataTrustCheck()); - assertTrue(idp.getEmailDomain().containsAll(asList("test.com", "test.org"))); - assertTrue(idp.isStoreCustomAttributes()); - assertNull(idp.getAuthnContext()); + assertThat(idp.getAttributeMappings()).isEqualTo(attributeMappings); + assertThat(idp.getExternalGroupsWhitelist()).isEqualTo(asList("admin", "user")); + assertThat(idp.isShowSamlLink()).isTrue(); + assertThat(idp.isMetadataTrustCheck()).isTrue(); + assertThat(idp.getEmailDomain()).contains("test.com", "test.org"); + assertThat(idp.isStoreCustomAttributes()).isTrue(); + assertThat(idp.getAuthnContext()).isNull(); break; } - case "okta-local-2" : { - assertEquals(SamlIdentityProviderDefinition.MetadataLocation.DATA, idp.getType()); - assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", idp.getNameID()); - assertEquals(0, idp.getAssertionConsumerIndex()); - assertEquals("Okta Preview 2", idp.getLinkText()); - assertNull(idp.getIconUrl()); - assertTrue(idp.isShowSamlLink()); - assertTrue(idp.isMetadataTrustCheck()); - assertTrue(idp.isStoreCustomAttributes()); + case "okta-local-2": { + assertThat(idp.getType()).isEqualTo(SamlIdentityProviderDefinition.MetadataLocation.DATA); + assertThat(idp.getNameID()).isEqualTo("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"); + assertThat(idp.getAssertionConsumerIndex()).isZero(); + assertThat(idp.getLinkText()).isEqualTo("Okta Preview 2"); + assertThat(idp.getIconUrl()).isNull(); + assertThat(idp.isShowSamlLink()).isTrue(); + assertThat(idp.isMetadataTrustCheck()).isTrue(); + assertThat(idp.isStoreCustomAttributes()).isTrue(); break; } - case "okta-local-3" : { - assertEquals(SamlIdentityProviderDefinition.MetadataLocation.DATA, idp.getType()); - assertEquals("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", idp.getNameID()); - assertEquals(0, idp.getAssertionConsumerIndex()); - assertEquals("Use your corporate credentials", idp.getLinkText()); - assertNull(idp.getIconUrl()); - assertTrue(idp.isShowSamlLink()); - assertTrue(idp.isMetadataTrustCheck()); + case "okta-local-3": { + assertThat(idp.getType()).isEqualTo(SamlIdentityProviderDefinition.MetadataLocation.DATA); + assertThat(idp.getNameID()).isEqualTo("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"); + assertThat(idp.getAssertionConsumerIndex()).isZero(); + assertThat(idp.getLinkText()).isEqualTo("Use your corporate credentials"); + assertThat(idp.getIconUrl()).isNull(); + assertThat(idp.isShowSamlLink()).isTrue(); + assertThat(idp.isMetadataTrustCheck()).isTrue(); break; } - case singleAddAlias : { - assertEquals(singleAdd, idp); - assertNotSame(singleAdd, idp); + case SINGLE_ADD_ALIAS: { + assertThat(idp).isEqualTo(singleAdd).isNotSameAs(singleAdd); break; } - case "simplesamlphp-url" : { - assertTrue(idp.isShowSamlLink()); - assertEquals("simplesamlphp-url", idp.getLinkText()); - assertFalse(idp.isStoreCustomAttributes()); + case "simplesamlphp-url": { + assertThat(idp.isShowSamlLink()).isTrue(); + assertThat(idp.getLinkText()).isEqualTo("simplesamlphp-url"); + assertThat(idp.isStoreCustomAttributes()).isFalse(); break; } - case "custom-authncontext" : { - assertEquals(2, idp.getAuthnContext().size()); - assertEquals("custom-context", idp.getAuthnContext().get(0)); - assertEquals("another-context", idp.getAuthnContext().get(1)); + case "custom-authncontext": { + assertThat(idp.getAuthnContext()).hasSize(2); + assertThat(idp.getAuthnContext().get(0)).isEqualTo("custom-context"); + assertThat(idp.getAuthnContext().get(1)).isEqualTo("another-context"); break; } default: - fail(); + fail("Invalid IdpEntityAlias"); } } } @Test - public void testGetIdentityProvidersWithLegacy_Valid_Provider() throws Exception { - bootstrap.setLegacyIdpMetaData(testXmlFileData2); + void testGetIdentityProvidersWithLegacy_Valid_Provider() { + bootstrap.setLegacyIdpMetaData(TEST_XML_FILE_DATA_2); bootstrap.setLegacyIdpIdentityAlias("okta-local-3"); bootstrap.setLegacyShowSamlLink(true); bootstrap.setLegacyNameId("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"); @@ -312,93 +302,112 @@ public void testGetIdentityProvidersWithLegacy_Valid_Provider() throws Exception } @Test - public void testGetIdentityProviders() throws Exception { + void testGetIdentityProviders() { testGetIdentityProviderDefinitions(4); } @Test - public void testCanParseASimpleSamlConfig() { - String yaml = " providers:\n" + - " my-okta:\n" + - " assertionConsumerIndex: 0\n" + - " emailDomain: \n" + - " - mydomain.io\n" + - " iconUrl: https://my.identityprovider.com/icon.png\n" + - " idpMetadata: https://pivotal.oktapreview.com/app/abcdefghasdfsafjdsklf/sso/saml/metadata\n" + - " linkText: Log in with Pivotal OktaPreview\n" + - " metadataTrustCheck: true\n" + - " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " showSamlLoginLink: false\n" + - " signMetaData: false\n" + - " signRequest: false\n" + - " skipSslValidation: false\n" + - " storeCustomAttributes: true"; + void testCanParseASimpleSamlConfig() { + String yaml = """ + providers: + my-okta: + assertionConsumerIndex: 0 + emailDomain:\s + - mydomain.io + iconUrl: https://my.identityprovider.com/icon.png + idpMetadata: https://pivotal.oktapreview.com/app/abcdefghasdfsafjdsklf/sso/saml/metadata + linkText: Log in with Pivotal OktaPreview + metadataTrustCheck: true + nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + showSamlLoginLink: false + signMetaData: false + signRequest: false + skipSslValidation: false + storeCustomAttributes: true\ + """; - bootstrap.setIdentityProviders(parseYaml(yaml)); - bootstrap.afterPropertiesSet(); + assertThatNoException().isThrownBy(() -> bootstrap.setIdentityProviders(parseYaml(yaml))); + assertThatNoException().isThrownBy(() -> bootstrap.afterPropertiesSet()); } - + @Test - public void testSetAddShadowUserOnLoginFromYaml() { - String yaml = " providers:\n" + - " provider-without-shadow-user-definition:\n" + - " storeCustomAttributes: true\n" + - " idpMetadata: |\n" + - " " + - " " + - " " + - " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + - " " + - " " + - " \n" + - " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " provider-with-shadow-users-enabled:\n" + - " storeCustomAttributes: false\n" + - " idpMetadata: |\n" + - " " + - " " + - " " + - " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + - " " + - " " + - " \n" + - " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " addShadowUserOnLogin: true\n" + - " provider-with-shadow-user-disabled:\n" + - " idpMetadata: |\n" + - " " + - " " + - " " + - " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + - " " + - " " + - " \n" + - " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " addShadowUserOnLogin: false\n"; + void testSetAddShadowUserOnLoginFromYaml() { + String yaml = """ + providers: + provider-without-shadow-user-definition: + storeCustomAttributes: true + idpMetadata: | + \ + \ + \ + \ + \ + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\ + \ + \ + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\ + \ + \ + + nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + provider-with-shadow-users-enabled: + storeCustomAttributes: false + idpMetadata: | + \ + \ + \ + \ + \ + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\ + \ + \ + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\ + \ + \ + + nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + addShadowUserOnLogin: true + provider-with-shadow-user-disabled: + idpMetadata: | + \ + \ + \ + \ + \ + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\ + \ + \ + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\ + \ + \ + + nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + addShadowUserOnLogin: false + """; bootstrap.setIdentityProviders(parseYaml(yaml)); bootstrap.afterPropertiesSet(); for (SamlIdentityProviderDefinition def : bootstrap.getIdentityProviderDefinitions()) { switch (def.getIdpEntityAlias()) { - case "provider-without-shadow-user-definition" : { - assertTrue("If not specified, addShadowUserOnLogin is set to true", def.isAddShadowUserOnLogin()); - assertTrue("Override store custom attributes to true", def.isStoreCustomAttributes()); + case "provider-without-shadow-user-definition": { + assertThat(def.isAddShadowUserOnLogin()).as("If not specified, addShadowUserOnLogin is set to true").isTrue(); + assertThat(def.isStoreCustomAttributes()).as("Override store custom attributes to true").isTrue(); break; } - case "provider-with-shadow-users-enabled" : { - assertTrue("addShadowUserOnLogin can be set to true", def.isAddShadowUserOnLogin()); - assertFalse("Default store custom attributes is false", def.isStoreCustomAttributes()); + case "provider-with-shadow-users-enabled": { + assertThat(def.isAddShadowUserOnLogin()).as("addShadowUserOnLogin can be set to true").isTrue(); + assertThat(def.isStoreCustomAttributes()).as("Default store custom attributes is false").isFalse(); break; } - case "provider-with-shadow-user-disabled" : { - assertFalse("addShadowUserOnLogin can be set to false", def.isAddShadowUserOnLogin()); - assertTrue("Default store custom attributes is false", def.isStoreCustomAttributes()); + case "provider-with-shadow-user-disabled": { + assertThat(def.isAddShadowUserOnLogin()).as("addShadowUserOnLogin can be set to false").isFalse(); + assertThat(def.isStoreCustomAttributes()).as("Default store custom attributes is false").isTrue(); break; } - default: fail(String.format("Unknown provider %s", def.getIdpEntityAlias())); + default: + fail(String.format("Unknown provider %s", def.getIdpEntityAlias())); } - } } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProviderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProviderTest.java deleted file mode 100644 index c15ba0e7f96..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProviderTest.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; /******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - *

    - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - *

    - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ - -import org.junit.Test; -import org.opensaml.xml.XMLObject; - -import static org.junit.Assert.*; - -public class ComparableProviderTest { - - class ComparableProviderImpl implements ComparableProvider{ - private String alias; - private String zoneId; - - @Override - public String getAlias() { - return alias; - } - - @Override - public String getZoneId() { - return zoneId; - } - - @Override - public XMLObject doGetMetadata() { - return null; - } - - @Override - public byte[] fetchMetadata() { - return new byte[0]; - } - - public ComparableProviderImpl setAlias(String alias) { - this.alias = alias; - return this; - } - - public ComparableProviderImpl setZoneId(String zoneId) { - this.zoneId = zoneId; - return this; - } - - } - - @Test - public void testCompareTo(){ - ComparableProviderImpl comparableProviderThis = new ComparableProviderImpl(); - ComparableProviderImpl comparableProviderThat = new ComparableProviderImpl(); - - comparableProviderThis.setAlias(null).setZoneId(null); - comparableProviderThat.setAlias("alias").setZoneId("zone"); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) < 0); - - comparableProviderThat.setAlias("alias").setZoneId(null); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) < 0); - - comparableProviderThat.setAlias(null).setZoneId("zone"); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) < 0); - - comparableProviderThat.setAlias(null).setZoneId(null); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) == 0); - - - comparableProviderThis.setAlias(null).setZoneId("zone"); - comparableProviderThat.setAlias("alias").setZoneId("zone"); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) < 0); - - comparableProviderThat.setAlias("alias").setZoneId(null); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) < 0); - - comparableProviderThat.setAlias(null).setZoneId("zone"); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) == 0); - - comparableProviderThat.setAlias(null).setZoneId(null); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) > 0); - - - comparableProviderThis.setAlias("alias").setZoneId(null); - comparableProviderThat.setAlias("alias").setZoneId("zone"); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) < 0); - - comparableProviderThat.setAlias("alias").setZoneId(null); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) == 0); - - comparableProviderThat.setAlias(null).setZoneId("zone"); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) > 0); - - comparableProviderThat.setAlias(null).setZoneId(null); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) > 0); - - comparableProviderThis.setAlias("alias").setZoneId("zone"); - comparableProviderThat.setAlias("alias").setZoneId("zone"); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) == 0); - - comparableProviderThat.setAlias("alias").setZoneId(null); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) > 0); - - comparableProviderThat.setAlias(null).setZoneId("zone"); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) > 0); - - comparableProviderThat.setAlias(null).setZoneId(null); - assertTrue(comparableProviderThis.compareTo(comparableProviderThat) > 0); - } - - @Test - public void testGetHashCode() { - ComparableProviderImpl comparableProvider1 = new ComparableProviderImpl(); - ComparableProviderImpl comparableProvider2 = new ComparableProviderImpl(); - comparableProvider1.setAlias(null).setZoneId(null); - - assertEquals(0, comparableProvider1.getHashCode()); - - comparableProvider1.setAlias(null).setZoneId("zone"); - comparableProvider2.setAlias(null).setZoneId("zone"); - assertEquals(comparableProvider1.getHashCode(), comparableProvider2.getHashCode()); - - comparableProvider1.setAlias("alias").setZoneId(null); - comparableProvider2.setAlias("alias").setZoneId(null); - assertEquals(comparableProvider1.getHashCode(), comparableProvider2.getHashCode()); - - comparableProvider1.setAlias("alias").setZoneId(null); - comparableProvider2.setAlias(null).setZoneId("zone"); - assertNotEquals(comparableProvider1.getHashCode(), comparableProvider2.getHashCode()); - } -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProviderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProviderTest.java deleted file mode 100644 index 3710ce68033..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProviderTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.junit.Test; -import org.opensaml.DefaultBootstrap; -import org.opensaml.saml2.metadata.impl.EntityDescriptorImpl; -import org.opensaml.xml.XMLObject; -import org.opensaml.xml.parse.BasicParserPool; - -import java.io.File; -import java.util.Scanner; - -import static org.junit.Assert.*; - -public class ConfigMetadataProviderTest { - @Test - public void testDoGetMetadata() throws Exception { - String metadataString = new Scanner(new File("../uaa/src/test/resources/idp.xml")).useDelimiter("\\Z").next(); - ConfigMetadataProvider provider = new ConfigMetadataProvider(IdentityZone.getUaaZoneId(), "testalias", metadataString); - ConfigMetadataProvider provider2 = new ConfigMetadataProvider(IdentityZone.getUaaZoneId(), "testalias", metadataString); - DefaultBootstrap.bootstrap(); - provider.setParserPool(new BasicParserPool()); - XMLObject xmlObject = provider.doGetMetadata(); - assertNotNull(xmlObject); - assertEquals("http://openam.example.com:8181/openam", ((EntityDescriptorImpl) xmlObject).getEntityID()); - assertEquals(provider, provider2); - } -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepositoryTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepositoryTest.java new file mode 100644 index 00000000000..d8a4170e40f --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepositoryTest.java @@ -0,0 +1,366 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.util.FileCopyUtils; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.x509Certificate1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.x509Certificate2; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512; + +@ExtendWith(MockitoExtension.class) +class ConfiguratorRelyingPartyRegistrationRepositoryTest { + private static final String ENTITY_ID = "entityId"; + private static final String ENTITY_ID_ALIAS = "entityIdAlias"; + private static final String REGISTRATION_ID = "registrationId"; + private static final String REGISTRATION_ID_2 = "registrationId2"; + private static final String DEFAULT_NAME_ID = "defaultNameId"; + private static final String NAME_ID = "name1"; + private static final String ZONE_DOMAIN = "zoneDomain"; + private static final String ZONED_ENTITY_ID = "zoneDomain.entityId"; + private static final String ZONE_SPECIFIC_ENTITY_ID = "zoneEntityId"; + + private static final SamlConfigProps samlConfigProps = new SamlConfigProps(); + + @Mock + private SamlIdentityProviderConfigurator configurator; + + @Mock + private IdentityZone identityZone; + + @Mock + private SamlIdentityProviderDefinition definition; + + @Mock + private IdentityZoneConfiguration identityZoneConfiguration; + + @Mock + private SamlConfig samlConfig; + + private ConfiguratorRelyingPartyRegistrationRepository repository; + + @BeforeAll + public static void beforeAll() { + new IdentityZoneHolder.Initializer(null, new SamlKeyManagerFactory(samlConfigProps)); + } + + @BeforeEach + void beforeEach() { + repository = spy(new ConfiguratorRelyingPartyRegistrationRepository(ENTITY_ID, ENTITY_ID_ALIAS, configurator, List.of(), DEFAULT_NAME_ID)); + } + + @Test + void constructorWithNullConfiguratorThrows() { + List signatureAlgorithms = List.of(); + assertThatThrownBy(() -> new ConfiguratorRelyingPartyRegistrationRepository( + ENTITY_ID, ENTITY_ID_ALIAS, null, signatureAlgorithms, DEFAULT_NAME_ID) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void findByRegistrationIdWithMultipleInDb() { + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(true); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + + //definition 1 + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getNameID()).thenReturn(NAME_ID); + when(definition.getMetaDataLocation()).thenReturn("saml-sample-metadata.xml"); + + //other definitions + SamlIdentityProviderDefinition otherDefinition = mock(SamlIdentityProviderDefinition.class); + when(otherDefinition.getIdpEntityAlias()).thenReturn("otherRegistrationId"); + SamlIdentityProviderDefinition anotherDefinition = mock(SamlIdentityProviderDefinition.class); + + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(Arrays.asList(otherDefinition, definition, anotherDefinition)); + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId) + .returns(ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) + // from xml + .extracting(RelyingPartyRegistration::getAssertingPartyDetails) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + } + + @Test + void findByRegistrationIdWhenNoneFound() { + when(repository.retrieveZone()).thenReturn(identityZone); + assertThat(repository.findByRegistrationId("registrationIdNotFound")).isNull(); + } + + @Test + void getsDefaultOnNoExactMatch() { + String metadata = loadResouceAsString("saml-sample-metadata.xml"); + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(true); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getNameID()).thenReturn(NAME_ID); + when(definition.getMetaDataLocation()).thenReturn(metadata); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + assertThat(repository.findByRegistrationId("defaultRegistrationId")) + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId); + } + + @Test + void buildsCorrectRegistrationWhenMetadataXmlIsStored() { + String metadata = loadResouceAsString("saml-sample-metadata.xml"); + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(true); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getNameID()).thenReturn(NAME_ID); + when(definition.getMetaDataLocation()).thenReturn(metadata); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId) + .returns(ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) + // from xml + .extracting(RelyingPartyRegistration::getAssertingPartyDetails) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + } + + @Test + void zoneWithCredentialsUsesCorrectValues() { + samlConfigProps.setKeys(Map.of(keyName1(), samlKey1(), keyName2(), samlKey2())); + samlConfigProps.setActiveKeyId(keyName1()); + + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getMetaDataLocation()).thenReturn("saml-sample-metadata.xml"); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration.getDecryptionX509Credentials()) + .hasSize(1) + .first() + .extracting(Saml2X509Credential::getCertificate) + .isEqualTo(x509Certificate1()); + assertThat(registration.getSigningX509Credentials()) + .hasSize(2) + .first() + .extracting(Saml2X509Credential::getCertificate) + .isEqualTo(x509Certificate1()); + // Check the second element + assertThat(registration.getSigningX509Credentials()) + .element(1) + .extracting(Saml2X509Credential::getCertificate) + .isEqualTo(x509Certificate2()); + } + + @Test + void buildsCorrectRegistrationWhenMetadataLocationIsStored() { + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(true); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID_2); + when(definition.getNameID()).thenReturn(NAME_ID); + when(definition.getMetaDataLocation()).thenReturn("saml-sample-metadata.xml"); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID_2); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID_2, RelyingPartyRegistration::getRegistrationId) + .returns(ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) + // from xml + .extracting(RelyingPartyRegistration::getAssertingPartyDetails) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + } + + @Test + void fallsBackToUaaWideValuesWhenNotProvided() { + repository = spy(new ConfiguratorRelyingPartyRegistrationRepository(ENTITY_ID, + null, configurator, List.of(), DEFAULT_NAME_ID)); + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(true); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getMetaDataLocation()).thenReturn("saml-sample-metadata.xml"); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId) + .returns(ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(DEFAULT_NAME_ID, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/entityId", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/entityId", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation); + } + + @Test + void buildsCorrectRegistrationWhenZoneIdIsStored() { + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(false); + when(identityZone.getSubdomain()).thenReturn(ZONE_DOMAIN); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getNameID()).thenReturn(NAME_ID); + when(definition.getMetaDataLocation()).thenReturn("saml-sample-metadata.xml"); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId) + .returns(ZONED_ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/zoneDomain.entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/zoneDomain.entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) + // from xml + .extracting(RelyingPartyRegistration::getAssertingPartyDetails) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId) + // signature algorithm defaults to SHA256 + .extracting(RelyingPartyRegistration.AssertingPartyDetails::getSigningAlgorithms) + .isEqualTo(List.of(ALGO_ID_SIGNATURE_RSA_SHA256)); + } + + @Test + void buildsCorrectRegistrationWithZoneEntityIdSet() { + repository = spy(new ConfiguratorRelyingPartyRegistrationRepository(ENTITY_ID, + null, configurator, List.of(), DEFAULT_NAME_ID)); + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(false); + when(identityZone.getSubdomain()).thenReturn(ZONE_DOMAIN); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + when(samlConfig.getEntityID()).thenReturn(ZONE_SPECIFIC_ENTITY_ID); + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getNameID()).thenReturn(NAME_ID); + when(definition.getMetaDataLocation()).thenReturn("saml-sample-metadata.xml"); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId) + .returns(ZONE_SPECIFIC_ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/zoneDomain.entityId", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/zoneDomain.entityId", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation); + } + + @Test + void failsWhenInvalidMetadataLocationIsStored() { + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(true); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getMetaDataLocation()).thenReturn("not_found_metadata.xml"); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + assertThatThrownBy(() -> repository.findByRegistrationId(REGISTRATION_ID)) + .isInstanceOf(Saml2Exception.class) + .hasMessageContaining("not_found_metadata.xml"); + } + + @Test + void failsWhenInvalidMetadataXmlIsStored() { + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(true); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getMetaDataLocation()).thenReturn("\ninvalid xml"); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + assertThatThrownBy(() -> repository.findByRegistrationId(REGISTRATION_ID)) + .isInstanceOf(Saml2Exception.class) + .hasMessageContaining("Unsupported element"); + } + + @Test + void withSha512SignatureAlgorithm() { + repository = spy(new ConfiguratorRelyingPartyRegistrationRepository(ENTITY_ID, ENTITY_ID_ALIAS, configurator, List.of(SignatureAlgorithm.SHA512), DEFAULT_NAME_ID)); + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + when(definition.getIdpEntityAlias()).thenReturn(REGISTRATION_ID); + when(definition.getMetaDataLocation()).thenReturn("saml-sample-metadata.xml"); + when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration.getAssertingPartyDetails().getSigningAlgorithms()) + .hasSize(1) + .first() + .isEqualTo(ALGO_ID_SIGNATURE_RSA_SHA512); + } + + private String loadResouceAsString(String resourceLocation) { + ResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource resource = resourceLoader.getResource(resourceLocation); + + try (Reader reader = new InputStreamReader(resource.getInputStream(), UTF_8)) { + return FileCopyUtils.copyToString(reader); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepositoryTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepositoryTest.java new file mode 100644 index 00000000000..fec52a8b76e --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepositoryTest.java @@ -0,0 +1,189 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.x509Certificate1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.x509Certificate2; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512; + +@ExtendWith(MockitoExtension.class) +class DefaultRelyingPartyRegistrationRepositoryTest { + private static final String ENTITY_ID = "entityId"; + private static final String ENTITY_ID_ALIAS = "entityIdAlias"; + private static final String NAME_ID_FORMAT = "nameIdFormat"; + private static final String ZONE_SUBDOMAIN = "testzone"; + private static final String ZONED_ENTITY_ID = "%s.%s".formatted(ZONE_SUBDOMAIN, ENTITY_ID); + private static final String REGISTRATION_ID = "registrationId"; + private static final String REGISTRATION_ID_2 = "registrationId2"; + + private static final SamlConfigProps samlConfigProps = new SamlConfigProps(); + + @Mock + private IdentityZone identityZone; + + @Mock + private IdentityZoneConfiguration identityZoneConfig; + + @Mock + private SamlConfig samlConfig; + + private DefaultRelyingPartyRegistrationRepository repository; + + @BeforeAll + public static void beforeAll() { + new IdentityZoneHolder.Initializer(null, new SamlKeyManagerFactory(samlConfigProps)); + } + + @BeforeEach + void beforeEach() { + repository = spy(new DefaultRelyingPartyRegistrationRepository(ENTITY_ID, ENTITY_ID_ALIAS, List.of(), NAME_ID_FORMAT)); + } + + @Test + void findByRegistrationId() { + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(true); + when(identityZone.getConfig()).thenReturn(identityZoneConfig); + when(identityZoneConfig.getSamlConfig()).thenReturn(samlConfig); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId) + .returns(ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID_FORMAT, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) + // from xml + .extracting(RelyingPartyRegistration::getAssertingPartyDetails) + .returns("exampleEntityId", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + } + + @Test + void findByRegistrationIdForZone() { + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(false); + when(identityZone.getConfig()).thenReturn(identityZoneConfig); + when(identityZone.getSubdomain()).thenReturn(ZONE_SUBDOMAIN); + when(identityZoneConfig.getSamlConfig()).thenReturn(samlConfig); + when(samlConfig.getEntityID()).thenReturn(ZONED_ENTITY_ID); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId) + .returns(ZONED_ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID_FORMAT, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/testzone.entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/testzone.entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) + // from xml + .extracting(RelyingPartyRegistration::getAssertingPartyDetails) + .returns("exampleEntityId", RelyingPartyRegistration.AssertingPartyDetails::getEntityId) + // signature algorithm defaults to SHA256 + .extracting(RelyingPartyRegistration.AssertingPartyDetails::getSigningAlgorithms) + .isEqualTo(List.of(ALGO_ID_SIGNATURE_RSA_SHA256)); + } + + @Test + void findByRegistrationIdForZoneWithoutConfig() { + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(false); + when(identityZone.getSubdomain()).thenReturn(ZONE_SUBDOMAIN); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID_2); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID_2, RelyingPartyRegistration::getRegistrationId) + .returns(ZONED_ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID_FORMAT, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/testzone.entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/testzone.entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation); + } + + @Test + void findByRegistrationId_NoAliasFailsOverToEntityId() { + repository = spy(new DefaultRelyingPartyRegistrationRepository(ENTITY_ID, null, List.of(), NAME_ID_FORMAT)); + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(true); + when(identityZone.getConfig()).thenReturn(identityZoneConfig); + when(identityZoneConfig.getSamlConfig()).thenReturn(samlConfig); + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.isUaa()).thenReturn(false); + when(identityZone.getSubdomain()).thenReturn(ZONE_SUBDOMAIN); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID_2); + assertThat(registration) + // from definition + .returns(REGISTRATION_ID_2, RelyingPartyRegistration::getRegistrationId) + .returns(ZONED_ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID_FORMAT, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/testzone.entityId", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/testzone.entityId", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation); + } + + @Test + void zoneWithCredentialsUsesCorrectValues() { + samlConfigProps.setKeys(Map.of(keyName1(), samlKey1(), keyName2(), samlKey2())); + samlConfigProps.setActiveKeyId(keyName1()); + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.getConfig()).thenReturn(identityZoneConfig); + when(identityZoneConfig.getSamlConfig()).thenReturn(samlConfig); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration.getDecryptionX509Credentials()) + .hasSize(1) + .first() + .extracting(Saml2X509Credential::getCertificate) + .isEqualTo(x509Certificate1()); + assertThat(registration.getSigningX509Credentials()) + .hasSize(2) + .first() + .extracting(Saml2X509Credential::getCertificate) + .isEqualTo(x509Certificate1()); + // Check the second element + assertThat(registration.getSigningX509Credentials()) + .element(1) + .extracting(Saml2X509Credential::getCertificate) + .isEqualTo(x509Certificate2()); + } + + @Test + void withSha512SignatureAlgorithm() { + repository = spy(new DefaultRelyingPartyRegistrationRepository(ENTITY_ID, ENTITY_ID_ALIAS, List.of(SignatureAlgorithm.SHA512), NAME_ID_FORMAT)); + when(repository.retrieveZone()).thenReturn(identityZone); + when(identityZone.getConfig()).thenReturn(identityZoneConfig); + when(identityZoneConfig.getSamlConfig()).thenReturn(samlConfig); + + RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); + assertThat(registration.getAssertingPartyDetails().getSigningAlgorithms()) + .hasSize(1) + .first() + .isEqualTo(ALGO_ID_SIGNATURE_RSA_SHA512); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepositoryTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepositoryTest.java new file mode 100644 index 00000000000..8f6e3fa3b68 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DelegatingRelyingPartyRegistrationRepositoryTest.java @@ -0,0 +1,93 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class DelegatingRelyingPartyRegistrationRepositoryTest { + + private static final String REGISTRATION_ID = "test"; + + @Mock + IdentityZone identityZone; + + @Test + void constructor_WhenRepositoriesAreNull() { + assertThatThrownBy(() -> { + new DelegatingRelyingPartyRegistrationRepository((List) null); + }).isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy(() -> { + new DelegatingRelyingPartyRegistrationRepository((RelyingPartyRegistrationRepository[]) null); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void constructor_whenRepositoriesAreEmpty() { + assertThatThrownBy(() -> { + new DelegatingRelyingPartyRegistrationRepository(Collections.emptyList()); + }).isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy(() -> { + new DelegatingRelyingPartyRegistrationRepository(new RelyingPartyRegistrationRepository[]{}); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void findWhenRegistrationNotFound() { + RelyingPartyRegistrationRepository mockRepository = mock(RelyingPartyRegistrationRepository.class); + when(mockRepository.findByRegistrationId(anyString())).thenReturn(null); + DelegatingRelyingPartyRegistrationRepository target = new DelegatingRelyingPartyRegistrationRepository(mockRepository); + assertThat(target.findByRegistrationId(REGISTRATION_ID)).isNull(); + } + + @Test + void findWhenRegistrationFound() { + RelyingPartyRegistration expectedRegistration = mock(RelyingPartyRegistration.class); + RelyingPartyRegistrationRepository mockRepository1 = mock(RelyingPartyRegistrationRepository.class); + + RelyingPartyRegistrationRepository mockRepository2 = mock(RelyingPartyRegistrationRepository.class); + when(mockRepository2.findByRegistrationId(REGISTRATION_ID)).thenReturn(expectedRegistration); + + DelegatingRelyingPartyRegistrationRepository target = new DelegatingRelyingPartyRegistrationRepository(mockRepository1, mockRepository2); + assertThat(target.findByRegistrationId(REGISTRATION_ID)).isEqualTo(expectedRegistration); + + verify(mockRepository1).findByRegistrationId(REGISTRATION_ID); + verify(mockRepository2).findByRegistrationId(REGISTRATION_ID); + } + + @Test + void findWhenZonedRegistrationFound() { + when(identityZone.isUaa()).thenReturn(false); + + RelyingPartyRegistration expectedRegistration = mock(RelyingPartyRegistration.class); + RelyingPartyRegistrationRepository mockRepository1 = mock(RelyingPartyRegistrationRepository.class); + + RelyingPartyRegistrationRepository mockRepository2 = mock(DefaultRelyingPartyRegistrationRepository.class); + when(mockRepository2.findByRegistrationId(REGISTRATION_ID)).thenReturn(expectedRegistration); + + DelegatingRelyingPartyRegistrationRepository target = spy(new DelegatingRelyingPartyRegistrationRepository(mockRepository1, mockRepository2)); + when(target.retrieveZone()).thenReturn(identityZone); + assertThat(target.findByRegistrationId(REGISTRATION_ID)).isEqualTo(expectedRegistration); + + // is not ZoneAware, so it should not call findByRegistrationId + verify(mockRepository1, never()).findByRegistrationId(REGISTRATION_ID); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java deleted file mode 100644 index 2f94c9a08ae..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java +++ /dev/null @@ -1,1077 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.cloudfoundry.identity.uaa.annotations.WithDatabaseContext; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.db.DatabaseUrlModifier; -import org.cloudfoundry.identity.uaa.db.Vendor; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.resources.jdbc.LimitSqlAdapter; -import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; -import org.cloudfoundry.identity.uaa.scim.ScimGroup; -import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; -import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.scim.ScimUserAliasHandler; -import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; -import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimUserBootstrap; -import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupExternalMembershipManager; -import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupMembershipManager; -import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; -import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; -import org.cloudfoundry.identity.uaa.scim.services.ScimUserService; -import org.cloudfoundry.identity.uaa.test.TestUtils; -import org.cloudfoundry.identity.uaa.user.JdbcUaaUserDatabase; -import org.cloudfoundry.identity.uaa.user.UaaAuthority; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; -import org.cloudfoundry.identity.uaa.user.UserInfo; -import org.cloudfoundry.identity.uaa.util.beans.DbUtils; -import org.cloudfoundry.identity.uaa.util.TimeService; -import org.cloudfoundry.identity.uaa.util.TimeServiceImpl; -import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; -import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; -import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; -import org.joda.time.DateTime; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.opensaml.common.SAMLException; -import org.opensaml.saml2.core.Assertion; -import org.opensaml.saml2.core.Attribute; -import org.opensaml.saml2.core.AuthnContext; -import org.opensaml.saml2.core.AuthnContextClassRef; -import org.opensaml.saml2.core.AuthnStatement; -import org.opensaml.saml2.core.NameID; -import org.opensaml.ws.wsaddressing.impl.AttributedURIImpl; -import org.opensaml.ws.wssecurity.impl.AttributedStringImpl; -import org.opensaml.xml.XMLObject; -import org.opensaml.xml.encryption.DecryptionException; -import org.opensaml.xml.schema.XSBoolean; -import org.opensaml.xml.schema.XSBooleanValue; -import org.opensaml.xml.schema.impl.XSAnyImpl; -import org.opensaml.xml.schema.impl.XSBase64BinaryImpl; -import org.opensaml.xml.schema.impl.XSBooleanBuilder; -import org.opensaml.xml.schema.impl.XSBooleanImpl; -import org.opensaml.xml.schema.impl.XSDateTimeImpl; -import org.opensaml.xml.schema.impl.XSIntegerImpl; -import org.opensaml.xml.schema.impl.XSQNameImpl; -import org.opensaml.xml.schema.impl.XSURIImpl; -import org.opensaml.xml.security.SecurityException; -import org.opensaml.xml.validation.ValidationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.dao.IncorrectResultSizeDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.saml.SAMLAuthenticationToken; -import org.springframework.security.saml.SAMLConstants; -import org.springframework.security.saml.SAMLCredential; -import org.springframework.security.saml.context.SAMLMessageContext; -import org.springframework.security.saml.log.SAMLLogger; -import org.springframework.security.saml.metadata.ExtendedMetadata; -import org.springframework.security.saml.websso.WebSSOProfileConsumer; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.context.request.ServletWebRequest; - -import javax.servlet.ServletContext; -import javax.xml.namespace.QName; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_VERIFIED_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; -import static org.cloudfoundry.identity.uaa.test.ModelTestUtils.getResourceAsString; -import static org.cloudfoundry.identity.uaa.user.UaaUserMatcher.aUaaUser; -import static org.cloudfoundry.identity.uaa.util.AssertThrowsWithMessage.assertThrowsWithMessageThat; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.emptyIterable; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@WithDatabaseContext -class LoginSamlAuthenticationProviderTests { - private static final String SAML_USER = "saml.user"; - private static final String SAML_ADMIN = "saml.admin"; - private static final String SAML_TEST = "saml.test"; - private static final String SAML_NOT_MAPPED = "saml.unmapped"; - private static final String SAML_NOT_ASSERTED = "saml.unasserted"; - private static final String UAA_USER = "uaa.user"; - private static final String UAA_SAML_USER = "uaa.saml.user"; - private static final String UAA_SAML_ADMIN = "uaa.saml.admin"; - private static final String UAA_SAML_TEST = "uaa.saml.test"; - private static final String COST_CENTER = "costCenter"; - private static final String DENVER_CO = "Denver,CO"; - private static final String MANAGER = "manager"; - private static final String JOHN_THE_SLOTH = "John the Sloth"; - private static final String KARI_THE_ANT_EATER = "Kari the Ant Eater"; - - private JdbcIdentityProviderProvisioning providerProvisioning; - private CreateUserPublisher publisher; - private JdbcUaaUserDatabase userDatabase; - private LoginSamlAuthenticationProvider authprovider; - private WebSSOProfileConsumer consumer; - private SAMLLogger samlLogger = mock(SAMLLogger.class); - private SamlIdentityProviderDefinition providerDefinition; - private IdentityProvider provider; - private ScimUserProvisioning userProvisioning; - private JdbcScimGroupExternalMembershipManager externalManager; - private ScimGroup uaaSamlUser; - private ScimGroup uaaSamlAdmin; - private IdentityZoneManager identityZoneManager; - - @Autowired - private JdbcTemplate jdbcTemplate; - - @Autowired - NamedParameterJdbcTemplate namedJdbcTemplate; - - @Autowired - private LimitSqlAdapter limitSqlAdapter; - - @Autowired - private PasswordEncoder passwordEncoder; - - @BeforeEach - void configureProvider() throws SAMLException, SecurityException, DecryptionException, ValidationException, SQLException { - identityZoneManager = new IdentityZoneManagerImpl(); - RequestContextHolder.resetRequestAttributes(); - MockHttpServletRequest request = new MockHttpServletRequest(mock(ServletContext.class)); - MockHttpServletResponse response = new MockHttpServletResponse(); - ServletWebRequest servletWebRequest = new ServletWebRequest(request, response); - RequestContextHolder.setRequestAttributes(servletWebRequest); - DbUtils dbUtils = new DbUtils(); - - ScimGroupProvisioning groupProvisioning = new JdbcScimGroupProvisioning( - namedJdbcTemplate, new JdbcPagingListFactory(namedJdbcTemplate, limitSqlAdapter), dbUtils); - identityZoneManager.getCurrentIdentityZone().getConfig().getUserConfig().setDefaultGroups(Collections.singletonList(UAA_USER)); - identityZoneManager.getCurrentIdentityZone().getConfig().getUserConfig().setAllowedGroups(Arrays.asList(UAA_USER, SAML_USER, - SAML_ADMIN,SAML_TEST,SAML_NOT_MAPPED, UAA_SAML_USER,UAA_SAML_ADMIN,UAA_SAML_TEST)); - groupProvisioning.createOrGet(new ScimGroup(null, UAA_USER, identityZoneManager.getCurrentIdentityZone().getId()), identityZoneManager.getCurrentIdentityZone().getId()); - providerDefinition = new SamlIdentityProviderDefinition(); - - userProvisioning = new JdbcScimUserProvisioning(namedJdbcTemplate, new JdbcPagingListFactory(namedJdbcTemplate, limitSqlAdapter), passwordEncoder, new IdentityZoneManagerImpl(), new JdbcIdentityZoneProvisioning(jdbcTemplate), new SimpleSearchQueryConverter(), new SimpleSearchQueryConverter(), new TimeServiceImpl(), true); - - - uaaSamlUser = groupProvisioning.create(new ScimGroup(null, UAA_SAML_USER, IdentityZone.getUaaZoneId()), identityZoneManager.getCurrentIdentityZone().getId()); - uaaSamlAdmin = groupProvisioning.create(new ScimGroup(null, UAA_SAML_ADMIN, IdentityZone.getUaaZoneId()), identityZoneManager.getCurrentIdentityZone().getId()); - ScimGroup uaaSamlTest = groupProvisioning.create(new ScimGroup(null, UAA_SAML_TEST, IdentityZone.getUaaZoneId()), identityZoneManager.getCurrentIdentityZone().getId()); - - JdbcScimGroupMembershipManager membershipManager = new JdbcScimGroupMembershipManager( - jdbcTemplate, new TimeServiceImpl(), userProvisioning, null, dbUtils); - membershipManager.setScimGroupProvisioning(groupProvisioning); - - final ScimUserAliasHandler aliasHandler = mock(ScimUserAliasHandler.class); - when(aliasHandler.aliasPropertiesAreValid(any(), any())).thenReturn(true); - - ScimUserBootstrap bootstrap = new ScimUserBootstrap( - userProvisioning, - new ScimUserService( - aliasHandler, - userProvisioning, - identityZoneManager, - null, // not required since alias is disabled - false - ), - groupProvisioning, - membershipManager, - Collections.emptyList(), - false, - Collections.emptyList(), - false - ); - - externalManager = new JdbcScimGroupExternalMembershipManager(jdbcTemplate, dbUtils); - externalManager.setScimGroupProvisioning(groupProvisioning); - externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - externalManager.mapExternalGroup(uaaSamlTest.getId(), SAML_TEST, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - - consumer = mock(WebSSOProfileConsumer.class); - SAMLCredential credential = getUserCredential("marissa-saml", "Marissa", "Bloggs", "marissa.bloggs@test.com", "1234567890"); - - when(consumer.processAuthenticationResponse(any())).thenReturn(credential); - - TimeService timeService = mock(TimeService.class); - DatabaseUrlModifier databaseUrlModifier = mock(DatabaseUrlModifier.class); - when(databaseUrlModifier.getDatabaseType()).thenReturn(Vendor.unknown); - userDatabase = new JdbcUaaUserDatabase(jdbcTemplate, timeService, false, identityZoneManager, - databaseUrlModifier, new DbUtils()); - providerProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); - publisher = new CreateUserPublisher(bootstrap); - - authprovider = new LoginSamlAuthenticationProvider( - identityZoneManager, - userDatabase, - providerProvisioning, - externalManager); - authprovider.setApplicationEventPublisher(publisher); - authprovider.setConsumer(consumer); - authprovider.setSamlLogger(samlLogger); - - provider = new IdentityProvider(); - provider.setIdentityZoneId(IdentityZone.getUaaZoneId()); - provider.setOriginKey(OriginKeys.SAML); - provider.setName("saml-test"); - provider.setActive(true); - provider.setType(OriginKeys.SAML); - providerDefinition.setMetaDataLocation(String.format(IDP_META_DATA, OriginKeys.SAML)); - providerDefinition.setIdpEntityAlias(OriginKeys.SAML); - provider.setConfig(providerDefinition); - provider = providerProvisioning.create(provider, identityZoneManager.getCurrentIdentityZone().getId()); - } - - @AfterEach - void tearDown(@Autowired ApplicationContext applicationContext) throws SQLException { - TestUtils.restoreToDefaults(applicationContext); - RequestContextHolder.resetRequestAttributes(); - } - - @Test - void testAuthenticateSimple() { - assertNotNull(authprovider.authenticate(mockSamlAuthentication())); - } - - @Test - void testAuthenticationEvents() { - authprovider.authenticate(mockSamlAuthentication()); - assertEquals(3, publisher.events.size()); - assertTrue(publisher.events.get(2) instanceof IdentityProviderAuthenticationSuccessEvent); - } - - @Test - void relay_sets_attribute() { - for (String url : Arrays.asList("test", "www.google.com", null)) { - authprovider.configureRelayRedirect(url); - assertNull(RequestContextHolder.currentRequestAttributes().getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)); - } - } - - @Test - void test_relay_state_when_url() { - String redirectUrl = "https://www.cloudfoundry.org"; - SAMLAuthenticationToken samlAuthenticationToken = mockSamlAuthentication(); - when(samlAuthenticationToken.getCredentials().getRelayState()).thenReturn(redirectUrl); - Authentication authentication = authprovider.authenticate(samlAuthenticationToken); - assertNotNull(authentication, "Authentication cannot be null"); - assertTrue(authentication instanceof UaaAuthentication, "Authentication should be of type:" + UaaAuthentication.class.getName()); - UaaAuthentication uaaAuthentication = (UaaAuthentication) authentication; - assertThat(uaaAuthentication.getAuthContextClassRef(), containsInAnyOrder(AuthnContext.PASSWORD_AUTHN_CTX)); - SAMLMessageContext context = samlAuthenticationToken.getCredentials(); - verify(context, times(1)).getRelayState(); - assertEquals(redirectUrl, RequestContextHolder.currentRequestAttributes().getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)); - } - - @Test - void saml_authentication_contains_acr() { - SAMLAuthenticationToken samlAuthenticationToken = mockSamlAuthentication(); - Authentication authentication = authprovider.authenticate(samlAuthenticationToken); - assertNotNull(authentication, "Authentication cannot be null"); - assertTrue(authentication instanceof UaaAuthentication, "Authentication should be of type:" + UaaAuthentication.class.getName()); - UaaAuthentication uaaAuthentication = (UaaAuthentication) authentication; - assertThat(uaaAuthentication.getAuthContextClassRef(), containsInAnyOrder(AuthnContext.PASSWORD_AUTHN_CTX)); - - SAMLMessageContext context = samlAuthenticationToken.getCredentials(); - verify(context, times(1)).getRelayState(); - assertNull(RequestContextHolder.currentRequestAttributes().getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)); - } - - @Test - void test_multiple_group_attributes() { - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, Arrays.asList("2ndgroups", "groups")); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - UaaAuthentication authentication = getAuthentication(authprovider); - assertEquals(4, authentication.getAuthorities().size(), "Four authorities should have been granted!"); - assertThat(authentication.getAuthorities(), - containsInAnyOrder( - new SimpleGrantedAuthority(UAA_SAML_ADMIN), - new SimpleGrantedAuthority(UAA_SAML_USER), - new SimpleGrantedAuthority(UAA_SAML_TEST), - new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) - ) - ); - } - - @Test - void authenticationContainsAmr() { - UaaAuthentication authentication = getAuthentication(authprovider); - assertThat(authentication.getAuthenticationMethods(), containsInAnyOrder("ext")); - } - - @Test - void test_external_groups_as_scopes() { - providerDefinition.setGroupMappingMode(SamlIdentityProviderDefinition.ExternalGroupMappingMode.AS_SCOPES); - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, Arrays.asList("2ndgroups", "groups")); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - UaaAuthentication authentication = getAuthentication(authprovider); - assertThat(authentication.getAuthorities(), - containsInAnyOrder( - new SimpleGrantedAuthority(SAML_ADMIN), - new SimpleGrantedAuthority(SAML_USER), - new SimpleGrantedAuthority(SAML_TEST), - new SimpleGrantedAuthority(SAML_NOT_MAPPED), - new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) - ) - ); - } - - @Test - void test_group_mapping() { - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - UaaAuthentication authentication = getAuthentication(authprovider); - assertEquals(3, authentication.getAuthorities().size(), "Three authorities should have been granted!"); - assertThat(authentication.getAuthorities(), - containsInAnyOrder( - new SimpleGrantedAuthority(UAA_SAML_ADMIN), - new SimpleGrantedAuthority(UAA_SAML_USER), - new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) - ) - ); - } - - @Test - void test_non_string_attributes() { - providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSURI", "XSURI"); - providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSAny", "XSAny"); - providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSQName", "XSQName"); - providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSInteger", "XSInteger"); - providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSBoolean", "XSBoolean"); - providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSDateTime", "XSDateTime"); - providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSBase64Binary", "XSBase64Binary"); - - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - UaaAuthentication authentication = getAuthentication(authprovider); - assertEquals("http://localhost:8080/someuri", authentication.getUserAttributes().getFirst("XSURI")); - assertEquals("XSAnyValue", authentication.getUserAttributes().getFirst("XSAny")); - assertEquals("XSQNameValue", authentication.getUserAttributes().getFirst("XSQName")); - assertEquals("3", authentication.getUserAttributes().getFirst("XSInteger")); - assertEquals("true", authentication.getUserAttributes().getFirst("XSBoolean")); - assertEquals(new DateTime(0).toString(), authentication.getUserAttributes().getFirst("XSDateTime")); - assertEquals("00001111", authentication.getUserAttributes().getFirst("XSBase64Binary")); - } - - @Test - void externalGroup_NotMapped_ToScope() { - try { - externalManager.unmapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - externalManager.unmapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - UaaAuthentication authentication = getAuthentication(authprovider); - assertEquals(1, authentication.getAuthorities().size(), "Three authorities should have been granted!"); - assertThat(authentication.getAuthorities(), - not(containsInAnyOrder( - new SimpleGrantedAuthority(UAA_SAML_ADMIN), - new SimpleGrantedAuthority(UAA_SAML_USER) - )) - ); - } finally { - externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - } - } - - @Test - void test_group_attribute_not_set() { - UaaAuthentication uaaAuthentication = getAuthentication(authprovider); - assertEquals(1, uaaAuthentication.getAuthorities().size(), "Only uaa.user should have been granted"); - assertEquals(UaaAuthority.UAA_USER.getAuthority(), uaaAuthentication.getAuthorities().iterator().next().getAuthority()); - } - - @Test - void dontAdd_external_groups_to_authentication_without_matching_whitelist() { - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - providerDefinition.addWhiteListedGroup(SAML_NOT_ASSERTED); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - UaaAuthentication authentication = getAuthentication(authprovider); - assertEquals(Collections.EMPTY_SET, authentication.getExternalGroups()); - } - - @Test - void add_external_groups_to_authentication_with_empty_whitelist() { - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - UaaAuthentication authentication = getAuthentication(authprovider); - assertThat(authentication.getExternalGroups(), containsInAnyOrder(SAML_USER, SAML_ADMIN, SAML_NOT_MAPPED)); - } - - @Test - void add_external_groups_to_authentication_with_whitelist() { - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - providerDefinition.addWhiteListedGroup(SAML_ADMIN); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - UaaAuthentication authentication = getAuthentication(authprovider); - assertEquals(Collections.singleton(SAML_ADMIN), authentication.getExternalGroups()); - } - - @Test - void add_external_groups_to_authentication_with_wildcard_whitelist() { - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - providerDefinition.addWhiteListedGroup("saml*"); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - UaaAuthentication authentication = getAuthentication(authprovider); - assertThat(authentication.getExternalGroups(), containsInAnyOrder(SAML_USER, SAML_ADMIN, SAML_NOT_MAPPED)); - } - - @Test - void update_invitedUser_whose_username_is_notEmail() throws Exception { - ScimUser scimUser = getInvitedUser(); - - SAMLCredential credential = getUserCredential("marissa-invited", "Marissa-invited", null, "marissa.invited@test.org", null); - when(consumer.processAuthenticationResponse(any())).thenReturn(credential); - getAuthentication(authprovider); - - UaaUser user = userDatabase.retrieveUserById(scimUser.getId()); - assertFalse(user.isVerified()); - assertEquals("marissa-invited", user.getUsername()); - assertEquals("marissa.invited@test.org", user.getEmail()); - - RequestContextHolder.resetRequestAttributes(); - } - - @Test - void invitedUser_authentication_whenAuthenticatedEmailDoesNotMatchInvitedEmail() throws Exception { - Map attributeMappings = new HashMap<>(); - attributeMappings.put("email", "emailAddress"); - providerDefinition.setAttributeMappings(attributeMappings); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - ScimUser scimUser = getInvitedUser(); - - SAMLCredential credential = getUserCredential("marissa-invited", "Marissa-invited", null, "different@test.org", null); - when(consumer.processAuthenticationResponse(any())).thenReturn(credential); - try { - getAuthentication(authprovider); - fail(); - } catch (BadCredentialsException e) { - UaaUser user = userDatabase.retrieveUserById(scimUser.getId()); - assertFalse(user.isVerified()); - } - RequestContextHolder.resetRequestAttributes(); - } - - private ScimUser getInvitedUser() { - ScimUser invitedUser = new ScimUser(null, "marissa.invited@test.org", "Marissa", "Bloggs"); - invitedUser.setVerified(false); - invitedUser.setPrimaryEmail("marissa.invited@test.org"); - invitedUser.setOrigin(OriginKeys.UAA); - ScimUser scimUser = userProvisioning.createUser(invitedUser, "getInvitedUser-password", identityZoneManager.getCurrentIdentityZone().getId()); - - RequestAttributes attributes = new ServletRequestAttributes(new MockHttpServletRequest()); - attributes.setAttribute("IS_INVITE_ACCEPTANCE", true, RequestAttributes.SCOPE_SESSION); - attributes.setAttribute("user_id", scimUser.getId(), RequestAttributes.SCOPE_SESSION); - RequestContextHolder.setRequestAttributes(attributes); - - return scimUser; - } - - @Test - void update_existingUser_if_attributes_different() throws Exception { - try { - userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - fail("user should not exist"); - } catch (UsernameNotFoundException ignored) { - } - getAuthentication(authprovider); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - assertFalse(user.isVerified()); - Map attributeMappings = new HashMap<>(); - attributeMappings.put("given_name", "firstName"); - attributeMappings.put("email", "emailAddress"); - attributeMappings.put("email_verified", "emailVerified"); - providerDefinition.setAttributeMappings(attributeMappings); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - SAMLCredential credential = getUserCredential("marissa-saml", "Marissa-changed", null, "marissa.bloggs@change.org", null); - when(consumer.processAuthenticationResponse(any())).thenReturn(credential); - getAuthentication(authprovider); - - user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - assertEquals("Marissa-changed", user.getGivenName()); - assertEquals("marissa.bloggs@change.org", user.getEmail()); - assertFalse(user.isVerified()); - - credential = getUserCredential("marissa-saml", "Marissa-changed", null, "marissa.bloggs@change.org", null, true); - when(consumer.processAuthenticationResponse(any())).thenReturn(credential); - getAuthentication(authprovider); - - user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - assertEquals("Marissa-changed", user.getGivenName()); - assertEquals("marissa.bloggs@change.org", user.getEmail()); - assertTrue(user.isVerified()); - } - - @Test - void update_existingUser_if_username_different() { - Map attributeMappings = new HashMap<>(); - attributeMappings.put("given_name", "firstName"); - attributeMappings.put("family_name", "lastName"); - attributeMappings.put("email", "emailAddress"); - attributeMappings.put("phone_number", "phone"); - providerDefinition.setAttributeMappings(attributeMappings); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - getAuthentication(authprovider); - - UaaUser originalUser = userDatabase.retrieveUserByEmail("marissa.bloggs@test.com", OriginKeys.SAML); - assertNotNull(originalUser); - assertEquals("marissa-saml", originalUser.getUsername()); - - LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); - attributes.add(GIVEN_NAME_ATTRIBUTE_NAME, "Marissa"); - attributes.add(FAMILY_NAME_ATTRIBUTE_NAME, "Bloggs"); - attributes.add(EMAIL_ATTRIBUTE_NAME, "marissa.bloggs@test.com"); - attributes.add(PHONE_NUMBER_ATTRIBUTE_NAME, "1234567890"); - - UaaPrincipal samlPrincipal = new UaaPrincipal(OriginKeys.NotANumber, "marissa-saml-changed", "marissa.bloggs@test.com", OriginKeys.SAML, "marissa-saml-changed", identityZoneManager.getCurrentIdentityZone().getId()); - UaaUser user = authprovider.createIfMissing(samlPrincipal, false, new ArrayList(), attributes); - - assertNotNull(user); - assertEquals("marissa-saml-changed", user.getUsername()); - } - - @Test - void dont_update_existingUser_if_attributes_areTheSame() { - getAuthentication(authprovider); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - - getAuthentication(authprovider); - UaaUser existingUser = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - - assertEquals(existingUser.getModified(), user.getModified()); - } - - @Test - void have_attributes_changed() { - getAuthentication(authprovider); - UaaUser existing = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - UaaUser modified = new UaaUser(new UaaUserPrototype(existing)); - assertFalse(authprovider.haveUserAttributesChanged(existing, modified), "Nothing modified"); - modified = new UaaUser(new UaaUserPrototype(existing).withEmail("other-email")); - assertTrue(authprovider.haveUserAttributesChanged(existing, modified), "Email modified"); - modified = new UaaUser(new UaaUserPrototype(existing).withPhoneNumber("other-phone")); - assertTrue(authprovider.haveUserAttributesChanged(existing, modified), "Phone number modified"); - modified = new UaaUser(new UaaUserPrototype(existing).withVerified(!existing.isVerified())); - assertTrue(authprovider.haveUserAttributesChanged(existing, modified), "Verified email modified"); - modified = new UaaUser(new UaaUserPrototype(existing).withGivenName("other-given")); - assertTrue(authprovider.haveUserAttributesChanged(existing, modified), "First name modified"); - modified = new UaaUser(new UaaUserPrototype(existing).withFamilyName("other-family")); - assertTrue(authprovider.haveUserAttributesChanged(existing, modified), "Last name modified"); - } - - @Test - void shadowAccount_createdWith_MappedUserAttributes() { - Map attributeMappings = new HashMap<>(); - attributeMappings.put("given_name", "firstName"); - attributeMappings.put("family_name", "lastName"); - attributeMappings.put("email", "emailAddress"); - attributeMappings.put("phone_number", "phone"); - providerDefinition.setAttributeMappings(attributeMappings); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - getAuthentication(authprovider); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - assertEquals("Marissa", user.getGivenName()); - assertEquals("Bloggs", user.getFamilyName()); - assertEquals("marissa.bloggs@test.com", user.getEmail()); - assertEquals("1234567890", user.getPhoneNumber()); - } - - @Test - void custom_user_attributes_stored_if_configured() { - Map attributeMappings = new HashMap<>(); - attributeMappings.put("given_name", "firstName"); - attributeMappings.put("family_name", "lastName"); - attributeMappings.put("email", "emailAddress"); - attributeMappings.put("phone_number", "phone"); - attributeMappings.put(USER_ATTRIBUTE_PREFIX + "secondary_email", "emailAddress"); - providerDefinition.setAttributeMappings(attributeMappings); - providerDefinition.setStoreCustomAttributes(false); - provider.setConfig(providerDefinition); - provider = providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - UaaAuthentication authentication = getAuthentication(authprovider); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - assertEquals("Marissa", user.getGivenName()); - assertEquals("Bloggs", user.getFamilyName()); - assertEquals("marissa.bloggs@test.com", user.getEmail()); - assertEquals("1234567890", user.getPhoneNumber()); - assertEquals("marissa.bloggs@test.com", authentication.getUserAttributes().getFirst("secondary_email")); - - UserInfo userInfo = userDatabase.getUserInfo(user.getId()); - assertNull(userInfo); - - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - providerDefinition.addWhiteListedGroup(SAML_ADMIN); - providerDefinition.setStoreCustomAttributes(true); - provider.setConfig(providerDefinition); - provider = providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - authentication = getAuthentication(authprovider); - assertEquals("marissa.bloggs@test.com", authentication.getUserAttributes().getFirst("secondary_email")); - userInfo = userDatabase.getUserInfo(user.getId()); - assertNotNull(userInfo); - assertEquals("marissa.bloggs@test.com", userInfo.getUserAttributes().getFirst("secondary_email")); - assertNotNull(userInfo.getRoles()); - assertEquals(1, userInfo.getRoles().size()); - assertEquals(SAML_ADMIN, userInfo.getRoles().get(0)); - } - - @Test - void authnContext_isvalidated_fail() { - providerDefinition.setAuthnContext(Arrays.asList("some-context", "another-context")); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - try { - getAuthentication(authprovider); - fail("Expected authentication to throw BadCredentialsException"); - } catch (BadCredentialsException ignored) { - - } - } - - @Test - void authnContext_isvalidated_good() { - providerDefinition.setAuthnContext(Collections.singletonList(AuthnContext.PASSWORD_AUTHN_CTX)); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - try { - getAuthentication(authprovider); - } catch (BadCredentialsException ex) { - fail("Expected authentication to succeed"); - } - } - - @Test - void shadowAccountNotCreated_givenShadowAccountCreationDisabled() { - Map attributeMappings = new HashMap<>(); - attributeMappings.put("given_name", "firstName"); - attributeMappings.put("family_name", "lastName"); - attributeMappings.put("email", "emailAddress"); - attributeMappings.put("phone_number", "phone"); - providerDefinition.setAttributeMappings(attributeMappings); - providerDefinition.setAddShadowUserOnLogin(false); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - try { - getAuthentication(authprovider); - fail("Expected authentication to throw LoginSAMLException"); - } catch (LoginSAMLException ignored) { - - } - - try { - userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - fail("Expected user not to exist in database"); - } catch (UsernameNotFoundException ignored) { - - } - } - - @Test - void should_NotCreateShadowAccount_AndInstead_UpdateExistingUserUsername_if_userWithEmailExists() { - Map attributeMappings = new HashMap<>(); - attributeMappings.put("email", "emailAddress"); - providerDefinition.setAttributeMappings(attributeMappings); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - ScimUser createdUser = createSamlUser("marissa.bloggs@test.com", identityZoneManager.getCurrentIdentityZone().getId(), userProvisioning); - - getAuthentication(authprovider); - - UaaUser uaaUser = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - assertEquals(createdUser.getId(), uaaUser.getId()); - assertEquals("marissa-saml", uaaUser.getUsername()); - } - - @Test - void error_when_multipleUsers_with_sameEmail() { - Map attributeMappings = new HashMap<>(); - attributeMappings.put("email", "emailAddress"); - providerDefinition.setAttributeMappings(attributeMappings); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - createSamlUser("marissa.bloggs@test.com", identityZoneManager.getCurrentIdentityZone().getId(), userProvisioning); - createSamlUser("marissa.bloggs", identityZoneManager.getCurrentIdentityZone().getId(), userProvisioning); - - assertThrows(IncorrectResultSizeDataAccessException.class, () -> getAuthentication(authprovider)); - } - - @Test - void shadowUser_GetsCreatedWithDefaultValues_IfAttributeNotMapped() { - Map attributeMappings = new HashMap<>(); - attributeMappings.put("surname", "lastName"); - attributeMappings.put("email", "emailAddress"); - providerDefinition.setAttributeMappings(attributeMappings); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - UaaAuthentication authentication = getAuthentication(authprovider); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - assertEquals("marissa.bloggs", user.getGivenName()); - assertEquals("test.com", user.getFamilyName()); - assertEquals("marissa.bloggs@test.com", user.getEmail()); - assertEquals(0, authentication.getUserAttributes().size(), "No custom attributes have been mapped"); - } - - @Test - void user_authentication_contains_custom_attributes() { - String COST_CENTERS = COST_CENTER + "s"; - String MANAGERS = MANAGER + "s"; - - Map attributeMappings = new HashMap<>(); - - attributeMappings.put(USER_ATTRIBUTE_PREFIX + COST_CENTERS, COST_CENTER); - attributeMappings.put(USER_ATTRIBUTE_PREFIX + MANAGERS, MANAGER); - - providerDefinition.setAttributeMappings(attributeMappings); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - - UaaAuthentication authentication = getAuthentication(authprovider); - - assertEquals(2, authentication.getUserAttributes().size(), "Expected two user attributes"); - assertNotNull(authentication.getUserAttributes().get(COST_CENTERS), "Expected cost center attribute"); - assertEquals(DENVER_CO, authentication.getUserAttributes().getFirst(COST_CENTERS)); - - assertNotNull(authentication.getUserAttributes().get(MANAGERS), "Expected manager attribute"); - assertEquals(2, authentication.getUserAttributes().get(MANAGERS).size(), "Expected 2 manager attribute values"); - assertThat(authentication.getUserAttributes().get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); - } - - @Test - void getUserByDefaultUsesTheAvailableData() { - UaaPrincipal principal = new UaaPrincipal( - UUID.randomUUID().toString(), - "user", - "user@example.com", - OriginKeys.SAML, - "user", - identityZoneManager.getCurrentIdentityZone().getId() - ); - LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); - attributes.add(EMAIL_ATTRIBUTE_NAME, "user@example.com"); - attributes.add(PHONE_NUMBER_ATTRIBUTE_NAME, "(415) 555-0111"); - attributes.add(GIVEN_NAME_ATTRIBUTE_NAME, "Jane"); - attributes.add(FAMILY_NAME_ATTRIBUTE_NAME, "Doe"); - attributes.add(EMAIL_VERIFIED_ATTRIBUTE_NAME, "true"); - - UaaUser user = authprovider.getUser(principal, attributes); - assertThat(user, is( - aUaaUser() - .withUsername("user") - .withEmail("user@example.com") - .withPhoneNumber("(415) 555-0111") - .withPassword("") - .withGivenName("Jane") - .withFamilyName("Doe") - .withAuthorities(emptyIterable()) - .withVerified(true) - .withOrigin(OriginKeys.SAML) - .withExternalId("user") - .withZoneId(identityZoneManager.getCurrentIdentityZoneId()) - )); - } - - @Test - void getUserWithoutOriginSuppliesDefaultsToLoginServer() { - UaaPrincipal principal = new UaaPrincipal( - UUID.randomUUID().toString(), - "user", - "user@example.com", - null, - "user", - identityZoneManager.getCurrentIdentityZone().getId() - ); - - LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); - UaaUser user = authprovider.getUser(principal, attributes); - assertThat(user, is(aUaaUser().withOrigin(OriginKeys.LOGIN_SERVER))); - } - - @Test - void getUserWithoutVerifiedDefaultsToFalse() { - UaaPrincipal principal = new UaaPrincipal( - UUID.randomUUID().toString(), - "user", - "user@example.com", - null, - "user", - identityZoneManager.getCurrentIdentityZone().getId() - ); - - LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); - UaaUser user = authprovider.getUser(principal, attributes); - assertThat(user, is(aUaaUser().withVerified(false))); - } - - @Test - void throwsIfUserNameAndEmailAreMissing() { - UaaPrincipal principal = new UaaPrincipal( - UUID.randomUUID().toString(), - null, - "user@example.com", - null, - "user", - identityZoneManager.getCurrentIdentityZone().getId() - ); - - LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); - - assertThrowsWithMessageThat( - BadCredentialsException.class, - () -> authprovider.getUser(principal, attributes), - is("Cannot determine username from credentials supplied") - ); - } - - private static ScimUser createSamlUser(String username, String zoneId, ScimUserProvisioning userProvisioning) { - ScimUser user = new ScimUser("", username, "Marissa", "Bloggs"); - user.setPrimaryEmail("marissa.bloggs@test.com"); - user.setOrigin(OriginKeys.SAML); - return userProvisioning.createUser(user, "", zoneId); - } - - private static UaaAuthentication getAuthentication(LoginSamlAuthenticationProvider authprovider) { - SAMLAuthenticationToken authentication1 = mockSamlAuthentication(); - Authentication authentication = authprovider.authenticate(authentication1); - assertNotNull(authentication, "Authentication should exist"); - assertTrue(authentication instanceof UaaAuthentication, "Authentication should be UaaAuthentication"); - return (UaaAuthentication) authentication; - } - - private static SAMLAuthenticationToken mockSamlAuthentication() { - ExtendedMetadata metadata = mock(ExtendedMetadata.class); - when(metadata.getAlias()).thenReturn(OriginKeys.SAML); - SAMLMessageContext contxt = mock(SAMLMessageContext.class); - - when(contxt.getPeerExtendedMetadata()).thenReturn(metadata); - when(contxt.getCommunicationProfileId()).thenReturn(SAMLConstants.SAML2_WEBSSO_PROFILE_URI); - return new SAMLAuthenticationToken(contxt); - } - - public static class CreateUserPublisher implements ApplicationEventPublisher { - final ScimUserBootstrap bootstrap; - final List events = new ArrayList<>(); - - CreateUserPublisher(ScimUserBootstrap bootstrap) { - this.bootstrap = bootstrap; - } - - - @Override - public void publishEvent(ApplicationEvent event) { - events.add(event); - if (event instanceof AuthEvent) { - bootstrap.onApplicationEvent((AuthEvent) event); - } - } - - @Override - public void publishEvent(Object event) { - throw new UnsupportedOperationException("not implemented"); - } - } - - private static final String IDP_META_DATA = getResourceAsString(LoginSamlAuthenticationProviderTests.class, "IDP_META_DATA.xml"); - - private static List getAttributes(Map values) { - List result = new LinkedList<>(); - for (Map.Entry entry : values.entrySet()) { - result.addAll(getAttributes(entry.getKey(), entry.getValue())); - } - return result; - } - - private static List getAttributes(final String name, Object value) { - Attribute attribute = mock(Attribute.class); - when(attribute.getName()).thenReturn(name); - when(attribute.getFriendlyName()).thenReturn(name); - - List xmlObjects = new LinkedList<>(); - if ("XSURI".equals(name)) { - XSURIImpl impl = new AttributedURIImpl("", "", ""); - impl.setValue((String) value); - xmlObjects.add(impl); - } else if ("XSAny".equals(name)) { - XSAnyImpl impl = new XSAnyImpl("", "", "") { - }; - impl.setTextContent((String) value); - xmlObjects.add(impl); - } else if ("XSQName".equals(name)) { - XSQNameImpl impl = new XSQNameImpl("", "", "") { - }; - impl.setValue(new QName("", (String) value)); - xmlObjects.add(impl); - } else if ("XSInteger".equals(name)) { - XSIntegerImpl impl = new XSIntegerImpl("", "", "") { - }; - impl.setValue((Integer) value); - xmlObjects.add(impl); - } else if ("XSBoolean".equals(name)) { - XSBooleanImpl impl = new XSBooleanImpl("", "", "") { - }; - impl.setValue(new XSBooleanValue((Boolean) value, false)); - xmlObjects.add(impl); - } else if ("XSDateTime".equals(name)) { - XSDateTimeImpl impl = new XSDateTimeImpl("", "", "") { - }; - impl.setValue((DateTime) value); - xmlObjects.add(impl); - } else if ("XSBase64Binary".equals(name)) { - XSBase64BinaryImpl impl = new XSBase64BinaryImpl("", "", "") { - }; - impl.setValue((String) value); - xmlObjects.add(impl); - } else if (value instanceof List) { - for (String s : (List) value) { - if (SAML_USER.equals(s)) { - XSAnyImpl impl = new XSAnyImpl("", "", "") { - }; - impl.setTextContent(s); - xmlObjects.add(impl); - } else { - AttributedStringImpl impl = new AttributedStringImpl("", "", ""); - impl.setValue(s); - xmlObjects.add(impl); - } - } - } else if (value instanceof Boolean) { - XSBoolean impl = new XSBooleanBuilder().buildObject("", "", ""); - impl.setValue(new XSBooleanValue((Boolean) value, false)); - xmlObjects.add(impl); - } else { - AttributedStringImpl impl = new AttributedStringImpl("", "", ""); - impl.setValue((String) value); - xmlObjects.add(impl); - } - when(attribute.getAttributeValues()).thenReturn(xmlObjects); - return Collections.singletonList(attribute); - } - - private static SAMLCredential getUserCredential(String username, String firstName, String lastName, String emailAddress, String phoneNumber) { - return getUserCredential(username, - firstName, - lastName, - emailAddress, - phoneNumber, - null); - } - - private static SAMLCredential getUserCredential(String username, - String firstName, - String lastName, - String emailAddress, - String phoneNumber, - Boolean emailVerified) { - NameID usernameID = mock(NameID.class); - when(usernameID.getValue()).thenReturn(username); - - Map attributes = new HashMap<>(); - attributes.put("firstName", firstName); - attributes.put("lastName", lastName); - attributes.put("emailAddress", emailAddress); - attributes.put("phone", phoneNumber); - attributes.put("groups", Arrays.asList(SAML_USER, SAML_ADMIN, SAML_NOT_MAPPED)); - attributes.put("2ndgroups", Collections.singletonList(SAML_TEST)); - attributes.put(COST_CENTER, Collections.singletonList(DENVER_CO)); - attributes.put(MANAGER, Arrays.asList(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); - if (emailVerified != null) { - attributes.put("emailVerified", emailVerified); - } - - //test different types - attributes.put("XSURI", "http://localhost:8080/someuri"); - attributes.put("XSAny", "XSAnyValue"); - attributes.put("XSQName", "XSQNameValue"); - attributes.put("XSInteger", 3); - attributes.put("XSBoolean", Boolean.TRUE); - attributes.put("XSDateTime", new DateTime(0)); - attributes.put("XSBase64Binary", "00001111"); - - - AuthnContextClassRef contextClassRef = mock(AuthnContextClassRef.class); - when(contextClassRef.getAuthnContextClassRef()).thenReturn(AuthnContext.PASSWORD_AUTHN_CTX); - - AuthnContext authenticationContext = mock(AuthnContext.class); - when(authenticationContext.getAuthnContextClassRef()).thenReturn(contextClassRef); - - AuthnStatement statement = mock(AuthnStatement.class); - when(statement.getAuthnContext()).thenReturn(authenticationContext); - - Assertion authenticationAssertion = mock(Assertion.class); - when(authenticationAssertion.getAuthnStatements()).thenReturn(Collections.singletonList(statement)); - - return new SAMLCredential( - usernameID, - authenticationAssertion, - "remoteEntityID", - getAttributes(attributes), - "localEntityID"); - } -} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlDiscoveryTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlDiscoveryTest.java deleted file mode 100644 index 050033b89c9..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlDiscoveryTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.junit.jupiter.api.Test; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class LoginSamlDiscoveryTest { - - @Test - void doFilter() throws ServletException, IOException { - LoginSamlDiscovery samlDiscovery = new LoginSamlDiscovery(); - HttpServletResponse servletResponse = mock(HttpServletResponse.class); - HttpServletRequest servletRequest = mock(HttpServletRequest.class); - HttpSession session = mock(HttpSession.class); - FilterChain chain = mock(FilterChain.class); - when(servletRequest.getSession(true)).thenReturn(session); - samlDiscovery.doFilter(servletRequest, servletResponse, chain); - assertNotNull(servletRequest); - } -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUaaTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUaaTests.java new file mode 100644 index 00000000000..bcca6c24f19 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUaaTests.java @@ -0,0 +1,865 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.annotations.WithDatabaseContext; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.db.DatabaseUrlModifier; +import org.cloudfoundry.identity.uaa.db.Vendor; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.LimitSqlAdapter; +import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; +import org.cloudfoundry.identity.uaa.scim.ScimGroup; +import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserAliasHandler; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimUserBootstrap; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupExternalMembershipManager; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupMembershipManager; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; +import org.cloudfoundry.identity.uaa.scim.services.ScimUserService; +import org.cloudfoundry.identity.uaa.test.TestUtils; +import org.cloudfoundry.identity.uaa.user.JdbcUaaUserDatabase; +import org.cloudfoundry.identity.uaa.user.UaaAuthority; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UserInfo; +import org.cloudfoundry.identity.uaa.util.TimeService; +import org.cloudfoundry.identity.uaa.util.TimeServiceImpl; +import org.cloudfoundry.identity.uaa.util.beans.DbUtils; +import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.opensaml.core.config.InitializationException; +import org.opensaml.core.config.InitializationService; +import org.opensaml.saml.saml2.core.AuthnContext; +import org.opensaml.saml.saml2.core.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.context.request.ServletWebRequest; + +import javax.servlet.ServletContext; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.authenticationToken; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.mockedStoredAuthenticationRequest; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.registration; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.responseWithAssertions; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.token; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.verifying; +import static org.cloudfoundry.identity.uaa.provider.saml.TestOpenSamlObjects.attributeStatements; +import static org.cloudfoundry.identity.uaa.test.ModelTestUtils.getResourceAsString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@WithDatabaseContext +class OpenSaml4AuthenticationProviderUaaTests { + + private static final String SAML_USER = "saml.user"; + private static final String SAML_ADMIN = "saml.admin"; + private static final String SAML_TEST = "saml.test"; + private static final String SAML_NOT_MAPPED = "saml.unmapped"; + private static final String SAML_NOT_ASSERTED = "saml.unasserted"; + private static final String UAA_USER = "uaa.user"; + private static final String UAA_SAML_USER = "uaa.saml.user"; + private static final String UAA_SAML_ADMIN = "uaa.saml.admin"; + private static final String UAA_SAML_TEST = "uaa.saml.test"; + private static final String COST_CENTER = "costCenter"; + private static final String DENVER_CO = "Denver,CO"; + private static final String MANAGER = "manager"; + private static final String JOHN_THE_SLOTH = "John the Sloth"; + private static final String KARI_THE_ANT_EATER = "Kari the Ant Eater"; + private static final String IDP_META_DATA = getResourceAsString( + OpenSaml4AuthenticationProviderUaaTests.class, "IDP_META_DATA.xml"); + + private static final String TEST_EMAIL = "john.doe@example.com"; + private static final String TEST_USERNAME = "test@saml.user"; + private static final String TEST_PHONE_NUMBER = "123-456-7890"; + + @Autowired + NamedParameterJdbcTemplate namedJdbcTemplate; + + private JdbcIdentityProviderProvisioning providerProvisioning; + private CreateUserPublisher publisher; + private JdbcUaaUserDatabase userDatabase; + private SamlUaaAuthenticationUserManager samlUaaAuthenticationUserManager; + private AuthenticationProvider authprovider; + private SamlIdentityProviderDefinition providerDefinition; + private IdentityProvider provider; + private ScimUserProvisioning userProvisioning; + private JdbcScimGroupExternalMembershipManager externalManager; + private ScimGroup uaaSamlUser; + private ScimGroup uaaSamlAdmin; + private IdentityZoneManager identityZoneManager; + private SamlAuthenticationFilterConfig samlAuthenticationFilterConfig; + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private LimitSqlAdapter limitSqlAdapter; + @Autowired + private PasswordEncoder passwordEncoder; + + private static ScimUser createSamlUser(String username, String zoneId, + ScimUserProvisioning userProvisioning) { + ScimUser user = new ScimUser("", username, "Marissa", "Bloggs"); + user.setPrimaryEmail(TEST_EMAIL); + user.setOrigin(OriginKeys.SAML); + return userProvisioning.createUser(user, "", zoneId); + } + + private UaaAuthentication authenticate() { + return authenticate(authenticationToken()); + } + + private UaaAuthentication authenticate(Authentication inAuthentication) { + Authentication authentication = authprovider.authenticate(inAuthentication); + assertThat(authentication).isInstanceOf(UaaAuthentication.class); + return (UaaAuthentication) authentication; + } + + @BeforeEach + void configureProvider() throws SecurityException, SQLException, InitializationException { + identityZoneManager = new IdentityZoneManagerImpl(); + RequestContextHolder.resetRequestAttributes(); + MockHttpServletRequest request = new MockHttpServletRequest(mock(ServletContext.class)); + MockHttpServletResponse response = new MockHttpServletResponse(); + ServletWebRequest servletWebRequest = new ServletWebRequest(request, response); + RequestContextHolder.setRequestAttributes(servletWebRequest); + DbUtils dbUtils = new DbUtils(); + + InitializationService.initialize(); + + ScimGroupProvisioning groupProvisioning = new JdbcScimGroupProvisioning( + namedJdbcTemplate, new JdbcPagingListFactory(namedJdbcTemplate, limitSqlAdapter), + dbUtils); + identityZoneManager.getCurrentIdentityZone().getConfig().getUserConfig() + .setDefaultGroups(Collections.singletonList(UAA_USER)); + identityZoneManager.getCurrentIdentityZone().getConfig().getUserConfig() + .setAllowedGroups(Arrays.asList(UAA_USER, SAML_USER, + SAML_ADMIN, SAML_TEST, SAML_NOT_MAPPED, UAA_SAML_USER, UAA_SAML_ADMIN, + UAA_SAML_TEST)); + groupProvisioning.createOrGet( + new ScimGroup(null, UAA_USER, identityZoneManager.getCurrentIdentityZone().getId()), + identityZoneManager.getCurrentIdentityZone().getId()); + + userProvisioning = new JdbcScimUserProvisioning(namedJdbcTemplate, + new JdbcPagingListFactory(namedJdbcTemplate, limitSqlAdapter), passwordEncoder, + new IdentityZoneManagerImpl(), new JdbcIdentityZoneProvisioning(jdbcTemplate), + new SimpleSearchQueryConverter(), new SimpleSearchQueryConverter(), new TimeServiceImpl(), + true); + + uaaSamlUser = groupProvisioning.create( + new ScimGroup(null, UAA_SAML_USER, IdentityZone.getUaaZoneId()), + identityZoneManager.getCurrentIdentityZone().getId()); + uaaSamlAdmin = groupProvisioning.create( + new ScimGroup(null, UAA_SAML_ADMIN, IdentityZone.getUaaZoneId()), + identityZoneManager.getCurrentIdentityZone().getId()); + ScimGroup uaaSamlTest = groupProvisioning.create( + new ScimGroup(null, UAA_SAML_TEST, IdentityZone.getUaaZoneId()), + identityZoneManager.getCurrentIdentityZone().getId()); + + JdbcScimGroupMembershipManager membershipManager = new JdbcScimGroupMembershipManager( + jdbcTemplate, new TimeServiceImpl(), userProvisioning, null, dbUtils); + membershipManager.setScimGroupProvisioning(groupProvisioning); + + final ScimUserAliasHandler aliasHandler = mock(ScimUserAliasHandler.class); + when(aliasHandler.aliasPropertiesAreValid(any(), any())).thenReturn(true); + + final ScimUserService scimUserService = new ScimUserService( + aliasHandler, + userProvisioning, + identityZoneManager, + null, // not required since alias is disabled + false + ); + ScimUserBootstrap bootstrap = new ScimUserBootstrap( + userProvisioning, + scimUserService, + groupProvisioning, + membershipManager, + Collections.emptyList(), + false, + Collections.emptyList(), + false + ); + + externalManager = new JdbcScimGroupExternalMembershipManager(jdbcTemplate, dbUtils); + externalManager.setScimGroupProvisioning(groupProvisioning); + externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); + externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); + externalManager.mapExternalGroup(uaaSamlTest.getId(), SAML_TEST, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); + + TimeService timeService = mock(TimeService.class); + DatabaseUrlModifier databaseUrlModifier = mock(DatabaseUrlModifier.class); + when(databaseUrlModifier.getDatabaseType()).thenReturn(Vendor.unknown); + userDatabase = new JdbcUaaUserDatabase(jdbcTemplate, timeService, false, + identityZoneManager, + databaseUrlModifier, new DbUtils()); + providerProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); + publisher = new CreateUserPublisher(bootstrap); + + samlAuthenticationFilterConfig = new SamlAuthenticationFilterConfig(); + samlUaaAuthenticationUserManager = samlAuthenticationFilterConfig.samlUaaAuthenticationUserManager(userDatabase, publisher); + authprovider = samlAuthenticationFilterConfig.samlAuthenticationProvider( + identityZoneManager, providerProvisioning, externalManager, samlUaaAuthenticationUserManager, publisher, new SamlConfigProps()); + + providerDefinition = new SamlIdentityProviderDefinition(); + providerDefinition.setMetaDataLocation(IDP_META_DATA.formatted(OriginKeys.SAML)); + providerDefinition.setIdpEntityAlias(OriginKeys.SAML); + + IdentityProvider createProvider = new IdentityProvider<>(); + createProvider.setIdentityZoneId(IdentityZone.getUaaZoneId()); + createProvider.setOriginKey(OriginKeys.SAML); + createProvider.setName("saml-test"); + createProvider.setActive(true); + createProvider.setType(OriginKeys.SAML); + createProvider.setConfig(providerDefinition); + provider = providerProvisioning.create(createProvider, identityZoneManager.getCurrentIdentityZone().getId()); + } + + @AfterEach + void tearDown(@Autowired ApplicationContext applicationContext) throws SQLException { + TestUtils.restoreToDefaults(applicationContext); + RequestContextHolder.resetRequestAttributes(); + } + + @Test + void testAuthenticateSimple() { + assertThat(authenticate()).isNotNull(); + } + + @ParameterizedTest(name = "#{index} relayRedirectRejectsNonUrls - {0}") + @ValueSource(strings = {"test", "www.google.com"}) + @NullSource + @EmptySource + void relayRedirectRejectsNonUrls(String url) { + Saml2AuthenticationToken authenticationToken = authenticationToken(); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = authenticationToken.getAuthenticationRequest(); + when(mockAuthenticationRequest.getRelayState()).thenReturn(url); + authenticate(authenticationToken); + verify(mockAuthenticationRequest, times(1)).getRelayState(); + + assertThat(RequestContextHolder.currentRequestAttributes() + .getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, + RequestAttributes.SCOPE_REQUEST)) + .isNull(); + } + + @Test + void relayRedirectIsSetForUrl() { + String redirectUrl = "https://www.cloudfoundry.org"; + + Saml2AuthenticationToken authenticationToken = authenticationToken(); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = authenticationToken.getAuthenticationRequest(); + when(mockAuthenticationRequest.getRelayState()).thenReturn(redirectUrl); + UaaAuthentication uaaAuthentication = authenticate(authenticationToken); + + assertThat(RequestContextHolder.currentRequestAttributes() + .getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, + RequestAttributes.SCOPE_REQUEST)) + .isEqualTo(redirectUrl); + assertThat(uaaAuthentication.getAuthContextClassRef()).contains(AuthnContext.PASSWORD_AUTHN_CTX); + } + + @Test + void testAuthenticationEvents() { + authenticate(); + assertThat(publisher.events).hasSize(3); + assertThat(publisher.events.get(2)).isInstanceOf(IdentityProviderAuthenticationSuccessEvent.class); + } + + @Test + void samlAuthenticationContainsAcr() { + Saml2AuthenticationToken mockAuthenticationToken = authenticationToken(); + UaaAuthentication uaaAuthentication = authenticate(mockAuthenticationToken); + assertThat(uaaAuthentication.getAuthContextClassRef()).contains(AuthnContext.PASSWORD_AUTHN_CTX); + verify(mockAuthenticationToken.getAuthenticationRequest(), times(1)).getRelayState(); + assertThat(RequestContextHolder.currentRequestAttributes() + .getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, + RequestAttributes.SCOPE_REQUEST)) + .isNull(); + } + + @Test + void multipleGroupAttributesMapping() { + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, + Arrays.asList("2ndgroups", "groups")); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getAuthorities()). + containsExactlyInAnyOrder( + new SimpleGrantedAuthority(UAA_SAML_ADMIN), + new SimpleGrantedAuthority(UAA_SAML_USER), + new SimpleGrantedAuthority(UAA_SAML_TEST), + new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) + ); + } + + @Test + void authenticationContainsAmr() { + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getAuthenticationMethods()).contains("ext"); + } + + @Test + void externalGroupsAsScopes() { + providerDefinition.setGroupMappingMode(SamlIdentityProviderDefinition.ExternalGroupMappingMode.AS_SCOPES); + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, Arrays.asList("2ndgroups", "groups")); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getAuthorities()).containsExactlyInAnyOrder( + new SimpleGrantedAuthority(SAML_ADMIN), + new SimpleGrantedAuthority(SAML_USER), + new SimpleGrantedAuthority(SAML_TEST), + new SimpleGrantedAuthority(SAML_NOT_MAPPED), + new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) + ); + } + + @Test + void groupMapping() { + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getAuthorities()).containsExactlyInAnyOrder( + new SimpleGrantedAuthority(UAA_SAML_ADMIN), + new SimpleGrantedAuthority(UAA_SAML_USER), + new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) + ); + } + + @Test + void nonStringAttributes() { + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSURI", "XSURI"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSAny", "XSAny"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSQName", "XSQName"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSInteger", "XSInteger"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSBoolean", "XSBoolean"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSDateTime", "XSDateTime"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSBase64Binary", "XSBase64Binary"); + + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + UaaAuthentication authentication = authenticate(); + + assertThat(authentication.getUserAttributes()) + .containsEntry("XSURI", List.of("http://localhost:8080/someuri")) + .containsEntry("XSAny", List.of("XSAnyValue")) + .containsEntry("XSQName", List.of("XSQNameValue")) + .containsEntry("XSInteger", List.of("3")) + .containsEntry("XSBoolean", List.of("true")) + .containsEntry("XSDateTime", List.of("1970-01-01T00:00:00Z")) + .containsEntry("XSBase64Binary", List.of("00001111")); + } + + @Test + void externalGroupNotMappedToScope() { + externalManager.unmapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); + externalManager.unmapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getAuthorities()).hasSize(1).doesNotContainAnyElementsOf(List.of( + new SimpleGrantedAuthority(UAA_SAML_ADMIN), + new SimpleGrantedAuthority(UAA_SAML_USER)) + ); + } + + @Test + void uaaUserAuthorityGrantedIfNoOtherProvided() { + UaaAuthentication uaaAuthentication = authenticate(); + assertThat(uaaAuthentication.getAuthorities()).containsExactly( + new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) + ); + } + + @Test + void dontAddExternalGroupsToAuthenticationWithoutMatchingWhitelist() { + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + providerDefinition.addWhiteListedGroup(SAML_NOT_ASSERTED); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getExternalGroups()).isEmpty(); + } + + @Test + void add_external_groups_to_authentication_with_empty_whitelist() { + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getExternalGroups()).contains(SAML_USER, SAML_ADMIN, SAML_NOT_MAPPED); + } + + @Test + void addExternalGroupsToAuthenticationWithWhitelist() { + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + providerDefinition.addWhiteListedGroup(SAML_ADMIN); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + UaaAuthentication authentication = authenticate(); + assertEquals(Collections.singleton(SAML_ADMIN), authentication.getExternalGroups()); + } + + @Test + void addExternalGroupsToAuthenticationWithWildcardWhitelist() { + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + providerDefinition.addWhiteListedGroup("saml*"); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getExternalGroups()).containsExactlyInAnyOrder(SAML_USER, SAML_ADMIN, SAML_NOT_MAPPED); + } + + @Test + void updateInvitedUserWhoseUsernameIsNotEmail() { + ScimUser scimUser = getInvitedUser(); + Map userAttributes = getUserAttributes("Marissa-invited", null, "marissa.invited@test.org", null, null); + authenticate(authenticationToken("marissa-invited", attributeStatements(userAttributes))); + + UaaUser user = userDatabase.retrieveUserById(scimUser.getId()); + assertThat(user.isVerified()).isFalse(); + assertThat(user.getUsername()).isEqualTo("marissa-invited"); + assertThat(user.getEmail()).isEqualTo("marissa.invited@test.org"); + + RequestContextHolder.resetRequestAttributes(); + } + + @Test + void invitedUserAuthenticationWhenAuthenticatedEmailDoesNotMatchInvitedEmail() { + Map attributeMappings = new HashMap<>(); + attributeMappings.put("email", "emailAddress"); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + ScimUser scimUser = getInvitedUser(); + Map userAttributes = getUserAttributes("Marissa-invited", null, "different@test.org", null, null); + Saml2AuthenticationToken authenticationToken = authenticationToken("marissa-invited", attributeStatements(userAttributes)); + + assertThatThrownBy(() -> authenticate(authenticationToken)) + .isInstanceOf(Saml2AuthenticationException.class) + .hasMessageContaining("Authenticated email doesn't match invited email"); + + UaaUser user = userDatabase.retrieveUserById(scimUser.getId()); + assertThat(user.isVerified()).isFalse(); + } + + private ScimUser getInvitedUser() { + ScimUser invitedUser = new ScimUser(null, "marissa.invited@test.org", "Marissa", "Bloggs"); + invitedUser.setVerified(false); + invitedUser.setPrimaryEmail("marissa.invited@test.org"); + invitedUser.setOrigin(OriginKeys.UAA); + ScimUser scimUser = userProvisioning.createUser(invitedUser, "getInvitedUser-password", + identityZoneManager.getCurrentIdentityZone().getId()); + + RequestAttributes attributes = new ServletRequestAttributes(new MockHttpServletRequest()); + attributes.setAttribute("IS_INVITE_ACCEPTANCE", true, RequestAttributes.SCOPE_SESSION); + attributes.setAttribute("user_id", scimUser.getId(), RequestAttributes.SCOPE_SESSION); + RequestContextHolder.setRequestAttributes(attributes); + + return scimUser; + } + + private Map getUserAttributes(String firstName, + String lastName, + String emailAddress, + String phoneNumber, + Boolean emailVerified) { + Map attributes = new HashMap<>(); + attributes.put("firstName", firstName); + attributes.put("lastName", lastName); + attributes.put("emailAddress", emailAddress); + attributes.put("phone", phoneNumber); + if (emailVerified != null) { + attributes.put("emailVerified", emailVerified.toString()); + } + + return attributes; + } + + @Test + void updateExistingUserWithDifferentAttributes() throws Exception { + try { + userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + fail("user should not exist"); + } catch (UsernameNotFoundException ignored) { + // expected + } + authenticate(); + + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + assertThat(user).returns("john.doe", UaaUser::getGivenName) + .returns(TEST_EMAIL, UaaUser::getEmail); + + Map attributeMappings = new HashMap<>(); + attributeMappings.put("given_name", "firstName"); + attributeMappings.put("email", "emailAddress"); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + authenticate(); + + user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + assertThat(user).returns("John", UaaUser::getGivenName) + .returns(TEST_EMAIL, UaaUser::getEmail); + } + + @Test + void updateExistingUserWithDifferentUsernameButSameEmail() { + Map attributeMappings = new HashMap<>(); + attributeMappings.put("given_name", "firstName"); + attributeMappings.put("family_name", "lastName"); + attributeMappings.put("email", "emailAddress"); + attributeMappings.put("phone_number", "phone"); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + authenticate(); + + UaaUser originalUser = userDatabase.retrieveUserByEmail(TEST_EMAIL, OriginKeys.SAML); + assertNotNull(originalUser); + assertEquals(TEST_USERNAME, originalUser.getUsername()); + + LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); + attributes.add(GIVEN_NAME_ATTRIBUTE_NAME, "Marissa"); + attributes.add(FAMILY_NAME_ATTRIBUTE_NAME, "Bloggs"); + attributes.add(EMAIL_ATTRIBUTE_NAME, TEST_EMAIL); + attributes.add(PHONE_NUMBER_ATTRIBUTE_NAME, TEST_PHONE_NUMBER); + + UaaPrincipal samlPrincipal = new UaaPrincipal(OriginKeys.NotANumber, + "test-changed@saml.user", TEST_EMAIL, OriginKeys.SAML, TEST_USERNAME, + identityZoneManager.getCurrentIdentityZone().getId()); + + UaaUser user = samlUaaAuthenticationUserManager.createIfMissing(samlPrincipal, false, new ArrayList(), attributes); + + assertNotNull(user); + assertEquals("test-changed@saml.user", user.getUsername()); + } + + @Test + void dontUpdateExistingUserIfAttributesSame() { + authenticate(); + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + + authenticate(); + UaaUser existingUser = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + + assertThat(existingUser.getModified()).isEqualTo(user.getModified()); + } + + @Test + void createShadowAccountWithMappedUserAttributes() { + Map attributeMappings = new HashMap<>(); + attributeMappings.put("given_name", "firstName"); + attributeMappings.put("family_name", "lastName"); + attributeMappings.put("email", "emailAddress"); + attributeMappings.put("phone_number", "phone"); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + authenticate(); + + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + assertThat(user) + .returns("John", UaaUser::getGivenName) + .returns("Doe", UaaUser::getFamilyName) + .returns(TEST_EMAIL, UaaUser::getEmail) + .returns(TEST_PHONE_NUMBER, UaaUser::getPhoneNumber); + } + + @Test + void setStoreCustomAttributesInProviderDefinitionFalse() { + providerDefinition.setStoreCustomAttributes(false); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + authenticate(); + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + UserInfo userInfo = userDatabase.getUserInfo(user.getId()); + assertThat(userInfo).isNull(); + } + + @Test + void setStoreCustomAttributesInProviderDefinitionTrue() { + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "secondary_email", + "secondaryEmail"); + providerDefinition.setStoreCustomAttributes(true); + provider.setConfig(providerDefinition); + provider = providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + authenticate(); + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + UserInfo userInfo = userDatabase.getUserInfo(user.getId()); + assertThat(userInfo).isNotNull(); + assertThat(userInfo.getUserAttributes()) + .hasSize(1) + .containsEntry("secondary_email", List.of("john.doe.secondary@example.com")); + } + + @Test + void setsUserInfoRolesWhenWhiteListIsSet() { + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + providerDefinition.setStoreCustomAttributes(true); + providerDefinition.addWhiteListedGroup(SAML_ADMIN); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + authenticate(); + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + UserInfo userInfo = userDatabase.getUserInfo(user.getId()); + + assertThat(userInfo).isNotNull(); + assertThat(userInfo.getRoles()).containsExactly(SAML_ADMIN); + } + + @Test + void authnContextValidationFails() { + providerDefinition.setAuthnContext(Arrays.asList("some-context", "another-context")); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + assertThatThrownBy(this::authenticate) + .isInstanceOf(Saml2AuthenticationException.class) + .hasCauseExactlyInstanceOf(BadCredentialsException.class) + .hasMessage("Identity Provider did not authenticate with the requested AuthnContext."); + } + + @Test + void authnContextValidationSucceeds() { + providerDefinition.setAuthnContext(Collections.singletonList(AuthnContext.PASSWORD_AUTHN_CTX)); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + assertThat(authenticate()).isNotNull(); + } + + @Test + void shadowAccountNotCreated_givenShadowAccountCreationDisabled() { + Map attributeMappings = new HashMap<>(); + attributeMappings.put("given_name", "firstName"); + attributeMappings.put("family_name", "lastName"); + attributeMappings.put("email", "emailAddress"); + attributeMappings.put("phone_number", "phone"); + providerDefinition.setAttributeMappings(attributeMappings); + providerDefinition.setAddShadowUserOnLogin(false); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + assertThatThrownBy(this::authenticate) + .isInstanceOf(Saml2AuthenticationException.class) + .hasCauseExactlyInstanceOf(SamlLoginException.class) + .hasMessage("SAML user does not exist. You can correct this by creating a shadow user for the SAML user."); + + assertThatThrownBy(() -> userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML)) + .isInstanceOf(UsernameNotFoundException.class) + .hasMessage(TEST_USERNAME); + } + + @Test + void should_NotCreateShadowAccount_AndInstead_UpdateExistingUserUsername_if_userWithEmailExists() { + Map attributeMappings = new HashMap<>(); + attributeMappings.put("email", "emailAddress"); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + ScimUser createdUser = createSamlUser(TEST_EMAIL, + identityZoneManager.getCurrentIdentityZone().getId(), userProvisioning); + + authenticate(); + UaaUser uaaUser = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + assertThat(uaaUser) + .returns(createdUser.getId(), UaaUser::getId) + .returns(TEST_USERNAME, UaaUser::getUsername); + } + + @Test + void authFailsIfMultipleExistingUsersWithSameEmailExist() { + Map attributeMappings = new HashMap<>(); + attributeMappings.put("email", "emailAddress"); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + createSamlUser(TEST_EMAIL, identityZoneManager.getCurrentIdentityZone().getId(), + userProvisioning); + + // get user by username should fail, then attempt get user by email causes exception in JdbcUaaUserDatabase.retrieveUserPrototypeByEmail + createSamlUser("randomUsername", identityZoneManager.getCurrentIdentityZone().getId(), + userProvisioning); + + assertThatThrownBy(() -> authenticate()) + .isInstanceOf(Saml2AuthenticationException.class) + .hasCauseExactlyInstanceOf(IncorrectResultSizeDataAccessException.class) + .hasMessage("Multiple users match email=john.doe@example.com origin=saml"); + } + + @Test + void shadowUserGetsCreatedWithDefaultValuesIfAttributeNotMapped() { + Map attributeMappings = new HashMap<>(); + attributeMappings.put("surname", "lastName"); + attributeMappings.put("email", "emailAddress"); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + UaaAuthentication authentication = authenticate(); + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + + // this splits name fields from email from TestOpenSamlObjects + assertThat(user).returns("john.doe", UaaUser::getGivenName) + .returns("example.com", UaaUser::getFamilyName) + .returns(TEST_EMAIL, UaaUser::getEmail); + assertThat(authentication.getUserAttributes()) + .as("No custom attributes have been mapped") + .isEmpty(); + } + + @Test + void user_authentication_contains_custom_attributes() { + String costCenters = COST_CENTER + "s"; + String managers = MANAGER + "s"; + + Map attributeMappings = new HashMap<>(); + attributeMappings.put(USER_ATTRIBUTE_PREFIX + costCenters, COST_CENTER); + attributeMappings.put(USER_ATTRIBUTE_PREFIX + managers, MANAGER); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getUserAttributes()) + .hasSize(2) + .containsEntry(costCenters, List.of(DENVER_CO)) + .containsEntry(managers, List.of(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); + } + + @Test + void failsWithIncorrectInResponseTo() { + // This test is to ensure that the InResponseTo attribute is being validated + // and that the response is not accepted if it does not match the stored request + Response response = responseWithAssertions(); + response.setInResponseTo("incorrect"); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken authenticationToken = token(response, verifying(registration()), mockAuthenticationRequest); + + assertThatThrownBy(() -> authenticate(authenticationToken)) + .isInstanceOf(Saml2AuthenticationException.class) + .hasMessage("The InResponseTo attribute [incorrect] does not match the ID of the authentication request [SAML2]"); + } + + @Test + void successWithIncorrectInResponseTo() { + // setup the same as failsWithIncorrectInResponseTo, + // but with the disableInResponseToCheck property set to true + // so that the InResponseTo check is skipped + SamlConfigProps samlConfigProps = new SamlConfigProps(); + samlConfigProps.setDisableInResponseToCheck(true); + authprovider = samlAuthenticationFilterConfig.samlAuthenticationProvider( + identityZoneManager, providerProvisioning, externalManager, samlUaaAuthenticationUserManager, publisher, samlConfigProps); + + Response response = responseWithAssertions(); + response.setInResponseTo("incorrect"); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken authenticationToken = token(response, verifying(registration()), mockAuthenticationRequest); + + UaaAuthentication authentication = authenticate(authenticationToken); + assertThat(authentication.isAuthenticated()).isTrue(); + } + + public static class CreateUserPublisher implements ApplicationEventPublisher { + + final ScimUserBootstrap bootstrap; + final List events = new ArrayList<>(); + + CreateUserPublisher(ScimUserBootstrap bootstrap) { + this.bootstrap = bootstrap; + } + + @Override + public void publishEvent(ApplicationEvent event) { + events.add(event); + if (event instanceof AuthEvent) { + bootstrap.onApplicationEvent((AuthEvent) event); + } + } + + @Override + public void publishEvent(Object event) { + throw new UnsupportedOperationException("not implemented"); + } + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUnitTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUnitTests.java new file mode 100644 index 00000000000..4a2ad182b6d --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml4AuthenticationProviderUnitTests.java @@ -0,0 +1,739 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.shibboleth.utilities.java.support.xml.SerializeSupport; +import org.cloudfoundry.identity.uaa.provider.saml.OpenSaml4AuthenticationProvider.ResponseToken; +import org.junit.jupiter.api.Test; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.Marshaller; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.core.xml.schema.XSDateTime; +import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder; +import org.opensaml.saml.common.SignableSAMLObject; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.AttributeValue; +import org.opensaml.saml.saml2.core.AuthnRequest; +import org.opensaml.saml.saml2.core.Conditions; +import org.opensaml.saml.saml2.core.EncryptedAssertion; +import org.opensaml.saml.saml2.core.EncryptedAttribute; +import org.opensaml.saml.saml2.core.EncryptedID; +import org.opensaml.saml.saml2.core.NameID; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.saml.saml2.core.SubjectConfirmation; +import org.opensaml.saml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml.saml2.core.impl.AttributeBuilder; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.Authentication; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2ErrorCodes; +import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +import javax.xml.namespace.QName; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.function.Consumer; + +import static java.util.Map.entry; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * This was copied from Spring Security, Test Classes and modified to work with the modified OpenSaml4AuthenticationProvider. + *

    + * Once we can move to the spring-security version of OpenSaml4AuthenticationProvider, + * this class should be removed, along with OpenSamlDecryptionUtils and OpenSamlVerificationUtils. + *

    + * Modified Tests: + * authenticateWhenAssertionContainsAttributesThenItSucceeds + * deserializeWhenAssertionContainsAttributesThenWorks + *

    + * Tests for {@link OpenSaml4AuthenticationProvider} + * + * @author Filip Hanik + * @author Josh Cummings + */ +class OpenSaml4AuthenticationProviderUnitTests { + + private static final String DESTINATION = "http://localhost:8080/uaa/saml/SSO/alias/integration-saml-entity-id"; + + private static final String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; + + private static final String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp"; + + private final OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); + + @Test + void supportsWhenSaml2AuthenticationTokenThenReturnTrue() { + assertThat(this.provider.supports(Saml2AuthenticationToken.class)) + .withFailMessage(OpenSaml4AuthenticationProvider.class + "should support " + Saml2AuthenticationToken.class) + .isTrue(); + } + + @Test + void supportsWhenNotSaml2AuthenticationTokenThenReturnFalse() { + assertThat(this.provider.supports(Authentication.class)) + .withFailMessage(OpenSaml4AuthenticationProvider.class + "should not support " + Authentication.class) + .isFalse(); + } + + @Test + void authenticateWhenUnknownDataClassThenThrowAuthenticationException() { + Assertion assertion = (Assertion) XMLObjectProviderRegistrySupport.getBuilderFactory() + .getBuilder(Assertion.DEFAULT_ELEMENT_NAME) + .buildObject(Assertion.DEFAULT_ELEMENT_NAME); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider + .authenticate(new Saml2AuthenticationToken(verifying(registration()).build(), serialize(assertion)))) + .satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA)); + } + + @Test + void authenticateWhenXmlErrorThenThrowAuthenticationException() { + Saml2AuthenticationToken token = new Saml2AuthenticationToken(verifying(registration()).build(), "invalid xml"); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA)); + } + + @Test + void authenticateWhenInvalidDestinationThenThrowAuthenticationException() { + Response response = response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID); + response.getAssertions().add(assertion()); + Saml2AuthenticationToken token = token(signed(response), verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_DESTINATION)); + } + + @Test + void authenticateWhenNoAssertionsPresentThenThrowAuthenticationException() { + Saml2AuthenticationToken token = token(); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, "No assertions found in response.")); + } + + @Test + void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() { + Response response = response(); + response.getAssertions().add(assertion()); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE)); + } + + @Test + void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() { + Response response = response(); + Assertion assertion = assertion(); + assertion.getSubject() + .getSubjectConfirmations() + .get(0) + .getSubjectConfirmationData() + .setNotOnOrAfter(Instant.now().minus(Duration.ofDays(3))); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_ASSERTION)); + } + + @Test + void authenticateWhenMissingSubjectThenThrowAuthenticationException() { + Response response = response(); + Assertion assertion = assertion(); + assertion.setSubject(null); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); + } + + @Test + void authenticateWhenUsernameMissingThenThrowAuthenticationException() { + Response response = response(); + Assertion assertion = assertion(); + assertion.getSubject().getNameID().setValue(null); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); + } + + @Test + void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + assertion.getSubject() + .getSubjectConfirmations() + .forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(response, verifying(registration())); + this.provider.authenticate(token); + } + + @Test + void evaluateInResponseToSucceedsWhenInResponseToInResponseAndAssertionsMatchRequestID() { + Response response = response(); + response.setInResponseTo("SAML2"); + response.getAssertions().add(signed(assertion("SAML2"))); + response.getAssertions().add(signed(assertion("SAML2"))); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); + this.provider.authenticate(token); + } + + @Test + void evaluateInResponseToSucceedsWhenInResponseToInAssertionOnlyMatchRequestID() { + Response response = response(); + response.getAssertions().add(signed(assertion())); + response.getAssertions().add(signed(assertion("SAML2"))); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); + this.provider.authenticate(token); + } + + @Test + void evaluateInResponseToFailsWhenInResponseToInAssertionOnlyAndCorruptedStoredRequest() { + Response response = response(); + response.getAssertions().add(signed(assertion())); + response.getAssertions().add(signed(assertion("SAML2"))); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, true); + Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .withStackTraceContaining("malformed_request_data"); + } + + @Test + void evaluateInResponseToFailsWhenInResponseToInAssertionMismatchWithRequestID() { + Response response = response(); + response.setInResponseTo("SAML2"); + response.getAssertions().add(signed(assertion("SAML2"))); + response.getAssertions().add(signed(assertion("BAD"))); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .withStackTraceContaining("invalid_assertion"); + } + + @Test + void evaluateInResponseToFailsWhenInResponseToInAssertionOnlyAndMismatchWithRequestID() { + Response response = response(); + response.getAssertions().add(signed(assertion())); + response.getAssertions().add(signed(assertion("BAD"))); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .withStackTraceContaining("invalid_assertion"); + } + + @Test + void evaluateInResponseToFailsWhenInResponseInToResponseMismatchWithRequestID() { + Response response = response(); + response.setInResponseTo("BAD"); + response.getAssertions().add(signed(assertion("SAML2"))); + response.getAssertions().add(signed(assertion("SAML2"))); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .withStackTraceContaining("invalid_in_response_to"); + } + + @Test + void evaluateInResponseToFailsWhenInResponseInToResponseAndCorruptedStoredRequest() { + Response response = response(); + response.setInResponseTo("SAML2"); + response.getAssertions().add(signed(assertion())); + response.getAssertions().add(signed(assertion())); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, true); + Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .withStackTraceContaining("malformed_request_data"); + } + + @Test + void evaluateInResponseToFailsWhenInResponseToInResponseButNoSavedRequest() { + Response response = response(); + response.setInResponseTo("BAD"); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .withStackTraceContaining("invalid_in_response_to"); + } + + @Test + void evaluateInResponseToSucceedsWhenNoInResponseToInResponseOrAssertions() { + Response response = response(); + response.getAssertions().add(signed(assertion())); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); + this.provider.authenticate(token); + } + + @Test + void authenticateWhenAssertionContainsAttributesThenItSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + List attributes = attributeStatements(); + assertion.getAttributeStatements().addAll(attributes); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(response, verifying(registration())); + Authentication authentication = this.provider.authenticate(token); + Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); + + Instant registeredDate = Instant.parse("1970-01-01T00:00:00Z"); + assertThat(principal.getAttributes()) + .contains(entry("email", List.of("john.doe@example.com", "doe.john@example.com"))) + .contains(entry("name", List.of("John Doe"))) + .contains(entry("age", List.of(21))) + .contains(entry("website", List.of("https://johndoe.com/"))) + .contains(entry("registered", List.of(true))) + .contains(entry("age", List.of(21))) + .contains(entry("registeredDate", List.of(registeredDate))) + .contains(entry("role", List.of("RoleOne", "RoleTwo"))); + assertThat(principal.getSessionIndexes()) + .contains("session-index"); + } + + // gh-11785 + @Test + void deserializeWhenAssertionContainsAttributesThenWorks() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + ClassLoader loader = getClass().getClassLoader(); + mapper.registerModules(SecurityJackson2Modules.getModules(loader)); + Response response = response(); + Assertion assertion = assertion(); + List attributes = TestOpenSamlObjects.attributeStatements(); + attributes.subList(2, attributes.size()).clear(); + assertion.getAttributeStatements().addAll(attributes); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(response, verifying(registration())); + Authentication authentication = this.provider.authenticate(token); + String result = mapper.writeValueAsString(authentication); + mapper.readValue(result, Authentication.class); + } + + @Test + void authenticateWhenAssertionContainsCustomAttributesThenItSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + AttributeStatement attribute = TestOpenSamlObjects.customAttributeStatement("Address", + TestCustomOpenSamlObjects.instance()); + assertion.getAttributeStatements().add(attribute); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(response, verifying(registration())); + Authentication authentication = this.provider.authenticate(token); + Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); + TestCustomOpenSamlObjects.CustomOpenSamlObject address = (TestCustomOpenSamlObjects.CustomOpenSamlObject) principal.getAttribute("Address").get(0); + assertThat(address.getStreet()).isEqualTo("Test Street"); + assertThat(address.getStreetNumber()).isEqualTo("1"); + assertThat(address.getZIP()).isEqualTo("11111"); + assertThat(address.getCity()).isEqualTo("Test City"); + } + + @Test + void authenticateWhenEncryptedAssertionWithoutSignatureThenItFails() { + Response response = response(); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE, "Did not decrypt response")); + } + + @Test + void authenticateWhenEncryptedAssertionWithSignatureThenItSucceeds() { + Response response = response(); + Assertion assertion = TestOpenSamlObjects.signed(assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion, + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); + this.provider.authenticate(token); + } + + @Test + void authenticateWhenEncryptedAssertionWithResponseSignatureThenItSucceeds() { + Response response = response(); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); + this.provider.authenticate(token); + } + + @Test + void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + NameID nameId = assertion.getSubject().getNameID(); + EncryptedID encryptedID = TestOpenSamlObjects.encrypted(nameId, + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + assertion.getSubject().setNameID(null); + assertion.getSubject().setEncryptedID(encryptedID); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); + this.provider.authenticate(token); + } + + @Test + void authenticateWhenEncryptedAttributeThenDecrypts() { + Response response = response(); + Assertion assertion = assertion(); + EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value", + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME); + statement.getEncryptedAttributes().add(attribute); + assertion.getAttributeStatements().add(statement); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); + Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); + Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); + assertThat(principal.getAttribute("name")).containsExactly("value"); + } + + @Test + void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() { + Response response = response(); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + Saml2AuthenticationToken token = token(signed(response), verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); + } + + @Test + void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() { + Response response = response(); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + Saml2AuthenticationToken token = token(signed(response), registration() + .decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartyPrivateCredential()))); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); + } + + @Test + void authenticateWhenAuthenticationHasDetailsThenSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + assertion.getSubject() + .getSubjectConfirmations() + .forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(response, verifying(registration())); + token.setDetails("some-details"); + Authentication authentication = this.provider.authenticate(token); + assertThat(authentication.getDetails()).isEqualTo("some-details"); + } + + @Test + void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException { + Response response = response(); + Assertion assertion = TestOpenSamlObjects.signed(assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); + EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion, + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + response.getEncryptedAssertions().add(encryptedAssertion); + Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); + Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); + // the following code will throw an exception if authentication isn't serializable + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream); + objectOutputStream.writeObject(authentication); + objectOutputStream.flush(); + } + + @Test + void createDefaultAssertionValidatorWhenAssertionThenValidates() { + Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); + Assertion assertion = response.getAssertions().get(0); + OpenSaml4AuthenticationProvider.AssertionToken assertionToken = new OpenSaml4AuthenticationProvider.AssertionToken( + assertion, token()); + assertThat( + OpenSaml4AuthenticationProvider.createDefaultAssertionValidator().convert(assertionToken).hasErrors()) + .isFalse(); + } + + @Test + void authenticateWithSHA1SignatureThenItSucceeds() throws Exception { + Response response = response(); + Assertion assertion = TestOpenSamlObjects.signed(assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID, + SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(response, verifying(registration())); + this.provider.authenticate(token); + } + + @Test + void createDefaultResponseAuthenticationConverterWhenResponseThenConverts() { + Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); + Saml2AuthenticationToken token = token(response, verifying(registration())); + ResponseToken responseToken = new ResponseToken(response, token); + Saml2Authentication authentication = OpenSaml4AuthenticationProvider + .createDefaultResponseAuthenticationConverter() + .convert(responseToken); + assertThat(authentication.getName()).isEqualTo("test@saml.user"); + } + + @Test + void authenticateWhenResponseAuthenticationConverterConfiguredThenUses() { + Converter authenticationConverter = mock(Converter.class); + OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); + provider.setResponseAuthenticationConverter(authenticationConverter); + Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); + Saml2AuthenticationToken token = token(response, verifying(registration())); + provider.authenticate(token); + verify(authenticationConverter).convert(any()); + } + + @Test + void setResponseAuthenticationConverterWhenNullThenIllegalArgument() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.provider.setResponseAuthenticationConverter(null)); + // @formatter:on + } + + @Test + void authenticateWhenResponseStatusIsNotSuccessThenFails() { + Response response = TestOpenSamlObjects + .signedResponseWithOneAssertion((r) -> r.setStatus(TestOpenSamlObjects.status(StatusCode.AUTHN_FAILED))); + Saml2AuthenticationToken token = token(response, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_RESPONSE, "Invalid status")); + } + + @Test + void authenticateWhenResponseStatusIsSuccessThenSucceeds() { + Response response = TestOpenSamlObjects + .signedResponseWithOneAssertion((r) -> r.setStatus(TestOpenSamlObjects.successStatus())); + Saml2AuthenticationToken token = token(response, verifying(registration())); + Authentication authentication = this.provider.authenticate(token); + assertThat(authentication.getName()).isEqualTo("test@saml.user"); + } + + @Test + void setResponseValidatorWhenNullThenIllegalArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setResponseValidator(null)); + } + + @Test + void authenticateWhenCustomResponseValidatorThenUses() { + Converter validator = mock( + Converter.class); + // @formatter:off + provider.setResponseValidator((responseToken) -> OpenSaml4AuthenticationProvider.createDefaultResponseValidator() + .convert(responseToken) + .concat(validator.convert(responseToken)) + ); + // @formatter:on + Response response = response(); + Assertion assertion = assertion(); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(signed(response), verifying(registration())); + given(validator.convert(any(ResponseToken.class))) + .willReturn(Saml2ResponseValidatorResult.success()); + provider.authenticate(token); + verify(validator).convert(any(ResponseToken.class)); + } + + @Test + void authenticateWhenAssertionIssuerNotValidThenFailsWithInvalidIssuer() { + Response response = response(); + Assertion assertion = assertion(); + assertion.setIssuer(TestOpenSamlObjects.issuer("https://invalid.idp.test/saml2/idp")); + response.getAssertions().add(assertion); + Saml2AuthenticationToken token = token(signed(response), verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> provider.authenticate(token)) + .withMessageContaining("did not match any valid issuers"); + } + + private T build(QName qName) { + return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName); + } + + private String serialize(XMLObject object) { + try { + Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Element element = marshaller.marshall(object); + return SerializeSupport.nodeToString(element); + } catch (MarshallingException ex) { + throw new Saml2Exception(ex); + } + } + + private Consumer errorOf(String errorCode) { + return errorOf(errorCode, null); + } + + private Consumer errorOf(String errorCode, String description) { + return ex -> { + assertThat(ex.getSaml2Error().getErrorCode()).isEqualTo(errorCode); + if (StringUtils.hasText(description)) { + assertThat(ex.getSaml2Error().getDescription()).contains(description); + } + }; + } + + private Response response() { + Response response = TestOpenSamlObjects.response(); + response.setIssueInstant(Instant.now()); + return response; + } + + private Response response(String destination, String issuerEntityId) { + Response response = TestOpenSamlObjects.response(destination, issuerEntityId); + response.setIssueInstant(Instant.now()); + return response; + } + + private AuthnRequest request() { + AuthnRequest request = TestOpenSamlObjects.authnRequest(); + return request; + } + + private String serializedRequest(AuthnRequest request, Saml2MessageBinding binding) { + String xml = serialize(request); + return (binding == Saml2MessageBinding.POST) ? Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8)) + : Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml)); + } + + private Assertion assertion(String inResponseTo) { + Assertion assertion = TestOpenSamlObjects.assertion(); + assertion.setIssueInstant(Instant.now()); + for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) { + SubjectConfirmationData data = confirmation.getSubjectConfirmationData(); + data.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); + data.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); + if (StringUtils.hasText(inResponseTo)) { + data.setInResponseTo(inResponseTo); + } + } + Conditions conditions = assertion.getConditions(); + conditions.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); + conditions.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); + return assertion; + } + + private Assertion assertion() { + return assertion(null); + } + + private T signed(T toSign) { + TestOpenSamlObjects.signed(toSign, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + return toSign; + } + + private List attributeStatements() { + List attributeStatements = TestOpenSamlObjects.attributeStatements(); + AttributeBuilder attributeBuilder = new AttributeBuilder(); + Attribute registeredDateAttr = attributeBuilder.buildObject(); + registeredDateAttr.setName("registeredDate"); + XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, + XSDateTime.TYPE_NAME); + registeredDate.setValue(Instant.parse("1970-01-01T00:00:00Z")); + registeredDateAttr.getAttributeValues().add(registeredDate); + attributeStatements.iterator().next().getAttributes().add(registeredDateAttr); + return attributeStatements; + } + + private Saml2AuthenticationToken token() { + Response response = response(); + RelyingPartyRegistration registration = verifying(registration()).build(); + return new Saml2AuthenticationToken(registration, serialize(response)); + } + + private Saml2AuthenticationToken token(Response response, RelyingPartyRegistration.Builder registration) { + return new Saml2AuthenticationToken(registration.build(), serialize(response)); + } + + private Saml2AuthenticationToken token(Response response, RelyingPartyRegistration.Builder registration, + AbstractSaml2AuthenticationRequest authenticationRequest) { + return new Saml2AuthenticationToken(registration.build(), serialize(response), authenticationRequest); + } + + private AbstractSaml2AuthenticationRequest mockedStoredAuthenticationRequest(String requestId, + Saml2MessageBinding binding, boolean corruptRequestString) { + AuthnRequest request = request(); + if (requestId != null) { + request.setID(requestId); + } + String serializedRequest = serializedRequest(request, binding); + if (corruptRequestString) { + serializedRequest = serializedRequest.substring(2, serializedRequest.length() - 2); + } + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mock(AbstractSaml2AuthenticationRequest.class); + given(mockAuthenticationRequest.getSamlRequest()).willReturn(serializedRequest); + given(mockAuthenticationRequest.getBinding()).willReturn(binding); + return mockAuthenticationRequest; + } + + private RelyingPartyRegistration.Builder registration() { + return TestRelyingPartyRegistrations.noCredentials() + .entityId(RELYING_PARTY_ENTITY_ID) + .assertionConsumerServiceLocation(DESTINATION) + .assertingPartyDetails(party -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); + } + + private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { + return builder.assertingPartyDetails(party -> party + .verificationX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); + } + + private RelyingPartyRegistration.Builder decrypting(RelyingPartyRegistration.Builder builder) { + return builder + .decryptionX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential())); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilderTest.java new file mode 100644 index 00000000000..28a4f962970 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilderTest.java @@ -0,0 +1,155 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.util.KeyWithCert; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.util.FileCopyUtils; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.util.List; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyWithCert1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyWithCert2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.x509Certificate1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.x509Certificate2; + +class RelyingPartyRegistrationBuilderTest { + + private static final String ENTITY_ID = "entityId"; + private static final String ENTITY_ID_ALIAS = "entityIdAlias"; + private static final String NAME_ID = "nameIdFormat"; + private static final String REGISTRATION_ID = "registrationId"; + private static final String SAML_SAMPLE_METADATA_XML = "saml-sample-metadata.xml"; + + @Test + void buildsRelyingPartyRegistrationFromLocation() { + RelyingPartyRegistrationBuilder.Params params = RelyingPartyRegistrationBuilder.Params.builder() + .samlEntityID(ENTITY_ID) + .samlSpNameId(NAME_ID) + .keys(List.of(keyWithCert1())) + .metadataLocation(SAML_SAMPLE_METADATA_XML) + .rpRegistrationId(REGISTRATION_ID) + .samlSpAlias(ENTITY_ID_ALIAS) + .requestSigned(true) + .signatureAlgorithms(List.of()) + .build(); + RelyingPartyRegistration registration = RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(params); + + assertThat(registration) + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId) + .returns(ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) + // from xml + .extracting(RelyingPartyRegistration::getAssertingPartyDetails) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId) + .returns(true, RelyingPartyRegistration.AssertingPartyDetails::getWantAuthnRequestsSigned); + } + + @Test + void buildsRelyingPartyRegistrationFromXML() { + String metadataXml = loadResouceAsString(SAML_SAMPLE_METADATA_XML); + + RelyingPartyRegistrationBuilder.Params params = RelyingPartyRegistrationBuilder.Params.builder() + .samlEntityID(ENTITY_ID) + .samlSpNameId(NAME_ID) + .keys(List.of(keyWithCert1())) + .metadataLocation(metadataXml) + .rpRegistrationId(REGISTRATION_ID) + .samlSpAlias(ENTITY_ID_ALIAS) + .requestSigned(false) + .signatureAlgorithms(List.of()) + .build(); + RelyingPartyRegistration registration = RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(params); + + assertThat(registration) + .returns(REGISTRATION_ID, RelyingPartyRegistration::getRegistrationId) + .returns(ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) + // from xml + .extracting(RelyingPartyRegistration::getAssertingPartyDetails) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId) + .returns(false, RelyingPartyRegistration.AssertingPartyDetails::getWantAuthnRequestsSigned); + } + + @Test + void withCredentials() { + String metadataXml = loadResouceAsString(SAML_SAMPLE_METADATA_XML); + + RelyingPartyRegistrationBuilder.Params params = RelyingPartyRegistrationBuilder.Params.builder() + .samlEntityID(ENTITY_ID) + .samlSpNameId(NAME_ID) + .keys(List.of(keyWithCert1(), keyWithCert2())) + .metadataLocation(metadataXml) + .rpRegistrationId(REGISTRATION_ID) + .samlSpAlias(ENTITY_ID_ALIAS) + .requestSigned(false) + .signatureAlgorithms(List.of(SignatureAlgorithm.SHA512, SignatureAlgorithm.SHA256)) + .build(); + RelyingPartyRegistration registration = RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(params); + + assertThat(registration.getSigningX509Credentials()) + .hasSize(2) + .extracting(Saml2X509Credential::getCertificate) + .containsOnly(x509Certificate1(), x509Certificate2()); + + assertThat(registration.getDecryptionX509Credentials()) + .hasSize(1) + .extracting(Saml2X509Credential::getCertificate) + .containsOnly(x509Certificate1()); + + assertThat(registration.getAssertingPartyDetails().getSigningAlgorithms()) + .hasSize(2) + .containsOnly(SignatureAlgorithm.SHA512.getSignatureAlgorithmURI(), SignatureAlgorithm.SHA256.getSignatureAlgorithmURI()); + } + + @Test + void failsWithInvalidXML() { + String metadataXml = "\ninvalid xml"; + List keyList = List.of(keyWithCert1()); + List signatureAlgorithms = List.of(); + + RelyingPartyRegistrationBuilder.Params params = RelyingPartyRegistrationBuilder.Params.builder() + .samlEntityID(ENTITY_ID) + .samlSpNameId(NAME_ID) + .keys(keyList) + .metadataLocation(metadataXml) + .rpRegistrationId(REGISTRATION_ID) + .samlSpAlias(ENTITY_ID_ALIAS) + .requestSigned(true) + .signatureAlgorithms(signatureAlgorithms) + .build(); + + assertThatThrownBy(() -> + RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(params)) + .isInstanceOf(Saml2Exception.class) + .hasMessageContaining("Unsupported element"); + } + + private static String loadResouceAsString(String resourceLocation) { + ResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource resource = resourceLoader.getResource(resourceLocation); + + try (Reader reader = new InputStreamReader(resource.getInputStream(), UTF_8)) { + return FileCopyUtils.copyToString(reader); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java new file mode 100644 index 00000000000..e8b06815b68 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java @@ -0,0 +1,542 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.shibboleth.utilities.java.support.xml.SerializeSupport; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.Marshaller; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.core.xml.schema.XSDateTime; +import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder; +import org.opensaml.saml.common.SignableSAMLObject; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.AttributeValue; +import org.opensaml.saml.saml2.core.Audience; +import org.opensaml.saml.saml2.core.AuthnRequest; +import org.opensaml.saml.saml2.core.Conditions; +import org.opensaml.saml.saml2.core.EncryptedAttribute; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.SubjectConfirmation; +import org.opensaml.saml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml.saml2.core.impl.AttributeBuilder; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2ErrorCodes; +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; +import org.w3c.dom.Element; + +import javax.xml.namespace.QName; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.function.Consumer; + +import static java.util.Map.entry; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * This is based on OpenSaml4AuthenticationProviderTest from Spring Security + */ +class Saml2BearerGrantAuthenticationConverterTest { + + private static final String DESTINATION = "http://localhost:8080/uaa/oauth/token/alias/integration-saml-entity-id"; + + private static final String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; + + private static final String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp"; + + private Saml2BearerGrantAuthenticationConverter provider; + + @BeforeEach + void beforeEach() { + IdentityZoneManager identityZoneManager = new IdentityZoneManagerImpl(); + RestTemplate restTemplate = new RestTemplate(); + SamlConfiguration samlConfiguration = new SamlConfiguration(); + JdbcIdentityProviderProvisioning providerProvisioning = mock(JdbcIdentityProviderProvisioning.class); + + SamlIdentityProviderConfigurator identityProviderConfigurator = new SamlIdentityProviderConfigurator( + providerProvisioning, identityZoneManager, samlConfiguration.fixedHttpMetaDataProvider(restTemplate, restTemplate, null) + ); + SamlRelyingPartyRegistrationRepositoryConfig samlRelyingPartyRegistrationRepositoryConfig = + new SamlRelyingPartyRegistrationRepositoryConfig( + "integration-saml-entity-id", new SamlConfigProps(), + new BootstrapSamlIdentityProviderData(identityProviderConfigurator), + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", + List.of(SignatureAlgorithm.SHA256, SignatureAlgorithm.SHA512)); + + RelyingPartyRegistrationRepository relyingPartyRegistrationRepository = samlRelyingPartyRegistrationRepositoryConfig.relyingPartyRegistrationRepository(identityProviderConfigurator); + RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = samlRelyingPartyRegistrationRepositoryConfig.relyingPartyRegistrationResolver(relyingPartyRegistrationRepository); + + provider = new Saml2BearerGrantAuthenticationConverter(relyingPartyRegistrationResolver, identityZoneManager, + providerProvisioning, null, null); + } + + @Test + void authenticateWhenUnknownDataClassThenThrowAuthenticationException() { + Audience audience = (Audience) XMLObjectProviderRegistrySupport.getBuilderFactory() + .getBuilder(Audience.DEFAULT_ELEMENT_NAME) + .buildObject(Audience.DEFAULT_ELEMENT_NAME); + Saml2AuthenticationToken token = new Saml2AuthenticationToken(verifying(registration()).build(), serialize(audience)); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_ASSERTION)); + } + + @Test + void authenticateWhenXmlErrorThenThrowAuthenticationException() { + Saml2AuthenticationToken token = new Saml2AuthenticationToken(verifying(registration()).build(), "invalid xml"); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_ASSERTION)); + } + + @Test + void authenticateWhenInvalidDestinationThenThrowAuthenticationException() { + Assertion assertion = assertion(null, DESTINATION + "invalid"); + + Saml2AuthenticationToken token = token(signed(assertion), verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_ASSERTION)); + } + + @Test + void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() { + Assertion assertion = signed(assertion()); + assertion.setID("changed"); + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE)); + } + + @Test + void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() { + Assertion assertion = assertion(); + assertion.getSubject() + .getSubjectConfirmations() + .get(0) + .getSubjectConfirmationData() + .setNotOnOrAfter(Instant.now().minus(Duration.ofDays(3))); + + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.INVALID_ASSERTION)); + } + + @Test + void authenticateWhenMissingSubjectThenThrowAuthenticationException() { + Assertion assertion = assertion(); + assertion.setSubject(null); + + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); + } + + @Test + void authenticateWhenUsernameMissingThenThrowAuthenticationException() { + Assertion assertion = assertion(); + assertion.getSubject().getNameID().setValue(null); + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); + } + + @Test + void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() { + Assertion assertion = assertion(); + assertion.getSubject() + .getSubjectConfirmations() + .forEach(sc -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")); + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + this.provider.authenticate(token); + } + + @Test + void evaluateInResponseToSucceedsWhenInResponseToInAssertionOnlyMatchRequestID() { + Assertion assertion = assertion(); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken token = token(assertion, verifying(registration()), mockAuthenticationRequest); + this.provider.authenticate(token); + } + + @Test + void evaluateInResponseToFailsWhenInResponseToInAssertionMismatchWithRequestID() { + Assertion assertion = assertion("saml2"); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + Saml2AuthenticationToken token = token(assertion, verifying(registration()), mockAuthenticationRequest); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .withStackTraceContaining("invalid_assertion"); + } + + @Test + void authenticateWhenAssertionContainsAttributesThenItSucceeds() { + Assertion assertion = assertion(); + List attributes = attributeStatements(); + assertion.getAttributeStatements().addAll(attributes); + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + Authentication authentication = this.provider.authenticate(token); + Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); + + Instant registeredDate = Instant.parse("1970-01-01T00:00:00Z"); + assertThat(principal.getAttributes()) + .contains(entry("email", List.of("john.doe@example.com", "doe.john@example.com"))) + .contains(entry("name", List.of("John Doe"))) + .contains(entry("age", List.of(21))) + .contains(entry("website", List.of("https://johndoe.com/"))) + .contains(entry("registered", List.of(true))) + .contains(entry("age", List.of(21))) + .contains(entry("registeredDate", List.of(registeredDate))) + .contains(entry("role", List.of("RoleOne", "RoleTwo"))); + assertThat(principal.getSessionIndexes()) + .contains("session-index"); + } + + // gh-11785 + @Test + void deserializeWhenAssertionContainsAttributesThenWorks() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + ClassLoader loader = getClass().getClassLoader(); + mapper.registerModules(SecurityJackson2Modules.getModules(loader)); + Assertion assertion = assertion(); + List attributes = TestOpenSamlObjects.attributeStatements(); + attributes.subList(2, attributes.size()).clear(); + + assertion.getAttributeStatements().addAll(attributes); + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + Authentication authentication = this.provider.authenticate(token); + String result = mapper.writeValueAsString(authentication); + mapper.readValue(result, Authentication.class); + } + + @Test + void authenticateWhenAssertionContainsCustomAttributesThenItSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + AttributeStatement attribute = TestOpenSamlObjects.customAttributeStatement("Address", + TestCustomOpenSamlObjects.instance()); + assertion.getAttributeStatements().add(attribute); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + Authentication authentication = this.provider.authenticate(token); + Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); + TestCustomOpenSamlObjects.CustomOpenSamlObject address = (TestCustomOpenSamlObjects.CustomOpenSamlObject) principal.getAttribute("Address").get(0); + assertThat(address.getStreet()).isEqualTo("Test Street"); + assertThat(address.getStreetNumber()).isEqualTo("1"); + assertThat(address.getZIP()).isEqualTo("11111"); + assertThat(address.getCity()).isEqualTo("Test City"); + } + + @Test + void authenticateWhenEncryptedAttributeWithoutSignatureThenItFails() { + Assertion assertion = assertion(); + EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value", + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME); + statement.getEncryptedAttributes().add(attribute); + assertion.getAttributeStatements().add(statement); + + Saml2AuthenticationToken token = token(signed(assertion), registration()); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); + } + + @Test + void authenticateWhenSignedAssertionWithSignatureThenItSucceeds() { + Assertion assertion = TestOpenSamlObjects.signed(assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(signed(assertion), decrypting(verifying(registration()))); + this.provider.authenticate(token); + } + + @Test + void authenticateWithAssertionSignatureThenItSucceeds() { + Assertion assertion = assertion(); + Saml2AuthenticationToken token = token(signed(assertion), decrypting(verifying(registration()))); + this.provider.authenticate(token); + } + + @Test + void authenticateWhenEncryptedAttributeThenDecrypts() { + Assertion assertion = assertion(); + EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value", + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME); + statement.getEncryptedAttributes().add(attribute); + assertion.getAttributeStatements().add(statement); + Saml2AuthenticationToken token = token(signed(assertion), decrypting(verifying(registration()))); + Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); + Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); + assertThat(principal.getAttribute("name")).containsExactly("value"); + } + + @Test + void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() { + Assertion assertion = assertion(); + EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value", + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME); + statement.getEncryptedAttributes().add(attribute); + assertion.getAttributeStatements().add(statement); + + Saml2AuthenticationToken token = token(signed(assertion), verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); + } + + @Test + void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() { + Assertion assertion = assertion(); + EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value", + TestSaml2X509Credentials.assertingPartyEncryptingCredential()); + AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME); + statement.getEncryptedAttributes().add(attribute); + assertion.getAttributeStatements().add(statement); + + Saml2AuthenticationToken token = token(signed(assertion), registration() + .decryptionX509Credentials(c -> c.add(TestSaml2X509Credentials.assertingPartyPrivateCredential()))); + assertThatExceptionOfType(Saml2AuthenticationException.class) + .isThrownBy(() -> this.provider.authenticate(token)) + .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); + } + + @Test + void authenticateWhenAuthenticationHasDetailsThenSucceeds() { + Response response = response(); + Assertion assertion = assertion(); + assertion.getSubject() + .getSubjectConfirmations() + .forEach(sc -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")); + response.getAssertions().add(signed(assertion)); + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + token.setDetails("some-details"); + Authentication authentication = this.provider.authenticate(token); + assertThat(authentication.getDetails()).isEqualTo("some-details"); + } + + @Test + void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException { + Assertion assertion = TestOpenSamlObjects.signed(assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); + Saml2AuthenticationToken token = token(signed(assertion), decrypting(verifying(registration()))); + Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); + // the following code will throw an exception if authentication isn't serializable + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream); + objectOutputStream.writeObject(authentication); + objectOutputStream.flush(); + } + + @Test + void createDefaultAssertionValidatorWhenAssertionThenValidates() { + Assertion assertion = signed(assertion()); + OpenSaml4AuthenticationProvider.AssertionToken assertionToken = new OpenSaml4AuthenticationProvider.AssertionToken( + assertion, token()); + assertThat( + Saml2BearerGrantAuthenticationConverter.createDefaultAssertionValidator().convert(assertionToken).hasErrors()) + .isFalse(); + } + + @Test + void authenticateWithSHA1SignatureThenItSucceeds() throws Exception { + Assertion assertion = TestOpenSamlObjects.signed(assertion(), + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID, + SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); + + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + this.provider.authenticate(token); + } + + @Test + void createDefaultResponseAuthenticationConverterWhenResponseThenConverts() { + Assertion assertion = assertion(); + Saml2AuthenticationToken token = token(assertion, verifying(registration())); + OpenSaml4AuthenticationProvider.AssertionToken assertionToken = new OpenSaml4AuthenticationProvider.AssertionToken(assertion, token); + AbstractAuthenticationToken authentication = Saml2BearerGrantAuthenticationConverter + .createDefaultAssertionAuthenticationConverter() + .convert(assertionToken); + assertThat(authentication.getName()).isEqualTo("test@saml.user"); + } + + @Test + void authenticateWhenAssertionIssuerNotValidThenFailsWithInvalidIssuer() { + Assertion assertion = assertion(); + assertion.setIssuer(TestOpenSamlObjects.issuer("https://invalid.idp.test/saml2/idp")); + Saml2AuthenticationToken token = token(signed(assertion), verifying(registration())); + assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> provider.authenticate(token)) + .withMessageContaining("from Issuer", "was not valid"); + } + + private T build(QName qName) { + return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName); + } + + private String serialize(XMLObject object) { + try { + Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Element element = marshaller.marshall(object); + return SerializeSupport.nodeToString(element); + } catch (MarshallingException ex) { + throw new Saml2Exception(ex); + } + } + + private Consumer errorOf(String errorCode) { + return errorOf(errorCode, null); + } + + private Consumer errorOf(String errorCode, String description) { + return ex -> { + assertThat(ex.getSaml2Error().getErrorCode()).isEqualTo(errorCode); + if (StringUtils.hasText(description)) { + assertThat(ex.getSaml2Error().getDescription()).contains(description); + } + }; + } + + private Response response() { + Response response = TestOpenSamlObjects.response(); + response.setIssueInstant(Instant.now()); + return response; + } + + private AuthnRequest request() { + return TestOpenSamlObjects.authnRequest(); + } + + private String serializedRequest(AuthnRequest request, Saml2MessageBinding binding) { + String xml = serialize(request); + return (binding == Saml2MessageBinding.POST) ? Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8)) + : Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml)); + } + + private Assertion assertion(String inResponseTo) { + return assertion(inResponseTo, DESTINATION); + } + + private Assertion assertion(String inResponseTo, String destination) { + Assertion assertion = TestOpenSamlObjects.assertion(); + assertion.setIssueInstant(Instant.now()); + + for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) { + SubjectConfirmationData data = confirmation.getSubjectConfirmationData(); + data.setRecipient(destination); + data.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); + data.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); + if (StringUtils.hasText(inResponseTo)) { + data.setInResponseTo(inResponseTo); + } + } + Conditions conditions = assertion.getConditions(); + conditions.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); + conditions.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); + return assertion; + } + + private Assertion assertion() { + return assertion(null); + } + + private T signed(T toSign) { + TestOpenSamlObjects.signed(toSign, TestSaml2X509Credentials.assertingPartySigningCredential(), + RELYING_PARTY_ENTITY_ID); + return toSign; + } + + private List attributeStatements() { + List attributeStatements = TestOpenSamlObjects.attributeStatements(); + AttributeBuilder attributeBuilder = new AttributeBuilder(); + Attribute registeredDateAttr = attributeBuilder.buildObject(); + registeredDateAttr.setName("registeredDate"); + XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, + XSDateTime.TYPE_NAME); + registeredDate.setValue(Instant.parse("1970-01-01T00:00:00Z")); + registeredDateAttr.getAttributeValues().add(registeredDate); + attributeStatements.iterator().next().getAttributes().add(registeredDateAttr); + return attributeStatements; + } + + private Saml2AuthenticationToken token() { + Assertion assertion = assertion(); + RelyingPartyRegistration registration = verifying(registration()).build(); + return new Saml2AuthenticationToken(registration, serialize(assertion)); + } + + private Saml2AuthenticationToken token(Assertion assertion, RelyingPartyRegistration.Builder registration) { + return new Saml2AuthenticationToken(registration.build(), serialize(assertion)); + } + + private Saml2AuthenticationToken token(Assertion assertion, RelyingPartyRegistration.Builder registration, + AbstractSaml2AuthenticationRequest authenticationRequest) { + return new Saml2AuthenticationToken(registration.build(), serialize(assertion), authenticationRequest); + } + + private AbstractSaml2AuthenticationRequest mockedStoredAuthenticationRequest(String requestId, + Saml2MessageBinding binding, boolean corruptRequestString) { + AuthnRequest request = request(); + if (requestId != null) { + request.setID(requestId); + } + String serializedRequest = serializedRequest(request, binding); + if (corruptRequestString) { + serializedRequest = serializedRequest.substring(48, serializedRequest.length() - 48); + } + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mock(AbstractSaml2AuthenticationRequest.class); + given(mockAuthenticationRequest.getSamlRequest()).willReturn(serializedRequest); + given(mockAuthenticationRequest.getBinding()).willReturn(binding); + return mockAuthenticationRequest; + } + + private RelyingPartyRegistration.Builder registration() { + return TestRelyingPartyRegistrations.noCredentials() + .entityId(RELYING_PARTY_ENTITY_ID) + .assertionConsumerServiceLocation(DESTINATION) + .assertingPartyDetails(party -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); + } + + private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { + return builder.assertingPartyDetails(party -> party + .verificationX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); + } + + private RelyingPartyRegistration.Builder decrypting(RelyingPartyRegistration.Builder builder) { + return builder + .decryptionX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential())); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java new file mode 100644 index 00000000000..16e66736ad7 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java @@ -0,0 +1,210 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import net.shibboleth.utilities.java.support.xml.SerializeSupport; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.Marshaller; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.core.xml.schema.XSDateTime; +import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.AttributeValue; +import org.opensaml.saml.saml2.core.AuthnRequest; +import org.opensaml.saml.saml2.core.Conditions; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.SubjectConfirmation; +import org.opensaml.saml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml.saml2.core.impl.AttributeBuilder; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Map; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * This class contains functions to create SAML Requests, Responses, Tokens and related objects for testing purposes. + * + * @see TestOpenSamlObjects + *

    + * The Functions in here were copied from Spring-Security Test Classes and made static: + * - spring-security/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests + */ +public final class Saml2TestUtils { + + private static final String DESTINATION = "http://localhost:8080/uaa/saml/SSO/alias/integration-saml-entity-id"; + + private static final String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; + + private static final String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp"; + + private Saml2TestUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static Saml2AuthenticationToken authenticationToken() { + return authenticationToken(null, attributeStatements(TestOpenSamlObjects.attributeStatements())); + } + + public static Saml2AuthenticationToken authenticationToken(String username, List attributeStatements) { + Response response = responseWithAssertions(username, attributeStatements); + + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2", + Saml2MessageBinding.POST, false); + return token(response, verifying(registration()), mockAuthenticationRequest); + } + + public static Response responseWithAssertions() { + return responseWithAssertions(null, TestOpenSamlObjects.attributeStatements()); + } + + public static Response responseWithAssertions(String username, List attributeStatements) { + Response response = response(); + Assertion assertion = assertion(username, null); + assertion.getAttributeStatements().addAll(attributeStatements); + + Assertion signedAssertion = TestOpenSamlObjects.signed(assertion, + TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID, + SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); + + response.getAssertions().add(signedAssertion); + + return response; + } + + public static String serialize(XMLObject object) { + try { + Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Element element = marshaller.marshall(object); + return SerializeSupport.nodeToString(element); + } catch (MarshallingException ex) { + throw new Saml2Exception(ex); + } + } + + public static Response response() { + Response response = TestOpenSamlObjects.response(); + response.setIssueInstant(Instant.now()); + return response; + } + + private static AuthnRequest request() { + return TestOpenSamlObjects.authnRequest(); + } + + private static String serializedRequest(AuthnRequest request, Saml2MessageBinding binding) { + String xml = serialize(request); + return (binding == Saml2MessageBinding.POST) ? Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8)) + : Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml)); + } + + public static String serializedResponse(Response response) { + String xml = serialize(response); + return Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8)); + } + + private static Assertion assertion(String username, String inResponseTo) { + Assertion assertion = TestOpenSamlObjects.assertion(username); + assertion.setIssueInstant(Instant.now()); + for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) { + SubjectConfirmationData data = confirmation.getSubjectConfirmationData(); + data.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); + data.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); + if (StringUtils.hasText(inResponseTo)) { + data.setInResponseTo(inResponseTo); + } + } + Conditions conditions = assertion.getConditions(); + conditions.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); + conditions.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); + return assertion; + } + + private static List attributeStatements(List attributeStatements) { + AttributeBuilder attributeBuilder = new AttributeBuilder(); + Attribute registeredDateAttr = attributeBuilder.buildObject(); + registeredDateAttr.setName("registeredDate"); + XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, + XSDateTime.TYPE_NAME); + registeredDate.setValue(Instant.parse("1970-01-01T00:00:00Z")); + registeredDateAttr.getAttributeValues().add(registeredDate); + attributeStatements.iterator().next().getAttributes().add(registeredDateAttr); + return attributeStatements; + } + + public static Saml2AuthenticationToken token(Response response, RelyingPartyRegistration.Builder registration, + AbstractSaml2AuthenticationRequest authenticationRequest) { + return new Saml2AuthenticationToken(registration.build(), serialize(response), authenticationRequest); + } + + public static AbstractSaml2AuthenticationRequest mockedStoredAuthenticationRequest(String requestId, + Saml2MessageBinding binding, boolean corruptRequestString) { + AuthnRequest request = request(); + if (requestId != null) { + request.setID(requestId); + } + String serializedRequest = serializedRequest(request, binding); + if (corruptRequestString) { + serializedRequest = serializedRequest.substring(2, serializedRequest.length() - 2); + } + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mock(AbstractSaml2AuthenticationRequest.class); + given(mockAuthenticationRequest.getSamlRequest()).willReturn(serializedRequest); + given(mockAuthenticationRequest.getBinding()).willReturn(binding); + return mockAuthenticationRequest; + } + + public static RelyingPartyRegistration.Builder registration() { + return TestRelyingPartyRegistrations.noCredentials() + .entityId(RELYING_PARTY_ENTITY_ID) + .assertionConsumerServiceLocation(DESTINATION) + .assertingPartyDetails(party -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); + } + + public static RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { + return builder.assertingPartyDetails(party -> party + .verificationX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); + } + + public static Map xmlNamespaces() { + return Map.of( + // Metadata + "md", "urn:oasis:names:tc:SAML:2.0:metadata", + "ds", "http://www.w3.org/2000/09/xmldsig#", + // Request + "saml2p", "urn:oasis:names:tc:SAML:2.0:protocol", + "saml2", "urn:oasis:names:tc:SAML:2.0:assertion", + // Response + "samlp", "urn:oasis:names:tc:SAML:2.0:protocol", + "saml", "urn:oasis:names:tc:SAML:2.0:assertion" + ); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfigurationBeanTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfigurationBeanTest.java deleted file mode 100644 index 0716eec6959..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlConfigurationBeanTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; -import org.junit.BeforeClass; -import org.junit.Test; -import org.opensaml.DefaultBootstrap; -import org.opensaml.xml.Configuration; -import org.opensaml.xml.security.BasicSecurityConfiguration; -import org.opensaml.xml.signature.SignatureConstants; - -import java.security.Security; - -import static org.junit.Assert.assertEquals; - -public class SamlConfigurationBeanTest { - - @BeforeClass - public static void initVM() throws Exception { - Security.addProvider(new BouncyCastleFipsProvider()); - DefaultBootstrap.bootstrap(); - } - - @Test - public void testSHA1SignatureAlgorithm() { - SamlConfigurationBean samlConfigurationBean = new SamlConfigurationBean(); - samlConfigurationBean.setSignatureAlgorithm(SamlConfigurationBean.SignatureAlgorithm.SHA1); - samlConfigurationBean.afterPropertiesSet(); - - BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration(); - assertEquals(SignatureConstants.ALGO_ID_DIGEST_SHA1, config.getSignatureReferenceDigestMethod()); - assertEquals(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1, config.getSignatureAlgorithmURI("RSA")); - } - - @Test - public void testSHA256SignatureAlgorithm() { - SamlConfigurationBean samlConfigurationBean = new SamlConfigurationBean(); - samlConfigurationBean.setSignatureAlgorithm(SamlConfigurationBean.SignatureAlgorithm.SHA256); - samlConfigurationBean.afterPropertiesSet(); - - BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration(); - assertEquals(SignatureConstants.ALGO_ID_DIGEST_SHA256, config.getSignatureReferenceDigestMethod()); - assertEquals(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256, config.getSignatureAlgorithmURI("RSA")); - } - - @Test - public void testSHA512SignatureAlgorithm() { - SamlConfigurationBean samlConfigurationBean = new SamlConfigurationBean(); - samlConfigurationBean.setSignatureAlgorithm(SamlConfigurationBean.SignatureAlgorithm.SHA512); - samlConfigurationBean.afterPropertiesSet(); - - BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration(); - assertEquals(SignatureConstants.ALGO_ID_DIGEST_SHA512, config.getSignatureReferenceDigestMethod()); - assertEquals(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512, config.getSignatureAlgorithmURI("RSA")); - } - -} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfiguratorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfiguratorTests.java index edf6dad7330..460ad8302e0 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfiguratorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfiguratorTests.java @@ -15,129 +15,80 @@ package org.cloudfoundry.identity.uaa.provider.saml; - +import org.apache.http.conn.ConnectTimeoutException; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.identity.uaa.cache.UrlContentCache; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.SlowHttpServer; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.Rule; -import org.junit.jupiter.api.*; -import org.junit.rules.ExpectedException; -import org.opensaml.DefaultBootstrap; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; -import org.opensaml.xml.parse.BasicParserPool; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import org.springframework.security.saml.trust.httpclient.TLSProtocolSocketFactory; - +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.RestTemplate; + +import java.net.SocketTimeoutException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Security; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Timer; import static java.time.Duration.ofSeconds; import static java.util.Arrays.asList; -import static org.cloudfoundry.identity.uaa.util.AssertThrowsWithMessage.assertThrowsWithMessageThat; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) public class SamlIdentityProviderConfiguratorTests { + @Mock + IdentityProviderProvisioning provisioning; - private Runnable stopHttpServer; + @Mock private FixedHttpMetaDataProvider fixedHttpMetaDataProvider; - private SlowHttpServer slowHttpServer; - @BeforeAll - public static void initializeOpenSAML() throws Exception { - if (!org.apache.xml.security.Init.isInitialized()) { - DefaultBootstrap.bootstrap(); - } - } + @Mock + IdentityProvider idp1; - public static final String xmlWithoutID = - "MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG\n" + - "A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\n" + - "MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu\n" + - "Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC\n" + - "VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM\n" + - "BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN\n" + - "AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU\n" + - "WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O\n" + - "Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL\n" + - "3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk\n" + - "vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6\n" + - "GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n"; - - private String getSimpleSamlPhpMetadata(String domain) { - return "\n" + - "\n" + - " \n" + - " \n" + - " +sYzzLx/5TXtBZhC03uaQT0E/L8=gt9z/i8o16H0KQfV8+gCLgrBYOgaWsQe1Bon3G3UJQqc+z7YTNXl6rX69wbcQum/95KiLcF41BHoCeA4KZL75HE6mpXAF8NrPZiXlwwJFZe31HIfwmeu7JavuB/8QotWraM/u9DGtHVfDWFT92MPr18Odbvl62Gd2zA2PdZR3rz7DsrFc1QSB/Qz1VnQ+3Y8OUBRFDeZZUsNGRJ/l/GfYkiqmyV4fOak6bz0WeCSxY3tOl+F9X8r2gOHxOp3QRtRaK/UElRmPxnYC7UESI0Rq0AphHO6vRulA/EpSXTwu4qgZ6nDtGBOW/C+nQmg8zkv0QPvzk5IE2eaAAE3jkZq4w==\n" + - "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + - " \n" + - " \n" + - " \n" + - " Filip\n" + - " Hanik\n" + - " fhanik@pivotal.io\n" + - " \n" + - "\n"; - } - - public static final String xml = String.format(xmlWithoutID, "http://www.okta.com/k2lw4l5bPODCMIIDBRYZ"); - - public static final String xmlWithoutHeader = xmlWithoutID.replace("", ""); + @Mock + IdentityProvider idp2; public static final String singleAddAlias = "sample-alias"; - + private SamlIdentityProviderDefinition singleAdd = null; + private SlowHttpServer slowHttpServer; private SamlIdentityProviderConfigurator configurator; - private BootstrapSamlIdentityProviderData bootstrap; - SamlIdentityProviderDefinition singleAdd = null; - SamlIdentityProviderDefinition singleAddWithoutHeader = null; - IdentityProviderProvisioning provisioning = mock(IdentityProviderProvisioning.class); + private SamlConfiguration samlConfiguration; + + @BeforeAll + static void beforeAll() { + Security.addProvider(new BouncyCastleFipsProvider()); + } @BeforeEach - public void setUp() { - bootstrap = new BootstrapSamlIdentityProviderData(new SamlIdentityProviderConfigurator(new BasicParserPool(), mock(JdbcIdentityProviderProvisioning.class), mock(FixedHttpMetaDataProvider.class))); + public void beforeEach() { + samlConfiguration = new SamlConfiguration(); + + slowHttpServer = new SlowHttpServer(); singleAdd = new SamlIdentityProviderDefinition() - .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.xmlWithoutID, new RandomValueStringGenerator().generate())) - .setIdpEntityAlias(singleAddAlias) - .setNameID("sample-nameID") - .setAssertionConsumerIndex(1) - .setMetadataTrustCheck(true) - .setLinkText("sample-link-test") - .setIconUrl("sample-icon-url") - .setZoneId("uaa"); - singleAddWithoutHeader = new SamlIdentityProviderDefinition() - .setMetaDataLocation(String.format(xmlWithoutHeader, new RandomValueStringGenerator().generate())) + .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID, new RandomValueStringGenerator().generate())) .setIdpEntityAlias(singleAddAlias) .setNameID("sample-nameID") .setAssertionConsumerIndex(1) @@ -145,170 +96,216 @@ public void setUp() { .setLinkText("sample-link-test") .setIconUrl("sample-icon-url") .setZoneId("uaa"); + fixedHttpMetaDataProvider = mock(FixedHttpMetaDataProvider.class); - configurator = new SamlIdentityProviderConfigurator( - new BasicParserPool(), provisioning, fixedHttpMetaDataProvider); + configurator = new SamlIdentityProviderConfigurator(provisioning, new IdentityZoneManagerImpl(), fixedHttpMetaDataProvider); + } + @AfterEach + public void afterEach() { + slowHttpServer.stop(); } @Test - public void testAddNullProvider() { - Assertions.assertThrows(NullPointerException.class, () -> configurator.validateSamlIdentityProviderDefinition(null, true)); + void testAddNullProvider() { + assertThatThrownBy(() -> configurator.validateSamlIdentityProviderDefinition(null, false)) + .isInstanceOf(NullPointerException.class); } @Test - public void testAddNullProviderAlias() { + void testAddNullProviderAlias() { singleAdd.setIdpEntityAlias(null); - Assertions.assertThrows(NullPointerException.class, () -> { - configurator.validateSamlIdentityProviderDefinition(singleAdd, true); - }); + assertThatThrownBy(() -> configurator.validateSamlIdentityProviderDefinition(singleAdd, false)) + .isInstanceOf(NullPointerException.class); } @Test - public void testGetEntityID() throws Exception { - - Timer t = new Timer(); + void testGetEntityID() { + when(fixedHttpMetaDataProvider.fetchMetadata(any(), anyBoolean())).thenReturn(getSimpleSamlPhpMetadata("http://simplesamlphp.somewhere.com").getBytes()); + BootstrapSamlIdentityProviderData bootstrap = new BootstrapSamlIdentityProviderData(configurator); bootstrap.setIdentityProviders(BootstrapSamlIdentityProviderDataTests.parseYaml(BootstrapSamlIdentityProviderDataTests.sampleYaml)); bootstrap.afterPropertiesSet(); - for (SamlIdentityProviderDefinition def : bootstrap.getIdentityProviderDefinitions()) { + List identityProviderDefinitions = bootstrap.getIdentityProviderDefinitions(); + + for (SamlIdentityProviderDefinition def : identityProviderDefinitions) { switch (def.getIdpEntityAlias()) { - case "okta-local": { - ComparableProvider provider = (ComparableProvider) configurator.getExtendedMetadataDelegateFromCache(def).getDelegate(); - assertEquals("http://www.okta.com/k2lvtem0VAJDMINKEYJW", provider.getEntityID()); + case "okta-local", "custom-authncontext": { + assertThat(def.getIdpEntityId()).isEqualTo("http://www.okta.com/k2lvtem0VAJDMINKEYJW"); break; } case "okta-local-3": { - ComparableProvider provider = (ComparableProvider) configurator.getExtendedMetadataDelegateFromCache(def).getDelegate(); - assertEquals("http://www.okta.com/k2lvtem0VAJDMINKEYJX", provider.getEntityID()); + assertThat(def.getIdpEntityId()).isEqualTo("http://www.okta.com/k2lvtem0VAJDMINKEYJX"); break; } case "okta-local-2": { - ComparableProvider provider = (ComparableProvider) configurator.getExtendedMetadataDelegateFromCache(def).getDelegate(); - IdentityProvider idp2 = mock(IdentityProvider.class); - when(idp2.getType()).thenReturn(OriginKeys.SAML); - when(idp2.getConfig()).thenReturn(def); - when(provisioning.retrieveActive(anyString())).thenReturn(asList(idp2)); - configurator.validateSamlIdentityProviderDefinition(def, true); - assertEquals("http://www.okta.com/k2lw4l5bPODCMIIDBRYZ", provider.getEntityID()); + assertThat(def.getIdpEntityId()).isEqualTo("http://www.okta.com/k2lw4l5bPODCMIIDBRYZ"); break; } case "simplesamlphp-url": { - when(fixedHttpMetaDataProvider.fetchMetadata(any(), anyBoolean())).thenReturn(getSimpleSamlPhpMetadata("http://simplesamlphp.somewhere.com").getBytes()); - ComparableProvider provider = (ComparableProvider) configurator.getExtendedMetadataDelegateFromCache(def).getDelegate(); - assertEquals("http://simplesamlphp.somewhere.com/saml2/idp/metadata.php", provider.getEntityID()); - break; - } - case "custom-authncontext": { - ComparableProvider provider = (ComparableProvider) configurator.getExtendedMetadataDelegateFromCache(def).getDelegate(); - assertEquals("http://www.okta.com/k2lvtem0VAJDMINKEYJW", provider.getEntityID()); + RelyingPartyRegistration extendedMetadataDelegate = configurator.getExtendedMetadataDelegate(def); + assertThat(extendedMetadataDelegate.getAssertingPartyDetails().getEntityId()).isEqualTo("http://simplesamlphp.somewhere.com/saml2/idp/metadata.php"); break; } default: fail(String.format("Unknown provider %s", def.getIdpEntityAlias())); } } - t.cancel(); } @Test - void testGetEntityIDExists() { - bootstrap.setIdentityProviders(BootstrapSamlIdentityProviderDataTests.parseYaml(BootstrapSamlIdentityProviderDataTests.sampleYaml)); - bootstrap.afterPropertiesSet(); - for (SamlIdentityProviderDefinition def : bootstrap.getIdentityProviderDefinitions()) { - if ("okta-local-2".equalsIgnoreCase(def.getIdpEntityAlias())) { - IdentityProvider idp2 = mock(IdentityProvider.class); - when(idp2.getType()).thenReturn(OriginKeys.SAML); - when(idp2.getConfig()).thenReturn(def.clone().setIdpEntityAlias("okta-local-1")); - when(provisioning.retrieveActive(anyString())).thenReturn(Arrays.asList(idp2)); - assertThrowsWithMessageThat( - MetadataProviderException.class, - () -> configurator.validateSamlIdentityProviderDefinition(def, true), - startsWith("Duplicate entity ID:http://www.okta.com") - ); - } - } + void socketFactoryDoesNotGetSet() { + assertThat(singleAdd.getSocketFactoryClassName()).isNull(); + singleAdd.setSocketFactoryClassName("SHOULD_NOT_SET"); + assertThat(singleAdd.getSocketFactoryClassName()).isNull(); } - @Test - public void testIdentityProviderDefinitionSocketFactoryTest() { - singleAdd.setMetaDataLocation("http://www.test.org/saml/metadata"); - assertNull(singleAdd.getSocketFactoryClassName()); - singleAdd.setMetaDataLocation("https://www.test.org/saml/metadata"); - assertNull(singleAdd.getSocketFactoryClassName()); - singleAdd.setSocketFactoryClassName(TLSProtocolSocketFactory.class.getName()); - assertNull(singleAdd.getSocketFactoryClassName()); - } + private List getSamlIdentityProviderDefinitions(List clientIdpAliases) { + String xmlMetadata = getOktaMetadata("http://www.okta.com/k2lw4l5bPODCMIIDBRYZ"); - protected List getSamlIdentityProviderDefinitions(List clientIdpAliases) { SamlIdentityProviderDefinition def1 = new SamlIdentityProviderDefinition() - .setMetaDataLocation(xml) - .setIdpEntityAlias("simplesamlphp-url") - .setNameID("sample-nameID") - .setAssertionConsumerIndex(1) - .setMetadataTrustCheck(true) - .setLinkText("sample-link-test") - .setIconUrl("sample-icon-url") - .setZoneId("other-zone-id"); - IdentityProvider idp1 = mock(IdentityProvider.class); + .setMetaDataLocation(xmlMetadata) + .setIdpEntityAlias("simplesamlphp-url") + .setNameID("sample-nameID") + .setAssertionConsumerIndex(1) + .setMetadataTrustCheck(true) + .setLinkText("sample-link-test") + .setIconUrl("sample-icon-url") + .setZoneId("other-zone-id"); + when(idp1.getType()).thenReturn(OriginKeys.SAML); when(idp1.getConfig()).thenReturn(def1); - - IdentityProvider idp2 = mock(IdentityProvider.class); when(idp2.getType()).thenReturn(OriginKeys.SAML); when(idp2.getConfig()).thenReturn(def1.clone().setIdpEntityAlias("okta-local-2")); - - IdentityProvider idp3 = mock(IdentityProvider.class); - when(idp3.getType()).thenReturn(OriginKeys.SAML); - when(idp3.getConfig()).thenReturn(def1.clone().setIdpEntityAlias("okta-local-3")); - when(provisioning.retrieveActive(anyString())).thenReturn(Arrays.asList(idp1, idp2)); - return configurator.getIdentityProviderDefinitions(clientIdpAliases, IdentityZoneHolder.get()); + return configurator.getIdentityProviderDefinitions(clientIdpAliases, new IdentityZoneManagerImpl().getCurrentIdentityZone()); } @Test - public void testGetIdentityProviderDefinititonsForAllowedProviders() { + void testGetIdentityProviderDefinitionsForAllowedProviders() { List clientIdpAliases = asList("simplesamlphp-url", "okta-local-2"); List clientIdps = getSamlIdentityProviderDefinitions(clientIdpAliases); - assertEquals(2, clientIdps.size()); - assertTrue(clientIdpAliases.contains(clientIdps.get(0).getIdpEntityAlias())); - assertTrue(clientIdpAliases.contains(clientIdps.get(1).getIdpEntityAlias())); + assertThat(clientIdps).hasSize(2); + assertThat(clientIdpAliases).contains(clientIdps.get(0).getIdpEntityAlias(), clientIdps.get(1).getIdpEntityAlias()); } @Test - public void testReturnNoIdpsInZoneForClientWithNoAllowedProviders() { + void testReturnNoIdpsInZoneForClientWithNoAllowedProviders() { List clientIdpAliases = Collections.singletonList("non-existent"); List clientIdps = getSamlIdentityProviderDefinitions(clientIdpAliases); - assertEquals(0, clientIdps.size()); + assertThat(clientIdps).isEmpty(); } - @Rule - public ExpectedException expectedException = ExpectedException.none(); + FixedHttpMetaDataProvider createNonMockFixedHttpMetaDataProvider(SamlConfiguration samlConfiguration) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + RestTemplate trustingRestTemplate = samlConfiguration.trustingRestTemplate(); + RestTemplate nonTrustingRestTemplate = samlConfiguration.nonTrustingRestTemplate(); + UrlContentCache urlContentCache = samlConfiguration.urlContentCache(samlConfiguration.timeService()); - @BeforeEach - public void setupHttp() { - slowHttpServer = new SlowHttpServer(); + return samlConfiguration.fixedHttpMetaDataProvider(trustingRestTemplate, nonTrustingRestTemplate, urlContentCache); } - @AfterEach - public void stopHttp() { - slowHttpServer.stop(); + @Test + void shouldTimeoutOnReadWhenFetchingMetadataURL() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + slowHttpServer.run(); + // set read timeout to value that will cause read timeout before 1s + samlConfiguration.setSocketReadTimeout(100); + FixedHttpMetaDataProvider realFixedHttpMetaDataProvider = createNonMockFixedHttpMetaDataProvider(samlConfiguration); + configurator = new SamlIdentityProviderConfigurator(provisioning, new IdentityZoneManagerImpl(), realFixedHttpMetaDataProvider); + + SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); + def.setMetaDataLocation(slowHttpServer.getUrl()); + def.setSkipSslValidation(true); + + assertTimeoutPreemptively(ofSeconds(1), () -> assertThatThrownBy(() -> configurator.configureURLMetadata(def)) + .isInstanceOf(ResourceAccessException.class) + .hasCauseInstanceOf(SocketTimeoutException.class)); } @Test - public void shouldTimeoutWhenFetchingMetadataURL() { + void shouldTimeoutOnConnectingWhenFetchingMetadataURL() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { slowHttpServer.run(); - - expectedException.expect(NullPointerException.class); + // Set connection timeout to very low value to cause connect timeout + samlConfiguration.setSocketConnectionTimeout(1); + FixedHttpMetaDataProvider realFixedHttpMetaDataProvider = createNonMockFixedHttpMetaDataProvider(samlConfiguration); + configurator = new SamlIdentityProviderConfigurator(provisioning, new IdentityZoneManagerImpl(), realFixedHttpMetaDataProvider); SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); - def.setMetaDataLocation("https://localhost:23439"); + def.setMetaDataLocation(slowHttpServer.getUrl()); def.setSkipSslValidation(true); - Assertions.assertTimeout(ofSeconds(1), () -> { - Assertions.assertThrows(NullPointerException.class, () -> configurator.configureURLMetadata(def)); - }); + assertTimeoutPreemptively(ofSeconds(1), () -> assertThatThrownBy(() -> configurator.configureURLMetadata(def)) + .isInstanceOf(ResourceAccessException.class) + .hasCauseInstanceOf(ConnectTimeoutException.class)); + } + + private String getSimpleSamlPhpMetadata(String domain) { + // %1$s gets replaced with the domain + return """ + + + + + +sYzzLx/5TXtBZhC03uaQT0E/L8=gt9z/i8o16H0KQfV8+gCLgrBYOgaWsQe1Bon3G3UJQqc+z7YTNXl6rX69wbcQum/95KiLcF41BHoCeA4KZL75HE6mpXAF8NrPZiXlwwJFZe31HIfwmeu7JavuB/8QotWraM/u9DGtHVfDWFT92MPr18Odbvl62Gd2zA2PdZR3rz7DsrFc1QSB/Qz1VnQ+3Y8OUBRFDeZZUsNGRJ/l/GfYkiqmyV4fOak6bz0WeCSxY3tOl+F9X8r2gOHxOp3QRtRaK/UElRmPxnYC7UESI0Rq0AphHO6vRulA/EpSXTwu4qgZ6nDtGBOW/C+nQmg8zkv0QPvzk5IE2eaAAE3jkZq4w== + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + Filip + Hanik + fhanik@pivotal.io + + + """.formatted(domain); + } + + private String getOktaMetadata(String entityId) { + return """ + + + + + + + MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu + Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC + VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM + BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN + AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU + WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O + Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL + 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk + vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6 + GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFb + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + + + + """.formatted(entityId); } -} \ No newline at end of file +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManagerFactoryTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManagerFactoryTests.java index cd994f10ce3..0cb4161b815 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManagerFactoryTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlKeyManagerFactoryTests.java @@ -1,265 +1,172 @@ package org.cloudfoundry.identity.uaa.provider.saml; import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; -import org.cloudfoundry.identity.uaa.saml.SamlKey; +import org.cloudfoundry.identity.uaa.util.KeyWithCert; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.cloudfoundry.identity.uaa.zone.SamlConfig; -import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.security.saml.key.JKSKeyManager; -import org.springframework.test.util.ReflectionTestUtils; -import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.Security; import java.util.HashMap; - -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.*; - -public class SamlKeyManagerFactoryTests { - - public static final String legacyKey = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5\n" + - "L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vA\n" + - "fpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQAB\n" + - "AoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges\n" + - "7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGu\n" + - "lhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3Xp\n" + - "ubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYX\n" + - "kKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyL\n" + - "gu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWK\n" + - "vw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCe\n" + - "A2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHS\n" + - "N+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMB\n" + - "qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/\n" + - "-----END RSA PRIVATE KEY-----"; - public static final String legacyPassphrase = "password"; - public static final String legacyCertificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO\n" + - "MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO\n" + - "MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h\n" + - "cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx\n" + - "CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM\n" + - "BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb\n" + - "BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN\n" + - "ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W\n" + - "qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw\n" + - "znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha\n" + - "MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc\n" + - "gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD\n" + - "VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD\n" + - "VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh\n" + - "QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ\n" + - "0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC\n" + - "KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK\n" + - "RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + - "-----END CERTIFICATE-----"; - - public static final String key1 = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIIEogIBAAKCAQEArRkvkddLUoNyuvu0ktkcLL0CyGG8Drh9oPsaVOLVHJqB1Ebr\n" + - "oNMTPbY0HPjuD5WBDZTi3ftNLp1mPn9wFy6FhMTvIYeQmTskH8m/kyVReXG/zfWq\n" + - "a4+V6UW4nmUcvfF3YNrHvN5VPTWTJrc2KBzseWQ70OaBNfBi6z4XbdOF45dDfck2\n" + - "oRnasinUv+rG+PUl7x8OjgdVyyen6qeCQ6xt8W9fHg//Nydlfwb3/L+syPoBujdu\n" + - "Hai7GoLUzm/zqOM9dhlR5mjuEJ3QUvnmGKrGDoeHFog0CMgLC+C0Z4ZANB6GbjlM\n" + - "bsQczsaYxHMqAMOnOe6xIXUrPOoc7rclwZeHMQIDAQABAoIBAAFB2ZKZmbZztfWd\n" + - "tmYKpaW9ibOi4hbJSEBPEpXjP+EBTkgYa8WzQsSD+kTrme8LCvDqT+uE076u7fsu\n" + - "OcYxVE7ujz4TGf3C7DQ+5uFOuBTFurroOeCmHlSfaQPdgCPxCQjvDdxVUREsvnDd\n" + - "i8smyqDnFXgi9HVL1awXu1vU2XgZshfl6wBOCNomVMCN8mVcBQ0KM88SUvoUwM7i\n" + - "sSdj1yQV16Za8+nVnMW41FMHegVRd3Y5EsXJfwGuXnZMIG87PavH1nUqn9NOFq9Y\n" + - "kb4SeOO47PaMxv7jMaXltVVokdGH8L/BY4we8tBL+wVeUJ94aYx/Q/LUAtRPbKPS\n" + - "ZSEi/7ECgYEA3dUg8DXzo59zl5a8kfz3aoLl8RqRYzuf8F396IuiVcqYlwlWOkZW\n" + - "javwviEOEdZhUZPxK1duXKTvYw7s6eDFwV+CklTZu4A8M3Os0D8bSL/pIKqcadt5\n" + - "JClIRmOmmQpj9AYhSdBTdQtJGjVDaDXJBb7902pDm9I4jMFbjAKLZNsCgYEAx8J3\n" + - "Y1c7GwHw6dxvTywrw3U6z1ILbx2olVLY6DIgZaMVT4EKTAv2Ke4xF4OZYG+lLRbt\n" + - "hhOHYzRMYC38MNl/9RXHBgUlQJXOQb9u644motl5dcMvzIIuWFCn5vXxR2C3McNy\n" + - "vPdzYS2M64xRGy+IENtPSCcUs9C99bEajRcuG+MCgYAONabEfFA8/OvEnA08NL4M\n" + - "fpIIHbGOb7VRClRHXxpo8G9RzXFOjk7hCFCFfUyPa/IT7awXIKSbHp2O9NfMK2+/\n" + - "cUTF5tWDozU3/oLlXAV9ZX2jcApQ5ZQe8t4EVEHJr9azPOlI9yVBbBWkriDBPiDA\n" + - "U3mi3z2xb4fbzE726vrO3QKBgA6PfTZPgG5qiM3zFGX3+USpAd1kxJKX3dbskAT0\n" + - "ymm+JmqCJGcApDPQOeHV5NMjsC2GM1AHkmHHyR1lnLFO2UXbDYPB0kJP6RXfx00C\n" + - "MozCP1k3Hf/RKWGkl2h9WtXyFchZz744Zz+ZG2F7+9l4cHmSEshWmOq2d3I2M5I/\n" + - "M0wzAoGAa2oM4Q6n+FMHl9e8H+2O4Dgm7wAdhuZI1LhnLL6GLVC1JTmGrz/6G2TX\n" + - "iNFhc0lnDcVeZlwg4i7M7MH8UFdWj3ZEylsXjrjIspuAJg7a/6qmP9s2ITVffqYk\n" + - "2slwG2SIQchM5/0uOiP9W0YIjYEe7hgHUmL9Rh8xFuo9y72GH8c=\n" + - "-----END RSA PRIVATE KEY-----"; - public static final String passphrase1 = "password"; - public static final String certificate1 = "-----BEGIN CERTIFICATE-----\n" + - "MIID0DCCArgCCQDBRxU0ucjw6DANBgkqhkiG9w0BAQsFADCBqTELMAkGA1UEBhMC\n" + - "VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMR8wHQYDVQQK\n" + - "ExZDbG91ZCBGb3VuZHJ5IElkZW50aXR5MQ4wDAYDVQQLEwVLZXkgMTEiMCAGA1UE\n" + - "AxMZbG9naW4uaWRlbnRpdHkuY2YtYXBwLmNvbTEgMB4GCSqGSIb3DQEJARYRZmhh\n" + - "bmlrQHBpdm90YWwuaW8wHhcNMTcwNDEwMTkxMTIyWhcNMTgwNDEwMTkxMTIyWjCB\n" + - "qTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp\n" + - "c2NvMR8wHQYDVQQKExZDbG91ZCBGb3VuZHJ5IElkZW50aXR5MQ4wDAYDVQQLEwVL\n" + - "ZXkgMTEiMCAGA1UEAxMZbG9naW4uaWRlbnRpdHkuY2YtYXBwLmNvbTEgMB4GCSqG\n" + - "SIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IB\n" + - "DwAwggEKAoIBAQCtGS+R10tSg3K6+7SS2RwsvQLIYbwOuH2g+xpU4tUcmoHURuug\n" + - "0xM9tjQc+O4PlYENlOLd+00unWY+f3AXLoWExO8hh5CZOyQfyb+TJVF5cb/N9apr\n" + - "j5XpRbieZRy98Xdg2se83lU9NZMmtzYoHOx5ZDvQ5oE18GLrPhdt04Xjl0N9yTah\n" + - "GdqyKdS/6sb49SXvHw6OB1XLJ6fqp4JDrG3xb18eD/83J2V/Bvf8v6zI+gG6N24d\n" + - "qLsagtTOb/Oo4z12GVHmaO4QndBS+eYYqsYOh4cWiDQIyAsL4LRnhkA0HoZuOUxu\n" + - "xBzOxpjEcyoAw6c57rEhdSs86hzutyXBl4cxAgMBAAEwDQYJKoZIhvcNAQELBQAD\n" + - "ggEBAB72QKF9Iri+UdCGAIok/qIeKw5AwZ0wtiONa+DF4B80/yAA1ObpuO3eeeka\n" + - "t0s4wtCRflE08zLrwqHlvKQAGKmJkfRLfEqfKStIUOTHQxE6wOaBtfW41M9ZF1hX\n" + - "NHpnkfmSQjaHVNTRbABiFH6eTq8J6CuO12PyDf7lW3EofvcTU3ulsDhuMAz02ypJ\n" + - "BgcOufnl+qP/m/BhVQsRD5mtJ56uJpHvri1VR2kj8N59V8f6KPO2m5Q6MulEhWml\n" + - "TsxyxUl03oyICDP1cbpYtDk2VddVNWipHHPH/mBVW41EBVv0VDV03LH3RfS9dXiK\n" + - "ynuP3shhqhFvaaiUTZP4l5yF/GQ=\n" + - "-----END CERTIFICATE-----"; - - public static final String key2 = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIIEpAIBAAKCAQEAwt7buITRZhXX98apcgJbiHhrPkrgn5MCsCphRQ89oWPUHWjN\n" + - "j9Kz2m9LaKgq9DnNLl22U4e6/LUQToBCLxkIqwaobZKjIUjNAmNomqbNO7AD2+K7\n" + - "RCiQ2qijWUwXGu+5+fSmF/MOermNKUDiQnRJSSSAPObAHOI980zTWVsApKpcFVaV\n" + - "vk/299L/0rk8I/mNvf63cdw4Nh3xn4Ct+oCnTaDg5OtpGz8sHlocOAti+LdrtNzH\n" + - "uBWq8q2sdhFQBRGe1MOeH8CAEHgKYwELTBCJEyLhykdRgxXJHSaL56+mb6HQvGO/\n" + - "oyZHn+qHsCCjcdR1L/U4qt4m7HBimv0qbvApQwIDAQABAoIBAQCftmmcnHbG1WZR\n" + - "NChSQa5ldlRnFJVvE90jJ0jbgfdAHAKQLAI2Ozme8JJ8bz/tNKZ+tt2lLlxJm9iG\n" + - "jkYwNbNOAMHwNDuxHuqvZ2wnPEh+/+7Zu8VBwoGeRJLEsEFLmWjyfNnYTSPz37nb\n" + - "Mst+LbKW2OylfXW89oxRqQibdqNbULpcU4NBDkMjToH1Z4dUFx3X2R2AAwgDz4Ku\n" + - "HN4HoxbsbUCI5wLDJrTGrJgEntMSdsSdOY48YOMBnHqqfw7KoJ0sGjrPUy0vOGq2\n" + - "CeP3uqbXX/mJpvJ+jg3Y2b1Zeu2I+vAnZrxlaZ+hYnZfoNqVjBZ/EEq/lmEovMvr\n" + - "erP8FYI5AoGBAOrlmMZYdhW0fRzfpx6WiBJUkFfmit4qs9nQRCouv+jHS5QL9aM9\n" + - "c+iKeP6kWuxBUYaDBmf5J1OBW4omNd384NX5PCiL/Fs/lxgdMZqEhnhT4Dj4Q6m6\n" + - "ZXUuY6hamoF5+z2mtkZzRyvD1LUAARKJw6ggUtcH28cYC3RkZ5P6SWHVAoGBANRg\n" + - "scI9pF2VUrmwpgIGhynLBEO26k8j/FyE3S7lPcUZdgPCUZB0/tGklSo183KT/KQY\n" + - "TgO2mqb8a8xKCz41DTnUPqJWZzBOFw5QaD2i9O6soXUAKqaUm3g40/gyWX1hUtHa\n" + - "K0Kw5z1Sf3MoCpW0Ozzn3znYbAoSvBRr53d0EVK3AoGAOD1ObbbCVwIGroIR1i3+\n" + - "WD0s7g7Bkt2wf+bwWxUkV4xX2RNf9XyCItv8iiM5rbUZ2tXGE+DAfKrNCu+JGCQy\n" + - "hKiOsbqKaiJ4f4qF1NQECg0y8xDlyl5Zakv4ClffBD77W1Bt9cIl+SGC7O8aUqDv\n" + - "WnKawucbxLhKDcz4S6KyLR0CgYEAhuRrw24XqgEgLCVRK9QtoZP7P28838uBjNov\n" + - "Cow8caY8WSLhX5mQCGQ7AjaGTG5Gd4ugcadYD1wgs/8LqRVVMzfmGII8xGe1KThV\n" + - "HWEVpUssuf3DGU8meHPP3sNMJ+DbE8M42wE1vrNZlDEImBGD1qmIFVurM7K2l1n6\n" + - "CNtF7X0CgYBuFf0A0cna8LnxOAPm8EPHgFq4TnDU7BJzzcO/nsORDcrh+dZyGJNS\n" + - "fUTMp4k+AQCm9UwJAiSf4VUwCbhXUZ3S+xB55vrH+Yc2OMtsIYhzr3OCkbgKBMDn\n" + - "nBVKSGAomYD2kCUmSbg7bUrFfGntmvOLqTHtVfrCyE5i8qS63RbHlA==\n" + - "-----END RSA PRIVATE KEY-----"; - public static final String passphrase2 = "password"; - public static final String certificate2 = "-----BEGIN CERTIFICATE-----\n" + - "MIID0DCCArgCCQDqnPTUvA17+TANBgkqhkiG9w0BAQsFADCBqTELMAkGA1UEBhMC\n" + - "VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMR8wHQYDVQQK\n" + - "ExZDbG91ZCBGb3VuZHJ5IElkZW50aXR5MQ4wDAYDVQQLEwVLZXkgMjEiMCAGA1UE\n" + - "AxMZbG9naW4uaWRlbnRpdHkuY2YtYXBwLmNvbTEgMB4GCSqGSIb3DQEJARYRZmhh\n" + - "bmlrQHBpdm90YWwuaW8wHhcNMTcwNDEwMTkxNTAyWhcNMTgwNDEwMTkxNTAyWjCB\n" + - "qTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp\n" + - "c2NvMR8wHQYDVQQKExZDbG91ZCBGb3VuZHJ5IElkZW50aXR5MQ4wDAYDVQQLEwVL\n" + - "ZXkgMjEiMCAGA1UEAxMZbG9naW4uaWRlbnRpdHkuY2YtYXBwLmNvbTEgMB4GCSqG\n" + - "SIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IB\n" + - "DwAwggEKAoIBAQDC3tu4hNFmFdf3xqlyAluIeGs+SuCfkwKwKmFFDz2hY9QdaM2P\n" + - "0rPab0toqCr0Oc0uXbZTh7r8tRBOgEIvGQirBqhtkqMhSM0CY2iaps07sAPb4rtE\n" + - "KJDaqKNZTBca77n59KYX8w56uY0pQOJCdElJJIA85sAc4j3zTNNZWwCkqlwVVpW+\n" + - "T/b30v/SuTwj+Y29/rdx3Dg2HfGfgK36gKdNoODk62kbPyweWhw4C2L4t2u03Me4\n" + - "Faryrax2EVAFEZ7Uw54fwIAQeApjAQtMEIkTIuHKR1GDFckdJovnr6ZvodC8Y7+j\n" + - "Jkef6oewIKNx1HUv9Tiq3ibscGKa/Spu8ClDAgMBAAEwDQYJKoZIhvcNAQELBQAD\n" + - "ggEBAKzeh/bRDEEP/WGsiYhCCfvESyt0QeKwUk+Hfl0/oP4m9pXNrnMRApyoi7FB\n" + - "owpmXIeqDqGigPai6pJ3xCO94P+Bz7WTk0+jScYm/hGpcIOeKh8FBfW0Fddu9Otn\n" + - "qVk0FdRSCTjUZKQlNOqVTjBeKOjHmTkgh96IR3EP2/hp8Ym4HLC+w265V7LnkqD2\n" + - "SoMez7b2V4NmN7z9OxTALUbTzmFG77bBDExHvfbiFlkIptx8+IloJOCzUsPEg6Ur\n" + - "kueuR7IB1S4q6Ja7Gb9b9NYQDFt4hjb5mC9aPxaX+KK2JlZg4cTFVCdkIyp2/fHI\n" + - "iQpMzNWb7zZWlCfDL4dJZHYoNfg=\n" + - "-----END CERTIFICATE-----"; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.bareCertificate1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.bareCertificate2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.bareLegacyCertificate; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName3; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyCertificate; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyKey; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyKeyName; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyPassphrase; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacySamlKey; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKeyCertOnly; + +class SamlKeyManagerFactoryTests { private SamlKeyManagerFactory samlKeyManagerFactory; private SamlConfig config; + private final SamlConfigProps samlConfigProps = new SamlConfigProps(); + @BeforeAll - static void addBCProvider() { - try { - Security.addProvider(new BouncyCastleFipsProvider()); - } catch (SecurityException e) { - e.printStackTrace(); - System.err.println("Ignoring provider error, may already be added."); - } + static void beforeAll() { + Security.addProvider(new BouncyCastleFipsProvider()); } @BeforeEach - void setup() { - samlKeyManagerFactory = new SamlKeyManagerFactory(); - + void beforeEach() { IdentityZoneHolder.clear(); config = new SamlConfig(); - config.setPrivateKey(legacyKey); - config.setCertificate(legacyCertificate); - config.setPrivateKeyPassword(legacyPassphrase); + config.setPrivateKey(legacyKey()); + config.setCertificate(legacyCertificate()); + config.setPrivateKeyPassword(legacyPassphrase()); + + config.addKey(keyName1(), samlKey1()); + config.addKey(keyName2(), samlKey2()); - config.addKey("key-1", new SamlKey(key1, passphrase1, certificate1)); - config.addKey("key-2", new SamlKey(key2, passphrase2, certificate2)); + samlKeyManagerFactory = new SamlKeyManagerFactory(samlConfigProps); } - @AfterEach - void clear() { + @AfterAll + static void afterAll() { IdentityZoneHolder.clear(); } + @Test + void withKeysInSamlConfig_Returns_SamlConfigSamlKeyManagerImpl() { + SamlKeyManager manager = samlKeyManagerFactory.getKeyManager(config); + assertThat(manager).isInstanceOf(SamlKeyManagerFactory.SamlConfigSamlKeyManagerImpl.class); + assertThat(manager.getDefaultCredentialName()).isEqualTo(legacyKeyName()); + assertThat(manager.getAvailableCredentials()) + .hasSize(3) + .extracting(KeyWithCert::getEncodedCertificate) + .contains(bareLegacyCertificate(), bareCertificate1(), bareCertificate2()) + .first() + .isEqualTo(bareLegacyCertificate()); + assertThat(manager.getAvailableCredentialIds()) + .contains(legacyKeyName(), keyName1(), keyName2()) + .first() + .isEqualTo(legacyKeyName()); + + assertThat(manager.getDefaultCredential().getCertificate()) + .isEqualTo(manager.getCredential(legacyKeyName()).getCertificate()); + assertThat(manager.getCredential("notFound")).isNull(); + } + + @Test + void withNoKeysInSamlConfig_FallsBackTo_SamlConfigPropsSamlKeyManagerImpl() { + samlConfigProps.setKeys(Map.of(legacyKeyName(), legacySamlKey(), + keyName1(), samlKey1(), + keyName2(), samlKey2())); + samlConfigProps.setActiveKeyId(legacyKeyName()); + + SamlKeyManager manager = samlKeyManagerFactory.getKeyManager(new SamlConfig()); + assertThat(manager).isInstanceOf(SamlKeyManagerFactory.SamlConfigPropsSamlKeyManagerImpl.class); + assertThat(manager.getDefaultCredentialName()).isEqualTo(legacyKeyName()); + assertThat(manager.getAvailableCredentials()) + .hasSize(3) + .extracting(KeyWithCert::getEncodedCertificate) + .contains(bareLegacyCertificate(), bareCertificate1(), bareCertificate2()) + .first() + .isEqualTo(bareLegacyCertificate()); + assertThat(manager.getAvailableCredentialIds()) + .contains(legacyKeyName(), keyName1(), keyName2()) + .first() + .isEqualTo(legacyKeyName()); + + assertThat(manager.getDefaultCredential().getCertificate()) + .isEqualTo(manager.getCredential(legacyKeyName()).getCertificate()); + assertThat(manager.getCredential("notFound")).isNull(); + } + @Test void multipleKeysLegacyIsActiveKey() { - String alias = SamlConfig.LEGACY_KEY_ID; - JKSKeyManager manager = (JKSKeyManager) samlKeyManagerFactory.getKeyManager(config); - assertEquals(alias, manager.getDefaultCredentialName()); - assertEquals(3, manager.getAvailableCredentials().size()); - assertThat(manager.getAvailableCredentials(), containsInAnyOrder(SamlConfig.LEGACY_KEY_ID, "key-1", "key-2")); + SamlKeyManager manager = samlKeyManagerFactory.getKeyManager(config); + assertThat(manager.getDefaultCredentialName()).isEqualTo(legacyKeyName()); + assertThat(manager.getAvailableCredentials()).hasSize(3); + assertThat(manager.getAvailableCredentialIds()) + .contains(legacyKeyName(), keyName1(), keyName2()) + .first() + .isEqualTo(legacyKeyName()); + assertThat(manager.getCredential(keyName1())).isNotNull(); + assertThat(manager.getCredential("notFound")).isNull(); } @Test void multipleKeysWithActiveKey() { - config.setActiveKeyId("key-1"); - String alias = "key-1"; - JKSKeyManager manager = (JKSKeyManager) samlKeyManagerFactory.getKeyManager(config); - assertEquals(alias, manager.getDefaultCredentialName()); - assertEquals(3, manager.getAvailableCredentials().size()); - assertThat(manager.getAvailableCredentials(), containsInAnyOrder(SamlConfig.LEGACY_KEY_ID + "", "key-1", "key-2")); + config.setActiveKeyId(keyName1()); + + SamlKeyManager manager = samlKeyManagerFactory.getKeyManager(config); + assertThat(manager.getDefaultCredentialName()).isEqualTo(keyName1()); + assertThat(manager.getAvailableCredentials()).hasSize(3); + assertThat(manager.getAvailableCredentialIds()) + .containsOnly(legacyKeyName(), keyName1(), keyName2()) + .first() + .isEqualTo(keyName1()); + assertThat(manager.getDefaultCredential().getCertificate()) + .isEqualTo(manager.getCredential(keyName1()).getCertificate()); } @Test void addActiveKey() { - config.addAndActivateKey("key-3", new SamlKey(key1, passphrase1, certificate1)); - String alias = "key-3"; - JKSKeyManager manager = (JKSKeyManager) samlKeyManagerFactory.getKeyManager(config); - assertEquals(alias, manager.getDefaultCredentialName()); - assertEquals(4, manager.getAvailableCredentials().size()); - assertThat(manager.getAvailableCredentials(), containsInAnyOrder(SamlConfig.LEGACY_KEY_ID, "key-1", "key-2", alias)); + config.addAndActivateKey(keyName3(), samlKey1()); + SamlKeyManager manager = samlKeyManagerFactory.getKeyManager(config); + assertThat(manager.getDefaultCredentialName()).isEqualTo(keyName3()); + assertThat(manager.getAvailableCredentials()).hasSize(4); + assertThat(manager.getAvailableCredentialIds()) + .containsOnly(legacyKeyName(), keyName1(), keyName2(), keyName3()) + .first() + .isEqualTo(keyName3()); } @Test void multipleKeysWithActiveKeyInOtherZone() { IdentityZoneHolder.set(MultitenancyFixture.identityZone("other-zone-id", "domain")); - config.setActiveKeyId("key-1"); - String alias = "key-1"; - JKSKeyManager manager = (JKSKeyManager) samlKeyManagerFactory.getKeyManager(config); - assertEquals(alias, manager.getDefaultCredentialName()); - assertEquals(3, manager.getAvailableCredentials().size()); - assertThat(manager.getAvailableCredentials(), containsInAnyOrder(SamlConfig.LEGACY_KEY_ID, "key-1", "key-2")); - } - - @Test - void keystoreImplsIsNotASingleton() throws KeyStoreException { - assertNotSame(KeyStore.getInstance("JKS"), KeyStore.getInstance("JKS")); - JKSKeyManager manager1 = (JKSKeyManager) samlKeyManagerFactory.getKeyManager(config); - config.setKeys(new HashMap<>()); - config.setPrivateKey(key1); - config.setPrivateKeyPassword("password"); - config.setCertificate(certificate1); - - JKSKeyManager manager2 = (JKSKeyManager) samlKeyManagerFactory.getKeyManager(config); - KeyStore ks1 = (KeyStore) ReflectionTestUtils.getField(manager1, JKSKeyManager.class, "keyStore"); - KeyStore ks2 = (KeyStore) ReflectionTestUtils.getField(manager2, JKSKeyManager.class, "keyStore"); - - String alias = SamlConfig.LEGACY_KEY_ID; - - assertNotEquals(ks1.getCertificate(alias), ks2.getCertificate(alias)); - assertEquals(ks1.getCertificate(alias), ks1.getCertificate(alias)); + config.setActiveKeyId(keyName1()); + SamlKeyManager manager = samlKeyManagerFactory.getKeyManager(config); + assertThat(manager.getDefaultCredentialName()).isEqualTo(keyName1()); + assertThat(manager.getAvailableCredentials()).hasSize(3); + assertThat(manager.getAvailableCredentialIds()) + .containsOnly(legacyKeyName(), keyName1(), keyName2()) + .first() + .isEqualTo(keyName1()); } @Test void testAddCertsKeysOnly() { config.setKeys(new HashMap<>()); - config.addAndActivateKey("cert-only", new SamlKey(null, null, certificate1)); - JKSKeyManager manager1 = (JKSKeyManager) samlKeyManagerFactory.getKeyManager(config); - assertNotNull(manager1.getDefaultCredential().getPublicKey()); - assertNull(manager1.getDefaultCredential().getPrivateKey()); + config.addAndActivateKey("cert-only", samlKeyCertOnly()); + SamlKeyManager manager1 = samlKeyManagerFactory.getKeyManager(config); + assertThat(manager1.getDefaultCredential()).isNotNull(); + assertThat(manager1.getDefaultCredential().getPrivateKey()).isNull(); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationFailureHandlerTest.java similarity index 70% rename from server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandlerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationFailureHandlerTest.java index b35fb707bbb..5306e6ddacc 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandlerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationFailureHandlerTest.java @@ -1,5 +1,6 @@ package org.cloudfoundry.identity.uaa.provider.saml; +import org.apache.http.HttpStatus; import org.cloudfoundry.identity.uaa.util.SessionUtils; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; @@ -10,22 +11,22 @@ import javax.servlet.ServletException; import java.io.IOException; +import java.io.Serial; import java.util.HashMap; import java.util.Map; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class LoginSAMLAuthenticationFailureHandlerTest { +public class SamlLoginAuthenticationFailureHandlerTest { @Test public void testErrorRedirect() throws IOException, ServletException { - LoginSAMLAuthenticationFailureHandler handler = new LoginSAMLAuthenticationFailureHandler(); + SamlLoginAuthenticationFailureHandler handler = new SamlLoginAuthenticationFailureHandler(); DefaultSavedRequest savedRequest = mock(DefaultSavedRequest.class); - Map parameterMap = new HashMap(); + Map parameterMap = new HashMap<>(); parameterMap.put("redirect_uri", new String[] { "https://example.com" }); when(savedRequest.getParameterMap()).thenReturn(parameterMap); @@ -35,21 +36,21 @@ public void testErrorRedirect() throws IOException, ServletException { request.setSession(session); MockHttpServletResponse response = new MockHttpServletResponse(); - LoginSAMLException exception = new LoginSAMLException("Denied!"); + SamlLoginException exception = new SamlLoginException("Denied!"); handler.onAuthenticationFailure(request, response, exception); String actual = response.getRedirectedUrl(); - assertEquals("https://example.com?error=access_denied&error_description=Denied%21", actual); + assertThat(actual).isEqualTo("https://example.com?error=access_denied&error_description=Denied%21"); int status = response.getStatus(); - assertEquals(302, status); + assertThat(status).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); } @Test public void testErrorRedirectWithExistingQueryParameters() throws IOException, ServletException { - LoginSAMLAuthenticationFailureHandler handler = new LoginSAMLAuthenticationFailureHandler(); + SamlLoginAuthenticationFailureHandler handler = new SamlLoginAuthenticationFailureHandler(); DefaultSavedRequest savedRequest = mock(DefaultSavedRequest.class); - Map parameterMap = new HashMap(); + Map parameterMap = new HashMap<>(); parameterMap.put("redirect_uri", new String[] { "https://example.com?go=bears" }); when(savedRequest.getParameterMap()).thenReturn(parameterMap); @@ -59,21 +60,21 @@ public void testErrorRedirectWithExistingQueryParameters() throws IOException, S request.setSession(session); MockHttpServletResponse response = new MockHttpServletResponse(); - LoginSAMLException exception = new LoginSAMLException("Denied!"); + SamlLoginException exception = new SamlLoginException("Denied!"); handler.onAuthenticationFailure(request, response, exception); String actual = response.getRedirectedUrl(); - assertEquals("https://example.com?go=bears&error=access_denied&error_description=Denied%21", actual); + assertThat(actual).isEqualTo("https://example.com?go=bears&error=access_denied&error_description=Denied%21"); int status = response.getStatus(); - assertEquals(302, status); + assertThat(status).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); } @Test public void testSomeOtherErrorCondition() throws IOException, ServletException { - LoginSAMLAuthenticationFailureHandler handler = new LoginSAMLAuthenticationFailureHandler(); + SamlLoginAuthenticationFailureHandler handler = new SamlLoginAuthenticationFailureHandler(); DefaultSavedRequest savedRequest = mock(DefaultSavedRequest.class); - Map parameterMap = new HashMap(); + Map parameterMap = new HashMap<>(); parameterMap.put("redirect_uri", new String[] { "https://example.com?go=bears" }); when(savedRequest.getParameterMap()).thenReturn(parameterMap); @@ -87,37 +88,38 @@ public void testSomeOtherErrorCondition() throws IOException, ServletException { /** * */ + @Serial private static final long serialVersionUID = 1L; }; handler.onAuthenticationFailure(request, response, exception); String actual = response.getRedirectedUrl(); - assertNull(actual); + assertThat(actual).isNull(); int status = response.getStatus(); - assertEquals(401, status); + assertThat(status).isEqualTo(HttpStatus.SC_UNAUTHORIZED); } @Test public void testNoSession() throws IOException, ServletException { - LoginSAMLAuthenticationFailureHandler handler = new LoginSAMLAuthenticationFailureHandler(); + SamlLoginAuthenticationFailureHandler handler = new SamlLoginAuthenticationFailureHandler(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); - LoginSAMLException exception = new LoginSAMLException("Denied!"); + SamlLoginException exception = new SamlLoginException("Denied!"); handler.onAuthenticationFailure(request, response, exception); String actual = response.getRedirectedUrl(); - assertNull(actual); + assertThat(actual).isNull(); int status = response.getStatus(); - assertEquals(401, status); + assertThat(status).isEqualTo(HttpStatus.SC_UNAUTHORIZED); } @Test public void testNoSavedRequest() throws IOException, ServletException { - LoginSAMLAuthenticationFailureHandler handler = new LoginSAMLAuthenticationFailureHandler(); + SamlLoginAuthenticationFailureHandler handler = new SamlLoginAuthenticationFailureHandler(); DefaultSavedRequest savedRequest = mock(DefaultSavedRequest.class); - Map parameterMap = new HashMap(); + Map parameterMap = new HashMap<>(); parameterMap.put("redirect_uri", new String[] { "https://example.com" }); when(savedRequest.getParameterMap()).thenReturn(parameterMap); @@ -126,21 +128,21 @@ public void testNoSavedRequest() throws IOException, ServletException { request.setSession(session); MockHttpServletResponse response = new MockHttpServletResponse(); - LoginSAMLException exception = new LoginSAMLException("Denied!"); + SamlLoginException exception = new SamlLoginException("Denied!"); handler.onAuthenticationFailure(request, response, exception); String actual = response.getRedirectedUrl(); - assertNull(actual); + assertThat(actual).isNull(); int status = response.getStatus(); - assertEquals(401, status); + assertThat(status).isEqualTo(HttpStatus.SC_UNAUTHORIZED); } @Test public void testNoRedirectURI() throws IOException, ServletException { - LoginSAMLAuthenticationFailureHandler handler = new LoginSAMLAuthenticationFailureHandler(); + SamlLoginAuthenticationFailureHandler handler = new SamlLoginAuthenticationFailureHandler(); DefaultSavedRequest savedRequest = mock(DefaultSavedRequest.class); - Map parameterMap = new HashMap(); + Map parameterMap = new HashMap<>(); when(savedRequest.getParameterMap()).thenReturn(parameterMap); MockHttpSession session = new MockHttpSession(); @@ -149,11 +151,11 @@ public void testNoRedirectURI() throws IOException, ServletException { request.setSession(session); MockHttpServletResponse response = new MockHttpServletResponse(); - LoginSAMLException exception = new LoginSAMLException("Denied!"); + SamlLoginException exception = new SamlLoginException("Denied!"); handler.onAuthenticationFailure(request, response, exception); String actual = response.getRedirectedUrl(); - assertNull(actual); + assertThat(actual).isNull(); int status = response.getStatus(); - assertEquals(401, status); + assertThat(status).isEqualTo(HttpStatus.SC_UNAUTHORIZED); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpointKeyRotationTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpointKeyRotationTests.java new file mode 100644 index 00000000000..41d598201c6 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpointKeyRotationTests.java @@ -0,0 +1,173 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; +import org.xmlunit.assertj.MultipleNodeAssert; +import org.xmlunit.assertj.XmlAssert; + +import java.security.Security; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.xmlNamespaces; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.certificate1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.certificate2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyKeyName; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacySamlKey; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey2; +import static org.mockito.Mockito.spy; + +public class SamlMetadataEndpointKeyRotationTests { + + private static final String ZONE_ID = "zone-id"; + private static final String REGISTRATION_ID = "regId"; + private static final String NAME_ID_FORMAT = "nameIdFormat"; + private static final String ENTITY_ID = "entityIdValue"; + private static final String ENTITY_ALIAS = "entityAlias"; + public static final String KEY_DESCRIPTOR_CERTIFICATE_XPATH_FORMAT = "//md:SPSSODescriptor/md:KeyDescriptor[@use='%s']//ds:X509Certificate"; + + private static IdentityZoneHolder.Initializer initializer; + + private SamlMetadataEndpoint endpoint; + private SamlConfig samlConfig; + + private MockHttpServletRequest request; + + @BeforeAll + static void beforeAll() { + Security.addProvider(new BouncyCastleFipsProvider()); + + SamlConfigProps samlConfigProps = new SamlConfigProps(); + samlConfigProps.setKeys(Map.of(legacyKeyName(), legacySamlKey())); + samlConfigProps.setActiveKeyId(legacyKeyName()); + samlConfigProps.setEntityIDAlias(ENTITY_ALIAS); + samlConfigProps.setSignMetaData(true); + + SamlKeyManagerFactory samlKeyManagerFactory = new SamlKeyManagerFactory(samlConfigProps); + initializer = new IdentityZoneHolder.Initializer(null, samlKeyManagerFactory); + } + + @BeforeEach + void beforeEach() { + IdentityZone otherZone = new IdentityZone(); + otherZone.setId(ZONE_ID); + otherZone.setName(ZONE_ID); + otherZone.setSubdomain(ZONE_ID); + IdentityZoneConfiguration otherZoneDefinition = new IdentityZoneConfiguration(); + otherZone.setConfig(otherZoneDefinition); + + samlConfig = otherZoneDefinition.getSamlConfig(); + samlConfig.setRequestSigned(true); + samlConfig.setWantAssertionSigned(true); + samlConfig.setEntityID(ENTITY_ID); + otherZoneDefinition.setIdpDiscoveryEnabled(true); + samlConfig.addAndActivateKey(keyName1(), samlKey1()); + + IdentityZoneManager identityZoneManager = new IdentityZoneManagerImpl(); + + RelyingPartyRegistrationRepository registrationRepository = + new DefaultRelyingPartyRegistrationRepository("entityId", "entityIdAlias", List.of(), NAME_ID_FORMAT); + RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrationRepository); + endpoint = spy(new SamlMetadataEndpoint(registrationResolver, identityZoneManager, SignatureAlgorithm.SHA256, true)); + IdentityZoneHolder.set(otherZone); + + request = new MockHttpServletRequest(); + } + + @AfterAll + static void afterAll() { + IdentityZoneHolder.clear(); + initializer.reset(); + } + + @Test + void metadataContainsSamlBearerGrantEndpoint() { + request.setServerName("zone-id.localhost"); + request.setServerPort(8080); + request.setContextPath("uaa"); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + MultipleNodeAssert acsAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()).nodesByXPath("//md:AssertionConsumerService"); + acsAssert.extractingAttribute("Binding").contains("urn:oasis:names:tc:SAML:2.0:bindings:URI"); + acsAssert.extractingAttribute("Location").contains("http://zone-id.localhost:8080/uaa/oauth/token/alias/zone-id.entityIdAlias"); + acsAssert.extractingAttribute("index").contains("1"); + } + + @Test + void defaultKeys() { + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert xmlAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()); + + assertThatEncryptionKeyHasValues(xmlAssert, certificate1()); + assertThatSigningKeyHasValues(xmlAssert, certificate1()); + } + + @Test + void multipleKeys() { + samlConfig.addKey(keyName2(), samlKey2()); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert xmlAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()); + + assertThatEncryptionKeyHasValues(xmlAssert, certificate1()); + assertThatSigningKeyHasValues(xmlAssert, certificate1(), certificate2()); + } + + @Test + void changeActiveKey() { + multipleKeys(); + samlConfig.addAndActivateKey(keyName2(), samlKey2()); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert xmlAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()); + + assertThatEncryptionKeyHasValues(xmlAssert, certificate2()); + assertThatSigningKeyHasValues(xmlAssert, certificate1(), certificate2()); + } + + @Test + void removeKey() { + changeActiveKey(); + samlConfig.removeKey(keyName1()); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert xmlAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()); + + assertThatEncryptionKeyHasValues(xmlAssert, certificate2()); + assertThatSigningKeyHasValues(xmlAssert, certificate2()); + } + + private void assertThatSigningKeyHasValues(XmlAssert xmlAssert, String... certificates) { + assertThatXmlKeysOfTypeHasValues(xmlAssert, "signing", certificates); + } + + private void assertThatEncryptionKeyHasValues(XmlAssert xmlAssert, String... certificates) { + assertThatXmlKeysOfTypeHasValues(xmlAssert, "encryption", certificates); + } + + private void assertThatXmlKeysOfTypeHasValues(XmlAssert xmlAssert, String type, String... certificates) { + String[] cleanCerts = Arrays.stream(certificates).map(TestCredentialObjects::bare).toArray(String[]::new); + xmlAssert.hasXPath(KEY_DESCRIPTOR_CERTIFICATE_XPATH_FORMAT.formatted(type)) + .isNotEmpty() + .extractingText() + .containsExactlyInAnyOrder(cleanCerts); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpointTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpointTest.java new file mode 100644 index 00000000000..18ab1497759 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpointTest.java @@ -0,0 +1,216 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; +import org.xmlunit.assertj.XmlAssert; + +import java.security.Security; +import java.security.cert.CertificateEncodingException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.xmlNamespaces; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_EMAIL; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_PERSISTENT; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_TRANSIENT; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_UNSPECIFIED; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_X509SUBJECT; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.encodedCertificate; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.formatCert; +import static org.cloudfoundry.identity.uaa.provider.saml.TestSaml2X509Credentials.relyingPartySigningCredential; +import static org.cloudfoundry.identity.uaa.provider.saml.TestSaml2X509Credentials.relyingPartyVerifyingCredential; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_DIGEST_SHA1; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_DIGEST_SHA256; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_DIGEST_SHA512; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.TRANSFORM_ENVELOPED_SIGNATURE; + +@ExtendWith(MockitoExtension.class) +class SamlMetadataEndpointTest { + private static final String ASSERTION_CONSUMER_SERVICE_1 = "http://localhost:8080/saml/SSO/alias/entityAlias"; + private static final String ASSERTION_CONSUMER_SERVICE_2 = "http://localhost:8080/oauth/token/alias/entityAlias"; + private static final String REGISTRATION_ID = "regId"; + private static final String ENTITY_ID = "entityId"; + private static final String TEST_ZONE = "testzone1"; + + SamlMetadataEndpoint endpoint; + + @Mock + RelyingPartyRegistrationResolver resolver; + @Mock + IdentityZoneManager identityZoneManager; + @Mock + RelyingPartyRegistration registration; + @Mock + IdentityZone identityZone; + @Mock + IdentityZoneConfiguration identityZoneConfiguration; + @Mock + SamlConfig samlConfig; + @Mock + SamlKeyManagerFactory keyManagerFactory; + + MockHttpServletRequest request; + + @BeforeAll + static void beforeAll() { + Security.addProvider(new BouncyCastleFipsProvider()); + } + + @BeforeEach + void beforeEach() { + request = new MockHttpServletRequest(); + endpoint = spy(new SamlMetadataEndpoint(resolver, identityZoneManager, SignatureAlgorithm.SHA256, true)); + when(registration.getEntityId()).thenReturn(ENTITY_ID); + when(registration.getSigningX509Credentials()).thenReturn(List.of(relyingPartySigningCredential())); + when(registration.getDecryptionX509Credentials()).thenReturn(List.of(relyingPartyVerifyingCredential())); + when(registration.getAssertionConsumerServiceBinding()).thenReturn(Saml2MessageBinding.REDIRECT); + when(registration.getAssertionConsumerServiceLocation()).thenReturn(ASSERTION_CONSUMER_SERVICE_1); + when(identityZoneManager.getCurrentIdentityZone()).thenReturn(identityZone); + when(identityZone.getConfig()).thenReturn(identityZoneConfiguration); + when(identityZoneConfiguration.getSamlConfig()).thenReturn(samlConfig); + IdentityZoneHolder.setSamlKeyManagerFactory(keyManagerFactory); + } + + @Test + void defaultZoneFileName() { + when(resolver.resolve(request, REGISTRATION_ID)).thenReturn(registration); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + assertThat(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION)) + .isEqualTo("attachment; filename=\"saml-sp.xml\"; filename*=UTF-8''saml-sp.xml"); + } + + @Test + void nonDefaultZoneFileName() { + when(resolver.resolve(request, REGISTRATION_ID)).thenReturn(registration); + when(identityZone.isUaa()).thenReturn(false); + when(identityZone.getSubdomain()).thenReturn(TEST_ZONE); + when(endpoint.retrieveZone()).thenReturn(identityZone); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + assertThat(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION)) + .isEqualTo("attachment; filename=\"saml-%1$s-sp.xml\"; filename*=UTF-8''saml-%1$s-sp.xml".formatted(TEST_ZONE)); + } + + @Test + void defaultMetadataXml() { + when(resolver.resolve(request, REGISTRATION_ID)).thenReturn(registration); + when(samlConfig.isWantAssertionSigned()).thenReturn(true); + when(samlConfig.isRequestSigned()).thenReturn(true); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert xmlAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()); + xmlAssert.valueByXPath("//md:EntityDescriptor/@entityID").isEqualTo(ENTITY_ID); + xmlAssert.valueByXPath("//md:EntityDescriptor/@ID").isEqualTo(ENTITY_ID); + xmlAssert.valueByXPath("//md:SPSSODescriptor/@AuthnRequestsSigned").isEqualTo(true); + xmlAssert.valueByXPath("//md:SPSSODescriptor/@WantAssertionsSigned").isEqualTo(true); + xmlAssert.nodesByXPath("//md:AssertionConsumerService") + .extractingAttribute("Location") + .containsExactly(ASSERTION_CONSUMER_SERVICE_1, ASSERTION_CONSUMER_SERVICE_2); + xmlAssert.nodesByXPath("//md:AssertionConsumerService") + .extractingAttribute("Binding") + .containsExactly(Saml2MessageBinding.REDIRECT.getUrn(), "urn:oasis:names:tc:SAML:2.0:bindings:URI"); + xmlAssert.nodesByXPath("//md:NameIDFormat") + .extractingText() + .containsExactlyInAnyOrder(NAMEID_FORMAT_EMAIL, NAMEID_FORMAT_PERSISTENT, + NAMEID_FORMAT_TRANSIENT, NAMEID_FORMAT_UNSPECIFIED, NAMEID_FORMAT_X509SUBJECT); + } + + @Test + void defaultMetadataXml_alternateValues() { + when(resolver.resolve(request, REGISTRATION_ID)).thenReturn(registration); + when(samlConfig.isWantAssertionSigned()).thenReturn(false); + when(samlConfig.isRequestSigned()).thenReturn(false); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert xmlAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()); + xmlAssert.valueByXPath("//md:SPSSODescriptor/@AuthnRequestsSigned").isEqualTo(false); + xmlAssert.valueByXPath("//md:SPSSODescriptor/@WantAssertionsSigned").isEqualTo(false); + } + + @Test + void unsigned() { + endpoint = spy(new SamlMetadataEndpoint(resolver, identityZoneManager, SignatureAlgorithm.SHA1, false)); + when(resolver.resolve(request, REGISTRATION_ID)).thenReturn(registration); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()) + .nodesByXPath("/md:EntityDescriptor/ds:Signature").doNotExist(); + } + + @Test + void unsignedIfNoAlgorithm() { + endpoint = spy(new SamlMetadataEndpoint(resolver, identityZoneManager, null, true)); + when(resolver.resolve(request, REGISTRATION_ID)).thenReturn(registration); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()) + .nodesByXPath("/md:EntityDescriptor/ds:Signature").doNotExist(); + } + + @Test + void sha256Signature() throws CertificateEncodingException { + when(resolver.resolve(request, REGISTRATION_ID)).thenReturn(registration); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + System.out.println(response.getBody()); + XmlAssert xmlAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()); + xmlAssert.valueByXPath("/md:EntityDescriptor/@ID").isEqualTo(ENTITY_ID); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm").isEqualTo(ALGO_ID_SIGNATURE_RSA_SHA256); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:CanonicalizationMethod/@Algorithm").isEqualTo(ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/@URI").isEqualTo("#" + ENTITY_ID); + xmlAssert.nodesByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/ds:Transforms/ds:Transform") + .extractingAttribute("Algorithm") + .containsExactlyInAnyOrder(TRANSFORM_C14N_EXCL_OMIT_COMMENTS, TRANSFORM_ENVELOPED_SIGNATURE); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm").isEqualTo(ALGO_ID_DIGEST_SHA256); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestValue").isNotEmpty(); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignatureValue").isNotEmpty(); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate") + .isEqualTo(formatCert(encodedCertificate(relyingPartySigningCredential().getCertificate()))); + } + + @Test + void sha512Signature() { + endpoint = spy(new SamlMetadataEndpoint(resolver, identityZoneManager, SignatureAlgorithm.SHA512, true)); + when(resolver.resolve(request, REGISTRATION_ID)).thenReturn(registration); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert xmlAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm").isEqualTo(ALGO_ID_SIGNATURE_RSA_SHA512); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm").isEqualTo(ALGO_ID_DIGEST_SHA512); + } + + @Test + void sha1Signature() { + endpoint = spy(new SamlMetadataEndpoint(resolver, identityZoneManager, SignatureAlgorithm.SHA1, true)); + when(resolver.resolve(request, REGISTRATION_ID)).thenReturn(registration); + + ResponseEntity response = endpoint.metadataEndpoint(request, REGISTRATION_ID); + XmlAssert xmlAssert = XmlAssert.assertThat(response.getBody()).withNamespaceContext(xmlNamespaces()); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm").isEqualTo(ALGO_ID_SIGNATURE_RSA_SHA1); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm").isEqualTo(ALGO_ID_DIGEST_SHA1); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java index 566ee223801..63a11ef7907 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java @@ -16,25 +16,59 @@ import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class SamlRedirectUtilsTest { +import static org.assertj.core.api.Assertions.assertThat; + +class SamlRedirectUtilsTest { + private static final String ENTITY_ID = "entityId"; + private static final String ZONE_ID = "zone-id"; @Test - public void testGetIdpRedirectUrl() { + void getIdpRedirectUrl() { SamlIdentityProviderDefinition definition = - new SamlIdentityProviderDefinition() - .setMetaDataLocation("http://some.meta.data") - .setIdpEntityAlias("simplesamlphp-url") - .setNameID("nameID") - .setMetadataTrustCheck(true) - .setLinkText("link text") - .setZoneId(IdentityZone.getUaaZoneId()); - - String domain = "login.random-made-up-url.com"; - String url = SamlRedirectUtils.getIdpRedirectUrl(definition, domain, IdentityZoneHolder.get()); - Assert.assertEquals("saml/discovery?returnIDParam=idp&entityID=" + domain + "&idp=simplesamlphp-url&isPassive=true", url); + new SamlIdentityProviderDefinition() + .setMetaDataLocation("http://some.meta.data") + .setIdpEntityAlias("simplesamlphp-url") + .setNameID("nameID") + .setMetadataTrustCheck(true) + .setLinkText("link text") + .setZoneId(IdentityZone.getUaaZoneId()); + + String url = SamlRedirectUtils.getIdpRedirectUrl(definition); + assertThat(url).isEqualTo("saml2/authenticate/simplesamlphp-url"); + } + + @Test + void getZonifiedEntityId() { + assertThat(SamlRedirectUtils.getZonifiedEntityId(ENTITY_ID, IdentityZone.getUaa())).isEqualTo(ENTITY_ID); + } + + @Test + void getZonifiedEntityId_forOtherZone() { + IdentityZone otherZone = new IdentityZone(); + otherZone.setId(ZONE_ID); + otherZone.setSubdomain(ZONE_ID); + + assertThat(SamlRedirectUtils.getZonifiedEntityId(ENTITY_ID, otherZone)).isEqualTo("zone-id.entityId"); + } + + @Test + void zonifiedValidAndInvalidEntityID() { + IdentityZone newZone = new IdentityZone(); + newZone.setId("new-zone-id"); + newZone.setName("new-zone-id"); + newZone.setSubdomain("new-zone-id"); + newZone.getConfig().getSamlConfig().setEntityID("local-name"); + + // valid entityID from SamlConfig + assertThat(SamlRedirectUtils.getZonifiedEntityId("local-name", newZone)) + .isEqualTo("local-name"); + + // remove SamlConfig + newZone.getConfig().setSamlConfig(null); + assertThat(SamlRedirectUtils.getZonifiedEntityId("local-idp", newZone)).isNotNull(); + // now the entityID is generated id as before this change + assertThat(SamlRedirectUtils.getZonifiedEntityId("local-name", newZone)).isEqualTo("new-zone-id.local-name"); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfigTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfigTest.java new file mode 100644 index 00000000000..baaa3ff7aa1 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfigTest.java @@ -0,0 +1,72 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; + +import java.security.Security; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class SamlRelyingPartyRegistrationRepositoryConfigTest { + private static final String ENTITY_ID = "entityId"; + private static final String NAME_ID = "nameIdFormat"; + + @Mock + SamlConfigProps samlConfigProps; + + @Mock + BootstrapSamlIdentityProviderData bootstrapSamlIdentityProviderData; + + @Mock + SamlIdentityProviderConfigurator samlIdentityProviderConfigurator; + + @BeforeAll + public static void beforeAll() { + Security.addProvider(new BouncyCastleFipsProvider()); + } + + @Test + void relyingPartyRegistrationRepository() { + SamlRelyingPartyRegistrationRepositoryConfig config = new SamlRelyingPartyRegistrationRepositoryConfig(ENTITY_ID, + samlConfigProps, bootstrapSamlIdentityProviderData, NAME_ID, List.of()); + RelyingPartyRegistrationRepository repository = config.relyingPartyRegistrationRepository(samlIdentityProviderConfigurator); + assertThat(repository).isNotNull(); + } + + @Test + void relyingPartyRegistrationResolver() { + SamlRelyingPartyRegistrationRepositoryConfig config = new SamlRelyingPartyRegistrationRepositoryConfig(ENTITY_ID, + samlConfigProps, bootstrapSamlIdentityProviderData, NAME_ID, List.of()); + RelyingPartyRegistrationRepository repository = config.relyingPartyRegistrationRepository(samlIdentityProviderConfigurator); + RelyingPartyRegistrationResolver resolver = config.relyingPartyRegistrationResolver(repository); + + assertThat(resolver).isNotNull(); + } + + @Test + void buildsRegistrationForExample() { + SamlRelyingPartyRegistrationRepositoryConfig config = new SamlRelyingPartyRegistrationRepositoryConfig(ENTITY_ID, + samlConfigProps, bootstrapSamlIdentityProviderData, NAME_ID, List.of()); + RelyingPartyRegistrationRepository repository = config.relyingPartyRegistrationRepository(samlIdentityProviderConfigurator); + RelyingPartyRegistration registration = repository.findByRegistrationId("example"); + assertThat(registration) + .returns("example", RelyingPartyRegistration::getRegistrationId) + .returns(ENTITY_ID, RelyingPartyRegistration::getEntityId) + .returns(NAME_ID, RelyingPartyRegistration::getNameIdFormat) + // from functions + .returns("{baseUrl}/saml/SSO/alias/entityId", RelyingPartyRegistration::getAssertionConsumerServiceLocation) + .returns("{baseUrl}/saml/SingleLogout/alias/entityId", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) + // from xml + .extracting(RelyingPartyRegistration::getAssertingPartyDetails) + .returns("exampleEntityId", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlSessionStorageFactoryTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlSessionStorageFactoryTests.java deleted file mode 100644 index 1955cc9ce56..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlSessionStorageFactoryTests.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.mock.web.MockHttpServletRequest; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -@ExtendWith(PollutionPreventionExtension.class) -class SamlSessionStorageFactoryTests { - - private SamlSessionStorageFactory factory; - private MockHttpServletRequest request; - - @BeforeEach - void setUp() { - request = new MockHttpServletRequest(); - factory = new SamlSessionStorageFactory(); - IdentityZoneHolder.clear(); - IdentityZoneHolder.setProvisioning(null); - } - - @Test - void get_storage_creates_session() { - assertNull(request.getSession(false)); - factory.getMessageStorage(request); - assertNotNull(request.getSession(false)); - } - - @Test - void disable_message_storage() { - IdentityZoneHolder.get().getConfig().getSamlConfig().setDisableInResponseToCheck(true); - assertNull(factory.getMessageStorage(request)); - } - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationUserManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationUserManagerTest.java new file mode 100644 index 00000000000..6e161bde564 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationUserManagerTest.java @@ -0,0 +1,139 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; +import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.util.LinkedMultiValueMap; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_VERIFIED_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; + +class SamlUaaAuthenticationUserManagerTest { + + private static final String TEST_USERNAME = "test@saml.user"; + private static final String ZONE_ID = "uaa"; + private final UaaUser existing = createUaaUser(TEST_USERNAME); + + private UaaUser createUaaUser(String username) { + return new UaaUser(username, "", "john.doe@example.com", "John", "Doe"); + } + + @Test + void haveAttributesChangedReturnsFalseForCopied() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing)); + assertThat(SamlUaaAuthenticationUserManager.haveUserAttributesChanged(existing, modified)).isFalse(); + } + + @Test + void haveAttributesChangedReturnsTrueForChangedEmail() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withEmail("other-email")); + assertThat(SamlUaaAuthenticationUserManager.haveUserAttributesChanged(existing, modified)).as("email modified").isTrue(); + } + + + @Test + void haveAttributesChangedReturnsTrueForChangedPhone() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withPhoneNumber("other-phone")); + assertThat(SamlUaaAuthenticationUserManager.haveUserAttributesChanged(existing, modified)).as("Phone number modified").isTrue(); + } + + @Test + void haveAttributesChangedReturnsTrueForChangedVerified() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withVerified(!existing.isVerified())); + assertThat(SamlUaaAuthenticationUserManager.haveUserAttributesChanged(existing, modified)).as("Verifiedemail modified").isTrue(); + } + + @Test + void haveAttributesChangedReturnsTrueForChangedGivenName() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withGivenName("other-given")); + assertThat(SamlUaaAuthenticationUserManager.haveUserAttributesChanged(existing, modified)).as("First name modified").isTrue(); + } + + @Test + void haveAttributesChangedReturnsTrueForChangedFamilyName() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withFamilyName("other-family")); + assertThat(SamlUaaAuthenticationUserManager.haveUserAttributesChanged(existing, modified)).as("Last name modified").isTrue(); + } + + @Test + void getUserByDefaultUsesTheAvailableData() { + SamlUaaAuthenticationUserManager userManager = new SamlUaaAuthenticationUserManager(null); + + UaaPrincipal principal = new UaaPrincipal( + UUID.randomUUID().toString(), + "user", + "user@example.com", + OriginKeys.SAML, + "user", + ZONE_ID + ); + LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); + attributes.add(EMAIL_ATTRIBUTE_NAME, "user@example.com"); + attributes.add(PHONE_NUMBER_ATTRIBUTE_NAME, "(415) 555-0111"); + attributes.add(GIVEN_NAME_ATTRIBUTE_NAME, "Jane"); + attributes.add(FAMILY_NAME_ATTRIBUTE_NAME, "Doe"); + attributes.add(EMAIL_VERIFIED_ATTRIBUTE_NAME, "true"); + + UaaUser user = userManager.getUser(principal, attributes); + assertThat(user) + .returns("user", UaaUser::getUsername) + .returns("user@example.com", UaaUser::getEmail) + .returns("(415) 555-0111", UaaUser::getPhoneNumber) + .returns("Jane", UaaUser::getGivenName) + .returns("Doe", UaaUser::getFamilyName) + .returns("", UaaUser::getPassword) + .returns(true, UaaUser::isVerified) + .returns(OriginKeys.SAML, UaaUser::getOrigin) + .returns("user", UaaUser::getExternalId) + .returns(ZONE_ID, UaaUser::getZoneId) + .returns(0, u -> u.getAuthorities().size()); + } + + @Test + void getUserWithoutVerifiedDefaultsToFalse() { + SamlUaaAuthenticationUserManager userManager = new SamlUaaAuthenticationUserManager(null); + + UaaPrincipal principal = new UaaPrincipal( + UUID.randomUUID().toString(), + "user", + "user@example.com", + null, + "user", + ZONE_ID + ); + + LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); + UaaUser user = userManager.getUser(principal, attributes); + assertThat(user).returns(false, UaaUser::isVerified); + } + + @Test + void throwsIfPrincipalUserNameAndUserAttributesEmailIsMissing() { + SamlUaaAuthenticationUserManager userManager = new SamlUaaAuthenticationUserManager(null); + + UaaPrincipal principal = new UaaPrincipal( + UUID.randomUUID().toString(), + null, + "getUser Should look at the userAttributes email, not this one!", + null, + "user", + ZONE_ID + ); + + LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); + + assertThatThrownBy(() -> userManager.getUser(principal, attributes)) + .isInstanceOf(BadCredentialsException.class) + .hasMessage("Cannot determine username from credentials supplied"); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCredentialObjects.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCredentialObjects.java new file mode 100644 index 00000000000..881345e0496 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCredentialObjects.java @@ -0,0 +1,423 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.identity.uaa.saml.SamlKey; +import org.cloudfoundry.identity.uaa.util.KeyWithCert; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; + +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Base64; + +public class TestCredentialObjects { + + private TestCredentialObjects() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + private static final KeyWithCert legacyKeyWithCert; + private static final KeyWithCert keyWithCert1; + private static final KeyWithCert keyWithCert2; + + static { + Security.addProvider(new BouncyCastleFipsProvider()); + try { + legacyKeyWithCert = KeyWithCert.fromSamlKey(legacySamlKey()); + keyWithCert1 = KeyWithCert.fromSamlKey(samlKey1()); + keyWithCert2 = KeyWithCert.fromSamlKey(samlKey2()); + } catch (CertificateException e) { + throw new RuntimeException(e); + } + } + + /** + * @return a private key as a string + */ + public static String legacyKey() { + return """ + -----BEGIN RSA PRIVATE KEY----- + MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5 + L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vA + fpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQAB + AoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges + 7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGu + lhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3Xp + ubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYX + kKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyL + gu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWK + vw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCe + A2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHS + N+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMB + qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + -----END RSA PRIVATE KEY-----"""; + } + + /** + * @return a private key as a string + */ + public static String key1() { + return """ + -----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEArRkvkddLUoNyuvu0ktkcLL0CyGG8Drh9oPsaVOLVHJqB1Ebr + oNMTPbY0HPjuD5WBDZTi3ftNLp1mPn9wFy6FhMTvIYeQmTskH8m/kyVReXG/zfWq + a4+V6UW4nmUcvfF3YNrHvN5VPTWTJrc2KBzseWQ70OaBNfBi6z4XbdOF45dDfck2 + oRnasinUv+rG+PUl7x8OjgdVyyen6qeCQ6xt8W9fHg//Nydlfwb3/L+syPoBujdu + Hai7GoLUzm/zqOM9dhlR5mjuEJ3QUvnmGKrGDoeHFog0CMgLC+C0Z4ZANB6GbjlM + bsQczsaYxHMqAMOnOe6xIXUrPOoc7rclwZeHMQIDAQABAoIBAAFB2ZKZmbZztfWd + tmYKpaW9ibOi4hbJSEBPEpXjP+EBTkgYa8WzQsSD+kTrme8LCvDqT+uE076u7fsu + OcYxVE7ujz4TGf3C7DQ+5uFOuBTFurroOeCmHlSfaQPdgCPxCQjvDdxVUREsvnDd + i8smyqDnFXgi9HVL1awXu1vU2XgZshfl6wBOCNomVMCN8mVcBQ0KM88SUvoUwM7i + sSdj1yQV16Za8+nVnMW41FMHegVRd3Y5EsXJfwGuXnZMIG87PavH1nUqn9NOFq9Y + kb4SeOO47PaMxv7jMaXltVVokdGH8L/BY4we8tBL+wVeUJ94aYx/Q/LUAtRPbKPS + ZSEi/7ECgYEA3dUg8DXzo59zl5a8kfz3aoLl8RqRYzuf8F396IuiVcqYlwlWOkZW + javwviEOEdZhUZPxK1duXKTvYw7s6eDFwV+CklTZu4A8M3Os0D8bSL/pIKqcadt5 + JClIRmOmmQpj9AYhSdBTdQtJGjVDaDXJBb7902pDm9I4jMFbjAKLZNsCgYEAx8J3 + Y1c7GwHw6dxvTywrw3U6z1ILbx2olVLY6DIgZaMVT4EKTAv2Ke4xF4OZYG+lLRbt + hhOHYzRMYC38MNl/9RXHBgUlQJXOQb9u644motl5dcMvzIIuWFCn5vXxR2C3McNy + vPdzYS2M64xRGy+IENtPSCcUs9C99bEajRcuG+MCgYAONabEfFA8/OvEnA08NL4M + fpIIHbGOb7VRClRHXxpo8G9RzXFOjk7hCFCFfUyPa/IT7awXIKSbHp2O9NfMK2+/ + cUTF5tWDozU3/oLlXAV9ZX2jcApQ5ZQe8t4EVEHJr9azPOlI9yVBbBWkriDBPiDA + U3mi3z2xb4fbzE726vrO3QKBgA6PfTZPgG5qiM3zFGX3+USpAd1kxJKX3dbskAT0 + ymm+JmqCJGcApDPQOeHV5NMjsC2GM1AHkmHHyR1lnLFO2UXbDYPB0kJP6RXfx00C + MozCP1k3Hf/RKWGkl2h9WtXyFchZz744Zz+ZG2F7+9l4cHmSEshWmOq2d3I2M5I/ + M0wzAoGAa2oM4Q6n+FMHl9e8H+2O4Dgm7wAdhuZI1LhnLL6GLVC1JTmGrz/6G2TX + iNFhc0lnDcVeZlwg4i7M7MH8UFdWj3ZEylsXjrjIspuAJg7a/6qmP9s2ITVffqYk + 2slwG2SIQchM5/0uOiP9W0YIjYEe7hgHUmL9Rh8xFuo9y72GH8c= + -----END RSA PRIVATE KEY-----"""; + } + + /** + * @return a private key as a string + */ + public static String key2() { + return """ + -----BEGIN RSA PRIVATE KEY----- + MIIEpAIBAAKCAQEAwt7buITRZhXX98apcgJbiHhrPkrgn5MCsCphRQ89oWPUHWjN + j9Kz2m9LaKgq9DnNLl22U4e6/LUQToBCLxkIqwaobZKjIUjNAmNomqbNO7AD2+K7 + RCiQ2qijWUwXGu+5+fSmF/MOermNKUDiQnRJSSSAPObAHOI980zTWVsApKpcFVaV + vk/299L/0rk8I/mNvf63cdw4Nh3xn4Ct+oCnTaDg5OtpGz8sHlocOAti+LdrtNzH + uBWq8q2sdhFQBRGe1MOeH8CAEHgKYwELTBCJEyLhykdRgxXJHSaL56+mb6HQvGO/ + oyZHn+qHsCCjcdR1L/U4qt4m7HBimv0qbvApQwIDAQABAoIBAQCftmmcnHbG1WZR + NChSQa5ldlRnFJVvE90jJ0jbgfdAHAKQLAI2Ozme8JJ8bz/tNKZ+tt2lLlxJm9iG + jkYwNbNOAMHwNDuxHuqvZ2wnPEh+/+7Zu8VBwoGeRJLEsEFLmWjyfNnYTSPz37nb + Mst+LbKW2OylfXW89oxRqQibdqNbULpcU4NBDkMjToH1Z4dUFx3X2R2AAwgDz4Ku + HN4HoxbsbUCI5wLDJrTGrJgEntMSdsSdOY48YOMBnHqqfw7KoJ0sGjrPUy0vOGq2 + CeP3uqbXX/mJpvJ+jg3Y2b1Zeu2I+vAnZrxlaZ+hYnZfoNqVjBZ/EEq/lmEovMvr + erP8FYI5AoGBAOrlmMZYdhW0fRzfpx6WiBJUkFfmit4qs9nQRCouv+jHS5QL9aM9 + c+iKeP6kWuxBUYaDBmf5J1OBW4omNd384NX5PCiL/Fs/lxgdMZqEhnhT4Dj4Q6m6 + ZXUuY6hamoF5+z2mtkZzRyvD1LUAARKJw6ggUtcH28cYC3RkZ5P6SWHVAoGBANRg + scI9pF2VUrmwpgIGhynLBEO26k8j/FyE3S7lPcUZdgPCUZB0/tGklSo183KT/KQY + TgO2mqb8a8xKCz41DTnUPqJWZzBOFw5QaD2i9O6soXUAKqaUm3g40/gyWX1hUtHa + K0Kw5z1Sf3MoCpW0Ozzn3znYbAoSvBRr53d0EVK3AoGAOD1ObbbCVwIGroIR1i3+ + WD0s7g7Bkt2wf+bwWxUkV4xX2RNf9XyCItv8iiM5rbUZ2tXGE+DAfKrNCu+JGCQy + hKiOsbqKaiJ4f4qF1NQECg0y8xDlyl5Zakv4ClffBD77W1Bt9cIl+SGC7O8aUqDv + WnKawucbxLhKDcz4S6KyLR0CgYEAhuRrw24XqgEgLCVRK9QtoZP7P28838uBjNov + Cow8caY8WSLhX5mQCGQ7AjaGTG5Gd4ugcadYD1wgs/8LqRVVMzfmGII8xGe1KThV + HWEVpUssuf3DGU8meHPP3sNMJ+DbE8M42wE1vrNZlDEImBGD1qmIFVurM7K2l1n6 + CNtF7X0CgYBuFf0A0cna8LnxOAPm8EPHgFq4TnDU7BJzzcO/nsORDcrh+dZyGJNS + fUTMp4k+AQCm9UwJAiSf4VUwCbhXUZ3S+xB55vrH+Yc2OMtsIYhzr3OCkbgKBMDn + nBVKSGAomYD2kCUmSbg7bUrFfGntmvOLqTHtVfrCyE5i8qS63RbHlA== + -----END RSA PRIVATE KEY-----"""; + } + + /** + * @return a certificate corresponding to legacyKey + */ + public static String legacyCertificate() { + return """ + -----BEGIN CERTIFICATE----- + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO + MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO + MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h + cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx + CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM + BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb + BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN + ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W + qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw + znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha + MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc + gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD + VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD + VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh + QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ + 0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC + KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK + RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= + -----END CERTIFICATE-----"""; + } + + /** + * @return a certificate corresponding to key1 + */ + public static String certificate1() { + return """ + -----BEGIN CERTIFICATE----- + MIID0DCCArgCCQDBRxU0ucjw6DANBgkqhkiG9w0BAQsFADCBqTELMAkGA1UEBhMC + VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMR8wHQYDVQQK + ExZDbG91ZCBGb3VuZHJ5IElkZW50aXR5MQ4wDAYDVQQLEwVLZXkgMTEiMCAGA1UE + AxMZbG9naW4uaWRlbnRpdHkuY2YtYXBwLmNvbTEgMB4GCSqGSIb3DQEJARYRZmhh + bmlrQHBpdm90YWwuaW8wHhcNMTcwNDEwMTkxMTIyWhcNMTgwNDEwMTkxMTIyWjCB + qTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp + c2NvMR8wHQYDVQQKExZDbG91ZCBGb3VuZHJ5IElkZW50aXR5MQ4wDAYDVQQLEwVL + ZXkgMTEiMCAGA1UEAxMZbG9naW4uaWRlbnRpdHkuY2YtYXBwLmNvbTEgMB4GCSqG + SIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IB + DwAwggEKAoIBAQCtGS+R10tSg3K6+7SS2RwsvQLIYbwOuH2g+xpU4tUcmoHURuug + 0xM9tjQc+O4PlYENlOLd+00unWY+f3AXLoWExO8hh5CZOyQfyb+TJVF5cb/N9apr + j5XpRbieZRy98Xdg2se83lU9NZMmtzYoHOx5ZDvQ5oE18GLrPhdt04Xjl0N9yTah + GdqyKdS/6sb49SXvHw6OB1XLJ6fqp4JDrG3xb18eD/83J2V/Bvf8v6zI+gG6N24d + qLsagtTOb/Oo4z12GVHmaO4QndBS+eYYqsYOh4cWiDQIyAsL4LRnhkA0HoZuOUxu + xBzOxpjEcyoAw6c57rEhdSs86hzutyXBl4cxAgMBAAEwDQYJKoZIhvcNAQELBQAD + ggEBAB72QKF9Iri+UdCGAIok/qIeKw5AwZ0wtiONa+DF4B80/yAA1ObpuO3eeeka + t0s4wtCRflE08zLrwqHlvKQAGKmJkfRLfEqfKStIUOTHQxE6wOaBtfW41M9ZF1hX + NHpnkfmSQjaHVNTRbABiFH6eTq8J6CuO12PyDf7lW3EofvcTU3ulsDhuMAz02ypJ + BgcOufnl+qP/m/BhVQsRD5mtJ56uJpHvri1VR2kj8N59V8f6KPO2m5Q6MulEhWml + TsxyxUl03oyICDP1cbpYtDk2VddVNWipHHPH/mBVW41EBVv0VDV03LH3RfS9dXiK + ynuP3shhqhFvaaiUTZP4l5yF/GQ= + -----END CERTIFICATE-----"""; + } + + /** + * @return a certificate corresponding to key2 + */ + public static String certificate2() { + return """ + -----BEGIN CERTIFICATE----- + MIID0DCCArgCCQDqnPTUvA17+TANBgkqhkiG9w0BAQsFADCBqTELMAkGA1UEBhMC + VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMR8wHQYDVQQK + ExZDbG91ZCBGb3VuZHJ5IElkZW50aXR5MQ4wDAYDVQQLEwVLZXkgMjEiMCAGA1UE + AxMZbG9naW4uaWRlbnRpdHkuY2YtYXBwLmNvbTEgMB4GCSqGSIb3DQEJARYRZmhh + bmlrQHBpdm90YWwuaW8wHhcNMTcwNDEwMTkxNTAyWhcNMTgwNDEwMTkxNTAyWjCB + qTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp + c2NvMR8wHQYDVQQKExZDbG91ZCBGb3VuZHJ5IElkZW50aXR5MQ4wDAYDVQQLEwVL + ZXkgMjEiMCAGA1UEAxMZbG9naW4uaWRlbnRpdHkuY2YtYXBwLmNvbTEgMB4GCSqG + SIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IB + DwAwggEKAoIBAQDC3tu4hNFmFdf3xqlyAluIeGs+SuCfkwKwKmFFDz2hY9QdaM2P + 0rPab0toqCr0Oc0uXbZTh7r8tRBOgEIvGQirBqhtkqMhSM0CY2iaps07sAPb4rtE + KJDaqKNZTBca77n59KYX8w56uY0pQOJCdElJJIA85sAc4j3zTNNZWwCkqlwVVpW+ + T/b30v/SuTwj+Y29/rdx3Dg2HfGfgK36gKdNoODk62kbPyweWhw4C2L4t2u03Me4 + Faryrax2EVAFEZ7Uw54fwIAQeApjAQtMEIkTIuHKR1GDFckdJovnr6ZvodC8Y7+j + Jkef6oewIKNx1HUv9Tiq3ibscGKa/Spu8ClDAgMBAAEwDQYJKoZIhvcNAQELBQAD + ggEBAKzeh/bRDEEP/WGsiYhCCfvESyt0QeKwUk+Hfl0/oP4m9pXNrnMRApyoi7FB + owpmXIeqDqGigPai6pJ3xCO94P+Bz7WTk0+jScYm/hGpcIOeKh8FBfW0Fddu9Otn + qVk0FdRSCTjUZKQlNOqVTjBeKOjHmTkgh96IR3EP2/hp8Ym4HLC+w265V7LnkqD2 + SoMez7b2V4NmN7z9OxTALUbTzmFG77bBDExHvfbiFlkIptx8+IloJOCzUsPEg6Ur + kueuR7IB1S4q6Ja7Gb9b9NYQDFt4hjb5mC9aPxaX+KK2JlZg4cTFVCdkIyp2/fHI + iQpMzNWb7zZWlCfDL4dJZHYoNfg= + -----END CERTIFICATE-----"""; + } + + /** + * @return a passphrase corresponding to legacyKey + */ + public static String legacyPassphrase() { + return "password"; + } + + /** + * @return a passphrase corresponding to key1 + */ + public static String passphrase1() { + return "password"; + } + + /** + * @return a passphrase corresponding to key2 + */ + public static String passphrase2() { + return "password"; + } + + /** + * @return a KeyWithCert for testing + */ + public static KeyWithCert legacyKeyWithCert() { + return legacyKeyWithCert; + } + + /** + * @return a KeyWithCert for testing + */ + public static KeyWithCert keyWithCert1() { + return keyWithCert1; + } + + /** + * @return a KeyWithCert for testing + */ + public static KeyWithCert keyWithCert2() { + return keyWithCert2; + } + + /** + * @return a X509Certificate for testing + */ + public static X509Certificate legacyX509Certificate() { + return legacyKeyWithCert.getCertificate(); + } + + /** + * @return a X509Certificate for testing + */ + public static X509Certificate x509Certificate1() { + return keyWithCert1.getCertificate(); + } + + /** + * @return a X509Certificate for testing + */ + public static X509Certificate x509Certificate2() { + return keyWithCert2.getCertificate(); + } + + + /** + * @return a PrivateKey for testing + */ + public static PrivateKey legacyPrivateKey() { + return legacyKeyWithCert.getPrivateKey(); + } + + /** + * @return a PrivateKey for testing + */ + public static PrivateKey privateKey1() { + return keyWithCert1.getPrivateKey(); + } + + /** + * @return a PrivateKey for testing + */ + public static PrivateKey privateKey2() { + return keyWithCert2.getPrivateKey(); + } + + /** + * @return a key name for testing + */ + public static String legacyKeyName() { + return SamlConfig.LEGACY_KEY_ID; + } + + /** + * @return a key name for testing + */ + public static String keyName1() { + return "key-1"; + } + + /** + * @return a key name for testing + */ + public static String keyName2() { + return "key-2"; + } + + /** + * @return a key name for testing + */ + public static String keyName3() { + return "key-3"; + } + + /** + * @return an empty SamlKey for testing + */ + public static SamlKey emptySamlKey() { + // SamlKeys are mutable, so we need to create a new one each time + return new SamlKey(); + } + + /** + * @return a SamlKey with legacy key, passphrase, and certificate for testing + */ + public static SamlKey legacySamlKey() { + // SamlKeys are mutable, so we need to create a new one each time + return new SamlKey(legacyKey(), legacyPassphrase(), legacyCertificate()); + } + + /** + * @return a SamlKey with key1, passphrase1, and certificate1 for testing + */ + public static SamlKey samlKey1() { + // SamlKeys are mutable, so we need to create a new one each time + return new SamlKey(key1(), passphrase1(), certificate1()); + } + + /** + * @return a SamlKey with null key, null passphrase, and certificate1 for testing + */ + public static SamlKey samlKeyCertOnly() { + // SamlKeys are mutable, so we need to create a new one each time + return new SamlKey(null, null, certificate1()); + } + + /** + * @return a SamlKey with key2, passphrase2, and certificate2 for testing + */ + public static SamlKey samlKey2() { + // SamlKeys are mutable, so we need to create a new one each time + return new SamlKey(key2(), passphrase2(), certificate2()); + } + + /** + * @return a bare certificate without the BEGIN and END lines, nor newlines + */ + public static String bare(String cert) { + return cert.replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replace("\n", ""); + } + + /** + * @return a bare legacy certificate as a string without the BEGIN and END lines, nor newlines + */ + public static String bareLegacyCertificate() { + return bare(legacyCertificate()); + } + + /** + * @return a bare certificate1 as a string without the BEGIN and END lines, nor newlines + */ + public static String bareCertificate1() { + return bare(certificate1()); + } + + /** + * @return a bare certificate2 as a string without the BEGIN and END lines, nor newlines + */ + public static String bareCertificate2() { + return bare(certificate2()); + } + + public static String encodedCertificate(X509Certificate certificate) throws CertificateEncodingException { + return new String(Base64.getEncoder().encode(certificate.getEncoded())); + } + + public static String formatCert(String cert) { + return formatCert(cert, 76); + } + + public static String formatCert(String cert, int lineLength) { + String bare = bare(cert); + String regex = "(.{" + lineLength + "})"; + return bare.replaceAll(regex, "$1\n"); + } + + /** + * @return a legacyCertificate as a string without the BEGIN and END lines, with newlines every 76 characters + */ + public static String formattedLegacyCertificate() { + return formatCert(legacyCertificate()); + } + + /** + * @return a legacyCertificate as a string without the BEGIN and END lines, with newlines every 76 characters + */ + public static String formattedCertificate1() { + return formatCert(certificate1()); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCustomOpenSamlObjects.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCustomOpenSamlObjects.java new file mode 100644 index 00000000000..c72eab0f697 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCustomOpenSamlObjects.java @@ -0,0 +1,220 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.xml.namespace.QName; + +import net.shibboleth.utilities.java.support.xml.ElementSupport; +import org.opensaml.core.xml.AbstractXMLObject; +import org.opensaml.core.xml.AbstractXMLObjectBuilder; +import org.opensaml.core.xml.ElementExtensibleXMLObject; +import org.opensaml.core.xml.Namespace; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.AbstractXMLObjectMarshaller; +import org.opensaml.core.xml.io.AbstractXMLObjectUnmarshaller; +import org.opensaml.core.xml.io.UnmarshallingException; +import org.opensaml.core.xml.schema.XSAny; +import org.opensaml.core.xml.schema.impl.XSAnyBuilder; +import org.opensaml.core.xml.util.IndexedXMLObjectChildrenList; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.AttributeValue; +import org.w3c.dom.Element; + +import org.springframework.security.saml2.core.OpenSamlInitializationService; + +/** + * This was copied from Spring Security Test Classes + *

    + * Once we can move to the spring-security version of OpenSaml4AuthenticationProvider, + * this class should be removed. + */ +public final class TestCustomOpenSamlObjects { + + static { + OpenSamlInitializationService.initialize(); + XMLObjectProviderRegistrySupport.getMarshallerFactory() + .registerMarshaller(CustomOpenSamlObject.TYPE_NAME, + new TestCustomOpenSamlObjects.CustomSamlObjectMarshaller()); + XMLObjectProviderRegistrySupport.getUnmarshallerFactory() + .registerUnmarshaller(CustomOpenSamlObject.TYPE_NAME, + new TestCustomOpenSamlObjects.CustomSamlObjectUnmarshaller()); + } + + public static CustomOpenSamlObject instance() { + CustomOpenSamlObject samlObject = new TestCustomOpenSamlObjects.CustomSamlObjectBuilder() + .buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, CustomOpenSamlObject.TYPE_NAME); + XSAny street = new XSAnyBuilder().buildObject(CustomOpenSamlObject.CUSTOM_NS, "Street", + CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); + street.setTextContent("Test Street"); + samlObject.getUnknownXMLObjects().add(street); + XSAny streetNumber = new XSAnyBuilder().buildObject(CustomOpenSamlObject.CUSTOM_NS, "Number", + CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); + streetNumber.setTextContent("1"); + samlObject.getUnknownXMLObjects().add(streetNumber); + XSAny zip = new XSAnyBuilder().buildObject(CustomOpenSamlObject.CUSTOM_NS, "ZIP", + CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); + zip.setTextContent("11111"); + samlObject.getUnknownXMLObjects().add(zip); + XSAny city = new XSAnyBuilder().buildObject(CustomOpenSamlObject.CUSTOM_NS, "City", + CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); + city.setTextContent("Test City"); + samlObject.getUnknownXMLObjects().add(city); + return samlObject; + } + + private TestCustomOpenSamlObjects() { + + } + + public interface CustomOpenSamlObject extends ElementExtensibleXMLObject { + + String TYPE_LOCAL_NAME = "CustomType"; + + String TYPE_CUSTOM_PREFIX = "custom"; + + String CUSTOM_NS = "https://custom.com/schema/custom"; + + /** QName of the CustomType type. */ + QName TYPE_NAME = new QName(CUSTOM_NS, TYPE_LOCAL_NAME, TYPE_CUSTOM_PREFIX); + + String getStreet(); + + String getStreetNumber(); + + String getZIP(); + + String getCity(); + + } + + public static class CustomOpenSamlObjectImpl extends AbstractXMLObject implements CustomOpenSamlObject { + + @Nonnull + private IndexedXMLObjectChildrenList unknownXMLObjects; + + /** + * Constructor. + * @param namespaceURI the namespace the element is in + * @param elementLocalName the local name of the XML element this Object + * represents + * @param namespacePrefix the prefix for the given namespace + */ + protected CustomOpenSamlObjectImpl(@Nullable String namespaceURI, @Nonnull String elementLocalName, + @Nullable String namespacePrefix) { + super(namespaceURI, elementLocalName, namespacePrefix); + super.getNamespaceManager().registerNamespaceDeclaration(new Namespace(CUSTOM_NS, TYPE_CUSTOM_PREFIX)); + this.unknownXMLObjects = new IndexedXMLObjectChildrenList<>(this); + } + + @Nonnull + @Override + public List getUnknownXMLObjects() { + return this.unknownXMLObjects; + } + + @Nonnull + @Override + public List getUnknownXMLObjects(@Nonnull QName typeOrName) { + return (List) this.unknownXMLObjects.subList(typeOrName); + } + + @Nullable + @Override + public List getOrderedChildren() { + return Collections.unmodifiableList(this.unknownXMLObjects); + } + + @Override + public String getStreet() { + return ((XSAny) getOrderedChildren().get(0)).getTextContent(); + } + + @Override + public String getStreetNumber() { + return ((XSAny) getOrderedChildren().get(1)).getTextContent(); + } + + @Override + public String getZIP() { + return ((XSAny) getOrderedChildren().get(2)).getTextContent(); + } + + @Override + public String getCity() { + return ((XSAny) getOrderedChildren().get(3)).getTextContent(); + } + + } + + public static class CustomSamlObjectBuilder extends AbstractXMLObjectBuilder { + + @Nonnull + @Override + public CustomOpenSamlObject buildObject(@Nullable String namespaceURI, @Nonnull String localName, + @Nullable String namespacePrefix) { + return new CustomOpenSamlObjectImpl(namespaceURI, localName, namespacePrefix); + } + + } + + public static class CustomSamlObjectMarshaller extends AbstractXMLObjectMarshaller { + + public CustomSamlObjectMarshaller() { + super(); + } + + @Override + protected void marshallElementContent(@Nonnull XMLObject xmlObject, @Nonnull Element domElement) { + final CustomOpenSamlObject customSamlObject = (CustomOpenSamlObject) xmlObject; + + for (XMLObject object : customSamlObject.getOrderedChildren()) { + ElementSupport.appendChildElement(domElement, object.getDOM()); + } + } + + } + + public static class CustomSamlObjectUnmarshaller extends AbstractXMLObjectUnmarshaller { + + public CustomSamlObjectUnmarshaller() { + super(); + } + + @Override + protected void processChildElement(@Nonnull XMLObject parentXMLObject, @Nonnull XMLObject childXMLObject) + throws UnmarshallingException { + final CustomOpenSamlObject customSamlObject = (CustomOpenSamlObject) parentXMLObject; + super.processChildElement(customSamlObject, childXMLObject); + customSamlObject.getUnknownXMLObjects().add(childXMLObject); + } + + @Nonnull + @Override + protected XMLObject buildXMLObject(@Nonnull Element domElement) { + return new CustomOpenSamlObjectImpl(SAMLConstants.SAML20_NS, AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME, + CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); + } + + } + +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java new file mode 100644 index 00000000000..9b0579d4780 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java @@ -0,0 +1,609 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.apache.xml.security.encryption.XMLCipherParameters; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.core.xml.schema.XSAny; +import org.opensaml.core.xml.schema.XSBase64Binary; +import org.opensaml.core.xml.schema.XSBoolean; +import org.opensaml.core.xml.schema.XSBooleanValue; +import org.opensaml.core.xml.schema.XSDateTime; +import org.opensaml.core.xml.schema.XSInteger; +import org.opensaml.core.xml.schema.XSQName; +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.core.xml.schema.XSURI; +import org.opensaml.core.xml.schema.impl.XSAnyBuilder; +import org.opensaml.core.xml.schema.impl.XSBase64BinaryBuilder; +import org.opensaml.core.xml.schema.impl.XSBooleanBuilder; +import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder; +import org.opensaml.core.xml.schema.impl.XSIntegerBuilder; +import org.opensaml.core.xml.schema.impl.XSQNameBuilder; +import org.opensaml.core.xml.schema.impl.XSStringBuilder; +import org.opensaml.core.xml.schema.impl.XSURIBuilder; +import org.opensaml.saml.common.SAMLVersion; +import org.opensaml.saml.common.SignableSAMLObject; +import org.opensaml.saml.saml2.core.*; +import org.opensaml.saml.saml2.core.impl.AttributeBuilder; +import org.opensaml.saml.saml2.core.impl.AttributeStatementBuilder; +import org.opensaml.saml.saml2.core.impl.IssuerBuilder; +import org.opensaml.saml.saml2.core.impl.LogoutRequestBuilder; +import org.opensaml.saml.saml2.core.impl.LogoutResponseBuilder; +import org.opensaml.saml.saml2.core.impl.NameIDBuilder; +import org.opensaml.saml.saml2.core.impl.StatusBuilder; +import org.opensaml.saml.saml2.core.impl.StatusCodeBuilder; +import org.opensaml.saml.saml2.encryption.Encrypter; +import org.opensaml.security.SecurityException; +import org.opensaml.security.credential.BasicCredential; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.CredentialSupport; +import org.opensaml.security.credential.UsageType; +import org.opensaml.xmlsec.SignatureSigningParameters; +import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters; +import org.opensaml.xmlsec.encryption.support.EncryptionException; +import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters; +import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.opensaml.xmlsec.signature.support.SignatureException; +import org.opensaml.xmlsec.signature.support.SignatureSupport; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.OpenSamlInitializationService; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.namespace.QName; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; + +/** + * This class contains functions to create SAML Requests, Responses, Tokens and related objects for testing purposes. + * These are building blocks, and most of the functionality here can be accessed via Saml2TestUtils, which does additional configuration. + *

    + * This was copied from Spring Security Test Classes + * Migrate to use the Spring Security class when it is made public + *

    + * Changes: + * - setValue on interface org.opensaml.core.xml.schema.XSURI + * - added alot of values to attributeStatements + */ +public final class TestOpenSamlObjects { + + private static final String USERNAME = "test@saml.user"; + private static final String DESTINATION = "http://localhost:8080/uaa/saml/SSO/alias/integration-saml-entity-id"; + private static final String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp"; + private static final SecretKey SECRET_KEY = new SecretKeySpec( + Base64.getDecoder().decode("shOnwNMoCv88HKMEa91+FlYoD5RNvzMTAL5LGxZKIFk="), "AES"); + public static String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; + + static { + OpenSamlInitializationService.initialize(); + } + + private TestOpenSamlObjects() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static Response response() { + return response(DESTINATION, ASSERTING_PARTY_ENTITY_ID); + } + + public static Response response(String destination, String issuerEntityId) { + Response response = build(Response.DEFAULT_ELEMENT_NAME); + response.setID("R" + UUID.randomUUID()); + response.setVersion(SAMLVersion.VERSION_20); + response.setID("_" + UUID.randomUUID()); + response.setDestination(destination); + response.setIssuer(issuer(issuerEntityId)); + return response; + } + + static Response signedResponseWithOneAssertion() { + return signedResponseWithOneAssertion(response -> { + }); + } + + static Response signedResponseWithOneAssertion(Consumer responseConsumer) { + Response response = response(); + response.getAssertions().add(assertion()); + responseConsumer.accept(response); + return signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); + } + + public static Assertion assertion() { + return assertion(USERNAME, ASSERTING_PARTY_ENTITY_ID, RELYING_PARTY_ENTITY_ID, DESTINATION); + } + + public static Assertion assertion(String username) { + if (username == null) { + username = USERNAME; + } + return assertion(username, ASSERTING_PARTY_ENTITY_ID, RELYING_PARTY_ENTITY_ID, DESTINATION); + } + + public static Assertion assertion(String username, String issuerEntityId, String recipientEntityId, String recipientUri) { + Assertion assertion = build(Assertion.DEFAULT_ELEMENT_NAME); + assertion.setID("A" + UUID.randomUUID()); + assertion.setVersion(SAMLVersion.VERSION_20); + assertion.setIssuer(issuer(issuerEntityId)); + assertion.setSubject(subject(username)); + assertion.setConditions(conditions()); + SubjectConfirmation subjectConfirmation = subjectConfirmation(); + subjectConfirmation.setMethod(SubjectConfirmation.METHOD_BEARER); + SubjectConfirmationData confirmationData = subjectConfirmationData(recipientEntityId); + confirmationData.setRecipient(recipientUri); + subjectConfirmation.setSubjectConfirmationData(confirmationData); + assertion.getSubject().getSubjectConfirmations().add(subjectConfirmation); + AuthnStatement statement = build(AuthnStatement.DEFAULT_ELEMENT_NAME); + statement.setSessionIndex("session-index"); + assertion.getAuthnStatements().add(statement); + return assertion; + } + + static Issuer issuer(String entityId) { + Issuer issuer = build(Issuer.DEFAULT_ELEMENT_NAME); + issuer.setValue(entityId); + return issuer; + } + + static Subject subject(String principalName) { + Subject subject = build(Subject.DEFAULT_ELEMENT_NAME); + if (principalName != null) { + subject.setNameID(nameId(principalName)); + } + return subject; + } + + static NameID nameId(String principalName) { + NameID nameId = build(NameID.DEFAULT_ELEMENT_NAME); + nameId.setValue(principalName); + return nameId; + } + + static SubjectConfirmation subjectConfirmation() { + return build(SubjectConfirmation.DEFAULT_ELEMENT_NAME); + } + + static SubjectConfirmationData subjectConfirmationData(String recipient) { + SubjectConfirmationData subject = build(SubjectConfirmationData.DEFAULT_ELEMENT_NAME); + subject.setRecipient(recipient); + return subject; + } + + static Conditions conditions() { + return build(Conditions.DEFAULT_ELEMENT_NAME); + } + + public static AuthnRequest authnRequest() { + Issuer issuer = build(Issuer.DEFAULT_ELEMENT_NAME); + issuer.setValue(ASSERTING_PARTY_ENTITY_ID); + AuthnRequest authnRequest = build(AuthnRequest.DEFAULT_ELEMENT_NAME); + authnRequest.setIssuer(issuer); + authnRequest.setDestination(ASSERTING_PARTY_ENTITY_ID + "/SSO.saml2"); + authnRequest.setAssertionConsumerServiceURL(DESTINATION); + return authnRequest; + } + + static Credential getSigningCredential(Saml2X509Credential credential, String entityId) { + BasicCredential cred = getBasicCredential(credential); + cred.setEntityId(entityId); + cred.setUsageType(UsageType.SIGNING); + return cred; + } + + static BasicCredential getBasicCredential(Saml2X509Credential credential) { + return CredentialSupport.getSimpleCredential(credential.getCertificate(), credential.getPrivateKey()); + } + + static T signed(T signable, Saml2X509Credential credential, String entityId, + String signAlgorithmUri) { + SignatureSigningParameters parameters = new SignatureSigningParameters(); + Credential signingCredential = getSigningCredential(credential, entityId); + parameters.setSigningCredential(signingCredential); + parameters.setSignatureAlgorithm(signAlgorithmUri); + parameters.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256); + parameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + + X509KeyInfoGeneratorFactory x509KeyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory(); + x509KeyInfoGeneratorFactory.setEmitEntityCertificate(true); + parameters.setKeyInfoGenerator(x509KeyInfoGeneratorFactory.newInstance()); + try { + SignatureSupport.signObject(signable, parameters); + } catch (MarshallingException | SignatureException | SecurityException ex) { + throw new Saml2Exception(ex); + } + return signable; + } + + public static T signed(T signable, Saml2X509Credential credential, String entityId) { + return signed(signable, credential, entityId, SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); + } + + static EncryptedAssertion encrypted(Assertion assertion, Saml2X509Credential credential) { + X509Certificate certificate = credential.getCertificate(); + Encrypter encrypter = getEncrypter(certificate); + try { + return encrypter.encrypt(assertion); + } catch (EncryptionException ex) { + throw new Saml2Exception("Unable to encrypt assertion.", ex); + } + } + + static EncryptedID encrypted(NameID nameId, Saml2X509Credential credential) { + X509Certificate certificate = credential.getCertificate(); + Encrypter encrypter = getEncrypter(certificate); + try { + return encrypter.encrypt(nameId); + } catch (EncryptionException ex) { + throw new Saml2Exception("Unable to encrypt nameID.", ex); + } + } + + static EncryptedAttribute encrypted(String name, String value, Saml2X509Credential credential) { + Attribute attribute = attribute(name, value); + X509Certificate certificate = credential.getCertificate(); + Encrypter encrypter = getEncrypter(certificate); + try { + return encrypter.encrypt(attribute); + } catch (EncryptionException ex) { + throw new Saml2Exception("Unable to encrypt nameID.", ex); + } + } + + private static Encrypter getEncrypter(X509Certificate certificate) { + String dataAlgorithm = XMLCipherParameters.AES_256; + String keyAlgorithm = XMLCipherParameters.RSA_1_5; + BasicCredential dataCredential = new BasicCredential(SECRET_KEY); + DataEncryptionParameters dataEncryptionParameters = new DataEncryptionParameters(); + dataEncryptionParameters.setEncryptionCredential(dataCredential); + dataEncryptionParameters.setAlgorithm(dataAlgorithm); + Credential credential = CredentialSupport.getSimpleCredential(certificate, null); + KeyEncryptionParameters keyEncryptionParameters = new KeyEncryptionParameters(); + keyEncryptionParameters.setEncryptionCredential(credential); + keyEncryptionParameters.setAlgorithm(keyAlgorithm); + Encrypter encrypter = new Encrypter(dataEncryptionParameters, keyEncryptionParameters); + Encrypter.KeyPlacement keyPlacement = Encrypter.KeyPlacement.valueOf("PEER"); + encrypter.setKeyPlacement(keyPlacement); + return encrypter; + } + + static Attribute attribute(String name, String value) { + Attribute attribute = build(Attribute.DEFAULT_ELEMENT_NAME); + attribute.setName(name); + XSString xsValue = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); + xsValue.setValue(value); + attribute.getAttributeValues().add(xsValue); + return attribute; + } + + static AttributeStatement customAttributeStatement(String attributeName, XMLObject customAttributeValue) { + AttributeStatementBuilder attributeStatementBuilder = new AttributeStatementBuilder(); + AttributeBuilder attributeBuilder = new AttributeBuilder(); + Attribute attribute = attributeBuilder.buildObject(); + attribute.setName(attributeName); + attribute.getAttributeValues().add(customAttributeValue); + AttributeStatement attributeStatement = attributeStatementBuilder.buildObject(); + attributeStatement.getAttributes().add(attribute); + return attributeStatement; + } + + public static List attributeStatements(Map attributeMap) { + Attribute[] attributes = attributeMap.entrySet().stream() + .map(entry -> attributeWithStringValue(entry.getKey(), entry.getValue())) + .toArray(Attribute[]::new); + + return List.of(attributeStatement(attributes)); + } + + public static List attributeStatements() { + List attributeStatements = new ArrayList<>(); + + attributeStatements.add(attributeStatement( + attributeWithAnyValues("email", "john.doe@example.com", "doe.john@example.com"), + attributeWithAnyValues("secondaryEmail", "john.doe.secondary@example.com"), + attributeWithStringValue("name", "John Doe"), + attributeWithStringValue("firstName", "John"), + attributeWithStringValue("lastName", "Doe"), + attributeWithStringValue("role", "RoleOne"), + attributeWithStringValue("role", "RoleTwo"), + attributeWithIntValue("age", 21), + attributeWithStringValue("phone", "123-456-7890"), + attributeWithAnyValues("manager", "John the Sloth", "Kari the Ant Eater"), + attributeWithAnyValues("costCenter", "Denver,CO") + )); + + attributeStatements.add(attributeStatement( + attributeWithUriValue("website", "https://johndoe.com/"), + attributeWithBooleanValue("registered", true), + attributeWithStringValue("acr", AuthnContext.PASSWORD_AUTHN_CTX), + attributeWithAnyValues("groups", "saml.admin", "saml.user", "saml.unmapped"), + attributeWithAnyValues("2ndgroups", "saml.test"), + // Ensure an empty attribute is handled properly + attributeWithNoValue(), + // Ensure an empty attribute value is handled properly + attributeWithNullValue() + )); + + // Ensure an empty attribute statement is handled properly + attributeStatements.add(attributeStatement()); + + attributeStatements.add(attributeStatement( + attributeWithUriValue("XSURI", "http://localhost:8080/someuri"), + attributeWithAnyValues("XSAny", "XSAnyValue"), + attributeWithQNameValue("XSQName", "XSQNameValue"), + attributeWithIntValue("XSInteger", 3), + attributeWithBooleanValue("XSBoolean", true), + attributeWithDateTimeValue("XSDateTime", Instant.ofEpochSecond(0)), + attributeWithBase64BinaryValue("XSBase64Binary", "00001111") + )); + + return attributeStatements; + } + + private static AttributeStatement attributeStatement(Attribute... attributes) { + AttributeStatement attrStmt = new AttributeStatementBuilder().buildObject(); + attrStmt.getAttributes().addAll(Arrays.asList(attributes)); + return attrStmt; + } + + /** + * Attribute with a null value + */ + private static Attribute attributeWithNullValue() { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName("emptyAttributeValue"); + attr.getAttributeValues().add(null); + + return attr; + } + + /** + * Attribute with no values + */ + private static Attribute attributeWithNoValue() { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName("emptyAttribute"); + + return attr; + } + + private static Attribute attributeWithAnyValues(String attributeName, String... attribValues) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + + for (String attribValue : attribValues) { + XSAny value = new XSAnyBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME); + value.setTextContent(attribValue); + attr.getAttributeValues().add(value); + } + return attr; + } + + private static Attribute attributeWithStringValue(String attributeName, String attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSString value = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); + value.setValue(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithBooleanValue(String attributeName, boolean attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSBoolean value = new XSBooleanBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSBoolean.TYPE_NAME); + value.setValue(new XSBooleanValue(attribValue, false)); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithUriValue(String attributeName, String attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSURI value = new XSURIBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSURI.TYPE_NAME); + value.setURI(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithIntValue(String attributeName, int attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSInteger value = new XSIntegerBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); + value.setValue(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithQNameValue(String attributeName, String attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSQName value = new XSQNameBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSQName.TYPE_NAME); + value.setValue(QName.valueOf(attribValue)); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithBase64BinaryValue(String attributeName, String attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSBase64Binary value = new XSBase64BinaryBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSBase64Binary.TYPE_NAME); + value.setValue(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithDateTimeValue(String attributeName, Instant attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSDateTime value = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSDateTime.TYPE_NAME); + value.setValue(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + + static Status successStatus() { + return status(StatusCode.SUCCESS); + } + + static Status status(String code) { + Status status = new StatusBuilder().buildObject(); + StatusCode statusCode = new StatusCodeBuilder().buildObject(); + statusCode.setValue(code); + status.setStatusCode(statusCode); + return status; + } + + public static LogoutRequest assertingPartyLogoutRequest(RelyingPartyRegistration registration) { + LogoutRequestBuilder logoutRequestBuilder = new LogoutRequestBuilder(); + LogoutRequest logoutRequest = logoutRequestBuilder.buildObject(); + logoutRequest.setID("id"); + NameIDBuilder nameIdBuilder = new NameIDBuilder(); + NameID nameId = nameIdBuilder.buildObject(); + nameId.setValue("user"); + logoutRequest.setNameID(nameId); + IssuerBuilder issuerBuilder = new IssuerBuilder(); + Issuer issuer = issuerBuilder.buildObject(); + issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); + logoutRequest.setIssuer(issuer); + logoutRequest.setDestination(registration.getSingleLogoutServiceLocation()); + return logoutRequest; + } + + public static LogoutRequest assertingPartyLogoutRequestNameIdInEncryptedId(RelyingPartyRegistration registration) { + LogoutRequestBuilder logoutRequestBuilder = new LogoutRequestBuilder(); + LogoutRequest logoutRequest = logoutRequestBuilder.buildObject(); + logoutRequest.setID("id"); + NameIDBuilder nameIdBuilder = new NameIDBuilder(); + NameID nameId = nameIdBuilder.buildObject(); + nameId.setValue("user"); + logoutRequest.setNameID(null); + Saml2X509Credential credential = registration.getAssertingPartyDetails() + .getEncryptionX509Credentials() + .iterator() + .next(); + EncryptedID encrypted = encrypted(nameId, credential); + logoutRequest.setEncryptedID(encrypted); + IssuerBuilder issuerBuilder = new IssuerBuilder(); + Issuer issuer = issuerBuilder.buildObject(); + issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); + logoutRequest.setIssuer(issuer); + logoutRequest.setDestination(registration.getSingleLogoutServiceLocation()); + return logoutRequest; + } + + public static LogoutResponse assertingPartyLogoutResponse(RelyingPartyRegistration registration) { + LogoutResponseBuilder logoutResponseBuilder = new LogoutResponseBuilder(); + LogoutResponse logoutResponse = logoutResponseBuilder.buildObject(); + logoutResponse.setID("id"); + StatusBuilder statusBuilder = new StatusBuilder(); + StatusCodeBuilder statusCodeBuilder = new StatusCodeBuilder(); + StatusCode code = statusCodeBuilder.buildObject(); + code.setValue(StatusCode.SUCCESS); + Status status = statusBuilder.buildObject(); + status.setStatusCode(code); + logoutResponse.setStatus(status); + IssuerBuilder issuerBuilder = new IssuerBuilder(); + Issuer issuer = issuerBuilder.buildObject(); + issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); + logoutResponse.setIssuer(issuer); + logoutResponse.setDestination(registration.getSingleLogoutServiceResponseLocation()); + return logoutResponse; + } + + public static LogoutRequest relyingPartyLogoutRequest(RelyingPartyRegistration registration) { + LogoutRequestBuilder logoutRequestBuilder = new LogoutRequestBuilder(); + LogoutRequest logoutRequest = logoutRequestBuilder.buildObject(); + logoutRequest.setID("id"); + NameIDBuilder nameIdBuilder = new NameIDBuilder(); + NameID nameId = nameIdBuilder.buildObject(); + nameId.setValue("user"); + logoutRequest.setNameID(nameId); + IssuerBuilder issuerBuilder = new IssuerBuilder(); + Issuer issuer = issuerBuilder.buildObject(); + issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); + logoutRequest.setIssuer(issuer); + logoutRequest.setDestination(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation()); + return logoutRequest; + } + + static T build(QName qName) { + return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName); + } + + public static String getEncodedAssertion(String issuerEntityId, + String format, String username, + String spEndpoint, String audienceEntityID, boolean sign) { + final Instant now = Instant.now(); + final Instant until = now.plus(1, java.time.temporal.ChronoUnit.HOURS); + + Assertion assertion = assertion(username, issuerEntityId, audienceEntityID, spEndpoint); + assertion.setIssueInstant(now); + + // update subject + assertion.getSubject().getNameID().setNameQualifier(NameID.UNSPECIFIED); + assertion.getSubject().getNameID().setFormat(format); + assertion.getSubject().getSubjectConfirmations().get(0).getSubjectConfirmationData().setNotOnOrAfter(until); + + // update conditions + final Audience audience = build(Audience.DEFAULT_ELEMENT_NAME); + audience.setURI(audienceEntityID); + final AudienceRestriction audienceRestriction = build(AudienceRestriction.DEFAULT_ELEMENT_NAME); + audienceRestriction.getAudiences().add(audience); + + assertion.getConditions().setNotBefore(now.minusSeconds(2)); + assertion.getConditions().setNotOnOrAfter(until); + assertion.getConditions().getAudienceRestrictions().add(audienceRestriction); + + // update authn statement + final AuthnContextClassRef authnContextClassRef = build(AuthnContextClassRef.DEFAULT_ELEMENT_NAME); + authnContextClassRef.setURI("urn:oasis:names:tc:SAML:2.0:ac:classes:Password"); + final AuthnContext authnContext = build(AuthnContext.DEFAULT_ELEMENT_NAME); + authnContext.setAuthnContextClassRef(authnContextClassRef); + + final AuthnStatement authnStatement = assertion.getAuthnStatements().get(0); + authnStatement.setAuthnInstant(now); + authnStatement.setSessionIndex("a358a06c15ja8d7a1idjaj07jb52gdi"); + authnStatement.setSessionNotOnOrAfter(until); + authnStatement.setAuthnContext(authnContext); + + // sign, serialize and encode + if (sign) { + Saml2X509Credential signingCredential = Saml2X509Credential.signing(TestCredentialObjects.legacyPrivateKey(), TestCredentialObjects.legacyX509Certificate()); + assertion = signed(assertion, signingCredential, issuerEntityId); + } + String serialized = Saml2TestUtils.serialize(assertion); + return Saml2Utils.samlEncode(serialized); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java new file mode 100644 index 00000000000..2bfe428f869 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter; + +/** + * This was copied from Spring Security Test Classes + * Migrate to use the Spring Security class when it is made public + *

    + * Modified to work with org.springframework.security.saml2.core.Saml2X509Credential + * instead of now deprecated org.springframework.security.saml2.credentials.Saml2X509Credential; + */ +public final class TestRelyingPartyRegistrations { + + private TestRelyingPartyRegistrations() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static RelyingPartyRegistration.Builder relyingPartyRegistration() { + String registrationId = "simplesamlphp"; + String rpEntityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}"; + Saml2X509Credential signingCredential = TestSaml2X509Credentials.relyingPartySigningCredential(); + String assertionConsumerServiceLocation = "{baseUrl}" + + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI; + String apEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"; + Saml2X509Credential verificationCertificate = TestSaml2X509Credentials.relyingPartyVerifyingCredential(); + String singleSignOnServiceLocation = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"; + String singleLogoutServiceLocation = "{baseUrl}/logout/saml2/slo"; + return RelyingPartyRegistration.withRegistrationId(registrationId) + .entityId(rpEntityId) + .nameIdFormat("format") + .assertionConsumerServiceLocation(assertionConsumerServiceLocation) + .singleLogoutServiceLocation(singleLogoutServiceLocation) + .assertingPartyDetails(c -> c.entityId(apEntityId).singleSignOnServiceLocation(singleSignOnServiceLocation)) + .signingX509Credentials(c -> c.add(signingCredential)) + .decryptionX509Credentials(c -> c.add(verificationCertificate)); + } + + public static RelyingPartyRegistration.Builder noCredentials() { + return RelyingPartyRegistration.withRegistrationId("saml")//"registration-id") + .entityId("rp-entity-id") + .singleLogoutServiceLocation("https://rp.example.org/logout/saml2/request") + .singleLogoutServiceResponseLocation("https://rp.example.org/logout/saml2/response") + .assertionConsumerServiceLocation("https://rp.example.org/acs") + .assertingPartyDetails(party -> party.entityId("ap-entity-id") + .singleSignOnServiceLocation("https://ap.example.org/sso") + .singleLogoutServiceLocation("https://ap.example.org/logout/saml2/request") + .singleLogoutServiceResponseLocation("https://ap.example.org/logout/saml2/response")); + } + + public static RelyingPartyRegistration.Builder full() { + return noCredentials() + .signingX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartySigningCredential())) + .decryptionX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential())) + .assertingPartyDetails(party -> party.verificationX509Credentials( + c -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestSaml2X509Credentials.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestSaml2X509Credentials.java new file mode 100644 index 00000000000..7c55bc95350 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestSaml2X509Credentials.java @@ -0,0 +1,254 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.bouncycastle.util.encoders.Base64; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; + +/** + * This was copied from Spring Security Test Classes + * Migrate to use the Spring Security class when it is made public + *

    + * Changed: + * privateKey/decodePrivateKey no longer uses bouncycastle and cryptacular with does not work with FIPS mode. + */ +public final class TestSaml2X509Credentials { + + private TestSaml2X509Credentials() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static Saml2X509Credential assertingPartySigningCredential() { + return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), Saml2X509CredentialType.SIGNING); + } + + public static Saml2X509Credential assertingPartyEncryptingCredential() { + return new Saml2X509Credential(spCertificate(), Saml2X509CredentialType.ENCRYPTION); + } + + public static Saml2X509Credential assertingPartyPrivateCredential() { + return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), Saml2X509CredentialType.SIGNING, + Saml2X509CredentialType.DECRYPTION); + } + + public static Saml2X509Credential relyingPartyVerifyingCredential() { + return new Saml2X509Credential(idpCertificate(), Saml2X509CredentialType.VERIFICATION); + } + + public static Saml2X509Credential relyingPartyEncryptingCredential() { + return new Saml2X509Credential(idpCertificate(), Saml2X509CredentialType.ENCRYPTION); + } + + public static Saml2X509Credential relyingPartySigningCredential() { + return new Saml2X509Credential(spPrivateKey(), spCertificate(), Saml2X509CredentialType.SIGNING); + } + + public static Saml2X509Credential relyingPartyDecryptingCredential() { + return new Saml2X509Credential(spPrivateKey(), spCertificate(), Saml2X509CredentialType.DECRYPTION); + } + + public static Saml2X509Credential altPublicCredential() { + return new Saml2X509Credential(altCertificate(), Saml2X509CredentialType.VERIFICATION, + Saml2X509CredentialType.ENCRYPTION); + } + + public static Saml2X509Credential altPrivateCredential() { + return new Saml2X509Credential(altPrivateKey(), altCertificate(), Saml2X509CredentialType.SIGNING, + Saml2X509CredentialType.DECRYPTION); + } + + private static X509Certificate certificate(String cert) { + ByteArrayInputStream certBytes = new ByteArrayInputStream(cert.getBytes()); + try { + return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certBytes); + } catch (CertificateException ex) { + throw new Saml2Exception(ex); + } + } + + private static PrivateKey privateKey(String key) { + key = key.replace("-----BEGIN PRIVATE KEY-----", ""); + key = key.replace("-----END PRIVATE KEY-----", ""); + key = key.replaceAll("\\s+", ""); + return decodePrivateKey(key.getBytes(StandardCharsets.UTF_8)); + } + + private static PrivateKey decodePrivateKey(byte[] keyBytes) { + try { + byte[] pkcs8EncodedBytes = Base64.decode(keyBytes); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate(keySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new Saml2Exception(e); + } + } + + private static X509Certificate idpCertificate() { + return certificate( + """ + -----BEGIN CERTIFICATE----- + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD + VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD + VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX + c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw + aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ + BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa + BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD + DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr + QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62 + E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz + 2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW + RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ + nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5 + cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph + iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5 + ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD + AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO + nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v + ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu + xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z + V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3 + lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + -----END CERTIFICATE-----"""); + } + + private static PrivateKey idpPrivateKey() { + return privateKey(""" + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4cn62E1xLqpN3 + 4PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZX + W+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHE + fDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7h + Z6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/T + Xy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7 + I+J5lS8VAgMBAAECggEBAKyxBlIS7mcp3chvq0RF7B3PHFJMMzkwE+t3pLJcs4cZ + nezh/KbREfP70QjXzk/llnZCvxeIs5vRu24vbdBm79qLHqBuHp8XfHHtuo2AfoAQ + l4h047Xc/+TKMivnPQ0jX9qqndKDLqZDf5wnbslDmlskvF0a/MjsLU0TxtOfo+dB + t55FW11cGqxZwhS5Gnr+cbw3OkHz23b9gEOt9qfwPVepeysbmm9FjU+k4yVa7rAN + xcbzVb6Y7GCITe2tgvvEHmjB9BLmWrH3mZ3Af17YU/iN6TrpPd6Sj3QoS+2wGtAe + HbUs3CKJu7bIHcj4poal6Kh8519S+erJTtqQ8M0ZiEECgYEA43hLYAPaUueFkdfh + 9K/7ClH6436CUH3VdizwUXi26fdhhV/I/ot6zLfU2mgEHU22LBECWQGtAFm8kv0P + zPn+qjaR3e62l5PIlSYbnkIidzoDZ2ztu4jF5LgStlTJQPteFEGgZVl5o9DaSZOq + Yd7G3XqXuQ1VGMW58G5FYJPtA1cCgYEAz5TPUtK+R2KXHMjUwlGY9AefQYRYmyX2 + Tn/OFgKvY8lpAkMrhPKONq7SMYc8E9v9G7A0dIOXvW7QOYSapNhKU+np3lUafR5F + 4ZN0bxZ9qjHbn3AMYeraKjeutHvlLtbHdIc1j3sxe/EzltRsYmiqLdEBW0p6hwWg + tyGhYWVyaXMCgYAfDOKtHpmEy5nOCLwNXKBWDk7DExfSyPqEgSnk1SeS1HP5ctPK + +1st6sIhdiVpopwFc+TwJWxqKdW18tlfT5jVv1E2DEnccw3kXilS9xAhWkfwrEvf + V5I74GydewFl32o+NZ8hdo9GL1I8zO1rIq/et8dSOWGuWf9BtKu/vTGTTQKBgFxU + VjsCnbvmsEwPUAL2hE/WrBFaKocnxXx5AFNt8lEyHtDwy4Sg1nygGcIJ4sD6koQk + RdClT3LkvR04TAiSY80bN/i6ZcPNGUwSaDGZEWAIOSWbkwZijZNFnSGOEgxZX/IG + yd39766vREEMTwEeiMNEOZQ/dmxkJm4OOVe25cLdAoGACOtPnq1Fxay80UYBf4rQ + +bJ9yX1ulB8WIree1hD7OHSB2lRHxrVYWrglrTvkh63Lgx+EcsTV788OsvAVfPPz + BZrn8SdDlQqalMxUBYEFwnsYD3cQ8yOUnijFVC4xNcdDv8OIqVgSk4KKxU5AshaA + xk6Mox+u8Cc2eAK12H13i+8= + -----END PRIVATE KEY-----"""); + } + + private static X509Certificate spCertificate() { + return certificate(""" + -----BEGIN CERTIFICATE----- + MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC + VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsG + A1UECgwUU3ByaW5nIFNlY3VyaXR5IFNBTUwxCzAJBgNVBAsMAnNwMSAwHgYDVQQD + DBdzcC5zcHJpbmcuc2VjdXJpdHkuc2FtbDAeFw0xODA1MTQxNDMwNDRaFw0yODA1 + MTExNDMwNDRaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjES + MBAGA1UEBwwJVmFuY291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FN + TDELMAkGA1UECwwCc3AxIDAeBgNVBAMMF3NwLnNwcmluZy5zZWN1cml0eS5zYW1s + MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRu7/EI0BlNzMEBFVAcbx+lLos + vzIWU+01dGTY8gBdhMQNYKZ92lMceo2CuVJ66cUURPym3i7nGGzoSnAxAre+0YIM + +U0razrWtAUE735bkcqELZkOTZLelaoOztmWqRbe5OuEmpewH7cx+kNgcVjdctOG + y3Q6x+I4qakY/9qhBQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAAeViTvHOyQopWEi + XOfI2Z9eukwrSknDwq/zscR0YxwwqDBMt/QdAODfSwAfnciiYLkmEjlozWRtOeN+ + qK7UFgP1bRl5qksrYX5S0z2iGJh0GvonLUt3e20Ssfl5tTEDDnAEUMLfBkyaxEHD + RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B + -----END CERTIFICATE-----"""); + } + + private static PrivateKey spPrivateKey() { + return privateKey(""" + -----BEGIN PRIVATE KEY----- + MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE + VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK + cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6 + Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn + x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5 + wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd + vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY + 8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX + oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx + EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0 + KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt + YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr + 9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM + INrtuLp4YHbgk1mi + -----END PRIVATE KEY-----"""); + } + + private static X509Certificate altCertificate() { + return certificate(""" + -----BEGIN CERTIFICATE----- + MIICkDCCAfkCFEstVfmWSFQp/j88GaMUwqVK72adMA0GCSqGSIb3DQEBCwUAMIGG + MQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjESMBAGA1UEBwwJVmFu + Y291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FNTDEMMAoGA1UECwwD + YWx0MSEwHwYDVQQDDBhhbHQuc3ByaW5nLnNlY3VyaXR5LnNhbWwwHhcNMjIwMjEw + MTY1ODA4WhcNMzIwMjEwMTY1ODA4WjCBhjELMAkGA1UEBhMCVVMxEzARBgNVBAgM + Cldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsGA1UECgwUU3ByaW5n + IFNlY3VyaXR5IFNBTUwxDDAKBgNVBAsMA2FsdDEhMB8GA1UEAwwYYWx0LnNwcmlu + Zy5zZWN1cml0eS5zYW1sMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9ZGWj + TPDsymQCJL044py4xLsBI/S9RvzNeR9oD/tHyoxCE+YZzjf0PyBtwqKzkKWqCPf4 + XGUYHfEpkM5kJYwCW8TsOx5fnwLIQweiPqjYrBr/O0IjHMqYG9HlR/ros7iBt4ab + EGUu/B9yYg1YRYPxKQ6TNP3AD+9tBT8TsFFyjwIDAQABMA0GCSqGSIb3DQEBCwUA + A4GBAKJf2VHLjkCHRxlbWn63jGiquq3ENYgd1JS0DZ3ggFmuc6zQiqxzRGtArIDZ + 0jH5nrG0jcvO0fqDqBQh0iT8thfUnkViAQvACZ9a+0x0NzUicJ+Ra51c8Z2enqbg + pXy+ga67HcAXrDekm1MCGCgiEb/Cgl41lsideqhC8Efl7PRN + -----END CERTIFICATE-----"""); + } + + private static PrivateKey altPrivateKey() { + return privateKey(""" + -----BEGIN PRIVATE KEY----- + MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAL1kZaNM8OzKZAIk + vTjinLjEuwEj9L1G/M15H2gP+0fKjEIT5hnON/Q/IG3CorOQpaoI9/hcZRgd8SmQ + zmQljAJbxOw7Hl+fAshDB6I+qNisGv87QiMcypgb0eVH+uizuIG3hpsQZS78H3Ji + DVhFg/EpDpM0/cAP720FPxOwUXKPAgMBAAECgYEApYKslAZ0cer5dSoYNzNLFOnQ + J1H92r/Dw+k6+h0lUvr+keyD5T9jhM76DxHOUDBzpmIKGoDcVDQugk2rILfzXsQA + JtwvDRJk32Z02Vt0jb7t/WUOOQhjKCjQuv9/tOx90GCl0VxYG69UOjaMRWrlg/i9 + 6/zcTRIahIn5XxF0psECQQD7ivJCpDbOLJGsc8gNJR4cvjZ1q0mHIOrbKqJC0y1n + 5DrzGEflPeyCUwnOKNp9HJQP8gmZzXfj0JM9KsjpiUChAkEAwL+FmhDoTiqStIrH + h9Kdnsev//imMmRHxjwDhntYvqavUsISRmY3imd8inoYq5dzWQMzBtoTyMRmqeLT + DHV1LwJAW4xaV37Eo4z9B7Kr4Hzd1MA1ueW5QQDt+Q4vN/r7z4/1FHyFzh0Xcucd + 7nZX7qj0CkmgzOVG+Rb0P5LOxJA7gQJBAK1KQ2qNct375qPM9bEGSVGchH6k5X7+ + q4ztHdpFgTb/EzdbZiTG935GpjC1rwJuinTnrHOnkwv4j7iDRm24GF8CQQDqPvrQ + GcItR6UUy0q/B8UxLzlE6t+HiznfiJKfyGgCHU56Y4/ZhzSQz2MZHz9SK4DsUL9s + bOYrWq8VY2fyjV1t + -----END PRIVATE KEY-----"""); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandlerTest.java new file mode 100644 index 00000000000..b43deb0b869 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandlerTest.java @@ -0,0 +1,195 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.authentication.UaaSamlPrincipal; +import org.cloudfoundry.identity.uaa.authentication.ZoneAwareWhitelistLogoutSuccessHandler; +import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthLogoutSuccessHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UaaDelegatingLogoutSuccessHandlerTest { + + private static final String REG_ID = "regId"; + private static final String URL = "https://url.com"; + UaaDelegatingLogoutSuccessHandler logoutSuccessHandler; + + @Mock + private ZoneAwareWhitelistLogoutSuccessHandler zoneAwareWhitelistLogoutHandler; + + @Mock + private Saml2RelyingPartyInitiatedLogoutSuccessHandler saml2RelyingPartyInitiatedLogoutSuccessHandler; + + @Mock + private ExternalOAuthLogoutSuccessHandler externalOAuthLogoutHandler; + + @Mock + private RelyingPartyRegistrationResolver relyingPartyRegistrationResolver; + + @Mock + private HttpServletRequest request; + + @Mock + private Authentication authentication; + + private static final HttpServletResponse response = null; + + @BeforeEach + void setup() { + logoutSuccessHandler = new UaaDelegatingLogoutSuccessHandler(zoneAwareWhitelistLogoutHandler, saml2RelyingPartyInitiatedLogoutSuccessHandler, externalOAuthLogoutHandler, relyingPartyRegistrationResolver); + } + + @Test + void fallsThruToZoneAwareWhitelistLogoutHandler() throws ServletException, IOException { + logoutSuccessHandler.onLogoutSuccess(request, response, authentication); + verifyCorrectOnLogoutSuccessCalled(false, false, true); + } + + @Test + void shouldPerformOAuthRpInitiatedLogout() throws ServletException, IOException { + var oauthConfig = mock(AbstractExternalOAuthIdentityProviderDefinition.class); + when(externalOAuthLogoutHandler.getOAuthProviderForAuthentication(authentication)).thenReturn(oauthConfig); + when(externalOAuthLogoutHandler.getLogoutUrl(oauthConfig)).thenReturn(URL); + when(externalOAuthLogoutHandler.getPerformRpInitiatedLogout(oauthConfig)).thenReturn(true); + + logoutSuccessHandler.onLogoutSuccess(request, response, authentication); + verifyCorrectOnLogoutSuccessCalled(false, true, false); + } + + @Test + void shouldPerformSamlRelyingPartyLogout() throws ServletException, IOException { + var mockPrincipal = mock(UaaSamlPrincipal.class); + when(authentication.getPrincipal()).thenReturn(mockPrincipal); + when(mockPrincipal.getRelyingPartyRegistrationId()).thenReturn(REG_ID); + var mockRegistration = mock(RelyingPartyRegistration.class); + when(relyingPartyRegistrationResolver.resolve(any(), eq(REG_ID))).thenReturn(mockRegistration); + var mockAssertingPartyDetails = mock(RelyingPartyRegistration.AssertingPartyDetails.class); + when(mockRegistration.getAssertingPartyDetails()).thenReturn(mockAssertingPartyDetails); + when(mockAssertingPartyDetails.getSingleLogoutServiceLocation()).thenReturn(URL); + + logoutSuccessHandler.onLogoutSuccess(request, response, authentication); + verifyCorrectOnLogoutSuccessCalled(true, false, false); + } + + /* + * Negative Tests for saml2RelyingPartyInitiatedLogoutSuccessHandler + */ + + @Test + void nullAuthFallsThruToZoneAwareWhitelistLogoutHandler() throws ServletException, IOException { + logoutSuccessHandler.onLogoutSuccess(request, response, null); + verify(zoneAwareWhitelistLogoutHandler).onLogoutSuccess(request, response, null); + verify(externalOAuthLogoutHandler, never()).onLogoutSuccess(any(), any(), any()); + verify(saml2RelyingPartyInitiatedLogoutSuccessHandler, never()).onLogoutSuccess(any(), any(), any()); + } + + @Test + void nullRegIdFallsThruToZoneAwareWhitelistLogoutHandler() throws ServletException, IOException { + var mockPrincipal = mock(UaaSamlPrincipal.class); + when(authentication.getPrincipal()).thenReturn(mockPrincipal); + + logoutSuccessHandler.onLogoutSuccess(request, response, authentication); + verifyCorrectOnLogoutSuccessCalled(false, false, true); + } + + @Test + void nullRegistrationFallsThruToZoneAwareWhitelistLogoutHandler() throws ServletException, IOException { + var mockPrincipal = mock(UaaSamlPrincipal.class); + when(authentication.getPrincipal()).thenReturn(mockPrincipal); + when(mockPrincipal.getRelyingPartyRegistrationId()).thenReturn(REG_ID); + + logoutSuccessHandler.onLogoutSuccess(request, response, authentication); + verifyCorrectOnLogoutSuccessCalled(false, false, true); + } + + @Test + void nullAssertingPartyDetailsFallsThruToZoneAwareWhitelistLogoutHandler() throws ServletException, IOException { + var mockPrincipal = mock(UaaSamlPrincipal.class); + when(authentication.getPrincipal()).thenReturn(mockPrincipal); + when(mockPrincipal.getRelyingPartyRegistrationId()).thenReturn(REG_ID); + var mockRegistration = mock(RelyingPartyRegistration.class); + when(relyingPartyRegistrationResolver.resolve(any(), eq(REG_ID))).thenReturn(mockRegistration); + + logoutSuccessHandler.onLogoutSuccess(request, response, authentication); + verifyCorrectOnLogoutSuccessCalled(false, false, true); + } + + @Test + void nullSingleLogoutServiceLocationFallsThruToZoneAwareWhitelistLogoutHandler() throws ServletException, IOException { + var mockPrincipal = mock(UaaSamlPrincipal.class); + when(authentication.getPrincipal()).thenReturn(mockPrincipal); + when(mockPrincipal.getRelyingPartyRegistrationId()).thenReturn(REG_ID); + var mockRegistration = mock(RelyingPartyRegistration.class); + when(relyingPartyRegistrationResolver.resolve(any(), eq(REG_ID))).thenReturn(mockRegistration); + var mockAssertingPartyDetails = mock(RelyingPartyRegistration.AssertingPartyDetails.class); + when(mockRegistration.getAssertingPartyDetails()).thenReturn(mockAssertingPartyDetails); + + logoutSuccessHandler.onLogoutSuccess(request, response, authentication); + verifyCorrectOnLogoutSuccessCalled(false, false, true); + } + + /* + * Negative Tests for externalOAuthLogoutHandler + */ + + @Test + void nullLogoutUrlFallsThruToZoneAwareWhitelistLogoutHandler() throws ServletException, IOException { + var oauthConfig = mock(AbstractExternalOAuthIdentityProviderDefinition.class); + when(externalOAuthLogoutHandler.getOAuthProviderForAuthentication(authentication)).thenReturn(oauthConfig); + when(externalOAuthLogoutHandler.getLogoutUrl(oauthConfig)).thenReturn(null); + when(externalOAuthLogoutHandler.getPerformRpInitiatedLogout(oauthConfig)).thenReturn(true); + + logoutSuccessHandler.onLogoutSuccess(request, response, authentication); + verifyCorrectOnLogoutSuccessCalled(false, false, true); + } + + @Test + void falsePerformRpInitiatedLogoutFallsThruToZoneAwareWhitelistLogoutHandler() throws ServletException, IOException { + var oauthConfig = mock(AbstractExternalOAuthIdentityProviderDefinition.class); + when(externalOAuthLogoutHandler.getOAuthProviderForAuthentication(authentication)).thenReturn(oauthConfig); + when(externalOAuthLogoutHandler.getLogoutUrl(oauthConfig)).thenReturn(URL); + when(externalOAuthLogoutHandler.getPerformRpInitiatedLogout(oauthConfig)).thenReturn(false); + + logoutSuccessHandler.onLogoutSuccess(request, response, authentication); + verifyCorrectOnLogoutSuccessCalled(false, false, true); + } + + private void verifyCorrectOnLogoutSuccessCalled(boolean saml, boolean oAuth, boolean zoneAware) throws IOException, ServletException { + if (saml) { + verify(saml2RelyingPartyInitiatedLogoutSuccessHandler).onLogoutSuccess(request, response, authentication); + } else { + verify(saml2RelyingPartyInitiatedLogoutSuccessHandler, never()).onLogoutSuccess(any(), any(), any()); + } + + if (oAuth) { + verify(externalOAuthLogoutHandler).onLogoutSuccess(request, response, authentication); + } else { + verify(externalOAuthLogoutHandler, never()).onLogoutSuccess(any(), any(), any()); + } + + if (zoneAware) { + verify(zoneAwareWhitelistLogoutHandler).onLogoutSuccess(request, response, authentication); + } else { + verify(zoneAwareWhitelistLogoutHandler, never()).onLogoutSuccess(any(), any(), any()); + } + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaInResponseToHandlingResponseValidatorTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaInResponseToHandlingResponseValidatorTest.java new file mode 100644 index 00000000000..81805f6f3da --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaInResponseToHandlingResponseValidatorTest.java @@ -0,0 +1,121 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UaaInResponseToHandlingResponseValidatorTest { + + @Mock + Converter delegate; + + @Mock + OpenSaml4AuthenticationProvider.ResponseToken responseToken; + + @BeforeEach + void beforeEach() { + IdentityZoneHolder.clear(); + } + + @Test + void delegateReturnsNullIsHandled() { + when(delegate.convert(any())).thenReturn(null); + UaaInResponseToHandlingResponseValidator uaaInResponseToHandlingResponseValidator = new UaaInResponseToHandlingResponseValidator(delegate, true); + + assertThat(uaaInResponseToHandlingResponseValidator.convert(responseToken)).isNull(); + } + + @Test + void delegateReturnsSuccessIsHandled() { + Saml2ResponseValidatorResult success = Saml2ResponseValidatorResult.success(); + when(delegate.convert(any())).thenReturn(success); + UaaInResponseToHandlingResponseValidator uaaInResponseToHandlingResponseValidator = new UaaInResponseToHandlingResponseValidator(delegate, true); + + assertThat(uaaInResponseToHandlingResponseValidator.convert(responseToken)).isSameAs(success); + } + + @Test + void notDisabledPassesThru() { + Saml2ResponseValidatorResult withErrors = Saml2ResponseValidatorResult.failure(new Saml2Error("invalid_in_response_to", "invalid_in_response_to")); + when(delegate.convert(any())).thenReturn(withErrors); + UaaInResponseToHandlingResponseValidator uaaInResponseToHandlingResponseValidator = new UaaInResponseToHandlingResponseValidator(delegate, false); + + Saml2ResponseValidatorResult result = uaaInResponseToHandlingResponseValidator.convert(responseToken); + assertThat(result).isSameAs(withErrors); + assertThat(result.hasErrors()).isTrue(); + assertThat(result.getErrors()) + .hasSize(1) + .extracting(Saml2Error::getErrorCode) + .containsExactly("invalid_in_response_to"); + } + + @Test + void otherErrorsArePassedThru() { + Saml2ResponseValidatorResult withErrors = Saml2ResponseValidatorResult.failure(new Saml2Error("other_error", "other_error")); + when(delegate.convert(any())).thenReturn(withErrors); + UaaInResponseToHandlingResponseValidator uaaInResponseToHandlingResponseValidator = new UaaInResponseToHandlingResponseValidator(delegate, true); + + Saml2ResponseValidatorResult result = uaaInResponseToHandlingResponseValidator.convert(responseToken); + + // different instance, but the same content + assertThat(result.hasErrors()).isTrue(); + assertThat(result.getErrors()) + .hasSize(1) + .extracting(Saml2Error::getErrorCode) + .containsExactly("other_error"); + } + + @Test + void inResponseToIsRemoved() { + Saml2ResponseValidatorResult withErrors = Saml2ResponseValidatorResult.failure(new Saml2Error("invalid_in_response_to", "invalid_in_response_to")); + when(delegate.convert(any())).thenReturn(withErrors); + UaaInResponseToHandlingResponseValidator uaaInResponseToHandlingResponseValidator = new UaaInResponseToHandlingResponseValidator(delegate, true); + Saml2ResponseValidatorResult result = uaaInResponseToHandlingResponseValidator.convert(responseToken); + assertThat(result.hasErrors()).isFalse(); + } + + @Test + void otherZoneIsDisabledRemovesError() { + setupIdentityZone(true); + Saml2ResponseValidatorResult withErrors = Saml2ResponseValidatorResult.failure(new Saml2Error("invalid_in_response_to", "invalid_in_response_to")); + when(delegate.convert(any())).thenReturn(withErrors); + UaaInResponseToHandlingResponseValidator uaaInResponseToHandlingResponseValidator = new UaaInResponseToHandlingResponseValidator(delegate, true); + + Saml2ResponseValidatorResult result = uaaInResponseToHandlingResponseValidator.convert(responseToken); + assertThat(result.hasErrors()).isFalse(); + } + + @Test + void otherZoneIsNotDisabledPassesThru() { + setupIdentityZone(false); + Saml2ResponseValidatorResult withErrors = Saml2ResponseValidatorResult.failure(new Saml2Error("invalid_in_response_to", "invalid_in_response_to")); + when(delegate.convert(any())).thenReturn(withErrors); + UaaInResponseToHandlingResponseValidator uaaInResponseToHandlingResponseValidator = new UaaInResponseToHandlingResponseValidator(delegate, false); + + Saml2ResponseValidatorResult result = uaaInResponseToHandlingResponseValidator.convert(responseToken); + assertThat(result).isSameAs(withErrors).returns(true, Saml2ResponseValidatorResult::hasErrors); + } + + private static void setupIdentityZone(boolean disableInResponseToCheck) { + IdentityZone identityZone = new IdentityZone(); + identityZone.setId("testZone"); + identityZone.setConfig(new IdentityZoneConfiguration()); + identityZone.getConfig().setSamlConfig(new SamlConfig()); + identityZone.getConfig().getSamlConfig().setDisableInResponseToCheck(disableInResponseToCheck); + IdentityZoneHolder.set(identityZone); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareKeyManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareKeyManagerTest.java new file mode 100644 index 00000000000..472160dd114 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareKeyManagerTest.java @@ -0,0 +1,66 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class ZoneAwareKeyManagerTest { + @Mock + SamlKeyManager mockSamlKeyManager; + + private ZoneAwareKeyManager zoneAwareKeyManager; + + @BeforeEach + void setUp() { + getKeyManagerThreadLocal().set(mockSamlKeyManager); + zoneAwareKeyManager = new ZoneAwareKeyManager(); + } + + @AfterAll + static void afterAll() { + getKeyManagerThreadLocal().remove(); + } + + @Test + void getCredential() { + zoneAwareKeyManager.getCredential("keyName"); + verify(mockSamlKeyManager).getCredential("keyName"); + } + + @Test + void getDefaultCredential() { + zoneAwareKeyManager.getDefaultCredential(); + verify(mockSamlKeyManager).getDefaultCredential(); + } + + @Test + void getDefaultCredentialName() { + zoneAwareKeyManager.getDefaultCredentialName(); + verify(mockSamlKeyManager).getDefaultCredentialName(); + } + + @Test + void getAvailableCredentials() { + zoneAwareKeyManager.getAvailableCredentials(); + verify(mockSamlKeyManager).getAvailableCredentials(); + } + + @Test + void getAvailableCredentialIds() { + zoneAwareKeyManager.getAvailableCredentialIds(); + verify(mockSamlKeyManager).getAvailableCredentialIds(); + } + + @SuppressWarnings("unchecked") + private static ThreadLocal getKeyManagerThreadLocal() { + return (ThreadLocal) ReflectionTestUtils.getField(IdentityZoneHolder.class, "KEY_MANAGER_THREAD_LOCAL"); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGeneratorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGeneratorTests.java deleted file mode 100644 index 25ac5b0d0e2..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGeneratorTests.java +++ /dev/null @@ -1,222 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; -import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils; -import org.cloudfoundry.identity.uaa.saml.SamlKey; -import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.opensaml.Configuration; -import org.opensaml.DefaultBootstrap; -import org.opensaml.xml.io.MarshallingException; -import org.opensaml.xml.security.keyinfo.NamedKeyInfoGeneratorManager; -import org.springframework.security.saml.SAMLConstants; -import org.springframework.security.saml.key.KeyManager; -import org.springframework.security.saml.metadata.ExtendedMetadata; -import org.springframework.security.saml.metadata.MetadataManager; -import org.springframework.security.saml.util.SAMLUtil; - -import java.security.Security; -import java.util.List; - -import static org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManagerFactoryTests.*; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; - -@ExtendWith(PollutionPreventionExtension.class) -public class ZoneAwareMetadataGeneratorTests { - - private static final String ZONE_ID = "zone-id"; - private ZoneAwareMetadataGenerator generator; - private IdentityZone otherZone; - private IdentityZoneConfiguration otherZoneDefinition; - private KeyManager keyManager; - private ExtendedMetadata extendedMetadata; - - public static final SamlKey samlKey1 = new SamlKey(key1, passphrase1, certificate1); - public static final SamlKey samlKey2 = new SamlKey(key2, passphrase2, certificate2); - - public static final String cert1Plain = certificate1.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----", "").replace("\n", ""); - public static final String cert2Plain = certificate2.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----", "").replace("\n", ""); - - @BeforeAll - static void bootstrap() throws Exception { - Security.addProvider(new BouncyCastleFipsProvider()); - DefaultBootstrap.bootstrap(); - NamedKeyInfoGeneratorManager keyInfoGeneratorManager = Configuration.getGlobalSecurityConfiguration().getKeyInfoGeneratorManager(); - keyInfoGeneratorManager.getManager(SAMLConstants.SAML_METADATA_KEY_INFO_GENERATOR); - } - - @BeforeEach - void setUp() { - otherZone = new IdentityZone(); - otherZone.setId(ZONE_ID); - otherZone.setName(ZONE_ID); - otherZone.setSubdomain(ZONE_ID); - otherZone.setConfig(new IdentityZoneConfiguration()); - otherZoneDefinition = otherZone.getConfig(); - otherZoneDefinition.getSamlConfig().setRequestSigned(true); - otherZoneDefinition.getSamlConfig().setWantAssertionSigned(true); - otherZoneDefinition.getSamlConfig().addAndActivateKey("key-1", samlKey1); - - otherZone.setConfig(otherZoneDefinition); - - generator = new ZoneAwareMetadataGenerator(); - generator.setEntityBaseURL("http://localhost:8080/uaa"); - generator.setEntityId("entityIdValue"); - - extendedMetadata = new org.springframework.security.saml.metadata.ExtendedMetadata(); - extendedMetadata.setIdpDiscoveryEnabled(true); - extendedMetadata.setAlias("entityAlias"); - extendedMetadata.setSignMetadata(true); - generator.setExtendedMetadata(extendedMetadata); - - keyManager = new ZoneAwareKeyManager(); - generator.setKeyManager(keyManager); - } - - @AfterEach - void tearDown() { - IdentityZoneHolder.clear(); - } - - @Test - void testRequestAndWantAssertionSignedInAnotherZone() { - generator.setRequestSigned(true); - generator.setWantAssertionSigned(true); - assertTrue(generator.isRequestSigned()); - assertTrue(generator.isWantAssertionSigned()); - - generator.setRequestSigned(false); - generator.setWantAssertionSigned(false); - assertFalse(generator.isRequestSigned()); - assertFalse(generator.isWantAssertionSigned()); - - IdentityZoneHolder.set(otherZone); - - assertTrue(generator.isRequestSigned()); - assertTrue(generator.isWantAssertionSigned()); - } - - @Test - void testMetadataContainsSamlBearerGrantEndpoint() throws Exception { - String metadata = getMetadata(otherZone, keyManager, generator, extendedMetadata); - assertThat(metadata, containsString("md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:URI\" Location=\"http://zone-id.localhost:8080/uaa/oauth/token/alias/zone-id.entityAlias\" index=\"1\"/>")); - } - - @Test - void testZonifiedEntityID() { - generator.setEntityId("local-name"); - assertEquals("local-name", generator.getEntityId()); - assertEquals("local-name", SamlRedirectUtils.getZonifiedEntityId(generator.getEntityId(), IdentityZoneHolder.get())); - - generator.setEntityId(null); - assertNotNull(generator.getEntityId()); - assertNotNull(SamlRedirectUtils.getZonifiedEntityId(generator.getEntityId(), IdentityZoneHolder.get())); - - IdentityZoneHolder.set(otherZone); - - assertNotNull(generator.getEntityId()); - assertNotNull(SamlRedirectUtils.getZonifiedEntityId(generator.getEntityId(), IdentityZoneHolder.get())); - } - - @Test - void testZonifiedValidAndInvalidEntityID() { - IdentityZone newZone = new IdentityZone(); - newZone.setId("new-zone-id"); - newZone.setName("new-zone-id"); - newZone.setSubdomain("new-zone-id"); - newZone.getConfig().getSamlConfig().setEntityID("local-name"); - IdentityZoneHolder.set(newZone); - - // valid entityID from SamlConfig - assertEquals("local-name", generator.getEntityId()); - assertEquals("local-name", SamlRedirectUtils.getZonifiedEntityId("local-name", IdentityZoneHolder.get())); - assertNotNull(generator.getEntityId()); - - // remove SamlConfig - newZone.getConfig().setSamlConfig(null); - assertNotNull(SamlRedirectUtils.getZonifiedEntityId("local-idp", IdentityZoneHolder.get())); - // now the entityID is generated id as before this change - assertEquals("new-zone-id.local-name", SamlRedirectUtils.getZonifiedEntityId("local-name", IdentityZoneHolder.get())); - } - - @Test - void defaultKeys() throws Exception { - String metadata = getMetadata(otherZone, keyManager, generator, extendedMetadata); - - List encryptionKeys = SamlTestUtils.getCertificates(metadata, "encryption"); - assertEquals(1, encryptionKeys.size()); - assertEquals(cert1Plain, encryptionKeys.get(0)); - - List signingVerificationCerts = SamlTestUtils.getCertificates(metadata, "signing"); - assertEquals(1, signingVerificationCerts.size()); - assertEquals(cert1Plain, signingVerificationCerts.get(0)); - } - - @Test - void multipleKeys() throws Exception { - otherZoneDefinition.getSamlConfig().addKey("key2", samlKey2); - String metadata = getMetadata(otherZone, keyManager, generator, extendedMetadata); - - List encryptionKeys = SamlTestUtils.getCertificates(metadata, "encryption"); - assertEquals(1, encryptionKeys.size()); - assertEquals(cert1Plain, encryptionKeys.get(0)); - - List signingVerificationCerts = SamlTestUtils.getCertificates(metadata, "signing"); - assertEquals(2, signingVerificationCerts.size()); - assertThat(signingVerificationCerts, contains(cert1Plain, cert2Plain)); - } - - @Test - void changeActiveKey() throws Exception { - multipleKeys(); - otherZoneDefinition.getSamlConfig().addAndActivateKey("key2", samlKey2); - String metadata = getMetadata(otherZone, keyManager, generator, extendedMetadata); - - List encryptionKeys = SamlTestUtils.getCertificates(metadata, "encryption"); - assertEquals(1, encryptionKeys.size()); - assertEquals(cert2Plain, encryptionKeys.get(0)); - - List signingVerificationCerts = SamlTestUtils.getCertificates(metadata, "signing"); - assertEquals(2, signingVerificationCerts.size()); - assertThat(signingVerificationCerts, contains(cert2Plain, cert1Plain)); - } - - @Test - void removeKey() throws Exception { - changeActiveKey(); - otherZoneDefinition.getSamlConfig().removeKey("key-1"); - String metadata = getMetadata(otherZone, keyManager, generator, extendedMetadata); - - List encryptionKeys = SamlTestUtils.getCertificates(metadata, "encryption"); - assertEquals(1, encryptionKeys.size()); - assertEquals(cert2Plain, encryptionKeys.get(0)); - - List signingVerificationCerts = SamlTestUtils.getCertificates(metadata, "signing"); - assertEquals(1, signingVerificationCerts.size()); - assertThat(signingVerificationCerts, contains(cert2Plain)); - } - - private static String getMetadata( - IdentityZone otherZone, - KeyManager keyManager, - ZoneAwareMetadataGenerator generator, - ExtendedMetadata extendedMetadata) throws MarshallingException { - IdentityZoneHolder.set(otherZone); - return SAMLUtil.getMetadataAsString( - mock(MetadataManager.class), - keyManager, - generator.generateMetadata(), - extendedMetadata); - } - -} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java index 7abe79fd456..04fc4e785c9 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java @@ -1,293 +1,14 @@ package org.cloudfoundry.identity.uaa.provider.saml.idp; -import java.io.IOException; -import java.io.StringReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; -import java.util.UUID; -import javax.xml.namespace.QName; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; - -import org.springframework.security.core.GrantedAuthority; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import org.springframework.security.saml.context.SAMLMessageContext; -import org.springframework.security.saml.key.KeyManager; -import org.springframework.security.saml.metadata.ExtendedMetadata; -import org.springframework.security.saml.metadata.MetadataGenerator; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang.StringUtils; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.apache.commons.lang3.StringUtils; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.login.AddBcProvider; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManagerFactory; -import org.cloudfoundry.identity.uaa.saml.SamlKey; -import org.cloudfoundry.identity.uaa.user.UaaAuthority; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.SamlConfig; -import org.joda.time.DateTime; -import org.opensaml.Configuration; -import org.opensaml.DefaultBootstrap; -import org.opensaml.common.SAMLObject; -import org.opensaml.common.SAMLObjectBuilder; -import org.opensaml.common.SAMLVersion; -import org.opensaml.saml2.core.Assertion; -import org.opensaml.saml2.core.Audience; -import org.opensaml.saml2.core.AudienceRestriction; -import org.opensaml.saml2.core.AuthnContext; -import org.opensaml.saml2.core.AuthnContextClassRef; -import org.opensaml.saml2.core.AuthnRequest; -import org.opensaml.saml2.core.AuthnStatement; -import org.opensaml.saml2.core.Conditions; -import org.opensaml.saml2.core.Issuer; -import org.opensaml.saml2.core.NameID; -import org.opensaml.saml2.core.Subject; -import org.opensaml.saml2.core.SubjectConfirmation; -import org.opensaml.saml2.core.SubjectConfirmationData; -import org.opensaml.saml2.core.impl.AssertionMarshaller; -import org.opensaml.saml2.metadata.EntityDescriptor; -import org.opensaml.saml2.metadata.SPSSODescriptor; -import org.opensaml.xml.ConfigurationException; -import org.opensaml.xml.XMLObjectBuilderFactory; -import org.opensaml.xml.io.Marshaller; -import org.opensaml.xml.security.SecurityHelper; -import org.opensaml.xml.security.credential.Credential; -import org.opensaml.xml.signature.Signature; -import org.opensaml.xml.signature.Signer; -import org.opensaml.xml.signature.impl.SignatureBuilder; -import org.opensaml.xml.util.XMLHelper; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.opensaml.common.xml.SAMLConstants.SAML20P_NS; - -// TODO this class seems to be used more broadly than what its location indicates (uaa as saml idp); need to move it -// also remove unused code in here +// Attempt to move usages to Saml2TestUtils style public class SamlTestUtils { - public static final String PROVIDER_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5\n" + - "L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vA\n" + - "fpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQAB\n" + - "AoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges\n" + - "7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGu\n" + - "lhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3Xp\n" + - "ubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYX\n" + - "kKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyL\n" + - "gu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWK\n" + - "vw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCe\n" + - "A2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHS\n" + - "N+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMB\n" + - "qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/\n" + - "-----END RSA PRIVATE KEY-----"; - - public static final String PROVIDER_PRIVATE_KEY_PASSWORD = "password"; - - public static final String PROVIDER_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" + - "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO\n" + - "MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO\n" + - "MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h\n" + - "cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx\n" + - "CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM\n" + - "BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb\n" + - "BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN\n" + - "ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W\n" + - "qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw\n" + - "znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha\n" + - "MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc\n" + - "gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD\n" + - "VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD\n" + - "VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh\n" + - "QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ\n" + - "0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC\n" + - "KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK\n" + - "RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + - "-----END CERTIFICATE-----"; - - static final String SP_ENTITY_ID = "unit-test-sp"; - static final String IDP_ENTITY_ID = "unit-test-idp"; - public static final String SAML_SP_METADATA_TESTZONE2_FOR_REDIRECT = "\n" + - "Qi+CZaMVIemficNn/klUhpk/3QY=OBLHKk8SzQsPx5l2s8MkUQtvSRjDokCDUCxm6zYFWaWVZbj+jGptVsGqNYu9Tf0Ec48JK+Ff2q6uPlFbVazynM3DLSx7AwEjMrVZPgMWg+Mb0Ca+ZFt49dGg1v0vZ/MPf6ajscODigJBbSgRO6zDQLhwUA6c1HCjVSZj0UsQ1RA=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:2.0:nameid-format:transienturn:oasis:names:tc:SAML:2.0:nameid-format:persistenturn:oasis:names:tc:SAML:1.1:nameid-format:unspecifiedurn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"; - - public static final String SAML_IDP_METADATA_REDIRECT_ONLY = "\n" + - "8rJXCEVOlzN2dmhPBlxbYdTS1Dc=GQgfzz5mSlUxFLeCdDFI76IeG8Y4kpvRtASHypPwFi8usO6uuuaESxiqd97pBz79TNXEoxRkVurbPOEA6Am4sV35GZD5TEAqnjhFN1ZVl4Pe0aW23BN/RoA7lECfom7ONcOKMLePmLJuFSKQb4FioIzF2oCoY9ZQbcTYgrTwJVI=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:2.0:nameid-format:persistent" + - "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" + - "" + - ""; - - public static final String SAML_IDP_METADATA_POST_ONLY = "\n" + - "8rJXCEVOlzN2dmhPBlxbYdTS1Dc=GQgfzz5mSlUxFLeCdDFI76IeG8Y4kpvRtASHypPwFi8usO6uuuaESxiqd97pBz79TNXEoxRkVurbPOEA6Am4sV35GZD5TEAqnjhFN1ZVl4Pe0aW23BN/RoA7lECfom7ONcOKMLePmLJuFSKQb4FioIzF2oCoY9ZQbcTYgrTwJVI=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:2.0:nameid-format:persistent" + - "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" + - "" + - ""; - - private XMLObjectBuilderFactory builderFactory; - - public void initializeSimple() { - builderFactory = Configuration.getBuilderFactory(); - } - - public void initialize() throws ConfigurationException { - IdentityZone.getUaa().getConfig().getSamlConfig().setPrivateKey(PROVIDER_PRIVATE_KEY); - IdentityZone.getUaa().getConfig().getSamlConfig().setPrivateKeyPassword(PROVIDER_PRIVATE_KEY_PASSWORD); - IdentityZone.getUaa().getConfig().getSamlConfig().setCertificate(PROVIDER_CERTIFICATE); - AddBcProvider.noop(); - DefaultBootstrap.bootstrap(); - initializeSimple(); - } - - void setupZoneWithSamlConfig(IdentityZone zone) { - SamlConfig config = zone.getConfig().getSamlConfig(); - config.setPrivateKey(PROVIDER_PRIVATE_KEY); - config.setPrivateKeyPassword(PROVIDER_PRIVATE_KEY_PASSWORD); - config.setCertificate(PROVIDER_CERTIFICATE); + private SamlTestUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); } public static SamlIdentityProviderDefinition createLocalSamlIdpDefinition(String alias, String zoneId, String idpMetaData) { @@ -307,726 +28,4 @@ public static SamlIdentityProviderDefinition createLocalSamlIdpDefinition(String } return def; } - - @SuppressWarnings("unchecked") - SAMLMessageContext mockSamlMessageContext() { - return mockSamlMessageContext(mockAuthnRequest()); - } - - @SuppressWarnings("unchecked") - SAMLMessageContext mockSamlMessageContext(AuthnRequest authnRequest) { - SAMLMessageContext context = new SAMLMessageContext(); - - context.setPeerEntityId(SP_ENTITY_ID); - context.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); - EntityDescriptor spMetadata = mockSpMetadata(); - context.setPeerEntityMetadata(spMetadata); - SPSSODescriptor spDescriptor = spMetadata.getSPSSODescriptor(SAML20P_NS); - context.setPeerEntityRoleMetadata(spDescriptor); - context.setInboundSAMLMessage(authnRequest); - - SamlConfig config = new SamlConfig(); - config.setPrivateKey(PROVIDER_PRIVATE_KEY); - config.setPrivateKeyPassword(PROVIDER_PRIVATE_KEY_PASSWORD); - config.setCertificate(PROVIDER_CERTIFICATE); - KeyManager keyManager = new SamlKeyManagerFactory().getKeyManager(config); - context.setLocalSigningCredential(keyManager.getDefaultCredential()); - return context; - } - - private EntityDescriptor mockSpMetadata() { - ExtendedMetadata extendedMetadata = new ExtendedMetadata(); - - MetadataGenerator metadataGenerator = new MetadataGenerator(); - metadataGenerator.setExtendedMetadata(extendedMetadata); - metadataGenerator.setEntityId(SP_ENTITY_ID); - metadataGenerator.setEntityBaseURL("http://localhost:8080/uaa/saml"); - metadataGenerator.setWantAssertionSigned(false); - - KeyManager keyManager = mock(KeyManager.class); - when(keyManager.getDefaultCredentialName()).thenReturn(null); - metadataGenerator.setKeyManager(keyManager); - return metadataGenerator.generateMetadata(); - } - - private AuthnRequest mockAuthnRequest() { - return mockAuthnRequest(null); - } - - public String mockAssertionEncoded(Assertion assertion) throws Exception { - AssertionMarshaller marshaller = new AssertionMarshaller(); - Element plaintextElement = marshaller.marshall(assertion); - String serializedElement = XMLHelper.nodeToString(plaintextElement); - return Base64.encodeBase64URLSafeString(serializedElement.getBytes(StandardCharsets.UTF_8)); - } - - public String mockAssertionEncoded( - String issuerEntityId, - String format, - String username, - String spEndpoint, - String audienceEntityID) throws Exception { - final Assertion assertion = mockAssertion(issuerEntityId, format, username, spEndpoint, audienceEntityID); - signAssertion(assertion, PROVIDER_PRIVATE_KEY, PROVIDER_PRIVATE_KEY_PASSWORD, PROVIDER_CERTIFICATE); - return mockAssertionEncoded(assertion); - } - - private Assertion mockAssertion( - String issuerEntityId, - String format, - String username, - String spEndpoint, - String audienceEntityID) { - final DateTime now = new DateTime(); - final DateTime until = now.plusHours(1); - - Assertion assertion = (Assertion) buildSamlObject(Assertion.DEFAULT_ELEMENT_NAME); - - { - assertion.setIssueInstant(now); - } - - { - final Issuer issuer = (Issuer) buildSamlObject(Issuer.DEFAULT_ELEMENT_NAME); - issuer.setValue(issuerEntityId); - assertion.setIssuer(issuer); - } - - { - final NameID nameId = (NameID) buildSamlObject(NameID.DEFAULT_ELEMENT_NAME); - nameId.setValue(username); - nameId.setNameQualifier(NameID.UNSPECIFIED); - nameId.setFormat(format); - - final SubjectConfirmationData confirmationMethod = (SubjectConfirmationData) buildSamlObject(SubjectConfirmationData.DEFAULT_ELEMENT_NAME); - confirmationMethod.setNotOnOrAfter(until); - confirmationMethod.setRecipient(spEndpoint); - - final SubjectConfirmation subjectConfirmation = (SubjectConfirmation) buildSamlObject(SubjectConfirmation.DEFAULT_ELEMENT_NAME); - subjectConfirmation.setSubjectConfirmationData(confirmationMethod); - subjectConfirmation.setMethod("urn:oasis:names:tc:SAML:2.0:cm:bearer"); - - final Subject subject = (Subject) buildSamlObject(Subject.DEFAULT_ELEMENT_NAME); - subject.setNameID(nameId); - subject.getSubjectConfirmations().add(subjectConfirmation); - - subject.getSubjectConfirmations().get(0).getSubjectConfirmationData().setInResponseTo(null); - subject.getSubjectConfirmations().get(0).getSubjectConfirmationData().setNotOnOrAfter(until); - - assertion.setSubject(subject); - } - - { - final Audience audience = (Audience) buildSamlObject(Audience.DEFAULT_ELEMENT_NAME); - audience.setAudienceURI(audienceEntityID); - - final AudienceRestriction audienceRestriction = (AudienceRestriction) buildSamlObject(AudienceRestriction.DEFAULT_ELEMENT_NAME); - audienceRestriction.getAudiences().add(audience); - - final Conditions conditions = (Conditions) buildSamlObject(Conditions.DEFAULT_ELEMENT_NAME); - conditions.getAudienceRestrictions().add(audienceRestriction); - conditions.setNotBefore(new DateTime().minusSeconds(2)); - conditions.setNotOnOrAfter(until); - - assertion.setConditions(conditions); - } - - { - final AuthnContextClassRef authnContextClassRef = (AuthnContextClassRef) buildSamlObject(AuthnContextClassRef.DEFAULT_ELEMENT_NAME); - authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:Password"); - - final AuthnContext authnContext = (AuthnContext) buildSamlObject(AuthnContext.DEFAULT_ELEMENT_NAME); - authnContext.setAuthnContextClassRef(authnContextClassRef); - - final AuthnStatement authnStatement = (AuthnStatement) buildSamlObject(AuthnStatement.DEFAULT_ELEMENT_NAME); - authnStatement.setAuthnInstant(now); - authnStatement.setSessionIndex("a358a06c15ja8d7a1idjaj07jb52gdi"); - authnStatement.setSessionNotOnOrAfter(until); - authnStatement.setAuthnContext(authnContext); - - assertion.getAuthnStatements().add(authnStatement); - } - - return assertion; - } - - private SAMLObject buildSamlObject(QName elementName) { - SAMLObjectBuilder issuerBuilder = (SAMLObjectBuilder) builderFactory.getBuilder(elementName); - return issuerBuilder.buildObject(); - } - - public void signAssertion( - Assertion assertion, - String privateKey, - String keyPassword, - String certificate) - throws Exception { - - final Signature signature = generateSignature(privateKey, keyPassword, certificate); - assertion.setSignature(signature); - Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(assertion); - marshaller.marshall(assertion); - Signer.signObject(signature); - } - - private Signature generateSignature(String privateKey, String keyPassword, String certificate) - throws org.opensaml.xml.security.SecurityException { - SamlConfig config = new SamlConfig(); - config.addAndActivateKey("active-key", new SamlKey(privateKey, keyPassword, certificate)); - KeyManager keyManager = new SamlKeyManagerFactory().getKeyManager(config); - SignatureBuilder signatureBuilder = (SignatureBuilder) builderFactory.getBuilder(Signature.DEFAULT_ELEMENT_NAME); - Signature signature = signatureBuilder.buildObject(); - final Credential defaultCredential = keyManager.getDefaultCredential(); - signature.setSigningCredential(defaultCredential); - SecurityHelper.prepareSignatureParams(signature, defaultCredential, null, null); - return signature; - } - - AuthnRequest mockAuthnRequest(String nameIDFormat) { - @SuppressWarnings("unchecked") - SAMLObjectBuilder builder = (SAMLObjectBuilder) builderFactory - .getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME); - AuthnRequest request = builder.buildObject(); - request.setVersion(SAMLVersion.VERSION_20); - request.setID(generateID()); - request.setIssuer(getIssuer(SP_ENTITY_ID)); - request.setVersion(SAMLVersion.VERSION_20); - request.setIssueInstant(new DateTime()); - if (null != nameIDFormat) { - NameID nameID = ((SAMLObjectBuilder) builderFactory.getBuilder(NameID.DEFAULT_ELEMENT_NAME)) - .buildObject(); - nameID.setFormat(nameIDFormat); - Subject subject = ((SAMLObjectBuilder) builderFactory.getBuilder(Subject.DEFAULT_ELEMENT_NAME)) - .buildObject(); - subject.setNameID(nameID); - request.setSubject(subject); - } - return request; - } - - private String generateID() { - Random r = new Random(); - return 'a' + Long.toString(Math.abs(r.nextLong()), 20) + Long.toString(Math.abs(r.nextLong()), 20); - } - - public Issuer getIssuer(String localEntityId) { - @SuppressWarnings("unchecked") - SAMLObjectBuilder issuerBuilder = (SAMLObjectBuilder) builderFactory - .getBuilder(Issuer.DEFAULT_ELEMENT_NAME); - Issuer issuer = issuerBuilder.buildObject(); - issuer.setValue(localEntityId); - return issuer; - } - - private UaaAuthentication mockUaaAuthentication() { - return mockUaaAuthentication(UUID.randomUUID().toString()); - } - - UaaAuthentication mockUaaAuthentication(String id) { - UaaAuthentication authentication = mock(UaaAuthentication.class); - when(authentication.getName()).thenReturn("marissa"); - - UaaPrincipal principal = new UaaPrincipal(id, "marissa", "marissa@testing.org", - OriginKeys.UAA, "marissa", "uaa"); - when(authentication.getPrincipal()).thenReturn(principal); - - Collection authorities = new ArrayList<>(); - authorities.add(UaaAuthority.UAA_USER); - doReturn(authorities).when(authentication).getAuthorities(); - - return authentication; - } - - static final String SAML_SP_METADATA = "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "mPb/c/Gb/PN61JNRptMgHbK9L08=" - + "" - + "" - + "Ra6mE3hjN68Jwk6D3DktVrOu0BXJCSPTMr0YTgQyII8fv7j93BhuGMoZHw48tww6N9zkUDEuy+uRp9vd4gepxs8+XiL+kvoclMAStmzJ62/2fGuI3hCvht2lBXIuFBpZab3iuqxBhwceLnsvvsM5y4nfYDXuBS1XGRzrygLbldM=" - + "" - + "" - + "" - + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" - + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" - + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" - + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" - + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" - + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" - + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" - + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" - + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" - + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" - + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" - + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" - + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" - + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" - + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" - + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" - + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" - + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" - + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" - + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" - + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" - + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" - + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" - + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" - + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" - + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" - + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" - + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" - + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" - + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" - + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" - + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" - + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" - + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" - + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" - + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" - + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" - + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" - + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" - + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" - + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" - + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" - + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" - + "" - + "" - + "" - + "" - + "" - + "" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" - + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" - + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" - + "" - + "" - + ""; - - static final String UNSIGNED_SAML_SP_METADATA = "" - + "" - + "" - + "" - + "" - + "" - + "" - + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" - + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" - + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" - + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" - + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" - + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" - + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" - + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" - + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" - + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" - + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" - + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" - + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" - + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" - + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" - + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" - + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" - + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" - + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" - + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" - + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" - + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" - + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" - + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" - + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" - + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" - + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" - + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" - + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" - + "" - + "" - + "" - + "" - + "" - + "" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" - + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" - + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" - + "" - + "" - + ""; - - static final String UNSIGNED_SAML_SP_METADATA_WITHOUT_ID = "" - + "" - + "" - + "" - + "" - + "" - + "" - + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" - + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" - + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" - + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" - + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" - + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" - + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" - + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" - + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" - + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" - + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" - + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" - + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" - + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" - + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" - + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" - + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" - + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" - + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" - + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" - + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" - + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" - + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" - + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" - + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" - + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" - + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" - + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" - + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" - + "" - + "" - + "" - + "" - + "" - + "" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" - + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" - + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" - + "" - + "" - + ""; - - private static final String UNSIGNED_SAML_SP_METADATA_ID_AND_ENTITY_ID = "" - + "" - + "" - + "" - + "" - + "" - + "" - + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" - + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" - + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" - + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" - + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" - + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" - + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" - + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" - + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" - + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" - + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" - + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" - + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" - + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" - + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF" - + "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM" - + "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2" - + "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE" - + "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx" - + "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB" - + "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR" - + "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY" - + "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy" - + "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3" - + "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL" - + "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA" - + "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am" - + "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o" - + "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" - + "" - + "" - + "" - + "" - + "" - + "" - + "%s" - + "" - + "" - + ""; - - public static final String SAML_SP_METADATA_TESTZONE2 = "\n" + - "Qi+CZaMVIemficNn/klUhpk/3QY=OBLHKk8SzQsPx5l2s8MkUQtvSRjDokCDUCxm6zYFWaWVZbj+jGptVsGqNYu9Tf0Ec48JK+Ff2q6uPlFbVazynM3DLSx7AwEjMrVZPgMWg+Mb0Ca+ZFt49dGg1v0vZ/MPf6ajscODigJBbSgRO6zDQLhwUA6c1HCjVSZj0UsQ1RA=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:2.0:nameid-format:transienturn:oasis:names:tc:SAML:2.0:nameid-format:persistenturn:oasis:names:tc:SAML:1.1:nameid-format:unspecifiedurn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"; - - public static final String SAML_IDP_METADATA_ARTIFACT_FIRST = "\n" + - "8rJXCEVOlzN2dmhPBlxbYdTS1Dc=GQgfzz5mSlUxFLeCdDFI76IeG8Y4kpvRtASHypPwFi8usO6uuuaESxiqd97pBz79TNXEoxRkVurbPOEA6Am4sV35GZD5TEAqnjhFN1ZVl4Pe0aW23BN/RoA7lECfom7ONcOKMLePmLJuFSKQb4FioIzF2oCoY9ZQbcTYgrTwJVI=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:2.0:nameid-format:persistent" + - "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" + - "" + - "" + - ""; - - public static final String SAML_IDP_METADATA_ARTIFACT_ONLY = "\n" + - "8rJXCEVOlzN2dmhPBlxbYdTS1Dc=GQgfzz5mSlUxFLeCdDFI76IeG8Y4kpvRtASHypPwFi8usO6uuuaESxiqd97pBz79TNXEoxRkVurbPOEA6Am4sV35GZD5TEAqnjhFN1ZVl4Pe0aW23BN/RoA7lECfom7ONcOKMLePmLJuFSKQb4FioIzF2oCoY9ZQbcTYgrTwJVI=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - "YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - "BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - "MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - "ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - "HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - "gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - "4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - "xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - "GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - "MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - "EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - "2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - "ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=" + - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:2.0:nameid-format:persistent" + - "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" + - ""; - - - private static final String DEFAULT_NAME_ID_FORMATS = - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" - + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" - + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" - + "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"; - - static final String MOCK_SP_ENTITY_ID = "mock-saml-sp-entity-id"; - - static SamlServiceProvider mockSamlServiceProviderForZone(String zoneId) { - SamlServiceProviderDefinition singleAddDef = SamlServiceProviderDefinition.Builder.get() - .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_ID_AND_ENTITY_ID, - new RandomValueStringGenerator().generate(), MOCK_SP_ENTITY_ID, DEFAULT_NAME_ID_FORMATS)) - .setNameID("sample-nameID").setSingleSignOnServiceIndex(1) - .setMetadataTrustCheck(true).build(); - - return new SamlServiceProvider().setEntityId(MOCK_SP_ENTITY_ID).setIdentityZoneId(zoneId) - .setConfig(singleAddDef); - } - - static SamlServiceProvider mockSamlServiceProviderMetadatauriForZone(String metadataURI) { - SamlServiceProviderDefinition singleAddDef = SamlServiceProviderDefinition.Builder.get() - .setMetaDataLocation(metadataURI) - .setNameID("sample-nameID").setSingleSignOnServiceIndex(1) - .setMetadataTrustCheck(false).setSkipSSLValidation(true).build(); - - return new SamlServiceProvider().setEntityId(MOCK_SP_ENTITY_ID).setIdentityZoneId("uaa") - .setConfig(singleAddDef); - } - - static SamlServiceProvider mockSamlServiceProvider(String entityId) { - return mockSamlServiceProvider(entityId, DEFAULT_NAME_ID_FORMATS); - } - - static SamlServiceProvider mockSamlServiceProvider(String entityId, String nameIdFormatsXML) { - SamlServiceProviderDefinition singleAddDef = SamlServiceProviderDefinition.Builder.get() - .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_ID_AND_ENTITY_ID, - new RandomValueStringGenerator().generate(), entityId, nameIdFormatsXML)) - .setNameID("sample-nameID").setSingleSignOnServiceIndex(1) - .setMetadataTrustCheck(true).build(); - return new SamlServiceProvider().setEntityId(entityId).setIdentityZoneId("uaa") - .setConfig(singleAddDef); - } - - static SamlServiceProvider mockSamlServiceProviderForZoneWithoutSPSSOInMetadata(String zoneId) { - SamlServiceProviderDefinition singleAddDef = SamlServiceProviderDefinition.Builder.get() - .setMetaDataLocation(String.format(SamlTestUtils.UNSIGNED_SAML_SP_METADATA_ID_AND_ENTITY_ID, - new RandomValueStringGenerator().generate(), MOCK_SP_ENTITY_ID, DEFAULT_NAME_ID_FORMATS)) - .setMetadataTrustCheck(true).build(); - return new SamlServiceProvider().setEntityId(MOCK_SP_ENTITY_ID).setIdentityZoneId(zoneId) - .setConfig(singleAddDef); - } - - public static List getCertificates(String metadata, String type) throws Exception { - Document doc = getMetadataDoc(metadata); - NodeList nodeList = evaluateXPathExpression(doc, "//*[local-name()='KeyDescriptor' and @*[local-name() = 'use']='" + type + "']//*[local-name()='X509Certificate']/text()"); - assertNotNull(nodeList); - List result = new LinkedList<>(); - for (int i = 0; i < nodeList.getLength(); i++) { - result.add(nodeList.item(i).getNodeValue().replace("\n", "")); - } - return result; - } - - public static NodeList evaluateXPathExpression(Document doc, String xpath) throws XPathExpressionException { - XPath xPath = XPathFactory.newInstance().newXPath(); - XPathExpression expression = xPath.compile(xpath); - return (NodeList) expression.evaluate(doc, XPathConstants.NODESET); - } - - public static Document getMetadataDoc(String metadata) throws SAXException, IOException, ParserConfigurationException { - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - documentBuilderFactory.setNamespaceAware(false); - InputSource is = new InputSource(new StringReader(metadata)); - return documentBuilderFactory.newDocumentBuilder().parse(is); - } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java index 7cd8c2cda70..108c30b08d8 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java @@ -5,22 +5,25 @@ import org.junit.Test; import org.springframework.security.web.SecurityFilterChain; -import javax.servlet.*; -import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class SecurityFilterChainPostProcessorTests { - private SecurityFilterChainPostProcessor processor = new SecurityFilterChainPostProcessor(); + private final SecurityFilterChainPostProcessor processor = new SecurityFilterChainPostProcessor(); private SecurityFilterChain fc; - private Map additionalFilters = new HashMap<>(); + private final Map additionalFilters = new HashMap<>(); private int count; @Before @@ -31,7 +34,7 @@ public void setUp() { filters.add(new TestFilter3()); fc = mock(SecurityFilterChain.class); when(fc.getFilters()).thenReturn(filters); - count = filters.size()+1; + count = filters.size() + 1; } @After @@ -40,14 +43,12 @@ public void tearDown() { } private void testPositionFilter(int pos) { - int expectedPos = pos>count ? count : pos; + int expectedPos = pos > count ? count : pos + 1; additionalFilters.put(SecurityFilterChainPostProcessor.FilterPosition.position(pos), new PositionFilter()); processor.setAdditionalFilters(additionalFilters); processor.postProcessAfterInitialization(fc, ""); - assertEquals(count+1, fc.getFilters().size()); - assertEquals(String.format("filter[%d] should be:%s", pos, PositionFilter.class.getSimpleName()), - fc.getFilters().get(expectedPos).getClass(), - PositionFilter.class); + assertThat(fc.getFilters()).hasSize(count + 1); + assertThat(fc.getFilters().get(expectedPos).getClass()).as(String.format("filter[%d] should be:%s", pos, PositionFilter.class.getSimpleName())).isEqualTo(PositionFilter.class); } @Test @@ -68,10 +69,8 @@ public void testPositionLastFilter() { private void testClassPlacementFilter(Class clazz, int expectedPosition) { processor.setAdditionalFilters(additionalFilters); processor.postProcessAfterInitialization(fc, ""); - assertEquals(count+1, fc.getFilters().size()); - assertEquals(String.format("filter[%s] should be at position:%d", clazz.getSimpleName(), expectedPosition), - fc.getFilters().get(expectedPosition).getClass(), - clazz); + assertThat(fc.getFilters()).hasSize(count + 1); + assertThat(clazz).as(String.format("filter[%s] should be at position:%d", clazz.getSimpleName(), expectedPosition)).isEqualTo(fc.getFilters().get(expectedPosition).getClass()); } @Test @@ -124,17 +123,27 @@ public void testAfterPlacementWhenMissing() { public static class TestFilter1 implements Filter { - @Override public void init(FilterConfig filterConfig) {} + @Override + public void init(FilterConfig filterConfig) { + } - @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {} + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { + } + } + + public static class TestFilter2 extends TestFilter1 { + } - @Override public void destroy() {} + public static class TestFilter3 extends TestFilter1 { } - public static class TestFilter2 extends TestFilter1 {} - public static class TestFilter3 extends TestFilter1 {} + public static class PositionFilter extends TestFilter1 { + } - public static class PositionFilter extends TestFilter1 {} - public static class AfterFilter extends TestFilter1 {} - public static class BeforeFilter extends TestFilter1 {} + public static class AfterFilter extends TestFilter1 { + } + + public static class BeforeFilter extends TestFilter1 { + } } \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java index 13ac9ef15c7..a9f1e61431d 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java @@ -5,6 +5,7 @@ import org.cloudfoundry.identity.uaa.impl.config.IdentityProviderBootstrap; import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; import org.cloudfoundry.identity.uaa.provider.saml.BootstrapSamlIdentityProviderData; +import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManagerFactory; import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimExternalGroupBootstrap; import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimGroupBootstrap; import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimUserBootstrap; @@ -131,11 +132,13 @@ public static void resetIdentityZoneHolder(ApplicationContext applicationContext if (applicationContext == null) { IdentityZoneHolder.setProvisioning(null); + IdentityZoneHolder.setSamlKeyManagerFactory(null); return; } try { IdentityZoneHolder.setProvisioning(applicationContext.getBean(JdbcIdentityZoneProvisioning.class)); + IdentityZoneHolder.setSamlKeyManagerFactory(applicationContext.getBean(SamlKeyManagerFactory.class)); } catch (NoSuchBeanDefinitionException ignored) { try { IdentityZoneHolder.setProvisioning(new JdbcIdentityZoneProvisioning(applicationContext.getBean(JdbcTemplate.class))); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/util/KeyWithCertTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/KeyWithCertTest.java index f98a502cc8a..3a183ec4e8f 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/util/KeyWithCertTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/util/KeyWithCertTest.java @@ -2,10 +2,10 @@ * ***************************************************************************** * Cloud Foundry * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. - *

    + * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. - *

    + * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the @@ -14,203 +14,223 @@ package org.cloudfoundry.identity.uaa.util; import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.security.Security; import java.security.cert.CertificateException; -import static org.junit.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class KeyWithCertTest { - @BeforeClass - public static void addProvider() { + @BeforeAll + static void addProvider() { Security.addProvider(new BouncyCastleFipsProvider()); } - public static final String key = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXgIBAAKBgQDfTLadf6QgJeS2XXImEHMsa+1O7MmIt44xaL77N2K+J/JGpfV3\n" + - "AnkyB06wFZ02sBLB7hko42LIsVEOyTuUBird/3vlyHFKytG7UEt60Fl88SbAEfsU\n" + - "JN1i1aSUlunPS/NCz+BKwwKFP9Ss3rNImE9Uc2LMvGy153LHFVW2zrjhTwIDAQAB\n" + - "AoGBAJDh21LRcJITRBQ3CUs9PR1DYZPl+tUkE7RnPBMPWpf6ny3LnDp9dllJeHqz\n" + - "a3ACSgleDSEEeCGzOt6XHnrqjYCKa42Z+Opnjx/OOpjyX1NAaswRtnb039jwv4gb\n" + - "RlwT49Y17UAQpISOo7JFadCBoMG0ix8xr4ScY+zCSoG5v0BhAkEA8llNsiWBJF5r\n" + - "LWQ6uimfdU2y1IPlkcGAvjekYDkdkHiRie725Dn4qRiXyABeaqNm2bpnD620Okwr\n" + - "sf7LY+BMdwJBAOvgt/ZGwJrMOe/cHhbujtjBK/1CumJ4n2r5V1zPBFfLNXiKnpJ6\n" + - "J/sRwmjgg4u3Anu1ENF3YsxYabflBnvOP+kCQCQ8VBCp6OhOMcpErT8+j/gTGQUL\n" + - "f5zOiPhoC2zTvWbnkCNGlqXDQTnPUop1+6gILI2rgFNozoTU9MeVaEXTuLsCQQDC\n" + - "AGuNpReYucwVGYet+LuITyjs/krp3qfPhhByhtndk4cBA5H0i4ACodKyC6Zl7Tmf\n" + - "oYaZoYWi6DzbQQUaIsKxAkEA2rXQjQFsfnSm+w/9067ChWg46p4lq5Na2NpcpFgH\n" + - "waZKhM1W0oB8MX78M+0fG3xGUtywTx0D4N7pr1Tk2GTgNw==\n" + - "-----END RSA PRIVATE KEY-----"; - - public static final String invalidCert = "-----BEGIN CERTIFICATE-----\n" + - "FILIPMIIEJTCCA46gAwIBAgIJANIqfxWTfhpkMA0GCSqGSIb3DQEBBQUAMIG+MQswCQYD\n" + - "VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n" + - "aXNjbzEdMBsGA1UEChMUUGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Ns\n" + - "b3VkIEZvdW5kcnkgSWRlbnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2Yt\n" + - "YXBwLmNvbTEfMB0GCSqGSIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzAeFw0xNTA1\n" + - "MTQxNzE5MTBaFw0yNTA1MTExNzE5MTBaMIG+MQswCQYDVQQGEwJVUzETMBEGA1UE\n" + - "CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEdMBsGA1UEChMU\n" + - "UGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Nsb3VkIEZvdW5kcnkgSWRl\n" + - "bnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2YtYXBwLmNvbTEfMB0GCSqG\n" + - "SIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\n" + - "gYkCgYEA30y2nX+kICXktl1yJhBzLGvtTuzJiLeOMWi++zdivifyRqX1dwJ5MgdO\n" + - "sBWdNrASwe4ZKONiyLFRDsk7lAYq3f975chxSsrRu1BLetBZfPEmwBH7FCTdYtWk\n" + - "lJbpz0vzQs/gSsMChT/UrN6zSJhPVHNizLxstedyxxVVts644U8CAwEAAaOCAScw\n" + - "ggEjMB0GA1UdDgQWBBSvWY/TyHysYGxKvII95wD/CzE1AzCB8wYDVR0jBIHrMIHo\n" + - "gBSvWY/TyHysYGxKvII95wD/CzE1A6GBxKSBwTCBvjELMAkGA1UEBhMCVVMxEzAR\n" + - "BgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHTAbBgNV\n" + - "BAoTFFBpdm90YWwgU29mdHdhcmUgSW5jMSQwIgYDVQQLExtDbG91ZCBGb3VuZHJ5\n" + - "IElkZW50aXR5IFRlYW0xHDAaBgNVBAMTE2lkZW50aXR5LmNmLWFwcC5jb20xHzAd\n" + - "BgkqhkiG9w0BCQEWEG1hcmlzc2FAdGVzdC5vcmeCCQDSKn8Vk34aZDAMBgNVHRME\n" + - "BTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAL5j1JCN5EoXMOOBSBUL8KeVZFQD3Nfy\n" + - "YkYKBatFEKdBFlAKLBdG+5KzE7sTYesn7EzBISHXFz3DhdK2tg+IF1DeSFVmFl2n\n" + - "iVxQ1sYjo4kCugHBsWo+MpFH9VBLFzsMlP3eIDuVKe8aPXFKYCGhctZEJdQTKlja\n" + - "lshe50nayKrT\n" + - "-----END CERTIFICATE-----\n"; - - public static final String password = "password"; - - public static final String encryptedKey = "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: DES-EDE3-CBC,BE03AC562D734AB1\n" + - "mvMS20ddwCJ6A+ABJKWViGTgLpWUVA5ZqKYU6Q3N+le769s4uygcMOtvTcjgH46E\n" + - "3gIDR+Qt+UO/Yv+EgIJnga+vLMayjg/pl2bR8p1lK7gUkAb7DwDviySSi18tAt0O\n" + - "NTyJEzy6G+WnlSs+3tzRUCneaoFB1/LDdUSOzaSLRtU/r+Vt/9BYBQbZMalnSQRE\n" + - "U17VhISbfj4MgNIfZU+7+ALfE0+Muno4WDk+IJXArAk7wckF6NO7M4EKHlLzrHI0\n" + - "+PccNBKN/rAevYZrZOmGCw4jKu5JJDtt6SgQJIp/XGEZlv+KD2cWPBC4nj7nJHAz\n" + - "ezt9SfnL8jQlClTwQyPHjwDPlL/WHQrBpxpFF83FnN8B02DWwXQE2oTC7RtijQVT\n" + - "NKto/vSODK0RfaulLHNx6RvJF0YFWSSofTm0G5TLwWCCrVekK0N5zAYPeG9LgjlG\n" + - "4xILPSE+Y6hYIVN2gXNZOVB8T5O+Jf1KQlmMnZ9A5o1gcUJq0rCBa6i2D2rveQGE\n" + - "eLm3BgyMp5v0JsyuzDBuxVWSgJFt+KHz/mhdgdG8End3QBF2BBaHpLP0+5BqIZHX\n" + - "NYCDBwWK/k40oxT8KLdFfkBU48Yndq7ARFdq3YzPU6FdSpgwZM5p8HYkl1THcskI\n" + - "Ri7zVHxpm0tPZqqqgzr6HBvSiQhACT4dOXV5V8bEoL5tlyuZllq2MBayl9yd0+bq\n" + - "6hVZXUYewtPyE2Wj2PDr2F7fGtYhKcrnQxH63w3OhIzgkxUTQ63h710QDJjOtYCm\n" + - "/PCAsNBePrnjrHHxMxkMVCtTYSeBePk0vkUtFOE5hIc=\n" + - "-----END RSA PRIVATE KEY-----\n"; - - public static final String goodCert = "-----BEGIN CERTIFICATE-----\n" + - "MIIC6TCCAlICCQDN85uMN+4K5jANBgkqhkiG9w0BAQsFADCBuDELMAkGA1UEBhMC\n" + - "VVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQK\n" + - "DBRQaXZvdGFsIFNvZnR3YXJlIEluYzEeMBwGA1UECwwVQ2xvdWRmb3VuZHJ5IElk\n" + - "ZW50aXR5MRswGQYDVQQDDBJ1YWEucnVuLnBpdm90YWwuaW8xKDAmBgkqhkiG9w0B\n" + - "CQEWGXZjYXAtZGV2QGNsb3VkZm91bmRyeS5vcmcwHhcNMTUwMzAyMTQyMDQ4WhcN\n" + - "MjUwMjI3MTQyMDQ4WjCBuDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYD\n" + - "VQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQKDBRQaXZvdGFsIFNvZnR3YXJlIElu\n" + - "YzEeMBwGA1UECwwVQ2xvdWRmb3VuZHJ5IElkZW50aXR5MRswGQYDVQQDDBJ1YWEu\n" + - "cnVuLnBpdm90YWwuaW8xKDAmBgkqhkiG9w0BCQEWGXZjYXAtZGV2QGNsb3VkZm91\n" + - "bmRyeS5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAN0u5J4BJUDgRv6I\n" + - "h5/r7rZjSrFVLL7bl71CzBIaVk1BQPYfBC8gggGAWmYYxJV0Kz+2Vx0Z96OnXhJk\n" + - "gG46Zo2KMDudEeSdXou+dSBNISDv4VpLKUGnVU4n/L0khbI+jX51aS80ub8vThca\n" + - "bkdY5x4Ir8G3QCQvCGKgU2emfFe7AgMBAAEwDQYJKoZIhvcNAQELBQADgYEAXghg\n" + - "PwMhO0+dASJ83e2Bu63pKO808BrVjD51sSEMb0qwFc5IV6RzK/mkJgO0fphhoqOm\n" + - "ZLzGcSYwCmj0Vc0GO5NgnFVZg4N9CyYCpDMeQynumlrNhRgnZRzlqXtQgL2bQDiu\n" + - "coxNL/KY05iVlE1bmq/fzNEmEi2zf3dQV8CNSYs=\n" + - "-----END CERTIFICATE----\n"; + private static final String KEY = """ + -----BEGIN RSA PRIVATE KEY----- + MIICXgIBAAKBgQDfTLadf6QgJeS2XXImEHMsa+1O7MmIt44xaL77N2K+J/JGpfV3 + AnkyB06wFZ02sBLB7hko42LIsVEOyTuUBird/3vlyHFKytG7UEt60Fl88SbAEfsU + JN1i1aSUlunPS/NCz+BKwwKFP9Ss3rNImE9Uc2LMvGy153LHFVW2zrjhTwIDAQAB + AoGBAJDh21LRcJITRBQ3CUs9PR1DYZPl+tUkE7RnPBMPWpf6ny3LnDp9dllJeHqz + a3ACSgleDSEEeCGzOt6XHnrqjYCKa42Z+Opnjx/OOpjyX1NAaswRtnb039jwv4gb + RlwT49Y17UAQpISOo7JFadCBoMG0ix8xr4ScY+zCSoG5v0BhAkEA8llNsiWBJF5r + LWQ6uimfdU2y1IPlkcGAvjekYDkdkHiRie725Dn4qRiXyABeaqNm2bpnD620Okwr + sf7LY+BMdwJBAOvgt/ZGwJrMOe/cHhbujtjBK/1CumJ4n2r5V1zPBFfLNXiKnpJ6 + J/sRwmjgg4u3Anu1ENF3YsxYabflBnvOP+kCQCQ8VBCp6OhOMcpErT8+j/gTGQUL + f5zOiPhoC2zTvWbnkCNGlqXDQTnPUop1+6gILI2rgFNozoTU9MeVaEXTuLsCQQDC + AGuNpReYucwVGYet+LuITyjs/krp3qfPhhByhtndk4cBA5H0i4ACodKyC6Zl7Tmf + oYaZoYWi6DzbQQUaIsKxAkEA2rXQjQFsfnSm+w/9067ChWg46p4lq5Na2NpcpFgH + waZKhM1W0oB8MX78M+0fG3xGUtywTx0D4N7pr1Tk2GTgNw== + -----END RSA PRIVATE KEY-----"""; + + public static final String INVALID_CERT = """ + -----BEGIN CERTIFICATE----- + FILIPMIIEJTCCA46gAwIBAgIJANIqfxWTfhpkMA0GCSqGSIb3DQEBBQUAMIG+MQswCQYD + VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j + aXNjbzEdMBsGA1UEChMUUGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Ns + b3VkIEZvdW5kcnkgSWRlbnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2Yt + YXBwLmNvbTEfMB0GCSqGSIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzAeFw0xNTA1 + MTQxNzE5MTBaFw0yNTA1MTExNzE5MTBaMIG+MQswCQYDVQQGEwJVUzETMBEGA1UE + CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEdMBsGA1UEChMU + UGl2b3RhbCBTb2Z0d2FyZSBJbmMxJDAiBgNVBAsTG0Nsb3VkIEZvdW5kcnkgSWRl + bnRpdHkgVGVhbTEcMBoGA1UEAxMTaWRlbnRpdHkuY2YtYXBwLmNvbTEfMB0GCSqG + SIb3DQEJARYQbWFyaXNzYUB0ZXN0Lm9yZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw + gYkCgYEA30y2nX+kICXktl1yJhBzLGvtTuzJiLeOMWi++zdivifyRqX1dwJ5MgdO + sBWdNrASwe4ZKONiyLFRDsk7lAYq3f975chxSsrRu1BLetBZfPEmwBH7FCTdYtWk + lJbpz0vzQs/gSsMChT/UrN6zSJhPVHNizLxstedyxxVVts644U8CAwEAAaOCAScw + ggEjMB0GA1UdDgQWBBSvWY/TyHysYGxKvII95wD/CzE1AzCB8wYDVR0jBIHrMIHo + gBSvWY/TyHysYGxKvII95wD/CzE1A6GBxKSBwTCBvjELMAkGA1UEBhMCVVMxEzAR + BgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHTAbBgNV + BAoTFFBpdm90YWwgU29mdHdhcmUgSW5jMSQwIgYDVQQLExtDbG91ZCBGb3VuZHJ5 + IElkZW50aXR5IFRlYW0xHDAaBgNVBAMTE2lkZW50aXR5LmNmLWFwcC5jb20xHzAd + BgkqhkiG9w0BCQEWEG1hcmlzc2FAdGVzdC5vcmeCCQDSKn8Vk34aZDAMBgNVHRME + BTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAL5j1JCN5EoXMOOBSBUL8KeVZFQD3Nfy + YkYKBatFEKdBFlAKLBdG+5KzE7sTYesn7EzBISHXFz3DhdK2tg+IF1DeSFVmFl2n + iVxQ1sYjo4kCugHBsWo+MpFH9VBLFzsMlP3eIDuVKe8aPXFKYCGhctZEJdQTKlja + lshe50nayKrT + -----END CERTIFICATE----- + """; + + private static final String PASSWORD = "password"; + + private static final String ENCRYPTED_KEY = """ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: DES-EDE3-CBC,BE03AC562D734AB1 + mvMS20ddwCJ6A+ABJKWViGTgLpWUVA5ZqKYU6Q3N+le769s4uygcMOtvTcjgH46E + 3gIDR+Qt+UO/Yv+EgIJnga+vLMayjg/pl2bR8p1lK7gUkAb7DwDviySSi18tAt0O + NTyJEzy6G+WnlSs+3tzRUCneaoFB1/LDdUSOzaSLRtU/r+Vt/9BYBQbZMalnSQRE + U17VhISbfj4MgNIfZU+7+ALfE0+Muno4WDk+IJXArAk7wckF6NO7M4EKHlLzrHI0 + +PccNBKN/rAevYZrZOmGCw4jKu5JJDtt6SgQJIp/XGEZlv+KD2cWPBC4nj7nJHAz + ezt9SfnL8jQlClTwQyPHjwDPlL/WHQrBpxpFF83FnN8B02DWwXQE2oTC7RtijQVT + NKto/vSODK0RfaulLHNx6RvJF0YFWSSofTm0G5TLwWCCrVekK0N5zAYPeG9LgjlG + 4xILPSE+Y6hYIVN2gXNZOVB8T5O+Jf1KQlmMnZ9A5o1gcUJq0rCBa6i2D2rveQGE + eLm3BgyMp5v0JsyuzDBuxVWSgJFt+KHz/mhdgdG8End3QBF2BBaHpLP0+5BqIZHX + NYCDBwWK/k40oxT8KLdFfkBU48Yndq7ARFdq3YzPU6FdSpgwZM5p8HYkl1THcskI + Ri7zVHxpm0tPZqqqgzr6HBvSiQhACT4dOXV5V8bEoL5tlyuZllq2MBayl9yd0+bq + 6hVZXUYewtPyE2Wj2PDr2F7fGtYhKcrnQxH63w3OhIzgkxUTQ63h710QDJjOtYCm + /PCAsNBePrnjrHHxMxkMVCtTYSeBePk0vkUtFOE5hIc= + -----END RSA PRIVATE KEY----- + """; + + private static final String GOOD_CERT = """ + -----BEGIN CERTIFICATE----- + MIIC6TCCAlICCQDN85uMN+4K5jANBgkqhkiG9w0BAQsFADCBuDELMAkGA1UEBhMC + VVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQK + DBRQaXZvdGFsIFNvZnR3YXJlIEluYzEeMBwGA1UECwwVQ2xvdWRmb3VuZHJ5IElk + ZW50aXR5MRswGQYDVQQDDBJ1YWEucnVuLnBpdm90YWwuaW8xKDAmBgkqhkiG9w0B + CQEWGXZjYXAtZGV2QGNsb3VkZm91bmRyeS5vcmcwHhcNMTUwMzAyMTQyMDQ4WhcN + MjUwMjI3MTQyMDQ4WjCBuDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYD + VQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQKDBRQaXZvdGFsIFNvZnR3YXJlIElu + YzEeMBwGA1UECwwVQ2xvdWRmb3VuZHJ5IElkZW50aXR5MRswGQYDVQQDDBJ1YWEu + cnVuLnBpdm90YWwuaW8xKDAmBgkqhkiG9w0BCQEWGXZjYXAtZGV2QGNsb3VkZm91 + bmRyeS5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAN0u5J4BJUDgRv6I + h5/r7rZjSrFVLL7bl71CzBIaVk1BQPYfBC8gggGAWmYYxJV0Kz+2Vx0Z96OnXhJk + gG46Zo2KMDudEeSdXou+dSBNISDv4VpLKUGnVU4n/L0khbI+jX51aS80ub8vThca + bkdY5x4Ir8G3QCQvCGKgU2emfFe7AgMBAAEwDQYJKoZIhvcNAQELBQADgYEAXghg + PwMhO0+dASJ83e2Bu63pKO808BrVjD51sSEMb0qwFc5IV6RzK/mkJgO0fphhoqOm + ZLzGcSYwCmj0Vc0GO5NgnFVZg4N9CyYCpDMeQynumlrNhRgnZRzlqXtQgL2bQDiu + coxNL/KY05iVlE1bmq/fzNEmEi2zf3dQV8CNSYs= + -----END CERTIFICATE---- + """; // openssl req -out cert.pem -nodes -keyout private.key -newkey rsa:2048 -new -x509 - public static final String opensslCert = "-----BEGIN CERTIFICATE-----\n" + - "MIIDXTCCAkWgAwIBAgIJAOpOBuLToBXJMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" + - "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" + - "aWRnaXRzIFB0eSBMdGQwHhcNMTcwNzE0MTcxNDE4WhcNMTcwODEzMTcxNDE4WjBF\n" + - "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" + - "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" + - "CgKCAQEA3+07F4S5Fz3wv/UFm/OWsJXm6s3pKI2mp4fSAY8rx9+0cyLAHsedWzeq\n" + - "5uKcDeRW858DOdnClaTOZC73FcvOmv1bw2eYcmfsbqHEhyR0dp+rDHt/7pr6kajC\n" + - "yUvAW+hoRRSMpooiZckxrjJ7LOa5iqRyZRwshfGN+mFSygfVguMDKrsE2rvpK6/K\n" + - "tkG/lcToLHiw4OnMnZ9ocrNRDAoCkzKGZTLJkUEr3MgOKmr2EO0P6KOAmNnOEmCf\n" + - "05ohcrUXeFZVnS5MMUzoGAOzBstZhA0dd7l297IDnWH9uIhCANCvZ9sovZWz/o3J\n" + - "pc2LyXsaI1cV7O1cGV4aEEn8zzWWGwIDAQABo1AwTjAdBgNVHQ4EFgQUXBO1+qo7\n" + - "w6iiiv1pnm+zdrQ3CzkwHwYDVR0jBBgwFoAUXBO1+qo7w6iiiv1pnm+zdrQ3Czkw\n" + - "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAT78lT5VEIetWPGk3szPz\n" + - "CT9zNpR1F+7o3rvRTI6Psyjz4tGlyX5iU0Z99Xa9yimIEhWme2UVsgQ9uOzk2IgH\n" + - "wMbB2TTP/RRK5+eO4BUu4zWWIXsIcfC6Rqw9Y3Hki+mRpuWMv+5pcOz/H+aYeSfy\n" + - "WvVYfRZJOhcztysII4HWIxw8qqwBrf5kX8IRKZXay+A2W04A6kjjX3zfN2OzljTA\n" + - "jZbtHedUGxSHvK8x6tHEwS0lZ9eZh+V4DWyRvrunwDCtA7zJQmrJd1qbM84H/1C8\n" + - "cAC6dglvc82n1BTAZbZwWHYt+Ro3Vp0GMPsZLOXJ0g03LbkhXg4krwXjJPD42nus\n" + - "3A==\n" + - "-----END CERTIFICATE-----\n"; - - public static final String opensslPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + - "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDf7TsXhLkXPfC/\n" + - "9QWb85awlebqzekojaanh9IBjyvH37RzIsAex51bN6rm4pwN5FbznwM52cKVpM5k\n" + - "LvcVy86a/VvDZ5hyZ+xuocSHJHR2n6sMe3/umvqRqMLJS8Bb6GhFFIymiiJlyTGu\n" + - "Mnss5rmKpHJlHCyF8Y36YVLKB9WC4wMquwTau+krr8q2Qb+VxOgseLDg6cydn2hy\n" + - "s1EMCgKTMoZlMsmRQSvcyA4qavYQ7Q/oo4CY2c4SYJ/TmiFytRd4VlWdLkwxTOgY\n" + - "A7MGy1mEDR13uXb3sgOdYf24iEIA0K9n2yi9lbP+jcmlzYvJexojVxXs7VwZXhoQ\n" + - "SfzPNZYbAgMBAAECggEAdEfMl7nkI52Wlxe1gfZMGga9ktC6csSb9gMhmo2uPmx8\n" + - "WA2Dlngxzlxp8ttaDhy0ym2YT0I1OWALjRqWVEsxTmqibCYvk7lDnW+Djmnv0Gm5\n" + - "eRHorQ7tbxYjkEQ174QQIU86eoDgu9puYfb036wwTT536OlodWWqRIqlYyQOS5h+\n" + - "KURvuQkH7CT30swTun11hHibNomxPd97D49aYqr86vrNKYRrVWuruc3OO9ofFaWO\n" + - "hRuLVivLiuGfFMtkVun2V9ropRArHeSOHPfyCUETJIcyrKPxk7ack0U/Nq9uf3Rd\n" + - "z9iImQkqMSMvaYmsqIM5qjgMqU+aXfj98l1v0hYvAQKBgQDzoC23vjx01as1BVIA\n" + - "Z0fc5t+LSonhrKphHnHxxUx7pcmS5HrBCL4Rv+6HL15zCfTkYdt5w+6hByJMpDL4\n" + - "ZwrdyoI1fL21PSo/TdzNLtgB6FUtYqrUSSFAG3fLN2OYqDW5vxumsFwjn1YmKG1f\n" + - "emTjNE422oQv/xVsjmj7tgc9CQKBgQDrTOjTVChO/H7WgV20TM8/fg9XD4mfimhD\n" + - "g9apKReOtKniBL0sFcJH7XpDoNahPRhk+iDY16IbLknstnxGRml+Y63ZHbnD+1v6\n" + - "vdR15vjJECWHdn8u1y9FqOWa4oXAOO4G1q1FQmjXEIX8svyXSkX65Qg3w9h5oYpN\n" + - "nhPHVJenAwKBgQDsCOmiVq5uJ8GLSg9LksTeMdStOFdkDQy5sWyF6DiUp2gnaDPC\n" + - "J/02ZzTrRqqEXEYmquSgEYN2AdpqVL+JSRQPFC+ZMLUADjWLRZ3CMTtYhcdYhHqr\n" + - "1/peCP7EJXLaKUZ8IrrggYeTf8FQkOR+l699LWUF4iol8kbIeSUfkhlrOQKBgQC2\n" + - "H7NeTxdb+6eZFEyZD5KiTEpHUqltKU4GY/c0u6+WL1QGszBQ/Q6BadhmnAlEh+tn\n" + - "zQq7jDvW2f8yDxUlt75Tq4eWM6HjhZzt+RyHnZ0W0z6ZGSjb8oaOXmpJdeecnvPt\n" + - "qyA2KW7Id+udalSELWL5DWlM8HOPwW8xIJeig2FWTQKBgBkGMD+32aXltymx0SIo\n" + - "JVfL+kPBtPyDdAJbxJu8fFfhzbFGlLI5qVQnrzjgjhnkHcnvmTu2ZgPStArpJqUk\n" + - "4KNl3HZLG6vreo137aKjXzshdNwx1Yzw0PigLAwLgx7APFZYkM0qpE0JPyFeFORu\n" + - "XXDWlzK1YYlKSBuSsm9VWfXq\n" + - "-----END PRIVATE KEY-----\n"; + private static final String OPEN_SSL_CERT = """ + -----BEGIN CERTIFICATE----- + MIIDXTCCAkWgAwIBAgIJAOpOBuLToBXJMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV + BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX + aWRnaXRzIFB0eSBMdGQwHhcNMTcwNzE0MTcxNDE4WhcNMTcwODEzMTcxNDE4WjBF + MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 + ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB + CgKCAQEA3+07F4S5Fz3wv/UFm/OWsJXm6s3pKI2mp4fSAY8rx9+0cyLAHsedWzeq + 5uKcDeRW858DOdnClaTOZC73FcvOmv1bw2eYcmfsbqHEhyR0dp+rDHt/7pr6kajC + yUvAW+hoRRSMpooiZckxrjJ7LOa5iqRyZRwshfGN+mFSygfVguMDKrsE2rvpK6/K + tkG/lcToLHiw4OnMnZ9ocrNRDAoCkzKGZTLJkUEr3MgOKmr2EO0P6KOAmNnOEmCf + 05ohcrUXeFZVnS5MMUzoGAOzBstZhA0dd7l297IDnWH9uIhCANCvZ9sovZWz/o3J + pc2LyXsaI1cV7O1cGV4aEEn8zzWWGwIDAQABo1AwTjAdBgNVHQ4EFgQUXBO1+qo7 + w6iiiv1pnm+zdrQ3CzkwHwYDVR0jBBgwFoAUXBO1+qo7w6iiiv1pnm+zdrQ3Czkw + DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAT78lT5VEIetWPGk3szPz + CT9zNpR1F+7o3rvRTI6Psyjz4tGlyX5iU0Z99Xa9yimIEhWme2UVsgQ9uOzk2IgH + wMbB2TTP/RRK5+eO4BUu4zWWIXsIcfC6Rqw9Y3Hki+mRpuWMv+5pcOz/H+aYeSfy + WvVYfRZJOhcztysII4HWIxw8qqwBrf5kX8IRKZXay+A2W04A6kjjX3zfN2OzljTA + jZbtHedUGxSHvK8x6tHEwS0lZ9eZh+V4DWyRvrunwDCtA7zJQmrJd1qbM84H/1C8 + cAC6dglvc82n1BTAZbZwWHYt+Ro3Vp0GMPsZLOXJ0g03LbkhXg4krwXjJPD42nus + 3A== + -----END CERTIFICATE----- + """; + + private static final String OPEN_SSL_PRIVATE_KEY = """ + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDf7TsXhLkXPfC/ + 9QWb85awlebqzekojaanh9IBjyvH37RzIsAex51bN6rm4pwN5FbznwM52cKVpM5k + LvcVy86a/VvDZ5hyZ+xuocSHJHR2n6sMe3/umvqRqMLJS8Bb6GhFFIymiiJlyTGu + Mnss5rmKpHJlHCyF8Y36YVLKB9WC4wMquwTau+krr8q2Qb+VxOgseLDg6cydn2hy + s1EMCgKTMoZlMsmRQSvcyA4qavYQ7Q/oo4CY2c4SYJ/TmiFytRd4VlWdLkwxTOgY + A7MGy1mEDR13uXb3sgOdYf24iEIA0K9n2yi9lbP+jcmlzYvJexojVxXs7VwZXhoQ + SfzPNZYbAgMBAAECggEAdEfMl7nkI52Wlxe1gfZMGga9ktC6csSb9gMhmo2uPmx8 + WA2Dlngxzlxp8ttaDhy0ym2YT0I1OWALjRqWVEsxTmqibCYvk7lDnW+Djmnv0Gm5 + eRHorQ7tbxYjkEQ174QQIU86eoDgu9puYfb036wwTT536OlodWWqRIqlYyQOS5h+ + KURvuQkH7CT30swTun11hHibNomxPd97D49aYqr86vrNKYRrVWuruc3OO9ofFaWO + hRuLVivLiuGfFMtkVun2V9ropRArHeSOHPfyCUETJIcyrKPxk7ack0U/Nq9uf3Rd + z9iImQkqMSMvaYmsqIM5qjgMqU+aXfj98l1v0hYvAQKBgQDzoC23vjx01as1BVIA + Z0fc5t+LSonhrKphHnHxxUx7pcmS5HrBCL4Rv+6HL15zCfTkYdt5w+6hByJMpDL4 + ZwrdyoI1fL21PSo/TdzNLtgB6FUtYqrUSSFAG3fLN2OYqDW5vxumsFwjn1YmKG1f + emTjNE422oQv/xVsjmj7tgc9CQKBgQDrTOjTVChO/H7WgV20TM8/fg9XD4mfimhD + g9apKReOtKniBL0sFcJH7XpDoNahPRhk+iDY16IbLknstnxGRml+Y63ZHbnD+1v6 + vdR15vjJECWHdn8u1y9FqOWa4oXAOO4G1q1FQmjXEIX8svyXSkX65Qg3w9h5oYpN + nhPHVJenAwKBgQDsCOmiVq5uJ8GLSg9LksTeMdStOFdkDQy5sWyF6DiUp2gnaDPC + J/02ZzTrRqqEXEYmquSgEYN2AdpqVL+JSRQPFC+ZMLUADjWLRZ3CMTtYhcdYhHqr + 1/peCP7EJXLaKUZ8IrrggYeTf8FQkOR+l699LWUF4iol8kbIeSUfkhlrOQKBgQC2 + H7NeTxdb+6eZFEyZD5KiTEpHUqltKU4GY/c0u6+WL1QGszBQ/Q6BadhmnAlEh+tn + zQq7jDvW2f8yDxUlt75Tq4eWM6HjhZzt+RyHnZ0W0z6ZGSjb8oaOXmpJdeecnvPt + qyA2KW7Id+udalSELWL5DWlM8HOPwW8xIJeig2FWTQKBgBkGMD+32aXltymx0SIo + JVfL+kPBtPyDdAJbxJu8fFfhzbFGlLI5qVQnrzjgjhnkHcnvmTu2ZgPStArpJqUk + 4KNl3HZLG6vreo137aKjXzshdNwx1Yzw0PigLAwLgx7APFZYkM0qpE0JPyFeFORu + XXDWlzK1YYlKSBuSsm9VWfXq + -----END PRIVATE KEY----- + """; // openssl req -out cert.pem -nodes -keyout private.key // -newkey ec:<(openssl ecparam -name secp224r1) -new -x509 - public static final String ecCertificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIBvjCCAWygAwIBAgIJAK/rmJC9QdjcMAoGCCqGSM49BAMCMEUxCzAJBgNVBAYT\n" + - "AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn\n" + - "aXRzIFB0eSBMdGQwHhcNMTcwNzE0MTgxNjU2WhcNMTcwODEzMTgxNjU2WjBFMQsw\n" + - "CQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJu\n" + - "ZXQgV2lkZ2l0cyBQdHkgTHRkME4wEAYHKoZIzj0CAQYFK4EEACEDOgAEY83DklF/\n" + - "qOPmJkASvf25MaDvzF7w+MeYaBZHiC18y9mayfAcKPti4MbPR6ADAo9NxKbdsZjA\n" + - "13+jUDBOMB0GA1UdDgQWBBQxUP7SZIeKaQmFaAIBDRCJjUcXbzAfBgNVHSMEGDAW\n" + - "gBQxUP7SZIeKaQmFaAIBDRCJjUcXbzAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMC\n" + - "A0AAMD0CHCR7SmBxeufWpfAECH+Zp/2NMhhyIuYoeOThi3wCHQCyJmYQs8xHzC17\n" + - "yMyZj8YGfSSXgdWkp381P0gl\n" + - "-----END CERTIFICATE-----\n"; - - public static final String ecPrivateKey = "-----BEGIN PRIVATE KEY-----\n" + - "MHgCAQAwEAYHKoZIzj0CAQYFK4EEACEEYTBfAgEBBBz+XVZZoypybMtDZWBVcrPu\n" + - "IiVn3yZ+kzF+f2NyoTwDOgAEY83DklF/qOPmJkASvf25MaDvzF7w+MeYaBZHiC18\n" + - "y9mayfAcKPti4MbPR6ADAo9NxKbdsZjA138=\n" + - "-----END PRIVATE KEY-----\n"; - - @Test(expected = CertificateException.class) - public void testInvalidCert() throws Exception { - new KeyWithCert(key, password, invalidCert); - } + private static final String EC_CERTIFICATE = """ + -----BEGIN CERTIFICATE----- + MIIBvjCCAWygAwIBAgIJAK/rmJC9QdjcMAoGCCqGSM49BAMCMEUxCzAJBgNVBAYT + AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn + aXRzIFB0eSBMdGQwHhcNMTcwNzE0MTgxNjU2WhcNMTcwODEzMTgxNjU2WjBFMQsw + CQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJu + ZXQgV2lkZ2l0cyBQdHkgTHRkME4wEAYHKoZIzj0CAQYFK4EEACEDOgAEY83DklF/ + qOPmJkASvf25MaDvzF7w+MeYaBZHiC18y9mayfAcKPti4MbPR6ADAo9NxKbdsZjA + 13+jUDBOMB0GA1UdDgQWBBQxUP7SZIeKaQmFaAIBDRCJjUcXbzAfBgNVHSMEGDAW + gBQxUP7SZIeKaQmFaAIBDRCJjUcXbzAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMC + A0AAMD0CHCR7SmBxeufWpfAECH+Zp/2NMhhyIuYoeOThi3wCHQCyJmYQs8xHzC17 + yMyZj8YGfSSXgdWkp381P0gl + -----END CERTIFICATE----- + """; + + private static final String EC_PRIVATE_KEY = """ + -----BEGIN PRIVATE KEY----- + MHgCAQAwEAYHKoZIzj0CAQYFK4EEACEEYTBfAgEBBBz+XVZZoypybMtDZWBVcrPu + IiVn3yZ+kzF+f2NyoTwDOgAEY83DklF/qOPmJkASvf25MaDvzF7w+MeYaBZHiC18 + y9mayfAcKPti4MbPR6ADAo9NxKbdsZjA138= + -----END PRIVATE KEY----- + """; @Test - public void testValidCert() throws Exception { - new KeyWithCert(encryptedKey, password, goodCert); + void invalidCert() { + assertThatThrownBy(() -> new KeyWithCert(KEY, PASSWORD, INVALID_CERT)) + .isInstanceOf(CertificateException.class); } @Test + void keyMismatch() { + assertThatThrownBy(() -> new KeyWithCert(KEY, "", OPEN_SSL_CERT)) + .isInstanceOf(CertificateException.class); + } - public void testEllipticCurve() throws Exception { - new KeyWithCert(ecPrivateKey, "", ecCertificate); + @Test + void validCert() throws CertificateException { + assertThat(new KeyWithCert(ENCRYPTED_KEY, PASSWORD, GOOD_CERT)).isNotNull(); } @Test - public void testEmbeddedPrivateKey() throws Exception { - new KeyWithCert(opensslPrivateKey, "", opensslCert); + void ellipticCurve() throws CertificateException { + assertThat(new KeyWithCert(EC_PRIVATE_KEY, "", EC_CERTIFICATE)).isNotNull(); } - @Test(expected = CertificateException.class) - public void testKeyMismatch() throws Exception { - new KeyWithCert(key, "", opensslCert); + @Test + void embeddedPrivateKey() throws CertificateException { + assertThat(new KeyWithCert(OPEN_SSL_PRIVATE_KEY, "", OPEN_SSL_CERT)).isNotNull(); } @Test - public void testCertOnly() throws Exception { - assertNotNull(new KeyWithCert(goodCert).getCertificate()); + void certOnly() throws CertificateException { + assertThat(new KeyWithCert(GOOD_CERT)) + .isNotNull() + .extracting(KeyWithCert::getCertificate) + .isNotNull(); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandlerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandlerTests.java index c331a30c375..d944d179269 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandlerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandlerTests.java @@ -15,52 +15,108 @@ package org.cloudfoundry.identity.uaa.web; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.core.Saml2ParameterNames; +import org.springframework.security.web.savedrequest.SavedRequest; +import javax.servlet.ServletException; +import javax.servlet.http.HttpSession; +import java.io.IOException; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import static org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler.FORM_REDIRECT_PARAMETER; import static org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE; -import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +class UaaSavedRequestAwareAuthenticationSuccessHandlerTests { -public class UaaSavedRequestAwareAuthenticationSuccessHandlerTests { + private static final String SPRING_SECURITY_SAVED_REQUEST = "SPRING_SECURITY_SAVED_REQUEST"; MockHttpServletRequest request; UaaSavedRequestAwareAuthenticationSuccessHandler handler; - @Before + + @BeforeEach public void setUp() { request = new MockHttpServletRequest(); handler = new UaaSavedRequestAwareAuthenticationSuccessHandler(); } @Test - public void allow_url_override() { - request.setAttribute(URI_OVERRIDE_ATTRIBUTE, "http://test.com"); - assertEquals("http://test.com", handler.determineTargetUrl(request, new MockHttpServletResponse())); + void allow_url_override() { + String overrideUrl = "https://test.com"; + request.setAttribute(URI_OVERRIDE_ATTRIBUTE, overrideUrl); + assertThat(handler.determineTargetUrl(request, new MockHttpServletResponse())).isEqualTo(overrideUrl); } @Test - public void form_parameter_is_overridden() { - request.setParameter(FORM_REDIRECT_PARAMETER, "http://test.com"); - request.setAttribute(URI_OVERRIDE_ATTRIBUTE, "http://override.test.com"); - assertEquals("http://override.test.com", handler.determineTargetUrl(request, new MockHttpServletResponse())); + void form_parameter_is_overridden() { + request.setParameter(FORM_REDIRECT_PARAMETER, "https://test.com"); + String overrideUrl = "https://override.test.com"; + request.setAttribute(URI_OVERRIDE_ATTRIBUTE, overrideUrl); + assertThat(handler.determineTargetUrl(request, new MockHttpServletResponse())).isEqualTo(overrideUrl); } @Test - public void validFormRedirectIsReturned() { + void validFormRedirectIsReturned() { String redirectUri = request.getScheme() + "://" + request.getServerName() + "/test"; request.setParameter(FORM_REDIRECT_PARAMETER, redirectUri); - assertEquals(redirectUri, handler.determineTargetUrl(request, new MockHttpServletResponse())); + assertThat(handler.determineTargetUrl(request, new MockHttpServletResponse())).isEqualTo(redirectUri); } @Test - public void invalidFormRedirectIsNotReturned() { - String redirectUri = "http://test.com/test"; + void invalidFormRedirectIsNotReturned() { + String redirectUri = "https://test.com/test"; request.setParameter(FORM_REDIRECT_PARAMETER, redirectUri); - assertEquals("/", handler.determineTargetUrl(request, new MockHttpServletResponse())); + assertThat(handler.determineTargetUrl(request, new MockHttpServletResponse())).isEqualTo("/"); + } + + @Test + void onAuthenticationSuccess_noSavedRequest_hasRelayStateUrl() throws ServletException, IOException { + String redirectUri = "https://test.com/test2"; + request.setParameter(Saml2ParameterNames.RELAY_STATE, redirectUri); + + var response = new MockHttpServletResponse(); + var authentication = mock(Authentication.class); + handler.onAuthenticationSuccess(request, response, authentication); + + assertThat(response.getRedirectedUrl()).isEqualTo(redirectUri); + } + + @Test + void onAuthenticationSuccess_noSavedRequest_noRelayStateUrl() throws ServletException, IOException { + request.setParameter(Saml2ParameterNames.RELAY_STATE, "123"); + request.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION", "exception"); + + var response = new MockHttpServletResponse(); + var authentication = mock(Authentication.class); + + handler.onAuthenticationSuccess(request, response, authentication); + + assertThat(response.getRedirectedUrl()).isEqualTo("/"); + // Clears Authentication Attributes + assertThat(request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION")).isNull(); + } + + @Test + void onAuthenticationSuccess_withSavedRequest_targetUrlParameter() throws ServletException, IOException { + String redirectUri = "https://test.com/test3"; + SavedRequest savedRequest = mock(SavedRequest.class); + when(savedRequest.getRedirectUrl()).thenReturn(redirectUri); + + HttpSession session = request.getSession(); + session.setAttribute(SPRING_SECURITY_SAVED_REQUEST, savedRequest); + + var response = new MockHttpServletResponse(); + var authentication = mock(Authentication.class); + + handler.onAuthenticationSuccess(request, response, authentication); + assertThat(response.getRedirectedUrl()).isEqualTo(redirectUri); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolderTest.java index fb873546cf7..7c13286e8e8 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolderTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolderTest.java @@ -14,59 +14,62 @@ */ package org.cloudfoundry.identity.uaa.zone; +import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManager; import org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManagerFactory; -import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; -import org.junit.jupiter.api.*; +import org.cloudfoundry.identity.uaa.saml.SamlKey; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InOrder; -import org.springframework.security.saml.key.KeyManager; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; +import java.util.Map; import java.util.UUID; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.mockito.Mockito.*; - -@ExtendWith(PollutionPreventionExtension.class) +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +// This class tests a deprecated class, naturally there will be deprecation warnings, suppress them +@SuppressWarnings("deprecation") +@ExtendWith(MockitoExtension.class) class IdentityZoneHolderTest { + @Mock + IdentityZoneProvisioning mockProvisioning; + + @Mock private SamlKeyManagerFactory mockSamlKeyManagerFactory; + @Mock + IdentityZone mockIdentityZone; + + @Mock + private SamlKeyManager mockSamlKeyManager; + @BeforeEach void setUp() { - mockSamlKeyManagerFactory = mock(SamlKeyManagerFactory.class); - setSamlKeyManagerFactory(mockSamlKeyManagerFactory); - } - - @AfterAll - static void tearDown() { - setSamlKeyManagerFactory(new SamlKeyManagerFactory()); + IdentityZoneHolder.setProvisioning(mockProvisioning); + IdentityZoneHolder.setSamlKeyManagerFactory(mockSamlKeyManagerFactory); } + // IdentityZoneHolder has a lot of SAML functionality built-in + // Also, note that it's deprecated and we should migrate the code to use IdentityZoneManager @Test void set() { - IdentityZone mockIdentityZone = mock(IdentityZone.class); - getKeyManagerThreadLocal().set(mock(KeyManager.class)); - IdentityZoneHolder.set(mockIdentityZone); - - assertThat(IdentityZoneHolder.get(), is(mockIdentityZone)); - assertThat(getKeyManagerThreadLocal().get(), is(nullValue())); - } - - @Test - void get() { - IdentityZone mockIdentityZone = mock(IdentityZone.class); - - IdentityZoneHolder.set(mockIdentityZone); - - assertThat(IdentityZoneHolder.get(), is(mockIdentityZone)); + assertThat(IdentityZoneHolder.get()).isSameAs(mockIdentityZone); + assertThat(IdentityZoneHolder.getSamlKeyManager()).isNull(); } @Nested - @ExtendWith(PollutionPreventionExtension.class) class WhenZoneIsUaa { @BeforeEach void setUp() { @@ -75,25 +78,41 @@ void setUp() { @Test void isUaa() { - assertThat(IdentityZoneHolder.isUaa(), is(true)); + assertThat(IdentityZoneHolder.isUaa()).isTrue(); } } @Nested - @ExtendWith(PollutionPreventionExtension.class) - class WhenZoneIsNotUaa { - private IdentityZone mockIdentityZone; + class InitializerSetUp { + @Mock + IdentityZoneProvisioning mockProvisioning2; + + @Mock + private SamlKeyManagerFactory mockSamlKeyManagerFactory2; + @Test + void initializerSetResetValues() { + IdentityZoneHolder.Initializer initializer = new IdentityZoneHolder.Initializer(mockProvisioning2, mockSamlKeyManagerFactory2); + assertThat(getIdentityZoneProvisioning()).isSameAs(mockProvisioning2); + assertThat(getSamlKeyManagerFactory()).isSameAs(mockSamlKeyManagerFactory2); + + initializer.reset(); + assertThat(getIdentityZoneProvisioning()).isNull(); + assertThat(getSamlKeyManagerFactory()).isNull(); + } + } + + @Nested + class WhenZoneIsNotUaa { @BeforeEach void setUp() { - mockIdentityZone = mock(IdentityZone.class); - when(mockIdentityZone.getId()).thenReturn("not uaa"); + when(mockIdentityZone.isUaa()).thenReturn(false); IdentityZoneHolder.set(mockIdentityZone); } @Test void isUaa() { - assertThat(IdentityZoneHolder.isUaa(), is(false)); + assertThat(IdentityZoneHolder.isUaa()).isFalse(); } } @@ -105,168 +124,113 @@ void setUp() { } @Test - void initializer() { + void get() { IdentityZoneHolder.clear(); - assertThat(IdentityZoneHolder.get(), is(IdentityZone.getUaa())); + assertThat(IdentityZoneHolder.get()).isEqualTo(IdentityZone.getUaa()); } @Test void getUaaZone() { - assertThat(IdentityZoneHolder.getUaaZone(), is(IdentityZone.getUaa())); - } - - @Test - void getSamlSPKeyManager_WhenSecondCallWorks() { - IdentityZone mockIdentityZone = mock(IdentityZone.class); - IdentityZoneHolder.set(mockIdentityZone); - - IdentityZoneConfiguration mockIdentityZoneConfiguration = mock(IdentityZoneConfiguration.class); - when(mockIdentityZone.getConfig()).thenReturn(mockIdentityZoneConfiguration); - - SamlConfig mockSamlConfig = mock(SamlConfig.class); - when(mockIdentityZoneConfiguration.getSamlConfig()).thenReturn(mockSamlConfig); - - KeyManager expectedKeyManager = mock(KeyManager.class); - when(mockSamlKeyManagerFactory.getKeyManager(any())) - .thenReturn(null) - .thenReturn(expectedKeyManager); - - // Call several times! The value is cached in KEY_MANAGER_THREAD_LOCAL - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - - verify(mockSamlKeyManagerFactory).getKeyManager(mockSamlConfig); - verify(mockSamlKeyManagerFactory, times(2)).getKeyManager(any()); + assertThat(IdentityZoneHolder.getUaaZone()).isEqualTo(IdentityZone.getUaa()); } } @Nested - @ExtendWith(PollutionPreventionExtension.class) class WithJdbcProvisioning { - private IdentityZoneProvisioning mockIdentityZoneProvisioning; private IdentityZone mockIdentityZoneFromProvisioning; @BeforeEach void setUp() { - mockIdentityZoneProvisioning = mock(IdentityZoneProvisioning.class); mockIdentityZoneFromProvisioning = mock(IdentityZone.class); - when(mockIdentityZoneProvisioning.retrieve(anyString())).thenReturn(mockIdentityZoneFromProvisioning); - IdentityZoneHolder.setProvisioning(mockIdentityZoneProvisioning); + when(mockProvisioning.retrieve(anyString())).thenReturn(mockIdentityZoneFromProvisioning); + IdentityZoneHolder.setProvisioning(mockProvisioning); } @Test void initializer() { IdentityZoneHolder.clear(); - assertThat(IdentityZoneHolder.get(), is(mockIdentityZoneFromProvisioning)); - verify(mockIdentityZoneProvisioning).retrieve("uaa"); + assertThat(IdentityZoneHolder.get()).isEqualTo(mockIdentityZoneFromProvisioning); + verify(mockProvisioning).retrieve("uaa"); } @Test void getUaaZone() { - assertThat(IdentityZoneHolder.getUaaZone(), is(mockIdentityZoneFromProvisioning)); - verify(mockIdentityZoneProvisioning).retrieve("uaa"); + assertThat(IdentityZoneHolder.getUaaZone()).isEqualTo(mockIdentityZoneFromProvisioning); + verify(mockProvisioning).retrieve("uaa"); } + } - @Test - void getSamlSPKeyManager_WhenSecondCallWorks() { - IdentityZoneConfiguration mockIdentityZoneConfigurationFromProvisioning = mock(IdentityZoneConfiguration.class); - when(mockIdentityZoneFromProvisioning.getConfig()).thenReturn(mockIdentityZoneConfigurationFromProvisioning); - - SamlConfig mockSamlConfigFromProvisioning = mock(SamlConfig.class); - when(mockIdentityZoneConfigurationFromProvisioning.getSamlConfig()).thenReturn(mockSamlConfigFromProvisioning); - - IdentityZone mockIdentityZone = mock(IdentityZone.class); - IdentityZoneConfiguration mockIdentityZoneConfiguration = mock(IdentityZoneConfiguration.class); - SamlConfig mockSamlConfig = mock(SamlConfig.class); - when(mockIdentityZone.getConfig()).thenReturn(mockIdentityZoneConfiguration); - when(mockIdentityZoneConfiguration.getSamlConfig()).thenReturn(mockSamlConfig); - when(mockSamlKeyManagerFactory.getKeyManager(mockSamlConfig)) - .thenReturn(null); - IdentityZoneHolder.set(mockIdentityZone); - - KeyManager expectedKeyManager = mock(KeyManager.class); - when(mockSamlKeyManagerFactory.getKeyManager(mockSamlConfigFromProvisioning)) - .thenReturn(expectedKeyManager); - - // Call several times! The value is cached in KEY_MANAGER_THREAD_LOCAL - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - - InOrder inOrder = inOrder(mockSamlKeyManagerFactory); + @Test + void getSamlKeyManager_WhenKeyManagerIsAlreadyCached() { + getKeyManagerThreadLocal().set(mockSamlKeyManager); - inOrder.verify(mockSamlKeyManagerFactory).getKeyManager(mockSamlConfig); - inOrder.verify(mockSamlKeyManagerFactory).getKeyManager(mockSamlConfigFromProvisioning); - verify(mockSamlKeyManagerFactory, times(2)).getKeyManager(any()); + // Call several times! The value is cached in KEY_MANAGER_THREAD_LOCAL + for (int i = 0; i < 3; i++) { + assertThat(IdentityZoneHolder.getSamlKeyManager()).isSameAs(mockSamlKeyManager); } + verify(mockSamlKeyManagerFactory, never()).getKeyManager(any()); } @Test - void getSamlSPKeyManager_WhenKeyManagerIsNotNull() { - KeyManager expectedKeyManager = mock(KeyManager.class); - getKeyManagerThreadLocal().set(expectedKeyManager); + void getSamlKeyManager_IsCachedForSubsequentCalls() { + IdentityZoneHolder.set(mockIdentityZone); + + IdentityZoneConfiguration mockIdentityZoneConfiguration = mock(IdentityZoneConfiguration.class); + when(mockIdentityZone.getConfig()).thenReturn(mockIdentityZoneConfiguration); + SamlConfig mockSamlConfig = mock(SamlConfig.class); + when(mockIdentityZoneConfiguration.getSamlConfig()).thenReturn(mockSamlConfig); + when(mockSamlConfig.getKeys()).thenReturn(Map.of("key1", new SamlKey("key1", "passphrase1", "certificate1"))); + when(mockSamlKeyManagerFactory.getKeyManager(mockSamlConfig)).thenReturn(mockSamlKeyManager); // Call several times! The value is cached in KEY_MANAGER_THREAD_LOCAL - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); + for (int i = 0; i < 10; i++) { + assertThat(IdentityZoneHolder.getSamlKeyManager()).isSameAs(mockSamlKeyManager); + } - verify(mockSamlKeyManagerFactory, never()).getKeyManager(any()); + verify(mockSamlKeyManagerFactory).getKeyManager(mockSamlConfig); + verify(mockSamlKeyManagerFactory, times(1)).getKeyManager(any()); } @Test - void getSamlSPKeyManager_WhenFirstCallWorks() { - IdentityZone mockIdentityZone = mock(IdentityZone.class); + void getSamlKeyManager_RetryOnNull_CachedForSubsequentCalls() { IdentityZoneHolder.set(mockIdentityZone); IdentityZoneConfiguration mockIdentityZoneConfiguration = mock(IdentityZoneConfiguration.class); when(mockIdentityZone.getConfig()).thenReturn(mockIdentityZoneConfiguration); - SamlConfig mockSamlConfig = mock(SamlConfig.class); when(mockIdentityZoneConfiguration.getSamlConfig()).thenReturn(mockSamlConfig); + when(mockSamlConfig.getKeys()).thenReturn(Map.of("key1", new SamlKey("key1", "passphrase1", "certificate1"))); + when(mockSamlKeyManagerFactory.getKeyManager(mockSamlConfig)).thenReturn(null, mockSamlKeyManager); - KeyManager expectedKeyManager = mock(KeyManager.class); - when(mockSamlKeyManagerFactory.getKeyManager(any())).thenReturn(expectedKeyManager); + assertThat(IdentityZoneHolder.getSamlKeyManager()).isNull(); // Call several times! The value is cached in KEY_MANAGER_THREAD_LOCAL - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); - assertThat(IdentityZoneHolder.getSamlSPKeyManager(), is(expectedKeyManager)); + for (int i = 0; i < 10; i++) { + assertThat(IdentityZoneHolder.getSamlKeyManager()).isSameAs(mockSamlKeyManager); + } - verify(mockSamlKeyManagerFactory).getKeyManager(mockSamlConfig); - verify(mockSamlKeyManagerFactory, times(1)).getKeyManager(any()); + verify(mockSamlKeyManagerFactory, times(2)).getKeyManager(any()); } @Test void getCurrentZoneId() { - IdentityZone mockIdentityZone = mock(IdentityZone.class); String expectedId = UUID.randomUUID().toString(); when(mockIdentityZone.getId()).thenReturn(expectedId); IdentityZoneHolder.set(mockIdentityZone); - assertThat(IdentityZoneHolder.getCurrentZoneId(), is(expectedId)); + assertThat(IdentityZoneHolder.getCurrentZoneId()).isEqualTo(expectedId); } - private static void setSamlKeyManagerFactory( - SamlKeyManagerFactory samlKeyManagerFactory) { - ReflectionTestUtils.setField( - IdentityZoneHolder.class, - "samlKeyManagerFactory", - samlKeyManagerFactory); + private static IdentityZoneProvisioning getIdentityZoneProvisioning() { + return (IdentityZoneProvisioning) ReflectionTestUtils.getField(IdentityZoneHolder.class, "provisioning"); } - private static ThreadLocal getKeyManagerThreadLocal() { - return (ThreadLocal) - ReflectionTestUtils.getField(IdentityZoneHolder.class, "KEY_MANAGER_THREAD_LOCAL"); + private static SamlKeyManagerFactory getSamlKeyManagerFactory() { + return (SamlKeyManagerFactory) ReflectionTestUtils.getField(IdentityZoneHolder.class, "samlKeyManagerFactory"); } + @SuppressWarnings("unchecked") + private static ThreadLocal getKeyManagerThreadLocal() { + return (ThreadLocal) ReflectionTestUtils.getField(IdentityZoneHolder.class, "KEY_MANAGER_THREAD_LOCAL"); + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java index 21f96d3c594..de3ce7d4264 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java @@ -16,7 +16,7 @@ public static IdentityZone identityZone(String id, String subdomain) { } public static IdentityProvider identityProvider(String originKey, String zoneId) { - IdentityProvider idp = new IdentityProvider(); + IdentityProvider idp = new IdentityProvider<>(); idp.setName(originKey+" name"); idp.setOriginKey(originKey); idp.setType(originKey+" type"); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEventTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEventTest.java index 381d94f5e64..882a3ce1aab 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEventTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEventTest.java @@ -26,7 +26,7 @@ void setup() { currentIdentityZoneId = "currentIdentityZoneId-" + randomValueStringGenerator.generate(); String origin = "idp-mock-saml-" + randomValueStringGenerator.generate(); - String metadata = String.format(BootstrapSamlIdentityProviderDataTests.xmlWithoutID, "http://localhost:9999/metadata/" + origin); + String metadata = String.format(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID, "http://localhost:9999/metadata/" + origin); provider = new IdentityProvider<>(); provider.setId("id"); provider.setActive(true); diff --git a/server/src/test/resources/no_single_logout_service-metadata.xml b/server/src/test/resources/no_single_logout_service-metadata.xml new file mode 100644 index 00000000000..3310e510839 --- /dev/null +++ b/server/src/test/resources/no_single_logout_service-metadata.xml @@ -0,0 +1,31 @@ + + + + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + TAS Identity & Credentials + mailto:tas-identity-and-credentials@groups.vmware.com + + \ No newline at end of file diff --git a/server/src/test/resources/org/cloudfoundry/identity/uaa/provider/saml/IDP_META_DATA.xml b/server/src/test/resources/org/cloudfoundry/identity/uaa/provider/saml/IDP_META_DATA.xml index 792ae07592e..386befef5a5 100644 --- a/server/src/test/resources/org/cloudfoundry/identity/uaa/provider/saml/IDP_META_DATA.xml +++ b/server/src/test/resources/org/cloudfoundry/identity/uaa/provider/saml/IDP_META_DATA.xml @@ -1,27 +1,54 @@ - - - - begl1WVCsXSn7iHixtWPP8d/X+k=BmbKqA3A0oSLcn5jImz/l5WbpVXj+8JIpT/ENWjOjSd/gcAsZm1QvYg+RxYPBk+iV2bBxD+/yAE/w0wibsHrl0u9eDhoMRUJBUSmeyuN1lYzBuoVa08PdAGtb5cGm4DMQT5Rzakb1P0hhEPPEDDHgTTxop89LUu6xx97t2Q03Khy8mXEmBmNt2NlFxJPNt0FwHqLKOHRKBOE/+BpswlBocjOQKFsI9tG3TyjFC68mM2jo0fpUQCgj5ZfhzolvS7z7c6V201d9Tqig0/mMFFJLTN8WuZPavw22AJlMjsDY9my+4R9HKhK5U53DhcTeECs9fb4gd7p5BJy4vVp7tqqOg== - MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + + + + + + + + begl1WVCsXSn7iHixtWPP8d/X+k= + + + + BmbKqA3A0oSLcn5jImz/l5WbpVXj+8JIpT/ENWjOjSd/gcAsZm1QvYg+RxYPBk+iV2bBxD+/yAE/w0wibsHrl0u9eDhoMRUJBUSmeyuN1lYzBuoVa08PdAGtb5cGm4DMQT5Rzakb1P0hhEPPEDDHgTTxop89LUu6xx97t2Q03Khy8mXEmBmNt2NlFxJPNt0FwHqLKOHRKBOE/+BpswlBocjOQKFsI9tG3TyjFC68mM2jo0fpUQCgj5ZfhzolvS7z7c6V201d9Tqig0/mMFFJLTN8WuZPavw22AJlMjsDY9my+4R9HKhK5U53DhcTeECs9fb4gd7p5BJy4vVp7tqqOg== + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + - MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + - MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + - + urn:oasis:names:tc:SAML:2.0:nameid-format:transient - + Filip diff --git a/server/src/test/resources/saml-sample-metadata.xml b/server/src/test/resources/saml-sample-metadata.xml new file mode 100644 index 00000000000..9b7d480d3c7 --- /dev/null +++ b/server/src/test/resources/saml-sample-metadata.xml @@ -0,0 +1,58 @@ + + + + + + + + MIID7TCCAtWgAwIBAgIJANn3qP9lF7M3MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJVQTEXMBUGA1UE + CAwOS2hhcmtpdiBSZWdpb24xEDAOBgNVBAcMB0toYXJrb3YxDzANBgNVBAoMBk9yYWNsZTEYMBYGA1UEAwwPc3RzeWJvdi12bTEudWEzMScw + JQYJKoZIhvcNAQkBFhhzZXJnaWkudHN5Ym92QG9yYWNsZS5jb20wHhcNMTUxMjI1MTIyMjU5WhcNMjUxMjI0MTIyMjU5WjCBjDELMAkGA1UE + BhMCVUExFzAVBgNVBAgMDktoYXJraXYgUmVnaW9uMRAwDgYDVQQHDAdLaGFya292MQ8wDQYDVQQKDAZPcmFjbGUxGDAWBgNVBAMMD3N0c3lib + 3Ytdm0xLnVhMzEnMCUGCSqGSIb3DQEJARYYc2VyZ2lpLnRzeWJvdkBvcmFjbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCA + QEAw4OFwuUNjn6xxb/OuAnmQA6mCWPY2hKMoOz0cAajUHjNZZMwGnuEeUyPtEcULfz2MYo1yKQLxVj3pY0HTIQAzpY8o+xCqJFQmdMiakb + PFHlh4z/qqiS5jHng6JCeUpCIxeiTG9JXVwF1ErBEZbwZYjVxa6S+0grVkS3YxuH4uTyqxskuGnHK/AviTHLBrLfSrbFKYuQUrXyy6X22wpzo + bQ3Z+4bhEE8SXQtVbQdy7K0MKWYopNhX05SMTv7yMfUGp8EkGNyJ5Km8AuQt6ZCbVao6cHL2hSujQiN6aMjKbdzHeA1QEicppnnoG/Zefyi/ + okWdlLAaLjcpYrjUSWQJZQIDAQABo1AwTjAdBgNVHQ4EFgQUIKa0zeXmAJsCuNhJjhU0o7KiQgYwHwYDVR0jBBgwFoAUIKa0zeXmAJsCuNhJj + hU0o7KiQgYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAJawU5WRXqkW4emm+djpJAxZ0076qPgEsaaog6ng4MLAlU7RmfIY/ + l0VhXQegvhIBfG4OfduuzGaqd9y4IsQZFJ0yuotl96iEVcqg7hJ1LEY6UT6u6dZyGj1a9I6IlwJm/9CXFZHuVqGJkMfQZ4gaunE4c5gjbQA5/ + +PEJwPorKn48w8bojymV8hriqzrmaP8eQNuZUJsJdnKENOE5/asGyj+R2YfP6bmlOX3q0ozLcyJbXeZ6IvDFdRiDH5wO4JqW/ujvdvC553y + CO3xxsorB4xCupuHu/c7vkzNpaKjYdmGRkqhEqBcCqYSxdwIFc1xhOwYPWKJzgn7pGQsT7yNJg== + + + + + + + + + MIID7TCCAtWgAwIBAgIJANn3qP9lF7M3MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJVQTEXMBUGA1 + UECAwOS2hhcmtpdiBSZWdpb24xEDAOBgNVBAcMB0toYXJrb3YxDzANBgNVBAoMBk9yYWNsZTEYMBYGA1UEAwwPc3RzeWJvdi12bTEud + WEzMScwJQYJKoZIhvcNAQkBFhhzZXJnaWkudHN5Ym92QG9yYWNsZS5jb20wHhcNMTUxMjI1MTIyMjU5WhcNMjUxMjI0MTIyMjU5WjCB + jDELMAkGA1UEBhMCVUExFzAVBgNVBAgMDktoYXJraXYgUmVnaW9uMRAwDgYDVQQHDAdLaGFya292MQ8wDQYDVQQKDAZPcmFjbGUxGDA + WBgNVBAMMD3N0c3lib3Ytdm0xLnVhMzEnMCUGCSqGSIb3DQEJARYYc2VyZ2lpLnRzeWJvdkBvcmFjbGUuY29tMIIBIjANBgkqhkiG9w0B + AQEFAAOCAQ8AMIIBCgKCAQEAw4OFwuUNjn6xxb/OuAnmQA6mCWPY2hKMoOz0cAajUHjNZZMwGnuEeUyPtEcULfz2MYo1yKQLxVj3pY0HT + IQAzpY8o+xCqJFQmdMiakbPFHlh4z/qqiS5jHng6JCeUpCIxeiTG9JXVwF1ErBEZbwZYjVxa6S+0grVkS3YxuH4uTyqxskuGnHK/ + AviTHLBrLfSrbFKYuQUrXyy6X22wpzobQ3Z+4bhEE8SXQtVbQdy7K0MKWYopNhX05SMTv7yMfUGp8EkGNyJ5Km8AuQt6ZCbVao6cHL2h + SujQiN6aMjKbdzHeA1QEicppnnoG/Zefyi/okWdlLAaLjcpYrjUSWQJZQIDAQABo1AwTjAdBgNVHQ4EFgQUIKa0zeXmAJsCuNhJjhU0o + 7KiQgYwHwYDVR0jBBgwFoAUIKa0zeXmAJsCuNhJjhU0o7KiQgYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAJawU5WRXq + kW4emm+djpJAxZ0076qPgEsaaog6ng4MLAlU7RmfIY/l0VhXQegvhIBfG4OfduuzGaqd9y4IsQZFJ0yuotl96iEVcqg7hJ1LEY6UT6u6d + ZyGj1a9I6IlwJm/9CXFZHuVqGJkMfQZ4gaunE4c5gjbQA5/+PEJwPorKn48w8bojymV8hriqzrmaP8eQNuZUJsJdnKENOE5/ + asGyj+R2YfP6bmlOX3q0ozLcyJbXeZ6IvDFdRiDH5wO4JqW/ujvdvC553yCO3xxsorB4xCupuHu/c7vkzNpaKjYdmGRkqhEqBcCqYSxd + wIFc1xhOwYPWKJzgn7pGQsT7yNJg== + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + Administrator + name@emailprovider.com + + \ No newline at end of file diff --git a/server/src/test/resources/test-saml-idp-metadata-post-binding.xml b/server/src/test/resources/test-saml-idp-metadata-post-binding.xml new file mode 100644 index 00000000000..ea4980fdbac --- /dev/null +++ b/server/src/test/resources/test-saml-idp-metadata-post-binding.xml @@ -0,0 +1,16 @@ + + + + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + + + diff --git a/server/src/test/resources/test-saml-idp-metadata-redirect-binding.xml b/server/src/test/resources/test-saml-idp-metadata-redirect-binding.xml new file mode 100644 index 00000000000..0f27dfff350 --- /dev/null +++ b/server/src/test/resources/test-saml-idp-metadata-redirect-binding.xml @@ -0,0 +1,16 @@ + + + + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + + + diff --git a/shadow/opensaml-security-api/build.gradle b/shadow/opensaml-security-api/build.gradle new file mode 100644 index 00000000000..1b9b0188bc9 --- /dev/null +++ b/shadow/opensaml-security-api/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.github.johnrengelman.shadow' + +dependencies { + implementation "org.opensaml:opensaml-security-api:${versions.opensaml}" + compileOnly "org.opensaml:opensaml-core:${versions.opensaml}" +} + +configurations { + configureEach { + exclude(group: "org.bouncycastle", module: "bcprov-jdk18on") + exclude(group: "org.bouncycastle", module: "bcpkix-jdk18on") + exclude(group: "org.bouncycastle", module: "bcutil-jdk18on") + } +} + +tasks.named("shadowJar").configure { + archiveBaseName = "cloudfoundry-identity-shadow-opensaml-security-api" + + manifest { + attributes 'Automatic-Module-Name': 'org.opensaml.security' + } + exclude 'META-INF/services/org.opensaml.security.crypto.ec.NamedCurve' +} \ No newline at end of file diff --git a/shadow/opensaml-security-api/src/main/java/org/opensaml/security/config/org/cloudfoundry/identity/uaa/OpenSamlShadowSecurityConfigurationPropertiesSource.java b/shadow/opensaml-security-api/src/main/java/org/opensaml/security/config/org/cloudfoundry/identity/uaa/OpenSamlShadowSecurityConfigurationPropertiesSource.java new file mode 100644 index 00000000000..68f7bf3640e --- /dev/null +++ b/shadow/opensaml-security-api/src/main/java/org/opensaml/security/config/org/cloudfoundry/identity/uaa/OpenSamlShadowSecurityConfigurationPropertiesSource.java @@ -0,0 +1,15 @@ +package org.opensaml.security.config.org.cloudfoundry.identity.uaa; + +import org.opensaml.core.config.ConfigurationPropertiesSource; + +import java.util.Properties; + +public class OpenSamlShadowSecurityConfigurationPropertiesSource implements ConfigurationPropertiesSource { + + @Override + public Properties getProperties() { + Properties properties = new Properties(); + properties.setProperty("opensaml.config.ecdh.defaultKDF", "PBKDF2"); + return properties; + } +} \ No newline at end of file diff --git a/shadow/opensaml-security-api/src/main/resources/META-INF/services/org.opensaml.core.config.ConfigurationPropertiesSource b/shadow/opensaml-security-api/src/main/resources/META-INF/services/org.opensaml.core.config.ConfigurationPropertiesSource new file mode 100644 index 00000000000..2aac80ecb23 --- /dev/null +++ b/shadow/opensaml-security-api/src/main/resources/META-INF/services/org.opensaml.core.config.ConfigurationPropertiesSource @@ -0,0 +1 @@ +org.opensaml.security.config.org.cloudfoundry.identity.uaa.OpenSamlShadowSecurityConfigurationPropertiesSource \ No newline at end of file diff --git a/uaa/build.gradle b/uaa/build.gradle index 98abb79921b..6a44cc492a8 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -15,9 +15,9 @@ jar { enabled = false } war { archiveClassifier = '' } war { enabled = true } - repositories { maven { url("https://repo.spring.io/libs-milestone") } + maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" } } description = "UAA" @@ -49,6 +49,7 @@ dependencies { implementation(libraries.braveContextSlf4j) providedCompile(libraries.tomcatEmbed) + implementation(libraries.bouncyCastleFipsProv) testImplementation(identityServer.sourceSets.test.output) @@ -71,6 +72,7 @@ dependencies { exclude(module: "mail") exclude(module: "activation") } + testImplementation(libraries.orgJson) testImplementation(libraries.jsonAssert) testImplementation(libraries.jsonPathAssert) testImplementation(libraries.unboundIdScimSdk) { @@ -84,9 +86,6 @@ dependencies { testImplementation(libraries.springSessionJdbc) testImplementation(libraries.springTest) testImplementation(libraries.springSecurityLdap) - testImplementation(libraries.springSecuritySaml) { - exclude(module: "commons-httpclient") - } testImplementation(libraries.springSecurityTest) testImplementation(libraries.springBootStarterMail) testImplementation(libraries.mockito) @@ -97,6 +96,9 @@ dependencies { testImplementation(libraries.commonsIo) testImplementation(libraries.owaspEsapi) testImplementation(libraries.apacheHttpClient) + testImplementation(libraries.openSamlApi) + testImplementation(libraries.xmlUnit) + testImplementation(libraries.awaitility) } ext { @@ -158,7 +160,15 @@ generateDocs { dependsOn(slate) } +//task declarations +tasks.register('killUaa', Exec) { + workingDir '../' + executable = 'scripts/kill_uaa.sh' +} + integrationTest { + dependsOn killUaa + filter { includeTestsMatching("org.cloudfoundry.identity.uaa.integration.*") includeTestsMatching("*IT") diff --git a/uaa/src/main/java/org/cloudfoundry/identity/uaa/TracingAutoConfiguration.java b/uaa/src/main/java/org/cloudfoundry/identity/uaa/TracingAutoConfiguration.java index dde28060079..edcf1120dbf 100644 --- a/uaa/src/main/java/org/cloudfoundry/identity/uaa/TracingAutoConfiguration.java +++ b/uaa/src/main/java/org/cloudfoundry/identity/uaa/TracingAutoConfiguration.java @@ -1,17 +1,5 @@ package org.cloudfoundry.identity.uaa; -import javax.servlet.Filter; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; - -import brave.CurrentSpanCustomizer; -import brave.SpanCustomizer; import brave.Tracing; import brave.context.slf4j.MDCScopeDecorator; import brave.http.HttpTracing; @@ -20,48 +8,71 @@ import brave.propagation.ThreadLocalCurrentTraceContext; import brave.servlet.TracingFilter; import brave.spring.webmvc.SpanCustomizingAsyncHandlerInterceptor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import javax.servlet.Filter; -/** This adds tracing configuration to any web mvc controllers or rest template clients. */ +/** + * This adds tracing configuration to any web mvc controllers or rest template clients. + */ @Configuration // Importing a class is effectively the same as declaring bean methods @Import(SpanCustomizingAsyncHandlerInterceptor.class) public class TracingAutoConfiguration { - /** Allows log patterns to use {@code %{traceId}} {@code %{spanId}} and {@code %{userName}} */ - @Bean ScopeDecorator correlationScopeDecorator() { - return MDCScopeDecorator.newBuilder() - .build(); - } + /** + * Allows log patterns to use {@code %{traceId}} {@code %{spanId}} and {@code %{userName}} + */ + @Bean + ScopeDecorator correlationScopeDecorator() { + return MDCScopeDecorator.newBuilder() + .build(); + } - /** Propagates trace context between threads. */ - @Bean CurrentTraceContext currentTraceContext(ScopeDecorator correlationScopeDecorator) { - return ThreadLocalCurrentTraceContext.newBuilder() - .addScopeDecorator(correlationScopeDecorator) - .build(); - } + /** + * Propagates trace context between threads. + */ + @Bean + CurrentTraceContext currentTraceContext(ScopeDecorator correlationScopeDecorator) { + return ThreadLocalCurrentTraceContext.newBuilder() + .addScopeDecorator(correlationScopeDecorator) + .build(); + } - /** Controls aspects of tracing such as the service name that shows up in the UI */ - @Bean Tracing tracing( - @Value("${brave.localServiceName:uaa}") String serviceName, - @Value("${brave.supportsJoin:true}") boolean supportsJoin, - @Value("${brave.traceId128Bit:false}") boolean traceId128Bit, - CurrentTraceContext currentTraceContext) { - return Tracing.newBuilder() - .localServiceName(serviceName) - .supportsJoin(supportsJoin) - .traceId128Bit(traceId128Bit) - .currentTraceContext(currentTraceContext) - .build(); - } + /** + * Controls aspects of tracing such as the service name that shows up in the UI + */ + @Bean + Tracing tracing( + @Value("${brave.localServiceName:uaa}") String serviceName, + @Value("${brave.supportsJoin:true}") boolean supportsJoin, + @Value("${brave.traceId128Bit:false}") boolean traceId128Bit, + CurrentTraceContext currentTraceContext) { + return Tracing.newBuilder() + .localServiceName(serviceName) + .supportsJoin(supportsJoin) + .traceId128Bit(traceId128Bit) + .currentTraceContext(currentTraceContext) + .build(); + } - /** Decides how to name and tag spans. By default they are named the same as the http method. */ - @Bean HttpTracing httpTracing(Tracing tracing) { - return HttpTracing.create(tracing); - } + /** + * Decides how to name and tag spans. By default they are named the same as the http method. + */ + @Bean + HttpTracing httpTracing(Tracing tracing) { + return HttpTracing.create(tracing); + } - /** Creates server spans for HTTP requests */ - @Bean Filter tracingFilter(HttpTracing httpTracing) { - return TracingFilter.create(httpTracing); - } + /** + * Creates server spans for HTTP requests + */ + @Bean + Filter tracingFilter(HttpTracing httpTracing) { + return TracingFilter.create(httpTracing); + } } diff --git a/uaa/src/main/resources/uaa.yml b/uaa/src/main/resources/uaa.yml index 3903ecc63a2..e619a139e0c 100755 --- a/uaa/src/main/resources/uaa.yml +++ b/uaa/src/main/resources/uaa.yml @@ -395,26 +395,30 @@ login: # RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= # -----END CERTIFICATE----- - # SAML - The entity base url is the location of this application - # (The host and port of the application that will accept assertions) + # The entity base url is the location of this application + # (No longer used for SAML SP metadata generation) entityBaseURL: http://localhost:8080/uaa - # The entityID of this SP + # The entityID of this SP (SAML SP metadata will declare this as "entityID"); SAML Authn Request will use this as the "Issuer" of the request entityID: cloudfoundry-saml-login saml: - #Entity ID Alias to login at /saml/SSO/alias/{login.saml.entityIDAlias} + # Entity ID Alias to login at /saml/SSO/alias/{login.saml.entityIDAlias}; + # both SAML SP metadata and SAML Authn Request will include this as part of various SAML URLs (such as the AssertionConsumerService URL); + # if not set, UAA will fall back to login.entityID #entityIDAlias: cloudfoundry-saml-login - #Default nameID if IDP nameID is not set + # Default nameID if IDP nameID is not set nameID: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified' - #Default assertionConsumerIndex if IDP value is not set + # Default assertionConsumerIndex if IDP value is not set assertionConsumerIndex: 0 - #Local/SP metadata - sign metadata + # Local/SP metadata - sign metadata signMetaData: true - #Local/SP metadata - requests signed + # Local/SP metadata - requests signed signRequest: true - #Local/SP metadata - want incoming assertions signed + # Local/SP metadata - want incoming assertions signed wantAssertionSigned: true - #Algorithm for SAML signatures. Defaults to SHA1. Accepts SHA1, SHA256, SHA512 + # Algorithm for SAML signatures. Defaults to SHA256. Accepts SHA1, SHA256, SHA512 signatureAlgorithm: SHA256 + # If true, do not validate the InResponseToField part of an incoming IDP assertion (default: false) + disableInResponseToCheck: false socket: # URL metadata fetch - pool timeout connectionManagerTimeout: 10000 diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index bfadb4f3a3a..9ca11fd9956 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -11,7 +11,7 @@ http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> - + @@ -62,6 +62,7 @@ + @@ -185,6 +186,7 @@ uiSecurity + secFilterOpen06SAMLMetadata @@ -211,23 +213,31 @@ - - - - + + key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(9)}"/> + + - + + - @@ -490,6 +499,8 @@ @config['login']['saml']==null ? null : @config['login']['saml']['keys']}"/> + + @@ -524,5 +535,4 @@ @config['uaa']['limitedFunctionality']['whitelist']==null ? null : @config['uaa']['limitedFunctionality']['whitelist']['methods']}"/> - diff --git a/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml index de9f3e1346a..f21c6b2f38a 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml @@ -7,21 +7,6 @@ http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> - - - - - - - - - - - + authorization-request-manager-ref="authorizationRequestManager" + request-validator-ref="oauth2RequestValidator"> - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -246,29 +219,8 @@ - - - - - - - - - - - - - - - - - - - - - + @@ -299,7 +251,6 @@ - @@ -336,7 +287,6 @@ - @@ -444,20 +394,19 @@ - - + - + - + - + @@ -698,4 +647,4 @@ - \ No newline at end of file + diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml deleted file mode 100644 index 83232f4ce57..00000000000 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml +++ /dev/null @@ -1,319 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/uaa/src/main/webapp/WEB-INF/web.xml b/uaa/src/main/webapp/WEB-INF/web.xml index 8f0724dc22b..cc1954fec53 100755 --- a/uaa/src/main/webapp/WEB-INF/web.xml +++ b/uaa/src/main/webapp/WEB-INF/web.xml @@ -16,6 +16,12 @@ + + + + + + springSessionRepositoryFilter org.springframework.web.filter.DelegatingFilterProxy @@ -94,6 +100,13 @@ /* + + + + + + + spring org.springframework.web.servlet.DispatcherServlet diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/AuthorizationCodeGrantIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/AuthorizationCodeGrantIntegrationTests.java index cb0e5414169..a61cf18252b 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/AuthorizationCodeGrantIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/AuthorizationCodeGrantIntegrationTests.java @@ -33,11 +33,11 @@ import java.net.URI; import java.util.Map; +import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.hamcrest.CoreMatchers.containsString; /** * @author Dave Syer @@ -61,97 +61,96 @@ public void testSuccessfulAuthorizationCodeFlow() throws Exception { @Test public void testSuccessfulAuthorizationCodeFlowWithPkceS256() throws Exception { - testAuthorizationCodeFlowWithPkce_Internal(UaaTestAccounts.CODE_CHALLENGE, - UaaTestAccounts.CODE_CHALLENGE_METHOD_S256, UaaTestAccounts.CODE_VERIFIER); - testAuthorizationCodeFlowWithPkce_Internal(UaaTestAccounts.CODE_CHALLENGE, - UaaTestAccounts.CODE_CHALLENGE_METHOD_S256, UaaTestAccounts.CODE_VERIFIER); + testAuthorizationCodeFlowWithPkce_Internal(UaaTestAccounts.CODE_CHALLENGE, + UaaTestAccounts.CODE_CHALLENGE_METHOD_S256, UaaTestAccounts.CODE_VERIFIER); + testAuthorizationCodeFlowWithPkce_Internal(UaaTestAccounts.CODE_CHALLENGE, + UaaTestAccounts.CODE_CHALLENGE_METHOD_S256, UaaTestAccounts.CODE_VERIFIER); } - + @Test public void testSuccessfulAuthorizationCodeFlowWithPkcePlain() throws Exception { testAuthorizationCodeFlowWithPkce_Internal(UaaTestAccounts.CODE_CHALLENGE, "plain", UaaTestAccounts.CODE_CHALLENGE); testAuthorizationCodeFlowWithPkce_Internal(UaaTestAccounts.CODE_CHALLENGE, "plain", UaaTestAccounts.CODE_CHALLENGE); } - + @Test public void testPkcePlainWithWrongCodeVerifier() throws Exception { ResponseEntity tokenResponse = doAuthorizeAndTokenRequest(UaaTestAccounts.CODE_CHALLENGE, "plain", UaaTestAccounts.CODE_VERIFIER); assertEquals(HttpStatus.BAD_REQUEST, tokenResponse.getStatusCode()); - Map body = tokenResponse.getBody(); + Map body = tokenResponse.getBody(); assertThat(body.get("error"), containsString("invalid_grant")); assertThat(body.get("error_description"), containsString("Invalid code verifier")); } - + @Test public void testPkceS256WithWrongCodeVerifier() throws Exception { ResponseEntity tokenResponse = doAuthorizeAndTokenRequest(UaaTestAccounts.CODE_CHALLENGE, UaaTestAccounts.CODE_CHALLENGE_METHOD_S256, UaaTestAccounts.CODE_CHALLENGE); assertEquals(HttpStatus.BAD_REQUEST, tokenResponse.getStatusCode()); - Map body = tokenResponse.getBody(); + Map body = tokenResponse.getBody(); assertThat(body.get("error"), containsString("invalid_grant")); assertThat(body.get("error_description"), containsString("Invalid code verifier")); } - + @Test public void testMissingCodeChallenge() throws Exception { - ResponseEntity tokenResponse = doAuthorizeAndTokenRequest("", UaaTestAccounts.CODE_CHALLENGE_METHOD_S256, UaaTestAccounts.CODE_VERIFIER); + ResponseEntity tokenResponse = doAuthorizeAndTokenRequest("", UaaTestAccounts.CODE_CHALLENGE_METHOD_S256, UaaTestAccounts.CODE_VERIFIER); assertEquals(HttpStatus.BAD_REQUEST, tokenResponse.getStatusCode()); - Map body = tokenResponse.getBody(); + Map body = tokenResponse.getBody(); assertThat(body.get("error"), containsString("invalid_grant")); assertThat(body.get("error_description"), containsString("PKCE error: Code verifier not required for this authorization code.")); } - + @Test public void testMissingCodeVerifier() throws Exception { - ResponseEntity tokenResponse = doAuthorizeAndTokenRequest(UaaTestAccounts.CODE_CHALLENGE, UaaTestAccounts.CODE_CHALLENGE_METHOD_S256, ""); + ResponseEntity tokenResponse = doAuthorizeAndTokenRequest(UaaTestAccounts.CODE_CHALLENGE, UaaTestAccounts.CODE_CHALLENGE_METHOD_S256, ""); assertEquals(HttpStatus.BAD_REQUEST, tokenResponse.getStatusCode()); - Map body = tokenResponse.getBody(); + Map body = tokenResponse.getBody(); assertThat(body.get("error"), containsString("invalid_grant")); assertThat(body.get("error_description"), containsString("PKCE error: Code verifier must be provided for this authorization code.")); } - + @Test public void testInvalidCodeChallenge() throws Exception { - AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); - String responseLocation = IntegrationTestUtils.getAuthorizationResponse(serverRunning, - resource.getClientId(), - testAccounts.getUserName(), - testAccounts.getPassword(), - resource.getPreEstablishedRedirectUri(), - "ShortCodeChallenge", - UaaTestAccounts.CODE_CHALLENGE_METHOD_S256); - assertThat(responseLocation, containsString("Code challenge length must between 43 and 128 and use only [A-Z],[a-z],[0-9],_,.,-,~ characters.")); + AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); + String responseLocation = IntegrationTestUtils.getAuthorizationResponse(serverRunning, + resource.getClientId(), + testAccounts.getUserName(), + testAccounts.getPassword(), + resource.getPreEstablishedRedirectUri(), + "ShortCodeChallenge", + UaaTestAccounts.CODE_CHALLENGE_METHOD_S256); + assertThat(responseLocation, containsString("Code challenge length must between 43 and 128 and use only [A-Z],[a-z],[0-9],_,.,-,~ characters.")); } - + @Test public void testInvalidCodeVerifier() throws Exception { - AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); - ResponseEntity tokenResponse = IntegrationTestUtils.getTokens(serverRunning, - testAccounts, - resource.getClientId(), - resource.getClientSecret(), - resource.getPreEstablishedRedirectUri(), - "invalidCodeVerifier", - "authorizationCode"); - assertEquals(HttpStatus.BAD_REQUEST, tokenResponse.getStatusCode()); - Map body = tokenResponse.getBody(); - assertThat(body.get("error"), containsString("invalid_request")); + AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); + ResponseEntity tokenResponse = IntegrationTestUtils.getTokens(serverRunning, + resource.getClientId(), + resource.getClientSecret(), + resource.getPreEstablishedRedirectUri(), + "invalidCodeVerifier", + "authorizationCode"); + assertEquals(HttpStatus.BAD_REQUEST, tokenResponse.getStatusCode()); + Map body = tokenResponse.getBody(); + assertThat(body.get("error"), containsString("invalid_request")); assertThat(body.get("error_description"), containsString("Code verifier length must")); } - + @Test public void testUnsupportedCodeChallengeMethod() throws Exception { - AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); - String responseLocation = IntegrationTestUtils.getAuthorizationResponse(serverRunning, - resource.getClientId(), - testAccounts.getUserName(), - testAccounts.getPassword(), - resource.getPreEstablishedRedirectUri(), - UaaTestAccounts.CODE_CHALLENGE, - "UnsupportedCodeChallengeMethod"); - assertThat(responseLocation, containsString("Unsupported code challenge method.")); + AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); + String responseLocation = IntegrationTestUtils.getAuthorizationResponse(serverRunning, + resource.getClientId(), + testAccounts.getUserName(), + testAccounts.getPassword(), + resource.getPreEstablishedRedirectUri(), + UaaTestAccounts.CODE_CHALLENGE, + "UnsupportedCodeChallengeMethod"); + assertThat(responseLocation, containsString("Unsupported code challenge method.")); } - - @Test + + @Test public void testZoneDoesNotExist() { ServerRunning.UriBuilder builder = serverRunning.buildUri(serverRunning.getAuthorizationUri().replace("localhost", "testzonedoesnotexist.localhost")) .queryParam("response_type", "code") @@ -198,76 +197,73 @@ public void testZoneInactive() { @Test public void testAuthorizationRequestWithoutRedirectUri() { - Map body = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, - testAccounts, - "login", - "loginsecret", - testAccounts.getUserName(), - testAccounts.getPassword(), - null, - null, - null, - null, - false); - - assertNotNull("Token not received", body.get("access_token")); - - try { - IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, testAccounts, "app", "appclientsecret", - testAccounts.getUserName(), testAccounts.getPassword(), - null, null, null, null, false); - } catch (AssertionError error) { - // expected - return; - } - Assert.fail("Token retrival not allowed"); + Map body = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, + "login", + "loginsecret", + testAccounts.getUserName(), + testAccounts.getPassword(), + null, + null, + null, + null, + false); + + assertNotNull("Token not received", body.get("access_token")); + + try { + IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, "app", "appclientsecret", + testAccounts.getUserName(), testAccounts.getPassword(), + null, null, null, null, false); + } catch (AssertionError error) { + // expected + return; + } + Assert.fail("Token retrival not allowed"); } public void testSuccessfulAuthorizationCodeFlow_Internal() { AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); Map body = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, - testAccounts, - resource.getClientId(), - resource.getClientSecret(), - testAccounts.getUserName(), - testAccounts.getPassword()); + testAccounts, + resource.getClientId(), + resource.getClientSecret(), + testAccounts.getUserName(), + testAccounts.getPassword()); Jwt token = JwtHelper.decode(body.get("access_token")); assertTrue("Wrong claims: " + token.getClaims(), token.getClaims().contains("\"aud\"")); assertTrue("Wrong claims: " + token.getClaims(), token.getClaims().contains("\"user_id\"")); } - + private void testAuthorizationCodeFlowWithPkce_Internal(String codeChallenge, String codeChallengeMethod, String codeVerifier) throws Exception { - + ResponseEntity tokenResponse = doAuthorizeAndTokenRequest(codeChallenge, codeChallengeMethod, codeVerifier); assertEquals(HttpStatus.OK, tokenResponse.getStatusCode()); - Map body = tokenResponse.getBody(); + Map body = tokenResponse.getBody(); Jwt token = JwtHelper.decode(body.get("access_token")); assertTrue("Wrong claims: " + token.getClaims(), token.getClaims().contains("\"aud\"")); assertTrue("Wrong claims: " + token.getClaims(), token.getClaims().contains("\"user_id\"")); IntegrationTestUtils.callCheckToken(serverRunning, - testAccounts, - body.get("access_token"), - testAccounts.getDefaultAuthorizationCodeResource().getClientId(), - testAccounts.getDefaultAuthorizationCodeResource().getClientSecret()); + body.get("access_token"), + testAccounts.getDefaultAuthorizationCodeResource().getClientId(), + testAccounts.getDefaultAuthorizationCodeResource().getClientSecret()); } - + private ResponseEntity doAuthorizeAndTokenRequest(String codeChallenge, String codeChallengeMethod, String codeVerifier) throws Exception { - AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); - String authorizationResponse = IntegrationTestUtils.getAuthorizationResponse(serverRunning, - resource.getClientId(), - testAccounts.getUserName(), - testAccounts.getPassword(), - resource.getPreEstablishedRedirectUri(), - codeChallenge, - codeChallengeMethod); - String authorizationCode = authorizationResponse.split("code=")[1].split("&")[0]; + AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); + String authorizationResponse = IntegrationTestUtils.getAuthorizationResponse(serverRunning, + resource.getClientId(), + testAccounts.getUserName(), + testAccounts.getPassword(), + resource.getPreEstablishedRedirectUri(), + codeChallenge, + codeChallengeMethod); + String authorizationCode = authorizationResponse.split("code=")[1].split("&")[0]; return IntegrationTestUtils.getTokens(serverRunning, - testAccounts, - resource.getClientId(), - resource.getClientSecret(), - resource.getPreEstablishedRedirectUri(), - codeVerifier, - authorizationCode); - } + resource.getClientId(), + resource.getClientSecret(), + resource.getPreEstablishedRedirectUri(), + codeVerifier, + authorizationCode); + } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java index e5749fb2f23..749d353d08d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java @@ -28,6 +28,7 @@ import org.cloudfoundry.identity.uaa.oauth.common.OAuth2AccessToken; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidClientException; import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; +import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetails; import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; @@ -36,11 +37,10 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -48,33 +48,23 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.crypto.codec.Base64; -import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetails; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.doesSupportZoneDNS; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; /** * @author Dave Syer @@ -83,12 +73,14 @@ public class ClientAdminEndpointsIntegrationTests { public static final String SECRET_TOO_LONG = "adfdfdasgdasgasdgafsgasfgfasgfadsgfagsagasddsafdsafsdfdafsdafdsfasdffasfasdfasdfdsfds" + - "ewrewrewqrweqrewqrewqrewerwqqweewqrdsadsfewqrewqrtewrewrewrewrererererererererererdfadsafasfdasfsdaf" + - "dsfasdfdsagfdsao43o4p43adfsfasdvcdasfmdsafzxcvaddsaaddfsafdsafdsfdsdfsfdsfdsasdfadfsadfsasadfsdfadfs"; + "ewrewrewqrweqrewqrewqrewerwqqweewqrdsadsfewqrewqrtewrewrewrewrererererererererererdfadsafasfdasfsdaf" + + "dsfasdfdsagfdsao43o4p43adfsfasdvcdasfmdsafzxcvaddsaaddfsafdsafdsfdsdfsfdsfdsasdfadfsadfsasadfsdfadfs"; + private static final Base64.Encoder ENCODER = Base64.getEncoder(); + @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); - private UaaTestAccounts testAccounts = UaaTestAccounts.standard(serverRunning); + private final UaaTestAccounts testAccounts = UaaTestAccounts.standard(serverRunning); @Rule public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts); @@ -97,70 +89,69 @@ public class ClientAdminEndpointsIntegrationTests { private HttpHeaders headers; private List clientDetailsModifications; - @Before - public void setUp() throws Exception { + @BeforeEach + public void setUp() { token = getClientCredentialsAccessToken("clients.read,clients.write,clients.admin"); headers = getAuthenticatedHeaders(token); } @Test - public void testGetClient() throws Exception { - HttpHeaders headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read")); - ResponseEntity result = serverRunning.getForString("/oauth/clients/cf", headers); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertTrue(result.getBody().contains("cf")); + void testGetClient() { + HttpHeaders myHeaders = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read")); + ResponseEntity result = serverRunning.getForString("/oauth/clients/cf", myHeaders); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(result.getBody()).contains("cf"); } @Test - public void testListClients() throws Exception { - HttpHeaders headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read")); - ResponseEntity result = serverRunning.getForString("/oauth/clients", headers); - assertEquals(HttpStatus.OK, result.getStatusCode()); - // System.err.println(result.getBody()); - assertTrue(result.getBody().contains("\"client_id\":\"cf\"")); - assertFalse(result.getBody().contains("secret\":")); + void testListClients() { + HttpHeaders myHeaders = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read")); + ResponseEntity result = serverRunning.getForString("/oauth/clients", myHeaders); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(result.getBody()).contains("\"client_id\":\"cf\"") + .doesNotContain("secret\":"); } - @Before - public void setupClients() { + @BeforeEach + void setupClients() { clientDetailsModifications = new ArrayList<>(); } - @After - public void teardownClients() { + @AfterEach + void teardownClients() { for (ClientDetailsModification clientDetailsModification : clientDetailsModifications) { serverRunning.getRestTemplate() - .exchange(serverRunning.getUrl("/oauth/clients/{client}"), HttpMethod.DELETE, - new HttpEntity(clientDetailsModification, headers), Void.class, - clientDetailsModification.getClientId()); + .exchange(serverRunning.getUrl("/oauth/clients/{client}"), HttpMethod.DELETE, + new HttpEntity(clientDetailsModification, headers), Void.class, + clientDetailsModification.getClientId()); } } @Test - public void testListClientsWithExtremePagination_defaultsTo500() throws Exception { + void testListClientsWithExtremePagination_defaultsTo500() throws Exception { for (int i = 0; i < 502; i++) { clientDetailsModifications.add(createClient("client_credentials")); } - HttpHeaders headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read")); - ResponseEntity result = serverRunning.getForString("/oauth/clients?count=3000", headers); - assertEquals(HttpStatus.OK, result.getStatusCode()); + HttpHeaders myHeaders = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read")); + ResponseEntity result = serverRunning.getForString("/oauth/clients?count=3000", myHeaders); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); SearchResults searchResults = new ObjectMapper().readValue(result.getBody(), SearchResults.class); - assertThat(searchResults.getItemsPerPage(), is(500)); - assertThat((List) searchResults.getResources(), hasSize(500)); - assertThat(searchResults.getTotalResults(), greaterThan(500)); + assertThat(searchResults.getItemsPerPage()).isEqualTo(500); + assertThat(searchResults.getResources()).hasSize(500); + assertThat(searchResults.getTotalResults()).isGreaterThan(500); } @Test - public void testCreateClient() throws Exception { + void testCreateClient() { createClient("client_credentials"); } @Test - public void createClientWithSecondarySecret() { - OAuth2AccessToken token = getClientCredentialsAccessToken("clients.read,clients.write"); - HttpHeaders headers = getAuthenticatedHeaders(token); + void createClientWithSecondarySecret() { + OAuth2AccessToken myToken = getClientCredentialsAccessToken("clients.read,clients.write"); + HttpHeaders myHeaders = getAuthenticatedHeaders(myToken); var client = new ClientDetailsCreation(); client.setClientId(new RandomValueStringGenerator().generate()); client.setClientSecret("primarySecret"); @@ -169,14 +160,14 @@ public void createClientWithSecondarySecret() { ResponseEntity result = serverRunning.getRestTemplate() .exchange(serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, - new HttpEntity<>(client, headers), Void.class); - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + new HttpEntity<>(client, myHeaders), Void.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); } @Test - public void createClientWithEmptySecret() { - OAuth2AccessToken token = getClientCredentialsAccessToken("clients.admin"); - HttpHeaders headers = getAuthenticatedHeaders(token); + void createClientWithEmptySecret() { + OAuth2AccessToken myToken = getClientCredentialsAccessToken("clients.admin"); + HttpHeaders myHeaders = getAuthenticatedHeaders(myToken); var client = new ClientDetailsCreation(); client.setClientId(new RandomValueStringGenerator().generate()); client.setClientSecret(UaaStringUtils.EMPTY_STRING); @@ -184,17 +175,17 @@ public void createClientWithEmptySecret() { ResponseEntity result = serverRunning.getRestTemplate() .exchange(serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, - new HttpEntity<>(client, headers), Void.class); - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + new HttpEntity<>(client, myHeaders), Void.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); } @Test - public void testCreateClients() throws Exception { + void testCreateClients() { doCreateClients(); } @Test - public void testCreateClientWithValidLongRedirectUris() { + void testCreateClientWithValidLongRedirectUris() { // redirectUri shorter than the database column size HashSet uris = new HashSet<>(); for (int i = 0; i < 666; ++i) { @@ -203,11 +194,11 @@ public void testCreateClientWithValidLongRedirectUris() { UaaClientDetails client = createClientWithSecretAndRedirectUri("secret", uris, "client_credentials"); ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients"), - HttpMethod.POST, new HttpEntity(client, headers), Void.class); - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + HttpMethod.POST, new HttpEntity<>(client, headers), Void.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); } - public ClientDetailsModification[] doCreateClients() throws Exception { + public ClientDetailsModification[] doCreateClients() { headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin,clients.read,clients.write,clients.secret")); headers.add("Accept", "application/json"); RandomValueStringGenerator gen = new RandomValueStringGenerator(); @@ -227,22 +218,22 @@ public ClientDetailsModification[] doCreateClients() throws Exception { clients[i].setRegisteredRedirectUri(Collections.singleton("http://redirect.url")); } ResponseEntity result = - serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/tx"), - HttpMethod.POST, - new HttpEntity(clients, headers), - ClientDetailsModification[].class); - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + serverRunning.getRestTemplate().exchange( + serverRunning.getUrl("/oauth/clients/tx"), + HttpMethod.POST, + new HttpEntity<>(clients, headers), + ClientDetailsModification[].class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); validateClients(clients, result.getBody()); for (String id : ids) { ClientDetails client = getClient(id); - assertNotNull(client); + assertThat(client).isNotNull(); } return result.getBody(); } @Test - public void createClientWithCommaDelimitedScopesValidatesAllTheScopes() throws Exception { + void createClientWithCommaDelimitedScopesValidatesAllTheScopes() { // log in as admin OAuth2AccessToken adminToken = getClientCredentialsAccessToken(""); HttpHeaders adminHeaders = getAuthenticatedHeaders(adminToken); @@ -258,15 +249,15 @@ public void createClientWithCommaDelimitedScopesValidatesAllTheScopes() throws E ); clientCreator.setClientSecret("secret"); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, - new HttpEntity<>(clientCreator, adminHeaders), UaaException.class); + serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, + new HttpEntity<>(clientCreator, adminHeaders), UaaException.class); // ensure success - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); // log in as new client - OAuth2AccessToken token = getClientCredentialsAccessToken(clientCreator.getClientId(), clientCreator.getClientSecret(), ""); - HttpHeaders headers = getAuthenticatedHeaders(token); + OAuth2AccessToken myToken = getClientCredentialsAccessToken(clientCreator.getClientId(), clientCreator.getClientSecret(), ""); + HttpHeaders myHeaders = getAuthenticatedHeaders(myToken); // make client with restricted scopes UaaClientDetails invalidClient = new UaaClientDetails( @@ -279,47 +270,46 @@ public void createClientWithCommaDelimitedScopesValidatesAllTheScopes() throws E invalidClient.setClientSecret("secret"); ResponseEntity invalidClientRequest = serverRunning.getRestTemplate().exchange( serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, - new HttpEntity<>(invalidClient, headers), InvalidClientException.class); + new HttpEntity<>(invalidClient, myHeaders), InvalidClientException.class); // ensure correct failure - assertEquals(HttpStatus.BAD_REQUEST, invalidClientRequest.getStatusCode()); - assertEquals("invalid_client", invalidClientRequest.getBody().getOAuth2ErrorCode()); - assertTrue("Error message is unexpected", invalidClientRequest.getBody().getMessage().startsWith("uaa.admin is not an allowed scope for caller")); + assertThat(invalidClientRequest.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(invalidClientRequest.getBody().getOAuth2ErrorCode()).isEqualTo("invalid_client"); + assertThat(invalidClientRequest.getBody().getMessage()).as("Error message is unexpected").startsWith("uaa.admin is not an allowed scope for caller"); } @Test - public void createClientWithoutSecretIsRejected() throws Exception { - OAuth2AccessToken token = getClientCredentialsAccessToken("clients.read,clients.write"); - HttpHeaders headers = getAuthenticatedHeaders(token); + void createClientWithoutSecretIsRejected() { + OAuth2AccessToken myToken = getClientCredentialsAccessToken("clients.read,clients.write"); + HttpHeaders myHeaders = getAuthenticatedHeaders(myToken); UaaClientDetails invalidSecretClient = new UaaClientDetails(new RandomValueStringGenerator().generate(), "", "foo,bar", - "client_credentials", "uaa.none"); + "client_credentials", "uaa.none"); invalidSecretClient.setClientSecret("tooLongSecret"); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, - new HttpEntity(invalidSecretClient, headers), InvalidClientException.class); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); - assertEquals("invalid_client", result.getBody().getOAuth2ErrorCode()); + serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, + new HttpEntity<>(invalidSecretClient, myHeaders), InvalidClientException.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(result.getBody().getOAuth2ErrorCode()).isEqualTo("invalid_client"); } - @Test - public void createClientWithTooLongSecretIsRejected() throws Exception { - OAuth2AccessToken token = getClientCredentialsAccessToken("clients.read,clients.write"); - HttpHeaders headers = getAuthenticatedHeaders(token); + void createClientWithTooLongSecretIsRejected() { + OAuth2AccessToken myToken = getClientCredentialsAccessToken("clients.read,clients.write"); + HttpHeaders myHeaders = getAuthenticatedHeaders(myToken); UaaClientDetails client = new UaaClientDetails(new RandomValueStringGenerator().generate(), "", "foo,bar", - "client_credentials", "uaa.none"); + "client_credentials", "uaa.none"); client.setClientSecret(SECRET_TOO_LONG); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, - new HttpEntity(client, headers), InvalidClientException.class); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); - assertEquals("invalid_client", result.getBody().getOAuth2ErrorCode()); + serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, + new HttpEntity<>(client, myHeaders), InvalidClientException.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(result.getBody().getOAuth2ErrorCode()).isEqualTo("invalid_client"); } @Test - public void createClientWithTooLongSecondarySecretIsRejected() throws Exception { - OAuth2AccessToken token = getClientCredentialsAccessToken("clients.read,clients.write"); - HttpHeaders headers = getAuthenticatedHeaders(token); + void createClientWithTooLongSecondarySecretIsRejected() { + OAuth2AccessToken myToken = getClientCredentialsAccessToken("clients.read,clients.write"); + HttpHeaders myHeaders = getAuthenticatedHeaders(myToken); var client = new ClientDetailsCreation(); client.setClientId(new RandomValueStringGenerator().generate()); client.setClientSecret("primarySecret"); @@ -328,22 +318,22 @@ public void createClientWithTooLongSecondarySecretIsRejected() throws Exception ResponseEntity result = serverRunning.getRestTemplate().exchange( serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, - new HttpEntity<>(client, headers), InvalidClientException.class); + new HttpEntity<>(client, myHeaders), InvalidClientException.class); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); - assertEquals("invalid_client", result.getBody().getOAuth2ErrorCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(result.getBody().getOAuth2ErrorCode()).isEqualTo("invalid_client"); } @Test - public void createClientWithStrictSecretPolicyTest() throws Exception { - assertTrue("Expected testzone1.localhost and testzone2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + void createClientWithStrictSecretPolicyTest() { + assertThat(doesSupportZoneDNS()).as("Expected testzone1.localhost and testzone2.localhost to resolve to 127.0.0.1").isTrue(); String testZoneId = "testzone1"; - RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(serverRunning.getBaseUrl(), new String[0], "admin", "adminsecret")); + IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(serverRunning.getBaseUrl(), new String[0], "admin", "adminsecret")); RestTemplate identityClient = IntegrationTestUtils - .getClientCredentialsTemplate(IntegrationTestUtils.getClientCredentialsResource(serverRunning.getBaseUrl(), - new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret")); + .getClientCredentialsTemplate(IntegrationTestUtils.getClientCredentialsResource(serverRunning.getBaseUrl(), + new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret")); IdentityZoneConfiguration config = new IdentityZoneConfiguration(); //min length 5, max length 12, requires 1 uppercase lowercase digit and specialChar, expries 6 months. @@ -352,27 +342,27 @@ public void createClientWithStrictSecretPolicyTest() throws Exception { UaaClientDetails client = new UaaClientDetails(new RandomValueStringGenerator().generate(), "", "foo,bar", - "client_credentials", "uaa.none"); + "client_credentials", "uaa.none"); client.setClientSecret("Secret1@"); String zoneAdminToken = IntegrationTestUtils.getZoneAdminToken(serverRunning.getBaseUrl(), serverRunning, testZoneId); HttpHeaders xZoneHeaders = getAuthenticatedHeaders(zoneAdminToken); xZoneHeaders.add(IdentityZoneSwitchingFilter.HEADER, testZoneId); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getBaseUrl() + "/oauth/clients", HttpMethod.POST, - new HttpEntity(client, xZoneHeaders), UaaException.class); + serverRunning.getBaseUrl() + "/oauth/clients", HttpMethod.POST, + new HttpEntity<>(client, xZoneHeaders), UaaException.class); - Assert.assertEquals(HttpStatus.CREATED, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); //Negative Test UaaClientDetails failClient = new UaaClientDetails(new RandomValueStringGenerator().generate(), "", "foo,bar", - "client_credentials", "uaa.none"); + "client_credentials", "uaa.none"); failClient.setClientSecret("badsecret"); result = serverRunning.getRestTemplate().exchange( - serverRunning.getBaseUrl() + "/oauth/clients", HttpMethod.POST, - new HttpEntity(failClient, xZoneHeaders), UaaException.class); + serverRunning.getBaseUrl() + "/oauth/clients", HttpMethod.POST, + new HttpEntity<>(failClient, xZoneHeaders), UaaException.class); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); //cleanup config.setClientSecretPolicy(new ClientSecretPolicy(0, 255, 0, 0, 0, 0, 6)); @@ -380,27 +370,27 @@ public void createClientWithStrictSecretPolicyTest() throws Exception { } @Test - public void testClientSecretExpiryCannotBeSet() { - assertTrue("Expected testzone1.localhost and testzone2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + void testClientSecretExpiryCannotBeSet() { + assertThat(doesSupportZoneDNS()).as("Expected testzone1.localhost and testzone2.localhost to resolve to 127.0.0.1").isTrue(); String testZoneId = "testzone1"; - RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(serverRunning.getBaseUrl(), new String[0], "admin", "adminsecret")); + IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(serverRunning.getBaseUrl(), new String[0], "admin", "adminsecret")); RestTemplate identityClient = IntegrationTestUtils - .getClientCredentialsTemplate(IntegrationTestUtils.getClientCredentialsResource(serverRunning.getBaseUrl(), - new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret")); + .getClientCredentialsTemplate(IntegrationTestUtils.getClientCredentialsResource(serverRunning.getBaseUrl(), + new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret")); IdentityZoneConfiguration config = new IdentityZoneConfiguration(); //min length 5, max length 12, requires 1 uppercase lowercase digit and specialChar, expries 6 months. config.setClientSecretPolicy(new ClientSecretPolicy(5, 12, 1, 1, 1, 1, 6)); IdentityZone createdZone = IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, serverRunning.getBaseUrl(), testZoneId, testZoneId, config); - assertEquals(-1, createdZone.getConfig().getClientSecretPolicy().getExpireSecretInMonths()); + assertThat(createdZone.getConfig().getClientSecretPolicy().getExpireSecretInMonths()).isEqualTo(-1); config.setClientSecretPolicy(new ClientSecretPolicy(0, 255, 0, 0, 0, 0, 6)); IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, serverRunning.getBaseUrl(), testZoneId, testZoneId, config); } @Test - public void nonImplicitGrantClientWithoutSecretIsRejectedTxFails() throws Exception { + void nonImplicitGrantClientWithoutSecretIsRejectedTxFails() { headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin,clients.read,clients.write,clients.secret")); headers.add("Accept", "application/json"); String grantTypes = "client_credentials"; @@ -416,20 +406,20 @@ public void nonImplicitGrantClientWithoutSecretIsRejectedTxFails() throws Except } clients[clients.length - 1].setClientSecret(null); ResponseEntity result = - serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/tx"), - HttpMethod.POST, - new HttpEntity(clients, headers), - UaaException.class); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); + serverRunning.getRestTemplate().exchange( + serverRunning.getUrl("/oauth/clients/tx"), + HttpMethod.POST, + new HttpEntity<>(clients, headers), + UaaException.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); for (String id : ids) { ClientDetails client = getClient(id); - assertNull(client); + assertThat(client).isNull(); } } @Test - public void duplicateIdsIsRejectedTxFails() throws Exception { + void duplicateIdsIsRejectedTxFails() { headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin,clients.read,clients.write,clients.secret")); headers.add("Accept", "application/json"); String grantTypes = "client_credentials"; @@ -446,69 +436,69 @@ public void duplicateIdsIsRejectedTxFails() throws Exception { } clients[clients.length - 1].setClientId(ids[0]); ResponseEntity result = - serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/tx"), - HttpMethod.POST, - new HttpEntity(clients, headers), - UaaException.class); - assertEquals(HttpStatus.CONFLICT, result.getStatusCode()); + serverRunning.getRestTemplate().exchange( + serverRunning.getUrl("/oauth/clients/tx"), + HttpMethod.POST, + new HttpEntity<>(clients, headers), + UaaException.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CONFLICT); for (String id : ids) { ClientDetails client = getClient(id); - assertNull(client); + assertThat(client).isNull(); } } @Test - public void implicitAndAuthCodeGrantClient() { + void implicitAndAuthCodeGrantClient() { UaaClientDetails client = new UaaClientDetails(new RandomValueStringGenerator().generate(), "", "foo,bar", - "implicit,authorization_code", "uaa.none"); + "implicit,authorization_code", "uaa.none"); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, - new HttpEntity(client, headers), InvalidClientException.class); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); - assertEquals("invalid_client", result.getBody().getOAuth2ErrorCode()); + serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, + new HttpEntity<>(client, headers), InvalidClientException.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(result.getBody().getOAuth2ErrorCode()).isEqualTo("invalid_client"); } @Test - public void implicitGrantClientWithoutSecretIsOk() { + void implicitGrantClientWithoutSecretIsOk() { UaaClientDetails client = new UaaClientDetails(new RandomValueStringGenerator().generate(), "", "foo,bar", - "implicit", "uaa.none", "http://redirect.url"); + "implicit", "uaa.none", "http://redirect.url"); ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients"), - HttpMethod.POST, new HttpEntity(client, headers), Void.class); + HttpMethod.POST, new HttpEntity<>(client, headers), Void.class); - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); } @Test - public void passwordGrantClientWithoutSecretIsOk() { + void passwordGrantClientWithoutSecretIsOk() { UaaClientDetails client = new UaaClientDetails(new RandomValueStringGenerator().generate(), "", "foo,bar", - "password", "uaa.none", "http://redirect.url"); + "password", "uaa.none", "http://redirect.url"); ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients"), - HttpMethod.POST, new HttpEntity(client, headers), Void.class); + HttpMethod.POST, new HttpEntity<>(client, headers), Void.class); - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); } @Test - public void authzCodeGrantAutomaticallyAddsRefreshToken() throws Exception { + void authzCodeGrantAutomaticallyAddsRefreshToken() { UaaClientDetails client = createClient(GRANT_TYPE_AUTHORIZATION_CODE); ResponseEntity result = serverRunning.getForString("/oauth/clients/" + client.getClientId(), headers); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertTrue(result.getBody().contains("\"authorized_grant_types\":[\"authorization_code\",\"refresh_token\"]")); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(result.getBody()).contains("\"authorized_grant_types\":[\"authorization_code\",\"refresh_token\"]"); } @Test - public void passwordGrantAutomaticallyAddsRefreshToken() throws Exception { + void passwordGrantAutomaticallyAddsRefreshToken() { UaaClientDetails client = createClient("password"); ResponseEntity result = serverRunning.getForString("/oauth/clients/" + client.getClientId(), headers); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertTrue(result.getBody().contains("\"authorized_grant_types\":[\"password\",\"refresh_token\"]")); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(result.getBody()).contains("\"authorized_grant_types\":[\"password\",\"refresh_token\"]"); } @Test - public void testUpdateClient() throws Exception { + void testUpdateClient() { UaaClientDetails client = createClient("client_credentials"); client.setResourceIds(Collections.singleton("foo")); @@ -520,24 +510,23 @@ public void testUpdateClient() throws Exception { Collections.singletonList("rab"))); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/{client}"), - HttpMethod.PUT, new HttpEntity(client, headers), Void.class, - client.getClientId()); - assertEquals(HttpStatus.OK, result.getStatusCode()); + serverRunning.getUrl("/oauth/clients/{client}"), + HttpMethod.PUT, new HttpEntity<>(client, headers), Void.class, + client.getClientId()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); ResponseEntity response = serverRunning.getForString("/oauth/clients/" + client.getClientId(), headers); - assertEquals(HttpStatus.OK, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); String body = response.getBody(); - assertTrue(body.contains(client.getClientId())); - assertTrue(body.contains("some.crap")); - assertTrue(body.contains("refresh_token_validity\":120")); - assertTrue(body.contains("access_token_validity\":60")); - assertTrue("Wrong body: " + body, body.contains("\"foo\":[\"rab\"]")); - + assertThat(body).contains(client.getClientId()) + .contains("some.crap") + .contains("refresh_token_validity\":120") + .contains("access_token_validity\":60") + .as("Wrong body: " + body).contains("\"foo\":[\"rab\"]"); } @Test - public void testUpdateClients() throws Exception { + void testUpdateClients() { UaaClientDetails[] clients = doCreateClients(); headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin,clients.read,clients.write,clients.secret")); headers.add("Accept", "application/json"); @@ -547,63 +536,63 @@ public void testUpdateClients() throws Exception { c.setRefreshTokenValiditySeconds(120); } ResponseEntity result = - serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/tx"), - HttpMethod.PUT, - new HttpEntity(clients, headers), - UaaClientDetails[].class); - assertEquals(HttpStatus.OK, result.getStatusCode()); + serverRunning.getRestTemplate().exchange( + serverRunning.getUrl("/oauth/clients/tx"), + HttpMethod.PUT, + new HttpEntity<>(clients, headers), + UaaClientDetails[].class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); validateClients(clients, result.getBody()); for (UaaClientDetails c : clients) { ClientDetails client = getClient(c.getClientId()); - assertNotNull(client); - assertEquals((Integer) 120, client.getRefreshTokenValiditySeconds()); - assertEquals((Integer) 60, client.getAccessTokenValiditySeconds()); + assertThat(client).isNotNull(); + assertThat(client.getRefreshTokenValiditySeconds()).isEqualTo((Integer) 120); + assertThat(client.getAccessTokenValiditySeconds()).isEqualTo((Integer) 60); } } @Test - public void testDeleteClients() throws Exception { + void testDeleteClients() { UaaClientDetails[] clients = doCreateClients(); headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin,clients.read,clients.write,clients.secret,clients.admin")); headers.add("Accept", "application/json"); ResponseEntity result = - serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/tx/delete"), - HttpMethod.POST, - new HttpEntity<>(clients, headers), - UaaClientDetails[].class); - assertEquals(HttpStatus.OK, result.getStatusCode()); + serverRunning.getRestTemplate().exchange( + serverRunning.getUrl("/oauth/clients/tx/delete"), + HttpMethod.POST, + new HttpEntity<>(clients, headers), + UaaClientDetails[].class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); validateClients(clients, result.getBody()); for (UaaClientDetails c : clients) { ClientDetails client = getClient(c.getClientId()); - assertNull(client); + assertThat(client).isNull(); } } @Test - public void testDeleteClientsMissingId() throws Exception { + void testDeleteClientsMissingId() { UaaClientDetails[] clients = doCreateClients(); headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin,clients.read,clients.write,clients.secret,clients.admin")); headers.add("Accept", "application/json"); String oldId = clients[clients.length - 1].getClientId(); clients[clients.length - 1].setClientId("unknown.id"); ResponseEntity result = - serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/tx/delete"), - HttpMethod.POST, - new HttpEntity(clients, headers), - UaaClientDetails[].class); - assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); + serverRunning.getRestTemplate().exchange( + serverRunning.getUrl("/oauth/clients/tx/delete"), + HttpMethod.POST, + new HttpEntity<>(clients, headers), + UaaClientDetails[].class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); clients[clients.length - 1].setClientId(oldId); for (UaaClientDetails c : clients) { ClientDetails client = getClient(c.getClientId()); - assertNotNull(client); + assertThat(client).isNotNull(); } } @Test - public void testChangeSecret() throws Exception { + void testChangeSecret() { headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read,clients.write,clients.secret,uaa.admin")); UaaClientDetails client = createClient("client_credentials"); @@ -613,14 +602,14 @@ public void testChangeSecret() throws Exception { change.setOldSecret(client.getClientSecret()); change.setSecret("newsecret"); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/{client}/secret"), - HttpMethod.PUT, new HttpEntity(change, headers), Void.class, - client.getClientId()); - assertEquals(HttpStatus.OK, result.getStatusCode()); + serverRunning.getUrl("/oauth/clients/{client}/secret"), + HttpMethod.PUT, new HttpEntity<>(change, headers), Void.class, + client.getClientId()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); } @Test - public void testChangeJwtConfig() throws Exception { + void testChangeJwtConfig() { headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read,clients.write,clients.trust,uaa.admin")); UaaClientDetails client = createClient("client_credentials"); @@ -631,14 +620,14 @@ public void testChangeJwtConfig() throws Exception { def.setClientId("admin"); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/{client}/clientjwt"), - HttpMethod.PUT, new HttpEntity(def, headers), Void.class, - client.getClientId()); - assertEquals(HttpStatus.OK, result.getStatusCode()); + serverRunning.getUrl("/oauth/clients/{client}/clientjwt"), + HttpMethod.PUT, new HttpEntity<>(def, headers), Void.class, + client.getClientId()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); } @Test - public void testChangeJwtConfigNoAuthorization() throws Exception { + void testChangeJwtConfigNoAuthorization() { headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read,clients.write,clients.trust,uaa.admin")); UaaClientDetails client = createClient("client_credentials"); headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read,clients.write")); @@ -650,14 +639,14 @@ public void testChangeJwtConfigNoAuthorization() throws Exception { def.setClientId("admin"); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/{client}/clientjwt"), - HttpMethod.PUT, new HttpEntity(def, headers), Void.class, - client.getClientId()); - assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode()); + serverRunning.getUrl("/oauth/clients/{client}/clientjwt"), + HttpMethod.PUT, new HttpEntity<>(def, headers), Void.class, + client.getClientId()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); } @Test - public void testChangeJwtConfigInvalidTokenKey() throws Exception { + void testChangeJwtConfigInvalidTokenKey() { headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read,clients.write,clients.secret,uaa.admin")); UaaClientDetails client = createClient("client_credentials"); @@ -668,14 +657,14 @@ public void testChangeJwtConfigInvalidTokenKey() throws Exception { def.setClientId("admin"); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/{client}/clientjwt"), - HttpMethod.PUT, new HttpEntity(def, headers), Void.class, - client.getClientId()); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); + serverRunning.getUrl("/oauth/clients/{client}/clientjwt"), + HttpMethod.PUT, new HttpEntity<>(def, headers), Void.class, + client.getClientId()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); } @Test - public void testCreateClientsWithStrictSecretPolicy() throws Exception { + void testCreateClientsWithStrictSecretPolicy() { headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read,clients.write,clients.secret,uaa.admin")); UaaClientDetails client = createClient("client_credentials"); @@ -685,27 +674,27 @@ public void testCreateClientsWithStrictSecretPolicy() throws Exception { change.setOldSecret(client.getClientSecret()); change.setSecret(SECRET_TOO_LONG); ResponseEntity result = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/{client}/secret"), - HttpMethod.PUT, new HttpEntity(change, headers), Void.class, - client.getClientId()); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); + serverRunning.getUrl("/oauth/clients/{client}/secret"), + HttpMethod.PUT, new HttpEntity<>(change, headers), Void.class, + client.getClientId()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); } @Test - public void testDeleteClient() throws Exception { + void testDeleteClient() { UaaClientDetails client = createClient("client_credentials"); client.setResourceIds(Collections.singleton("foo")); ResponseEntity result = serverRunning.getRestTemplate() - .exchange(serverRunning.getUrl("/oauth/clients/{client}"), HttpMethod.DELETE, - new HttpEntity(client, headers), Void.class, - client.getClientId()); - assertEquals(HttpStatus.OK, result.getStatusCode()); + .exchange(serverRunning.getUrl("/oauth/clients/{client}"), HttpMethod.DELETE, + new HttpEntity<>(client, headers), Void.class, + client.getClientId()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); } @Test - public void testAddUpdateAndDeleteTx() throws Exception { + void testAddUpdateAndDeleteTx() { ClientDetailsModification[] clients = doCreateClients(); for (int i = 1; i < clients.length; i++) { clients[i] = new ClientDetailsModification(clients[i]); @@ -721,61 +710,60 @@ public void testAddUpdateAndDeleteTx() throws Exception { clients[0].setClientId(new RandomValueStringGenerator().generate()); clients[clients.length - 1].setAction(ClientDetailsModification.DELETE); - headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin")); headers.add("Accept", "application/json"); String oldId = clients[clients.length - 1].getClientId(); ResponseEntity result = - serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/tx/modify"), - HttpMethod.POST, - new HttpEntity(clients, headers), - UaaClientDetails[].class); - assertEquals(HttpStatus.OK, result.getStatusCode()); + serverRunning.getRestTemplate().exchange( + serverRunning.getUrl("/oauth/clients/tx/modify"), + HttpMethod.POST, + new HttpEntity<>(clients, headers), + UaaClientDetails[].class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); //set the deleted client ID so we can verify it is gone. clients[clients.length - 1].setClientId(oldId); for (int i = 0; i < clients.length; i++) { ClientDetails client = getClient(clients[i].getClientId()); if (i == (clients.length - 1)) { - assertNull(client); + assertThat(client).isNull(); } else { - assertNotNull(client); + assertThat(client).isNotNull(); } } } @Test - // CFID-372 - public void testCreateExistingClientFails() throws Exception { + // CFID-372 + void testCreateExistingClientFails() { UaaClientDetails client = createClient("client_credentials"); @SuppressWarnings("rawtypes") ResponseEntity attempt = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients"), - HttpMethod.POST, new HttpEntity(client, headers), Map.class); - assertEquals(HttpStatus.CONFLICT, attempt.getStatusCode()); + HttpMethod.POST, new HttpEntity<>(client, headers), Map.class); + assertThat(attempt.getStatusCode()).isEqualTo(HttpStatus.CONFLICT); @SuppressWarnings("unchecked") Map map = attempt.getBody(); - assertEquals("invalid_client", map.get("error")); + assertThat(map).containsEntry("error", "invalid_client"); } @Test - public void testClientApprovalsDeleted() throws Exception { + void testClientApprovalsDeleted() { //create client UaaClientDetails client = createClient("client_credentials", "password"); - assertNotNull(getClient(client.getClientId())); + assertThat(getClient(client.getClientId())).isNotNull(); //issue a user token for this client OAuth2AccessToken userToken = getUserAccessToken(client.getClientId(), "secret", testAccounts.getUserName(), testAccounts.getPassword(), "oauth.approvals"); //make sure we don't have any approvals Approval[] approvals = getApprovals(userToken.getValue(), client.getClientId()); - Assert.assertEquals(0, approvals.length); + assertThat(approvals).isEmpty(); //create three approvals addApprovals(userToken.getValue(), client.getClientId()); approvals = getApprovals(userToken.getValue(), client.getClientId()); - Assert.assertEquals(3, approvals.length); + assertThat(approvals).hasSize(3); //delete the client ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients/{client}"), HttpMethod.DELETE, - new HttpEntity(client, getAuthenticatedHeaders(token)), Void.class, client.getClientId()); - assertEquals(HttpStatus.OK, result.getStatusCode()); + new HttpEntity<>(client, getAuthenticatedHeaders(token)), Void.class, client.getClientId()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); //create a client that can read another clients approvals String deletedClientId = client.getClientId(); @@ -783,118 +771,117 @@ public void testClientApprovalsDeleted() throws Exception { userToken = getUserAccessToken(client.getClientId(), "secret", testAccounts.getUserName(), testAccounts.getPassword(), "oauth.approvals"); //make sure we don't have any approvals approvals = getApprovals(userToken.getValue(), deletedClientId); - Assert.assertEquals(0, approvals.length); - assertNull(getClient(deletedClientId)); + assertThat(approvals).isEmpty(); + assertThat(getClient(deletedClientId)).isNull(); } @Test - public void testClientTxApprovalsDeleted() throws Exception { + void testClientTxApprovalsDeleted() { //create client UaaClientDetails client = createClient("client_credentials", "password"); - assertNotNull(getClient(client.getClientId())); + assertThat(getClient(client.getClientId())).isNotNull(); //issue a user token for this client OAuth2AccessToken userToken = getUserAccessToken(client.getClientId(), "secret", testAccounts.getUserName(), testAccounts.getPassword(), "oauth.approvals"); //make sure we don't have any approvals Approval[] approvals = getApprovals(userToken.getValue(), client.getClientId()); - Assert.assertEquals(0, approvals.length); + assertThat(approvals).isEmpty(); //create three approvals addApprovals(userToken.getValue(), client.getClientId()); approvals = getApprovals(userToken.getValue(), client.getClientId()); - Assert.assertEquals(3, approvals.length); + assertThat(approvals).hasSize(3); //delete the client ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients/tx/delete"), HttpMethod.POST, - new HttpEntity(new UaaClientDetails[]{client}, getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin"))), Void.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); + new HttpEntity<>(new UaaClientDetails[]{client}, getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin"))), Void.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); //create a client that can read another clients approvals String deletedClientId = client.getClientId(); client = createApprovalsClient("password"); userToken = getUserAccessToken(client.getClientId(), "secret", testAccounts.getUserName(), testAccounts.getPassword(), "oauth.approvals"); //make sure we don't have any approvals approvals = getApprovals(userToken.getValue(), deletedClientId); - Assert.assertEquals(0, approvals.length); - assertNull(getClient(deletedClientId)); + assertThat(approvals).isEmpty(); + assertThat(getClient(deletedClientId)).isNull(); } @Test - public void testClientTxModifyApprovalsDeleted() throws Exception { + void testClientTxModifyApprovalsDeleted() { //create client ClientDetailsModification client = createClient("client_credentials", "password"); - assertNotNull(getClient(client.getClientId())); + assertThat(getClient(client.getClientId())).isNotNull(); //issue a user token for this client OAuth2AccessToken userToken = getUserAccessToken(client.getClientId(), "secret", testAccounts.getUserName(), testAccounts.getPassword(), "oauth.approvals"); //make sure we don't have any approvals Approval[] approvals = getApprovals(userToken.getValue(), client.getClientId()); - Assert.assertEquals(0, approvals.length); + assertThat(approvals).isEmpty(); //create three approvals addApprovals(userToken.getValue(), client.getClientId()); approvals = getApprovals(userToken.getValue(), client.getClientId()); - Assert.assertEquals(3, approvals.length); + assertThat(approvals).hasSize(3); //delete the client client.setAction(ClientDetailsModification.DELETE); ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients/tx/modify"), HttpMethod.POST, - new HttpEntity(new UaaClientDetails[]{client}, getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin"))), Void.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); + new HttpEntity<>(new UaaClientDetails[]{client}, getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin"))), Void.class); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); //create a client that can read another clients approvals String deletedClientId = client.getClientId(); client = createApprovalsClient("password"); userToken = getUserAccessToken(client.getClientId(), "secret", testAccounts.getUserName(), testAccounts.getPassword(), "oauth.approvals"); //make sure we don't have any approvals approvals = getApprovals(userToken.getValue(), deletedClientId); - Assert.assertEquals(0, approvals.length); - assertNull(getClient(deletedClientId)); + assertThat(approvals).isEmpty(); + assertThat(getClient(deletedClientId)).isNull(); } private Approval[] getApprovals(String token, String clientId) { String filter = "client_id eq \"" + clientId + "\""; - HttpHeaders headers = getAuthenticatedHeaders(token); + HttpHeaders myHeaders = getAuthenticatedHeaders(token); ResponseEntity approvals = - serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/approvals"), - HttpMethod.GET, - new HttpEntity<>(headers), - Approval[].class, - filter); - assertEquals(HttpStatus.OK, approvals.getStatusCode()); + serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/approvals"), + HttpMethod.GET, + new HttpEntity<>(myHeaders), + Approval[].class, + filter); + assertThat(approvals.getStatusCode()).isEqualTo(HttpStatus.OK); return Arrays.stream(approvals.getBody()).filter(a -> clientId.equals(a.getClientId())).toArray(Approval[]::new); } - private Approval[] addApprovals(String token, String clientId) { Date oneMinuteAgo = new Date(System.currentTimeMillis() - 60000); Date expiresAt = new Date(System.currentTimeMillis() + 60000); Approval[] approvals = new Approval[]{ - new Approval() - .setUserId(null) - .setClientId(clientId) - .setScope("cloud_controller.read") - .setExpiresAt(expiresAt) - .setStatus(Approval.ApprovalStatus.APPROVED) - .setLastUpdatedAt(oneMinuteAgo), - new Approval() - .setUserId(null) - .setClientId(clientId) - .setScope("openid") - .setExpiresAt(expiresAt) - .setStatus(Approval.ApprovalStatus.APPROVED) - .setLastUpdatedAt(oneMinuteAgo), - new Approval() - .setUserId(null) - .setClientId(clientId) - .setScope("password.write") - .setExpiresAt(expiresAt) - .setStatus(Approval.ApprovalStatus.APPROVED) - .setLastUpdatedAt(oneMinuteAgo) + new Approval() + .setUserId(null) + .setClientId(clientId) + .setScope("cloud_controller.read") + .setExpiresAt(expiresAt) + .setStatus(Approval.ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneMinuteAgo), + new Approval() + .setUserId(null) + .setClientId(clientId) + .setScope("openid") + .setExpiresAt(expiresAt) + .setStatus(Approval.ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneMinuteAgo), + new Approval() + .setUserId(null) + .setClientId(clientId) + .setScope("password.write") + .setExpiresAt(expiresAt) + .setStatus(Approval.ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneMinuteAgo) }; - HttpHeaders headers = getAuthenticatedHeaders(token); - HttpEntity entity = new HttpEntity(approvals, headers); + HttpHeaders myHeaders = getAuthenticatedHeaders(token); + HttpEntity entity = new HttpEntity<>(approvals, myHeaders); ResponseEntity response = serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/approvals/{clientId}"), - HttpMethod.PUT, - entity, - Approval[].class, - clientId); - assertEquals(HttpStatus.OK, response.getStatusCode()); + serverRunning.getUrl("/approvals/{clientId}"), + HttpMethod.PUT, + entity, + Approval[].class, + clientId); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); return response.getBody(); } @@ -912,27 +899,27 @@ private ClientDetailsModification createClientWithSecretAndRedirectUri( client.setClientSecret(secret); client.setAdditionalInformation(Collections.singletonMap("foo", Collections.singletonList("bar"))); - client.setRegisteredRedirectUri(redirectUris == null? - Collections.singleton("http://redirect.url"): redirectUris); + client.setRegisteredRedirectUri(redirectUris == null ? + Collections.singleton("http://redirect.url") : redirectUris); return client; } private ClientDetailsModification createClientWithSecret(String secret, String... grantTypes) { ClientDetailsModification client = createClientWithSecretAndRedirectUri(secret, - Collections.singleton("http://redirect.url"), grantTypes); + Collections.singleton("http://redirect.url"), grantTypes); ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, new HttpEntity(client, headers), Void.class); - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); return client; } private ClientDetailsModification createApprovalsClient(String... grantTypes) { - ClientDetailsModification client =createClientWithSecretAndRedirectUri("secret", + ClientDetailsModification client = createClientWithSecretAndRedirectUri("secret", Collections.singleton("http://redirect.url"), grantTypes); ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients"), HttpMethod.POST, new HttpEntity(client, headers), Void.class); - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); return client; } @@ -941,11 +928,11 @@ public HttpHeaders getAuthenticatedHeaders(OAuth2AccessToken token) { } public HttpHeaders getAuthenticatedHeaders(String token) { - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.set("Authorization", "Bearer " + token); - return headers; + HttpHeaders myHeaders = new HttpHeaders(); + myHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + myHeaders.setContentType(MediaType.APPLICATION_JSON); + myHeaders.set("Authorization", "Bearer " + token); + return myHeaders; } private OAuth2AccessToken getClientCredentialsAccessToken(String scope) { @@ -956,18 +943,18 @@ private OAuth2AccessToken getClientCredentialsAccessToken(String scope) { } private OAuth2AccessToken getClientCredentialsAccessToken(String clientId, String clientSecret, String scope) { - MultiValueMap formData = new LinkedMultiValueMap(); + MultiValueMap formData = new LinkedMultiValueMap<>(); formData.add("grant_type", "client_credentials"); formData.add("client_id", clientId); formData.add("scope", scope); - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - headers.set("Authorization", - "Basic " + new String(Base64.encode(String.format("%s:%s", clientId, clientSecret).getBytes()))); + HttpHeaders myHeaders = new HttpHeaders(); + myHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + myHeaders.set("Authorization", + "Basic " + new String(ENCODER.encode(String.format("%s:%s", clientId, clientSecret).getBytes()))); @SuppressWarnings("rawtypes") - ResponseEntity response = serverRunning.postForMap("/oauth/token", formData, headers); - assertEquals(HttpStatus.OK, response.getStatusCode()); + ResponseEntity response = serverRunning.postForMap("/oauth/token", formData, myHeaders); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @SuppressWarnings("unchecked") OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(response.getBody()); @@ -975,36 +962,34 @@ private OAuth2AccessToken getClientCredentialsAccessToken(String clientId, Strin } private OAuth2AccessToken getUserAccessToken(String clientId, String clientSecret, String username, String password, String scope) { - MultiValueMap formData = new LinkedMultiValueMap(); + MultiValueMap formData = new LinkedMultiValueMap<>(); formData.add("grant_type", "password"); formData.add("client_id", clientId); formData.add("scope", scope); formData.add("username", username); formData.add("password", password); - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - headers.set("Authorization", - "Basic " + new String(Base64.encode(String.format("%s:%s", clientId, clientSecret).getBytes()))); + HttpHeaders myHeaders = new HttpHeaders(); + myHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + myHeaders.set("Authorization", + "Basic " + new String(ENCODER.encode(String.format("%s:%s", clientId, clientSecret).getBytes()))); @SuppressWarnings("rawtypes") - ResponseEntity response = serverRunning.postForMap("/oauth/token", formData, headers); - assertEquals(HttpStatus.OK, response.getStatusCode()); + ResponseEntity response = serverRunning.postForMap("/oauth/token", formData, myHeaders); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @SuppressWarnings("unchecked") OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(response.getBody()); return accessToken; - } - public ClientDetails getClient(String id) throws Exception { - HttpHeaders headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read")); + public ClientDetails getClient(String id) { + HttpHeaders myHeaders = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.read")); ResponseEntity result = - serverRunning.getRestTemplate().exchange( - serverRunning.getUrl("/oauth/clients/" + id), - HttpMethod.GET, - new HttpEntity(null, headers), - UaaClientDetails.class); - + serverRunning.getRestTemplate().exchange( + serverRunning.getUrl("/oauth/clients/" + id), + HttpMethod.GET, + new HttpEntity(null, myHeaders), + UaaClientDetails.class); if (result.getStatusCode() == HttpStatus.NOT_FOUND) { return null; @@ -1013,31 +998,16 @@ public ClientDetails getClient(String id) throws Exception { } else { throw new InvalidClientDetailsException("Unknown status code:" + result.getStatusCode()); } - } public boolean validateClients(UaaClientDetails[] expected, UaaClientDetails[] actual) { - assertNotNull(expected); - assertNotNull(actual); - assertEquals(expected.length, actual.length); + assertThat(expected).isNotNull(); + assertThat(actual).hasSameSizeAs(expected); for (int i = 0; i < expected.length; i++) { - assertNotNull(expected[i]); - assertNotNull(actual[i]); - assertEquals(expected[i].getClientId(), actual[i].getClientId()); + assertThat(expected[i]).isNotNull(); + assertThat(actual[i]).isNotNull(); + assertThat(actual[i].getClientId()).isEqualTo(expected[i].getClientId()); } return true; } - - private static class ClientIdComparator implements Comparator { - @Override - public int compare(UaaClientDetails o1, UaaClientDetails o2) { - return (o1.getClientId().compareTo(o2.getClientId())); - } - - @Override - public boolean equals(Object obj) { - return obj == this; - } - } - } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegrationTests.java index e5b5bebd673..9a2290ff5f9 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegrationTests.java @@ -16,59 +16,53 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; -import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; -import org.junit.Test; -import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; import java.util.Collections; import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.doesSupportZoneDNS; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -public class LdapIntegrationTests { +class LdapIntegrationTests { @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); - @Before - public void setup() { + @BeforeEach + void setup() { String token = IntegrationTestUtils.getClientCredentialsToken(serverRunning.getBaseUrl(), "admin", "adminsecret"); IntegrationTestUtils.ensureGroupExists(token, "", serverRunning.getBaseUrl(), "zones.testzone1.admin"); } - @After - public void cleanup() { + @AfterEach + void cleanup() { String token = IntegrationTestUtils.getClientCredentialsToken(serverRunning.getBaseUrl(), "admin", "adminsecret"); String groupId = IntegrationTestUtils.getGroup(token, "", serverRunning.getBaseUrl(), "zones.testzone1.admin").getId(); IntegrationTestUtils.deleteGroup(token, "", serverRunning.getBaseUrl(), groupId); } @Test - public void test_LDAP_Custom_User_Attributes_In_ID_Token() { - assertTrue("Expected testzone1.localhost and testzone2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + void test_LDAP_Custom_User_Attributes_In_ID_Token() { + assertThat(doesSupportZoneDNS()).as("Expected testzone1.localhost and testzone2.localhost to resolve to 127.0.0.1").isTrue(); final String COST_CENTER = "costCenter"; final String COST_CENTERS = "costCenters"; @@ -85,65 +79,63 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() { //identity client token RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); //admin client token - to create users RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); //create the zone - IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, null); //create a zone admin user - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); String groupId = IntegrationTestUtils.findGroupId( - adminClient, serverRunning.getBaseUrl(), String.format("zones.%s.admin", zoneId) + adminClient, serverRunning.getBaseUrl(), "zones.%s.admin".formatted(zoneId) ); IntegrationTestUtils.addMemberToGroup(adminClient, serverRunning.getBaseUrl(), user.getId(), groupId); //get the zone admin token String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); LdapIdentityProviderDefinition ldapIdentityProviderDefinition = LdapIdentityProviderDefinition.searchAndBindMapGroupToScopes( - "ldap://localhost:389/", - "cn=admin,dc=test,dc=com", - "password", - "dc=test,dc=com", - "cn={0}", - "ou=scopes,dc=test,dc=com", - "member={0}", - "mail", - null, - false, - true, - true, - 100, - true); - ldapIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+COST_CENTERS, COST_CENTER); - ldapIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+MANAGERS, MANAGER); + "ldap://localhost:389/", + "cn=admin,dc=test,dc=com", + "password", + "dc=test,dc=com", + "cn={0}", + "ou=scopes,dc=test,dc=com", + "member={0}", + "mail", + null, + false, + true, + true, + 100, + true); + ldapIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + COST_CENTERS, COST_CENTER); + ldapIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + MANAGERS, MANAGER); ldapIdentityProviderDefinition.addWhiteListedGroup("marissaniner"); ldapIdentityProviderDefinition.addWhiteListedGroup("marissaniner2"); - - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); provider.setType(OriginKeys.LDAP); provider.setActive(true); provider.setConfig(ldapIdentityProviderDefinition); provider.setOriginKey(OriginKeys.LDAP); provider.setName("simplesamlphp for uaa"); - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); - assertNotNull(provider.getId()); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertThat(provider.getId()).isNotNull(); - assertEquals(OriginKeys.LDAP, provider.getOriginKey()); + assertThat(provider.getOriginKey()).isEqualTo(OriginKeys.LDAP); List idps = Collections.singletonList(provider.getOriginKey()); @@ -156,65 +148,57 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() { clientDetails = IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, zoneId, clientDetails); clientDetails.setClientSecret("secret"); - - String idToken = - (String) IntegrationTestUtils.getPasswordToken(zoneUrl, - clientDetails.getClientId(), - clientDetails.getClientSecret(), - "marissa9", - "ldap9", - "openid user_attributes roles") + String idToken = (String) IntegrationTestUtils.getPasswordToken(zoneUrl, + clientDetails.getClientId(), + clientDetails.getClientSecret(), + "marissa9", + "ldap9", + "openid user_attributes roles") .get("id_token"); - assertNotNull(idToken); + assertThat(idToken).isNotNull(); Jwt idTokenClaims = JwtHelper.decode(idToken); - Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() {}); + Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference<>() { + }); - assertNotNull(claims.get(ClaimConstants.USER_ATTRIBUTES)); - Map> userAttributes = (Map>) claims.get(ClaimConstants.USER_ATTRIBUTES); - assertThat(userAttributes.get(COST_CENTERS), containsInAnyOrder(DENVER_CO)); - assertThat(userAttributes.get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); + assertThat(claims).containsKey(ClaimConstants.USER_ATTRIBUTES); + Map> userAttributes = (Map>) claims.get(ClaimConstants.USER_ATTRIBUTES); + assertThat(userAttributes.get(COST_CENTERS)).contains(DENVER_CO); + assertThat(userAttributes.get(MANAGERS)).contains(JOHN_THE_SLOTH, KARI_THE_ANT_EATER); - - assertNotNull(claims.get(ClaimConstants.ROLES)); + assertThat(claims).containsKey(ClaimConstants.ROLES); List roles = (List) claims.get(ClaimConstants.ROLES); - assertThat(roles, containsInAnyOrder("marissaniner", "marissaniner2")); + assertThat(roles).contains("marissaniner", "marissaniner2"); //no user_attribute scope provided idToken = - (String) IntegrationTestUtils.getPasswordToken(zoneUrl, - clientDetails.getClientId(), - clientDetails.getClientSecret(), - "marissa9", - "ldap9", - "openid") - .get("id_token"); + (String) IntegrationTestUtils.getPasswordToken(zoneUrl, + clientDetails.getClientId(), + clientDetails.getClientSecret(), + "marissa9", + "ldap9", + "openid") + .get("id_token"); - assertNotNull(idToken); + assertThat(idToken).isNotNull(); idTokenClaims = JwtHelper.decode(idToken); - claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() {}); - assertNull(claims.get(ClaimConstants.USER_ATTRIBUTES)); - assertNull(claims.get(ClaimConstants.ROLES)); - + claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference<>() { + }); + assertThat(claims).doesNotContainKey(ClaimConstants.USER_ATTRIBUTES) + .doesNotContainKey(ClaimConstants.ROLES); - String username = "\u7433\u8D3A"; + String username = "琳贺"; idToken = - (String) IntegrationTestUtils.getPasswordToken(zoneUrl, - clientDetails.getClientId(), - clientDetails.getClientSecret(), - username, - "koala", - "openid") - .get("id_token"); - - assertNotNull(idToken); + (String) IntegrationTestUtils.getPasswordToken(zoneUrl, + clientDetails.getClientId(), + clientDetails.getClientSecret(), + username, + "koala", + "openid") + .get("id_token"); + + assertThat(idToken).isNotNull(); } - - protected boolean isLdapEnabled() { - String profile = System.getProperty("spring.profiles.active",""); - return profile.contains(OriginKeys.LDAP); - } - } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java index debd85a12c1..1e4b7efdc29 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java @@ -24,6 +24,7 @@ import org.cloudfoundry.identity.uaa.oauth.client.test.BeforeOAuth2Context; import org.cloudfoundry.identity.uaa.oauth.client.test.OAuth2ContextConfiguration; import org.cloudfoundry.identity.uaa.oauth.client.test.OAuth2ContextSetup; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; @@ -38,25 +39,19 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; /** * Integration test to verify that the Login Server authentication channel is @@ -69,14 +64,10 @@ public class LoginServerSecurityIntegrationTests { private final String JOE = "joe" + new RandomValueStringGenerator().generate().toLowerCase(); private final String LOGIN_SERVER_JOE = "ls_joe" + new RandomValueStringGenerator().generate().toLowerCase(); - private final String userEndpoint = "/Users"; - - private ScimUser joe; - @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); - - private UaaTestAccounts testAccounts = UaaTestAccounts.standard(serverRunning); + private ScimUser joe; + private final UaaTestAccounts testAccounts = UaaTestAccounts.standard(serverRunning); @Rule public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts); @@ -84,9 +75,9 @@ public class LoginServerSecurityIntegrationTests { @Rule public OAuth2ContextSetup context = OAuth2ContextSetup.withTestAccounts(serverRunning, testAccountSetup); - private MultiValueMap params = new LinkedMultiValueMap(); + private final MultiValueMap params = new LinkedMultiValueMap<>(); - private HttpHeaders headers = new HttpHeaders(); + private final HttpHeaders headers = new HttpHeaders(); private ScimUser userForLoginServer; @Before @@ -94,13 +85,13 @@ public void init() { params.set("source", "login"); params.set("redirect_uri", "http://localhost:8080/app/"); params.set("response_type", "token"); - if (joe!=null) { + if (joe != null) { params.set("username", joe.getUserName()); } headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - ((RestTemplate)serverRunning.getRestTemplate()).setErrorHandler(new OAuth2ErrorHandler(context.getResource()) { + ((RestTemplate) serverRunning.getRestTemplate()).setErrorHandler(new OAuth2ErrorHandler(context.getResource()) { // Pass errors through in response entity for status code analysis @Override public boolean hasError(ClientHttpResponse response) { @@ -133,29 +124,28 @@ public void setUpUserAccounts() { userForLoginServer.setVerified(true); userForLoginServer.setOrigin(LOGIN_SERVER); - ResponseEntity newuser = client.postForEntity(serverRunning.getUrl(userEndpoint), user, - ScimUser.class); + String userEndpoint = "/Users"; + ResponseEntity newuser = client.postForEntity(serverRunning.getUrl(userEndpoint), user, ScimUser.class); userForLoginServer = client.postForEntity(serverRunning.getUrl(userEndpoint), userForLoginServer, ScimUser.class).getBody(); joe = newuser.getBody(); - assertEquals(JOE, joe.getUserName()); + assertThat(joe.getUserName()).isEqualTo(JOE); PasswordChangeRequest change = new PasswordChangeRequest(); change.setPassword("Passwo3d"); - HttpHeaders headers = new HttpHeaders(); + headers.clear(); ResponseEntity result = client - .exchange(serverRunning.getUrl(userEndpoint) + "/{id}/password", - HttpMethod.PUT, new HttpEntity(change, headers), - Void.class, joe.getId()); - assertEquals(HttpStatus.OK, result.getStatusCode()); + .exchange(serverRunning.getUrl(userEndpoint) + "/{id}/password", + HttpMethod.PUT, new HttpEntity<>(change, headers), + Void.class, joe.getId()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); // The implicit grant for cf requires extra parameters in the // authorization request context.setParameters(Collections.singletonMap("credentials", - testAccounts.getJsonCredentials(joe.getUserName(), "Passwo3d"))); - + testAccounts.getJsonCredentials(joe.getUserName(), "Passwo3d"))); } @Test @@ -164,10 +154,11 @@ public void testAuthenticateReturnsUserID() { params.set("username", JOE); params.set("password", "Passwo3d"); ResponseEntity response = serverRunning.postForMap("/authenticate", params, headers); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(JOE, response.getBody().get("username")); - assertEquals(OriginKeys.UAA, response.getBody().get(OriginKeys.ORIGIN)); - assertTrue(StringUtils.hasText((String)response.getBody().get("user_id"))); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()) + .containsEntry("username", JOE) + .containsEntry(OriginKeys.ORIGIN, OriginKeys.UAA); + assertThat(StringUtils.hasText((String) response.getBody().get("user_id"))).isTrue(); } @Test @@ -176,10 +167,10 @@ public void testAuthenticateMarissaReturnsUserID() { params.set("username", testAccounts.getUserName()); params.set("password", testAccounts.getPassword()); ResponseEntity response = serverRunning.postForMap("/authenticate", params, headers); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals("marissa", response.getBody().get("username")); - assertEquals(OriginKeys.UAA, response.getBody().get(OriginKeys.ORIGIN)); - assertTrue(StringUtils.hasText((String)response.getBody().get("user_id"))); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).containsEntry("username", "marissa") + .containsEntry(OriginKeys.ORIGIN, OriginKeys.UAA); + assertThat(StringUtils.hasText((String) response.getBody().get("user_id"))).isTrue(); } @Test @@ -188,7 +179,7 @@ public void testAuthenticateMarissaFails() { params.set("username", testAccounts.getUserName()); params.set("password", ""); ResponseEntity response = serverRunning.postForMap("/authenticate", params, headers); - assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); } @Test @@ -196,10 +187,10 @@ public void testAuthenticateDoesNotReturnsUserID() { params.set("username", testAccounts.getUserName()); params.set("password", testAccounts.getPassword()); ResponseEntity response = serverRunning.postForMap("/authenticate", params, headers); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals("marissa", response.getBody().get("username")); - assertNull(response.getBody().get(OriginKeys.ORIGIN)); - assertNull(response.getBody().get("user_id")); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).containsEntry("username", "marissa") + .doesNotContainKey(OriginKeys.ORIGIN) + .doesNotContainKey("user_id"); } @Test @@ -216,9 +207,9 @@ public void testLoginServerCanAuthenticateUserForCf() { } @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAuthorizationUri(), params, headers); - assertEquals(HttpStatus.FOUND, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND); String results = response.getHeaders().getLocation().toString(); - assertTrue("There should be an access token: " + results, results.contains("access_token")); + assertThat(results).as("There should be an access token: " + results).contains("access_token"); } @Test @@ -234,13 +225,14 @@ public void testLoginServerCanAuthenticateUserForAuthorizationCode() { if (response.getStatusCode().is4xxClientError()) { fail(response.getBody().toString()); } else { - assertEquals(HttpStatus.OK, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @SuppressWarnings("unchecked") Map results = response.getBody(); // The approval page messaging response - assertNotNull("There should be scopes: " + results, results.get("scopes")); + assertThat(results).as("There should be scopes: " + results).containsKey("scopes"); } } + @Test @OAuth2ContextConfiguration(LoginClient.class) public void testLoginServerCanAuthenticateUserWithIDForAuthorizationCode() { @@ -253,15 +245,14 @@ public void testLoginServerCanAuthenticateUserWithIDForAuthorizationCode() { if (response.getStatusCode().is4xxClientError()) { fail(response.getBody().toString()); } else { - assertEquals(HttpStatus.OK, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @SuppressWarnings("unchecked") Map results = response.getBody(); // The approval page messaging response - assertNotNull("There should be scopes: " + results, results.get("scopes")); + assertThat(results).as("There should be scopes: " + results).containsKey("scopes"); } } - @Test @OAuth2ContextConfiguration(LoginClient.class) public void testMissingUserInfoIsError() { @@ -269,27 +260,27 @@ public void testMissingUserInfoIsError() { params.remove("username"); @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAuthorizationUri(), params, headers); - assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); @SuppressWarnings("unchecked") Map results = response.getBody(); - assertTrue("There should be an error: " + results, results.containsKey("error")); + assertThat(results).as("There should be an error: " + results).containsKey("error"); } @Test @OAuth2ContextConfiguration(LoginClient.class) public void testMissingUsernameIsError() { ((RestTemplate) serverRunning.getRestTemplate()) - .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); params.set("client_id", testAccounts.getDefaultImplicitResource().getClientId()); params.remove("username"); // Some of the user info is there but not enough to determine a username params.set("given_name", "Mabel"); @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAuthorizationUri(), params, headers); - assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); @SuppressWarnings("unchecked") Map results = response.getBody(); - assertTrue("There should be an error: " + results, results.containsKey("error")); + assertThat(results).as("There should be an error: " + results).containsKey("error"); } @Test @@ -297,7 +288,7 @@ public void testMissingUsernameIsError() { public void testWrongUsernameIsErrorAddNewEnabled() { ((RestTemplate) serverRunning.getRestTemplate()) - .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); params.set("client_id", resource.getClientId()); @@ -310,9 +301,9 @@ public void testWrongUsernameIsErrorAddNewEnabled() { @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAuthorizationUri(), params, headers); // add_new:true user accounts are automatically provisioned. - assertEquals(HttpStatus.FOUND, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND); String results = response.getHeaders().getLocation().getFragment(); - assertTrue("There should be an access token: " + results, results.contains("access_token")); + assertThat(results).as("There should be an access token: " + results).contains("access_token"); } @Test @@ -320,7 +311,7 @@ public void testWrongUsernameIsErrorAddNewEnabled() { public void testWrongUsernameIsErrorAddNewDisabled() { ((RestTemplate) serverRunning.getRestTemplate()) - .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); params.set("client_id", resource.getClientId()); @@ -332,19 +323,19 @@ public void testWrongUsernameIsErrorAddNewDisabled() { } @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAuthorizationUri(), params, headers); - assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); @SuppressWarnings("unchecked") Map results = response.getBody(); - assertTrue("There should be an error: " + results, results.containsKey("error")); + assertThat(results).as("There should be an error: " + results).containsKey("error"); } @Test @OAuth2ContextConfiguration(LoginClient.class) public void testAddNewUserWithWrongEmailFormat() { ((RestTemplate) serverRunning.getRestTemplate()) - .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); params.set("client_id", testAccounts.getDefaultImplicitResource().getClientId()); - params.set("source","login"); + params.set("source", "login"); params.set("username", "newuser"); params.remove("given_name"); params.remove("family_name"); @@ -352,13 +343,14 @@ public void testAddNewUserWithWrongEmailFormat() { params.set(UaaAuthenticationDetails.ADD_NEW, "true"); @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAuthorizationUri(), params, headers); - assertNotNull(response); - assertNotEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertEquals(HttpStatus.FOUND, response.getStatusCode()); + assertThat(response).isNotNull() + .extracting(ResponseEntity::getStatusCode) + .isNotEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) + .isEqualTo(HttpStatus.FOUND); @SuppressWarnings("unchecked") Map results = response.getBody(); if (results != null) { - assertFalse("There should not be an error: " + results, results.containsKey("error")); + assertThat(results).as("There should not be an error: " + results).doesNotContainKey("error"); } } @@ -366,11 +358,11 @@ public void testAddNewUserWithWrongEmailFormat() { @OAuth2ContextConfiguration(LoginClient.class) public void testLoginServerCfPasswordToken() { ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Accept",MediaType.APPLICATION_JSON_VALUE); + headers.clear(); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); params.set("client_id", resource.getClientId()); - params.set("client_secret",""); - params.set("source","login"); + params.set("client_secret", ""); + params.set("source", "login"); params.set("username", userForLoginServer.getUserName()); params.set(OriginKeys.ORIGIN, userForLoginServer.getOrigin()); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); @@ -381,22 +373,22 @@ public void testLoginServerCfPasswordToken() { } @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAccessTokenUri(), params, headers); - assertEquals(HttpStatus.OK, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); Map results = response.getBody(); - assertTrue("There should be a token: " + results, results.containsKey("access_token")); - assertTrue("There should be a refresh: " + results, results.containsKey("refresh_token")); + assertThat(results).as("There should be a token: " + results).containsKey("access_token") + .as("There should be a refresh: " + results).containsKey("refresh_token"); } @Test @OAuth2ContextConfiguration(LoginClient.class) public void testLoginServerWithoutBearerToken() { ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Accept",MediaType.APPLICATION_JSON_VALUE); + headers.clear(); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); headers.add("Authorization", getAuthorizationEncodedValue(resource.getClientId(), "")); params.set("client_id", resource.getClientId()); - params.set("client_secret",""); - params.set("source","login"); + params.set("client_secret", ""); + params.set("source", "login"); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); params.set("grant_type", "password"); String redirect = resource.getPreEstablishedRedirectUri(); @@ -405,18 +397,18 @@ public void testLoginServerWithoutBearerToken() { } @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAccessTokenUri(), params, headers); - assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); } @Test @OAuth2ContextConfiguration(LoginClient.class) public void testLoginServerCfInvalidClientPasswordToken() { ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Accept",MediaType.APPLICATION_JSON_VALUE); + headers.clear(); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); params.set("client_id", resource.getClientId()); - params.set("client_secret","bogus"); - params.set("source","login"); + params.set("client_secret", "bogus"); + params.set("source", "login"); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); params.set("grant_type", "password"); @@ -427,18 +419,18 @@ public void testLoginServerCfInvalidClientPasswordToken() { @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAccessTokenUri(), params, headers); HttpStatus statusCode = response.getStatusCode(); - assertTrue("Status code should be 401 or 403.", statusCode==HttpStatus.FORBIDDEN || statusCode==HttpStatus.UNAUTHORIZED); + assertThat(statusCode == HttpStatus.FORBIDDEN || statusCode == HttpStatus.UNAUTHORIZED).as("Status code should be 401 or 403.").isTrue(); } @Test @OAuth2ContextConfiguration(AppClient.class) public void testLoginServerCfInvalidClientToken() { ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Accept",MediaType.APPLICATION_JSON_VALUE); + headers.clear(); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); params.set("client_id", resource.getClientId()); - params.set("client_secret","bogus"); - params.set("source","login"); + params.set("client_secret", "bogus"); + params.set("source", "login"); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); params.set("grant_type", "password"); @@ -450,22 +442,21 @@ public void testLoginServerCfInvalidClientToken() { ResponseEntity response = serverRunning.postForMap(serverRunning.getAccessTokenUri(), params, headers); HttpStatus statusCode = response.getStatusCode(); - assertTrue("Status code should be 401 or 403.", statusCode==HttpStatus.FORBIDDEN || statusCode==HttpStatus.UNAUTHORIZED); + assertThat(statusCode == HttpStatus.FORBIDDEN || statusCode == HttpStatus.UNAUTHORIZED).as("Status code should be 401 or 403.").isTrue(); } private String getAuthorizationEncodedValue(String username, String password) { String auth = username + ":" + password; - byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("US-ASCII"))); - return "Basic " + new String( encodedAuth ); + byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.US_ASCII)); + return "Basic " + new String(encodedAuth); } - private static class LoginClient extends ClientCredentialsResourceDetails { @SuppressWarnings("unused") public LoginClient(Object target) { LoginServerSecurityIntegrationTests test = (LoginServerSecurityIntegrationTests) target; ClientCredentialsResourceDetails resource = test.testAccounts.getClientCredentialsResource( - new String[] {"oauth.login"}, "login", "loginsecret"); + new String[]{"oauth.login"}, "login", "loginsecret"); setClientId(resource.getClientId()); setClientSecret(resource.getClientSecret()); setId(getClientId()); @@ -484,5 +475,4 @@ public AppClient(Object target) { setAccessTokenUri(test.serverRunning.getAccessTokenUri()); } } - } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/endpoints/OauthAuthorizeEndpoint.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/endpoints/OauthAuthorizeEndpoint.java index 5ea03defd70..cc3433b76d0 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/endpoints/OauthAuthorizeEndpoint.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/endpoints/OauthAuthorizeEndpoint.java @@ -1,24 +1,24 @@ package org.cloudfoundry.identity.uaa.integration.endpoints; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - import org.cloudfoundry.identity.uaa.integration.pageObjects.SamlLoginPage; import org.openqa.selenium.WebDriver; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + public class OauthAuthorizeEndpoint { - static final private String urlPath = "/oauth/authorize"; + private static final String URL_PATH = "/oauth/authorize"; - static public SamlLoginPage authorize_goesToSamlLoginPage(WebDriver driver, String baseUrl, String redirectUri, String clientId, String response_type) { - driver.get(buildAuthorizeUrl(baseUrl, redirectUri, clientId, response_type)); + public static SamlLoginPage assertThatAuthorize_goesToSamlLoginPage(WebDriver driver, String baseUrl, String redirectUri, String clientId, String responseType) { + driver.get(buildAuthorizeUrl(baseUrl, redirectUri, clientId, responseType)); return new SamlLoginPage(driver); } - private static String buildAuthorizeUrl(String baseUrl, String redirectUri, String clientId, String response_type) { + private static String buildAuthorizeUrl(String baseUrl, String redirectUri, String clientId, String responseType) { return baseUrl - + urlPath + + URL_PATH + "?client_id=" + clientId - + "&response_type=" + response_type + + "&response_type=" + responseType + "&redirect_uri=" + URLEncoder.encode(redirectUri, StandardCharsets.UTF_8); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/endpoints/SamlLogoutAuthSourceEndpoint.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/endpoints/SamlLogoutAuthSourceEndpoint.java index 00a88e0da58..d0a55fa0f01 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/endpoints/SamlLogoutAuthSourceEndpoint.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/endpoints/SamlLogoutAuthSourceEndpoint.java @@ -1,15 +1,13 @@ package org.cloudfoundry.identity.uaa.integration.endpoints; -import org.cloudfoundry.identity.uaa.integration.pageObjects.Page; import org.cloudfoundry.identity.uaa.integration.pageObjects.SamlWelcomePage; import org.openqa.selenium.WebDriver; public class SamlLogoutAuthSourceEndpoint { - static final private String urlPath = "/module.php/core/logout"; + private static final String URL_PATH = "/module.php/core/logout"; - static public SamlWelcomePage logoutAuthSource_goesToSamlWelcomePage(WebDriver driver, String baseUrl, String authSource) { - driver.get(baseUrl + urlPath + "/" + authSource); + public static SamlWelcomePage assertThatLogoutAuthSource_goesToSamlWelcomePage(WebDriver driver, String baseUrl, String authSource) { + driver.get(baseUrl + URL_PATH + "/" + authSource); return new SamlWelcomePage(driver); } } - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ChangeEmailIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ChangeEmailIT.java index abbfb93f511..69177b7ee19 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ChangeEmailIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ChangeEmailIT.java @@ -2,34 +2,30 @@ import com.dumbster.smtp.SimpleSmtpServer; import com.dumbster.smtp.SmtpMessage; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import java.security.SecureRandom; import java.util.Iterator; import static org.apache.commons.lang3.StringUtils.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) -public class ChangeEmailIT { +class ChangeEmailIT { - @Autowired @Rule + @Autowired + @Rule public IntegrationTestRule integrationTestRule; @Autowired @@ -46,20 +42,20 @@ public class ChangeEmailIT { private String userEmail; - @Before - @After - public void logout_and_clear_cookies() { + @BeforeEach + @AfterEach + void logout_and_clear_cookies() { try { webDriver.get(baseUrl + "/logout.do"); - }catch (org.openqa.selenium.TimeoutException x) { + } catch (org.openqa.selenium.TimeoutException x) { //try again - this should not be happening - 20 second timeouts webDriver.get(baseUrl + "/logout.do"); } webDriver.manage().deleteAllCookies(); } - @Before - public void setUp() { + @BeforeEach + void setUp() { int randomInt = new SecureRandom().nextInt(); String adminAccessToken = testClient.getOAuthAccessToken("admin", "adminsecret", "client_credentials", "clients.read clients.write clients.secret clients.admin"); @@ -74,52 +70,52 @@ public void setUp() { } @Test - public void testChangeEmailWithLogout() { - String newEmail = testChangeEmail(true); + void changeEmailWithLogout() { + String newEmail = changeEmail(true); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Welcome")); - assertThat(webDriver.findElement(By.cssSelector(".alert-success")).getText(), containsString("Email address successfully verified. Login to access your account.")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Welcome"); + assertThat(webDriver.findElement(By.cssSelector(".alert-success")).getText()).contains("Email address successfully verified. Login to access your account."); signIn(newEmail, "secr3T"); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Where to?")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); } @Test - public void testChangeEmailWithoutLogout() { - String newEmail = testChangeEmail(false); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Account Settings")); - assertThat(webDriver.findElement(By.cssSelector(".alert-success")).getText(), containsString("Email address successfully verified.")); - assertThat(webDriver.findElement(By.cssSelector(".nav")).getText(), containsString(newEmail)); - assertThat(webDriver.findElement(By.cssSelector(".profile")).getText(), containsString(newEmail)); + void changeEmailWithoutLogout() { + String newEmail = changeEmail(false); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Account Settings"); + assertThat(webDriver.findElement(By.cssSelector(".alert-success")).getText()).contains("Email address successfully verified."); + assertThat(webDriver.findElement(By.cssSelector(".nav")).getText()).contains(newEmail); + assertThat(webDriver.findElement(By.cssSelector(".profile")).getText()).contains(newEmail); } - public String testChangeEmail(boolean logout) { + private String changeEmail(boolean logout) { signIn(userEmail, "secr3T"); int receivedEmailSize = simpleSmtpServer.getReceivedEmailSize(); webDriver.get(baseUrl + "/profile"); - Assert.assertEquals(userEmail, webDriver.findElement(By.cssSelector(".profile .email")).getText()); + assertThat(webDriver.findElement(By.cssSelector(".profile .email")).getText()).isEqualTo(userEmail); webDriver.findElement(By.linkText("Change Email")).click(); - Assert.assertEquals("Current Email Address: " + userEmail, webDriver.findElement(By.cssSelector(".email-display")).getText()); + assertThat(webDriver.findElement(By.cssSelector(".email-display")).getText()).isEqualTo("Current Email Address: " + userEmail); String newEmail = userEmail.replace("user", "new"); webDriver.findElement(By.name("newEmail")).sendKeys(newEmail); webDriver.findElement(By.xpath("//input[@value='Send Verification Link']")).click(); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Instructions Sent")); - assertEquals(receivedEmailSize + 1, simpleSmtpServer.getReceivedEmailSize()); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Instructions Sent"); + assertThat(simpleSmtpServer.getReceivedEmailSize()).isEqualTo(receivedEmailSize + 1); - Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); - SmtpMessage message = (SmtpMessage) receivedEmail.next(); + Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); + SmtpMessage message = receivedEmail.next(); receivedEmail.remove(); - assertEquals(newEmail, message.getHeaderValue("To")); - assertThat(message.getBody(), containsString("Verify your email")); + assertThat(message.getHeaderValue("To")).isEqualTo(newEmail); + assertThat(message.getBody()).contains("Verify your email"); String link = testClient.extractLink(message.getBody()); - assertFalse(contains(link, "@")); - assertFalse(contains(link, "%40")); + assertThat(contains(link, "@")).isFalse(); + assertThat(contains(link, "%40")).isFalse(); if (logout) { webDriver.get(baseUrl + "/logout.do"); @@ -131,7 +127,7 @@ public String testChangeEmail(boolean logout) { } @Test - public void testChangeEmailWithClientRedirect() { + void changeEmailWithClientRedirect() { signIn(userEmail, "secr3T"); webDriver.get(baseUrl + "/change_email?client_id=app"); @@ -147,7 +143,7 @@ public void testChangeEmailWithClientRedirect() { webDriver.get(link); webDriver.findElement(By.id("authorize")).click(); - assertThat(webDriver.getCurrentUrl(), startsWith("http://localhost:8080/app/")); + assertThat(webDriver.getCurrentUrl()).startsWith("http://localhost:8080/app/"); } private void signIn(String userName, String password) { @@ -156,6 +152,6 @@ private void signIn(String userName, String password) { webDriver.findElement(By.name("username")).sendKeys(userName); webDriver.findElement(By.name("password")).sendKeys(password); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Where to?")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/CreateAccountIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/CreateAccountIT.java index cd8e580896e..43771d0217a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/CreateAccountIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/CreateAccountIT.java @@ -15,47 +15,43 @@ import com.dumbster.smtp.SimpleSmtpServer; import com.dumbster.smtp.SmtpMessage; +import org.assertj.core.api.Assertions; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.oauth.client.test.TestAccounts; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import java.net.URL; import java.security.SecureRandom; import java.util.Collections; import java.util.Iterator; -import static org.apache.commons.lang3.StringUtils.contains; -import static org.apache.commons.lang3.StringUtils.isEmpty; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) -public class CreateAccountIT { +class CreateAccountIT { public static final String SECRET = "s3Cret"; + @Autowired TestAccounts testAccounts; - @Autowired @Rule + @Autowired + @Rule public IntegrationTestRule integrationTestRule; @Autowired @@ -73,57 +69,56 @@ public class CreateAccountIT { @Value("${integration.test.app_url}") String appUrl; - @Before - @After - public void logout_and_clear_cookies() { + @BeforeEach + @AfterEach + void logout_and_clear_cookies() { try { webDriver.get(baseUrl + "/logout.do"); - }catch (org.openqa.selenium.TimeoutException x) { + } catch (org.openqa.selenium.TimeoutException x) { //try again - this should not be happening - 20 second timeouts webDriver.get(baseUrl + "/logout.do"); } - webDriver.get(appUrl+"/j_spring_security_logout"); + webDriver.get(appUrl + "/j_spring_security_logout"); webDriver.manage().deleteAllCookies(); } @Test - public void testUserInitiatedSignup() { + void userInitiatedSignup() { int receivedEmailSize = simpleSmtpServer.getReceivedEmailSize(); String userEmail = startCreateUserFlow(SECRET); - assertEquals(receivedEmailSize + 1, simpleSmtpServer.getReceivedEmailSize()); - Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); - SmtpMessage message = (SmtpMessage) receivedEmail.next(); + assertThat(simpleSmtpServer.getReceivedEmailSize()).isEqualTo(receivedEmailSize + 1); + Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); + SmtpMessage message = receivedEmail.next(); receivedEmail.remove(); - assertEquals(userEmail, message.getHeaderValue("To")); + assertThat(message.getHeaderValue("To")).isEqualTo(userEmail); String body = message.getBody(); - assertThat(body, containsString("Activate your account")); + assertThat(body).contains("Activate your account"); - assertEquals("Create your account", webDriver.findElement(By.tagName("h1")).getText()); - assertEquals("Please check email for an activation link.", webDriver.findElement(By.cssSelector(".instructions-sent")).getText()); + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Create your account"); + assertThat(webDriver.findElement(By.cssSelector(".instructions-sent")).getText()).isEqualTo("Please check email for an activation link."); String link = testClient.extractLink(body); - assertFalse(isEmpty(link)); - assertFalse(contains(link, "@")); - assertFalse(contains(link, "%40")); + assertThat(link).isNotEmpty() + .doesNotContain("@") + .doesNotContain("%40"); webDriver.get(link); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), not(containsString("Where to?"))); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).doesNotContain("Where to?"); webDriver.findElement(By.name("username")).sendKeys(userEmail); webDriver.findElement(By.name("password")).sendKeys(SECRET); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Where to?")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); } @Test - public void testClientInitiatedSignup() { + void clientInitiatedSignup() { String userEmail = "user" + new SecureRandom().nextInt() + "@example.com"; - webDriver.get(baseUrl + "/create_account?client_id=app"); - assertEquals("Create your account", webDriver.findElement(By.tagName("h1")).getText()); + Assertions.assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Create your account"); int receivedEmailSize = simpleSmtpServer.getReceivedEmailSize(); @@ -132,35 +127,35 @@ public void testClientInitiatedSignup() { webDriver.findElement(By.name("password_confirmation")).sendKeys(SECRET); webDriver.findElement(By.xpath("//input[@value='Send activation link']")).click(); - assertEquals(receivedEmailSize + 1, simpleSmtpServer.getReceivedEmailSize()); - Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); - SmtpMessage message = (SmtpMessage) receivedEmail.next(); + assertThat(simpleSmtpServer.getReceivedEmailSize()).isEqualTo(receivedEmailSize + 1); + Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); + SmtpMessage message = receivedEmail.next(); receivedEmail.remove(); - assertEquals(userEmail, message.getHeaderValue("To")); - assertThat(message.getBody(), containsString("Activate your account")); + assertThat(message.getHeaderValue("To")).isEqualTo(userEmail); + assertThat(message.getBody()).contains("Activate your account"); - assertEquals("Please check email for an activation link.", webDriver.findElement(By.cssSelector(".instructions-sent")).getText()); + Assertions.assertThat(webDriver.findElement(By.cssSelector(".instructions-sent")).getText()).isEqualTo("Please check email for an activation link."); String link = testClient.extractLink(message.getBody()); - assertFalse(isEmpty(link)); + assertThat(link).isNotEmpty(); webDriver.get(link); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), not(containsString("Where to?"))); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).doesNotContain("Where to?"); webDriver.findElement(By.name("username")).sendKeys(userEmail); webDriver.findElement(By.name("password")).sendKeys(SECRET); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); // Authorize the app for some scopes - assertEquals("Application Authorization", webDriver.findElement(By.cssSelector("h1")).getText()); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).isEqualTo("Application Authorization"); webDriver.findElement(By.xpath("//button[text()='Authorize']")).click(); - assertEquals("Sample Home Page", webDriver.findElement(By.cssSelector("h1")).getText()); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).isEqualTo("Sample Home Page"); } @Test - public void testEnteringContraveningPasswordShowsErrorMessage() { + void enteringContraveningPasswordShowsErrorMessage() { startCreateUserFlow(new RandomValueStringGenerator(260).generate()); - assertEquals("Password must be no more than 255 characters in length.", webDriver.findElement(By.cssSelector(".alert-error")).getText()); + assertThat(webDriver.findElement(By.cssSelector(".alert-error")).getText()).isEqualTo("Password must be no more than 255 characters in length."); } private String startCreateUserFlow(String secret) { @@ -169,8 +164,7 @@ private String startCreateUserFlow(String secret) { webDriver.get(baseUrl + "/"); webDriver.findElement(By.xpath("//*[text()='Create account']")).click(); - assertEquals("Create your account", webDriver.findElement(By.tagName("h1")).getText()); - + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Create your account"); webDriver.findElement(By.name("email")).sendKeys(userEmail); webDriver.findElement(By.name("password")).sendKeys(secret); @@ -181,9 +175,9 @@ private String startCreateUserFlow(String secret) { } @Test - public void testEmailDomainRegisteredWithIDPDoesNotAllowAccountCreation() throws Exception { + void emailDomainRegisteredWithIDPDoesNotAllowAccountCreation() throws Exception { String adminToken = IntegrationTestUtils.getClientCredentialsToken(baseUrl, "admin", "adminsecret"); - IdentityProvider oidcProvider = new IdentityProvider().setName("oidc_provider").setActive(true).setType(OriginKeys.OIDC10).setOriginKey(OriginKeys.OIDC10).setConfig(new OIDCIdentityProviderDefinition()); + IdentityProvider oidcProvider = new IdentityProvider().setName("oidc_provider").setActive(true).setType(OriginKeys.OIDC10).setOriginKey(OriginKeys.OIDC10).setConfig(new OIDCIdentityProviderDefinition()); oidcProvider.getConfig().setAuthUrl(new URL("http://example.com")); oidcProvider.getConfig().setShowLinkText(false); oidcProvider.getConfig().setTokenUrl(new URL("http://localhost:8080/uaa/idp_login")); @@ -192,13 +186,12 @@ public void testEmailDomainRegisteredWithIDPDoesNotAllowAccountCreation() throws oidcProvider.getConfig().setRelyingPartyId("client_id"); oidcProvider.getConfig().setRelyingPartySecret("client_secret"); IntegrationTestUtils.createOrUpdateProvider(adminToken, baseUrl, oidcProvider); - try { + try { startCreateUserFlow("test"); - - assertEquals("Account sign-up is not required for this email domain. Please login with the identity provider", webDriver.findElement(By.cssSelector(".alert-error")).getText()); + assertThat(webDriver.findElement(By.cssSelector(".alert-error")).getText()).isEqualTo("Account sign-up is not required for this email domain. Please login with the identity provider"); webDriver.findElement(By.xpath("//input[@value='Login with provider']")).click(); - assertThat(webDriver.getCurrentUrl(), startsWith(oidcProvider.getConfig().getAuthUrl().toString())); + assertThat(webDriver.getCurrentUrl()).matches("^https?://example.com/.*"); } finally { IntegrationTestUtils.deleteProvider(adminToken, baseUrl, OriginKeys.UAA, OriginKeys.OIDC10); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/DefaultIntegrationTestConfig.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/DefaultIntegrationTestConfig.java index ff3312205ea..d9bed7097cc 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/DefaultIntegrationTestConfig.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/DefaultIntegrationTestConfig.java @@ -28,13 +28,13 @@ import java.io.IOException; import java.net.HttpURLConnection; -import java.util.concurrent.TimeUnit; +import java.time.Duration; @PropertySource("classpath:integration.test.properties") public class DefaultIntegrationTestConfig { - static final int IMPLICIT_WAIT_TIME = 30; - static final int PAGE_LOAD_TIMEOUT = 40; - static final int SCRIPT_TIMEOUT = 30; + static final Duration IMPLICIT_WAIT_TIME = Duration.ofSeconds(30L); + static final Duration PAGE_LOAD_TIMEOUT = Duration.ofSeconds(40L); + static final Duration SCRIPT_TIMEOUT = Duration.ofSeconds(30L); private final int timeoutMultiplier; @@ -59,31 +59,34 @@ public ChromeDriver webDriver() { System.setProperty("webdriver.chrome.verboseLogging", "true"); System.setProperty("webdriver.http.factory", "jdk-http-client"); + ChromeDriver driver = new ChromeDriver(getChromeOptions()); + driver.manage().timeouts() + .implicitlyWait(IMPLICIT_WAIT_TIME.multipliedBy(timeoutMultiplier)) + .pageLoadTimeout(PAGE_LOAD_TIMEOUT.multipliedBy(timeoutMultiplier)) + .scriptTimeout(SCRIPT_TIMEOUT.multipliedBy(timeoutMultiplier)); + driver.manage().window().setSize(new Dimension(1024, 768)); + return driver; + } + + private static ChromeOptions getChromeOptions() { ChromeOptions options = new ChromeOptions(); options.addArguments( - "--verbose", - "--headless=old", - "--window-position=-2400,-2400", - "--window-size=1024,768", - "--disable-web-security", - "--ignore-certificate-errors", - "--allow-running-insecure-content", - "--allow-insecure-localhost", - "--no-sandbox", - "--disable-gpu", - "--remote-allow-origins=*" + "--verbose", + // Comment the following line to run selenium test browser in Headed Mode + "--headless=old", + "--window-position=-2400,-2400", + "--window-size=1024,768", + "--disable-web-security", + "--ignore-certificate-errors", + "--allow-running-insecure-content", + "--allow-insecure-localhost", + "--no-sandbox", + "--disable-gpu", + "--remote-allow-origins=*" ); - options.setAcceptInsecureCerts(true); - ChromeDriver driver = new ChromeDriver(options); - - driver.manage().timeouts() - .implicitlyWait(IMPLICIT_WAIT_TIME * timeoutMultiplier, TimeUnit.SECONDS) - .pageLoadTimeout(PAGE_LOAD_TIMEOUT * timeoutMultiplier, TimeUnit.SECONDS) - .setScriptTimeout(SCRIPT_TIMEOUT * timeoutMultiplier, TimeUnit.SECONDS); - driver.manage().window().setSize(new Dimension(1024, 768)); - return driver; + return options; } @Bean(destroyMethod = "stop") @@ -108,6 +111,7 @@ public TestAccounts testAccounts() { } public static class HttpClientFactory extends SimpleClientHttpRequestFactory { + @Override protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { super.prepareConnection(connection, httpMethod); connection.setInstanceFollowRedirects(false); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java index bc30267fb4d..2bb914e8165 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java @@ -13,9 +13,9 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import com.dumbster.smtp.SimpleSmtpServer; import com.google.common.collect.Lists; import org.cloudfoundry.identity.uaa.ServerRunning; +import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; @@ -24,29 +24,24 @@ import org.cloudfoundry.identity.uaa.integration.util.ScreenshotOnFail; import org.cloudfoundry.identity.uaa.invitations.InvitationsRequest; import org.cloudfoundry.identity.uaa.invitations.InvitationsResponse; -import org.cloudfoundry.identity.uaa.oauth.client.test.TestAccounts; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.util.RetryRule; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.runner.RunWith; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; @@ -55,42 +50,28 @@ import java.sql.Timestamp; import java.util.concurrent.TimeUnit; - +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.SAML_AUTH_SOURCE; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.getZoneAdminToken; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.springframework.http.HttpMethod.POST; import static org.springframework.http.MediaType.APPLICATION_JSON; -@RunWith(SpringJUnit4ClassRunner.class) +@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) @ExtendWith(PollutionPreventionExtension.class) public class InvitationsIT { - @Autowired - TestAccounts testAccounts; - @Autowired @Rule public IntegrationTestRule integrationTestRule; - @Rule public ScreenshotOnFail screenShootRule = new ScreenshotOnFail(); @Rule public RetryRule retryRule = new RetryRule(3); - @Autowired WebDriver webDriver; - @Autowired - SimpleSmtpServer simpleSmtpServer; - @Autowired TestClient testClient; @@ -106,7 +87,48 @@ public class InvitationsIT { private String loginToken; private String testInviteEmail; - @Before + public static String createInvitation(String baseUrl, String username, String userEmail, String origin, String redirectUri, String loginToken, String scimToken) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + scimToken); + RestTemplate uaaTemplate = new RestTemplate(); + ScimUser scimUser = new ScimUser(); + scimUser.setPassword("password"); + scimUser.setUserName(username); + scimUser.setPrimaryEmail(userEmail); + scimUser.setOrigin(origin); + scimUser.setVerified(false); + + String userId = null; + try { + userId = IntegrationTestUtils.getUserIdByField(scimToken, baseUrl, origin, "email", userEmail); + scimUser = IntegrationTestUtils.getUser(scimToken, baseUrl, userId); + } catch (RuntimeException ignored) { + // ignored + } + if (userId == null) { + HttpEntity request = new HttpEntity<>(scimUser, headers); + ResponseEntity response = uaaTemplate.exchange(baseUrl + "/Users", POST, request, ScimUser.class); + if (response.getStatusCode().value() != HttpStatus.CREATED.value()) { + throw new IllegalStateException("Unable to create test user:" + scimUser); + } + userId = response.getBody().getId(); + } else { + scimUser.setVerified(false); + IntegrationTestUtils.updateUser(scimToken, baseUrl, scimUser); + } + + HttpHeaders invitationHeaders = new HttpHeaders(); + invitationHeaders.add("Authorization", "Bearer " + loginToken); + + Timestamp expiry = new Timestamp(System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(System.currentTimeMillis() + 24 * 3600, TimeUnit.MILLISECONDS)); + ExpiringCode expiringCode = new ExpiringCode(null, expiry, "{\"origin\":\"" + origin + "\", \"client_id\":\"app\", \"redirect_uri\":\"" + redirectUri + "\", \"user_id\":\"" + userId + "\", \"email\":\"" + userEmail + "\"}", null); + HttpEntity expiringCodeRequest = new HttpEntity<>(expiringCode, invitationHeaders); + ResponseEntity expiringCodeResponse = uaaTemplate.exchange(baseUrl + "/Codes", POST, expiringCodeRequest, ExpiringCode.class); + expiringCode = expiringCodeResponse.getBody(); + return expiringCode.getCode(); + } + + @BeforeEach public void setup() { scimToken = testClient.getOAuthAccessToken("admin", "adminsecret", "client_credentials", "scim.read,scim.write,clients.admin"); loginToken = testClient.getOAuthAccessToken("login", "loginsecret", "client_credentials", "oauth.login"); @@ -133,8 +155,8 @@ public void setup() { } } - @Before - @After + @BeforeEach + @AfterEach public void logout_and_clear_cookies() { try { webDriver.get(baseUrl + "/logout.do"); @@ -143,7 +165,7 @@ public void logout_and_clear_cookies() { webDriver.get(baseUrl + "/logout.do"); } webDriver.get(appUrl + "/j_spring_security_logout"); - SamlLogoutAuthSourceEndpoint.logoutAuthSource_goesToSamlWelcomePage(webDriver, IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); + SamlLogoutAuthSourceEndpoint.assertThatLogoutAuthSource_goesToSamlWelcomePage(webDriver, IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); webDriver.manage().deleteAllCookies(); webDriver.get("http://localhost:8080/app/"); @@ -151,7 +173,7 @@ public void logout_and_clear_cookies() { } @Test - public void invite_fails() { + void invite_fails() { RestTemplate uaaTemplate = new RestTemplate(); uaaTemplate.setErrorHandler(new DefaultResponseErrorHandler() { @Override @@ -163,11 +185,11 @@ protected boolean hasError(HttpStatus statusCode) { headers.setContentType(APPLICATION_JSON); HttpEntity request = new HttpEntity<>("{\"emails\":[\"marissa@test.org\"]}", headers); ResponseEntity response = uaaTemplate.exchange(baseUrl + "/invite_users/?client_id=admin&redirect_uri={uri}", POST, request, Void.class, "https://www.google.com"); - assertThat(response.getStatusCode(), is(HttpStatus.UNAUTHORIZED)); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); } @Test - public void testInviteUserWithClientRedirect() throws Exception { + void testInviteUserWithClientRedirect() { String userEmail = "user-" + new RandomValueStringGenerator().generate() + "@example.com"; //user doesn't exist performInviteUser(userEmail, false); @@ -189,37 +211,38 @@ public void performInviteUser(String email, boolean isVerified) { try { currentUserId = IntegrationTestUtils.getUserId(scimToken, baseUrl, OriginKeys.UAA, email); } catch (RuntimeException ignored) { + // ignored } - assertEquals(invitedUserId, currentUserId); + assertThat(currentUserId).isEqualTo(invitedUserId); webDriver.get(baseUrl + "/invitations/accept?code=" + code); if (!isVerified) { - assertEquals("Create your account", webDriver.findElement(By.tagName("h1")).getText()); + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Create your account"); webDriver.findElement(By.name("password")).sendKeys("secr3T"); webDriver.findElement(By.name("password_confirmation")).sendKeys("secr3T"); webDriver.findElement(By.xpath("//input[@value='Create account']")).click(); - assertTrue(IntegrationTestUtils.getUser(scimToken, baseUrl, OriginKeys.UAA, email).isVerified()); + assertThat(IntegrationTestUtils.getUser(scimToken, baseUrl, OriginKeys.UAA, email).isVerified()).isTrue(); webDriver.findElement(By.name("username")).sendKeys(email); webDriver.findElement(By.name("password")).sendKeys("secr3T"); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - Assert.assertEquals(redirectUri, webDriver.getCurrentUrl()); + assertThat(webDriver.getCurrentUrl()).isEqualTo(redirectUri); } else { //redirect to the home page to login - Assert.assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Welcome!")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Welcome!"); } String acceptedUserId = IntegrationTestUtils.getUserId(scimToken, baseUrl, OriginKeys.UAA, email); if (currentUserId == null) { - assertEquals(invitedUserId, acceptedUserId); + assertThat(acceptedUserId).isEqualTo(invitedUserId); } else { - assertEquals(currentUserId, acceptedUserId); + assertThat(acceptedUserId).isEqualTo(currentUserId); } } @Test - public void acceptInvitation_for_samlUser() throws Exception { + void acceptInvitation_for_samlUser() throws Exception { webDriver.get(baseUrl + "/logout.do"); UaaClientDetails appClient = IntegrationTestUtils.getClient(scimToken, baseUrl, "app"); @@ -243,31 +266,31 @@ public void acceptInvitation_for_samlUser() throws Exception { webDriver.findElement(By.id("application_authorization")); String acceptedUsername = IntegrationTestUtils.getUsernameById(scimToken, baseUrl, invitedUserId); //webdriver follows redirects so we should be on the UAA authorization page - assertEquals("user_only_for_invitations_test", acceptedUsername); + assertThat(acceptedUsername).isEqualTo("user_only_for_invitations_test"); //external users should default to not being "verified" since we can't determine this ScimUser user = IntegrationTestUtils.getUser(scimToken, baseUrl, invitedUserId); - assertFalse(user.isVerified()); + assertThat(user.isVerified()).isFalse(); } @Test - public void testInsecurePasswordDisplaysErrorMessage() { + void testInsecurePasswordDisplaysErrorMessage() { String code = createInvitation(); webDriver.get(baseUrl + "/invitations/accept?code=" + code); - assertEquals("Create your account", webDriver.findElement(By.tagName("h1")).getText()); + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Create your account"); String newPassword = new RandomValueStringGenerator(260).generate(); webDriver.findElement(By.name("password")).sendKeys(newPassword); webDriver.findElement(By.name("password_confirmation")).sendKeys(newPassword); webDriver.findElement(By.xpath("//input[@value='Create account']")).click(); - assertThat(webDriver.findElement(By.cssSelector(".alert-error")).getText(), containsString("Password must be no more than 255 characters in length.")); + assertThat(webDriver.findElement(By.cssSelector(".alert-error")).getText()).contains("Password must be no more than 255 characters in length."); webDriver.findElement(By.name("password")); webDriver.findElement(By.name("password_confirmation")); } @Test - public void invitedOIDCUserVerified() throws Exception { + void invitedOIDCUserVerified() throws Exception { String clientId = "invite-client" + new RandomValueStringGenerator().generate(); UaaClientDetails clientDetails = new UaaClientDetails(clientId, null, null, "client_credentials", "scim.invite"); clientDetails.setClientSecret("invite-client-secret"); @@ -284,7 +307,7 @@ public void invitedOIDCUserVerified() throws Exception { body.setEmails(emailList); HttpEntity request = new HttpEntity<>(body, headers); ResponseEntity response = uaaTemplate.exchange(baseUrl + "/invite_users?client_id=app&redirect_uri=" + appUrl, POST, request, InvitationsResponse.class); - assertThat(response.getStatusCode(), is(HttpStatus.OK)); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); String userId = response.getBody().getNewInvites().get(0).getUserId(); URL inviteLink = response.getBody().getNewInvites().get(0).getInviteLink(); @@ -297,7 +320,7 @@ public void invitedOIDCUserVerified() throws Exception { webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); ScimUser user = IntegrationTestUtils.getUser(scimToken, baseUrl, userId); - assertTrue(user.isVerified()); + assertThat(user.isVerified()).isTrue(); webDriver.get(IntegrationTestUtils.OIDC_ACCEPTANCE_URL + "logout.do"); IntegrationTestUtils.deleteProvider(getZoneAdminToken(baseUrl, serverRunning), baseUrl, "uaa", "puppy-invite"); @@ -311,44 +334,4 @@ private String createInvitation() { private String createInvitation(String username, String userEmail, String redirectUri, String origin) { return createInvitation(baseUrl, username, userEmail, origin, redirectUri, loginToken, scimToken); } - - public static String createInvitation(String baseUrl, String username, String userEmail, String origin, String redirectUri, String loginToken, String scimToken) { - HttpHeaders headers = new HttpHeaders(); - headers.add("Authorization", "Bearer " + scimToken); - RestTemplate uaaTemplate = new RestTemplate(); - ScimUser scimUser = new ScimUser(); - scimUser.setPassword("password"); - scimUser.setUserName(username); - scimUser.setPrimaryEmail(userEmail); - scimUser.setOrigin(origin); - scimUser.setVerified(false); - - String userId = null; - try { - userId = IntegrationTestUtils.getUserIdByField(scimToken, baseUrl, origin, "email", userEmail); - scimUser = IntegrationTestUtils.getUser(scimToken, baseUrl, userId); - } catch (RuntimeException ignored) { - } - if (userId == null) { - HttpEntity request = new HttpEntity<>(scimUser, headers); - ResponseEntity response = uaaTemplate.exchange(baseUrl + "/Users", POST, request, ScimUser.class); - if (response.getStatusCode().value()!= HttpStatus.CREATED.value()) { - throw new IllegalStateException("Unable to create test user:"+scimUser); - } - userId = response.getBody().getId(); - } else { - scimUser.setVerified(false); - IntegrationTestUtils.updateUser(scimToken, baseUrl, scimUser); - } - - HttpHeaders invitationHeaders = new HttpHeaders(); - invitationHeaders.add("Authorization", "Bearer " + loginToken); - - Timestamp expiry = new Timestamp(System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(System.currentTimeMillis() + 24 * 3600, TimeUnit.MILLISECONDS)); - ExpiringCode expiringCode = new ExpiringCode(null, expiry, "{\"origin\":\"" + origin + "\", \"client_id\":\"app\", \"redirect_uri\":\"" + redirectUri + "\", \"user_id\":\"" + userId + "\", \"email\":\"" + userEmail + "\"}", null); - HttpEntity expiringCodeRequest = new HttpEntity<>(expiringCode, invitationHeaders); - ResponseEntity expiringCodeResponse = uaaTemplate.exchange(baseUrl + "/Codes", POST, expiringCodeRequest, ExpiringCode.class); - expiringCode = expiringCodeResponse.getBody(); - return expiringCode.getCode(); - } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java index 45ca407a72d..833adfea4a9 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java @@ -14,9 +14,9 @@ package org.cloudfoundry.identity.uaa.integration.feature; import com.fasterxml.jackson.core.type.TypeReference; - import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.account.UserInfoResponse; +import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.endpoints.SamlLogoutAuthSourceEndpoint; import org.cloudfoundry.identity.uaa.integration.pageObjects.Page; @@ -24,6 +24,7 @@ import org.cloudfoundry.identity.uaa.integration.util.ScreenshotOnFail; import org.cloudfoundry.identity.uaa.oauth.client.test.TestAccounts; import org.cloudfoundry.identity.uaa.oauth.common.DefaultOAuth2AccessToken; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; @@ -35,19 +36,17 @@ import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.openqa.selenium.By; import org.openqa.selenium.Cookie; import org.openqa.selenium.WebDriver; @@ -56,14 +55,11 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; -import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import java.net.Inet4Address; @@ -72,37 +68,26 @@ import java.net.URLEncoder; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; - +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.SAML_AUTH_SOURCE; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.isMember; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SUB; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_NAME; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -@RunWith(SpringJUnit4ClassRunner.class) + +@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) public class OIDCLoginIT { + private static final String PASSWORD_AUTHN_CTX = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"; + @Autowired @Rule public IntegrationTestRule integrationTestRule; @@ -110,48 +95,45 @@ public class OIDCLoginIT { @Rule public ScreenshotOnFail screenShootRule = new ScreenshotOnFail(); - @Autowired - RestOperations restOperations; - @Autowired WebDriver webDriver; @Value("${integration.test.base_url}") String baseUrl; - @Value("${integration.test.app_url}") - String appUrl; - @Autowired TestAccounts testAccounts; - @Autowired - TestClient testClient; - - private static final String PASSWORD_AUTHN_CTX = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"; - private ServerRunning serverRunning = ServerRunning.isRunning(); private IdentityZone zone; private String adminToken; private String subdomain; private String zoneUrl; - private IdentityProvider identityProvider; + private IdentityProvider> identityProvider; private String clientCredentialsToken; private UaaClientDetails zoneClient; private ScimGroup createdGroup; private RestTemplate identityClient; - @Before - public void setUp() throws Exception { - assertTrue("/etc/hosts should contain the host 'oidcloginit.localhost' for this test to work", doesSupportZoneDNS()); + public static boolean doesSupportZoneDNS() { + try { + return Arrays.equals(Inet4Address.getByName("oidcloginit.localhost").getAddress(), new byte[]{127, 0, 0, 1}); + } catch (UnknownHostException e) { + return false; + } + } + + @BeforeEach + void setUp() throws Exception { + assertThat(doesSupportZoneDNS()).as("/etc/hosts should contain the host 'oidcloginit.localhost' for this test to work").isTrue(); screenShootRule.setWebDriver(webDriver); subdomain = "oidcloginit"; //identity client token identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); IdentityZoneConfiguration zoneConfiguration = new IdentityZoneConfiguration(); @@ -192,10 +174,7 @@ public void setUp() throws Exception { config.setSkipSslValidation(true); config.setRelyingPartyId("identity"); config.setRelyingPartySecret("identitysecret"); - List requestedScopes = new ArrayList<>(); - requestedScopes.add("openid"); - requestedScopes.add("cloud_controller.read"); - config.setScopes(requestedScopes); + config.setScopes(List.of("openid", "cloud_controller.read")); identityProvider.setConfig(config); identityProvider.setOriginKey("puppy"); identityProvider.setIdentityZoneId(zone.getId()); @@ -208,7 +187,6 @@ public void setUp() throws Exception { createdGroupExternalMapping.setOrigin(identityProvider.getOriginKey()); IntegrationTestUtils.mapExternalGroup(adminToken, subdomain, baseUrl, createdGroupExternalMapping); - zoneClient = new UaaClientDetails(new RandomValueStringGenerator().generate(), null, "openid,user_attributes", "authorization_code,client_credentials", "uaa.admin,scim.read,scim.write,uaa.resource", zoneUrl); zoneClient.setClientSecret("secret"); zoneClient.setAutoApproveScopes(Collections.singleton("true")); @@ -220,25 +198,17 @@ public void setUp() throws Exception { public void updateProvider() { identityProvider = IntegrationTestUtils.createOrUpdateProvider(clientCredentialsToken, baseUrl, identityProvider); - assertNull(identityProvider.getConfig().getRelyingPartySecret()); + assertThat(identityProvider.getConfig().getRelyingPartySecret()).isNull(); } - public static boolean doesSupportZoneDNS() { - try { - return Arrays.equals(Inet4Address.getByName("oidcloginit.localhost").getAddress(), new byte[]{127, 0, 0, 1}); - } catch (UnknownHostException e) { - return false; - } - } - - @After - public void tearDown() throws URISyntaxException { + @AfterEach + void tearDown() throws URISyntaxException { doLogout(zoneUrl); IntegrationTestUtils.deleteZone(baseUrl, zone.getId(), adminToken); } private void doLogout(String zoneUrl) { - SamlLogoutAuthSourceEndpoint.logoutAuthSource_goesToSamlWelcomePage(webDriver, IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); + SamlLogoutAuthSourceEndpoint.assertThatLogoutAuthSource_goesToSamlWelcomePage(webDriver, IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); webDriver.manage().deleteAllCookies(); for (String url : Arrays.asList(baseUrl + "/logout.do", zoneUrl + "/logout.do")) { @@ -259,24 +229,24 @@ private void login(String zoneUrl, String userName, String password) { webDriver.get(zoneUrl + "/logout.do"); webDriver.get(zoneUrl + "/"); Cookie beforeLogin = webDriver.manage().getCookieNamed("JSESSIONID"); - assertNotNull(beforeLogin); - assertNotNull(beforeLogin.getValue()); + assertThat(beforeLogin).isNotNull(); + assertThat(beforeLogin.getValue()).isNotNull(); webDriver.findElement(By.linkText("My OIDC Provider")).click(); - assertThat(webDriver.getCurrentUrl(), containsString(baseUrl)); + assertThat(webDriver.getCurrentUrl()).contains(baseUrl); webDriver.findElement(By.name("username")).sendKeys(userName); webDriver.findElement(By.name("password")).sendKeys(password); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - Assert.assertThat(webDriver.getCurrentUrl(), containsString(zoneUrl)); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Where to?")); + assertThat(webDriver.getCurrentUrl()).contains(zoneUrl); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); Cookie afterLogin = webDriver.manage().getCookieNamed("JSESSIONID"); - assertNotNull(afterLogin); - assertNotNull(afterLogin.getValue()); - assertNotEquals(beforeLogin.getValue(), afterLogin.getValue()); + assertThat(afterLogin).isNotNull(); + assertThat(afterLogin.getValue()).isNotNull() + .isNotEqualTo(beforeLogin.getValue()); } @Test - public void successfulLoginWithOIDCProvider() { + void successfulLoginWithOIDCProvider() { Long beforeTest = System.currentTimeMillis(); validateSuccessfulOIDCLogin(zoneUrl, testAccounts.getUserName(), testAccounts.getPassword()); Long afterTest = System.currentTimeMillis(); @@ -285,12 +255,12 @@ public void successfulLoginWithOIDCProvider() { ScimUser user = IntegrationTestUtils .getUserByZone(zoneAdminToken, baseUrl, subdomain, testAccounts.getUserName()); IntegrationTestUtils.validateUserLastLogon(user, beforeTest, afterTest); - assertEquals(origUserId, user.getExternalId()); - assertEquals(user.getGivenName(), user.getUserName()); + assertThat(user.getExternalId()).isEqualTo(origUserId); + assertThat(user.getUserName()).isEqualTo(user.getGivenName()); } @Test - public void loginWithOIDCProviderUpdatesExternalId() { + void loginWithOIDCProviderUpdatesExternalId() { Long beforeTest = System.currentTimeMillis(); String zoneAdminToken = IntegrationTestUtils.getClientCredentialsToken(serverRunning, "admin", "adminsecret"); @@ -301,112 +271,113 @@ public void loginWithOIDCProviderUpdatesExternalId() { minimalShadowUser.setOrigin(identityProvider.getOriginKey()); IntegrationTestUtils.createUser(zoneClientToken, zoneUrl, minimalShadowUser, null); ScimUser userCreated = IntegrationTestUtils.getUserByZone(zoneAdminToken, baseUrl, subdomain, testAccounts.getUserName()); - assertFalse(StringUtils.hasText(userCreated.getExternalId())); + assertThat(StringUtils.hasText(userCreated.getExternalId())).isFalse(); validateSuccessfulOIDCLogin(zoneUrl, testAccounts.getUserName(), testAccounts.getPassword()); Long afterTest = System.currentTimeMillis(); String origUserId = IntegrationTestUtils.getUserId(adminToken, baseUrl, "uaa", testAccounts.getUserName()); ScimUser user = IntegrationTestUtils.getUserByZone(zoneAdminToken, baseUrl, subdomain, testAccounts.getUserName()); IntegrationTestUtils.validateUserLastLogon(user, beforeTest, afterTest); - assertEquals(origUserId, user.getExternalId()); - assertEquals(user.getGivenName(), user.getUserName()); - assertTrue(StringUtils.hasText(user.getExternalId())); + assertThat(user.getExternalId()).isEqualTo(origUserId); + assertThat(user.getUserName()).isEqualTo(user.getGivenName()); + assertThat(user.getExternalId()).isNotEmpty().doesNotContainOnlyWhitespaces(); } @Test - public void testLoginWithInactiveProviderDoesNotWork() { + void testLoginWithInactiveProviderDoesNotWork() { webDriver.get(zoneUrl + "/logout.do"); webDriver.get(zoneUrl + "/"); Cookie beforeLogin = webDriver.manage().getCookieNamed("JSESSIONID"); - assertNotNull(beforeLogin); - assertNotNull(beforeLogin.getValue()); + assertThat(beforeLogin).isNotNull(); + assertThat(beforeLogin.getValue()).isNotNull(); String linkLocation = webDriver.findElement(By.linkText("My OIDC Provider")).getAttribute("href"); identityProvider.setActive(false); updateProvider(); webDriver.get(linkLocation); - Assert.assertThat(webDriver.getCurrentUrl(), containsString(baseUrl)); + Page.assertThatUrlEventuallySatisfies(webDriver, asa -> asa.contains(baseUrl)); webDriver.findElement(By.name("username")).sendKeys(testAccounts.getUserName()); webDriver.findElement(By.name("password")).sendKeys(testAccounts.getPassword()); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - Assert.assertThat(webDriver.getCurrentUrl(), containsString(zoneUrl)); - assertThat(webDriver.getPageSource(), containsString("Could not resolve identity provider with given origin.")); + Page.assertThatUrlEventuallySatisfies(webDriver, asa -> asa.contains(zoneUrl)); + + assertThat(webDriver.getPageSource()).contains("Could not resolve identity provider with given origin."); webDriver.get(zoneUrl + "/"); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Welcome to")); + Page.assertThatUrlEventuallySatisfies(webDriver, urlAssert -> urlAssert.endsWith("/login")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Welcome to"); } @Test - public void testLoginWithLoginHintUaa() { + void testLoginWithLoginHintUaa() { webDriver.get(zoneUrl + "/logout.do"); String loginHint = URLEncoder.encode("{\"origin\":\"puppy\"}", StandardCharsets.UTF_8); webDriver.get(zoneUrl + "/login?login_hint=" + loginHint); - - Assert.assertThat(webDriver.getCurrentUrl(), startsWith(baseUrl)); + assertThat(webDriver.getCurrentUrl()).startsWith(baseUrl); } @Test - public void successfulLoginWithOIDCProviderWithExternalGroups() { + void successfulLoginWithOIDCProviderWithExternalGroups() { validateSuccessfulOIDCLogin(zoneUrl, testAccounts.getUserName(), testAccounts.getPassword()); - String adminToken = IntegrationTestUtils.getClientCredentialsToken(serverRunning, "admin", "adminsecret"); - ScimUser user = IntegrationTestUtils.getUserByZone(adminToken, baseUrl, subdomain, testAccounts.getUserName()); - assertEquals(user.getGivenName(), user.getUserName()); + String anAdminToken = IntegrationTestUtils.getClientCredentialsToken(serverRunning, "admin", "adminsecret"); + ScimUser user = IntegrationTestUtils.getUserByZone(anAdminToken, baseUrl, subdomain, testAccounts.getUserName()); + assertThat(user.getUserName()).isEqualTo(user.getGivenName()); - ScimGroup updatedCreatedGroup = IntegrationTestUtils.getGroup(adminToken, subdomain, baseUrl, createdGroup.getDisplayName()); - assertTrue(isMember(user.getId(), updatedCreatedGroup)); - assertTrue("Expect group members to have origin: " + user.getOrigin(), updatedCreatedGroup.getMembers().stream().allMatch(p -> user.getOrigin().equals(p.getOrigin()))); + ScimGroup updatedCreatedGroup = IntegrationTestUtils.getGroup(anAdminToken, subdomain, baseUrl, createdGroup.getDisplayName()); + assertThat(isMember(user.getId(), updatedCreatedGroup)).isTrue(); + assertThat(updatedCreatedGroup.getMembers().stream().allMatch(p -> user.getOrigin().equals(p.getOrigin()))).as("Expect group members to have origin: " + user.getOrigin()).isTrue(); } @Test - public void successfulLoginWithOIDCProviderAndClientAuthInBody() { + void successfulLoginWithOIDCProviderAndClientAuthInBody() { identityProvider.getConfig().setClientAuthInBody(true); - assertTrue(identityProvider.getConfig().isClientAuthInBody()); + assertThat(identityProvider.getConfig().isClientAuthInBody()).isTrue(); updateProvider(); - assertTrue(identityProvider.getConfig().isClientAuthInBody()); + assertThat(identityProvider.getConfig().isClientAuthInBody()).isTrue(); validateSuccessfulOIDCLogin(zoneUrl, testAccounts.getUserName(), testAccounts.getPassword()); } @Test - public void successfulLoginWithOIDCProviderSetsLastLogin() { + void successfulLoginWithOIDCProviderSetsLastLogin() { login(zoneUrl, testAccounts.getUserName(), testAccounts.getPassword()); doLogout(zoneUrl); login(zoneUrl, testAccounts.getUserName(), testAccounts.getPassword()); - assertNotNull(webDriver.findElement(By.cssSelector("#last_login_time"))); + assertThat(webDriver.findElement(By.cssSelector("#last_login_time"))).isNotNull(); } @Test - public void successfulLoginWithOIDCProvider_MultiKeys() throws Exception { + void successfulLoginWithOIDCProvider_MultiKeys() throws Exception { identityProvider.getConfig().setTokenKeyUrl(new URL(baseUrl + "/token_keys")); updateProvider(); validateSuccessfulOIDCLogin(zoneUrl, testAccounts.getUserName(), testAccounts.getPassword()); } @Test - public void login_with_wrong_keys() throws Exception { + void login_with_wrong_keys() throws Exception { identityProvider.getConfig().setTokenKeyUrl(new URL("https://login.microsoftonline.com/9bc40aaf-e150-4c30-bb3c-a8b3b677266e/discovery/v2.0/keys")); updateProvider(); webDriver.get(zoneUrl + "/login"); webDriver.findElement(By.linkText("My OIDC Provider")).click(); - Assert.assertThat(webDriver.getCurrentUrl(), containsString(baseUrl)); + assertThat(webDriver.getCurrentUrl()).contains(baseUrl); webDriver.findElement(By.name("username")).sendKeys("marissa"); webDriver.findElement(By.name("password")).sendKeys("koala"); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - assertThat(webDriver.getCurrentUrl(), containsString(zoneUrl + "/oauth_error")); - // no error as parameter sent - assertThat(webDriver.getCurrentUrl(), not(containsString("?error="))); - assertThat(webDriver.findElement(By.cssSelector("h2")).getText(), containsString("There was an error when authenticating against the external identity provider")); + assertThat(webDriver.getCurrentUrl()).contains(zoneUrl + "/oauth_error") + // no error as parameter sent + .doesNotContain("?error="); + assertThat(webDriver.findElement(By.cssSelector("h2")).getText()).contains("There was an error when authenticating against the external identity provider"); List cookies = IntegrationTestUtils.getAccountChooserCookies(zoneUrl, webDriver); - assertThat(cookies, not(Matchers.hasItem(startsWith("Saved-Account-")))); + assertThat(cookies).noneMatch(e -> e.startsWith("Saved-Account-")); } @Test - public void testShadowUserNameDefaultsToOIDCSubjectClaim() { + void testShadowUserNameDefaultsToOIDCSubjectClaim() { Map attributeMappings = new HashMap<>(identityProvider.getConfig().getAttributeMappings()); attributeMappings.remove(USER_NAME_ATTRIBUTE_NAME); identityProvider.getConfig().setAttributeMappings(attributeMappings); @@ -423,7 +394,7 @@ public void testShadowUserNameDefaultsToOIDCSubjectClaim() { webDriver.get(baseUrl); Cookie cookie = webDriver.manage().getCookieNamed("JSESSIONID"); - ServerRunning serverRunning = ServerRunning.isRunning(); + serverRunning = ServerRunning.isRunning(); serverRunning.setHostName("localhost"); String clientId = "client" + new RandomValueStringGenerator(5).generate(); @@ -433,53 +404,50 @@ public void testShadowUserNameDefaultsToOIDCSubjectClaim() { IntegrationTestUtils.createClient(adminToken, baseUrl, client); Map authCodeTokenResponse = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, - UaaTestAccounts.standard(serverRunning), - clientId, - "clientsecret", - null, - null, - "token id_token", - cookie.getValue(), - baseUrl, - null, - false); + clientId, + "clientsecret", + null, + null, + "token id_token", + cookie.getValue(), + baseUrl, + null, + false); //validate that we have an ID token, and that it contains costCenter and manager values String idToken = authCodeTokenResponse.get("id_token"); - assertNotNull(idToken); + assertThat(idToken).isNotNull(); Jwt idTokenClaims = JwtHelper.decode(idToken); - Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() { + Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference<>() { }); String expectedUsername = (String) claims.get(SUB); - String adminToken = IntegrationTestUtils.getClientCredentialsToken(zoneUrl, zoneClient.getClientId(), zoneClient.getClientSecret()); - ScimUser shadowUser = IntegrationTestUtils.getUser(adminToken, zoneUrl, identityProvider.getOriginKey(), expectedUsername); - assertEquals(expectedUsername, shadowUser.getUserName()); + String anAdminToken = IntegrationTestUtils.getClientCredentialsToken(zoneUrl, zoneClient.getClientId(), zoneClient.getClientSecret()); + ScimUser shadowUser = IntegrationTestUtils.getUser(anAdminToken, zoneUrl, identityProvider.getOriginKey(), expectedUsername); + assertThat(shadowUser.getUserName()).isEqualTo(expectedUsername); } @Test - public void successfulLoginWithOIDC_and_SAML_Provider_PlusRefreshRotation() throws Exception { + @Disabled("SAML test fails: acr value is not set in the id_token") + void successfulLoginWithOIDC_and_SAML_Provider_PlusRefreshRotation() throws Exception { SamlIdentityProviderDefinition saml = IntegrationTestUtils.createSimplePHPSamlIDP("simplesamlphp", OriginKeys.UAA); saml.setLinkText("SAML Login"); saml.setShowSamlLink(true); IdentityProvider samlProvider = new IdentityProvider<>(); samlProvider - .setName("SAML to default zone") - .setOriginKey(saml.getIdpEntityAlias()) - .setType(OriginKeys.SAML) - .setConfig(saml) - .setIdentityZoneId(saml.getZoneId()); + .setName("SAML to default zone") + .setOriginKey(saml.getIdpEntityAlias()) + .setType(OriginKeys.SAML) + .setConfig(saml) + .setIdentityZoneId(saml.getZoneId()); samlProvider = IntegrationTestUtils.createOrUpdateProvider(clientCredentialsToken, baseUrl, samlProvider); try { - - /* - This test creates an OIDC provider. That provider in turn has a SAML provider. - The end user is authenticated using OIDC federating to SAML - */ + // This test creates an OIDC provider. That provider in turn has a SAML provider. + // The end user is authenticated using OIDC federating to SAML webDriver.get(zoneUrl + "/login"); webDriver.findElement(By.linkText("My OIDC Provider")).click(); - Assert.assertThat(webDriver.getCurrentUrl(), containsString(baseUrl)); + assertThat(webDriver.getCurrentUrl()).contains(baseUrl); webDriver.findElement(By.linkText("SAML Login")).click(); webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); @@ -488,85 +456,80 @@ public void successfulLoginWithOIDC_and_SAML_Provider_PlusRefreshRotation() thro webDriver.findElement(By.name("password")).sendKeys("saml6"); webDriver.findElement(By.id("submit_button")).click(); - Page.validateUrlStartsWithWait(webDriver, zoneUrl); - assertThat(webDriver.getCurrentUrl(), containsString(zoneUrl)); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Where to?")); + Page.assertThatUrlEventuallySatisfies(webDriver, assertUrl -> assertUrl.startsWith(zoneUrl)); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); Cookie cookie = webDriver.manage().getCookieNamed("JSESSIONID"); - ServerRunning serverRunning = ServerRunning.isRunning(); + serverRunning = ServerRunning.isRunning(); serverRunning.setHostName(zone.getSubdomain() + ".localhost"); Map authCodeTokenResponse = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, - UaaTestAccounts.standard(serverRunning), - zoneClient.getClientId(), - "secret", - null, - null, - "token id_token", - cookie.getValue(), - null, - null, - false); + zoneClient.getClientId(), + "secret", + null, + null, + "token id_token", + cookie.getValue(), + null, + null, + false); //validate that we have an ID token, and that it contains costCenter and manager values String idToken = authCodeTokenResponse.get("id_token"); - assertNotNull(idToken); + assertThat(idToken).isNotNull(); Jwt idTokenClaims = JwtHelper.decode(idToken); - Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() { + Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference<>() { }); - assertNotNull("id_token should contain ACR claim", claims.get(ClaimConstants.ACR)); + assertThat(claims) + .as("id_token should contain ACR claim") + .containsKey(ClaimConstants.ACR); Map acr = (Map) claims.get(ClaimConstants.ACR); - assertNotNull("acr claim should contain values attribute", acr.get("values")); - assertThat((List) acr.get("values"), containsInAnyOrder(PASSWORD_AUTHN_CTX)); - + assertThat((List) acr.get("values")) + .as("acr claim should contain values attribute") + .contains(PASSWORD_AUTHN_CTX); UserInfoResponse userInfo = IntegrationTestUtils.getUserInfo(zoneUrl, authCodeTokenResponse.get("access_token")); Map> userAttributeMap = userInfo.getUserAttributes(); - assertNotNull(userAttributeMap); + assertThat(userAttributeMap).isNotNull(); List clientIds = userAttributeMap.get("the_client_id"); - assertNotNull(clientIds); - assertEquals("identity", clientIds.get(0)); + assertThat(clientIds).isNotNull(); + assertThat(clientIds.get(0)).isEqualTo("identity"); setRefreshTokenRotate(false); String refreshToken1 = getRefreshTokenResponse(serverRunning, authCodeTokenResponse.get("refresh_token")); String refreshToken2 = getRefreshTokenResponse(serverRunning, refreshToken1); - assertEquals("New refresh token should be equal to the old one.", - refreshToken1, - refreshToken2); + assertThat(refreshToken2).as("New refresh token should be equal to the old one.").isEqualTo(refreshToken1); setRefreshTokenRotate(true); refreshToken1 = getRefreshTokenResponse(serverRunning, refreshToken2); refreshToken2 = getRefreshTokenResponse(serverRunning, refreshToken1); - assertNotEquals("New access token should be different from the old one.", - refreshToken1, - refreshToken2); + assertThat(refreshToken2).as("New access token should be different from the old one.").isNotEqualTo(refreshToken1); } finally { IntegrationTestUtils.deleteProvider(clientCredentialsToken, baseUrl, OriginKeys.UAA, samlProvider.getOriginKey()); } } @Test - public void testResponseTypeRequired() { + void testResponseTypeRequired() { UaaClientDetails uaaClient = new UaaClientDetails(new RandomValueStringGenerator().generate(), null, "openid,user_attributes", "authorization_code,client_credentials", "uaa.admin,scim.read,scim.write,uaa.resource", baseUrl); uaaClient.setClientSecret("secret"); uaaClient.setAutoApproveScopes(Collections.singleton("true")); uaaClient = IntegrationTestUtils.createClient(clientCredentialsToken, baseUrl, uaaClient); uaaClient.setClientSecret("secret"); - StringBuilder uriBuilder = new StringBuilder(); - uriBuilder.append(baseUrl).append("/oauth/authorize").append("?scope=openid&client_id=").append(uaaClient.getClientId()).append("&redirect_uri=").append(baseUrl); - webDriver.get(uriBuilder.toString()); + String uriBuilder = baseUrl + "/oauth/authorize" + "?scope=openid&client_id=" + uaaClient.getClientId() + "&redirect_uri=" + baseUrl; + webDriver.get(uriBuilder); webDriver.findElement(By.name("username")).sendKeys(testAccounts.getUserName()); webDriver.findElement(By.name("password")).sendKeys(testAccounts.getPassword()); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - assertThat(webDriver.getCurrentUrl(), containsString("error=invalid_request")); - assertThat(webDriver.getCurrentUrl(), containsString("error_description=Missing%20response_type%20in%20authorization%20request")); + assertThat(webDriver.getCurrentUrl()).contains("error=invalid_request") + .contains("error_description=Missing%20response_type%20in%20authorization%20request"); } @Test - public void successfulUaaLogoutTriggersExternalOIDCProviderLogout_whenConfiguredTo() { + void successfulUaaLogoutTriggersExternalOIDCProviderLogout_whenConfiguredTo() { identityProvider.getConfig().setPerformRpInitiatedLogout(true); updateProvider(); @@ -574,12 +537,11 @@ public void successfulUaaLogoutTriggersExternalOIDCProviderLogout_whenConfigured String externalOIDCProviderLoginPage = baseUrl; webDriver.get(externalOIDCProviderLoginPage); - Assert.assertThat("Did not land on the external OIDC provider login page (as an unauthenticated user).", - webDriver.getCurrentUrl(), endsWith("/login")); + assertThat(webDriver.getCurrentUrl()).as("Did not land on the external OIDC provider login page (as an unauthenticated user).").endsWith("/login"); } @Test - public void successfulUaaLogoutDoesNotTriggerExternalOIDCProviderLogout_whenConfiguredNotTo() { + void successfulUaaLogoutDoesNotTriggerExternalOIDCProviderLogout_whenConfiguredNotTo() { identityProvider.getConfig().setPerformRpInitiatedLogout(false); updateProvider(); @@ -587,8 +549,7 @@ public void successfulUaaLogoutDoesNotTriggerExternalOIDCProviderLogout_whenConf String externalOIDCProviderLoginPage = baseUrl; webDriver.get(externalOIDCProviderLoginPage); - Assert.assertThat("Did not land on the external OIDC provider home page (as an authenticated user).", - webDriver.getPageSource(), containsString("Where to?")); + assertThat(webDriver.getPageSource()).as("Did not land on the external OIDC provider home page (as an authenticated user).").contains("Where to?"); } private String getRefreshTokenResponse(ServerRunning serverRunning, String refreshToken) { @@ -601,8 +562,8 @@ private String getRefreshTokenResponse(ServerRunning serverRunning, String refre HttpHeaders tokenHeaders = new HttpHeaders(); tokenHeaders.set("Cache-Control", "no-store"); ResponseEntity tokenResponse = serverRunning.postForMap("/oauth/token", formData, tokenHeaders); - assertEquals(HttpStatus.OK, tokenResponse.getStatusCode()); - assertEquals("no-store", tokenResponse.getHeaders().getFirst("Cache-Control")); + assertThat(tokenResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(tokenResponse.getHeaders().getFirst("Cache-Control")).isEqualTo("no-store"); return DefaultOAuth2AccessToken.valueOf(tokenResponse.getBody()).getRefreshToken().getValue(); } @@ -614,23 +575,4 @@ private void setRefreshTokenRotate(boolean isRotate) { config.setTokenPolicy(policy); IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zone.getId(), zone.getSubdomain(), config); } - - private OIDCIdentityProviderDefinition azureConfig() throws Exception { - OIDCIdentityProviderDefinition config = new OIDCIdentityProviderDefinition(); - config.addAttributeMapping(USER_NAME_ATTRIBUTE_NAME, "unique_name"); - config.setAuthUrl(new URL("https://login.microsoftonline.com/9bc40aaf-e150-4c30-bb3c-a8b3b677266e/oauth2/authorize")); - config.setTokenUrl(new URL("https://login.microsoftonline.com/9bc40aaf-e150-4c30-bb3c-a8b3b677266e/oauth2/token")); - config.setTokenKeyUrl(new URL("https://login.microsoftonline.com/9bc40aaf-e150-4c30-bb3c-a8b3b677266e/discovery/v2.0/keys")); - config.setShowLinkText(true); - config.setLinkText("Test Azure Provider"); - config.setSkipSslValidation(false); - config.setAddShadowUserOnLogin(true); - config.setRelyingPartyId("8c5ea049-869e-47f8-a492-852a05f507af"); - config.setRelyingPartySecret(null); - config.setIssuer("https://sts.windows.net/9bc40aaf-e150-4c30-bb3c-a8b3b677266e/"); - config.setScopes(Collections.singletonList("openid")); - config.setResponseType("code id_token"); - return config; - } - } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ResetPasswordIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ResetPasswordIT.java index f0546f43219..1cc9aa3ff42 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ResetPasswordIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ResetPasswordIT.java @@ -18,8 +18,8 @@ import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.login.test.UnlessProfileActive; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -28,7 +28,6 @@ import org.openqa.selenium.WebDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.client.RestTemplate; @@ -38,19 +37,15 @@ import java.util.Iterator; import static org.apache.commons.lang3.StringUtils.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) @UnlessProfileActive(values = "saml") public class ResetPasswordIT { - @Autowired @Rule + @Autowired + @Rule public IntegrationTestRule integrationTestRule; @Autowired @@ -122,81 +117,80 @@ public void resettingAPasswordWithPrimaryEmail() { beginPasswordReset(email); - assertEquals(receivedEmailSize, simpleSmtpServer.getReceivedEmailSize()); + assertThat(simpleSmtpServer.getReceivedEmailSize()).isEqualTo(receivedEmailSize); } @Test public void resetPassword_with_clientRedirect() { webDriver.get(baseUrl + "/forgot_password?client_id=" + scimClientId + "&redirect_uri=http://example.redirect.com"); - Assert.assertEquals("Reset Password", webDriver.findElement(By.tagName("h1")).getText()); + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Reset Password"); int receivedEmailSize = simpleSmtpServer.getReceivedEmailSize(); webDriver.findElement(By.name("username")).sendKeys(username); webDriver.findElement(By.xpath("//input[@value='Send reset password link']")).click(); - Assert.assertEquals("Instructions Sent", webDriver.findElement(By.tagName("h1")).getText()); + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Instructions Sent"); - assertEquals(receivedEmailSize + 1, simpleSmtpServer.getReceivedEmailSize()); - Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); - SmtpMessage message = (SmtpMessage) receivedEmail.next(); + assertThat(simpleSmtpServer.getReceivedEmailSize()).isEqualTo(receivedEmailSize + 1); + Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); + SmtpMessage message = receivedEmail.next(); receivedEmail.remove(); - assertEquals(email, message.getHeaderValue("To")); - assertThat(message.getBody(), containsString("Reset your password")); + assertThat(message.getHeaderValue("To")).isEqualTo(email); + assertThat(message.getBody()).contains("Reset your password"); - Assert.assertEquals("Please check your email for a reset password link.", webDriver.findElement(By.cssSelector(".instructions-sent")).getText()); + assertThat(webDriver.findElement(By.cssSelector(".instructions-sent")).getText()).isEqualTo("Please check your email for a reset password link."); // Click link in email String link = testClient.extractLink(message.getBody()); - assertFalse(contains(link, "@")); - assertFalse(contains(link, "%40")); + assertThat(contains(link, "@")).isFalse(); + assertThat(contains(link, "%40")).isFalse(); webDriver.get(link); webDriver.findElement(By.name("password")).sendKeys("new_password"); webDriver.findElement(By.name("password_confirmation")).sendKeys("new_password"); webDriver.findElement(By.xpath("//input[@value='Create new password']")).click(); - assertEquals(baseUrl + "/login?success=password_reset&form_redirect_uri=http://example.redirect.com", webDriver.getCurrentUrl()); + assertThat(webDriver.getCurrentUrl()).isEqualTo(baseUrl + "/login?success=password_reset&form_redirect_uri=http://example.redirect.com"); } @Test public void testNotAutoLoginAfterResetPassword() { webDriver.get(baseUrl + "/oauth/authorize?client_id=" + authCodeClientId + "&redirect_uri=http://example.redirect.com&grant_type=authorization_code&response_type=code"); -// webDriver.get(); webDriver.findElement(By.linkText("Reset password")).click(); - Assert.assertEquals("Reset Password", webDriver.findElement(By.tagName("h1")).getText()); + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Reset Password"); int receivedEmailSize = simpleSmtpServer.getReceivedEmailSize(); webDriver.findElement(By.name("username")).sendKeys(username); webDriver.findElement(By.xpath("//input[@value='Send reset password link']")).click(); - Assert.assertEquals("Instructions Sent", webDriver.findElement(By.tagName("h1")).getText()); + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Instructions Sent"); - assertEquals(receivedEmailSize + 1, simpleSmtpServer.getReceivedEmailSize()); - Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); - SmtpMessage message = (SmtpMessage) receivedEmail.next(); + assertThat(simpleSmtpServer.getReceivedEmailSize()).isEqualTo(receivedEmailSize + 1); + Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); + SmtpMessage message = receivedEmail.next(); receivedEmail.remove(); - assertEquals(email, message.getHeaderValue("To")); - assertThat(message.getBody(), containsString("Reset your password")); + assertThat(message.getHeaderValue("To")).isEqualTo(email); + assertThat(message.getBody()).contains("Reset your password"); - Assert.assertEquals("Please check your email for a reset password link.", webDriver.findElement(By.cssSelector(".instructions-sent")).getText()); + assertThat(webDriver.findElement(By.cssSelector(".instructions-sent")).getText()).isEqualTo("Please check your email for a reset password link."); // Click link in email String link = testClient.extractLink(message.getBody()); - assertFalse(contains(link, "@")); - assertFalse(contains(link, "%40")); + assertThat(link).doesNotContain("@") + .doesNotContain("%40"); webDriver.get(link); webDriver.findElement(By.name("password")).sendKeys("new_password"); webDriver.findElement(By.name("password_confirmation")).sendKeys("new_password"); webDriver.findElement(By.xpath("//input[@value='Create new password']")).click(); - assertEquals(baseUrl + "/login?success=password_reset", webDriver.getCurrentUrl()); - assertThat(webDriver.findElement(By.cssSelector(".alert-success")).getText(), containsString("Password reset successful")); + assertThat(webDriver.getCurrentUrl()).isEqualTo(baseUrl + "/login?success=password_reset"); + assertThat(webDriver.findElement(By.cssSelector(".alert-success")).getText()).contains("Password reset successful"); webDriver.findElement(By.name("username")).sendKeys(username); webDriver.findElement(By.name("password")).sendKeys("new_password"); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - assertThat(webDriver.getCurrentUrl(), startsWith("http://example.redirect.com/?code=")); + assertThat(webDriver.getCurrentUrl()).startsWith("http://example.redirect.com/?code="); } @Test @@ -205,7 +199,7 @@ public void resettingAPasswordForANonExistentUser() { beginPasswordReset("nonexistent_user"); - assertEquals(receivedEmailSize, simpleSmtpServer.getReceivedEmailSize()); + assertThat(simpleSmtpServer.getReceivedEmailSize()).isEqualTo(receivedEmailSize); } @Test @@ -219,7 +213,7 @@ public void resettingAPasswordWithInvalidPassword() { webDriver.findElement(By.name("password")).sendKeys("newsecret"); webDriver.findElement(By.name("password_confirmation")).sendKeys(""); webDriver.findElement(By.xpath("//input[@value='Create new password']")).click(); - assertThat(webDriver.findElement(By.cssSelector(".error-message")).getText(), containsString("Passwords must match and not be empty.")); + assertThat(webDriver.findElement(By.cssSelector(".error-message")).getText()).contains("Passwords must match and not be empty."); } @Test @@ -232,7 +226,7 @@ public void codesCanOnlyBeUsedOnce() { // Attempt to use same code again webDriver.get(link); - assertThat(webDriver.findElement(By.cssSelector(".error-message")).getText(), containsString("Sorry, your reset password link is no longer valid. You can request another one below.")); + assertThat(webDriver.findElement(By.cssSelector(".error-message")).getText()).contains("Sorry, your reset password link is no longer valid. You can request another one below."); } @Test @@ -246,7 +240,7 @@ public void resetPassword_displaysErrorMessage_WhenPasswordIsInvalid() { webDriver.findElement(By.name("password")).sendKeys(newPassword); webDriver.findElement(By.name("password_confirmation")).sendKeys(newPassword); webDriver.findElement(By.xpath("//input[@value='Create new password']")).click(); - assertThat(webDriver.findElement(By.cssSelector(".error-message")).getText(), containsString("Password must be no more than 255 characters in length.")); + assertThat(webDriver.findElement(By.cssSelector(".error-message")).getText()).contains("Password must be no more than 255 characters in length."); } @Test @@ -258,29 +252,29 @@ public void resetPassword_displaysErrorMessage_NewPasswordSameAsOld() { webDriver.findElement(By.name("password")).sendKeys("secr3T"); webDriver.findElement(By.name("password_confirmation")).sendKeys("secr3T"); webDriver.findElement(By.xpath("//input[@value='Create new password']")).click(); - assertThat(webDriver.findElement(By.cssSelector(".error-message")).getText(), containsString("Your new password cannot be the same as the old password.")); + assertThat(webDriver.findElement(By.cssSelector(".error-message")).getText()).contains("Your new password cannot be the same as the old password."); } private void beginPasswordReset(String username) { webDriver.get(baseUrl + "/login"); - Assert.assertEquals("Cloud Foundry", webDriver.getTitle()); + assertThat(webDriver.getTitle()).isEqualTo("Cloud Foundry"); webDriver.findElement(By.linkText("Reset password")).click(); - Assert.assertEquals("Reset Password", webDriver.findElement(By.tagName("h1")).getText()); + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Reset Password"); // Enter email address webDriver.findElement(By.name("username")).sendKeys(username); webDriver.findElement(By.xpath("//input[@value='Send reset password link']")).click(); - Assert.assertEquals("Instructions Sent", webDriver.findElement(By.tagName("h1")).getText()); + assertThat(webDriver.findElement(By.tagName("h1")).getText()).isEqualTo("Instructions Sent"); } private String getPasswordResetLink(String email) { - Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); - SmtpMessage message = (SmtpMessage) receivedEmail.next(); + Iterator receivedEmail = simpleSmtpServer.getReceivedEmail(); + SmtpMessage message = receivedEmail.next(); receivedEmail.remove(); - assertEquals(email, message.getHeaderValue("To")); - assertThat(message.getBody(), containsString("Reset your password")); + assertThat(message.getHeaderValue("To")).isEqualTo(email); + assertThat(message.getBody()).contains("Reset your password"); - Assert.assertEquals("Please check your email for a reset password link.", webDriver.findElement(By.cssSelector(".instructions-sent")).getText()); + assertThat(webDriver.findElement(By.cssSelector(".instructions-sent")).getText()).isEqualTo("Please check your email for a reset password link."); // Extract link from email return testClient.extractLink(message.getBody()); @@ -294,13 +288,12 @@ private void finishPasswordReset(String username, String email) { webDriver.findElement(By.name("password")).sendKeys("newsecr3T"); webDriver.findElement(By.name("password_confirmation")).sendKeys("newsecr3T"); webDriver.findElement(By.xpath("//input[@value='Create new password']")).click(); - assertThat(webDriver.getCurrentUrl(), is(baseUrl + "/login?success=password_reset")); + assertThat(webDriver.getCurrentUrl()).isEqualTo(baseUrl + "/login?success=password_reset"); webDriver.findElement(By.name("username")).sendKeys(username); webDriver.findElement(By.name("password")).sendKeys("newsecr3T"); webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Where to?")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); } - } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index b2f57e90ac7..1f6930498c1 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -13,34 +13,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.cloudfoundry.identity.uaa.client.UaaClientDetails; -import org.cloudfoundry.identity.uaa.oauth.client.test.TestAccounts; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.account.UserInfoResponse; +import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.endpoints.LogoutDoEndpoint; import org.cloudfoundry.identity.uaa.integration.endpoints.OauthAuthorizeEndpoint; @@ -50,10 +26,14 @@ import org.cloudfoundry.identity.uaa.integration.pageObjects.LoginPage; import org.cloudfoundry.identity.uaa.integration.pageObjects.Page; import org.cloudfoundry.identity.uaa.integration.pageObjects.PasscodePage; +import org.cloudfoundry.identity.uaa.integration.pageObjects.SamlLoginPage; +import org.cloudfoundry.identity.uaa.integration.pageObjects.SamlWelcomePage; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.integration.util.ScreenshotOnFail; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.test.TestAccounts; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; @@ -71,59 +51,91 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.flywaydb.core.internal.util.StringUtils; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.Cookie; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; +import org.xmlunit.assertj.XmlAssert; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.SAML_AUTH_SOURCE; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR; +import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.createSimplePHPSamlIDP; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.doesSupportZoneDNS; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.getZoneAdminToken; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.isMember; +import static org.cloudfoundry.identity.uaa.oauth.common.OAuth2AccessToken.ACCESS_TOKEN; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ATTRIBUTES; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItem; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.xmlNamespaces; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_EMAIL; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_PERSISTENT; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_TRANSIENT; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_UNSPECIFIED; +import static org.cloudfoundry.identity.uaa.provider.saml.SamlNameIdFormats.NAMEID_FORMAT_X509SUBJECT; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_DIGEST_SHA256; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.TEXT_HTML_VALUE; -import static org.cloudfoundry.identity.uaa.oauth.common.OAuth2AccessToken.ACCESS_TOKEN; -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = DefaultIntegrationTestConfig.class) +@SpringJUnitConfig(classes = DefaultIntegrationTestConfig.class) public class SamlLoginIT { - public static final String MARISSA4_USERNAME = "marissa4"; - private static final String MARISSA4_PASSWORD = "saml2"; - public static final String MARISSA4_EMAIL = "marissa4@test.org"; - public static final String MARISSA2_USERNAME = "marissa2"; + private static final String MARISSA2_USERNAME = "marissa2"; private static final String MARISSA2_PASSWORD = "saml2"; - public static final String MARISSA3_USERNAME = "marissa3"; + private static final String MARISSA3_USERNAME = "marissa3"; private static final String MARISSA3_PASSWORD = "saml2"; + private static final String MARISSA4_USERNAME = "marissa4"; + private static final String MARISSA4_EMAIL = "marissa4@test.org"; + private static final String MARISSA4_PASSWORD = "saml2"; private static final String SAML_ORIGIN = "simplesamlphp"; - @Autowired @Rule + private static final By byUsername = By.name("username"); + private static final By byPassword = By.name("password"); + + @Autowired + @Rule public IntegrationTestRule integrationTestRule; @Rule @@ -149,15 +161,31 @@ public class SamlLoginIT { ServerRunning serverRunning = ServerRunning.isRunning(); - @BeforeClass - public static void checkZoneDNSSupport() { - assertTrue("Expected testzone1.localhost, testzone2.localhost, testzone3.localhost, testzone4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + @BeforeAll + static void checkZoneDNSSupport() { + assertThat(doesSupportZoneDNS()) + .as("Expected testzone1.localhost, testzone2.localhost, testzone3.localhost, testzone4.localhost to resolve to 127.0.0.1") + .isTrue(); } - @Before - public void setup() { - String token = IntegrationTestUtils.getClientCredentialsToken(baseUrl, "admin", "adminsecret"); + public static String getValidRandomIDPMetaData() { + return MockMvcUtils.IDP_META_DATA.formatted(new RandomValueStringGenerator().generate()); + } + + private static String loadResouceAsString(String resourceLocation) { + ResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource resource = resourceLoader.getResource(resourceLocation); + try (Reader reader = new InputStreamReader(resource.getInputStream(), UTF_8)) { + return FileCopyUtils.copyToString(reader); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @BeforeEach + void setupZones() { + String token = IntegrationTestUtils.getClientCredentialsToken(baseUrl, "admin", "adminsecret"); IntegrationTestUtils.ensureGroupExists(token, null, baseUrl, "zones.uaa.admin"); IntegrationTestUtils.ensureGroupExists(token, null, baseUrl, "zones.testzone1.admin"); IntegrationTestUtils.ensureGroupExists(token, null, baseUrl, "zones.testzone2.admin"); @@ -165,151 +193,205 @@ public void setup() { IntegrationTestUtils.ensureGroupExists(token, null, baseUrl, "zones.testzone4.admin"); } - @After - public void cleanup() { + @AfterEach + void afterEach() { String token = IntegrationTestUtils.getClientCredentialsToken(baseUrl, "admin", "adminsecret"); for (String zoneId : Arrays.asList("testzone1", "testzone2", "testzone3", "testzone4", "uaa")) { - String groupId = IntegrationTestUtils.getGroup(token, "", baseUrl, String.format("zones.%s.admin", zoneId)).getId(); - IntegrationTestUtils.deleteGroup(token, "", baseUrl, groupId); + ScimGroup group = IntegrationTestUtils.getGroup(token, "", baseUrl, "zones.%s.admin".formatted(zoneId)); + if (group != null) { + IntegrationTestUtils.deleteGroup(token, "", baseUrl, group.getId()); + } try { IntegrationTestUtils.deleteZone(baseUrl, zoneId, token); IntegrationTestUtils.deleteProvider(token, baseUrl, "uaa", zoneId + ".cloudfoundry-saml-login"); - } catch(Exception ignored){} + } catch (Exception ignored) { + // ignored + } } + IntegrationTestUtils.deleteProvider(token, baseUrl, "uaa", SAML_ORIGIN); + IntegrationTestUtils.deleteProvider(token, baseUrl, "uaa", "simplesamlphp2"); } - public static String getValidRandomIDPMetaData() { - return String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); - } - - @Before - public void clearWebDriverOfCookies() { + @BeforeEach + void clearWebDriverOfCookies() { screenShootRule.setWebDriver(webDriver); for (String domain : Arrays.asList("localhost", "testzone1.localhost", "testzone2.localhost", "testzone3.localhost", "testzone4.localhost")) { LogoutDoEndpoint.logout(webDriver, baseUrl.replace("localhost", domain)); new Page(webDriver).clearCookies(); } - SamlLogoutAuthSourceEndpoint.logoutAuthSource_goesToSamlWelcomePage(webDriver, IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); + SamlLogoutAuthSourceEndpoint.assertThatLogoutAuthSource_goesToSamlWelcomePage(webDriver, SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); } @Test - public void testSamlSPMetadata() { + void samlSPMetadata() { RestTemplate request = new RestTemplate(); - ResponseEntity response = request.getForEntity( - baseUrl + "/saml/metadata", String.class); - assertEquals(HttpStatus.OK, response.getStatusCode()); - String metadataXml = (String)response.getBody(); + ResponseEntity response = request.getForEntity("%s/saml/metadata".formatted(baseUrl), String.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + String metadataXml = response.getBody(); + XmlAssert xmlAssert = XmlAssert.assertThat(metadataXml).withNamespaceContext(xmlNamespaces()); // The SAML SP metadata should match the following UAA configs: // login.entityID - assertThat(metadataXml, containsString( - "entityID=\"cloudfoundry-saml-login\"")); - // login.saml.signatureAlgorithm - assertThat(metadataXml, containsString( - "")); - assertThat(metadataXml, containsString( - "")); + xmlAssert.valueByXPath("//md:EntityDescriptor/@entityID").isEqualTo("cloudfoundry-saml-login"); // login.saml.signRequest - assertThat(metadataXml, containsString("AuthnRequestsSigned=\"true\"")); + xmlAssert.valueByXPath("//md:EntityDescriptor/md:SPSSODescriptor/@AuthnRequestsSigned").isEqualTo(true); // login.saml.wantAssertionSigned - assertThat(metadataXml, containsString( - "WantAssertionsSigned=\"true\"")); + xmlAssert.valueByXPath("//md:EntityDescriptor/md:SPSSODescriptor/@WantAssertionsSigned").isEqualTo(true); + // the AssertionConsumerService endpoint needs to be: /saml/SSO/alias/[UAA-wide SAML entity ID, aka UAA.yml's login.saml.entityIDAlias or login.entityID] + xmlAssert.valueByXPath("//md:EntityDescriptor/md:SPSSODescriptor/md:AssertionConsumerService/@Location") + .contains("localhost", "/saml/SSO/alias/cloudfoundry-saml-login"); + // login.saml.signatureAlgorithm + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm").isEqualTo(ALGO_ID_SIGNATURE_RSA_SHA256); + xmlAssert.valueByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm").isEqualTo(ALGO_ID_DIGEST_SHA256); + xmlAssert.nodesByXPath("/md:EntityDescriptor/ds:Signature/ds:SignatureValue").exist(); + xmlAssert.nodesByXPath("/md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestValue").exist(); // login.saml.nameID - assertThat(metadataXml, containsString( - "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified")); + xmlAssert.nodesByXPath("/md:EntityDescriptor/md:SPSSODescriptor/md:NameIDFormat") + .extractingText() + .contains(NAMEID_FORMAT_UNSPECIFIED, NAMEID_FORMAT_EMAIL, NAMEID_FORMAT_X509SUBJECT, + NAMEID_FORMAT_PERSISTENT, NAMEID_FORMAT_TRANSIENT); + + assertThat(response.getHeaders().getContentDisposition().getFilename()).isEqualTo("saml-sp.xml"); + } + + @Test + void samlSPMetadataForZone() { + String zoneId = "testzone1"; + String zoneUrl = baseUrl.replace("localhost", "%s.localhost".formatted(zoneId)); + + //identity client token + RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + ); + IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + ); + + //create the zone + IdentityZoneConfiguration config = new IdentityZoneConfiguration(); + config.getCorsPolicy().getDefaultConfiguration().setAllowedMethods(List.of(GET.toString(), POST.toString())); + config.getSamlConfig().setEntityID("%s-saml-login".formatted(zoneId)); + config.getSamlConfig().setWantAssertionSigned(false); + config.getSamlConfig().setRequestSigned(false); + IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); + + RestTemplate request = new RestTemplate(); + ResponseEntity response = request.getForEntity("%s/saml/metadata".formatted(zoneUrl), String.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + String metadataXml = response.getBody(); + XmlAssert xmlAssert = XmlAssert.assertThat(metadataXml).withNamespaceContext(xmlNamespaces()); + + // The SAML SP metadata should match the following UAA configs: + // id zone config's samlConfig.entityID + xmlAssert.valueByXPath("//md:EntityDescriptor/@entityID").isEqualTo("testzone1-saml-login"); + // determined by zone config field: config.samlConfig.requestSigned + xmlAssert.valueByXPath("//md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm").isEqualTo(ALGO_ID_SIGNATURE_RSA_SHA256); + xmlAssert.valueByXPath("//md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm").isEqualTo(ALGO_ID_DIGEST_SHA256); + + // determined by zone config field: config.samlConfig.wantAssertionSigned + xmlAssert.valueByXPath("//md:EntityDescriptor/md:SPSSODescriptor/@WantAssertionsSigned").isEqualTo(false); + // the AssertionConsumerService endpoint needs to be: /saml/SSO/alias/[zone-subdomain].[UAA-wide SAML entity ID, aka UAA.yml's login.saml.entityIDAlias, or fall back on login.entityID] + xmlAssert.valueByXPath("//md:EntityDescriptor/md:SPSSODescriptor/md:AssertionConsumerService/@Location") + .contains("testzone1.localhost") + .contains("/saml/SSO/alias/testzone1.cloudfoundry-saml-login"); + xmlAssert.nodesByXPath("//md:EntityDescriptor/ds:Signature/ds:SignatureValue").exist(); + xmlAssert.nodesByXPath("//md:EntityDescriptor/ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestValue").exist(); + + assertThat(response.getHeaders().getContentDisposition().getFilename()).isEqualTo("saml-testzone1-sp.xml"); } @Test - public void testContentTypes() { - String loginUrl = baseUrl + "/login"; + void contentTypes() { + String loginUrl = "%s/login".formatted(baseUrl); HttpHeaders jsonHeaders = new HttpHeaders(); jsonHeaders.add("Accept", "application/json"); ResponseEntity jsonResponseEntity = restOperations.exchange(loginUrl, - HttpMethod.GET, - new HttpEntity<>(jsonHeaders), - Map.class); - assertThat(jsonResponseEntity.getHeaders().get("Content-Type").get(0), containsString(APPLICATION_JSON_VALUE)); + HttpMethod.GET, + new HttpEntity<>(jsonHeaders), + Map.class); + assertThat(jsonResponseEntity.getHeaders().getFirst("Content-Type")).contains(APPLICATION_JSON_VALUE); HttpHeaders htmlHeaders = new HttpHeaders(); htmlHeaders.add("Accept", "text/html"); ResponseEntity htmlResponseEntity = restOperations.exchange(loginUrl, - HttpMethod.GET, - new HttpEntity<>(htmlHeaders), - Void.class); - assertThat(htmlResponseEntity.getHeaders().get("Content-Type").get(0), containsString(TEXT_HTML_VALUE)); + HttpMethod.GET, + new HttpEntity<>(htmlHeaders), + Void.class); + assertThat(htmlResponseEntity.getHeaders().getFirst("Content-Type")).contains(TEXT_HTML_VALUE); HttpHeaders defaultHeaders = new HttpHeaders(); defaultHeaders.add("Accept", "*/*"); ResponseEntity defaultResponseEntity = restOperations.exchange(loginUrl, - HttpMethod.GET, - new HttpEntity<>(defaultHeaders), - Void.class); - assertThat(defaultResponseEntity.getHeaders().get("Content-Type").get(0), containsString(TEXT_HTML_VALUE)); + HttpMethod.GET, + new HttpEntity<>(defaultHeaders), + Void.class); + assertThat(defaultResponseEntity.getHeaders().getFirst("Content-Type")).contains(TEXT_HTML_VALUE); } @Test - public void testSimpleSamlPhpPasscodeRedirect() throws Exception { + void simpleSamlPhpPasscodeRedirect() { createIdentityProvider(SAML_ORIGIN); - PasscodePage.requestPasscode_goesToLoginPage(webDriver, baseUrl) - .clickSamlLink_goesToSamlLoginPage() - .login_goesToPasscodePage(testAccounts.getUserName(), testAccounts.getPassword()); + PasscodePage.assertThatRequestPasscode_goesToLoginPage(webDriver, baseUrl) + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToPasscodePage(testAccounts.getUserName(), testAccounts.getPassword()); } @Test - public void testSimpleSamlLoginWithAddShadowUserOnLoginFalse() throws Exception { + void simpleSamlLoginWithAddShadowUserOnLoginFalse() { // Deleting marissa@test.org from simplesamlphp because previous SAML authentications automatically // create a UAA user with the email address as the username. deleteUser(SAML_ORIGIN, testAccounts.getEmail()); - IdentityProvider provider = IntegrationTestUtils.createIdentityProvider(SAML_ORIGIN, false, baseUrl, serverRunning); - String clientId = "app-addnew-false"+ new RandomValueStringGenerator().generate(); + IdentityProvider provider = IntegrationTestUtils.createIdentityProvider(SAML_ORIGIN, false, baseUrl, serverRunning); + String clientId = "app-addnew-false" + new RandomValueStringGenerator().generate(); String redirectUri = "http://nosuchhostname:0/nosuchendpoint"; createClientAndSpecifyProvider(clientId, provider, redirectUri); OauthAuthorizeEndpoint - .authorize_goesToSamlLoginPage(webDriver, baseUrl, redirectUri, clientId, "code") - .login_goesToCustomErrorPage( + .assertThatAuthorize_goesToSamlLoginPage(webDriver, baseUrl, redirectUri, clientId, "code") + .assertThatLogin_goesToCustomErrorPage( testAccounts.getUserName(), testAccounts.getPassword(), - containsString(redirectUri + "?error=access_denied&error_description=SAML+user+does+not+exist.+You+can+correct+this+by+creating+a+shadow+user+for+the+SAML+user.")); + "%s?error=access_denied&error_description=SAML+user+does+not+exist.+You+can+correct+this+by+creating+a+shadow+user+for+the+SAML+user.".formatted(redirectUri)); } @Test - public void incorrectResponseFromSamlIDP_showErrorFromSaml() { + void incorrectResponseFromSamlIdpShowErrorFromSaml() { String zoneId = "testzone3"; - String zoneUrl = baseUrl.replace("localhost",zoneId+".localhost"); + String zoneUrl = baseUrl.replace("localhost", "%s.localhost".formatted(zoneId)); //identity client token RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); + //create the zone IdentityZoneConfiguration config = new IdentityZoneConfiguration(); config.getCorsPolicy().getDefaultConfiguration().setAllowedMethods(List.of(GET.toString(), POST.toString())); IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); //create a zone admin user - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); - String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + zoneId + ".admin"); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); + String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones.%s.admin".formatted(zoneId)); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); //get the zone admin token String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); SamlIdentityProviderDefinition samlIdentityProviderDefinition = createSimplePHPSamlIDP(SAML_ORIGIN, "testzone3"); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); provider.setType(OriginKeys.SAML); provider.setActive(true); @@ -319,20 +401,20 @@ public void incorrectResponseFromSamlIDP_showErrorFromSaml() { IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); - HomePage.tryToGoHome_redirectsToLoginPage(webDriver, zoneUrl) - .clickSamlLink_goesToSamlLoginPage() - .login_goesToSamlErrorPage(testAccounts.getUserName(), testAccounts.getPassword()) - .validatePageSource(containsString("No local entity found for alias invalid, verify your configuration")); + HomePage.assertThatGoHome_redirectsToLoginPage(webDriver, zoneUrl) + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToSamlErrorPage(testAccounts.getUserName(), testAccounts.getPassword()) + .assertThatPageSource().contains("Invalid destination"); } @Test - public void testSimpleSamlPhpLogin() throws Exception { + void simpleSamlPhpLogin() { createIdentityProvider(SAML_ORIGIN); Long beforeTest = System.currentTimeMillis(); LoginPage.go(webDriver, baseUrl) - .clickSamlLink_goesToSamlLoginPage() - .login_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()); + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()); Long afterTest = System.currentTimeMillis(); String zoneAdminToken = IntegrationTestUtils.getClientCredentialsToken(serverRunning, "admin", "adminsecret"); @@ -341,16 +423,25 @@ public void testSimpleSamlPhpLogin() throws Exception { } @Test - public void testSimpleSamlPhpLoginDisplaysLastLogin() throws Exception { + void idpInitiatedLogin() { + createIdentityProvider(SAML_ORIGIN); + webDriver.get("%s/saml2/idp/SSOService.php?spentityid=cloudfoundry-saml-login".formatted(SIMPLESAMLPHP_UAA_ACCEPTANCE)); + new SamlLoginPage(webDriver) + .assertThatLogin_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()); + } + + @Test + void simpleSamlPhpLoginDisplaysLastLogin() { + createIdentityProvider(SAML_ORIGIN); + Long beforeTest = System.currentTimeMillis(); - IdentityProvider provider = createIdentityProvider(SAML_ORIGIN); - LoginPage.go(webDriver, baseUrl) - .clickSamlLink_goesToSamlLoginPage() - .login_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()) - .logout_goesToLoginPage() - .clickSamlLink_goesToSamlLoginPage() - .login_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()) - .hasLastLoginTime(); + HomePage homePage = LoginPage.go(webDriver, baseUrl) + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()) + .assertThatLogout_goesToLoginPage() + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()); + assertThat(homePage.hasLastLoginTime()).isTrue(); Long afterTest = System.currentTimeMillis(); String zoneAdminToken = IntegrationTestUtils.getClientCredentialsToken(serverRunning, "admin", "adminsecret"); @@ -359,54 +450,72 @@ public void testSimpleSamlPhpLoginDisplaysLastLogin() throws Exception { } @Test - public void testSingleLogout() throws Exception { - IdentityProvider provider = createIdentityProvider(SAML_ORIGIN); + void singleLogout() { + createIdentityProvider(SAML_ORIGIN); LoginPage.go(webDriver, baseUrl) - .clickSamlLink_goesToSamlLoginPage() - .login_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()) - .logout_goesToLoginPage() - .clickSamlLink_goesToSamlLoginPage(); + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()) + .assertThatLogout_goesToLoginPage() + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN); } @Test - public void testSingleLogoutWithNoLogoutUrlOnIDP_withLogoutRedirect() { + void idpInitiatedLogout() { + createIdentityProvider(SAML_ORIGIN); + + LoginPage.go(webDriver, baseUrl) + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()); + + // Logout via IDP + webDriver.get("%s/saml2/idp/SingleLogoutService.php?ReturnTo=%1$s/module.php/core/welcome".formatted(SIMPLESAMLPHP_UAA_ACCEPTANCE)); + // UAA should redirect to the welcome page + new SamlWelcomePage(webDriver); + + // UAA Should no longer be logged in + HomePage.assertThatGoHome_redirectsToLoginPage(webDriver, baseUrl); + } + + @Test + void singleLogoutWithNoLogoutUrlOnIDPWithLogoutRedirect() { String zoneId = "testzone2"; - String zoneUrl = baseUrl.replace("localhost",zoneId+".localhost"); + String zoneUrl = baseUrl.replace("localhost", "%s.localhost".formatted(zoneId)); //identity client token RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); + //admin client token - to create users RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); IdentityZoneConfiguration config = new IdentityZoneConfiguration(); config.getLinks().getLogout().setDisableRedirectParameter(false); config.getCorsPolicy().getDefaultConfiguration().setAllowedMethods( List.of(GET.toString(), POST.toString())); + //create the zone IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); - //create a zone admin user - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); - String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + zoneId + ".admin"); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); + String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones.%s.admin".formatted(zoneId)); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); //get the zone admin token String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); SamlIdentityProviderDefinition providerDefinition = createIDPWithNoSLOSConfigured(); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); provider.setType(OriginKeys.SAML); provider.setActive(true); @@ -416,24 +525,24 @@ public void testSingleLogoutWithNoLogoutUrlOnIDP_withLogoutRedirect() { IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); LoginPage loginPage = LoginPage.go(webDriver, zoneUrl); - loginPage.validateTitle(Matchers.containsString("testzone2")); - loginPage.clickSamlLink_goesToSamlLoginPage() - .login_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()); + loginPage.assertThatTitle().contains("testzone2"); + loginPage.assertThatSamlLink_goesToSamlLoginPage("simplesamlphp") + .assertThatLogin_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()); - String redirectUrl = zoneUrl + "/login?test=test"; + String redirectUrl = "%s/login?test=test".formatted(zoneUrl); UaaClientDetails clientDetails = new UaaClientDetails("test-logout-redirect", null, null, GRANT_TYPE_AUTHORIZATION_CODE, null); clientDetails.setRegisteredRedirectUri(Collections.singleton(redirectUrl)); clientDetails.setClientSecret("secret"); IntegrationTestUtils.createOrUpdateClient(zoneAdminToken, baseUrl, zoneId, clientDetails); LogoutDoEndpoint.logout_goesToLoginPage(webDriver, zoneUrl, redirectUrl, "test-logout-redirect") - .validateUrl(equalTo(redirectUrl)); + .assertThatUrl().isEqualTo(redirectUrl); } @Test - public void testSingleLogoutWithNoLogoutUrlOnIDP() throws Exception { + void singleLogoutWithNoLogoutUrlOnIDP() { SamlIdentityProviderDefinition providerDefinition = createIDPWithNoSLOSConfigured(); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(OriginKeys.UAA); provider.setType(OriginKeys.SAML); provider.setActive(true); @@ -442,74 +551,58 @@ public void testSingleLogoutWithNoLogoutUrlOnIDP() throws Exception { provider.setName("simplesamlphp for uaa"); String zoneAdminToken = getZoneAdminToken(baseUrl, serverRunning); - - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); LoginPage.go(webDriver, baseUrl) - .clickSamlLink_goesToSamlLoginPage() - .login_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()) - .logout_goesToLoginPage() - .clickSamlLink_goesToHomePage(); + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToHomePage(testAccounts.getUserName(), testAccounts.getPassword()) + .assertThatLogout_goesToLoginPage() + // Local Logout, but not logged out of IDP, login should skip U/P prompt + .assertThatSamlLink_goesToHomePage(SAML_ORIGIN); } @Test - public void testGroupIntegration() throws Exception { + void groupIntegration() { createIdentityProvider(SAML_ORIGIN); LoginPage.go(webDriver, baseUrl) - .clickSamlLink_goesToSamlLoginPage() - .login_goesToHomePage(MARISSA4_USERNAME, MARISSA4_PASSWORD); + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToHomePage(MARISSA4_USERNAME, MARISSA4_PASSWORD); } @Test - public void testFavicon_Should_Not_Save() throws Exception { + void faviconShouldNotSave() { createIdentityProvider(SAML_ORIGIN); FaviconElement.getDefaultIcon(webDriver, baseUrl); LoginPage.go(webDriver, baseUrl) - .clickSamlLink_goesToSamlLoginPage() - .login_goesToHomePage(MARISSA4_USERNAME, MARISSA4_PASSWORD); - } - - - private void testSimpleSamlLogin(String firstUrl, String lookfor) throws Exception { - testSimpleSamlLogin(firstUrl, lookfor, testAccounts.getUserName(), testAccounts.getPassword()); + .assertThatSamlLink_goesToSamlLoginPage(SAML_ORIGIN) + .assertThatLogin_goesToHomePage(MARISSA4_USERNAME, MARISSA4_PASSWORD); } - private void testSimpleSamlLogin(String firstUrl, String lookfor, String username, String password) throws Exception { - IdentityProvider provider = createIdentityProvider(SAML_ORIGIN); - webDriver.get(baseUrl + firstUrl); - Assert.assertEquals("Cloud Foundry", webDriver.getTitle()); - webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); - //takeScreenShot(); - assertThat(webDriver.getCurrentUrl(), Matchers.containsString("loginuserpass")); - sendCredentials(username, password); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString(lookfor)); - } - - protected IdentityProvider createIdentityProvider(String originKey) throws Exception { + protected IdentityProvider createIdentityProvider(String originKey) { return IntegrationTestUtils.createIdentityProvider(originKey, true, baseUrl, serverRunning); } - protected UaaClientDetails createClientAndSpecifyProvider(String clientId, IdentityProvider provider, - String redirectUri) { + protected void createClientAndSpecifyProvider(String clientId, IdentityProvider provider, + String redirectUri) { - RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "identity", "identitysecret") ); RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + OriginKeys.UAA + ".admin"); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); UaaClientDetails clientDetails = new UaaClientDetails(clientId, null, "openid", GRANT_TYPE_AUTHORIZATION_CODE, "uaa.resource", redirectUri); @@ -518,14 +611,11 @@ protected UaaClientDetails createClientAndSpecifyProvider(String clientId, Ident clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, idps); clientDetails.setAutoApproveScopes(Collections.singleton("true")); IntegrationTestUtils.createClient(zoneAdminToken, baseUrl, clientDetails); - - return clientDetails; } protected void deleteUser(String origin, String username) { - String zoneAdminToken = IntegrationTestUtils.getClientCredentialsToken(serverRunning, - "admin", "adminsecret"); + "admin", "adminsecret"); String userId = IntegrationTestUtils.getUserId(zoneAdminToken, baseUrl, origin, username); if (null == userId) { @@ -536,28 +626,28 @@ protected void deleteUser(String origin, String username) { } @Test - public void test_SamlInvitation_Automatic_Redirect_In_Zone2() throws Exception { - perform_SamlInvitation_Automatic_Redirect_In_Zone2(MARISSA2_USERNAME, MARISSA2_PASSWORD, true); - perform_SamlInvitation_Automatic_Redirect_In_Zone2(MARISSA2_USERNAME, MARISSA2_PASSWORD, true); - perform_SamlInvitation_Automatic_Redirect_In_Zone2(MARISSA2_USERNAME, MARISSA2_PASSWORD, true); - - perform_SamlInvitation_Automatic_Redirect_In_Zone2(MARISSA3_USERNAME, MARISSA3_PASSWORD, false); - perform_SamlInvitation_Automatic_Redirect_In_Zone2(MARISSA3_USERNAME, MARISSA3_PASSWORD, false); - perform_SamlInvitation_Automatic_Redirect_In_Zone2(MARISSA3_USERNAME, MARISSA3_PASSWORD, false); + void samlInvitationAutomaticRedirectInZone2() { + performSamlInvitationAutomaticRedirectInZone2(MARISSA2_USERNAME, MARISSA2_PASSWORD, true); + performSamlInvitationAutomaticRedirectInZone2(MARISSA2_USERNAME, MARISSA2_PASSWORD, true); + performSamlInvitationAutomaticRedirectInZone2(MARISSA2_USERNAME, MARISSA2_PASSWORD, true); + + performSamlInvitationAutomaticRedirectInZone2(MARISSA3_USERNAME, MARISSA3_PASSWORD, false); + performSamlInvitationAutomaticRedirectInZone2(MARISSA3_USERNAME, MARISSA3_PASSWORD, false); + performSamlInvitationAutomaticRedirectInZone2(MARISSA3_USERNAME, MARISSA3_PASSWORD, false); } - public void perform_SamlInvitation_Automatic_Redirect_In_Zone2(String username, String password, boolean emptyList) { + public void performSamlInvitationAutomaticRedirectInZone2(String username, String password, boolean emptyList) { //ensure we are able to resolve DNS for hostname testzone1.localhost String zoneId = "testzone2"; - String zoneUrl = baseUrl.replace("localhost",zoneId+".localhost"); + String zoneUrl = baseUrl.replace("localhost", "%s.localhost".formatted(zoneId)); //identity client token RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); //admin client token - to create users RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); //create the zone IdentityZoneConfiguration config = new IdentityZoneConfiguration(); @@ -565,19 +655,19 @@ public void perform_SamlInvitation_Automatic_Redirect_In_Zone2(String username, IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); //create a zone admin user - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); - String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + zoneId + ".admin"); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); + String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones.%s.admin".formatted(zoneId)); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); //get the zone admin token String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone2IDP(SAML_ORIGIN); IdentityProvider provider = new IdentityProvider<>(); @@ -588,19 +678,19 @@ public void perform_SamlInvitation_Automatic_Redirect_In_Zone2(String username, provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); provider.setName("simplesamlphp for testzone2"); - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); - assertEquals(provider.getOriginKey(), provider.getConfig().getIdpEntityAlias()); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertThat(provider.getConfig().getIdpEntityAlias()).isEqualTo(provider.getOriginKey()); UaaIdentityProviderDefinition uaaDefinition = new UaaIdentityProviderDefinition( - new PasswordPolicy(1,255,0,0,0,0,12), - new LockoutPolicy(10, 10, 10) + new PasswordPolicy(1, 255, 0, 0, 0, 0, 12), + new LockoutPolicy(10, 10, 10) ); - uaaDefinition.setEmailDomain(emptyList ? Collections.EMPTY_LIST : Arrays.asList("*.*","*.*.*")); - IdentityProvider uaaProvider = IntegrationTestUtils.getProvider(zoneAdminToken, baseUrl, zoneId, OriginKeys.UAA); + uaaDefinition.setEmailDomain(emptyList ? Collections.emptyList() : Arrays.asList("*.*", "*.*.*")); + IdentityProvider uaaProvider = (IdentityProvider) IntegrationTestUtils.getProvider(zoneAdminToken, baseUrl, zoneId, OriginKeys.UAA); uaaProvider.setConfig(uaaDefinition); - uaaProvider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,uaaProvider); + uaaProvider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, uaaProvider); - UaaClientDetails uaaAdmin = new UaaClientDetails("admin","","", "client_credentials","uaa.admin,scim.read,scim.write"); + UaaClientDetails uaaAdmin = new UaaClientDetails("admin", "", "", "client_credentials", "uaa.admin,scim.read,scim.write"); uaaAdmin.setClientSecret("adminsecret"); IntegrationTestUtils.createOrUpdateClient(zoneAdminToken, baseUrl, zoneId, uaaAdmin); @@ -610,125 +700,80 @@ public void perform_SamlInvitation_Automatic_Redirect_In_Zone2(String username, String code = InvitationsIT.createInvitation(zoneUrl, useremail, useremail, samlIdentityProviderDefinition.getIdpEntityAlias(), "", uaaAdminToken, uaaAdminToken); String invitedUserId = IntegrationTestUtils.getUserId(uaaAdminToken, zoneUrl, samlIdentityProviderDefinition.getIdpEntityAlias(), useremail); String existingUserId = IntegrationTestUtils.getUserId(uaaAdminToken, zoneUrl, samlIdentityProviderDefinition.getIdpEntityAlias(), useremail); - webDriver.get(zoneUrl + "/logout.do"); - webDriver.get(zoneUrl + "/invitations/accept?code=" + code); + webDriver.get("%s/logout.do".formatted(zoneUrl)); + webDriver.get("%s/invitations/accept?code=%s".formatted(zoneUrl, code)); //redirected to saml login webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); sendCredentials(username, password); //we should now be on the login page because we don't have a redirect - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); uaaProvider.setConfig((UaaIdentityProviderDefinition) uaaDefinition.setEmailDomain(null)); - IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,uaaProvider); + IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, uaaProvider); String acceptedUserId = IntegrationTestUtils.getUserId(uaaAdminToken, zoneUrl, samlIdentityProviderDefinition.getIdpEntityAlias(), useremail); if (StringUtils.hasText(existingUserId)) { - assertEquals(acceptedUserId, existingUserId); + assertThat(existingUserId).isEqualTo(acceptedUserId); } else { - assertEquals(invitedUserId, acceptedUserId); + assertThat(acceptedUserId).isEqualTo(invitedUserId); } - webDriver.get(baseUrl + "/logout.do"); - webDriver.get(zoneUrl + "/logout.do"); - SamlLogoutAuthSourceEndpoint.logoutAuthSource_goesToSamlWelcomePage(webDriver, IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); + webDriver.get("%s/logout.do".formatted(baseUrl)); + webDriver.get("%s/logout.do".formatted(zoneUrl)); + SamlLogoutAuthSourceEndpoint.assertThatLogoutAuthSource_goesToSamlWelcomePage(webDriver, SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); } @Test - public void test_RelayState_redirect_from_idp() throws InterruptedException { - //ensure we are able to resolve DNS for hostname testzone1.localhost - String zoneId = "testzone1"; - - //identity client token - RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") - ); - //admin client token - to create users - RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") - ); - //create the zone - IdentityZoneConfiguration config = new IdentityZoneConfiguration(); - config.getCorsPolicy().getDefaultConfiguration().setAllowedMethods(List.of(GET.toString(), POST.toString())); - IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); - - //create a zone admin user - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); - String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + zoneId + ".admin"); - IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); - - //get the zone admin token - String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); - - SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone1IDP(SAML_ORIGIN); - IdentityProvider provider = new IdentityProvider<>(); - provider.setIdentityZoneId(zoneId); - provider.setType(OriginKeys.SAML); - provider.setActive(true); - provider.setConfig(samlIdentityProviderDefinition); - provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); - provider.setName("simplesamlphp for testzone1"); - - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); - assertEquals(provider.getOriginKey(), provider.getConfig().getIdpEntityAlias()); - - String zoneUrl = baseUrl.replace("localhost", "testzone1.localhost"); + void relayStateRedirectFromIdpInitiatedLogin() { + createIdentityProvider(SAML_ORIGIN); - webDriver.get(zoneUrl + "/logout.do"); + webDriver.get("%s/saml2/idp/SSOService.php?spentityid=cloudfoundry-saml-login&RelayState=https://www.google.com".formatted(SIMPLESAMLPHP_UAA_ACCEPTANCE)); - String samlUrl = IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE + "/saml2/idp/SSOService.php?"+ - "spentityid=testzone1.cloudfoundry-saml-login&" + - "RelayState=https://www.google.com"; - webDriver.get(samlUrl); - //we should now be in the Simple SAML PHP site + // we should now be in the Simple SAML PHP site webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); - sendCredentials(testAccounts.getUserName(), "koala"); + sendCredentials(testAccounts.getUserName(), testAccounts.getPassword()); + Page.assertThatUrlEventuallySatisfies(webDriver, + assertUrl -> assertUrl.startsWith("https://www.google.com")); - Page.validateUrlStartsWithWait(webDriver, "https://www.google.com"); - webDriver.get(baseUrl + "/logout.do"); - webDriver.get(zoneUrl + "/logout.do"); + webDriver.get("%s/logout.do".formatted(baseUrl)); } @Test - public void testSamlLoginClientIDPAuthorizationAutomaticRedirectInZone1() { + void samlLoginClientIDPAuthorizationAutomaticRedirectInZone1() { //ensure we are able to resolve DNS for hostname testzone1.localhost String zoneId = "testzone1"; //identity client token RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); + //admin client token - to create users RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); + //create the zone IdentityZoneConfiguration config = new IdentityZoneConfiguration(); config.getCorsPolicy().getDefaultConfiguration().setAllowedMethods(List.of(GET.toString(), POST.toString())); IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); //create a zone admin user - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); - String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + zoneId + ".admin"); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); + String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones.%s.admin".formatted(zoneId)); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); //get the zone admin token String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone1IDP(SAML_ORIGIN); IdentityProvider provider = new IdentityProvider<>(); @@ -739,8 +784,8 @@ public void testSamlLoginClientIDPAuthorizationAutomaticRedirectInZone1() { provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); provider.setName("simplesamlphp for testzone1"); - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); - assertEquals(provider.getOriginKey(), provider.getConfig().getIdpEntityAlias()); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertThat(provider.getConfig().getIdpEntityAlias()).isEqualTo(provider.getOriginKey()); List idps = Collections.singletonList(provider.getOriginKey()); String clientId = UUID.randomUUID().toString(); @@ -749,55 +794,57 @@ public void testSamlLoginClientIDPAuthorizationAutomaticRedirectInZone1() { clientDetails.setClientSecret("secret"); clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, idps); clientDetails.setAutoApproveScopes(Collections.singleton("true")); - clientDetails = IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, zoneId, clientDetails); + IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, zoneId, clientDetails); - webDriver.get(zoneUrl + "/logout.do"); + webDriver.get("%s/logout.do".formatted(zoneUrl)); - String authUrl = zoneUrl + "/oauth/authorize?client_id=" + clientId + "&redirect_uri=" + URLEncoder.encode(zoneUrl) + "&response_type=code&state=8tp0tR"; + String authUrl = "%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=8tp0tR".formatted(zoneUrl, clientId, URLEncoder.encode(zoneUrl, StandardCharsets.UTF_8)); webDriver.get(authUrl); + //we should now be in the Simple SAML PHP site webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); sendCredentials(testAccounts.getUserName(), "koala"); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); - webDriver.get(baseUrl + "/logout.do"); - webDriver.get(zoneUrl + "/logout.do"); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); + webDriver.get("%s/logout.do".formatted(baseUrl)); + webDriver.get("%s/logout.do".formatted(zoneUrl)); } - @Test - public void testSamlLogin_Map_Groups_In_Zone1() { + void samlLoginMapGroupsInZone1() { //ensure we are able to resolve DNS for hostname testzone1.localhost String zoneId = "testzone1"; String zoneUrl = baseUrl.replace("localhost", "testzone1.localhost"); //identity client token RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); + //admin client token - to create users RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); + //create the zone IdentityZoneConfiguration config = new IdentityZoneConfiguration(); config.getCorsPolicy().getDefaultConfiguration().setAllowedMethods(List.of(GET.toString(), POST.toString())); IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); //create a zone admin user - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); - String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + zoneId + ".admin"); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); + String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones.%s.admin".formatted(zoneId)); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); //get the zone admin token String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone1IDP(SAML_ORIGIN); samlIdentityProviderDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); @@ -812,10 +859,10 @@ public void testSamlLogin_Map_Groups_In_Zone1() { provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); provider.setName("simplesamlphp for testzone1"); - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); - assertEquals(provider.getOriginKey(), provider.getConfig().getIdpEntityAlias()); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertThat(provider.getConfig().getIdpEntityAlias()).isEqualTo(provider.getOriginKey()); - List idps = Collections.singletonList(provider.getOriginKey()); + List idps = List.of(provider.getOriginKey()); String adminClientInZone = new RandomValueStringGenerator().generate(); UaaClientDetails clientDetails = new UaaClientDetails(adminClientInZone, null, "openid", "authorization_code,client_credentials", "uaa.admin,scim.read,scim.write", zoneUrl); @@ -824,8 +871,7 @@ public void testSamlLogin_Map_Groups_In_Zone1() { clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, idps); clientDetails = IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, zoneId, clientDetails); - String adminTokenInZone = IntegrationTestUtils.getClientCredentialsToken(zoneUrl,clientDetails.getClientId(), "secret"); - + String adminTokenInZone = IntegrationTestUtils.getClientCredentialsToken(zoneUrl, clientDetails.getClientId(), "secret"); ScimGroup uaaSamlUserGroup = new ScimGroup(null, "uaa.saml.user", zoneId); uaaSamlUserGroup = IntegrationTestUtils.createOrUpdateGroup(adminTokenInZone, null, zoneUrl, uaaSamlUserGroup); @@ -840,32 +886,37 @@ public void testSamlLogin_Map_Groups_In_Zone1() { IntegrationTestUtils.mapExternalGroup(zoneAdminToken, zoneId, baseUrl, uaaSamlUserMapping); IntegrationTestUtils.mapExternalGroup(zoneAdminToken, zoneId, baseUrl, uaaSamlAdminMapping); - webDriver.get(zoneUrl + "/logout.do"); + webDriver.get("%s/logout.do".formatted(zoneUrl)); - String authUrl = zoneUrl + "/oauth/authorize?client_id=" + clientDetails.getClientId() + "&redirect_uri=" + URLEncoder.encode(zoneUrl) + "&response_type=code&state=8tp0tR"; + String authUrl = "%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=8tp0tR" + .formatted(zoneUrl, clientDetails.getClientId(), URLEncoder.encode(zoneUrl, StandardCharsets.UTF_8)); webDriver.get(authUrl); + //we should now be in the Simple SAML PHP site webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); sendCredentials(MARISSA4_USERNAME, MARISSA4_PASSWORD); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); - webDriver.get(baseUrl + "/logout.do"); - webDriver.get(zoneUrl + "/logout.do"); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); + webDriver.get("%s/logout.do".formatted(baseUrl)); + webDriver.get("%s/logout.do".formatted(zoneUrl)); //validate that the groups were mapped String samlUserId = IntegrationTestUtils.getUserId(adminTokenInZone, zoneUrl, provider.getOriginKey(), MARISSA4_EMAIL); uaaSamlUserGroup = IntegrationTestUtils.getGroup(adminTokenInZone, null, zoneUrl, "uaa.saml.user"); uaaSamlAdminGroup = IntegrationTestUtils.getGroup(adminTokenInZone, null, zoneUrl, "uaa.saml.admin"); IdentityProvider finalProvider = provider; - assertTrue(isMember(samlUserId, uaaSamlUserGroup)); - assertTrue("Expect saml user members to have origin: " + finalProvider.getOriginKey(), uaaSamlUserGroup.getMembers().stream().allMatch(p -> finalProvider.getOriginKey().equals(p.getOrigin()))); - assertTrue(isMember(samlUserId, uaaSamlAdminGroup)); - assertTrue("Expect admin members to have origin: " + finalProvider.getOriginKey(), uaaSamlAdminGroup.getMembers().stream().allMatch(p -> finalProvider.getOriginKey().equals(p.getOrigin()))); - + assertThat(isMember(samlUserId, uaaSamlUserGroup)).isTrue(); + assertThat(uaaSamlUserGroup.getMembers().stream()) + .as("Expect saml user members to have origin: " + finalProvider.getOriginKey()) + .allMatch(p -> finalProvider.getOriginKey().equals(p.getOrigin())); + assertThat(isMember(samlUserId, uaaSamlAdminGroup)).isTrue(); + assertThat(uaaSamlAdminGroup.getMembers().stream()) + .as("Expect admin members to have origin: " + finalProvider.getOriginKey()) + .allMatch(p -> finalProvider.getOriginKey().equals(p.getOrigin())); } @Test - public void testSamlLogin_Custom_User_Attributes_And_Roles_In_ID_Token() throws Exception { + void samlLoginCustomUserAttributesAndRolesInIDToken() throws Exception { final String COST_CENTER = "costCenter"; final String COST_CENTERS = "costCenters"; @@ -875,50 +926,53 @@ public void testSamlLogin_Custom_User_Attributes_And_Roles_In_ID_Token() throws final String JOHN_THE_SLOTH = "John the Sloth"; final String KARI_THE_ANT_EATER = "Kari the Ant Eater"; - //ensure we are able to resolve DNS for hostname testzone1.localhost String zoneId = "testzone1"; String zoneUrl = baseUrl.replace("localhost", "testzone1.localhost"); //identity client token RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); + //admin client token - to create users RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); + //create the zone IdentityZoneConfiguration config = new IdentityZoneConfiguration(); config.getCorsPolicy().getDefaultConfiguration().setAllowedMethods(List.of(GET.toString(), POST.toString())); IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); //create a zone admin user - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + zoneId + ".admin"); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); //get the zone admin token String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); // create a SAML external IDP SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone1IDP(SAML_ORIGIN); samlIdentityProviderDefinition.setStoreCustomAttributes(true); - samlIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+COST_CENTERS, COST_CENTER); - samlIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+MANAGERS, MANAGER); - // External groups will only appear as roles if they are whitelisted + samlIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + COST_CENTERS, COST_CENTER); + samlIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + MANAGERS, MANAGER); + + // External groups will only appear as roles if they are allowlisted samlIdentityProviderDefinition.setExternalGroupsWhitelist(List.of("*")); - // External groups will only be found when there is a configured attribute name for them + + // External groups will only be found when there is a configured attribute name for them samlIdentityProviderDefinition.addAttributeMapping("external_groups", Collections.singletonList("groups")); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); provider.setType(OriginKeys.SAML); provider.setActive(true); @@ -926,8 +980,8 @@ public void testSamlLogin_Custom_User_Attributes_And_Roles_In_ID_Token() throws provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); provider.setName("simplesamlphp for testzone1"); - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); - assertEquals(provider.getOriginKey(), provider.getConfig().getIdpEntityAlias()); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertThat(provider.getConfig().getIdpEntityAlias()).isEqualTo(provider.getOriginKey()); List idps = Collections.singletonList(provider.getOriginKey()); @@ -941,95 +995,93 @@ public void testSamlLogin_Custom_User_Attributes_And_Roles_In_ID_Token() throws clientDetails = IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, zoneId, clientDetails); clientDetails.setClientSecret("secret"); - IntegrationTestUtils.getClientCredentialsToken(zoneUrl,clientDetails.getClientId(), "secret"); + IntegrationTestUtils.getClientCredentialsToken(zoneUrl, clientDetails.getClientId(), "secret"); - webDriver.get(zoneUrl + "/logout.do"); + webDriver.get("%s/logout.do".formatted(zoneUrl)); - String authUrl = zoneUrl + "/oauth/authorize?client_id=" + clientDetails.getClientId() + "&redirect_uri=" + URLEncoder.encode(zoneUrl) + "&response_type=code&state=8tp0tR"; + String authUrl = zoneUrl + "/oauth/authorize?response_type=code&state=8tp0tR&client_id=" + clientDetails.getClientId() + "&redirect_uri=" + URLEncoder.encode(zoneUrl, StandardCharsets.UTF_8); webDriver.get(authUrl); //we should now be in the Simple SAML PHP site webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); sendCredentials("marissa5", "saml5"); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); - Cookie cookie= webDriver.manage().getCookieNamed("JSESSIONID"); + Cookie cookie = webDriver.manage().getCookieNamed("JSESSIONID"); - //do an auth code grant - //pass up the jsessionid - System.out.println("cookie = " + String.format("%s=%s",cookie.getName(), cookie.getValue())); + // do an auth code grant, passing the jsessionid + System.out.printf("Cookie: %s=%s%n", cookie.getName(), cookie.getValue()); serverRunning.setHostName("testzone1.localhost"); - Map authCodeTokenResponse = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, - UaaTestAccounts.standard(serverRunning), - clientDetails.getClientId(), - clientDetails.getClientSecret(), - null, - null, - "token id_token", - cookie.getValue(), - zoneUrl, - null, - false); - - webDriver.get(baseUrl + "/logout.do"); - webDriver.get(zoneUrl + "/logout.do"); + Map authCodeTokenResponse = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, + clientDetails.getClientId(), + clientDetails.getClientSecret(), + null, + null, + "token id_token", + cookie.getValue(), + zoneUrl, + null, + false); + + webDriver.get("%s/logout.do".formatted(baseUrl)); + webDriver.get("%s/logout.do".formatted(zoneUrl)); //validate access token - String accessToken = (String) authCodeTokenResponse.get(ACCESS_TOKEN); + String accessToken = authCodeTokenResponse.get(ACCESS_TOKEN); Jwt accessTokenJwt = JwtHelper.decode(accessToken); - Map accessTokenClaims = JsonUtils.readValue(accessTokenJwt.getClaims(), new TypeReference>() { + Map accessTokenClaims = JsonUtils.readValue(accessTokenJwt.getClaims(), new TypeReference<>() { }); List accessTokenScopes = (List) accessTokenClaims.get(ClaimConstants.SCOPE); - // Check that the user had the roles scope, which is a pre-requisite for getting roles returned in the id_token - assertThat(accessTokenScopes, hasItem(ClaimConstants.ROLES)); + // Check that the user had the roles scope, which is a pre-requisite for getting roles returned in the id_token + assertThat(accessTokenScopes).contains(ClaimConstants.ROLES); //validate that we have an ID token, and that it contains costCenter and manager values String idToken = authCodeTokenResponse.get("id_token"); - assertNotNull(idToken); + assertThat(idToken).isNotNull(); Jwt idTokenClaims = JwtHelper.decode(idToken); - Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() { + Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference<>() { }); - assertNotNull(claims.get(USER_ATTRIBUTES)); - Map> userAttributes = (Map>) claims.get(USER_ATTRIBUTES); - assertThat(userAttributes.get(COST_CENTERS), containsInAnyOrder(DENVER_CO)); - assertThat(userAttributes.get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); + assertThat(claims).containsKey(USER_ATTRIBUTES); + Map> userAttributes = (Map>) claims.get(USER_ATTRIBUTES); + assertThat(userAttributes.get(COST_CENTERS)).containsExactlyInAnyOrder(DENVER_CO); + assertThat(userAttributes.get(MANAGERS)).containsExactlyInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER); //validate that ID token contains the correct roles String[] expectedRoles = new String[]{"saml.user", "saml.admin"}; List idTokenRoles = (List) claims.get(ClaimConstants.ROLES); - assertThat(idTokenRoles, containsInAnyOrder(expectedRoles)); + assertThat(idTokenRoles).containsExactlyInAnyOrder(expectedRoles); //validate user info UserInfoResponse userInfo = IntegrationTestUtils.getUserInfo(zoneUrl, authCodeTokenResponse.get("access_token")); - Map> userAttributeMap = userInfo.getUserAttributes(); + Map> userAttributeMap = userInfo.getUserAttributes(); List costCenterData = userAttributeMap.get(COST_CENTERS); List managerData = userAttributeMap.get(MANAGERS); - assertThat(costCenterData, containsInAnyOrder(DENVER_CO)); - assertThat(managerData, containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); + assertThat(costCenterData).containsExactlyInAnyOrder(DENVER_CO); + assertThat(managerData).containsExactlyInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER); - // user info should contain the user's roles - List userInfoRoles = (List) userInfo.getRoles(); - assertThat(userInfoRoles, containsInAnyOrder(expectedRoles)); + // user info should contain the user's roles + List userInfoRoles = userInfo.getRoles(); + assertThat(userInfoRoles).containsExactlyInAnyOrder(expectedRoles); } @Test - public void testSamlLogin_Email_In_ID_Token_When_UserID_IsNotEmail() { + void samlLoginEmailInIDTokenWhenUserIDIsNotEmail() { //ensure we are able to resolve DNS for hostname testzone1.localhost String zoneId = "testzone4"; - String zoneUrl = baseUrl.replace("localhost", zoneId+".localhost"); + String zoneUrl = baseUrl.replace("localhost", zoneId + ".localhost"); //identity client token RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); //admin client token - to create users RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); //create the zone IdentityZoneConfiguration config = new IdentityZoneConfiguration(); @@ -1037,33 +1089,33 @@ public void testSamlLogin_Email_In_ID_Token_When_UserID_IsNotEmail() { IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); //create a zone admin user - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + zoneId + ".admin"); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); //get the zone admin token String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZoneIDP(SAML_ORIGIN, zoneId); samlIdentityProviderDefinition.addAttributeMapping(EMAIL_ATTRIBUTE_NAME, "emailAddress"); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); provider.setType(OriginKeys.SAML); provider.setActive(true); provider.setConfig(samlIdentityProviderDefinition); provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); - provider.setName("simplesamlphp for "+zoneId); + provider.setName("simplesamlphp for " + zoneId); - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); - assertEquals(provider.getOriginKey(), provider.getConfig().getIdpEntityAlias()); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertThat(provider.getConfig().getIdpEntityAlias()).isEqualTo(provider.getOriginKey()); List idps = Collections.singletonList(provider.getOriginKey()); @@ -1076,81 +1128,77 @@ public void testSamlLogin_Email_In_ID_Token_When_UserID_IsNotEmail() { clientDetails = IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, zoneId, clientDetails); clientDetails.setClientSecret("secret"); - String adminTokenInZone = IntegrationTestUtils.getClientCredentialsToken(zoneUrl,clientDetails.getClientId(), "secret"); + IntegrationTestUtils.getClientCredentialsToken(zoneUrl, clientDetails.getClientId(), "secret"); - webDriver.get(zoneUrl + "/logout.do"); + webDriver.get("%s/logout.do".formatted(zoneUrl)); - String authUrl = zoneUrl + "/oauth/authorize?client_id=" + clientDetails.getClientId() + "&redirect_uri=" + URLEncoder.encode(zoneUrl) + "&response_type=code&state=8tp0tR"; + String authUrl = zoneUrl + "/oauth/authorize?client_id=" + clientDetails.getClientId() + "&redirect_uri=" + URLEncoder.encode(zoneUrl, StandardCharsets.UTF_8) + "&response_type=code&state=8tp0tR"; webDriver.get(authUrl); //we should now be in the Simple SAML PHP site webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); sendCredentials("marissa6", "saml6"); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); - - Cookie cookie= webDriver.manage().getCookieNamed("JSESSIONID"); - - //do an auth code grant - //pass up the jsessionid - System.out.println("cookie = " + String.format("%s=%s",cookie.getName(), cookie.getValue())); - - serverRunning.setHostName(zoneId+".localhost"); - Map authCodeTokenResponse = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, - UaaTestAccounts.standard(serverRunning), - clientDetails.getClientId(), - clientDetails.getClientSecret(), - null, - null, - "token id_token", - cookie.getValue(), - zoneUrl, - null, - false); - - webDriver.get(baseUrl + "/logout.do"); - webDriver.get(zoneUrl + "/logout.do"); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); + + Cookie cookie = webDriver.manage().getCookieNamed("JSESSIONID"); + + // do an auth code grant, passing the jsessionid + System.out.println("cookie = " + "%s=%s".formatted(cookie.getName(), cookie.getValue())); + + serverRunning.setHostName(zoneId + ".localhost"); + Map authCodeTokenResponse = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, + clientDetails.getClientId(), + clientDetails.getClientSecret(), + null, + null, + "token id_token", + cookie.getValue(), + zoneUrl, + null, + false); + + webDriver.get("%s/logout.do".formatted(baseUrl)); + webDriver.get("%s/logout.do".formatted(zoneUrl)); //validate that we have an ID token, and that it contains costCenter and manager values String idToken = authCodeTokenResponse.get("id_token"); - assertNotNull(idToken); + assertThat(idToken).isNotNull(); Jwt idTokenClaims = JwtHelper.decode(idToken); - Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() { + Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference<>() { }); - assertNotNull(claims.get(USER_ATTRIBUTES)); - assertEquals("marissa6", claims.get(ClaimConstants.USER_NAME)); - assertEquals("marissa6@test.org", claims.get(ClaimConstants.EMAIL)); + assertThat(claims).containsKey(USER_ATTRIBUTES) + .containsEntry(ClaimConstants.USER_NAME, "marissa6") + .containsEntry(ClaimConstants.EMAIL, "marissa6@test.org"); } - @Test - public void testSimpleSamlPhpLoginInTestZone1Works() { + void simpleSamlPhpLoginInTestZone1Works() { String zoneId = "testzone1"; RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") ); RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); IdentityZoneConfiguration config = new IdentityZoneConfiguration(); config.getCorsPolicy().getDefaultConfiguration().setAllowedMethods(List.of(GET.toString(), POST.toString())); IdentityZone zone = IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); - String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; - ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); + String email = new RandomValueStringGenerator().generate() + "@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, "zones." + zoneId + ".admin"); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); - String zoneAdminToken = - IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, - UaaTestAccounts.standard(serverRunning), - "identity", - "identitysecret", - email, - "secr3T"); + IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone1IDP(SAML_ORIGIN); IdentityProvider provider = new IdentityProvider<>(); @@ -1161,88 +1209,82 @@ public void testSimpleSamlPhpLoginInTestZone1Works() { provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); provider.setName("simplesamlphp for testzone1"); - - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); //we have to create two providers to avoid automatic redirect SamlIdentityProviderDefinition samlIdentityProviderDefinition1 = samlIdentityProviderDefinition.clone(); - samlIdentityProviderDefinition1.setIdpEntityAlias(samlIdentityProviderDefinition.getIdpEntityAlias()+"-1"); + samlIdentityProviderDefinition1.setIdpEntityAlias(samlIdentityProviderDefinition.getIdpEntityAlias() + "-1"); samlIdentityProviderDefinition1.setMetaDataLocation(getValidRandomIDPMetaData()); samlIdentityProviderDefinition1.setLinkText("Dummy SAML provider"); - IdentityProvider provider1 = new IdentityProvider(); + IdentityProvider provider1 = new IdentityProvider<>(); provider1.setIdentityZoneId(zoneId); provider1.setType(OriginKeys.SAML); provider1.setActive(true); provider1.setConfig(samlIdentityProviderDefinition1); provider1.setOriginKey(samlIdentityProviderDefinition1.getIdpEntityAlias()); provider1.setName("simplesamlphp 1 for testzone1"); - provider1 = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider1); + IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider1); - assertNotNull(provider.getId()); + assertThat(provider.getId()).isNotNull(); - String testZone1Url = baseUrl.replace("localhost", zoneId+".localhost"); - webDriver.get(baseUrl + "/logout.do"); - webDriver.get(testZone1Url + "/logout.do"); - webDriver.get(testZone1Url + "/login"); - Assert.assertEquals(zone.getName(), webDriver.getTitle()); + String testZone1Url = baseUrl.replace("localhost", zoneId + ".localhost"); + webDriver.get("%s/logout.do".formatted(baseUrl)); + webDriver.get("%s/logout.do".formatted(testZone1Url)); + webDriver.get("%s/login".formatted(testZone1Url)); + assertThat(webDriver.getTitle()).isEqualTo(zone.getName()); // the first provider is shown - List elements = webDriver.findElements(By.xpath("//a[text()='"+ samlIdentityProviderDefinition.getLinkText()+"']")); - assertNotNull(elements); - assertEquals(1, elements.size()); + List elements = webDriver.findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); + assertThat(elements).hasSize(1); // the dummy provider is shown - elements = webDriver.findElements(By.xpath("//a[text()='"+ samlIdentityProviderDefinition1.getLinkText()+"']")); - assertNotNull(elements); - assertEquals(1, elements.size()); + elements = webDriver.findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition1.getLinkText() + "']")); + assertThat(elements).hasSize(1); // click on the first provider to login WebElement element = webDriver.findElement(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); element.click(); webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); sendCredentials(testAccounts.getUserName(), testAccounts.getPassword()); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); - webDriver.get(baseUrl + "/logout.do"); - webDriver.get(testZone1Url + "/logout.do"); + webDriver.get("%s/logout.do".formatted(baseUrl)); + webDriver.get("%s/logout.do".formatted(testZone1Url)); //disable the first provider - SamlLogoutAuthSourceEndpoint.logoutAuthSource_goesToSamlWelcomePage(webDriver, IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); + SamlLogoutAuthSourceEndpoint.assertThatLogoutAuthSource_goesToSamlWelcomePage(webDriver, IntegrationTestUtils.SIMPLESAMLPHP_UAA_ACCEPTANCE, SAML_AUTH_SOURCE); provider.setActive(false); - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); - assertNotNull(provider.getId()); - webDriver.get(testZone1Url + "/login"); - Assert.assertEquals(zone.getName(), webDriver.getTitle()); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertThat(provider.getId()).isNotNull(); + webDriver.get("%s/logout.do".formatted(testZone1Url)); + assertThat(webDriver.getTitle()).isEqualTo(zone.getName()); // the first provider is not shown - elements = webDriver.findElements(By.xpath("//a[text()='"+ samlIdentityProviderDefinition.getLinkText()+"']")); - Assert.assertTrue(elements.isEmpty()); + elements = webDriver.findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); + assertThat(elements).isEmpty(); // the dummy provider is shown - elements = webDriver.findElements(By.xpath("//a[text()='"+ samlIdentityProviderDefinition1.getLinkText()+"']")); - assertNotNull(elements); - assertEquals(1, elements.size()); + elements = webDriver.findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition1.getLinkText() + "']")); + assertThat(elements).hasSize(1); //enable the first provider provider.setActive(true); - provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); - assertNotNull(provider.getId()); - webDriver.get(testZone1Url + "/login"); - Assert.assertEquals(zone.getName(), webDriver.getTitle()); + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); + assertThat(provider.getId()).isNotNull(); + webDriver.get("%s/login".formatted(testZone1Url)); + assertThat(webDriver.getTitle()).isEqualTo(zone.getName()); // the first provider is shown - elements = webDriver.findElements(By.xpath("//a[text()='"+ samlIdentityProviderDefinition.getLinkText()+"']")); - assertNotNull(elements); - assertEquals(1, elements.size()); + elements = webDriver.findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition.getLinkText() + "']")); + assertThat(elements).hasSize(1); // the dummy provider is shown - elements = webDriver.findElements(By.xpath("//a[text()='"+ samlIdentityProviderDefinition1.getLinkText()+"']")); - assertNotNull(elements); - assertEquals(1, elements.size()); + elements = webDriver.findElements(By.xpath("//a[text()='" + samlIdentityProviderDefinition1.getLinkText() + "']")); + assertThat(elements).hasSize(1); } @Test - public void testLoginPageShowsIDPsForAuthcodeClient() throws Exception { + void loginPageShowsIDPsForAuthCodeClient() { IdentityProvider provider = createIdentityProvider(SAML_ORIGIN); IdentityProvider provider2 = createIdentityProvider("simplesamlphp2"); List idps = Arrays.asList( - provider.getConfig().getIdpEntityAlias(), - provider2.getConfig().getIdpEntityAlias() + provider.getConfig().getIdpEntityAlias(), + provider2.getConfig().getIdpEntityAlias() ); String adminAccessToken = testClient.getOAuthAccessToken("admin", "adminsecret", "client_credentials", "clients.read clients.write clients.secret clients.admin"); @@ -1254,17 +1296,17 @@ public void testLoginPageShowsIDPsForAuthcodeClient() throws Exception { testClient.createClient(adminAccessToken, clientDetails); - webDriver.get(baseUrl + "/oauth/authorize?client_id=" + clientId + "&redirect_uri=http%3A%2F%2Flocalhost%3A8888%2Flogin&response_type=code&state=8tp0tR"); - webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")); - webDriver.findElement(By.xpath("//a[text()='" + provider2.getConfig().getLinkText() + "']")); + webDriver.get("%s/oauth/authorize?client_id=%s&redirect_uri=http%%3A%%2F%%2Flocalhost%%3A8888%%2Flogin&response_type=code&state=8tp0tR".formatted(baseUrl, clientId)); + assertThat(webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']"))).isNotNull(); + assertThat(webDriver.findElement(By.xpath("//a[text()='" + provider2.getConfig().getLinkText() + "']"))).isNotNull(); } @Test - public void testLoginSamlOnlyProviderNoUsernamePassword() throws Exception { - IdentityProvider provider = createIdentityProvider(SAML_ORIGIN); - IdentityProvider provider2 = createIdentityProvider("simplesamlphp2"); + void loginSamlOnlyProviderNoUsernamePassword() { + IdentityProvider provider = createIdentityProvider(SAML_ORIGIN); + IdentityProvider provider2 = createIdentityProvider("simplesamlphp2"); List idps = Arrays.asList(provider.getOriginKey(), provider2.getOriginKey()); - webDriver.get(baseUrl + "/logout.do"); + webDriver.get("%s/logout.do".formatted(baseUrl)); String adminAccessToken = testClient.getOAuthAccessToken("admin", "adminsecret", "client_credentials", "clients.read clients.write clients.secret clients.admin"); String clientId = UUID.randomUUID().toString(); @@ -1272,26 +1314,22 @@ public void testLoginSamlOnlyProviderNoUsernamePassword() throws Exception { clientDetails.setClientSecret("secret"); clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, idps); testClient.createClient(adminAccessToken, clientDetails); - webDriver.get(baseUrl + "/oauth/authorize?client_id=" + clientId + "&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fuaa%3Alogin&response_type=code&state=8tp0tR"); - try { - webDriver.findElement(By.name("username")); - fail("Element username should not be present"); - } catch (NoSuchElementException ignored) { - } - try { - webDriver.findElement(By.name("password")); - fail("Element username should not be present"); - } catch (NoSuchElementException ignored) { - } - webDriver.get(baseUrl + "/logout.do"); + webDriver.get("%s/oauth/authorize?client_id=%s&redirect_uri=http%%3A%%2F%%2Flocalhost%%3A8080%%2Fuaa%%3Alogin&response_type=code&state=8tp0tR".formatted(baseUrl, clientId)); + + assertThatThrownBy(() -> webDriver.findElement(byUsername)) + .isInstanceOf(NoSuchElementException.class); + assertThatThrownBy(() -> webDriver.findElement(byPassword)) + .isInstanceOf(NoSuchElementException.class); + + webDriver.get("%s/logout.do".formatted(baseUrl)); } @Test - public void testSamlLoginClientIDPAuthorizationAutomaticRedirect() throws Exception { + void samlLoginClientIDPAuthorizationAutomaticRedirect() { + webDriver.get("%s/logout.do".formatted(baseUrl)); IdentityProvider provider = createIdentityProvider(SAML_ORIGIN); - assertEquals(provider.getOriginKey(), provider.getConfig().getIdpEntityAlias()); + assertThat(provider.getConfig().getIdpEntityAlias()).isEqualTo(provider.getOriginKey()); List idps = Collections.singletonList(provider.getOriginKey()); - webDriver.get(baseUrl + "/logout.do"); String adminAccessToken = testClient.getOAuthAccessToken("admin", "adminsecret", "client_credentials", "clients.read clients.write clients.secret clients.admin"); String clientId = UUID.randomUUID().toString(); @@ -1302,18 +1340,18 @@ public void testSamlLoginClientIDPAuthorizationAutomaticRedirect() throws Except testClient.createClient(adminAccessToken, clientDetails); - webDriver.get(baseUrl + "/oauth/authorize?client_id=" + clientId + "&redirect_uri=" + URLEncoder.encode(baseUrl) + "&response_type=code&state=8tp0tR"); - //we should now be in the Simple SAML PHP site + webDriver.get("%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=8tp0tR".formatted(baseUrl, clientId, URLEncoder.encode(baseUrl, StandardCharsets.UTF_8))); + // we should now be in the Simple SAML PHP site webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); sendCredentials(testAccounts.getUserName(), "koala"); - assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); - webDriver.get(baseUrl + "/logout.do"); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText()).contains("Where to?"); + webDriver.get("%s/logout.do".formatted(baseUrl)); } @Test - public void testLoginClientIDPAuthorizationAlreadyLoggedIn() { - webDriver.get(baseUrl + "/logout.do"); + void loginClientIDPAuthorizationAlreadyLoggedIn() { + webDriver.get("%s/logout.do".formatted(baseUrl)); String adminAccessToken = testClient.getOAuthAccessToken("admin", "adminsecret", "client_credentials", "clients.read clients.write clients.secret clients.admin"); String clientId = UUID.randomUUID().toString(); @@ -1326,19 +1364,20 @@ public void testLoginClientIDPAuthorizationAlreadyLoggedIn() { sendCredentials(testAccounts.getUserName(), "koala", By.xpath("//input[@value='Sign in']")); - webDriver.get(baseUrl + "/oauth/authorize?client_id=" + clientId + "&redirect_uri=http%3A%2F%2Flocalhost%3A8888%2Flogin&response_type=code&state=8tp0tR"); + webDriver.get("%s/oauth/authorize?client_id=%s&redirect_uri=http%%3A%%2F%%2Flocalhost%%3A8888%%2Flogin&response_type=code&state=8tp0tR".formatted(baseUrl, clientId)); - assertThat(webDriver.findElement(By.cssSelector("p")).getText(), Matchers.containsString(clientId + " does not support your identity provider. To log into an identity provider supported by the application")); - webDriver.get(baseUrl + "/logout.do"); + assertThat(webDriver.findElement(By.cssSelector("p")).getText()).contains(clientId + " does not support your identity provider. To log into an identity provider supported by the application"); + webDriver.get("%s/logout.do".formatted(baseUrl)); } @Test - public void testSpringSamlEndpointsWithEmptyContext() throws IOException { - CallEmpptyPageAndCheckHttpStatusCode("/saml/discovery", 200); - CallEmpptyPageAndCheckHttpStatusCode("/saml/SingleLogout", 400); - CallEmpptyPageAndCheckHttpStatusCode("/saml/login/alias/foo", 400); - CallEmpptyPageAndCheckHttpStatusCode("/saml/web/metadata/login", 404); - CallEmpptyPageAndCheckHttpStatusCode("/saml/SSO/foo", 200); + void springSamlEndpointsWithEmptyContext() throws IOException { + CallEmptyPageAndCheckHttpStatusCode("/saml/web/metadata/login", 404); + // These endpoints are now redirect to /login + CallEmptyPageAndCheckHttpStatusCode("/saml/SingleLogout/alias/foo", 302); + CallEmptyPageAndCheckHttpStatusCode("/saml/login/alias/foo", 302); + // and to /saml_error + CallEmptyPageAndCheckHttpStatusCode("/saml/SSO/alias/foo", 302); } public SamlIdentityProviderDefinition createTestZone2IDP(String alias) { @@ -1354,35 +1393,11 @@ public SamlIdentityProviderDefinition createTestZoneIDP(String alias, String zon } private SamlIdentityProviderDefinition createIDPWithNoSLOSConfigured() { - String idpMetaData = "\n" + - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + - " \n" + - " \n" + - " \n" + - " TAS Identity & Credentials\n" + - " mailto:tas-identity-and-credentials@groups.vmware.com\n" + - " \n" + - "\n"; + String metadata = loadResouceAsString("no_single_logout_service-metadata.xml"); SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); def.setZoneId("uaa"); - def.setMetaDataLocation(idpMetaData); + def.setMetaDataLocation(metadata); def.setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"); def.setAssertionConsumerIndex(0); def.setMetadataTrustCheck(false); @@ -1392,23 +1407,10 @@ private SamlIdentityProviderDefinition createIDPWithNoSLOSConfigured() { return def; } - private void logout() { - webDriver.findElement(By.cssSelector(".dropdown-trigger")).click(); - webDriver.findElement(By.linkText("Sign Out")).click(); - } - - private void login(IdentityProvider provider) { - webDriver.get(baseUrl + "/login"); - Assert.assertEquals("Cloud Foundry", webDriver.getTitle()); - webDriver.findElement(By.xpath("//a[text()='" + provider.getConfig().getLinkText() + "']")).click(); - webDriver.findElement(By.xpath(SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR)); - sendCredentials(testAccounts.getUserName(), testAccounts.getPassword()); - } - private void sendCredentials(String username, String password, By loginButtonSelector) { - webDriver.findElement(By.name("username")).clear(); - webDriver.findElement(By.name("username")).sendKeys(username); - webDriver.findElement(By.name("password")).sendKeys(password); + webDriver.findElement(byUsername).clear(); + webDriver.findElement(byUsername).sendKeys(username); + webDriver.findElement(byPassword).sendKeys(password); webDriver.findElement(loginButtonSelector).click(); } @@ -1416,10 +1418,11 @@ private void sendCredentials(String username, String password) { sendCredentials(username, password, By.id("submit_button")); } - private void CallEmpptyPageAndCheckHttpStatusCode(String errorPath, int codeExpected) throws IOException { - HttpURLConnection cn = (HttpURLConnection)new URL(baseUrl + errorPath).openConnection(); - cn.setRequestMethod("GET"); - cn.connect(); - assertEquals("Check status code from " + errorPath + " is " + codeExpected, cn.getResponseCode(), codeExpected); + private void CallEmptyPageAndCheckHttpStatusCode(String errorPath, int codeExpected) throws IOException { + HttpURLConnection cn = (HttpURLConnection) new URL(baseUrl + errorPath).openConnection(); + cn.setInstanceFollowRedirects(false); + assertThat(cn.getResponseCode()) + .as("Check status code from %s", errorPath) + .isEqualTo(codeExpected); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/CustomErrorPage.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/CustomErrorPage.java index 3a3732dc7f1..2b51261de84 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/CustomErrorPage.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/CustomErrorPage.java @@ -1,13 +1,14 @@ package org.cloudfoundry.identity.uaa.integration.pageObjects; -import org.hamcrest.Matcher; import org.openqa.selenium.WebDriver; +/** + * The CustomErrorPage class represents the custom error page on the UAA server. + */ public class CustomErrorPage extends Page { - public CustomErrorPage(WebDriver driver, Matcher urlMatcher) { + public CustomErrorPage(WebDriver driver, String urlContent) { super(driver); - validateUrl(driver, urlMatcher); + assertThatUrlEventuallySatisfies(assertUrl -> assertUrl.contains(urlContent)); } } - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/DnsErrorPage.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/DnsErrorPage.java deleted file mode 100644 index e9768c2cd15..00000000000 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/DnsErrorPage.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.cloudfoundry.identity.uaa.integration.pageObjects; - -import java.util.Date; - -import org.openqa.selenium.WebDriver; - -import static org.hamcrest.Matchers.containsString; - -public class DnsErrorPage extends Page { - public DnsErrorPage(WebDriver driver) { - super(driver); - validatePageSource(driver, containsString("This site can’t be reached")); - } -} - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/FaviconElement.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/FaviconElement.java index 8de2b522a2a..a710441cc50 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/FaviconElement.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/FaviconElement.java @@ -2,23 +2,29 @@ import org.openqa.selenium.WebDriver; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; -import static org.junit.Assert.assertThat; - +/** + * The FaviconElement class represents the favicon image on the UAA server. + */ public class FaviconElement extends Page { - // The favicon.ico image is not present on the server because we specify a custom icon URL - // in the headers, but browsers try to hit it and tests need to hit this default URL. - static public FaviconElement getDefaultIcon(WebDriver driver, String baseUrl) { - driver.get(baseUrl + "/favicon.ico"); - return new FaviconElement(driver); - } + private static final String FAVICON_ICO = "/favicon.ico"; - // Expect a 404 error when landing on the favicon URL. + /** + * Expect a 404 error when landing on the favicon URL. + */ public FaviconElement(WebDriver driver) { super(driver); - assertThat("Should be on the favicon image", driver.getCurrentUrl(), endsWith("/favicon.ico")); - assertThat(driver.getPageSource(), containsString("Something went amiss.")); + assertThatUrlEventuallySatisfies(assertUrl -> assertUrl.as("Should be on the favicon image").endsWith(FAVICON_ICO)); + assertThatPageSource().contains("Something went amiss."); + } + + /** + * Get the default favicon image. + * The favicon.ico image is not present on the server because we specify a custom icon URL + * in the headers, but browsers try to hit it and tests need to hit this default URL. + */ + public static FaviconElement getDefaultIcon(WebDriver driver, String baseUrl) { + driver.get(baseUrl + FAVICON_ICO); + return new FaviconElement(driver); } -} \ No newline at end of file +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/HomePage.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/HomePage.java index 051088c27f9..0721a74a901 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/HomePage.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/HomePage.java @@ -1,31 +1,41 @@ package org.cloudfoundry.identity.uaa.integration.pageObjects; +import org.cloudfoundry.identity.uaa.home.HomeController; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.springframework.ui.Model; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; +import java.security.Principal; +import java.util.function.Consumer; -// TODO extend LoggedInPage +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The HomePage class represents the home page on the UAA server. + * It can have either url: `/home` or just `/`. + * {@link HomeController#home(Model, Principal)} + */ public class HomePage extends Page { - static final private String urlPath = "/"; + private static final String SLASH_URL_PATH = "/"; + private static final String HOME_URL_PATH = "/home"; public HomePage(WebDriver driver) { super(driver); - validateUrl(driver, endsWith(urlPath)); - validatePageSource(driver, containsString("Where to?")); + Consumer endsWithSlash = url -> assertThat(url).endsWith(SLASH_URL_PATH); + Consumer endsWithHome = url -> assertThat(url).endsWith(HOME_URL_PATH); + assertThatUrlEventuallySatisfies(assertUrl -> assertUrl.satisfiesAnyOf(endsWithSlash, endsWithHome)); + assertThatPageSource().contains("Where to?"); } - static public LoginPage tryToGoHome_redirectsToLoginPage(WebDriver driver, String baseUrl) { - driver.get(baseUrl + urlPath); + public static LoginPage assertThatGoHome_redirectsToLoginPage(WebDriver driver, String baseUrl) { + driver.get(baseUrl + SLASH_URL_PATH); return new LoginPage(driver); } public boolean hasLastLoginTime() { WebElement lastLoginTime = driver.findElement(By.id("last_login_time")); String loginTime = lastLoginTime.getText(); - return loginTime != null && ! loginTime.isBlank(); + return loginTime != null && !loginTime.isBlank(); } } - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/LoginPage.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/LoginPage.java index e8f23839c18..a39603ab9b2 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/LoginPage.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/LoginPage.java @@ -2,39 +2,61 @@ import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; -import static org.hamcrest.Matchers.matchesPattern; +import java.util.concurrent.atomic.AtomicReference; +/** + * The LoginPage class represents the login page on the UAA server. + * It has url matching: `/login`. + */ public class LoginPage extends Page { - static final private String urlPath = "/login"; + private static final String URL_PATH = "/login"; public LoginPage(WebDriver driver) { super(driver); - validateUrl(driver, matchesPattern(".*" + urlPath + "(\\?.*)?$")); + assertThatUrlEventuallySatisfies(assertUrl -> assertUrl.matches(".*" + URL_PATH + "(\\?.*)?$")); } - static public LoginPage go(WebDriver driver, String baseUrl) { - driver.get(baseUrl + urlPath); + public static LoginPage go(WebDriver driver, String baseUrl) { + driver.get(baseUrl + URL_PATH); return new LoginPage(driver); } - // When there is a SAML integration, there is a link to go to a SAML login page instead. This assumes there is - // only one SAML link. - public SamlLoginPage clickSamlLink_goesToSamlLoginPage() { - clickFirstSamlLoginLink(); + /** + * When there is a SAML integration, there is a link to go to a SAML login page. + * Clicking the link will go to the SAML login page. + */ + public SamlLoginPage assertThatSamlLink_goesToSamlLoginPage(String matchText) { + clickSamlLoginLinkWithText(matchText); return new SamlLoginPage(driver); } - // If the SAML IDP has no logout URL in the metadata, logging out of UAA will leave - // the IDP still logged in, and when going back to the SAML login page, it will log - // the app back in automatically and immediately redirect to the post-login page. - public HomePage clickSamlLink_goesToHomePage() { - clickFirstSamlLoginLink(); + /** + * If the SAML IDP has no logout URL in the metadata, logging out of UAA will leave + * the IDP still logged in. + * When going back to the SAML login page, it will log + * the app back in automatically and immediately redirect to the post-login page. + */ + public HomePage assertThatSamlLink_goesToHomePage(String matchText) { + clickSamlLoginLinkWithText(matchText); return new HomePage(driver); } - private void clickFirstSamlLoginLink() { - driver.findElement(By.className("saml-login-link")).click(); + /** + * Click the first link that contains the given text + */ + private void clickSamlLoginLinkWithText(String matchText) { + final AtomicReference matchingElement = new AtomicReference<>(); + driver.findElements(By.className("saml-login-link")).forEach(webElement -> { + if (webElement.getText().contains(matchText)) { + matchingElement.compareAndSet(null, webElement); + } + }); + if (matchingElement.get() == null) { + throw new RuntimeException("No element with text " + matchText + " found"); + } + matchingElement.get().click(); } -} \ No newline at end of file +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/Page.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/Page.java index 7373c8fd5db..04fba53b2ad 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/Page.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/Page.java @@ -1,15 +1,20 @@ package org.cloudfoundry.identity.uaa.integration.pageObjects; -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -import org.hamcrest.Matcher; +import org.assertj.core.api.AbstractStringAssert; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.assertThat; +import java.time.Duration; +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +/** + * The Page class is the base class, representing a web page. + * It provides methods for validating the URL, page source, and title, + * as well as performing common page actions like logging out and clearing cookies. + */ public class Page { protected WebDriver driver; @@ -18,27 +23,35 @@ public Page(WebDriver driver) { driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5)); } - protected static void validateUrl(WebDriver driver, Matcher urlMatcher) { - assertThat("URL validation failed", driver.getCurrentUrl(), urlMatcher); + public static AbstractStringAssert assertThatUrlEventuallySatisfies(WebDriver driver, Consumer> assertUrl) { + await().atMost(Duration.ofSeconds(5)) + .untilAsserted(() -> assertUrl.accept(assertThatUrl(driver))); + return assertThatUrl(driver); } - public void validateUrl(Matcher urlMatcher) { - validateUrl(driver, urlMatcher); + private static AbstractStringAssert assertThatUrl(WebDriver driver) { + return assertThat(driver.getCurrentUrl()); } - protected static void validatePageSource(WebDriver driver, Matcher matcher) { - assertThat(driver.getPageSource(), matcher); + public AbstractStringAssert assertThatUrlEventuallySatisfies(Consumer> assertUrl) { + await().atMost(Duration.ofSeconds(5)) + .untilAsserted(() -> assertUrl.accept(assertThatUrl(driver))); + return assertThatUrl(); } - public void validatePageSource(Matcher matcher) { - validatePageSource(driver, matcher); + public AbstractStringAssert assertThatUrl() { + return assertThat(driver.getCurrentUrl()); } - public void validateTitle(Matcher matcher) { - assertThat(driver.getTitle(), matcher); + public AbstractStringAssert assertThatPageSource() { + return assertThat(driver.getPageSource()); } - public LoginPage logout_goesToLoginPage() { + public AbstractStringAssert assertThatTitle() { + return assertThat(driver.getTitle()); + } + + public LoginPage assertThatLogout_goesToLoginPage() { clickLogout(); return new LoginPage(driver); } @@ -51,11 +64,4 @@ private void clickLogout() { public void clearCookies() { driver.manage().deleteAllCookies(); } - - public static void validateUrlStartsWithWait(WebDriver driver, String currentUrlStart) throws InterruptedException { - if (!driver.getCurrentUrl().startsWith(currentUrlStart)) { - TimeUnit.SECONDS.sleep(5); - } - assertThat(driver.getCurrentUrl(), startsWith(currentUrlStart)); - } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/PasscodePage.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/PasscodePage.java index 69a49536b33..40ad60f7529 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/PasscodePage.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/PasscodePage.java @@ -2,21 +2,22 @@ import org.openqa.selenium.WebDriver; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; -import static org.junit.Assert.assertThat; - +/** + * The PasscodePage class represents the passcode page on the UAA server. + * Which displays the temporary authentication code. + * It has url matching: `/passcode`. + */ public class PasscodePage extends Page { - static final private String urlPath = "/passcode"; + private static final String URL_PATH = "/passcode"; public PasscodePage(WebDriver driver) { super(driver); - validateUrl(driver, endsWith(urlPath)); - validatePageSource(driver, containsString("Temporary Authentication Code") ); + assertThatUrlEventuallySatisfies(assertUrl -> assertUrl.endsWith(URL_PATH)); + assertThatPageSource().contains("Temporary Authentication Code"); } - static public LoginPage requestPasscode_goesToLoginPage(WebDriver driver, String baseUrl) { - driver.get(baseUrl + urlPath); + public static LoginPage assertThatRequestPasscode_goesToLoginPage(WebDriver driver, String baseUrl) { + driver.get(baseUrl + URL_PATH); return new LoginPage(driver); } -} \ No newline at end of file +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlErrorPage.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlErrorPage.java index ef76ba3cc06..434c0c4161f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlErrorPage.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlErrorPage.java @@ -2,15 +2,15 @@ import org.openqa.selenium.WebDriver; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; - +/** + * The SamlErrorPage class represents the saml error page on the UAA server. + * It has url matching: `/saml_error`. + */ public class SamlErrorPage extends Page { - static final private String urlPath = "/saml_error"; + private static final String URL_PATH = "/saml_error"; public SamlErrorPage(WebDriver driver) { super(driver); - validateUrl(driver, endsWith(urlPath)); + assertThatUrlEventuallySatisfies(assertUrl -> assertUrl.endsWith(URL_PATH)); } } - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlLoginPage.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlLoginPage.java index e524e600237..ebc8ae3e19f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlLoginPage.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlLoginPage.java @@ -1,35 +1,39 @@ package org.cloudfoundry.identity.uaa.integration.pageObjects; -import org.hamcrest.Matcher; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import static org.hamcrest.Matchers.containsString; - +/** + * The SamlLoginPage class represents the login page on the SimpleSAML server. + * This class provides methods to interact with the SAML login page and perform login actions. + * It has url matching: `/module.php/core/loginuserpass`. + */ public class SamlLoginPage extends Page { // This is on the saml server, not the UAA server - static final private String urlPath = "/module.php/core/loginuserpass"; + private static final String URL_PATH = "/module.php/core/loginuserpass"; public SamlLoginPage(WebDriver driver) { super(driver); - validateUrl(driver, containsString(urlPath)); + assertThatUrlEventuallySatisfies(assertUrl -> assertUrl.contains(URL_PATH)); } - public HomePage login_goesToHomePage(String username, String password) { + public HomePage assertThatLogin_goesToHomePage(String username, String password) { sendLoginCredentials(username, password); return new HomePage(driver); } - public PasscodePage login_goesToPasscodePage(String username, String password) { + public PasscodePage assertThatLogin_goesToPasscodePage(String username, String password) { sendLoginCredentials(username, password); return new PasscodePage(driver); } - public CustomErrorPage login_goesToCustomErrorPage(String username, String password, Matcher urlMatcher) { + + public CustomErrorPage assertThatLogin_goesToCustomErrorPage(String username, String password, String urlContent) { sendLoginCredentials(username, password); - return new CustomErrorPage(driver, urlMatcher); + return new CustomErrorPage(driver, urlContent); } - public SamlErrorPage login_goesToSamlErrorPage(String username, String password) { + + public SamlErrorPage assertThatLogin_goesToSamlErrorPage(String username, String password) { sendLoginCredentials(username, password); return new SamlErrorPage(driver); } @@ -41,4 +45,4 @@ private void sendLoginCredentials(String username, String password) { driver.findElement(By.name("password")).sendKeys(password); driver.findElement(By.id("submit_button")).click(); } -} \ No newline at end of file +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlWelcomePage.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlWelcomePage.java index 2a88d6aa0a8..f4eb170175c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlWelcomePage.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/pageObjects/SamlWelcomePage.java @@ -1,19 +1,16 @@ package org.cloudfoundry.identity.uaa.integration.pageObjects; -import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; +/** + * The SamlWelcomePage class represents the welcome page on the SimpleSAML server. + * It has url matching: `/module.php/core/welcome`. + */ public class SamlWelcomePage extends Page { - static final private String urlPath = "module.php/core/welcome"; + private static final String URL_PATH = "module.php/core/welcome"; public SamlWelcomePage(WebDriver driver) { super(driver); - validateUrl(driver, endsWith(urlPath)); + assertThatUrlEventuallySatisfies(assertUrl -> assertUrl.endsWith(URL_PATH)); } - } - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index e3a158aa6a3..62e1b16b922 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -9,6 +9,7 @@ import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.cookie.BasicClientCookie; +import org.assertj.core.api.Assertions; import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.account.UserAccountStatus; import org.cloudfoundry.identity.uaa.account.UserInfoResponse; @@ -25,6 +26,7 @@ import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtClientAuthentication; import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; @@ -40,11 +42,8 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; -import org.hamcrest.CoreMatchers; import org.hamcrest.Description; -import org.hamcrest.Matchers; import org.hamcrest.TypeSafeMatcher; -import org.junit.Assert; import org.openqa.selenium.By; import org.openqa.selenium.Cookie; import org.openqa.selenium.OutputType; @@ -61,7 +60,6 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.StringHttpMessageConverter; -import org.springframework.security.crypto.codec.Base64; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -82,6 +80,7 @@ import java.security.SecureRandom; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -90,66 +89,71 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.stream.Collectors.joining; +import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.USER_OAUTH_APPROVAL; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils.createRequestFactory; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.core.StringStartsWith.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.springframework.http.HttpHeaders.ACCEPT; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.USER_OAUTH_APPROVAL; import static org.springframework.util.StringUtils.hasText; public class IntegrationTestUtils { public static final String SIMPLESAMLPHP_UAA_ACCEPTANCE = "http://simplesamlphp.uaa-acceptance.cf-app.com"; public static final String SIMPLESAMLPHP_LOGIN_PROMPT_XPATH_EXPR = - "//h1[contains(text(), 'Enter your username and password')]"; + "//h1[contains(text(), 'Enter your username and password')]"; public static final String SAML_AUTH_SOURCE = "example-userpass"; - public static final String EXAMPLE_DOT_COM_SAML_IDP_METADATA = "\n" + - "\n" + - " \n" + - " \n" + - " HOSWDJYkLvErI1gVynUVmufFVDCKPqExLnnnMjXgoJQ=ryMe0PXC+vR/c0nSEhSJsTaF0lHiuZ6PguqCbul7RC9WKLmFS9DD7Dgp3WHQ2zWpRimCTHxw/VO9hyCTxAcW9zxW4OdpD4YorqcmXtLkpasBCVuFLbQ8oylnjrem4kpGflfnuk3bW1mp6AXy52jwALDm8MsTwLK+O74YkeVTPP5bki/PK0N4jHnhYhvhHKUyT8Gug0v2o4KA/1ik83e9vcYEFc/9WGpXFeDMF6pXsJQqC/+eWoLfZJDNrwSsSlg+oD+ZF91YccN9i9lJoaIPcVvPWDfEv7vL79LgnmPBeYxm/fWb4/ANMxvCLIP1R3Ixrz5oFoIX2NP1+uZOpoRWbg==\n" + - "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + - " \n" + - " \n" + - " \n" + - " Filip\n" + - " Hanik\n" + - " fhanik@pivotal.io\n" + - " \n" + - "\n"; + public static final String EXAMPLE_DOT_COM_SAML_IDP_METADATA = """ + + + + + HOSWDJYkLvErI1gVynUVmufFVDCKPqExLnnnMjXgoJQ=ryMe0PXC+vR/c0nSEhSJsTaF0lHiuZ6PguqCbul7RC9WKLmFS9DD7Dgp3WHQ2zWpRimCTHxw/VO9hyCTxAcW9zxW4OdpD4YorqcmXtLkpasBCVuFLbQ8oylnjrem4kpGflfnuk3bW1mp6AXy52jwALDm8MsTwLK+O74YkeVTPP5bki/PK0N4jHnhYhvhHKUyT8Gug0v2o4KA/1ik83e9vcYEFc/9WGpXFeDMF6pXsJQqC/+eWoLfZJDNrwSsSlg+oD+ZF91YccN9i9lJoaIPcVvPWDfEv7vL79LgnmPBeYxm/fWb4/ANMxvCLIP1R3Ixrz5oFoIX2NP1+uZOpoRWbg== + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + + + MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + Filip + Hanik + fhanik@pivotal.io + + + """; public static final String OIDC_ACCEPTANCE_URL = "https://oidc10.uaa-acceptance.cf-app.com/"; + private static final Base64.Encoder BASE_64_ENCODER = Base64.getEncoder(); + + + private static final DefaultResponseErrorHandler fiveHundredErrorHandler = new DefaultResponseErrorHandler() { + @Override + protected boolean hasError(HttpStatus statusCode) { + return statusCode.is5xxServerError(); + } + }; public static void updateUserToForcePasswordChange(RestTemplate restTemplate, String baseUrl, String adminToken, String userId) { updateUserToForcePasswordChange(restTemplate, baseUrl, adminToken, userId, null); @@ -181,13 +185,13 @@ public static ScimUser createUnapprovedUser(ServerRunning serverRunning) { user.setVerified(true); ResponseEntity result = restTemplate.postForEntity(serverRunning.getUrl("/Users"), user, ScimUser.class); - assertEquals(HttpStatus.CREATED, result.getStatusCode()); + Assertions.assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); return user; } public static boolean isMember(String userId, ScimGroup group) { - for (ScimGroupMember member : group.getMembers()) { + for (ScimGroupMember member : group.getMembers()) { if (userId.equals(member.getMemberId())) { return true; } @@ -195,7 +199,6 @@ public static boolean isMember(String userId, ScimGroup group) { return false; } - public static UserInfoResponse getUserInfo(String url, String token) throws URISyntaxException { RestTemplate rest = new RestTemplate(createRequestFactory(true, 60_000)); MultiValueMap headers = new LinkedMultiValueMap<>(); @@ -205,7 +208,7 @@ public static UserInfoResponse getUserInfo(String url, String token) throws URIS final ResponseEntity response = rest.exchange(request, UserInfoResponse.class); assertStatusCode(response, HttpStatus.OK); final UserInfoResponse responseBody = response.getBody(); - assertNotNull(responseBody); + assertThat(responseBody).isNotNull(); return responseBody; } @@ -242,37 +245,6 @@ public static boolean zoneExists(final String baseUrl, final String id, final St return true; } - public static class RegexMatcher extends TypeSafeMatcher { - - private final String regex; - - RegexMatcher(final String regex) { - this.regex = regex; - } - - @Override - public void describeTo(final Description description) { - description.appendText("matches regex=`" + regex + "`"); - } - - @Override - public boolean matchesSafely(final String string) { - return string.matches(regex); - } - - - public static RegexMatcher matchesRegex(final String regex) { - return new RegexMatcher(regex); - } - } - - private static final DefaultResponseErrorHandler fiveHundredErrorHandler = new DefaultResponseErrorHandler() { - @Override - protected boolean hasError(HttpStatus statusCode) { - return statusCode.is5xxServerError(); - } - }; - public static boolean doesSupportZoneDNS() { try { return Arrays.equals(Inet4Address.getByName("testzone1.localhost").getAddress(), new byte[]{127, 0, 0, 1}) && @@ -314,6 +286,7 @@ public boolean hasError(ClientHttpResponse response) { @Override public void handleError(ClientHttpResponse response) { + // ignore } }); return client; @@ -348,7 +321,7 @@ public static ScimUser createUserWithPhone(RestTemplate client, final ResponseEntity response = client.postForEntity(url + "/Users", user, ScimUser.class); assertStatusCode(response, HttpStatus.CREATED); final ScimUser responseBody = response.getBody(); - assertNotNull(responseBody); + assertThat(responseBody).isNotNull(); return responseBody; } @@ -362,7 +335,7 @@ public static ScimUser createUser(String token, String url, ScimUser user, Strin if (hasText(zoneSwitchId)) { headers.add(IdentityZoneSwitchingFilter.HEADER, zoneSwitchId); } - HttpEntity getHeaders = new HttpEntity<>(user, headers); + HttpEntity getHeaders = new HttpEntity<>(user, headers); ResponseEntity userInfoGet = template.exchange( url + "/Users", HttpMethod.POST, @@ -382,7 +355,7 @@ public static void updateUser(String token, String url, ScimUser user) { headers.add("Authorization", "bearer " + token); headers.add("Content-Type", APPLICATION_JSON_VALUE); headers.add("If-Match", String.valueOf(user.getVersion())); - HttpEntity getHeaders = new HttpEntity<>(user, headers); + HttpEntity getHeaders = new HttpEntity<>(user, headers); ResponseEntity userInfoGet = template.exchange( url + "/Users/" + user.getId(), HttpMethod.PUT, @@ -420,9 +393,9 @@ public static ScimUser getUserByZone(String token, String url, String subdomain, if (userInfoGet.getStatusCode() == HttpStatus.OK) { SearchResults results = JsonUtils.readValue(userInfoGet.getBody(), SearchResults.class); - assertNotNull(results); + assertThat(results).isNotNull(); List resources = results.getResources(); - if (resources.size() < 1) { + if (resources.isEmpty()) { return null; } user = JsonUtils.readValue(JsonUtils.writeValueAsString(resources.get(0)), ScimUser.class); @@ -471,9 +444,9 @@ public static String getUserIdByField(String token, String url, String origin, S if (userInfoGet.getStatusCode() == HttpStatus.OK) { HashMap results = JsonUtils.readValue(userInfoGet.getBody(), HashMap.class); - assertNotNull(results); + assertThat(results).isNotNull(); List resources = (List) results.get("resources"); - if (resources.size() < 1) { + if (resources.isEmpty()) { return null; } HashMap resource = (HashMap) resources.get(0); @@ -512,8 +485,8 @@ private static Map findAllGroups(RestTemplate client, @SuppressWarnings("rawtypes") Map results = response.getBody(); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertTrue("There should be more than zero groups", (Integer) results.get("totalResults") > 0); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat((Integer) results.get("totalResults")).as("There should be more than zero groups").isGreaterThan(0); return results; } @@ -522,8 +495,8 @@ public static String findGroupId(RestTemplate client, String groupName) { Map map = findAllGroups(client, url); for (Map group : (List) map.get("resources")) { - assertTrue(group.containsKey("displayName")); - assertTrue(group.containsKey("id")); + assertThat(group).containsKey("displayName") + .containsKey("id"); if (groupName.equals(group.get("displayName"))) { return (String) group.get("id"); } @@ -564,7 +537,7 @@ public static ScimGroup getGroup(String token, url + "/Groups?filter=displayName eq \"{groupId}\"", HttpMethod.GET, new HttpEntity<>(headers), - new ParameterizedTypeReference>() { + new ParameterizedTypeReference<>() { }, displayName ); @@ -599,7 +572,7 @@ public static ScimGroup createGroup( ); assertStatusCode(response, HttpStatus.CREATED); final ScimGroup responseBody = response.getBody(); - assertNotNull(responseBody); + assertThat(responseBody).isNotNull(); return responseBody; } @@ -623,7 +596,7 @@ private static ScimGroup updateGroup(String token, ScimGroup.class, group.getId() ); - assertEquals(HttpStatus.OK, updateGroup.getStatusCode()); + assertThat(updateGroup.getStatusCode()).isEqualTo(HttpStatus.OK); return updateGroup.getBody(); } @@ -698,7 +671,7 @@ private static IdentityZone createZoneOrUpdateSubdomain(RestTemplate client, if (zoneGet.getStatusCode() == HttpStatus.OK) { IdentityZone existing = JsonUtils.readValue(zoneGet.getBody(), IdentityZone.class); - assertNotNull(existing); + assertThat(existing).isNotNull(); existing.setSubdomain(subdomain); existing.setConfig(config); existing.setActive(active); @@ -717,7 +690,7 @@ private static IdentityZone createZoneOrUpdateSubdomain(RestTemplate client, ResponseEntity zone = client.postForEntity(url + "/identity-zones", identityZone, IdentityZone.class); assertStatusCode(zone, HttpStatus.CREATED); final IdentityZone responseBody = zone.getBody(); - assertNotNull(responseBody); + assertThat(responseBody).isNotNull(); return responseBody; } @@ -745,12 +718,12 @@ public static void addMemberToGroup(RestTemplate client, ) { ScimGroupMember groupMember = new ScimGroupMember(userId); ResponseEntity response = client.postForEntity(url + "/Groups/{groupId}/members", groupMember, String.class, groupId); - assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); } public static UaaClientDetails getClient(String token, - String url, - String clientId) { + String url, + String clientId) { RestTemplate template = new RestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); @@ -786,9 +759,9 @@ private static void assertStatusCode(final ResponseEntity response, final Htt } public static UaaClientDetails createClientAsZoneAdmin(String zoneAdminToken, - String url, - String zoneId, - UaaClientDetails client) { + String url, + String zoneId, + UaaClientDetails client) { RestTemplate template = new RestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); @@ -810,15 +783,15 @@ public static UaaClientDetails createClientAsZoneAdmin(String zoneAdminToken, } public static UaaClientDetails createClient(String adminToken, - String url, - UaaClientDetails client) { + String url, + UaaClientDetails client) { return createOrUpdateClient(adminToken, url, null, client); } public static UaaClientDetails createOrUpdateClient(String adminToken, - String url, - String switchToZoneId, - UaaClientDetails client) { + String url, + String switchToZoneId, + UaaClientDetails client) { RestTemplate template = new RestTemplate(); template.setErrorHandler(new DefaultResponseErrorHandler() { @@ -879,16 +852,16 @@ public static void updateClient(String url, UaaClientDetails.class ); assertStatusCode(response, HttpStatus.OK); - assertNotNull(response.getBody()); + assertThat(response.getBody()).isNotNull(); } - public static IdentityProvider getProvider(String zoneAdminToken, - String url, - String zoneId, - String originKey) { - List providers = getProviders(zoneAdminToken, url, zoneId); + public static IdentityProvider getProvider(String zoneAdminToken, + String url, + String zoneId, + String originKey) { + List> providers = getProviders(zoneAdminToken, url, zoneId); if (providers != null) { - for (IdentityProvider p : providers) { + for (IdentityProvider p : providers) { if (zoneId.equals(p.getIdentityZoneId()) && originKey.equals(p.getOriginKey())) { return p; } @@ -900,16 +873,16 @@ public static IdentityProvider getProvider(String zoneAdminToken, /** * @return the list of identity providers or {@code null} if the request was not successful */ - private static List getProviders(String zoneAdminToken, - String url, - String zoneId) { + private static List> getProviders(String zoneAdminToken, + String url, + String zoneId) { RestTemplate client = new RestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + zoneAdminToken); headers.add("Content-Type", APPLICATION_JSON_VALUE); headers.add(IdentityZoneSwitchingFilter.HEADER, zoneId); - HttpEntity getHeaders = new HttpEntity<>(headers); + HttpEntity getHeaders = new HttpEntity<>(headers); ResponseEntity providerGet = client.exchange( url + "/identity-providers", HttpMethod.GET, @@ -917,7 +890,7 @@ private static List getProviders(String zoneAdminToken, String.class ); if (providerGet != null && providerGet.getStatusCode() == HttpStatus.OK) { - return JsonUtils.readValue(providerGet.getBody(), new TypeReference>() { + return JsonUtils.readValue(providerGet.getBody(), new TypeReference>>() { }); } return null; @@ -927,12 +900,15 @@ public static void deleteProvider(String zoneAdminToken, String url, String zoneId, String originKey) { - IdentityProvider provider = getProvider(zoneAdminToken, url, zoneId, originKey); + IdentityProvider provider = getProvider(zoneAdminToken, url, zoneId, originKey); + if (provider == null) { + return; + } RestTemplate client = new RestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Authorization", "bearer " + zoneAdminToken); headers.add(IdentityZoneSwitchingFilter.HEADER, zoneId); - HttpEntity getHeaders = new HttpEntity<>(headers); + HttpEntity getHeaders = new HttpEntity<>(headers); final ResponseEntity response = client.exchange( url + "/identity-providers/" + provider.getId(), HttpMethod.DELETE, @@ -946,9 +922,8 @@ public static void deleteProvider(String zoneAdminToken, * @param originKey The unique identifier used to reference the identity provider in UAA. * @param addShadowUserOnLogin Specifies whether UAA should automatically create shadow users upon successful SAML authentication. * @return An object representation of an identity provider. - * @throws Exception on error */ - public static IdentityProvider createIdentityProvider(String originKey, boolean addShadowUserOnLogin, String baseUrl, ServerRunning serverRunning) throws Exception { + public static IdentityProvider createIdentityProvider(String originKey, boolean addShadowUserOnLogin, String baseUrl, ServerRunning serverRunning) { getZoneAdminToken(baseUrl, serverRunning); SamlIdentityProviderDefinition samlIdentityProviderDefinition = createSimplePHPSamlIDP(originKey, OriginKeys.UAA); return createIdentityProvider("simplesamlphp for uaa", addShadowUserOnLogin, baseUrl, serverRunning, samlIdentityProviderDefinition); @@ -957,14 +932,15 @@ public static IdentityProvider createIdentityProvider(String originKey, boolean /** * @param addShadowUserOnLogin Specifies whether UAA should automatically create shadow users upon successful SAML authentication. * @return An object representation of an identity provider. - * @throws Exception on error */ - public static IdentityProvider createIdentityProvider(String name, boolean addShadowUserOnLogin, String baseUrl, ServerRunning serverRunning, SamlIdentityProviderDefinition samlIdentityProviderDefinition) throws Exception { + public static IdentityProvider createIdentityProvider( + String name, boolean addShadowUserOnLogin, String baseUrl, ServerRunning serverRunning, + SamlIdentityProviderDefinition samlIdentityProviderDefinition) { String zoneAdminToken = getZoneAdminToken(baseUrl, serverRunning); samlIdentityProviderDefinition.setAddShadowUserOnLogin(addShadowUserOnLogin); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(OriginKeys.UAA); provider.setType(OriginKeys.SAML); provider.setActive(true); @@ -973,7 +949,7 @@ public static IdentityProvider createIdentityProvider(String name, boolean addSh provider.setName(name); provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); - assertNotNull(provider.getId()); + assertThat(provider.getId()).isNotNull(); return provider; } @@ -998,7 +974,7 @@ public static void createOidcIdentityProvider(String name, String originKey, Str IntegrationTestUtils.createOrUpdateProvider(clientCredentialsToken, baseUrl, identityProvider); } - public static String getZoneAdminToken(String baseUrl, ServerRunning serverRunning) throws Exception { + public static String getZoneAdminToken(String baseUrl, ServerRunning serverRunning) { return getZoneAdminToken(baseUrl, serverRunning, OriginKeys.UAA); } @@ -1012,7 +988,7 @@ public static String getZoneAdminToken(String baseUrl, ServerRunning serverRunni String groupName = "zones." + zoneId + ".admin"; ensureGroupExists(getClientCredentialsToken(baseUrl, "admin", "adminsecret"), "", baseUrl, groupName); String groupId = IntegrationTestUtils.findGroupId(adminClient, baseUrl, groupName); - assertThat("Couldn't find group : " + groupId, groupId, is(CoreMatchers.notNullValue())); + assertThat(groupId).as("Couldn't find group : " + groupId).isNotNull(); IntegrationTestUtils.addMemberToGroup(adminClient, baseUrl, user.getId(), groupId); return IntegrationTestUtils.getAccessTokenByAuthCode(serverRunning, @@ -1033,7 +1009,7 @@ public static ScimUser createRandomUser(String baseUrl) { } public static void updateIdentityProvider( - String baseUrl, ServerRunning serverRunning, IdentityProvider provider) { + String baseUrl, ServerRunning serverRunning, IdentityProvider provider) { RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") ); @@ -1052,7 +1028,7 @@ public static void updateIdentityProvider( "secr3T"); provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken, baseUrl, provider); - assertNotNull(provider.getId()); + assertThat(provider.getId()).isNotNull(); } public static SamlIdentityProviderDefinition createSimplePHPSamlIDP(String alias, String zoneId) { @@ -1074,18 +1050,20 @@ public static SamlIdentityProviderDefinition createSimplePHPSamlIDP(String alias return def; } - public static IdentityProvider createOrUpdateProvider(String accessToken, - String url, - IdentityProvider provider) { + public static IdentityProvider + createOrUpdateProvider(String accessToken, + String url, + IdentityProvider provider) { + RestTemplate client = new RestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + accessToken); headers.add("Content-Type", APPLICATION_JSON_VALUE); headers.add(IdentityZoneSwitchingFilter.HEADER, provider.getIdentityZoneId()); - List existing = getProviders(accessToken, url, provider.getIdentityZoneId()); + List> existing = getProviders(accessToken, url, provider.getIdentityZoneId()); if (existing != null) { - for (IdentityProvider p : existing) { + for (IdentityProvider p : existing) { if (p.getOriginKey().equals(provider.getOriginKey()) && p.getIdentityZoneId().equals(provider.getIdentityZoneId())) { provider.setId(p.getId()); HttpEntity putHeaders = new HttpEntity<>(provider, headers); @@ -1128,7 +1106,7 @@ public static String getClientCredentialsToken(String baseUrl, HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.set("Authorization", "Basic " + new String(Base64.encode(String.format("%s:%s", clientId, clientSecret).getBytes()))); + headers.set("Authorization", "Basic " + new String(BASE_64_ENCODER.encode("%s:%s".formatted(clientId, clientSecret).getBytes()))); @SuppressWarnings("rawtypes") ResponseEntity response = template.exchange( @@ -1137,10 +1115,11 @@ public static String getClientCredentialsToken(String baseUrl, new HttpEntity<>(formData, headers), Map.class); - Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); final Map responseBody = response.getBody(); - assertNotNull(responseBody); + assertThat(responseBody).isNotNull(); + @SuppressWarnings("unchecked") OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(responseBody); return accessToken.getValue(); } @@ -1166,7 +1145,7 @@ public static Map getPasswordToken(String baseUrl, HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.set("Authorization", "Basic " + new String(Base64.encode(String.format("%s:%s", clientId, clientSecret).getBytes()))); + headers.set("Authorization", "Basic " + new String(BASE_64_ENCODER.encode("%s:%s".formatted(clientId, clientSecret).getBytes()))); @SuppressWarnings("rawtypes") ResponseEntity response = template.exchange( @@ -1175,7 +1154,7 @@ public static Map getPasswordToken(String baseUrl, new HttpEntity<>(formData, headers), Map.class); - Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); return response.getBody(); } @@ -1188,14 +1167,15 @@ public static String getClientCredentialsToken(ServerRunning serverRunning, HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); headers.set("Authorization", - "Basic " + new String(Base64.encode(String.format("%s:%s", clientId, clientSecret).getBytes()))); + "Basic " + new String(BASE_64_ENCODER.encode("%s:%s".formatted(clientId, clientSecret).getBytes()))); @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap("/oauth/token", formData, headers); - Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); final Map responseBody = response.getBody(); - assertNotNull(responseBody); + assertThat(responseBody).isNotNull(); + @SuppressWarnings("unchecked") OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(responseBody); return accessToken.getValue(); } @@ -1222,7 +1202,6 @@ public static Map getAuthorizationCodeTokenMap(ServerRunning ser resource.setClientSecret(clientSecret); return getAuthorizationCodeTokenMap(serverRunning, - testAccounts, clientId, clientSecret, username, @@ -1246,169 +1225,163 @@ public static HttpHeaders getHeaders(CookieStore cookies) { } public static String getAuthorizationResponse(ServerRunning serverRunning, - String clientId, - String username, - String password, - String redirectUri, - String codeChallenge, - String codeChallengeMethod) throws Exception { - BasicCookieStore cookies = new BasicCookieStore(); - String mystateid = "mystateid"; - ServerRunning.UriBuilder builder = serverRunning.buildUri("/oauth/authorize") - .queryParam("response_type", "code") - .queryParam("state", mystateid) - .queryParam("client_id", clientId); - if (hasText(redirectUri)) { - builder = builder.queryParam("redirect_uri", redirectUri); - } - if (hasText(codeChallenge)) { - builder = builder.queryParam("code_challenge", codeChallenge); - } - if (hasText(codeChallengeMethod)) { - builder = builder.queryParam("code_challenge_method", codeChallengeMethod); - } - URI uri = builder.build(); - ResponseEntity result = - serverRunning.createRestTemplate().exchange( - uri.toString(), - HttpMethod.GET, - new HttpEntity<>(null, getHeaders(cookies)), - Void.class - ); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); - String location = result.getHeaders().getLocation().toString(); - if (result.getHeaders().containsKey("Set-Cookie")) { - for (String header : result.getHeaders().get("Set-Cookie")) { - int nameLength = header.indexOf('='); - cookies.addCookie(new BasicClientCookie(header.substring(0, nameLength), header.substring(nameLength + 1))); - } - } - ResponseEntity response = serverRunning.getForString(location, getHeaders(cookies)); - if (response.getHeaders().containsKey("Set-Cookie")) { - for (String cookie : response.getHeaders().get("Set-Cookie")) { - int nameLength = cookie.indexOf('='); - cookies.addCookie(new BasicClientCookie(cookie.substring(0, nameLength), cookie.substring(nameLength + 1))); - } - } - MultiValueMap formData = new LinkedMultiValueMap<>(); - assertTrue(response.getBody().contains("/login.do")); - assertTrue(response.getBody().contains("username")); - assertTrue(response.getBody().contains("password")); - String csrf = IntegrationTestUtils.extractCookieCsrf(response.getBody()); - formData.add("username", username); - formData.add("password", password); - formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf); - // Should be redirected to the original URL, but now authenticated - result = serverRunning.postForResponse("/login.do", getHeaders(cookies), formData); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); - cookies.clear(); - if (result.getHeaders().containsKey("Set-Cookie")) { - for (String cookie : result.getHeaders().get("Set-Cookie")) { - int nameLength = cookie.indexOf('='); - cookies.addCookie(new BasicClientCookie(cookie.substring(0, nameLength), cookie.substring(nameLength + 1))); - } - } - response = serverRunning.createRestTemplate().exchange( - result.getHeaders().getLocation().toString(), HttpMethod.GET, new HttpEntity<>(null, getHeaders(cookies)), - String.class); - if (response.getHeaders().containsKey("Set-Cookie")) { - for (String cookie : response.getHeaders().get("Set-Cookie")) { - int nameLength = cookie.indexOf('='); - cookies.addCookie(new BasicClientCookie(cookie.substring(0, nameLength), cookie.substring(nameLength + 1))); - } - } - if (response.getStatusCode() == HttpStatus.OK) { - // The grant access page should be returned - assertTrue(response.getBody().contains("

    Application Authorization

    ")); - formData.clear(); - formData.add(USER_OAUTH_APPROVAL, "true"); - formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); - result = serverRunning.postForResponse("/oauth/authorize", getHeaders(cookies), formData); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); - location = result.getHeaders().getLocation().toString(); - } else if(response.getStatusCode() == HttpStatus.BAD_REQUEST){ - return response.getBody(); - } else { - // Token cached so no need for second approval - assertEquals(HttpStatus.FOUND, response.getStatusCode()); - location = response.getHeaders().getLocation().toString(); - } - return location; + String clientId, + String username, + String password, + String redirectUri, + String codeChallenge, + String codeChallengeMethod) throws Exception { + BasicCookieStore cookies = new BasicCookieStore(); + String mystateid = "mystateid"; + ServerRunning.UriBuilder builder = serverRunning.buildUri("/oauth/authorize") + .queryParam("response_type", "code") + .queryParam("state", mystateid) + .queryParam("client_id", clientId); + if (hasText(redirectUri)) { + builder = builder.queryParam("redirect_uri", redirectUri); + } + if (hasText(codeChallenge)) { + builder = builder.queryParam("code_challenge", codeChallenge); + } + if (hasText(codeChallengeMethod)) { + builder = builder.queryParam("code_challenge_method", codeChallengeMethod); + } + URI uri = builder.build(); + ResponseEntity result = + serverRunning.createRestTemplate().exchange( + uri.toString(), + HttpMethod.GET, + new HttpEntity<>(null, getHeaders(cookies)), + Void.class + ); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FOUND); + String location = result.getHeaders().getLocation().toString(); + if (result.getHeaders().containsKey("Set-Cookie")) { + for (String header : result.getHeaders().get("Set-Cookie")) { + int nameLength = header.indexOf('='); + cookies.addCookie(new BasicClientCookie(header.substring(0, nameLength), header.substring(nameLength + 1))); + } + } + ResponseEntity response = serverRunning.getForString(location, getHeaders(cookies)); + if (response.getHeaders().containsKey("Set-Cookie")) { + for (String cookie : response.getHeaders().get("Set-Cookie")) { + int nameLength = cookie.indexOf('='); + cookies.addCookie(new BasicClientCookie(cookie.substring(0, nameLength), cookie.substring(nameLength + 1))); + } + } + MultiValueMap formData = new LinkedMultiValueMap<>(); + assertThat(response.getBody()).contains("/login.do") + .contains("username") + .contains("password"); + String csrf = IntegrationTestUtils.extractCookieCsrf(response.getBody()); + formData.add("username", username); + formData.add("password", password); + formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf); + // Should be redirected to the original URL, but now authenticated + result = serverRunning.postForResponse("/login.do", getHeaders(cookies), formData); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FOUND); + cookies.clear(); + if (result.getHeaders().containsKey("Set-Cookie")) { + for (String cookie : result.getHeaders().get("Set-Cookie")) { + int nameLength = cookie.indexOf('='); + cookies.addCookie(new BasicClientCookie(cookie.substring(0, nameLength), cookie.substring(nameLength + 1))); + } + } + response = serverRunning.createRestTemplate().exchange( + result.getHeaders().getLocation().toString(), HttpMethod.GET, new HttpEntity<>(null, getHeaders(cookies)), + String.class); + if (response.getHeaders().containsKey("Set-Cookie")) { + for (String cookie : response.getHeaders().get("Set-Cookie")) { + int nameLength = cookie.indexOf('='); + cookies.addCookie(new BasicClientCookie(cookie.substring(0, nameLength), cookie.substring(nameLength + 1))); + } + } + if (response.getStatusCode() == HttpStatus.OK) { + // The grant access page should be returned + assertThat(response.getBody()).contains("

    Application Authorization

    "); + formData.clear(); + formData.add(USER_OAUTH_APPROVAL, "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); + result = serverRunning.postForResponse("/oauth/authorize", getHeaders(cookies), formData); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FOUND); + location = result.getHeaders().getLocation().toString(); + } else if (response.getStatusCode() == HttpStatus.BAD_REQUEST) { + return response.getBody(); + } else { + // Token cached so no need for second approval + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND); + location = response.getHeaders().getLocation().toString(); + } + return location; } public static ResponseEntity getTokens(ServerRunning serverRunning, - UaaTestAccounts testAccounts, - String clientId, - String clientSecret, - String redirectUri, - String codeVerifier, - String authorizationCode) throws Exception { - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.clear(); - formData.add("client_id", clientId); - formData.add("grant_type", GRANT_TYPE_AUTHORIZATION_CODE); - formData.add("code", authorizationCode); - if (hasText(redirectUri)) { - formData.add("redirect_uri", redirectUri); - } - if (hasText(codeVerifier)) { - formData.add("code_verifier", codeVerifier); - } - HttpHeaders tokenHeaders = new HttpHeaders(); - tokenHeaders.set("Authorization", testAccounts.getAuthorizationHeader(clientId, clientSecret)); + String clientId, + String clientSecret, + String redirectUri, + String codeVerifier, + String authorizationCode) throws Exception { + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.clear(); + formData.add("client_id", clientId); + formData.add("grant_type", GRANT_TYPE_AUTHORIZATION_CODE); + formData.add("code", authorizationCode); + if (hasText(redirectUri)) { + formData.add("redirect_uri", redirectUri); + } + if (hasText(codeVerifier)) { + formData.add("code_verifier", codeVerifier); + } + HttpHeaders tokenHeaders = new HttpHeaders(); + tokenHeaders.set("Authorization", UaaTestAccounts.getAuthorizationHeader(clientId, clientSecret)); return serverRunning.postForMap("/oauth/token", formData, tokenHeaders); - } + } public static void callCheckToken(ServerRunning serverRunning, - UaaTestAccounts testAccounts, - String accessToken, - String clientId, - String clientSecret) { - MultiValueMap formData = new LinkedMultiValueMap<>(); + String accessToken, + String clientId, + String clientSecret) { + MultiValueMap formData = new LinkedMultiValueMap<>(); HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", testAccounts.getAuthorizationHeader(clientId, clientSecret)); + headers.set("Authorization", UaaTestAccounts.getAuthorizationHeader(clientId, clientSecret)); formData.add("token", accessToken); ResponseEntity tokenResponse = serverRunning.postForMap("/check_token", formData, headers); - assertEquals(HttpStatus.OK, tokenResponse.getStatusCode()); + assertThat(tokenResponse.getStatusCode()).isEqualTo(HttpStatus.OK); final Map tokenResponseBody = tokenResponse.getBody(); - assertNotNull(tokenResponseBody); - assertNotNull(tokenResponseBody.get("iss")); + assertThat(tokenResponseBody).isNotNull() + .containsKey("iss"); } public static String getAuthorizationCodeToken( - ServerRunning serverRunning, - String clientId, - String clientAssertion, - String username, - String password, - String tokenResponseType, - String redirectUri, - String loginHint, - boolean callCheckToken) - { - return getAuthorizationCodeTokenMap(serverRunning, UaaTestAccounts.standard(serverRunning), clientId, null, clientAssertion, - username, password, tokenResponseType, null, redirectUri, loginHint, callCheckToken).get("access_token"); + ServerRunning serverRunning, + String clientId, + String clientAssertion, + String username, + String password, + String tokenResponseType, + String redirectUri, + String loginHint, + boolean callCheckToken) { + return getAuthorizationCodeTokenMap(serverRunning, clientId, null, clientAssertion, + username, password, tokenResponseType, null, redirectUri, loginHint, callCheckToken).get("access_token"); } public static Map getAuthorizationCodeTokenMap( - ServerRunning serverRunning, - UaaTestAccounts testAccounts, - String clientId, - String clientSecret, - String username, - String password, - String tokenResponseType, - String jSessionId, - String redirectUri, - String loginHint, - boolean callCheckToken) - { - return getAuthorizationCodeTokenMap(serverRunning, testAccounts, clientId, clientSecret, null, username, password, - tokenResponseType, jSessionId, redirectUri, loginHint, callCheckToken); + ServerRunning serverRunning, + String clientId, + String clientSecret, + String username, + String password, + String tokenResponseType, + String jSessionId, + String redirectUri, + String loginHint, + boolean callCheckToken) { + return getAuthorizationCodeTokenMap(serverRunning, clientId, clientSecret, null, username, password, + tokenResponseType, jSessionId, redirectUri, loginHint, callCheckToken); } public static Map getAuthorizationCodeTokenMap(ServerRunning serverRunning, - UaaTestAccounts testAccounts, String clientId, String clientSecret, String clientAssertion, @@ -1445,7 +1418,7 @@ public static Map getAuthorizationCodeTokenMap(ServerRunning ser Void.class ); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FOUND); String location = result.getHeaders().getLocation().toString(); if (result.getHeaders().containsKey("Set-Cookie")) { @@ -1467,9 +1440,9 @@ public static Map getAuthorizationCodeTokenMap(ServerRunning ser MultiValueMap formData = new LinkedMultiValueMap<>(); if (!hasText(jSessionId)) { // should be directed to the login screen... - assertTrue(response.getBody().contains("/login.do")); - assertTrue(response.getBody().contains("username")); - assertTrue(response.getBody().contains("password")); + assertThat(response.getBody()).contains("/login.do") + .contains("username") + .contains("password"); String csrf = IntegrationTestUtils.extractCookieCsrf(response.getBody()); formData.add("username", username); @@ -1478,7 +1451,7 @@ public static Map getAuthorizationCodeTokenMap(ServerRunning ser // Should be redirected to the original URL, but now authenticated result = serverRunning.postForResponse("/login.do", getHeaders(cookies), formData); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FOUND); cookies.clear(); if (result.getHeaders().containsKey("Set-Cookie")) { @@ -1501,21 +1474,21 @@ public static Map getAuthorizationCodeTokenMap(ServerRunning ser } if (response.getStatusCode() == HttpStatus.OK) { // The grant access page should be returned - assertTrue(response.getBody().contains("

    Application Authorization

    ")); + assertThat(response.getBody()).contains("

    Application Authorization

    "); formData.clear(); formData.add(USER_OAUTH_APPROVAL, "true"); formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", getHeaders(cookies), formData); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FOUND); location = result.getHeaders().getLocation().toString(); } else { // Token cached so no need for second approval - assertEquals(HttpStatus.FOUND, response.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND); location = response.getHeaders().getLocation().toString(); } if (hasText(redirectUri)) { - assertTrue("Wrong location: " + location, location.matches(redirectUri + ".*code=.+")); + assertThat(location).as("Wrong location: " + location).matches(redirectUri + ".*code=.+"); } formData.clear(); @@ -1530,14 +1503,14 @@ public static Map getAuthorizationCodeTokenMap(ServerRunning ser formData.add("code", location.split("code=")[1].split("&")[0]); HttpHeaders tokenHeaders = new HttpHeaders(); if (clientSecret != null) { - tokenHeaders.set("Authorization", testAccounts.getAuthorizationHeader(clientId, clientSecret)); + tokenHeaders.set("Authorization", UaaTestAccounts.getAuthorizationHeader(clientId, clientSecret)); } else if (clientAssertion != null) { formData.add(JwtClientAuthentication.CLIENT_ASSERTION_TYPE, JwtClientAuthentication.GRANT_TYPE); formData.add(JwtClientAuthentication.CLIENT_ASSERTION, clientAssertion); } @SuppressWarnings("rawtypes") ResponseEntity tokenResponse = serverRunning.postForMap("/oauth/token", formData, tokenHeaders); - assertEquals(HttpStatus.OK, tokenResponse.getStatusCode()); + assertThat(tokenResponse.getStatusCode()).isEqualTo(HttpStatus.OK); @SuppressWarnings("unchecked") OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(tokenResponse.getBody()); @@ -1546,7 +1519,7 @@ public static Map getAuthorizationCodeTokenMap(ServerRunning ser formData = new LinkedMultiValueMap<>(); HttpHeaders headers = new HttpHeaders(); if (clientSecret != null) { - headers.set("Authorization", testAccounts.getAuthorizationHeader(clientId, clientSecret)); + headers.set("Authorization", UaaTestAccounts.getAuthorizationHeader(clientId, clientSecret)); } else if (clientAssertion != null) { formData.add(JwtClientAuthentication.CLIENT_ASSERTION_TYPE, JwtClientAuthentication.GRANT_TYPE); formData.add(JwtClientAuthentication.CLIENT_ASSERTION, clientAssertion); @@ -1555,9 +1528,8 @@ public static Map getAuthorizationCodeTokenMap(ServerRunning ser if (callCheckToken) { tokenResponse = serverRunning.postForMap("/check_token", formData, headers); - assertEquals(HttpStatus.OK, tokenResponse.getStatusCode()); - //System.err.println(tokenResponse.getBody()); - assertNotNull(tokenResponse.getBody().get("iss")); + assertThat(tokenResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(tokenResponse.getBody().get("iss")).isNotNull(); } return body; } @@ -1591,43 +1563,20 @@ public static void takeScreenShot(String prefix, WebDriver webDriver) { public static void validateAccountChooserCookie(String baseUrl, WebDriver webDriver, IdentityZone identityZone) { if (identityZone.getConfig().isAccountChooserEnabled()) { List cookies = getAccountChooserCookies(baseUrl, webDriver); - assertThat(cookies, Matchers.hasItem(startsWith("Saved-Account-"))); + assertThat(cookies).anySatisfy(cookie -> assertThat(cookie).startsWith("Saved-Account-")); } } public static void validateUserLastLogon(ScimUser user, Long beforeTestTime, Long afterTestTime) { Long userLastLogon = user.getLastLogonTime(); - assertNotNull(userLastLogon); - assertTrue((userLastLogon > beforeTestTime) && (userLastLogon < afterTestTime)); + assertThat(userLastLogon).isNotNull(); + assertThat((userLastLogon > beforeTestTime) && (userLastLogon < afterTestTime)).isTrue(); } public static List getAccountChooserCookies(String baseUrl, WebDriver webDriver) { webDriver.get(baseUrl + "/logout.do"); webDriver.get(baseUrl + "/login"); - return webDriver.manage().getCookies().stream().map(Cookie::getName).collect(Collectors.toList()); - } - - public static class HttpRequestFactory extends HttpComponentsClientHttpRequestFactory { - private final boolean disableRedirect; - private final boolean disableCookieHandling; - - HttpRequestFactory(boolean disableCookieHandling, boolean disableRedirect) { - this.disableCookieHandling = disableCookieHandling; - this.disableRedirect = disableRedirect; - } - - @Override - public HttpClient getHttpClient() { - HttpClientBuilder builder = HttpClientBuilder.create() - .useSystemProperties(); - if (disableRedirect) { - builder = builder.disableRedirectHandling(); - } - if (disableCookieHandling) { - builder = builder.disableCookieManagement(); - } - return builder.build(); - } + return webDriver.manage().getCookies().stream().map(Cookie::getName).toList(); } public static String createAnotherUser(WebDriver webDriver, String password, SimpleSmtpServer simpleSmtpServer, String url, TestClient testClient) { @@ -1647,13 +1596,6 @@ public static String createAnotherUser(WebDriver webDriver, String password, Sim return userEmail; } - - public static class StatelessRequestFactory extends HttpRequestFactory { - public StatelessRequestFactory() { - super(true, true); - } - } - public static HttpHeaders getAuthenticatedHeaders(String token) { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); @@ -1664,14 +1606,66 @@ public static HttpHeaders getAuthenticatedHeaders(String token) { public static String createClientAdminTokenInZone(String baseUrl, String uaaAdminToken, String zoneId, IdentityZoneConfiguration config) { RestTemplate identityClient = getClientCredentialsTemplate(getClientCredentialsResource(baseUrl, - new String[] { "zones.write", "zones.read", "scim.zones" }, "identity", "identitysecret")); + new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret")); createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId, config); String zoneUrl = baseUrl.replace("localhost", zoneId + ".localhost"); UaaClientDetails zoneClient = new UaaClientDetails("admin-client-in-zone", null, "openid", - "authorization_code,client_credentials", "uaa.admin,scim.read,scim.write,zones.testzone1.admin ", zoneUrl); + "authorization_code,client_credentials", "uaa.admin,scim.read,scim.write,zones.testzone1.admin ", zoneUrl); zoneClient.setClientSecret("admin-secret-in-zone"); createOrUpdateClient(uaaAdminToken, baseUrl, zoneId, zoneClient); return getClientCredentialsToken(zoneUrl, "admin-client-in-zone", "admin-secret-in-zone"); } -} + public static class RegexMatcher extends TypeSafeMatcher { + + private final String regex; + + RegexMatcher(final String regex) { + this.regex = regex; + } + + public static RegexMatcher matchesRegex(final String regex) { + return new RegexMatcher(regex); + } + + @Override + public void describeTo(final Description description) { + description.appendText("matches regex=`" + regex + "`"); + } + + @Override + public boolean matchesSafely(final String string) { + return string.matches(regex); + } + } + + public static class HttpRequestFactory extends HttpComponentsClientHttpRequestFactory { + private final boolean disableRedirect; + private final boolean disableCookieHandling; + + HttpRequestFactory(boolean disableCookieHandling, boolean disableRedirect) { + this.disableCookieHandling = disableCookieHandling; + this.disableRedirect = disableRedirect; + } + + @Override + public HttpClient getHttpClient() { + HttpClientBuilder builder = HttpClientBuilder.create() + .useSystemProperties(); + if (disableRedirect) { + builder = builder.disableRedirectHandling(); + } + if (disableCookieHandling) { + builder = builder.disableCookieManagement(); + } + return builder.build(); + } + } + + public static class StatelessRequestFactory extends HttpRequestFactory { + public StatelessRequestFactory() { + super(true, true); + } + } + +} \ No newline at end of file diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java index 15f13d05db6..e021816c23e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java @@ -2,9 +2,9 @@ import org.apache.commons.lang3.RandomStringUtils; import org.cloudfoundry.identity.uaa.DefaultTestContext; -import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.account.EmailAccountCreationService; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.test.MockMvcTestClient; @@ -66,18 +66,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; +import static org.springframework.util.StringUtils.hasLength; import static org.springframework.util.StringUtils.hasText; -import static org.springframework.util.StringUtils.isEmpty; @DefaultTestContext class AccountsControllerMockMvcTests { - private final String LOGIN_REDIRECT = "/login?success=verify_success"; - private final String USER_PASSWORD = "secr3T"; + private static final String LOGIN_REDIRECT = "/login?success=verify_success"; + private static final String USER_PASSWORD = "secr3T"; + private final AlphanumericRandomValueStringGenerator generator = new AlphanumericRandomValueStringGenerator(); private String userEmail; private MockMvcTestClient mockMvcTestClient; - private AlphanumericRandomValueStringGenerator generator = new AlphanumericRandomValueStringGenerator(); - @Autowired private WebApplicationContext webApplicationContext; @Autowired @@ -128,7 +127,7 @@ void testCreateActivationEmailPageWithinZone() throws Exception { MockMvcUtils.createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, IdentityZoneHolder.getCurrentZoneId()); mockMvc.perform(get("/create_account") - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) .andExpect(content().string(containsString("Create your account"))); } @@ -146,7 +145,7 @@ void testActivationEmailSentPageWithinZone() throws Exception { MockMvcUtils.createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, IdentityZoneHolder.getCurrentZoneId()); mockMvc.perform(get("/accounts/email_sent") - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) .andExpect(status().isOk()) .andExpect(content().string(containsString("Create your account"))) .andExpect(xpath("//input[@disabled='disabled']/@value").string("Email successfully sent")) @@ -165,7 +164,7 @@ void testPageTitleWithinZone() throws Exception { IdentityZone zone = MockMvcUtils.createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, IdentityZoneHolder.getCurrentZoneId()); mockMvc.perform(get("/create_account") - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) .andExpect(content().string(containsString("" + zone.getName() + ""))); } @@ -178,7 +177,7 @@ void testCreateAccountWithDisableSelfService() throws Exception { MockMvcUtils.createOtherIdentityZoneAndReturnResult(mockMvc, webApplicationContext, getUaaBaseClientDetails(), zone, IdentityZoneHolder.getCurrentZoneId()); mockMvc.perform(get("/create_account") - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) .andExpect(model().attribute("error_message_code", "self_service_disabled")) .andExpect(view().name("error")) .andExpect(status().isNotFound()); @@ -193,11 +192,11 @@ void testDisableSelfServiceCreateAccountPost() throws Exception { MockMvcUtils.createOtherIdentityZoneAndReturnResult(mockMvc, webApplicationContext, getUaaBaseClientDetails(), zone, IdentityZoneHolder.getCurrentZoneId()); mockMvc.perform(post("/create_account.do") - .with(cookieCsrf()) - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) - .param("email", userEmail) - .param("password", "secr3T") - .param("password_confirmation", "secr3T")) + .with(cookieCsrf()) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) + .param("email", userEmail) + .param("password", "secr3T") + .param("password_confirmation", "secr3T")) .andExpect(model().attribute("error_message_code", "self_service_disabled")) .andExpect(view().name("error")) .andExpect(status().isNotFound()); @@ -215,7 +214,7 @@ void zoneLogoNull_doNotDisplayImage() throws Exception { MockMvcUtils.createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, IdentityZoneHolder.getCurrentZoneId()); mockMvc.perform(get("/create_account") - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) .andExpect(content().string(not(containsString("background-image: url(/resources/oss/images/product-logo.png);")))); } @@ -226,10 +225,10 @@ void testCreatingAnAccount() throws Exception { store.setGenerator(generator); mockMvc.perform(post("/create_account.do") - .with(cookieCsrf()) - .param("email", userEmail) - .param("password", "secr3T") - .param("password_confirmation", "secr3T")) + .with(cookieCsrf()) + .param("email", userEmail) + .param("password", "secr3T") + .param("password_confirmation", "secr3T")) .andExpect(status().isFound()) .andExpect(redirectedUrl("accounts/email_sent")); @@ -238,7 +237,7 @@ void testCreatingAnAccount() throws Exception { assertFalse(scimUser.isVerified()); mockMvc.perform(get("/verify_user") - .param("code", "test" + generator.counter.get())) + .param("code", "test" + generator.counter.get())) .andExpect(status().isFound()) .andExpect(redirectedUrl(LOGIN_REDIRECT)) .andReturn(); @@ -262,16 +261,16 @@ void testCreatingAnAccountWithAnEmptyClientId() throws Exception { store.setGenerator(generator); mockMvc.perform(post("/create_account.do") - .with(cookieCsrf()) - .param("email", userEmail) - .param("password", "secr3T") - .param("password_confirmation", "secr3T") - .param("client_id", "")) + .with(cookieCsrf()) + .param("email", userEmail) + .param("password", "secr3T") + .param("password_confirmation", "secr3T") + .param("client_id", "")) .andExpect(status().isFound()) .andExpect(redirectedUrl("accounts/email_sent")); mockMvc.perform(get("/verify_user") - .param("code", "test" + generator.counter.get())) + .param("code", "test" + generator.counter.get())) .andExpect(status().isFound()) .andExpect(redirectedUrl(LOGIN_REDIRECT)) .andReturn(); @@ -305,10 +304,10 @@ void testCreatingAnAccountWithNoClientRedirect() throws Exception { store.setGenerator(generator); mockMvc.perform(post("/create_account.do") - .with(cookieCsrf()) - .param("email", userEmail) - .param("password", "secr3T") - .param("password_confirmation", "secr3T")) + .with(cookieCsrf()) + .param("email", userEmail) + .param("password", "secr3T") + .param("password_confirmation", "secr3T")) .andExpect(status().isFound()) .andExpect(redirectedUrl("accounts/email_sent")); @@ -317,7 +316,7 @@ void testCreatingAnAccountWithNoClientRedirect() throws Exception { assertThat(message.getMessage().getHeader("From"), hasItemInArray("Cloud Foundry ")); mockMvc.perform(get("/verify_user") - .param("code", "test" + generator.counter.get())) + .param("code", "test" + generator.counter.get())) .andExpect(status().isFound()) .andExpect(redirectedUrl(LOGIN_REDIRECT)) .andReturn(); @@ -348,17 +347,17 @@ void testCreatingAnAccountInAnotherZoneWithNoClientRedirect() throws Exception { String zonesCreateToken = mockMvcTestClient.getOAuthAccessToken("identity", "identitysecret", "client_credentials", "zones.write"); mockMvc.perform(post("/identity-zones") - .header("Authorization", "Bearer " + zonesCreateToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) + .header("Authorization", "Bearer " + zonesCreateToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) .andExpect(status().isCreated()); mockMvc.perform(post("/create_account.do") - .with(cookieCsrf()) - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) - .param("email", userEmail) - .param("password", USER_PASSWORD) - .param("password_confirmation", USER_PASSWORD)) + .with(cookieCsrf()) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) + .param("email", userEmail) + .param("password", USER_PASSWORD) + .param("password_confirmation", USER_PASSWORD)) .andExpect(status().isFound()) .andExpect(redirectedUrl("accounts/email_sent")); @@ -368,12 +367,12 @@ void testCreatingAnAccountInAnotherZoneWithNoClientRedirect() throws Exception { assertThat(message.getMessage().getHeader("From"), hasItemInArray(subdomain + "zone ")); assertFalse(message.getContentString().contains("Cloud Foundry")); assertFalse(message.getContentString().contains("Pivotal")); - assertFalse(isEmpty(link)); + assertTrue(hasLength(link)); assertTrue(link.contains(subdomain + ".localhost")); mockMvc.perform(get("/verify_user") - .param("code", "test" + generator.counter.get()) - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) + .param("code", "test" + generator.counter.get()) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) .andExpect(status().isFound()) .andExpect(redirectedUrl(LOGIN_REDIRECT)) .andReturn(); @@ -406,24 +405,24 @@ void testCreatingAnAccountInAnotherZoneWithClientRedirect() throws Exception { MockMvcUtils.createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, getUaaBaseClientDetails(), IdentityZoneHolder.getCurrentZoneId()); mockMvc.perform(post("/create_account.do") - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) - .with(cookieCsrf()) - .param("email", userEmail) - .param("password", "secr3T") - .param("password_confirmation", "secr3T") - .param("client_id", "myzoneclient") - .param("redirect_uri", "http://myzoneclient.example.com")) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) + .with(cookieCsrf()) + .param("email", userEmail) + .param("password", "secr3T") + .param("password_confirmation", "secr3T") + .param("client_id", "myzoneclient") + .param("redirect_uri", "http://myzoneclient.example.com")) .andExpect(status().isFound()) .andExpect(redirectedUrl("accounts/email_sent")); FakeJavaMailSender.MimeMessageWrapper message = fakeJavaMailSender.getSentMessages().get(0); String link = mockMvcTestClient.extractLink(message.getContentString()); - assertFalse(isEmpty(link)); + assertTrue(hasLength(link)); assertTrue(link.contains(subdomain + ".localhost")); mockMvc.perform(get("/verify_user") - .param("code", "test" + generator.counter.get()) - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) + .param("code", "test" + generator.counter.get()) + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))) .andExpect(redirectedUrl(LOGIN_REDIRECT + "&form_redirect_uri=http://myzoneclient.example.com")) .andReturn(); @@ -457,16 +456,16 @@ void redirectToSavedRequest_ifPresent() throws Exception { store.setGenerator(generator); mockMvc.perform(post("/create_account.do") - .with(cookieCsrf()) - .session(session) - .param("email", "testuser@test.org") - .param("password", "test-password") - .param("password_confirmation", "test-password")) + .with(cookieCsrf()) + .session(session) + .param("email", "testuser@test.org") + .param("password", "test-password") + .param("password_confirmation", "test-password")) .andExpect(redirectedUrl("accounts/email_sent")); mockMvc.perform(get("/verify_user") - .session(session) - .param("code", "test" + generator.counter.get())) + .session(session) + .param("code", "test" + generator.counter.get())) .andExpect(status().isFound()) .andExpect(redirectedUrl(LOGIN_REDIRECT)) .andReturn(); @@ -477,7 +476,7 @@ void redirectToSavedRequest_ifPresent() throws Exception { @Test void ifInvalidOrExpiredCode_goTo_createAccountDefaultPage() throws Exception { mockMvc.perform(get("/verify_user") - .param("code", "expired-code")) + .param("code", "expired-code")) .andExpect(status().isUnprocessableEntity()) .andExpect(model().attribute("error_message_code", "code_expired")) .andExpect(view().name("accounts/link_prompt")) @@ -491,7 +490,7 @@ void ifInvalidOrExpiredCode_withNonDefaultSignupLinkProperty_goToNonDefaultSignu setProperty("links.signup", signUpLink); mockMvc.perform(get("/verify_user") - .param("code", "expired-code")) + .param("code", "expired-code")) .andExpect(status().isUnprocessableEntity()) .andExpect(model().attribute("error_message_code", "code_expired")) .andExpect(view().name("accounts/link_prompt")) @@ -513,7 +512,7 @@ void testConsentIfConfigured_displaysConsentTextAndLink() throws Exception { MockMvcUtils.updateZone(mockMvc, zone); mockMvc.perform(get("/create_account") - .with(new SetServerNameRequestPostProcessor(randomZoneSubdomain + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(randomZoneSubdomain + ".localhost"))) .andExpect(content().string(containsString(consentText))) .andExpect(content().string(containsString(consentLink))); } @@ -531,7 +530,7 @@ void testConsentIfConfigured_displayConsentTextWhenNoLinkConfigured() throws Exc MockMvcUtils.updateZone(mockMvc, zone); mockMvc.perform(get("/create_account") - .with(new SetServerNameRequestPostProcessor(randomZoneSubdomain + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(randomZoneSubdomain + ".localhost"))) .andExpect(content().string(containsString(consentText))); } @@ -548,12 +547,12 @@ void testConsentIfConfigured_displaysMeaningfulErrorWhenConsentNotProvided() thr MockMvcUtils.updateZone(mockMvc, zone); mockMvc.perform(post("/create_account.do") - .with(new SetServerNameRequestPostProcessor(randomZoneSubdomain + ".localhost")) - .with(cookieCsrf()) - .param("email", userEmail) - .param("password", USER_PASSWORD) - .param("password_confirmation", USER_PASSWORD) - .param("does_user_consent", "false")) + .with(new SetServerNameRequestPostProcessor(randomZoneSubdomain + ".localhost")) + .with(cookieCsrf()) + .param("email", userEmail) + .param("password", USER_PASSWORD) + .param("password_confirmation", USER_PASSWORD) + .param("does_user_consent", "false")) .andExpect(content().string(containsString("Please agree before continuing."))); } @@ -580,12 +579,12 @@ private void createAccount(String expectedRedirectUri, String redirectUri) throw mockMvc.perform(post("/create_account.do") - .with(cookieCsrf()) - .param("email", userEmail) - .param("password", USER_PASSWORD) - .param("password_confirmation", USER_PASSWORD) - .param("client_id", clientDetails.getClientId()) - .param("redirect_uri", redirectUri)) + .with(cookieCsrf()) + .param("email", userEmail) + .param("password", USER_PASSWORD) + .param("password_confirmation", USER_PASSWORD) + .param("client_id", clientDetails.getClientId()) + .param("redirect_uri", redirectUri)) .andExpect(status().isFound()) .andExpect(redirectedUrl("accounts/email_sent")); @@ -594,7 +593,7 @@ private void createAccount(String expectedRedirectUri, String redirectUri) throw assertThat(message.getMessage().getHeader("From"), hasItemInArray("Cloud Foundry ")); mockMvc.perform(get("/verify_user") - .param("code", "test" + generator.counter.get())) + .param("code", "test" + generator.counter.get())) .andExpect(status().isFound()) .andExpect(redirectedUrl(LOGIN_REDIRECT + "&form_redirect_uri=" + expectedRedirectUri)) .andReturn(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index d2468ae228b..6e2849a1b9d 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -1,101 +1,68 @@ package org.cloudfoundry.identity.uaa.login; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.Condition; import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; import org.cloudfoundry.identity.uaa.extensions.SpringProfileCleanupExtension; +import org.cloudfoundry.identity.uaa.extensions.SystemPropertiesCleanupExtension; import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; import org.cloudfoundry.identity.uaa.impl.config.YamlServletProfileInitializer; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.saml.BootstrapSamlIdentityProviderData; -import org.cloudfoundry.identity.uaa.provider.saml.SamlConfigurationBean; +import org.cloudfoundry.identity.uaa.provider.saml.SignatureAlgorithm; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; -import org.cloudfoundry.identity.uaa.util.PredicateMatcher; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.ResourceEntityResolver; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockRequestDispatcher; import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletContext; -import org.springframework.security.saml.log.SAMLDefaultLogger; +import org.springframework.util.FileCopyUtils; import org.springframework.util.StringUtils; import org.springframework.web.context.support.AbstractRefreshableWebApplicationContext; import org.springframework.web.servlet.ViewResolver; import javax.servlet.RequestDispatcher; -import java.io.File; -import java.util.Arrays; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; import java.util.EventListener; import java.util.List; -import java.util.Scanner; -import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; -class SystemPropertiesCleanupExtension implements BeforeAllCallback, AfterAllCallback { - - private final Set properties; - - SystemPropertiesCleanupExtension(String... props) { - this.properties = Arrays.stream(props).collect(Collectors.toUnmodifiableSet()); - } - - @Override - public void beforeAll(ExtensionContext context) { - ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create(context.getRequiredTestClass())); - - properties.forEach(s -> store.put(s, System.getProperty(s))); - } - - @Override - public void afterAll(ExtensionContext context) { - ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create(context.getRequiredTestClass())); - - properties.forEach(key -> { - String value = store.get(key, String.class); - if (value == null) { - System.clearProperty(key); - } else { - System.setProperty(key, value); - } - } - ); - } -} - @ExtendWith(PollutionPreventionExtension.class) @ExtendWith(SpringProfileCleanupExtension.class) class BootstrapTests { - private static final String LOGIN_IDP_METADATA = "login.idpMetadata"; private static final String LOGIN_IDP_ENTITY_ALIAS = "login.idpEntityAlias"; private static final String LOGIN_IDP_METADATA_URL = "login.idpMetadataURL"; private static final String LOGIN_SAML_METADATA_TRUST_CHECK = "login.saml.metadataTrustCheck"; + @RegisterExtension static final SystemPropertiesCleanupExtension systemPropertiesCleanupExtension = new SystemPropertiesCleanupExtension( LOGIN_IDP_METADATA, @@ -103,132 +70,60 @@ class BootstrapTests { LOGIN_IDP_METADATA_URL, LOGIN_SAML_METADATA_TRUST_CHECK); - private ConfigurableApplicationContext context; - - @Test - void xlegacyTestDeprecatedProperties() { - context = getServletContext(null, "test/bootstrap/deprecated_properties_still_work.yml"); - ScimGroupProvisioning scimGroupProvisioning = context.getBean("scimGroupProvisioning", ScimGroupProvisioning.class); - List scimGroups = scimGroupProvisioning.retrieveAll(IdentityZoneHolder.get().getId()); - assertThat(scimGroups, PredicateMatcher.has(g -> g.getDisplayName().equals("pony") && "The magic of friendship".equals(g.getDescription()))); - assertThat(scimGroups, PredicateMatcher.has(g -> g.getDisplayName().equals("cat") && "The cat".equals(g.getDescription()))); - IdentityZoneConfigurationBootstrap zoneBootstrap = context.getBean(IdentityZoneConfigurationBootstrap.class); - assertEquals("https://deprecated.home_redirect.com", zoneBootstrap.getHomeRedirect()); - IdentityZone defaultZone = context.getBean(IdentityZoneProvisioning.class).retrieve("uaa"); - IdentityZoneConfiguration defaultConfig = defaultZone.getConfig(); - assertTrue(defaultConfig.getSamlConfig().getKeys().containsKey(SamlConfig.LEGACY_KEY_ID), "Legacy SAML keys should be available"); - assertEquals(SamlLoginServerKeyManagerTests.CERTIFICATE.trim(), defaultConfig.getSamlConfig().getCertificate().trim()); - assertEquals(SamlLoginServerKeyManagerTests.KEY.trim(), defaultConfig.getSamlConfig().getPrivateKey().trim()); - assertEquals(SamlLoginServerKeyManagerTests.PASSWORD.trim(), defaultConfig.getSamlConfig().getPrivateKeyPassword().trim()); - } + private final static MockServletContext mockServletContext = new MockServletContext() { + @Override + @NonNull + public RequestDispatcher getNamedDispatcher(@Nullable String path) { + return new MockRequestDispatcher("/"); + } - @Test - void legacySamlIdpAsTopLevelElement() { - System.setProperty(LOGIN_SAML_METADATA_TRUST_CHECK, "false"); - System.setProperty(LOGIN_IDP_METADATA_URL, "http://simplesamlphp.uaa.com/saml2/idp/metadata.php"); - System.setProperty(LOGIN_IDP_ENTITY_ALIAS, "testIDPFile"); + @Override + @NonNull + public String getVirtualServerName() { + return "localhost"; + } - context = getServletContext("default", "uaa.yml"); - assertNotNull(context.getBean("viewResolver", ViewResolver.class)); - assertNotNull(context.getBean("samlLogger", SAMLDefaultLogger.class)); - assertFalse(context.getBean(BootstrapSamlIdentityProviderData.class).isLegacyMetadataTrustCheck()); - List defs = context.getBean(BootstrapSamlIdentityProviderData.class).getIdentityProviderDefinitions(); - assertNotNull(findProvider(defs, "testIDPFile")); - assertEquals( - SamlIdentityProviderDefinition.MetadataLocation.URL, - findProvider(defs, "testIDPFile").getType()); - assertEquals( - SamlIdentityProviderDefinition.MetadataLocation.URL, - defs.get(defs.size() - 1).getType() - ); - } + @Override + public void addListener(@Nullable Type t) { + //no op + } + }; - @Test - void legacySamlMetadataAsXml() throws Exception { - String metadataString = new Scanner(new File("./src/test/resources/sample-okta-localhost.xml")).useDelimiter("\\Z").next(); - System.setProperty(LOGIN_IDP_METADATA, metadataString); - System.setProperty(LOGIN_IDP_ENTITY_ALIAS, "testIDPData"); - context = getServletContext("default,saml,configMetadata", "uaa.yml"); - List defs = context.getBean(BootstrapSamlIdentityProviderData.class).getIdentityProviderDefinitions(); - assertEquals( - SamlIdentityProviderDefinition.MetadataLocation.DATA, - findProvider(defs, "testIDPData").getType()); - } + private static final AbstractRefreshableWebApplicationContext abstractRefreshableWebApplicationContext = new AbstractRefreshableWebApplicationContext() { - @Test - void legacySamlMetadataAsUrl() { - System.setProperty(LOGIN_SAML_METADATA_TRUST_CHECK, "false"); - System.setProperty(LOGIN_IDP_METADATA_URL, "http://simplesamlphp.uaa.com:80/saml2/idp/metadata.php"); - System.setProperty(LOGIN_IDP_ENTITY_ALIAS, "testIDPUrl"); + @Override + protected void loadBeanDefinitions(@NonNull DefaultListableBeanFactory beanFactory) throws BeansException { + XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); - context = getServletContext("default", "uaa.yml"); - assertNotNull(context.getBean("viewResolver", ViewResolver.class)); - assertNotNull(context.getBean("samlLogger", SAMLDefaultLogger.class)); - assertFalse(context.getBean(BootstrapSamlIdentityProviderData.class).isLegacyMetadataTrustCheck()); - List defs = context.getBean(BootstrapSamlIdentityProviderData.class).getIdentityProviderDefinitions(); - assertNull( - defs.get(defs.size() - 1).getSocketFactoryClassName() - ); - assertEquals( - SamlIdentityProviderDefinition.MetadataLocation.URL, - defs.get(defs.size() - 1).getType() - ); - } + // Configure the bean definition reader with this context's + // resource loading environment. + beanDefinitionReader.setEnvironment(this.getEnvironment()); + beanDefinitionReader.setResourceLoader(this); + beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); - @ParameterizedTest - @MethodSource("samlSignatureParameterProvider") - void samlSignatureAlgorithm(String yamlFile, SamlConfigurationBean.SignatureAlgorithm algorithm) { - // When we override the SHA1 default for login.saml.signatureAlgorithm in the yaml, make sure it works. - context = getServletContext("default", yamlFile); + beanDefinitionReader.loadBeanDefinitions("file:./src/main/webapp/WEB-INF/spring-servlet.xml"); + } + }; - SamlConfigurationBean samlConfig = context.getBean("defaultSamlConfig", SamlConfigurationBean.class); - assertEquals( - algorithm, - samlConfig.getSignatureAlgorithm(), - "The SAML signature algorithm in the yaml file is set in the bean" - ); - } + private ConfigurableApplicationContext context; static Stream samlSignatureParameterProvider() { final String yamlPath = "test/config/"; return Stream.of( - arguments(yamlPath + "saml-algorithm-sha256.yml", SamlConfigurationBean.SignatureAlgorithm.SHA256), - arguments(yamlPath + "saml-algorithm-sha512.yml", SamlConfigurationBean.SignatureAlgorithm.SHA512) + arguments(yamlPath + "saml-algorithm-sha1.yml", SignatureAlgorithm.SHA1), + arguments(yamlPath + "saml-algorithm-sha256.yml", SignatureAlgorithm.SHA256), + arguments(yamlPath + "saml-algorithm-sha512.yml", SignatureAlgorithm.SHA512) ); } - @Test - void legacySamlUrlWithoutPort() { - System.setProperty(LOGIN_SAML_METADATA_TRUST_CHECK, "false"); - System.setProperty(LOGIN_IDP_METADATA_URL, "http://simplesamlphp.uaa.com/saml2/idp/metadata.php"); - System.setProperty(LOGIN_IDP_ENTITY_ALIAS, "testIDPUrl"); - - context = getServletContext("default", "uaa.yml"); - assertNotNull(context.getBean("viewResolver", ViewResolver.class)); - assertNotNull(context.getBean("samlLogger", SAMLDefaultLogger.class)); - assertFalse(context.getBean(BootstrapSamlIdentityProviderData.class).isLegacyMetadataTrustCheck()); - List defs = context.getBean(BootstrapSamlIdentityProviderData.class).getIdentityProviderDefinitions(); - assertFalse( - context.getBean(BootstrapSamlIdentityProviderData.class).getIdentityProviderDefinitions().isEmpty() - ); - assertNull( - defs.get(defs.size() - 1).getSocketFactoryClassName() - ); - assertEquals( - SamlIdentityProviderDefinition.MetadataLocation.URL, - defs.get(defs.size() - 1).getType() - ); - } - - private static SamlIdentityProviderDefinition findProvider( + private static SamlIdentityProviderDefinition providerByAlias( final List defs, final String alias) { - for (SamlIdentityProviderDefinition def : defs) { - if (alias.equals(def.getIdpEntityAlias())) { - return def; - } - } - return null; + + return defs.stream() + .filter(def -> alias.equals(def.getIdpEntityAlias())) + .findFirst() + .orElse(null); } private static ConfigurableApplicationContext getServletContext( @@ -255,38 +150,108 @@ private static ConfigurableApplicationContext getServletContext( return abstractRefreshableWebApplicationContext; } - private final static MockServletContext mockServletContext = new MockServletContext() { - @Override - public RequestDispatcher getNamedDispatcher(String path) { - return new MockRequestDispatcher("/"); - } + @BeforeEach + void beforeEach() { + System.clearProperty(LOGIN_IDP_METADATA); + System.clearProperty(LOGIN_IDP_ENTITY_ALIAS); + System.clearProperty(LOGIN_IDP_METADATA_URL); + System.clearProperty(LOGIN_SAML_METADATA_TRUST_CHECK); + } - @Override - public String getVirtualServerName() { - return "localhost"; - } + @Test + void legacyDeprecatedProperties() { + context = getServletContext(null, "test/bootstrap/deprecated_properties_still_work.yml"); + ScimGroupProvisioning scimGroupProvisioning = context.getBean("scimGroupProvisioning", ScimGroupProvisioning.class); + List scimGroups = scimGroupProvisioning.retrieveAll(IdentityZoneHolder.get().getId()); + Assertions.assertThat(scimGroups) + .haveAtLeastOne(new Condition<>(g -> g.getDisplayName().equals("pony") && g.getDescription().equals("The magic of friendship"), "pony group")) + .haveAtLeastOne(new Condition<>(g -> g.getDisplayName().equals("cat") && g.getDescription().equals("The cat"), "cat group")); - @Override - public void addListener(Type t) { - //no op - } - }; + IdentityZoneConfigurationBootstrap zoneBootstrap = context.getBean(IdentityZoneConfigurationBootstrap.class); + assertThat(zoneBootstrap.getHomeRedirect()).isEqualTo("https://deprecated.home_redirect.com"); + IdentityZone defaultZone = context.getBean(IdentityZoneProvisioning.class).retrieve("uaa"); + IdentityZoneConfiguration defaultConfig = defaultZone.getConfig(); - private static final AbstractRefreshableWebApplicationContext abstractRefreshableWebApplicationContext = new AbstractRefreshableWebApplicationContext() { + assertThat(defaultConfig.getSamlConfig().getKeys()).as("Legacy SAML keys should be available").containsKey(SamlConfig.LEGACY_KEY_ID); + assertThat(defaultConfig.getSamlConfig().getCertificate().trim()).isEqualTo(SamlKeyManagerFactoryCertificateTests.CERTIFICATE.trim()); + assertThat(defaultConfig.getSamlConfig().getPrivateKey().trim()).isEqualTo(SamlKeyManagerFactoryCertificateTests.KEY.trim()); + assertThat(defaultConfig.getSamlConfig().getPrivateKeyPassword().trim()).isEqualTo(SamlKeyManagerFactoryCertificateTests.PASSWORD.trim()); + } - @Override - protected void loadBeanDefinitions(@NonNull DefaultListableBeanFactory beanFactory) throws BeansException { - XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); + @Test + void legacySamlIdpAsTopLevelElement() { + System.setProperty(LOGIN_SAML_METADATA_TRUST_CHECK, "false"); + System.setProperty(LOGIN_IDP_METADATA_URL, "classpath:sample-okta-localhost.xml"); + System.setProperty(LOGIN_IDP_ENTITY_ALIAS, "testIDPFile"); - // Configure the bean definition reader with this context's - // resource loading environment. - beanDefinitionReader.setEnvironment(this.getEnvironment()); - beanDefinitionReader.setResourceLoader(this); - beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); + context = getServletContext("default", "uaa.yml"); + assertThat(context.getBean("viewResolver", ViewResolver.class)).isNotNull(); + // assertNotNull(context.getBean("samlLogger", SAMLDefaultLogger.class)) + assertThat(context.getBean(BootstrapSamlIdentityProviderData.class)) + .returns(false, BootstrapSamlIdentityProviderData::isLegacyMetadataTrustCheck); + List defs = context.getBean(BootstrapSamlIdentityProviderData.class).getIdentityProviderDefinitions(); + assertThat(providerByAlias(defs, "testIDPFile")) + .returns(SamlIdentityProviderDefinition.MetadataLocation.URL, SamlIdentityProviderDefinition::getType); + } - beanDefinitionReader.loadBeanDefinitions("file:./src/main/webapp/WEB-INF/spring-servlet.xml"); - } - }; + @Test + void legacySamlMetadataAsXml() { + String metadataString = loadResouceAsString("sample-okta-localhost.xml"); + System.setProperty(LOGIN_IDP_METADATA, metadataString); + System.setProperty(LOGIN_IDP_ENTITY_ALIAS, "testIDPData"); + context = getServletContext("default,saml,configMetadata", "uaa.yml"); + List defs = context.getBean(BootstrapSamlIdentityProviderData.class).getIdentityProviderDefinitions(); + Assertions.assertThat(providerByAlias(defs, "testIDPData")) + .isNotNull() + .returns(SamlIdentityProviderDefinition.MetadataLocation.DATA, SamlIdentityProviderDefinition::getType); + } + @Test + void legacySamlMetadataAsUrl() { + System.setProperty(LOGIN_SAML_METADATA_TRUST_CHECK, "false"); + System.setProperty(LOGIN_IDP_METADATA_URL, "http://simplesamlphp.uaa-acceptance.cf-app.com/saml2/idp/metadata.php"); + System.setProperty(LOGIN_IDP_ENTITY_ALIAS, "testIDPUrl"); + + context = getServletContext("default", "uaa.yml"); + assertThat(context.getBean("viewResolver", ViewResolver.class)).isNotNull(); + // assertNotNull(context.getBean("samlLogger", SAMLDefaultLogger.class)) + assertThat(context.getBean(BootstrapSamlIdentityProviderData.class)) + .returns(false, BootstrapSamlIdentityProviderData::isLegacyMetadataTrustCheck); + List defs = context.getBean(BootstrapSamlIdentityProviderData.class).getIdentityProviderDefinitions(); + Assertions.assertThat(providerByAlias(defs, "testIDPUrl")) + .isNotNull() + .returns(null, SamlIdentityProviderDefinition::getSocketFactoryClassName) + .returns(SamlIdentityProviderDefinition.MetadataLocation.URL, SamlIdentityProviderDefinition::getType); + } + + @ParameterizedTest + @MethodSource("samlSignatureParameterProvider") + void samlSignatureAlgorithmsWereBootstrapped(String yamlFile, SignatureAlgorithm algorithm) { + // When we override the SHA1 default for login.saml.signatureAlgorithm in the yaml, make sure it works. + context = getServletContext("default", yamlFile); + SignatureAlgorithm signatureAlgorithm = context.getBean(SignatureAlgorithm.class); + assertThat(signatureAlgorithm) + .as("The SAML signature algorithm in the yaml file is set in the bean") + .isEqualTo(algorithm); + } + + @Test + void samlSignatureAlgorithmIsInvalid() { + context = getServletContext("default", "test/config/saml-algorithm-invalid.yml"); + // When we override the SHA1 default for login.saml.signatureAlgorithm in the yaml, make sure it works. + SignatureAlgorithm signatureAlgorithm = context.getBean(SignatureAlgorithm.class); + assertThat(signatureAlgorithm).isSameAs(SignatureAlgorithm.INVALID); + } + + private static String loadResouceAsString(String resourceLocation) { + ResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource resource = resourceLoader.getResource(resourceLocation); + + try (Reader reader = new InputStreamReader(resource.getInputStream(), UTF_8)) { + return FileCopyUtils.copyToString(reader); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java index a2039f0adbb..3051cefd138 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java @@ -14,6 +14,7 @@ package org.cloudfoundry.identity.uaa.login; +import org.assertj.core.api.Assertions; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; @@ -22,6 +23,7 @@ import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.ZoneScimInviteData; +import org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; @@ -38,7 +40,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mock.web.MockHttpSession; -import org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -50,14 +51,18 @@ import java.util.Arrays; import java.util.Collections; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; -import static org.junit.Assert.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; @DefaultTestContext public class InvitationsServiceMockMvcTests { @@ -78,8 +83,8 @@ public class InvitationsServiceMockMvcTests { public static final String REDIRECT_URI = "http://invitation.redirect.test"; private JavaMailSender originalSender; - private FakeJavaMailSender fakeJavaMailSender = new FakeJavaMailSender(); - private AlphanumericRandomValueStringGenerator generator = new AlphanumericRandomValueStringGenerator(); + private final FakeJavaMailSender fakeJavaMailSender = new FakeJavaMailSender(); + private final AlphanumericRandomValueStringGenerator generator = new AlphanumericRandomValueStringGenerator(); private String clientId; private String userInviteToken; @@ -122,20 +127,20 @@ void inviteUserCorrectOriginSet() throws Exception { void testAuthorizeWithInvitationLogin() throws Exception { String email = new AlphanumericRandomValueStringGenerator().generate().toLowerCase() + "@test.org"; URL inviteLink = inviteUser(webApplicationContext, mockMvc, email, userInviteToken, null, clientId, OriginKeys.UAA); - assertEquals(OriginKeys.UAA, jdbcTemplate.queryForObject("SELECT origin FROM users WHERE username=?", new Object[]{email}, String.class)); + assertThat(jdbcTemplate.queryForObject("SELECT origin FROM users WHERE username=?", new Object[]{email}, String.class)).isEqualTo(OriginKeys.UAA); String code = extractInvitationCode(inviteLink.toString()); MvcResult result = mockMvc.perform( - get("/invitations/accept") - .param("code", code) - .accept(MediaType.TEXT_HTML) - ) + get("/invitations/accept") + .param("code", code) + .accept(MediaType.TEXT_HTML) + ) .andExpect(status().isOk()) .andExpect(content().string(containsString("Email: " + email))) .andReturn(); MockHttpSession inviteSession = (MockHttpSession) result.getRequest().getSession(false); - assertNotNull(inviteSession); - assertNotNull(inviteSession.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)); + assertThat(inviteSession).isNotNull(); + assertThat(inviteSession.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)).isNotNull(); String redirectUri = "https://example.com/dashboard/?appGuid=app-guid"; String clientId = "authclient-" + new AlphanumericRandomValueStringGenerator().generate(); UaaClientDetails client = new UaaClientDetails(clientId, "", "openid", GRANT_TYPE_AUTHORIZATION_CODE, "", redirectUri); @@ -157,34 +162,33 @@ void testAuthorizeWithInvitationLogin() throws Exception { .andExpect(status().is3xxRedirection()) .andReturn(); String location = result.getResponse().getHeader("Location"); - assertThat(location, endsWith("/login")); - assertEquals(-1, location.indexOf("code")); + assertThat(location).endsWith("/login") + .doesNotContain("code"); } @Test void acceptInvitationShouldNotLogYouIn() throws Exception { String email = new AlphanumericRandomValueStringGenerator().generate().toLowerCase() + "@test.org"; URL inviteLink = inviteUser(webApplicationContext, mockMvc, email, userInviteToken, null, clientId, OriginKeys.UAA); - assertEquals(OriginKeys.UAA, jdbcTemplate.queryForObject("SELECT origin FROM users WHERE username=?", new Object[]{email}, String.class)); + assertThat(jdbcTemplate.queryForObject("SELECT origin FROM users WHERE username=?", new Object[]{email}, String.class)).isEqualTo(OriginKeys.UAA); String code = extractInvitationCode(inviteLink.toString()); MvcResult result = mockMvc.perform(get("/invitations/accept") - .param("code", code) - .accept(MediaType.TEXT_HTML) - ) + .param("code", code) + .accept(MediaType.TEXT_HTML) + ) .andExpect(status().isOk()) .andExpect(content().string(containsString("Email: " + email))) .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); mockMvc.perform( - get("/profile") - .session(session) - .accept(MediaType.TEXT_HTML) - ) + get("/profile") + .session(session) + .accept(MediaType.TEXT_HTML) + ) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/login")); - } @Test @@ -193,15 +197,15 @@ void acceptInvitationForVerifiedUserSendsRedirect() throws Exception { URL inviteLink = inviteUser(webApplicationContext, mockMvc, email, userInviteToken, null, clientId, OriginKeys.UAA); jdbcTemplate.update("UPDATE users SET verified=true WHERE email=?", email); - assertTrue("User should not be verified", queryUserForField(jdbcTemplate, email, "verified", Boolean.class)); - assertEquals(OriginKeys.UAA, queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)); + assertThat(queryUserForField(jdbcTemplate, email, "verified", Boolean.class)).as("User should not be verified").isTrue(); + assertThat(queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)).isEqualTo(OriginKeys.UAA); String code = extractInvitationCode(inviteLink.toString()); mockMvc.perform( - get("/invitations/accept") - .param("code", code) - .accept(MediaType.TEXT_HTML) - ) + get("/invitations/accept") + .param("code", code) + .accept(MediaType.TEXT_HTML) + ) .andExpect(status().isFound()) .andExpect(redirectedUrl(REDIRECT_URI)); } @@ -210,7 +214,7 @@ void acceptInvitationForVerifiedUserSendsRedirect() throws Exception { void acceptInvitationForUaaUserShouldNotExpireInvitelink() throws Exception { String email = new AlphanumericRandomValueStringGenerator().generate().toLowerCase() + "@test.org"; URL inviteLink = inviteUser(webApplicationContext, mockMvc, email, userInviteToken, null, clientId, OriginKeys.UAA); - assertEquals(OriginKeys.UAA, queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)); + assertThat(queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)).isEqualTo(OriginKeys.UAA); String code = extractInvitationCode(inviteLink.toString()); MockHttpServletRequestBuilder get = get("/invitations/accept") @@ -219,7 +223,7 @@ void acceptInvitationForUaaUserShouldNotExpireInvitelink() throws Exception { mockMvc.perform(get) .andExpect(status().isOk()); mockMvc.perform(get) - .andExpect(status().isOk()); + .andExpect(status().isOk()); mockMvc.perform(get) .andExpect(status().isOk()); } @@ -231,44 +235,44 @@ void invalid_code() throws Exception { URL inviteLink = inviteUser(webApplicationContext, mockMvc, email, userInviteToken, null, clientId, OriginKeys.UAA); URL invalidLink = inviteUser(webApplicationContext, mockMvc, invalid, userInviteToken, null, clientId, OriginKeys.UAA); - assertFalse("User should not be verified", queryUserForField(jdbcTemplate, email, "verified", Boolean.class)); - assertEquals(OriginKeys.UAA, queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)); + assertThat(queryUserForField(jdbcTemplate, email, "verified", Boolean.class)).as("User should not be verified").isFalse(); + assertThat(queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)).isEqualTo(OriginKeys.UAA); String code = extractInvitationCode(inviteLink.toString()); String invalidCode = extractInvitationCode(invalidLink.toString()); MvcResult result = mockMvc.perform(get("/invitations/accept") - .param("code", code) - .accept(MediaType.TEXT_HTML) - ) + .param("code", code) + .accept(MediaType.TEXT_HTML) + ) .andExpect(status().isOk()) .andExpect(content().string(containsString("Email: " + email))) .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); result = mockMvc.perform( - post("/invitations/accept.do") - .session(session) - .param("password", "s3cret") - .param("password_confirmation", "s3cret") - .param("code", invalidCode) - .with(cookieCsrf()) - ) + post("/invitations/accept.do") + .session(session) + .param("password", "s3cret") + .param("password_confirmation", "s3cret") + .param("code", invalidCode) + .with(cookieCsrf()) + ) .andExpect(status().isUnprocessableEntity()) .andExpect(model().attribute("error_message_code", "code_expired")) .andExpect(view().name("invitations/accept_invite")) .andReturn(); - assertFalse("User should be not yet be verified", queryUserForField(jdbcTemplate, email, "verified", Boolean.class)); - assertNull(session.getAttribute("SPRING_SECURITY_CONTEXT")); + assertThat(queryUserForField(jdbcTemplate, email, "verified", Boolean.class)).as("User should be not yet be verified").isFalse(); + assertThat(session.getAttribute("SPRING_SECURITY_CONTEXT")).isNull(); session = (MockHttpSession) result.getRequest().getSession(false); //not logged in anymore mockMvc.perform( - get("/profile") - .session(session) - .accept(MediaType.TEXT_HTML) - ) + get("/profile") + .session(session) + .accept(MediaType.TEXT_HTML) + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://localhost/login")); } @@ -278,14 +282,14 @@ void acceptInvitationSetsYourPassword() throws Exception { String email = new AlphanumericRandomValueStringGenerator().generate().toLowerCase() + "@test.org"; URL inviteLink = inviteUser(webApplicationContext, mockMvc, email, userInviteToken, null, clientId, OriginKeys.UAA); - assertFalse("User should not be verified", queryUserForField(jdbcTemplate, email, "verified", Boolean.class)); - assertEquals(OriginKeys.UAA, queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)); + assertThat(queryUserForField(jdbcTemplate, email, "verified", Boolean.class)).as("User should not be verified").isFalse(); + assertThat(queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)).isEqualTo(OriginKeys.UAA); String code = extractInvitationCode(inviteLink.toString()); MvcResult result = mockMvc.perform(get("/invitations/accept") - .param("code", code) - .accept(MediaType.TEXT_HTML) - ) + .param("code", code) + .accept(MediaType.TEXT_HTML) + ) .andExpect(status().isOk()) .andExpect(content().string(containsString("Email: " + email))) .andReturn(); @@ -293,25 +297,25 @@ void acceptInvitationSetsYourPassword() throws Exception { code = jdbcTemplate.queryForObject("SELECT code FROM expiring_code_store", String.class); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); result = mockMvc.perform( - post("/invitations/accept.do") - .session(session) - .param("password", "s3cret") - .param("password_confirmation", "s3cret") - .param("code", code) - .with(cookieCsrf()) - ) + post("/invitations/accept.do") + .session(session) + .param("password", "s3cret") + .param("password_confirmation", "s3cret") + .param("code", code) + .with(cookieCsrf()) + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?success=invite_accepted&form_redirect_uri=" + REDIRECT_URI)) .andReturn(); - assertTrue("User should be verified after password reset", queryUserForField(jdbcTemplate, email, "verified", Boolean.class)); + assertThat(queryUserForField(jdbcTemplate, email, "verified", Boolean.class)).as("User should be verified after password reset").isTrue(); session = (MockHttpSession) result.getRequest().getSession(false); mockMvc.perform( - get("/profile") - .session(session) - .accept(MediaType.TEXT_HTML) - ) + get("/profile") + .session(session) + .accept(MediaType.TEXT_HTML) + ) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/login")); } @@ -328,8 +332,8 @@ void inviteLdapUsersVerifiesAndRedirects() throws Exception { URL inviteLink = inviteUser(webApplicationContext, mockMvc, email, zone.getAdminToken(), zone.getZone().getIdentityZone().getSubdomain(), zone.getScimInviteClient().getClientId(), provider.getOriginKey()); String code = extractInvitationCode(inviteLink.toString()); - assertFalse("User should not be verified", queryUserForField(jdbcTemplate, email, "verified", Boolean.class)); - assertEquals(OriginKeys.LDAP, queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)); + assertThat(queryUserForField(jdbcTemplate, email, "verified", Boolean.class)).as("User should not be verified").isFalse(); + assertThat(queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)).isEqualTo(OriginKeys.LDAP); ResultActions actions = mockMvc.perform(get("/invitations/accept") .param("code", code) @@ -340,7 +344,7 @@ void inviteLdapUsersVerifiesAndRedirects() throws Exception { .andExpect(status().isOk()) .andExpect(content().string(containsString("Email: " + email))); - assertFalse("LDAP user should not be verified after accepting invite until logging in", queryUserForField(jdbcTemplate, email, "verified", Boolean.class)); + assertThat(queryUserForField(jdbcTemplate, email, "verified", Boolean.class)).as("LDAP user should not be verified after accepting invite until logging in").isFalse(); } @Test @@ -352,34 +356,27 @@ void inviteSamlUserWillRedirectUponAccept() throws Exception { SamlIdentityProviderDefinition definition = getSamlIdentityProviderDefinition(zone.getZone(), entityID); definition.setEmailDomain(Collections.singletonList(domain)); definition.setIdpEntityAlias(originKey); - IdentityProvider provider = createIdentityProvider(mockMvc, zone.getZone(), originKey, definition); + IdentityProvider provider = createIdentityProvider(mockMvc, zone.getZone(), originKey, definition); String email = new AlphanumericRandomValueStringGenerator().generate().toLowerCase() + "@" + domain; URL inviteLink = inviteUser(webApplicationContext, mockMvc, email, zone.getAdminToken(), zone.getZone().getIdentityZone().getSubdomain(), zone.getScimInviteClient().getClientId(), provider.getOriginKey()); String code = extractInvitationCode(inviteLink.toString()); - assertFalse("User should not be verified", queryUserForField(jdbcTemplate, email, "verified", Boolean.class)); - assertEquals(originKey, queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)); + assertThat(queryUserForField(jdbcTemplate, email, "verified", Boolean.class)).as("User should not be verified").isFalse(); + assertThat(queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)).isEqualTo(originKey); //should redirect to saml provider mockMvc.perform( - get("/invitations/accept") - .param("code", code) - .accept(MediaType.TEXT_HTML) - .header("Host", zone.getZone().getIdentityZone().getSubdomain() + ".localhost") - ) + get("/invitations/accept") + .param("code", code) + .accept(MediaType.TEXT_HTML) + .header("Host", zone.getZone().getIdentityZone().getSubdomain() + ".localhost") + ) .andExpect(status().is3xxRedirection()) - .andExpect( - redirectedUrl( - String.format("/saml/discovery?returnIDParam=idp&entityID=%s.cloudfoundry-saml-login&idp=%s&isPassive=true", - zone.getZone().getIdentityZone().getId(), - originKey) - ) - ); - + .andExpect(redirectedUrl("/saml2/authenticate/%s".formatted(originKey))); - assertEquals(provider.getOriginKey(), queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)); - assertFalse("Saml user should not yet be verified after clicking on the accept link", queryUserForField(jdbcTemplate, email, "verified", Boolean.class)); + Assertions.assertThat(queryUserForField(jdbcTemplate, email, OriginKeys.ORIGIN, String.class)).isEqualTo(provider.getOriginKey()); + assertThat(queryUserForField(jdbcTemplate, email, "verified", Boolean.class)).as("Saml user should not yet be verified after clicking on the accept link").isFalse(); } private static T queryUserForField(JdbcTemplate jdbcTemplate, String email, String field, Class type) { @@ -390,7 +387,7 @@ private static ZoneScimInviteData createZoneForInvites(MockMvc mockMvc, WebAppli return MockMvcUtils.createZoneForInvites(mockMvc, webApplicationContext, clientId, REDIRECT_URI, IdentityZoneHolder.getCurrentZoneId()); } - private static IdentityProvider createIdentityProvider(MockMvc mockMvc, IdentityZoneCreationResult zone, String nameAndOriginKey, AbstractIdentityProviderDefinition definition) throws Exception { + private static IdentityProvider createIdentityProvider(MockMvc mockMvc, IdentityZoneCreationResult zone, String nameAndOriginKey, T definition) throws Exception { return MockMvcUtils.createIdentityProvider(mockMvc, zone, nameAndOriginKey, definition); } @@ -410,5 +407,4 @@ private static URL inviteUser(WebApplicationContext webApplicationContext, MockM private static String extractInvitationCode(String inviteLink) { return MockMvcUtils.extractInvitationCode(inviteLink); } - } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index a3243e18736..afffd8a60f1 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -10,6 +10,7 @@ import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; @@ -56,8 +57,6 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.crypto.codec.Base64; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.springframework.security.web.PortResolverImpl; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.savedrequest.DefaultSavedRequest; @@ -83,6 +82,7 @@ import java.nio.charset.StandardCharsets; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -97,6 +97,7 @@ import static java.util.Collections.EMPTY_LIST; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.SAML; @@ -105,38 +106,31 @@ import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createOtherIdentityZone; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getMarissaSecurityContext; -import static org.cloudfoundry.identity.uaa.security.web.CorsFilter.X_REQUESTED_WITH; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getUaaSecurityContext; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; +import static org.cloudfoundry.identity.uaa.security.web.CorsFilter.X_REQUESTED_WITH; import static org.cloudfoundry.identity.uaa.util.SessionUtils.SAVED_REQUEST_SESSION_ATTRIBUTE; import static org.cloudfoundry.identity.uaa.zone.IdentityZone.getUaa; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isEmptyOrNullString; -import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.http.HttpHeaders.*; -import static org.springframework.http.HttpMethod.*; +import static org.springframework.http.HttpHeaders.ACCEPT; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.OPTIONS; +import static org.springframework.http.HttpMethod.POST; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.TEXT_HTML; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.securityContext; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; @@ -155,6 +149,10 @@ @DefaultTestContext @DirtiesContext public class LoginMockMvcTests { + private static final Base64.Encoder ENCODER = Base64.getEncoder(); + private static final String DEFAULT_COPYRIGHT_TEMPLATE = "Copyright © %s"; + private static final String CF_COPYRIGHT_TEXT = String.format(DEFAULT_COPYRIGHT_TEMPLATE, "CloudFoundry.org Foundation, Inc."); + private static final String CF_LAST_LOGIN = "Last Login"; private WebApplicationContext webApplicationContext; @@ -182,7 +180,7 @@ void setUpContext( this.limitedModeUaaFilter = limitedModeUaaFilter; SecurityContextHolder.clearContext(); - String adminToken = MockMvcUtils.getClientCredentialsOAuthAccessToken(mockMvc, "admin", "adminsecret", null, null); + MockMvcUtils.getClientCredentialsOAuthAccessToken(mockMvc, "admin", "adminsecret", null, null); identityZoneConfiguration = identityZoneProvisioning.retrieve(IdentityZone.getUaaZoneId()).getConfig(); IdentityZoneHolder.setProvisioning(identityZoneProvisioning); @@ -193,7 +191,7 @@ void setUpContext( originalLimitedModeStatusFile = MockMvcUtils.getLimitedModeStatusFile(webApplicationContext); MockMvcUtils.resetLimitedModeStatusFile(webApplicationContext, null); - assertFalse(isLimitedMode(limitedModeUaaFilter)); + assertThat(isLimitedMode(limitedModeUaaFilter)).isFalse(); } @AfterEach @@ -270,9 +268,9 @@ private void expect_idp_discovery( MockHttpSession session = configure_UAA_for_idp_discovery(webApplicationContext, identityProviderProvisioning, generator, originKey, zone, allowedProviders); mockMvc.perform(get("/login") - .session(session) - .header("Accept", TEXT_HTML) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .session(session) + .header("Accept", TEXT_HTML) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()) .andExpect(view().name("idp_discovery/email")) .andExpect(xpath("//input[@name='email']").exists()); @@ -285,9 +283,9 @@ void access_discovery_when_expected( List> allowedProvidersPermutations = new ArrayList<>(); allowedProvidersPermutations.add(new ArrayList<>(asList(UAA, LDAP, SAML))); // Model should not contain a login hint if we allow both UAA and LDAP - allowedProvidersPermutations.add(new ArrayList<>(asList(UAA, LDAP ))); // Model should not contain a login hint if we allow both UAA and LDAP - allowedProvidersPermutations.add(new ArrayList<>(asList(UAA, SAML))); // Model should contain a login hint if we exclude LDAP from allowed providers - allowedProvidersPermutations.add(new ArrayList<>(asList( LDAP, SAML))); // Model should contain a login hint if we exclude UAA from allowed providers + allowedProvidersPermutations.add(new ArrayList<>(asList(UAA, LDAP))); // Model should not contain a login hint if we allow both UAA and LDAP + allowedProvidersPermutations.add(new ArrayList<>(asList(UAA, SAML))); // Model should contain a login hint if we exclude LDAP from allowed providers + allowedProvidersPermutations.add(new ArrayList<>(asList(LDAP, SAML))); // Model should contain a login hint if we exclude UAA from allowed providers allowedProvidersPermutations.add(new ArrayList<>(singletonList(UAA))); // Model should contain a login hint if we exclude LDAP from allowed providers allowedProvidersPermutations.add(new ArrayList<>(singletonList(LDAP))); // Model should contain a login hint if we exclude UAA from allowed providers @@ -317,9 +315,9 @@ void redirect_when_only_saml_allowed( new ArrayList<>(asList(originKey, SAML))); mockMvc.perform(get("/login") - .session(session) - .header("Accept", TEXT_HTML) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .session(session) + .header("Accept", TEXT_HTML) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().is3xxRedirection()); } @@ -327,10 +325,10 @@ void redirect_when_only_saml_allowed( void access_login_page_while_logged_in() throws Exception { SecurityContext securityContext = MockMvcUtils.getMarissaSecurityContext(webApplicationContext, IdentityZoneHolder.getCurrentZoneId()); mockMvc.perform( - get("/login") - .header("Accept", MediaType.TEXT_HTML_VALUE) - .with(securityContext(securityContext)) - ) + get("/login") + .header("Accept", MediaType.TEXT_HTML_VALUE) + .with(securityContext(securityContext)) + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("/home")); } @@ -338,9 +336,9 @@ void access_login_page_while_logged_in() throws Exception { @Test void invalid_accept_media_type() throws Exception { mockMvc.perform( - get("/login") - .header("Accept", MediaType.TEXT_XML_VALUE) - ) + get("/login") + .header("Accept", MediaType.TEXT_XML_VALUE) + ) .andExpect(status().isNotAcceptable()); } @@ -369,9 +367,9 @@ void self_service_zone_variable_links( IdentityZone zone = createZoneLinksZone(); mockMvc.perform( - get("/login") - .header("Host", zone.getSubdomain() + ".localhost") - ) + get("/login") + .header("Host", zone.getSubdomain() + ".localhost") + ) .andExpect(status().isOk()) .andExpect(view().name("login")) .andExpect(model().attribute("links", hasEntry("forgotPasswordLink", "/forgot_password"))) @@ -385,9 +383,9 @@ void self_service_zone_variable_links( )); mockMvc.perform( - get("/login") - .header("Host", zone.getSubdomain() + ".localhost") - ) + get("/login") + .header("Host", zone.getSubdomain() + ".localhost") + ) .andExpect(status().isOk()) .andExpect(view().name("login")) .andExpect(model().attribute("links", hasEntry("forgotPasswordLink", "/passwd?id=" + zone.getId()))) @@ -402,9 +400,9 @@ void self_service_zone_variable_links( ); zone = MockMvcUtils.updateIdentityZone(zone, webApplicationContext); mockMvc.perform( - get("/login") - .header("Host", zone.getSubdomain() + ".localhost") - ) + get("/login") + .header("Host", zone.getSubdomain() + ".localhost") + ) .andExpect(status().isOk()) .andExpect(view().name("login")) .andExpect(model().attribute("links", hasEntry("forgotPasswordLink", "/local_passwd?id=" + zone.getId()))) @@ -424,30 +422,30 @@ void global_zone_variable_home_redirect( ScimUser marissa = createUser(scimUserProvisioning, generator, zone.getId()); mockMvc.perform( - get("/") - .with(securityContext(getUaaSecurityContext(marissa.getUserName(), webApplicationContext, zone.getId()))) - .header("Host", zone.getSubdomain() + ".localhost") - ) + get("/") + .with(securityContext(getUaaSecurityContext(marissa.getUserName(), webApplicationContext, zone.getId()))) + .header("Host", zone.getSubdomain() + ".localhost") + ) .andDo(print()) .andExpect(status().isOk()); globalLinks.setHomeRedirect("http://{zone.subdomain}.redirect.to/z/{zone.id}"); mockMvc.perform( - get("/") - .with(securityContext(getUaaSecurityContext(marissa.getUserName(), webApplicationContext, zone.getId()))) - .header("Host", zone.getSubdomain() + ".localhost") - ) + get("/") + .with(securityContext(getUaaSecurityContext(marissa.getUserName(), webApplicationContext, zone.getId()))) + .header("Host", zone.getSubdomain() + ".localhost") + ) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://" + zone.getSubdomain() + ".redirect.to/z/" + zone.getId())); zone.getConfig().getLinks().setHomeRedirect("http://configured.{zone.subdomain}.redirect.to/z/{zone.id}"); zone = MockMvcUtils.updateIdentityZone(zone, webApplicationContext); mockMvc.perform( - get("/") - .with(securityContext(getUaaSecurityContext(marissa.getUserName(), webApplicationContext, zone.getId()))) - .header("Host", zone.getSubdomain() + ".localhost") - ) + get("/") + .with(securityContext(getUaaSecurityContext(marissa.getUserName(), webApplicationContext, zone.getId()))) + .header("Host", zone.getSubdomain() + ".localhost") + ) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://configured." + zone.getSubdomain() + ".redirect.to/z/" + zone.getId())); } @@ -475,8 +473,8 @@ void testLogin_Csrf_Reset_On_Refresh() throws Exception { .cookie(csrf1)) .andReturn(); Cookie csrf2 = mvcResult.getResponse().getCookie(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME); - assertNotNull(csrf2); - assertNotEquals(csrf1.getValue(), csrf2.getValue()); + assertThat(csrf2).isNotNull(); + assertThat(csrf2.getValue()).isNotEqualTo(csrf1.getValue()); } @Test @@ -488,7 +486,7 @@ void testLoginPageReloadOnCsrfExpiry( MvcResult mvcResult = mockMvc .perform(get("/login")) .andReturn(); - assertThat("", mvcResult.getResponse().getContentAsString(), containsString("http-equiv=\"refresh\" content=\"3\"")); + assertThat(mvcResult.getResponse().getContentAsString()).as("").contains("http-equiv=\"refresh\" content=\"3\""); cookieBasedCsrfTokenRepository.setCookieMaxAge(CookieBasedCsrfTokenRepository.DEFAULT_COOKIE_MAX_AGE); } @@ -513,10 +511,10 @@ void test_cookie_csrf( Cookie cookie = new Cookie(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrfValue); mockMvc.perform( - invalidPost - .cookie(cookie) - .param(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, "other-value") - ) + invalidPost + .cookie(cookie) + .param(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, "other-value") + ) .andDo(print()) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://localhost/login?error=invalid_login_request")); @@ -540,7 +538,7 @@ void test_case_insensitive_login( ) throws Exception { String username = "mixed-CASE-USER-" + generator.generate() + "@testdomain.com"; ScimUser user = createUser(scimUserProvisioning, username, IdentityZone.getUaaZoneId()); - assertEquals(username, user.getUserName()); + assertThat(user.getUserName()).isEqualTo(username); MockHttpServletRequestBuilder loginPost = post("/authenticate") .accept(MediaType.APPLICATION_JSON_VALUE) .param("username", user.getUserName()) @@ -577,7 +575,7 @@ void test_previous_login_time_upon_authentication( .param("password", user.getPassword())); long afterAuthTime = System.currentTimeMillis(); SecurityContext securityContext = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); - assertNull(((UaaAuthentication) securityContext.getAuthentication()).getLastLoginSuccessTime()); + assertThat(((UaaAuthentication) securityContext.getAuthentication()).getLastLoginSuccessTime()).isNull(); session = new MockHttpSession(); mockMvc.perform(post("/uaa/login.do") @@ -589,8 +587,8 @@ void test_previous_login_time_upon_authentication( securityContext = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); Long lastLoginTime = ((UaaAuthentication) securityContext.getAuthentication()).getLastLoginSuccessTime(); - assertThat(lastLoginTime, greaterThanOrEqualTo(beforeAuthTime)); - assertThat(lastLoginTime, lessThanOrEqualTo(afterAuthTime)); + assertThat(lastLoginTime).isGreaterThanOrEqualTo(beforeAuthTime) + .isLessThanOrEqualTo(afterAuthTime); } @@ -602,18 +600,18 @@ void testLogin_Post_When_DisableInternalUserManagement_Is_True( MockMvcUtils.setDisableInternalAuth(webApplicationContext, IdentityZone.getUaaZoneId(), true); try { mockMvc.perform(post("/login.do") - .with(cookieCsrf()) - .param("username", user.getUserName()) - .param("password", user.getPassword())) + .with(cookieCsrf()) + .param("username", user.getUserName()) + .param("password", user.getPassword())) .andExpect(redirectedUrl("/login?error=login_failure")); } finally { MockMvcUtils.setDisableInternalAuth(webApplicationContext, IdentityZone.getUaaZoneId(), false); } mockMvc.perform(post("/uaa/login.do") - .with(cookieCsrf()) - .contextPath("/uaa") - .param("username", user.getUserName()) - .param("password", user.getPassword())) + .with(cookieCsrf()) + .contextPath("/uaa") + .param("username", user.getUserName()) + .param("password", user.getPassword())) .andDo(print()) .andExpect(redirectedUrl("/uaa/")); } @@ -677,7 +675,7 @@ void testCustomFavIcon_With_LineBreaks() throws Exception { @Test void testDefaultFooter() throws Exception { mockMvc.perform(get("/login")) - .andExpect(content().string(containsString(cfCopyrightText))) + .andExpect(content().string(containsString(CF_COPYRIGHT_TEXT))) .andExpect(content().string(not(containsString(CF_LAST_LOGIN)))); } @@ -690,7 +688,7 @@ void testCustomizedFooter() throws Exception { MockMvcUtils.setZoneConfiguration(webApplicationContext, IdentityZone.getUaaZoneId(), identityZoneConfiguration); mockMvc.perform(get("/login")) - .andExpect(content().string(allOf(containsString(customFooterText), not(containsString(cfCopyrightText))))) + .andExpect(content().string(allOf(containsString(customFooterText), not(containsString(CF_COPYRIGHT_TEXT))))) .andExpect(content().string(not(containsString(CF_LAST_LOGIN)))); } @@ -702,7 +700,7 @@ void testCustomCompanyName() throws Exception { identityZoneConfiguration.setBranding(branding); MockMvcUtils.setZoneConfiguration(webApplicationContext, IdentityZone.getUaaZoneId(), identityZoneConfiguration); - String expectedFooterText = String.format(defaultCopyrightTemplate, companyName); + String expectedFooterText = String.format(DEFAULT_COPYRIGHT_TEMPLATE, companyName); mockMvc.perform(get("/login")) .andExpect(content().string(allOf(containsString(expectedFooterText)))); } @@ -725,7 +723,7 @@ void testCustomCompanyNameInZone( IdentityZone identityZone = setupZone(webApplicationContext, mockMvc, identityZoneProvisioning, generator, config); - String expectedFooterText = String.format(defaultCopyrightTemplate, zoneCompanyName); + String expectedFooterText = String.format(DEFAULT_COPYRIGHT_TEMPLATE, zoneCompanyName); mockMvc.perform(get("/login").accept(TEXT_HTML).with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()) @@ -759,9 +757,9 @@ void testForgotPasswordPageDoesNotHaveCsrf() throws Exception { void testForgotPasswordSubmitDoesNotValidateCsrf() throws Exception { assumeFalse(isLimitedMode(limitedModeUaaFilter), "Test only runs in non limited mode."); mockMvc.perform( - post("/forgot_password.do") - .param("username", "marissa") - .with(cookieCsrf().useInvalidToken())) + post("/forgot_password.do") + .param("username", "marissa") + .with(cookieCsrf().useInvalidToken())) .andExpect(status().isFound()) .andExpect(redirectedUrl("email_sent?code=reset_password")); } @@ -769,9 +767,9 @@ void testForgotPasswordSubmitDoesNotValidateCsrf() throws Exception { @Test void testChangePasswordPageDoesHaveCsrf() throws Exception { mockMvc.perform( - get("/change_password") - .with(securityContext(MockMvcUtils.getMarissaSecurityContext(webApplicationContext, IdentityZoneHolder.getCurrentZoneId()))) - ) + get("/change_password") + .with(securityContext(MockMvcUtils.getMarissaSecurityContext(webApplicationContext, IdentityZoneHolder.getCurrentZoneId()))) + ) .andExpect(status().isOk()) .andExpect(view().name("change_password")) .andExpect(content().string(containsString("action=\"/change_password.do\""))) @@ -785,22 +783,22 @@ void testChangePasswordSubmitDoesValidateCsrf( assumeFalse(isLimitedMode(limitedModeUaaFilter), "Test only runs in non limited mode."); ScimUser user = createUser(scimUserProvisioning, generator, IdentityZone.getUaaZoneId()); mockMvc.perform( - post("/change_password.do") - .with(securityContext(MockMvcUtils.getUaaSecurityContext(user.getUserName(), webApplicationContext, IdentityZoneHolder.getCurrentZoneId()))) - .param("current_password", user.getPassword()) - .param("new_password", "newSecr3t") - .param("confirm_password", "newSecr3t") - .with(cookieCsrf().useInvalidToken())) + post("/change_password.do") + .with(securityContext(MockMvcUtils.getUaaSecurityContext(user.getUserName(), webApplicationContext, IdentityZoneHolder.getCurrentZoneId()))) + .param("current_password", user.getPassword()) + .param("new_password", "newSecr3t") + .param("confirm_password", "newSecr3t") + .with(cookieCsrf().useInvalidToken())) .andExpect(status().isForbidden()) .andExpect(forwardedUrl("/invalid_request")); mockMvc.perform( - post("/change_password.do") - .with(securityContext(MockMvcUtils.getUaaSecurityContext(user.getUserName(), webApplicationContext, IdentityZoneHolder.getCurrentZoneId()))) - .param("current_password", user.getPassword()) - .param("new_password", "newSecr3t") - .param("confirm_password", "newSecr3t") - .with(cookieCsrf())) + post("/change_password.do") + .with(securityContext(MockMvcUtils.getUaaSecurityContext(user.getUserName(), webApplicationContext, IdentityZoneHolder.getCurrentZoneId()))) + .param("current_password", user.getPassword()) + .param("new_password", "newSecr3t") + .param("confirm_password", "newSecr3t") + .with(cookieCsrf())) .andExpect(status().isFound()) .andExpect(redirectedUrl("profile")); } @@ -929,13 +927,13 @@ void testLogoutRedirectIsEnabledInZone( IdentityZone zone = MultitenancyFixture.identityZone(zoneId, zoneId); zone.setConfig(new IdentityZoneConfiguration()); zone = identityZoneProvisioning.create(zone); - assertFalse(zone.getConfig().getLinks().getLogout().isDisableRedirectParameter()); + assertThat(zone.getConfig().getLinks().getLogout().isDisableRedirectParameter()).isFalse(); } @Test void testLogOutChangeUrlValue() throws Exception { Links.Logout original = MockMvcUtils.getLogout(webApplicationContext, IdentityZone.getUaaZoneId()); - assertFalse(original.isDisableRedirectParameter()); + assertThat(original.isDisableRedirectParameter()).isFalse(); Links.Logout logout = MockMvcUtils.getLogout(webApplicationContext, IdentityZone.getUaaZoneId()); logout.setRedirectUrl("https://www.google.com"); MockMvcUtils.setLogout(webApplicationContext, IdentityZone.getUaaZoneId(), logout); @@ -962,31 +960,31 @@ void testLogOutWithClientRedirect() throws Exception { client.setClientSecret(clientId); MockMvcUtils.createClient(webApplicationContext, client, getUaa()); mockMvc.perform( - get("/uaa/logout.do") - .param(CLIENT_ID, clientId) - .param("redirect", "http://testing.com") - .contextPath("/uaa") - ) + get("/uaa/logout.do") + .param(CLIENT_ID, clientId) + .param("redirect", "http://testing.com") + .contextPath("/uaa") + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://testing.com")) .andExpect(emptyCurrentUserCookie()); mockMvc.perform( - get("/uaa/logout.do") - .param(CLIENT_ID, clientId) - .param("redirect", "http://www.wildcard.testing") - .contextPath("/uaa") - ) + get("/uaa/logout.do") + .param(CLIENT_ID, clientId) + .param("redirect", "http://www.wildcard.testing") + .contextPath("/uaa") + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://www.wildcard.testing")) .andExpect(emptyCurrentUserCookie()); mockMvc.perform( - get("/uaa/logout.do") - .param(CLIENT_ID, "non-existent-client") - .param("redirect", "http://www.wildcard.testing") - .contextPath("/uaa") - ) + get("/uaa/logout.do") + .param(CLIENT_ID, "non-existent-client") + .param("redirect", "http://www.wildcard.testing") + .contextPath("/uaa") + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("/uaa/login")) .andExpect(emptyCurrentUserCookie()); @@ -1017,17 +1015,17 @@ void testLogOut_Config_For_Zone( //other zone mockMvc.perform(get("/uaa/logout.do") - .contextPath("/uaa") - .header("Host", zoneId + ".localhost")) + .contextPath("/uaa") + .header("Host", zoneId + ".localhost")) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://test.redirect.com")) .andExpect(emptyCurrentUserCookie()); mockMvc.perform(get("/uaa/logout.do") - .contextPath("/uaa") - .header("Host", zoneId + ".localhost") - .param("redirect", "http://google.com") - ) + .contextPath("/uaa") + .header("Host", zoneId + ".localhost") + .param("redirect", "http://google.com") + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://test.redirect.com")) .andExpect(emptyCurrentUserCookie()); @@ -1036,10 +1034,10 @@ void testLogOut_Config_For_Zone( zone = identityZoneProvisioning.update(zone); mockMvc.perform(get("/uaa/logout.do") - .contextPath("/uaa") - .header("Host", zoneId + ".localhost") - .param("redirect", "http://google.com") - ) + .contextPath("/uaa") + .header("Host", zoneId + ".localhost") + .param("redirect", "http://google.com") + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://test.redirect.com")) .andExpect(emptyCurrentUserCookie()); @@ -1049,10 +1047,10 @@ void testLogOut_Config_For_Zone( zone = identityZoneProvisioning.update(zone); mockMvc.perform(get("/uaa/logout.do") - .contextPath("/uaa") - .header("Host", zoneId + ".localhost") - .param("redirect", "http://google.com") - ) + .contextPath("/uaa") + .header("Host", zoneId + ".localhost") + .param("redirect", "http://google.com") + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://google.com")) .andExpect(emptyCurrentUserCookie()); @@ -1061,19 +1059,19 @@ void testLogOut_Config_For_Zone( identityZoneProvisioning.update(zone); mockMvc.perform(get("/uaa/logout.do") - .contextPath("/uaa") - .header("Host", zoneId + ".localhost") - .param("redirect", "http://google.com") - ) + .contextPath("/uaa") + .header("Host", zoneId + ".localhost") + .param("redirect", "http://google.com") + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://test.redirect.com")) .andExpect(emptyCurrentUserCookie()); mockMvc.perform(get("/uaa/logout.do") - .contextPath("/uaa") - .header("Host", zoneId + ".localhost") - .param("redirect", "http://yahoo.com") - ) + .contextPath("/uaa") + .header("Host", zoneId + ".localhost") + .param("redirect", "http://yahoo.com") + ) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://yahoo.com")) .andExpect(emptyCurrentUserCookie()); @@ -1193,7 +1191,7 @@ void testLoginWithExplicitJsonPrompts() throws Exception { MockMvcUtils.setPrompts(webApplicationContext, IdentityZone.getUaaZoneId(), asList(first, second)); mockMvc.perform(get("/login") - .accept(APPLICATION_JSON)) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(view().name("login")) .andExpect(model().attribute("prompts", hasKey("how"))) @@ -1207,7 +1205,7 @@ void testLoginWithExplicitJsonPrompts() throws Exception { @Test void testLoginWithRemoteUaaPrompts() throws Exception { mockMvc.perform(get("/login") - .accept(TEXT_HTML)) + .accept(TEXT_HTML)) .andExpect(status().isOk()) .andExpect(view().name("login")) .andExpect(model().attribute("prompts", hasKey("username"))) @@ -1218,7 +1216,7 @@ void testLoginWithRemoteUaaPrompts() throws Exception { @Test void testLoginWithRemoteUaaJsonPrompts() throws Exception { mockMvc.perform(get("/login") - .accept(APPLICATION_JSON)) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(view().name("login")) .andExpect(model().attribute("prompts", hasKey("username"))) @@ -1228,7 +1226,7 @@ void testLoginWithRemoteUaaJsonPrompts() throws Exception { @Test void testInfoWithRemoteUaaJsonPrompts() throws Exception { mockMvc.perform(get("/info") - .accept(APPLICATION_JSON)) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(view().name("login")) .andExpect(model().attribute("prompts", hasKey("username"))) @@ -1283,7 +1281,7 @@ void testSamlLoginLinksShowActiveProviders( .setLinkText("Active SAML Provider") .setShowSamlLink(true) .setZoneId(identityZone.getId()); - IdentityProvider activeIdentityProvider = new IdentityProvider(); + IdentityProvider activeIdentityProvider = new IdentityProvider<>(); activeIdentityProvider.setType(SAML); activeIdentityProvider.setName("Active SAML Provider"); activeIdentityProvider.setConfig(activeSamlIdentityProviderDefinition); @@ -1297,7 +1295,7 @@ void testSamlLoginLinksShowActiveProviders( .setIdpEntityAlias(inactiveAlias) .setLinkText("You should not see me") .setZoneId(identityZone.getId()); - IdentityProvider inactiveIdentityProvider = new IdentityProvider(); + IdentityProvider inactiveIdentityProvider = new IdentityProvider<>(); inactiveIdentityProvider.setType(SAML); inactiveIdentityProvider.setName("Inactive SAML Provider"); inactiveIdentityProvider.setConfig(inactiveSamlIdentityProviderDefinition); @@ -1329,7 +1327,7 @@ void testSamlRedirectWhenTheOnlyProvider( .setIdpEntityAlias(alias) .setLinkText("Active SAML Provider") .setZoneId(identityZone.getId()); - IdentityProvider activeIdentityProvider = new IdentityProvider(); + IdentityProvider activeIdentityProvider = new IdentityProvider<>(); activeIdentityProvider.setType(SAML); activeIdentityProvider.setName("Active SAML Provider"); activeIdentityProvider.setActive(true); @@ -1345,16 +1343,16 @@ void testSamlRedirectWhenTheOnlyProvider( SessionUtils.setSavedRequestSession(session, savedRequest); mockMvc.perform(get("/login") - .accept(TEXT_HTML) - .session(session) - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .accept(TEXT_HTML) + .session(session) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/saml/discovery?returnIDParam=idp&entityID=" + identityZone.getSubdomain() + ".cloudfoundry-saml-login&idp=" + alias + "&isPassive=true")); + .andExpect(redirectedUrl("/saml2/authenticate/%s".formatted(alias))); mockMvc.perform(get("/login") - .accept(APPLICATION_JSON) - .session(session) - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .accept(APPLICATION_JSON) + .session(session) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()); IdentityProvider uaaProvider = jdbcIdentityProviderProvisioning.retrieveByOriginIgnoreActiveFlag(UAA, identityZone.getId()); @@ -1363,9 +1361,9 @@ void testSamlRedirectWhenTheOnlyProvider( uaaProvider.setActive(false); jdbcIdentityProviderProvisioning.update(uaaProvider, uaaProvider.getIdentityZoneId()); mockMvc.perform(get("/login") - .accept(APPLICATION_JSON) - .session(session) - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .accept(APPLICATION_JSON) + .session(session) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()); } finally { IdentityZoneHolder.set(identityZone); @@ -1393,7 +1391,7 @@ void samlRedirect_onlyOneProvider_noClientContext( .setIdpEntityAlias(alias) .setLinkText("Active SAML Provider") .setZoneId(identityZone.getId()); - IdentityProvider activeIdentityProvider = new IdentityProvider(); + IdentityProvider activeIdentityProvider = new IdentityProvider<>(); activeIdentityProvider.setType(SAML); activeIdentityProvider.setName("Active SAML Provider"); activeIdentityProvider.setActive(true); @@ -1407,9 +1405,9 @@ void samlRedirect_onlyOneProvider_noClientContext( jdbcIdentityProviderProvisioning.update(uaaIdentityProvider, uaaIdentityProvider.getIdentityZoneId()); mockMvc.perform(get("/login").accept(TEXT_HTML).with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/saml/discovery?returnIDParam=idp&entityID=" + identityZone.getSubdomain() + ".cloudfoundry-saml-login&idp=" + alias + "&isPassive=true")); + .andExpect(redirectedUrl("/saml2/authenticate/%s".formatted(alias))); IdentityZoneHolder.clear(); } @@ -1423,7 +1421,6 @@ void externalOauthRedirect_onlyOneProvider_noClientContext_and_ResponseType_Set( IdentityZoneCreationResult identityZoneCreationResult = MockMvcUtils.createOtherIdentityZoneAndReturnResult("puppy-" + new RandomValueStringGenerator().generate(), mockMvc, webApplicationContext, zoneAdminClient, false, IdentityZoneHolder.getCurrentZoneId()); IdentityZone identityZone = identityZoneCreationResult.getIdentityZone(); - String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); String oauthAlias = createOIDCProviderInZone(jdbcIdentityProviderProvisioning, identityZone, null); @@ -1433,20 +1430,20 @@ void externalOauthRedirect_onlyOneProvider_noClientContext_and_ResponseType_Set( jdbcIdentityProviderProvisioning.update(uaaIdentityProvider, uaaIdentityProvider.getIdentityZoneId()); MvcResult mvcResult = mockMvc.perform(get("/login").accept(TEXT_HTML) - .servletPath("/login") - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .servletPath("/login") + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andReturn(); String location = mvcResult.getResponse().getHeader("Location"); Map queryParams = UriComponentsBuilder.fromUriString(location).build().getQueryParams().toSingleValueMap(); - assertThat(location, startsWith("http://auth.url")); - assertThat(queryParams, hasEntry("client_id", "uaa")); - assertThat(queryParams, hasEntry("response_type", "code+id_token")); - assertThat(queryParams, hasEntry("redirect_uri", "http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias)); - assertThat(queryParams, hasEntry("scope", "openid+roles")); - assertThat(queryParams, hasKey("nonce")); + assertThat(location).startsWith("http://auth.url"); + assertThat(queryParams).containsEntry("client_id", "uaa") + .containsEntry("response_type", "code+id_token") + .containsEntry("redirect_uri", "http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias) + .containsEntry("scope", "openid+roles") + .containsKey("nonce"); IdentityZoneHolder.clear(); } @@ -1470,7 +1467,7 @@ void ExternalOAuthRedirectOnlyOneProviderWithDiscoveryUrl( definition.setAuthUrl(new URL(oidcAuthUrl)); return null; }).when(oidcMetadataFetcher) - .fetchMetadataAndUpdateDefinition(any(OIDCIdentityProviderDefinition.class)); + .fetchMetadataAndUpdateDefinition(any(OIDCIdentityProviderDefinition.class)); IdentityZoneHolder.set(identityZone); IdentityProvider uaaIdentityProvider = jdbcIdentityProviderProvisioning.retrieveByOriginIgnoreActiveFlag(UAA, identityZone.getId()); @@ -1478,20 +1475,20 @@ void ExternalOAuthRedirectOnlyOneProviderWithDiscoveryUrl( jdbcIdentityProviderProvisioning.update(uaaIdentityProvider, uaaIdentityProvider.getIdentityZoneId()); MvcResult mvcResult = mockMvc.perform(get("/login").accept(TEXT_HTML) - .servletPath("/login") - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .servletPath("/login") + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andReturn(); String location = mvcResult.getResponse().getHeader("Location"); Map queryParams = UriComponentsBuilder.fromUriString(location).build().getQueryParams().toSingleValueMap(); - assertThat(location, startsWith(oidcAuthUrl)); - assertThat(queryParams, hasEntry("client_id", "uaa")); - assertThat(queryParams, hasEntry("response_type", "code+id_token")); - assertThat(queryParams, hasEntry("redirect_uri", "http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias)); - assertThat(queryParams, hasEntry("scope", "openid+roles")); - assertThat(queryParams, hasKey("nonce")); + assertThat(location).startsWith(oidcAuthUrl); + assertThat(queryParams).containsEntry("client_id", "uaa") + .containsEntry("response_type", "code+id_token") + .containsEntry("redirect_uri", "http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias) + .containsEntry("scope", "openid+roles") + .containsKey("nonce"); IdentityZoneHolder.clear(); } @@ -1506,7 +1503,6 @@ void oauthRedirect_stateParameterPassedGetsReturned( IdentityZoneCreationResult identityZoneCreationResult = MockMvcUtils.createOtherIdentityZoneAndReturnResult("puppy-" + new RandomValueStringGenerator().generate(), mockMvc, webApplicationContext, zoneAdminClient, false, IdentityZoneHolder.getCurrentZoneId()); IdentityZone identityZone = identityZoneCreationResult.getIdentityZone(); - String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); String oauthAlias = createOIDCProviderInZone(jdbcIdentityProviderProvisioning, identityZone, null); @@ -1516,21 +1512,21 @@ void oauthRedirect_stateParameterPassedGetsReturned( jdbcIdentityProviderProvisioning.update(uaaIdentityProvider, uaaIdentityProvider.getIdentityZoneId()); MvcResult mvcResult = mockMvc.perform(get("/login").accept(TEXT_HTML) - .servletPath("/login") - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .servletPath("/login") + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andReturn(); String location = mvcResult.getResponse().getHeader("Location"); Map queryParams = UriComponentsBuilder.fromUriString(location).build().getQueryParams().toSingleValueMap(); - assertThat(location, startsWith("http://auth.url")); - assertThat(queryParams, hasEntry("client_id", "uaa")); - assertThat(queryParams, hasEntry("response_type", "code+id_token")); - assertThat(queryParams, hasEntry("redirect_uri", "http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias)); - assertThat(queryParams, hasEntry("scope", "openid+roles")); - assertThat(queryParams, hasKey("nonce")); - assertThat(queryParams, hasEntry(is("state"), not(isEmptyOrNullString()))); + assertThat(location).startsWith("http://auth.url"); + assertThat(queryParams).containsEntry("client_id", "uaa") + .containsEntry("response_type", "code+id_token") + .containsEntry("redirect_uri", "http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias) + .containsEntry("scope", "openid+roles") + .containsKey("nonce") + .extractingByKey("state").isNotNull(); IdentityZoneHolder.clear(); } @@ -1543,7 +1539,7 @@ void testLoginHintRedirect( UaaClientDetails zoneAdminClient = new UaaClientDetails(zoneAdminClientId, null, "openid", "client_credentials,authorization_code", "clients.admin,scim.read,scim.write", "http://test.redirect.com"); zoneAdminClient.setClientSecret("admin-secret"); - MockMvcUtils.IdentityZoneCreationResult identityZoneCreationResult = MockMvcUtils.createOtherIdentityZoneAndReturnResult("puppy-" + new RandomValueStringGenerator().generate(), mockMvc, webApplicationContext, zoneAdminClient, false, IdentityZoneHolder.getCurrentZoneId()); + IdentityZoneCreationResult identityZoneCreationResult = MockMvcUtils.createOtherIdentityZoneAndReturnResult("puppy-" + new RandomValueStringGenerator().generate(), mockMvc, webApplicationContext, zoneAdminClient, false, IdentityZoneHolder.getCurrentZoneId()); IdentityZone identityZone = identityZoneCreationResult.getIdentityZone(); OIDCIdentityProviderDefinition definition = new OIDCIdentityProviderDefinition(); @@ -1573,23 +1569,23 @@ void testLoginHintRedirect( MvcResult mvcResult = mockMvc.perform(get("/login") - .accept(TEXT_HTML) - .session(session) - .servletPath("/login") - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) - ) + .accept(TEXT_HTML) + .session(session) + .servletPath("/login") + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) + ) .andExpect(status().isFound()) .andReturn(); String location = mvcResult.getResponse().getHeader("Location"); Map queryParams = UriComponentsBuilder.fromUriString(location).build().getQueryParams().toSingleValueMap(); - assertThat(location, startsWith("http://auth.url")); - assertThat(queryParams, hasEntry("client_id", "uaa")); - assertThat(queryParams, hasEntry("response_type", "code")); - assertThat(queryParams, hasEntry("redirect_uri", "http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias)); - assertThat(queryParams, hasEntry("scope", "openid+roles")); - assertThat(queryParams, hasKey("nonce")); + assertThat(location).startsWith("http://auth.url"); + assertThat(queryParams).containsEntry("client_id", "uaa") + .containsEntry("response_type", "code") + .containsEntry("redirect_uri", "http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias) + .containsEntry("scope", "openid+roles") + .containsKey("nonce"); IdentityZoneHolder.clear(); } @@ -1612,7 +1608,7 @@ void noRedirect_ifProvidersOfDifferentTypesPresent( .setIdpEntityAlias(alias) .setLinkText("Active SAML Provider") .setZoneId(identityZone.getId()); - IdentityProvider activeIdentityProvider = new IdentityProvider(); + IdentityProvider activeIdentityProvider = new IdentityProvider<>(); activeIdentityProvider.setType(SAML); activeIdentityProvider.setName("Active SAML Provider"); activeIdentityProvider.setActive(true); @@ -1642,7 +1638,7 @@ void noRedirect_ifProvidersOfDifferentTypesPresent( jdbcIdentityProviderProvisioning.update(uaaIdentityProvider, uaaIdentityProvider.getIdentityZoneId()); mockMvc.perform(get("/login").accept(TEXT_HTML).with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()) .andExpect(view().name("login")); IdentityZoneHolder.clear(); @@ -1662,11 +1658,11 @@ void testNoCreateAccountLinksWhenUAAisNotAllowedProvider( IdentityZone identityZone = identityZoneCreationResult.getIdentityZone(); SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition3 = new SamlIdentityProviderDefinition() - .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.xmlWithoutID, "http://example3.com/saml/metadata")) + .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID, "http://example3.com/saml/metadata")) .setIdpEntityAlias(alias3) .setLinkText("Active3 SAML Provider") .setZoneId(identityZone.getId()); - IdentityProvider activeIdentityProvider3 = new IdentityProvider(); + IdentityProvider activeIdentityProvider3 = new IdentityProvider<>(); activeIdentityProvider3.setType(SAML); activeIdentityProvider3.setName("Active 3 SAML Provider"); activeIdentityProvider3.setActive(true); @@ -1675,11 +1671,11 @@ void testNoCreateAccountLinksWhenUAAisNotAllowedProvider( activeIdentityProvider3 = createIdentityProvider(jdbcIdentityProviderProvisioning, identityZone, activeIdentityProvider3); SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition2 = new SamlIdentityProviderDefinition() - .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.xmlWithoutID, "http://example2.com/saml/metadata")) + .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID, "http://example2.com/saml/metadata")) .setIdpEntityAlias(alias2) .setLinkText("Active2 SAML Provider") .setZoneId(identityZone.getId()); - IdentityProvider activeIdentityProvider2 = new IdentityProvider(); + IdentityProvider activeIdentityProvider2 = new IdentityProvider<>(); activeIdentityProvider2.setType(SAML); activeIdentityProvider2.setName("Active 2 SAML Provider"); activeIdentityProvider2.setActive(true); @@ -1738,8 +1734,8 @@ public Map getParameterMap() { SessionUtils.setSavedRequestSession(session, savedRequest); mockMvc.perform(get("/login").accept(TEXT_HTML).with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) - .session(session) - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) + .session(session) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()) .andExpect(xpath("//a[text()='Create account']").doesNotExist()) .andExpect(xpath("//a[text()='Reset password']").doesNotExist()); @@ -1764,7 +1760,7 @@ void testDeactivatedProviderIsRemovedFromSamlLoginLinks( .setLinkText("SAML Provider") .setShowSamlLink(true) .setZoneId(identityZone.getId()); - IdentityProvider identityProvider = new IdentityProvider(); + IdentityProvider identityProvider = new IdentityProvider<>(); identityProvider.setType(SAML); identityProvider.setName("SAML Provider"); identityProvider.setActive(true); @@ -2156,8 +2152,8 @@ void testXhrCorsPreflight_ForNonDefaultZone_WhenZoneSpecificCorsPolicyIsNull(@Au httpHeaders.add("Access-Control-Request-Method", "GET"); httpHeaders.add("Origin", "testzone1.localhost"); mockMvc.perform(options("/logout.do") - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) - .headers(httpHeaders)) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) + .headers(httpHeaders)) .andExpect(status().isOk()); } @@ -2168,7 +2164,7 @@ void testXhrCorsPreflight_ForNonDefaultZone_WhenZoneSpecificCorsPolicyIsNull(@Au */ @Test void testXhrCorsPreflight_ForNonDefaultZone_WhenZoneSpecificCorsPolicyExists(@Autowired CorsFilter corsFilter) throws Exception { - // setting the default zone CORS policy to not allow POST + // setting the default zone CORS policy to not allow POST corsFilter.setCorsXhrAllowedMethods(List.of(GET.toString(), OPTIONS.toString())); corsFilter.initialize(); @@ -2183,8 +2179,8 @@ void testXhrCorsPreflight_ForNonDefaultZone_WhenZoneSpecificCorsPolicyExists(@Au httpHeaders.add("Access-Control-Request-Method", "POST"); httpHeaders.add("Origin", "testzone1.localhost"); mockMvc.perform(options("/logout.do") - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) - .headers(httpHeaders)) + .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) + .headers(httpHeaders)) .andExpect(status().isOk()); } @@ -2195,10 +2191,10 @@ void login_LockoutPolicySucceeds_ForDefaultZone( ScimUser userToLockout = createUser(scimUserProvisioning, generator, IdentityZone.getUaaZoneId()); attemptUnsuccessfulLogin(mockMvc, 5, userToLockout.getUserName(), ""); mockMvc.perform(post("/uaa/login.do") - .contextPath("/uaa") - .with(cookieCsrf()) - .param("username", userToLockout.getUserName()) - .param("password", userToLockout.getPassword())) + .contextPath("/uaa") + .with(cookieCsrf()) + .param("username", userToLockout.getUserName()) + .param("password", userToLockout.getPassword())) .andExpect(redirectedUrl("/uaa/login?error=account_locked")) .andExpect(emptyCurrentUserCookie()); } @@ -2218,11 +2214,11 @@ void login_LockoutPolicySucceeds_WhenPolicyIsUpdatedByApi( attemptUnsuccessfulLogin(mockMvc, 2, userToLockout.getUserName(), subdomain); mockMvc.perform(post("/uaa/login.do") - .contextPath("/uaa") - .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) - .with(cookieCsrf()) - .param("username", userToLockout.getUserName()) - .param("password", userToLockout.getPassword())) + .contextPath("/uaa") + .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) + .with(cookieCsrf()) + .param("username", userToLockout.getUserName()) + .param("password", userToLockout.getPassword())) .andExpect(redirectedUrl("/uaa/login?error=account_locked")) .andExpect(emptyCurrentUserCookie()); } @@ -2240,15 +2236,15 @@ void autologin_with_validCode_RedirectsToSavedRequest_ifPresent( request.setUsername("marissa"); request.setPassword("koala"); mockMvc.perform(post("/autologin") - .header("Authorization", "Basic " + new String(Base64.encode("admin:adminsecret".getBytes()))) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(request))) + .header("Authorization", "Basic " + new String(ENCODER.encode("admin:adminsecret".getBytes()))) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(request))) .andExpect(status().isOk()); mockMvc.perform(get("/autologin") - .session(session) - .param("code", "test" + generator.counter.get()) - .param("client_id", "admin")) + .session(session) + .param("code", "test" + generator.counter.get()) + .param("client_id", "admin")) .andExpect(redirectedUrl("http://test/redirect/oauth/authorize")); } @@ -2263,14 +2259,14 @@ void autologin_with_validCode_RedirectsToHome( request.setUsername("marissa"); request.setPassword("koala"); mockMvc.perform(post("/autologin") - .header("Authorization", "Basic " + new String(Base64.encode("admin:adminsecret".getBytes()))) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(request))) + .header("Authorization", "Basic " + new String(ENCODER.encode("admin:adminsecret".getBytes()))) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(request))) .andExpect(status().isOk()); mockMvc.perform(get("/autologin") - .param("code", "test" + generator.counter.get()) - .param("client_id", "admin")) + .param("code", "test" + generator.counter.get()) + .param("client_id", "admin")) .andExpect(redirectedUrl("home")); } @@ -2282,8 +2278,8 @@ void idpDiscoveryPageDisplayed_IfFlagIsEnabled( config.setIdpDiscoveryEnabled(true); IdentityZone zone = setupZone(webApplicationContext, mockMvc, identityZoneProvisioning, generator, config); mockMvc.perform(get("/login") - .header("Accept", TEXT_HTML) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .header("Accept", TEXT_HTML) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()) .andExpect(view().name("idp_discovery/email")) .andExpect(content().string(containsString("Sign in"))) @@ -2301,8 +2297,8 @@ void idpDiscoveryPageNotDisplayed_IfFlagIsEnabledAndDiscoveryUnsuccessfulPreviou IdentityZone zone = setupZone(webApplicationContext, mockMvc, identityZoneProvisioning, generator, config); mockMvc.perform(get("/login?discoveryPerformed=true") - .header("Accept", TEXT_HTML) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .header("Accept", TEXT_HTML) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()) .andExpect(view().name("idp_discovery/password")); } @@ -2327,9 +2323,9 @@ void idpDiscoveryClientNameDisplayed_WithUTF8Characters( SessionUtils.setSavedRequestSession(session, savedRequest); mockMvc.perform(get("/login") - .session(session) - .header("Accept", TEXT_HTML) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .session(session) + .header("Accept", TEXT_HTML) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()) .andExpect(view().name("idp_discovery/email")) .andExpect(content().string(containsString("Sign in to continue to " + clientName))) @@ -2360,9 +2356,9 @@ void accountChooserEnabled_NoSaveAccounts( savedAccount.setUserId("1234-5678"); savedAccount.setUsername("test@example.org"); mockMvc.perform(get("/login") - .session(session) - .header("Accept", TEXT_HTML) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .session(session) + .header("Accept", TEXT_HTML) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()) .andExpect(view().name("idp_discovery/email")); } @@ -2390,10 +2386,10 @@ void accountChooserEnabled( savedAccount.setUserId("1234-5678"); savedAccount.setUsername("test@example.org"); mockMvc.perform(get("/login") - .session(session) - .cookie(new Cookie("Saved-Account-12345678", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount)))) - .header("Accept", TEXT_HTML) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .session(session) + .cookie(new Cookie("Saved-Account-12345678", URLEncoder.encode(JsonUtils.writeValueAsString(savedAccount)))) + .header("Accept", TEXT_HTML) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andDo(print()) .andExpect(status().isOk()) .andExpect(view().name("idp_discovery/account_chooser")); @@ -2411,9 +2407,9 @@ void accountChooserWithoutDiscovery( MockHttpSession session = new MockHttpSession(); mockMvc.perform(get("/login") - .session(session) - .header("Accept", TEXT_HTML) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .session(session) + .header("Accept", TEXT_HTML) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andDo(print()) .andExpect(status().isOk()) .andExpect(view().name("idp_discovery/origin")); @@ -2430,23 +2426,23 @@ void accountChooserWithoutDiscovery_loginWithProvidedLoginHint( IdentityZone zone = setupZone(webApplicationContext, mockMvc, identityZoneProvisioning, generator, config); String originKey = createOIDCProvider(jdbcIdentityProviderProvisioning, generator, zone, "id_token code"); - String loginHint = "%7B%22origin%22%3A%22"+originKey+"%22%7D"; + String loginHint = "%7B%22origin%22%3A%22" + originKey + "%22%7D"; MvcResult mvcResult = mockMvc.perform(post("/origin-chooser") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .servletPath("/origin-chooser") - .param("login_hint", originKey) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .servletPath("/origin-chooser") + .param("login_hint", originKey) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andReturn(); String location = mvcResult.getResponse().getHeader("Location"); Map queryParams = UriComponentsBuilder.fromUriString(location).build().getQueryParams().toSingleValueMap(); - assertThat(location, startsWith("/login")); - assertThat(queryParams, hasEntry("login_hint", loginHint)); - assertThat(queryParams, hasEntry("discoveryPerformed", "true")); + assertThat(location).startsWith("/login"); + assertThat(queryParams).containsEntry("login_hint", loginHint) + .containsEntry("discoveryPerformed", "true"); } @Test @@ -2462,19 +2458,20 @@ void accountChooserWithoutDiscovery_noDefaultReturnsLoginPage( createOIDCProvider(jdbcIdentityProviderProvisioning, generator, zone, "id_token code"); MvcResult mvcResult = mockMvc.perform(post("/origin-chooser") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .servletPath("/origin-chooser") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .servletPath("/origin-chooser") + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andReturn(); String location = mvcResult.getResponse().getHeader("Location"); Map queryParams = UriComponentsBuilder.fromUriString(location).build().getQueryParams().toSingleValueMap(); - assertThat(location, startsWith("/login")); - assertThat(queryParams, not(hasKey("login_hint"))); - assertThat(queryParams, hasEntry("discoveryPerformed", "true")); + assertThat(location).startsWith("/login"); + assertThat(queryParams) + .containsEntry("discoveryPerformed", "true") + .doesNotContainKey("login_hint"); } @Test @@ -2489,7 +2486,7 @@ void emailPageIdpDiscoveryEnabled_SelfServiceLinksDisabled( MockMvcUtils.setSelfServiceLinksEnabled(webApplicationContext, IdentityZone.getUaaZoneId(), false); mockMvc.perform(MockMvcRequestBuilders.get("/login") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(xpath("//div[@class='action']//a").doesNotExist()); } @@ -2505,13 +2502,13 @@ void idpDiscoveryRedirectsToSamlExternalProvider_withClientContext( MockHttpSession session = setUpClientAndProviderForIdpDiscovery(webApplicationContext, jdbcIdentityProviderProvisioning, generator, originKey, zone); mockMvc.perform(post("/login/idp_discovery") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .session(session) - .param("email", "marissa@test.org") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .session(session) + .param("email", "marissa@test.org") + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) - .andExpect(redirectedUrl("/saml/discovery?returnIDParam=idp&entityID=" + zone.getSubdomain() + ".cloudfoundry-saml-login&idp=" + originKey + "&isPassive=true")); + .andExpect(redirectedUrl("/saml2/authenticate/%s".formatted(originKey))); } @Test @@ -2525,22 +2522,22 @@ void idpDiscoveryRedirectsToOIDCProvider( String originKey = createOIDCProvider(jdbcIdentityProviderProvisioning, generator, zone, "id_token code"); MvcResult mvcResult = mockMvc.perform(post("/login/idp_discovery") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .servletPath("/login/idp_discovery") - .param("email", "marissa@test.org") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .servletPath("/login/idp_discovery") + .param("email", "marissa@test.org") + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andReturn(); String location = mvcResult.getResponse().getHeader("Location"); Map queryParams = UriComponentsBuilder.fromUriString(location).build().getQueryParams().toSingleValueMap(); - assertThat(location, startsWith("http://myauthurl.com")); - assertThat(queryParams, hasEntry("client_id", "id")); - assertThat(queryParams, hasEntry("response_type", "id_token+code")); - assertThat(queryParams, hasEntry("redirect_uri", "http%3A%2F%2F" + subdomain + ".localhost%2Flogin%2Fcallback%2F" + originKey)); - assertThat(queryParams, hasKey("nonce")); + assertThat(location).startsWith("http://myauthurl.com"); + assertThat(queryParams).containsEntry("client_id", "id") + .containsEntry("response_type", "id_token+code") + .containsEntry("redirect_uri", "http%3A%2F%2F" + subdomain + ".localhost%2Flogin%2Fcallback%2F" + originKey) + .containsKey("nonce"); } @Test @@ -2555,9 +2552,9 @@ void multiple_oidc_providers_use_response_type_in_url( createOIDCProvider(jdbcIdentityProviderProvisioning, generator, zone, "code id_token"); mockMvc.perform(get("/login") - .header("Accept", TEXT_HTML) - .servletPath("/login") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .header("Accept", TEXT_HTML) + .servletPath("/login") + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isOk()) .andExpect(content().string(containsString("http://myauthurl.com?client_id=id&response_type=code&"))) .andExpect(content().string(containsString("http://myauthurl.com?client_id=id&response_type=code+id_token&"))); @@ -2581,11 +2578,11 @@ void idpDiscoveryWithNoEmailDomainMatch_withClientContext( MockHttpSession session = setUpClientAndProviderForIdpDiscovery(webApplicationContext, jdbcIdentityProviderProvisioning, generator, originKey, zone); mockMvc.perform(post("/login/idp_discovery") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .session(session) - .param("email", "marissa@other.domain") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .session(session) + .param("email", "marissa@other.domain") + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?discoveryPerformed=true&email=marissa%40other.domain")); } @@ -2608,11 +2605,11 @@ void idpDiscoveryWithMultipleEmailDomainMatches_withClientContext( MockHttpSession session = setUpClientAndProviderForIdpDiscovery(webApplicationContext, jdbcIdentityProviderProvisioning, generator, originKey, zone); mockMvc.perform(post("/login/idp_discovery") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .session(session) - .param("email", "marissa@test.org") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .session(session) + .param("email", "marissa@test.org") + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?discoveryPerformed=true&email=marissa%40test.org")); } @@ -2630,19 +2627,19 @@ void idpDiscoveryWithUaaFallBack_withClientContext( MockHttpSession session = setUpClientAndProviderForIdpDiscovery(webApplicationContext, jdbcIdentityProviderProvisioning, generator, originKey, zone); mockMvc.perform(post("/login/idp_discovery") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .session(session) - .param("email", "marissa@other.domain") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .session(session) + .param("email", "marissa@other.domain") + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?discoveryPerformed=true&email=marissa%40other.domain")); mockMvc.perform(get("/login?discoveryPerformed=true&email=marissa%40other.domain") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .session(session) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .session(session) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(model().attributeExists("zone_name")) .andExpect(view().name("login")); } @@ -2666,11 +2663,11 @@ void idpDiscoveryWithLdap_withClientContext( MockHttpSession session = setUpClientAndProviderForIdpDiscovery(webApplicationContext, jdbcIdentityProviderProvisioning, generator, originKey, zone); mockMvc.perform(post("/login/idp_discovery") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .session(session) - .param("email", "marissa@testLdap.org") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .session(session) + .param("email", "marissa@testLdap.org") + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?discoveryPerformed=true&email=marissa%40testLdap.org")); } @@ -2683,17 +2680,17 @@ void passwordPageDisplayed_ifUaaIsFallbackIDPForEmailDomain( config.setIdpDiscoveryEnabled(true); IdentityZone zone = setupZone(webApplicationContext, mockMvc, identityZoneProvisioning, generator, config); mockMvc.perform(post("/login/idp_discovery") - .header("Accept", TEXT_HTML) - .with(cookieCsrf()) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost")) - .param("email", "marissa@koala.com")) + .header("Accept", TEXT_HTML) + .with(cookieCsrf()) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost")) + .param("email", "marissa@koala.com")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?discoveryPerformed=true&email=marissa%40koala.com")); mockMvc.perform(get("/login?discoveryPerformed=true&email=marissa@koala.com") - .with(cookieCsrf()) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost")) - .header("Accept", TEXT_HTML)) + .with(cookieCsrf()) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost")) + .header("Accept", TEXT_HTML)) .andExpect(view().name("idp_discovery/password")) .andExpect(xpath("//input[@name='password']").exists()) .andExpect(xpath("//input[@name='username']/@value").string("marissa@koala.com")) @@ -2706,15 +2703,15 @@ void passwordPageIdpDiscoveryEnabled_SelfServiceLinksDisabled() throws Exception MockMvcUtils.setSelfServiceLinksEnabled(webApplicationContext, IdentityZone.getUaaZoneId(), false); mockMvc.perform(post("/login/idp_discovery") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML) - .param("email", "marissa@koala.org")) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML) + .param("email", "marissa@koala.org")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?discoveryPerformed=true&email=marissa%40koala.org")); mockMvc.perform(get("/login?discoveryPerformed=true&email=marissa%40koala.org") - .with(cookieCsrf()) - .header("Accept", TEXT_HTML)) + .with(cookieCsrf()) + .header("Accept", TEXT_HTML)) .andExpect(status().isOk()) .andExpect(xpath("//div[@class='action pull-right']//a").doesNotExist()); } @@ -2727,15 +2724,15 @@ void userNamePresentInPasswordPage( config.setIdpDiscoveryEnabled(true); IdentityZone zone = setupZone(webApplicationContext, mockMvc, identityZoneProvisioning, generator, config); mockMvc.perform(post("/login/idp_discovery") - .with(cookieCsrf()) - .param("email", "test@email.com") - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .param("email", "test@email.com") + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?discoveryPerformed=true&email=test%40email.com")); mockMvc.perform(get("/login?discoveryPerformed=true&email=test@email.com") - .with(cookieCsrf()) - .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) + .with(cookieCsrf()) + .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))) .andExpect(xpath("//input[@name='username']/@value").string("test@email.com")); } @@ -2794,9 +2791,9 @@ void authorizeForClientWithIdpNotAllowed( String extractPattern = "logout.do\\?redirect\\=(.*?)\">click here<"; Pattern pattern = Pattern.compile(extractPattern); Matcher matcher = pattern.matcher(html); - assertTrue(matcher.find()); + assertThat(matcher.find()).isTrue(); String group = matcher.group(1); - assertEquals(expectedUrl, URLDecoder.decode(group, StandardCharsets.UTF_8)); + assertThat(URLDecoder.decode(group, StandardCharsets.UTF_8)).isEqualTo(expectedUrl); } private static MockHttpSession setUpClientAndProviderForIdpDiscovery( @@ -2807,11 +2804,11 @@ private static MockHttpSession setUpClientAndProviderForIdpDiscovery( IdentityZone zone) { String metadata = String.format(MockMvcUtils.IDP_META_DATA, new AlphanumericRandomValueStringGenerator().generate()); SamlIdentityProviderDefinition config = (SamlIdentityProviderDefinition) new SamlIdentityProviderDefinition() - .setMetaDataLocation(metadata) - .setIdpEntityAlias(originKey) - .setLinkText("Active SAML Provider") - .setZoneId(zone.getId()) - .setEmailDomain(Collections.singletonList("test.org")); + .setMetaDataLocation(metadata) + .setIdpEntityAlias(originKey) + .setLinkText("Active SAML Provider") + .setZoneId(zone.getId()) + .setEmailDomain(Collections.singletonList("test.org")); IdentityProvider identityProvider = MultitenancyFixture.identityProvider(originKey, zone.getId()); identityProvider.setType(SAML); @@ -2854,34 +2851,34 @@ class ErrorAndSuccessMessages { @Test void hasValidError() throws Exception { mockMvc.perform( - get("/login?error=login_failure")) + get("/login?error=login_failure")) .andExpect(content().string(containsString("Provided credentials are invalid. Please try again."))); } @Test void hasInvalidError() throws Exception { mockMvc.perform( - get("/login?error=foobar&error=login_failure")) + get("/login?error=foobar&error=login_failure")) .andExpect(content().string(containsString("Error!"))); } @Test void hasValidSuccess() throws Exception { mockMvc.perform( - get("/login?success=verify_success")) + get("/login?success=verify_success")) .andExpect(content().string(containsString("Verification successful. Login to access your account."))); } @Test void hasInvalidSuccess() throws Exception { mockMvc.perform( - get("/login?success=foobar&success=verify_success")) + get("/login?success=foobar&success=verify_success")) .andExpect(content().string(containsString("Success!"))); } } private static void attemptUnsuccessfulLogin(MockMvc mockMvc, int numberOfAttempts, String username, String subdomain) throws Exception { - String requestDomain = subdomain.equals("") ? "localhost" : subdomain + ".localhost"; + String requestDomain = subdomain.isEmpty() ? "localhost" : subdomain + ".localhost"; MockHttpServletRequestBuilder post = post("/uaa/login.do") .with(new SetServerNameRequestPostProcessor(requestDomain)) .with(cookieCsrf()) @@ -3036,13 +3033,8 @@ private static void setZoneFavIconAndProductLogo(WebApplicationContext webApplic MockMvcUtils.setZoneConfiguration(webApplicationContext, IdentityZone.getUaaZoneId(), identityZoneConfiguration); } - private static final String defaultCopyrightTemplate = "Copyright " + "\u00a9" + " %s"; - private static final String cfCopyrightText = String.format(defaultCopyrightTemplate, "CloudFoundry.org Foundation, Inc."); - private static final String CF_LAST_LOGIN = "Last Login"; - private static IdentityProvider createIdentityProvider(JdbcIdentityProviderProvisioning jdbcIdentityProviderProvisioning, IdentityZone identityZone, IdentityProvider activeIdentityProvider) { activeIdentityProvider.setIdentityZoneId(identityZone.getId()); return jdbcIdentityProviderProvisioning.create(activeIdentityProvider, identityZone.getId()); } - } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java index 119d73c8997..68e1b0f21af 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java @@ -8,12 +8,11 @@ import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.RemoteUserAuthentication; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2Authentication; -import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,18 +21,14 @@ import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import org.springframework.security.providers.ExpiringUsernameAuthenticationToken; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.GenericFilterBean; @@ -42,17 +37,15 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; +import java.io.Serial; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -61,7 +54,6 @@ @DefaultTestContext class PasscodeMockMvcTests { - private static String USERNAME = "marissa"; private CaptureSecurityContextFilter captureSecurityContextFilter; private UaaPrincipal marissa; @@ -78,19 +70,17 @@ void setUp(@Autowired WebApplicationContext webApplicationContext, @Autowired Mo List chains = filterChainProxy.getFilterChains(); for (SecurityFilterChain chain : chains) { - if (chain instanceof DefaultSecurityFilterChain) { - DefaultSecurityFilterChain dfc = (DefaultSecurityFilterChain) chain; - if (dfc.getRequestMatcher() instanceof UaaRequestMatcher) { - UaaRequestMatcher matcher = (UaaRequestMatcher) dfc.getRequestMatcher(); - if (matcher.toString().contains("passcodeTokenMatcher")) { - dfc.getFilters().add(captureSecurityContextFilter); - break; - } - } + if (chain instanceof DefaultSecurityFilterChain dfc + && dfc.getRequestMatcher() instanceof UaaRequestMatcher matcher + && matcher.toString().contains("passcodeTokenMatcher")) { + dfc.getFilters().add(captureSecurityContextFilter); + break; } + + } UaaUserDatabase db = webApplicationContext.getBean(UaaUserDatabase.class); - marissa = new UaaPrincipal(db.retrieveUserByName(USERNAME, OriginKeys.UAA)); + marissa = new UaaPrincipal(db.retrieveUserByName("marissa", OriginKeys.UAA)); webApplicationContext.getBean(JdbcExpiringCodeStore.class).setGenerator(new RandomValueStringGenerator(32)); } } @@ -102,23 +92,17 @@ void clearSecContext() { @Test void testLoginUsingPasscodeWithSamlToken() throws Exception { - ExpiringUsernameAuthenticationToken et = new ExpiringUsernameAuthenticationToken(USERNAME, null); - UaaAuthentication auth = new LoginSamlAuthenticationToken(marissa, et).getUaaAuthentication( - Collections.emptyList(), - Collections.emptySet(), - new LinkedMultiValueMap<>() - ); - final MockSecurityContext mockSecurityContext = new MockSecurityContext(auth); + UaaAuthenticationDetails details = new UaaAuthenticationDetails(new MockHttpServletRequest()); + UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList<>(), details); + final MockSecurityContext mockSecurityContext = new MockSecurityContext(uaaAuthentication); SecurityContextHolder.setContext(mockSecurityContext); MockHttpSession session = new MockHttpSession(); - session.setAttribute( HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, mockSecurityContext ); - MockHttpServletRequestBuilder get = get("/passcode") .accept(APPLICATION_JSON) .session(session); @@ -145,32 +129,28 @@ void testLoginUsingPasscodeWithSamlToken() throws Exception { .param("passcode", passcode) .param("response_type", "token"); - - Map accessToken = - JsonUtils.readValue( + Map accessToken = + JsonUtils.readValueAsMap( mockMvc.perform(post) .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(), - Map.class); - assertEquals("bearer", accessToken.get("token_type")); - assertNotNull(accessToken.get("access_token")); - assertNotNull(accessToken.get("refresh_token")); + .andReturn().getResponse().getContentAsString()); + assertThat(accessToken).containsEntry("token_type", "bearer") + .containsKey("access_token") + .containsKey("refresh_token"); String[] scopes = ((String) accessToken.get("scope")).split(" "); - assertThat(Arrays.asList(scopes), containsInAnyOrder("uaa.user", "scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read")); + assertThat(Arrays.asList(scopes)).contains("uaa.user", "scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read"); Authentication authentication = captureSecurityContextFilter.getAuthentication(); - assertNotNull(authentication); - assertTrue(authentication instanceof OAuth2Authentication); - assertTrue(((OAuth2Authentication) authentication).getUserAuthentication() instanceof UsernamePasswordAuthenticationToken); - assertTrue(authentication.getPrincipal() instanceof UaaPrincipal); - assertEquals(marissa.getOrigin(), ((UaaPrincipal) authentication.getPrincipal()).getOrigin()); + assertThat(authentication).isInstanceOf(OAuth2Authentication.class); + assertThat(((OAuth2Authentication) authentication).getUserAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class); + assertThat(authentication.getPrincipal()).isInstanceOf(UaaPrincipal.class); + assertThat(((UaaPrincipal) authentication.getPrincipal()).getOrigin()).isEqualTo(marissa.getOrigin()); } @Test void testLoginUsingPasscodeWithUaaToken() throws Exception { UaaAuthenticationDetails details = new UaaAuthenticationDetails(new MockHttpServletRequest()); - UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList(), details); - + UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList<>(), details); final MockSecurityContext mockSecurityContext = new MockSecurityContext(uaaAuthentication); SecurityContextHolder.setContext(mockSecurityContext); @@ -181,7 +161,6 @@ void testLoginUsingPasscodeWithUaaToken() throws Exception { mockSecurityContext ); - MockHttpServletRequestBuilder get = get("/passcode") .accept(APPLICATION_JSON) .session(session); @@ -208,25 +187,22 @@ void testLoginUsingPasscodeWithUaaToken() throws Exception { .param("passcode", passcode) .param("response_type", "token"); - - Map accessToken = - JsonUtils.readValue( + Map accessToken = + JsonUtils.readValueAsMap( mockMvc.perform(post) .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(), - Map.class); - assertEquals("bearer", accessToken.get("token_type")); - assertNotNull(accessToken.get("access_token")); - assertNotNull(accessToken.get("refresh_token")); + .andReturn().getResponse().getContentAsString()); + assertThat(accessToken).containsEntry("token_type", "bearer") + .containsKey("access_token") + .containsKey("refresh_token"); String[] scopes = ((String) accessToken.get("scope")).split(" "); - assertThat(Arrays.asList(scopes), containsInAnyOrder("uaa.user", "scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read")); + assertThat(Arrays.asList(scopes)).contains("uaa.user", "scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read"); Authentication authentication = captureSecurityContextFilter.getAuthentication(); - assertNotNull(authentication); - assertTrue(authentication instanceof OAuth2Authentication); - assertTrue(((OAuth2Authentication) authentication).getUserAuthentication() instanceof UsernamePasswordAuthenticationToken); - assertTrue(authentication.getPrincipal() instanceof UaaPrincipal); - assertEquals(marissa.getOrigin(), ((UaaPrincipal) authentication.getPrincipal()).getOrigin()); + assertThat(authentication).isInstanceOf(OAuth2Authentication.class); + assertThat(((OAuth2Authentication) authentication).getUserAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class); + assertThat(authentication.getPrincipal()).isInstanceOf(UaaPrincipal.class); + assertThat(((UaaPrincipal) authentication.getPrincipal()).getOrigin()).isEqualTo(marissa.getOrigin()); } @Test @@ -235,7 +211,7 @@ void testLoginUsingPasscodeWithUnknownToken() throws Exception { marissa.getId(), marissa.getName(), marissa.getEmail(), - new ArrayList() + new ArrayList<>() ); final MockSecurityContext mockSecurityContext = new MockSecurityContext(userAuthentication); @@ -247,7 +223,6 @@ void testLoginUsingPasscodeWithUnknownToken() throws Exception { mockSecurityContext ); - MockHttpServletRequestBuilder get = get("/passcode") .accept(APPLICATION_JSON) .session(session); @@ -259,8 +234,7 @@ void testLoginUsingPasscodeWithUnknownToken() throws Exception { @Test void testLoginUsingOldPasscode() throws Exception { UaaAuthenticationDetails details = new UaaAuthenticationDetails(new MockHttpServletRequest()); - UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList(), details); - + UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList<>(), details); final MockSecurityContext mockSecurityContext = new MockSecurityContext(uaaAuthentication); SecurityContextHolder.setContext(mockSecurityContext); @@ -320,7 +294,7 @@ void loginUsingInvalidPasscode() throws Exception { String content = mockMvc.perform(post) .andExpect(status().isUnauthorized()) .andReturn().getResponse().getContentAsString(); - assertThat(content, Matchers.containsString("Invalid passcode")); + assertThat(content).contains("Invalid passcode"); } @Test @@ -337,38 +311,40 @@ void loginUsingNoPasscode() throws Exception { String content = mockMvc.perform(post) .andExpect(status().isUnauthorized()) .andReturn().getResponse().getContentAsString(); - assertThat(content, Matchers.containsString("Passcode information is missing.")); + assertThat(content).contains("Passcode information is missing."); } - // NOTE: This test is flaky but passes on retry @Test - void testPasscodeReturnSpecialCharaters() throws Exception { + void testPasscodeReturnSpecialCharacters() throws Exception { + // NOTE: This test is flaky but passes on retry UaaAuthenticationDetails details = new UaaAuthenticationDetails(new MockHttpServletRequest()); - UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList(), details); - + UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList<>(), details); MockSecurityContext mockSecurityContext = new MockSecurityContext(uaaAuthentication); SecurityContextHolder.setContext(mockSecurityContext); MockHttpSession session = new MockHttpSession(); - session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, mockSecurityContext); - int MAX_TRAILS = 30; - String passcode = ""; - for(int i = 0; i < MAX_TRAILS; i++) { + + // try for up to 15 seconds to get a passcode with - or _ + await().atMost(15, SECONDS).untilAsserted(() -> { MockHttpServletRequestBuilder get = get("/passcode").accept(APPLICATION_JSON).session(session); - passcode = JsonUtils.readValue(mockMvc.perform(get).andExpect(status().isOk()).andReturn().getResponse().getContentAsString(), - String.class); - if (passcode.contains("-") || passcode.contains("_")) { - return; - } - } - assertThat("Passcode information is missing - or _", passcode, - Matchers.containsString("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_")); + String passcode = JsonUtils.readValue(mockMvc.perform(get) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(), + String.class); + + assertThat(passcode).as("Passcode information is missing - or _") + .containsAnyOf("-", "_") + .matches("[1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\\-_]*"); + }); } public static class MockSecurityContext implements SecurityContext { + @Serial private static final long serialVersionUID = -1386535243513362694L; private Authentication authentication; @@ -402,5 +378,4 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha chain.doFilter(request, response); } } - } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java index 1ae67b5b61e..2f31254b16a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java @@ -1,11 +1,35 @@ package org.cloudfoundry.identity.uaa.login; -import java.net.URI; -import java.util.Collections; - +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; +import org.cloudfoundry.identity.uaa.mock.token.AbstractTokenMockMvcTests; +import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.common.OAuth2RefreshToken; +import org.cloudfoundry.identity.uaa.oauth.jwt.JwtClientAuthentication; +import org.cloudfoundry.identity.uaa.oauth.pkce.PkceValidationService; +import org.cloudfoundry.identity.uaa.oauth.token.CompositeToken; +import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.TestOpenSamlObjects; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.test.JUnitRestDocumentationExtension; +import org.cloudfoundry.identity.uaa.test.SnippetUtils; +import org.cloudfoundry.identity.uaa.test.TestClient; +import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; +import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.opensaml.saml.saml2.core.NameID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -26,35 +50,20 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import org.apache.commons.codec.binary.Base64; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.mock.token.AbstractTokenMockMvcTests; -import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.oauth.jwt.JwtClientAuthentication; -import org.cloudfoundry.identity.uaa.oauth.pkce.PkceValidationService; -import org.cloudfoundry.identity.uaa.oauth.token.CompositeToken; -import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils; -import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.test.JUnitRestDocumentationExtension; -import org.cloudfoundry.identity.uaa.test.SnippetUtils; -import org.cloudfoundry.identity.uaa.test.TestClient; -import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.user.UaaAuthority; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.opensaml.saml2.core.NameID; +import java.net.URI; +import java.security.Security; +import java.util.Base64; +import java.util.Collections; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.MockSecurityContext; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getClientCredentialsOAuthAccessToken; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getUserOAuthAccessToken; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.GRANT_TYPE; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.REDIRECT_URI; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.RESPONSE_TYPE; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.SCOPE; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.STATE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_CLIENT_CREDENTIALS; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_PASSWORD; @@ -64,6 +73,9 @@ import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.REQUEST_TOKEN_FORMAT; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat.JWT; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat.OPAQUE; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyCertificate; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyKey; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyPassphrase; import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils.createLocalSamlIdpDefinition; import static org.cloudfoundry.identity.uaa.test.SnippetUtils.parameterWithName; import static org.hamcrest.Matchers.containsString; @@ -86,12 +98,6 @@ import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.templates.TemplateFormats.markdown; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.GRANT_TYPE; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.REDIRECT_URI; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.RESPONSE_TYPE; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.SCOPE; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.STATE; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -99,6 +105,7 @@ @ExtendWith(JUnitRestDocumentationExtension.class) class TokenEndpointDocs extends AbstractTokenMockMvcTests { + private static final Base64.Encoder ENCODER = Base64.getEncoder(); private final ParameterDescriptor grantTypeParameter = parameterWithName(GRANT_TYPE).required().type(STRING).description("OAuth 2 grant type"); @@ -126,7 +133,7 @@ class TokenEndpointDocs extends AbstractTokenMockMvcTests { private final SnippetUtils.ConstrainableHeader authorizationHeader = SnippetUtils.headerWithName("Authorization"); - private Snippet listTokenResponseFields = responseFields( + private final Snippet listTokenResponseFields = responseFields( fieldWithPath("[].zoneId").type(STRING).description("The zone ID for the token"), fieldWithPath("[].tokenId").type(STRING).description("The unique ID for the token"), fieldWithPath("[].clientId").type(STRING).description("Client ID for this token, will always match the client_id claim in the access token used for this call"), @@ -148,6 +155,18 @@ class TokenEndpointDocs extends AbstractTokenMockMvcTests { @Autowired FilterChainProxy springSecurityFilterChain; + @BeforeAll + static void beforeAll() { + Security.addProvider(new BouncyCastleFipsProvider()); + } + + @BeforeEach + void beforeEach() { + IdentityZone.getUaa().getConfig().getSamlConfig().setPrivateKey(legacyKey()); + IdentityZone.getUaa().getConfig().getSamlConfig().setPrivateKeyPassword(legacyPassphrase()); + IdentityZone.getUaa().getConfig().getSamlConfig().setCertificate(legacyCertificate()); + } + @BeforeEach void setUpContext(ManualRestDocumentation manualRestDocumentation) { mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) @@ -198,7 +217,7 @@ void getTokenUsingAuthCodeGrant() throws Exception { UriComponents location = UriComponentsBuilder.fromUri(URI.create(authCodeResponse.getHeader("Location"))).build(); String code = location.getQueryParams().getFirst("code"); - String clientAuthBase64 = new String(org.springframework.security.crypto.codec.Base64.encode(("login:loginsecret".getBytes()))); + String clientAuthBase64 = new String(ENCODER.encode(("login:loginsecret".getBytes()))); Snippet headerFields = requestHeaders(CLIENT_BASIC_AUTH_HEADER); MockHttpServletRequestBuilder postForToken = post("/oauth/token") @@ -281,7 +300,7 @@ void getTokenUsingClientCredentialGrant() throws Exception { @Test void getTokenUsingClientCredentialGrantWithAuthorizationHeader() throws Exception { - String clientAuthorization = new String(Base64.encodeBase64("login:loginsecret".getBytes())); + String clientAuthorization = new String(ENCODER.encode("login:loginsecret".getBytes())); MockHttpServletRequestBuilder postForToken = post("/oauth/token") .accept(APPLICATION_JSON) .contentType(APPLICATION_FORM_URLENCODED) @@ -396,137 +415,39 @@ void getTokenUsingUserTokenGrant() throws Exception { @Test void getTokenUsingSaml2BearerGrant() throws Exception { - SamlTestUtils samlTestUtils = new SamlTestUtils(); - samlTestUtils.initializeSimple(); - final String subdomain = "68uexx"; - //all our SAML defaults use :8080/uaa/ so we have to use that here too - final String host = subdomain + ".localhost"; - final String fullPath = "/uaa/oauth/token/alias/" + subdomain + ".cloudfoundry-saml-login"; - final String origin = subdomain + ".cloudfoundry-saml-login"; - - MockMvcUtils.IdentityZoneCreationResult zone = MockMvcUtils.createOtherIdentityZoneAndReturnResult(subdomain, mockMvc, this.webApplicationContext, null, IdentityZoneHolder.getCurrentZoneId()); - - //Mock an IDP metadata - String idpMetadata = "\n" + - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MNO5mOgijKliauTLhxL1pqT15s4=\n" + - " \n" + - " \n" + - " \n" + - " CwxB189hOth7P4g+jswYiG1XHyy0a8Pci6LahimDi0sSuWF5ui1Dw8MSamNDfi2GC5QGArrupPdxgX5F8BFFuio3XkmcQqRhsC01R2u1/NhpabGTgczrk1LYMpCaIOitaXRM2cEkqrmf/s6S3zXDQkQJTcJefc/0NrYgFN6Pisc=\n" + - " \n" + - " \n" + - " \n" + - " MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - " YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - " BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - " MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - " ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - " HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - " gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - " 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - " xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - " GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - " MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - " EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - " MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - " 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - " ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - " YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - " BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - " MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - " ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - " HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - " gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - " 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - " xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - " GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - " MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - " EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - " MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - " 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - " ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - " YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - " BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - " MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - " ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - " HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - " gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - " 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - " xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - " GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - " MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - " EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - " MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - " 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - " ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\n" + - " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n" + - " \n" + - " \n" + - " \n" + - ""; + // all our SAML defaults use `:8080/uaa/` so we have to use that here too + final String host = "%s.localhost".formatted(subdomain); + final String fullPath = "/uaa/oauth/token/alias/%s.integration-saml-entity-id".formatted(subdomain); + final String origin = "%s.integration-saml-entity-id".formatted(subdomain); + MockMvcUtils.IdentityZoneCreationResult testZone = MockMvcUtils.createOtherIdentityZoneAndReturnResult( + subdomain, mockMvc, this.webApplicationContext, null, + IdentityZoneHolder.getCurrentZoneId()); //create an IDP in the default zone - SamlIdentityProviderDefinition idpDef = createLocalSamlIdpDefinition(origin, zone.getIdentityZone().getId(), idpMetadata); - IdentityProvider provider = new IdentityProvider(); + String idpMetadata = getIdpMetadata(host, origin); + SamlIdentityProviderDefinition idpDef = createLocalSamlIdpDefinition( + origin, testZone.getIdentityZone().getId(), idpMetadata); + IdentityProvider provider = new IdentityProvider<>(); provider.setConfig(idpDef); provider.setActive(true); - provider.setIdentityZoneId(zone.getIdentityZone().getId()); + provider.setIdentityZoneId(testZone.getIdentityZone().getId()); provider.setName(origin); provider.setOriginKey(origin); - IdentityZoneHolder.set(zone.getIdentityZone()); - identityProviderProvisioning.create(provider, zone.getIdentityZone().getId()); + IdentityZoneHolder.set(testZone.getIdentityZone()); + identityProviderProvisioning.create(provider, testZone.getIdentityZone().getId()); IdentityZoneHolder.clear(); - String assertion = samlTestUtils.mockAssertionEncoded( - origin, - NameID.UNSPECIFIED, - "Saml2BearerIntegrationUser", - "http://" + host + ":8080/uaa/oauth/token/alias/" + origin, - origin); + String spEndpoint = "http://%s:8080/uaa/oauth/token/alias/%s".formatted(host, origin); + String assertionStr = TestOpenSamlObjects.getEncodedAssertion("68uexx.cloudfoundry-saml-login", NameID.UNSPECIFIED, + "Saml2BearerIntegrationUser", spEndpoint, origin, true); - //create client in default zone + // create a client in the default zone String clientId = "testclient" + generator.generate(); - setUpClients(clientId, "uaa.none", "uaa.user,openid", GRANT_TYPE_SAML2_BEARER + ",password,refresh_token", true, TEST_REDIRECT_URI, null, 600, zone.getIdentityZone()); + setUpClients(clientId, "uaa.none", "uaa.user,openid", + GRANT_TYPE_SAML2_BEARER + ",password,refresh_token", true, + TEST_REDIRECT_URI, null, 600, testZone.getIdentityZone()); MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post(fullPath) .with(request -> { @@ -544,7 +465,7 @@ void getTokenUsingSaml2BearerGrant() throws Exception { .param("client_secret", "secret") .param("client_assertion", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjU4ZDU1YzUwMGNjNmI1ODM3OTYxN2UwNmU3ZGVjNmNhIn0.eyJzdWIiOiJsb2dpbiIsImlzcyI6ImxvZ2luIiwianRpIjoiNThkNTVjNTAwY2M2YjU4Mzc5NjE3ZTA2ZTdhZmZlZSIsImV4cCI6MTIzNDU2NzgsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4ifQ.jwWw0OKZecd4ZjtwQ_ievqBVrh2SieqMF6vY74Oo5H6v-Ibcmumq96NLNtoUEwaAEQQOHb8MWcC8Gwi9dVQdCrtpomC86b_LKkihRBSKuqpw0udL9RMH5kgtC04ctsN0yZNifUWMP85VHn97Ual5eZ2miaBFob3H5jUe98CcBj1TSRehr64qBFYuwt9vD19q6U-ONhRt0RXBPB7ayHAOMYtb1LFIzGAiKvqWEy9f-TBPXSsETjKkAtSuM-WVWi4EhACMtSvI6iJN15f7qlverRSkGIdh1j2vPXpKKBJoRhoLw6YqbgcUC9vAr17wfa_POxaRHvh9JPty0ZXLA4XPtA") .param("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") - .param("assertion", assertion) + .param("assertion", assertionStr) .param("scope", "openid"); final ParameterDescriptor assertionFormatParameter = parameterWithName("assertion").required().type(STRING).description("An XML based SAML 2.0 bearer assertion, which is Base64URl encoded."); @@ -574,10 +495,110 @@ void getTokenUsingSaml2BearerGrant() throws Exception { .andExpect(jsonPath("$.scope").value("openid")); } + private static String getIdpMetadata(String host, String origin) { + //Mock an IDP metadata: %1$s is the host; %2$s is the origin + return """ + + + + + + + + + + + + + MNO5mOgijKliauTLhxL1pqT15s4= + + + + CwxB189hOth7P4g+jswYiG1XHyy0a8Pci6LahimDi0sSuWF5ui1Dw8MSamNDfi2GC5QGArrupPdxgX5F8BFFuio3XkmcQqRhsC01R2u1/NhpabGTgczrk1LYMpCaIOitaXRM2cEkqrmf/s6S3zXDQkQJTcJefc/0NrYgFN6Pisc= + + + + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF + YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM + BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2 + MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE + ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx + HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB + gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR + 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY + xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy + GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3 + MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL + EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA + MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am + 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o + ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= + + + + + + + + + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF + YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM + BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2 + MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE + ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx + HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB + gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR + 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY + xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy + GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3 + MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL + EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA + MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am + 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o + ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= + + + + + + + + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF + YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM + BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2 + MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE + ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx + HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB + gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR + 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY + xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy + GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3 + MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL + EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA + MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am + 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o + ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + + + """.formatted(host, origin); + } + @Test void getTokenWithClientAuthInHeader() throws Exception { - String clientAuthorization = new String(Base64.encodeBase64("app:appclientsecret".getBytes())); + String clientAuthorization = new String(ENCODER.encode("app:appclientsecret".getBytes())); MockHttpServletRequestBuilder postForToken = post("/oauth/token") .accept(APPLICATION_JSON) .header("Authorization", "Basic " + clientAuthorization) @@ -633,7 +654,7 @@ void getTokenUsingPasscode() throws Exception { .andReturn().getResponse().getContentAsString(), String.class); - String clientAuthorization = new String(Base64.encodeBase64("app:appclientsecret".getBytes())); + String clientAuthorization = new String(ENCODER.encode("app:appclientsecret".getBytes())); MockHttpServletRequestBuilder postForToken = post("/oauth/token") .accept(APPLICATION_JSON) @@ -832,13 +853,13 @@ void revokeAllTokens_forAUser() throws Exception { mockMvc.perform(get - .header("Authorization", "Bearer " + adminToken)) + .header("Authorization", "Bearer " + adminToken)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters)); mockMvc.perform( - get("/oauth/clients") - .header("Authorization", "Bearer " + userInfoToken)) + get("/oauth/clients") + .header("Authorization", "Bearer " + userInfoToken)) .andExpect(status().isUnauthorized()) .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); } @@ -889,26 +910,26 @@ void revokeAllTokens_forAUserClientCombination() throws Exception { ); mockMvc.perform( - get("/userinfo") - .header("Authorization", "Bearer " + userInfoTokenToRevoke)) + get("/userinfo") + .header("Authorization", "Bearer " + userInfoTokenToRevoke)) .andExpect(status().isOk()); MockHttpServletRequestBuilder get = RestDocumentationRequestBuilders.get("/oauth/token/revoke/user/{userId}/client/{clientId}", user.getId(), client.getClientId()); mockMvc.perform(get - .header("Authorization", "Bearer " + adminToken)) + .header("Authorization", "Bearer " + adminToken)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters)); mockMvc.perform( - get("/userinfo") - .header("Authorization", "Bearer " + userInfoTokenToRevoke)) + get("/userinfo") + .header("Authorization", "Bearer " + userInfoTokenToRevoke)) .andExpect(status().isUnauthorized()) .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); mockMvc.perform( - get("/userinfo") - .header("Authorization", "Bearer " + userInfoTokenToRemainValid)) + get("/userinfo") + .header("Authorization", "Bearer " + userInfoTokenToRemainValid)) .andExpect(status().isOk()); } @@ -940,13 +961,13 @@ void revokeAllTokens_forAClient() throws Exception { Snippet pathParameters = pathParameters(parameterWithName("clientId").description("The id of the client")); MockHttpServletRequestBuilder get = RestDocumentationRequestBuilders.get("/oauth/token/revoke/client/{clientId}", client.getClientId()); mockMvc.perform(get - .header("Authorization", "Bearer " + adminToken)) + .header("Authorization", "Bearer " + adminToken)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters)); mockMvc.perform( - get("/oauth/clients") - .header("Authorization", "Bearer " + readClientsToken)) + get("/oauth/clients") + .header("Authorization", "Bearer " + readClientsToken)) .andExpect(status().isUnauthorized()) .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); } @@ -994,7 +1015,7 @@ void revokeSingleToken() throws Exception { MockHttpServletRequestBuilder delete = RestDocumentationRequestBuilders.delete("/oauth/token/revoke/{tokenId}", userInfoToken); mockMvc.perform(delete - .header(HttpHeaders.AUTHORIZATION, "Bearer " + userInfoToken)) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + userInfoToken)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters)); } @@ -1032,9 +1053,9 @@ void listTokens_client() throws Exception { MockHttpServletRequestBuilder get = RestDocumentationRequestBuilders.get("/oauth/token/list/client/{clientId}", client.getClientId()); mockMvc.perform( - get - .header(HttpHeaders.AUTHORIZATION, "Bearer " + clientToken) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) + get + .header(HttpHeaders.AUTHORIZATION, "Bearer " + clientToken) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters, listTokenResponseFields)); } @@ -1083,9 +1104,9 @@ void listTokens_user() throws Exception { MockHttpServletRequestBuilder get = RestDocumentationRequestBuilders.get("/oauth/token/list/user/{userId}", user.getId()); mockMvc.perform( - get - .header(HttpHeaders.AUTHORIZATION, "Bearer " + clientToken) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) + get + .header(HttpHeaders.AUTHORIZATION, "Bearer " + clientToken) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters, listTokenResponseFields)); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/config/HealthzShouldNotBeProtectedMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/config/HealthzShouldNotBeProtectedMockMvcTests.java index c1c80c12c61..d27910cbffd 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/config/HealthzShouldNotBeProtectedMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/config/HealthzShouldNotBeProtectedMockMvcTests.java @@ -19,6 +19,7 @@ import java.util.stream.Stream; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -31,7 +32,7 @@ class HealthzShouldNotBeProtectedMockMvcTests { private MockMvc mockMvc; @BeforeEach - void setUp( + void beforeEach( @Autowired SecurityFilterChainPostProcessor securityFilterChainPostProcessor, @Autowired MockMvc mockMvc ) { @@ -41,7 +42,7 @@ void setUp( } @AfterEach - void tearDown() { + void afterEach() { chainPostProcessor.setRequireHttps(originalRequireHttps); } @@ -62,29 +63,10 @@ public Stream provideArguments(ExtensionContext context) { class WithHttpsRequired { @BeforeEach - void setUp() { + void beforeEach() { chainPostProcessor.setRequireHttps(true); } - @DefaultTestContext - @Nested - class WithHttpPortSetToNonDefaultValue { - @BeforeEach - void setUp() { - chainPostProcessor.setHttpsPort(9998); - } - - @Test - void redirectedRequestsGoToTheConfiguredPort() throws Exception { - MockHttpServletRequestBuilder getRequest = get("/login") - .accept(MediaType.TEXT_HTML); - - mockMvc.perform(getRequest) - .andExpect(status().is3xxRedirection()) - .andExpect(header().string("Location", "https://localhost:9998/login")); - } - } - @ParameterizedTest @ArgumentsSource(HealthzGetRequestParams.class) void healthzIsNotRejected(MockHttpServletRequestBuilder getRequest) throws Exception { @@ -112,6 +94,25 @@ void samlMetadataRedirects() throws Exception { .andExpect(status().is3xxRedirection()) .andExpect(header().string("Location", "https://localhost/saml/metadata")); } + + @DefaultTestContext + @Nested + class WithHttpPortSetToNonDefaultValue { + @BeforeEach + void setUp() { + chainPostProcessor.setHttpsPort(9998); + } + + @Test + void redirectedRequestsGoToTheConfiguredPort() throws Exception { + MockHttpServletRequestBuilder getRequest = get("/login") + .accept(MediaType.TEXT_HTML); + + mockMvc.perform(getRequest) + .andExpect(status().is3xxRedirection()) + .andExpect(header().string("Location", "https://localhost:9998/login")); + } + } } @DefaultTestContext @@ -119,7 +120,7 @@ void samlMetadataRedirects() throws Exception { class WithHttpsNotRequired { @BeforeEach - void setUp() { + void beforeEach() { chainPostProcessor.setRequireHttps(false); } @@ -146,6 +147,7 @@ void samlMetadataReturnsOk() throws Exception { .accept(MediaType.ALL); mockMvc.perform(getRequest) + .andDo(print()) .andExpect(status().isOk()); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointDocs.java index 4d308d1d042..0a09aa48842 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointDocs.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointDocs.java @@ -1205,7 +1205,7 @@ private IdentityProvider getSamlProvider(String originKey) { identityProvider.setType(SAML); SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition() - .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.xmlWithoutID, "http://www.okta.com/" + identityProvider.getOriginKey())) + .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID, "http://www.okta.com/" + identityProvider.getOriginKey())) .setIdpEntityAlias(identityProvider.getOriginKey()) .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java index 99963df126a..850757d61cf 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java @@ -22,7 +22,18 @@ import org.cloudfoundry.identity.uaa.impl.config.IdentityProviderBootstrap; import org.cloudfoundry.identity.uaa.login.Prompt; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.provider.*; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; +import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderStatus; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.ldap.DynamicPasswordComparator; import org.cloudfoundry.identity.uaa.provider.saml.BootstrapSamlIdentityProviderDataTests; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -43,7 +54,6 @@ import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; @@ -53,15 +63,24 @@ import java.net.MalformedURLException; import java.net.URL; -import java.util.*; - +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_NAME; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.*; import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; // TODO: Check to see if the helper methods can be moved to MockMvcUtils @@ -111,25 +130,21 @@ void clearUaaConfig() { MockMvcUtils.removeEventListener(webApplicationContext, eventListener); } - // TODO: Do something with these try... catches @Test void test_delete_through_event() throws Exception { String accessToken = setUpAccessToken(); IdentityProvider idp = createAndUpdateIdentityProvider(accessToken); String origin = idp.getOriginKey(); IdentityProviderBootstrap bootstrap = webApplicationContext.getBean(IdentityProviderBootstrap.class); - assertNotNull(identityProviderProvisioning.retrieveByOrigin(origin, IdentityZone.getUaaZoneId())); + assertThat(identityProviderProvisioning.retrieveByOrigin(origin, IdentityZone.getUaaZoneId())).isNotNull(); try { bootstrap.setOriginsToDelete(Collections.singletonList(origin)); bootstrap.onApplicationEvent(new ContextRefreshedEvent(webApplicationContext)); } finally { bootstrap.setOriginsToDelete(null); } - try { - identityProviderProvisioning.retrieveByOrigin(origin, IdentityZone.getUaaZoneId()); - fail("Identity provider should have been deleted"); - } catch (EmptyResultDataAccessException ignored) { - } + assertThatThrownBy(() -> identityProviderProvisioning.retrieveByOrigin(origin, IdentityZone.getUaaZoneId())) + .isInstanceOf(EmptyResultDataAccessException.class); } @Test @@ -141,31 +156,31 @@ void testCreateAndUpdateIdentityProvider() throws Exception { @Test void testCreateAndUpdateIdentityProviderWithMissingConfig() throws Exception { String accessToken = setUpAccessToken(); - IdentityProvider identityProvider = MultitenancyFixture.identityProvider("testnoconfig", IdentityZone.getUaaZoneId()); + IdentityProvider identityProvider = MultitenancyFixture.identityProvider("testnoconfig", IdentityZone.getUaaZoneId()); HashMap identityProviderFields = JsonUtils.convertValue(identityProvider, HashMap.class); identityProviderFields.remove("config"); MvcResult create = mockMvc.perform(post("/identity-providers/") - .header("Authorization", "Bearer " + accessToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityProviderFields))) + .header("Authorization", "Bearer " + accessToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityProviderFields))) .andExpect(status().isCreated()) .andReturn(); identityProvider = JsonUtils.readValue(create.getResponse().getContentAsString(), IdentityProvider.class); mockMvc.perform(put("/identity-providers/" + identityProvider.getId()) - .header("Authorization", "Bearer " + accessToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityProviderFields))) + .header("Authorization", "Bearer " + accessToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityProviderFields))) .andExpect(status().isOk()); } @Test void test_Create_and_Delete_SamlProvider() throws Exception { String origin = "idp-mock-saml-" + new RandomValueStringGenerator().generate(); - String metadata = String.format(BootstrapSamlIdentityProviderDataTests.xmlWithoutID, "http://localhost:9999/metadata/" + origin); + String metadata = String.format(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID, "http://localhost:9999/metadata/" + origin); String accessToken = setUpAccessToken(); IdentityProvider provider = new IdentityProvider<>(); provider.setActive(true); @@ -183,18 +198,17 @@ void test_Create_and_Delete_SamlProvider() throws Exception { attributeMappings.put("given_name", "first_name"); samlDefinition.setExternalGroupsWhitelist(externalGroupsWhitelist); samlDefinition.setAttributeMappings(attributeMappings); - provider.setConfig(samlDefinition); IdentityProvider created = createIdentityProvider(null, provider, accessToken, status().isCreated()); - assertNotNull(created.getConfig()); + assertThat(created.getConfig()).isNotNull(); createIdentityProvider(null, created, accessToken, status().isConflict()); SamlIdentityProviderDefinition samlCreated = created.getConfig(); - assertEquals(Arrays.asList("test.com", "test2.com"), samlCreated.getEmailDomain()); - assertEquals(externalGroupsWhitelist, samlCreated.getExternalGroupsWhitelist()); - assertEquals(attributeMappings, samlCreated.getAttributeMappings()); - assertEquals(IdentityZone.getUaaZoneId(), samlCreated.getZoneId()); - assertEquals(provider.getOriginKey(), samlCreated.getIdpEntityAlias()); + assertThat(samlCreated.getEmailDomain()).isEqualTo(Arrays.asList("test.com", "test2.com")); + assertThat(samlCreated.getExternalGroupsWhitelist()).isEqualTo(externalGroupsWhitelist); + assertThat(samlCreated.getAttributeMappings()).isEqualTo(attributeMappings); + assertThat(samlCreated.getZoneId()).isEqualTo(IdentityZone.getUaaZoneId()); + assertThat(samlCreated.getIdpEntityAlias()).isEqualTo(provider.getOriginKey()); //no access token mockMvc.perform( @@ -252,8 +266,8 @@ void test_delete_response_not_containing_relying_party_secret() throws Exception MvcResult result = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn(); IdentityProvider returnedIdentityProvider = JsonUtils.readValue( result.getResponse().getContentAsString(), IdentityProvider.class); - assertNull(((AbstractExternalOAuthIdentityProviderDefinition)returnedIdentityProvider.getConfig()) - .getRelyingPartySecret()); + assertThat(((AbstractExternalOAuthIdentityProviderDefinition) returnedIdentityProvider.getConfig()) + .getRelyingPartySecret()).isNull(); } @Test @@ -321,8 +335,8 @@ void test_delete_response_not_containing_bind_password() throws Exception { IdentityProvider returnedIdentityProvider = JsonUtils.readValue( deleteResult.getResponse().getContentAsString(), IdentityProvider.class); - assertNull(((LdapIdentityProviderDefinition)returnedIdentityProvider. - getConfig()).getBindPassword()); + assertThat(((LdapIdentityProviderDefinition) returnedIdentityProvider. + getConfig()).getBindPassword()).isNull(); } } @@ -340,14 +354,14 @@ void testRetrieveOnlyActiveIdps() throws Exception { void testCreateIdentityProviderWithInsufficientScopes() throws Exception { IdentityProvider identityProvider = MultitenancyFixture.identityProvider("testorigin", IdentityZone.getUaaZoneId()); createIdentityProvider(null, identityProvider, lowPrivilegeToken, status().isForbidden()); - assertEquals(0, eventListener.getEventCount()); + assertThat(eventListener.getEventCount()).isZero(); } @Test void testUpdateIdentityProviderWithInsufficientScopes() throws Exception { IdentityProvider identityProvider = MultitenancyFixture.identityProvider("testorigin", IdentityZone.getUaaZoneId()); updateIdentityProvider(null, identityProvider, lowPrivilegeToken, status().isForbidden()); - assertEquals(0, eventListener.getEventCount()); + assertThat(eventListener.getEventCount()).isZero(); } @Test @@ -359,7 +373,7 @@ void testUpdateUaaIdentityProviderDoesUpdateOfPasswordPolicy() throws Exception String accessToken = setUpAccessToken(); updateIdentityProvider(null, identityProvider, accessToken, status().isOk()); IdentityProvider modifiedIdentityProvider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaaZoneId()); - assertEquals(newConfig, ((UaaIdentityProviderDefinition) modifiedIdentityProvider.getConfig()).getPasswordPolicy()); + assertThat(((UaaIdentityProviderDefinition) modifiedIdentityProvider.getConfig()).getPasswordPolicy()).isEqualTo(newConfig); } @Test @@ -372,7 +386,7 @@ void testUpdateUaaIdentityProviderDoesUpdateOfPasswordPolicyWithPasswordNewerTha String accessToken = setUpAccessToken(); updateIdentityProvider(null, identityProvider, accessToken, status().isOk()); IdentityProvider modifiedIdentityProvider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaaZoneId()); - assertEquals(newConfig, ((UaaIdentityProviderDefinition) modifiedIdentityProvider.getConfig()).getPasswordPolicy()); + assertThat(((UaaIdentityProviderDefinition) modifiedIdentityProvider.getConfig()).getPasswordPolicy()).isEqualTo(newConfig); } @Test @@ -403,13 +417,12 @@ void testCreateAndUpdateIdentityProviderInOtherZone() throws Exception { eventListener.clearEvents(); IdentityProvider createdIDP = createIdentityProvider(zone.getId(), identityProvider, userAccessToken, status().isCreated()); - - assertNotNull(createdIDP.getId()); - assertEquals(identityProvider.getName(), createdIDP.getName()); - assertEquals(identityProvider.getOriginKey(), createdIDP.getOriginKey()); - assertEquals(1, eventListener.getEventCount()); + assertThat(createdIDP.getId()).isNotNull(); + assertThat(createdIDP.getName()).isEqualTo(identityProvider.getName()); + assertThat(createdIDP.getOriginKey()).isEqualTo(identityProvider.getOriginKey()); + assertThat(eventListener.getEventCount()).isOne(); IdentityProviderModifiedEvent event = eventListener.getLatestEvent(); - assertEquals(AuditEventType.IdentityProviderCreatedEvent, event.getAuditEvent().getType()); + assertThat(event.getAuditEvent().getType()).isEqualTo(AuditEventType.IdentityProviderCreatedEvent); } @Test @@ -423,12 +436,11 @@ void test_Create_Duplicate_Saml_Identity_Provider_In_Other_Zone() throws Excepti String userAccessToken = MockMvcUtils.getUserOAuthAccessTokenAuthCode(mockMvc, "identity", "identitysecret", user.getId(), user.getUserName(), "secr3T", "zones." + zone.getId() + ".idps.write", IdentityZone.getUaaZoneId()); eventListener.clearEvents(); - IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, zone.getId()); identityProvider.setType(OriginKeys.SAML); SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition() - .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.xmlWithoutID, "http://www.okta.com/" + identityProvider.getOriginKey())) + .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID, "http://www.okta.com/" + identityProvider.getOriginKey())) .setIdpEntityAlias(identityProvider.getOriginKey()) .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) @@ -437,11 +449,11 @@ void test_Create_Duplicate_Saml_Identity_Provider_In_Other_Zone() throws Excepti IdentityProvider createdIDP = createIdentityProvider(zone.getId(), identityProvider, userAccessToken, status().isCreated()); - assertNotNull(createdIDP.getId()); - assertEquals(identityProvider.getName(), createdIDP.getName()); - assertEquals(identityProvider.getOriginKey(), createdIDP.getOriginKey()); - assertEquals(identityProvider.getConfig().getIdpEntityAlias(), createdIDP.getConfig().getIdpEntityAlias()); - assertEquals(identityProvider.getConfig().getZoneId(), createdIDP.getConfig().getZoneId()); + assertThat(createdIDP.getId()).isNotNull(); + assertThat(createdIDP.getName()).isEqualTo(identityProvider.getName()); + assertThat(createdIDP.getOriginKey()).isEqualTo(identityProvider.getOriginKey()); + assertThat(createdIDP.getConfig().getIdpEntityAlias()).isEqualTo(identityProvider.getConfig().getIdpEntityAlias()); + assertThat(createdIDP.getConfig().getZoneId()).isEqualTo(identityProvider.getConfig().getZoneId()); identityProvider.setOriginKey(origin2); providerDefinition = new SamlIdentityProviderDefinition() @@ -460,15 +472,13 @@ void test_Create_Duplicate_Saml_Identity_Provider_In_Default_Zone() throws Excep String origin1 = "IDPEndpointsMockTests3-" + new RandomValueStringGenerator().generate(); String origin2 = "IDPEndpointsMockTests4-" + new RandomValueStringGenerator().generate(); String userAccessToken = setUpAccessToken(); - eventListener.clearEvents(); - IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, IdentityZone.getUaaZoneId()); identityProvider.setType(OriginKeys.SAML); SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition() - .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.xmlWithoutID, "http://www.okta.com/" + identityProvider.getOriginKey())) + .setMetaDataLocation(String.format(BootstrapSamlIdentityProviderDataTests.XML_WITHOUT_ID, "http://www.okta.com/" + identityProvider.getOriginKey())) .setIdpEntityAlias(identityProvider.getOriginKey()) .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) @@ -477,9 +487,9 @@ void test_Create_Duplicate_Saml_Identity_Provider_In_Default_Zone() throws Excep IdentityProvider createdIDP = createIdentityProvider(null, identityProvider, userAccessToken, status().isCreated()); - assertNotNull(createdIDP.getId()); - assertEquals(identityProvider.getName(), createdIDP.getName()); - assertEquals(identityProvider.getOriginKey(), createdIDP.getOriginKey()); + assertThat(createdIDP.getId()).isNotNull(); + assertThat(createdIDP.getName()).isEqualTo(identityProvider.getName()); + assertThat(createdIDP.getOriginKey()).isEqualTo(identityProvider.getOriginKey()); identityProvider.setOriginKey(origin2); providerDefinition = new SamlIdentityProviderDefinition() @@ -503,9 +513,9 @@ void testReadIdentityProviderInOtherZone_Using_Zones_Token() throws Exception { eventListener.clearEvents(); IdentityProvider createdIDP = createIdentityProvider(zone.getId(), identityProvider, userAccessToken, status().isCreated()); - assertNotNull(createdIDP.getId()); - assertEquals(identityProvider.getName(), createdIDP.getName()); - assertEquals(identityProvider.getOriginKey(), createdIDP.getOriginKey()); + assertThat(createdIDP.getId()).isNotNull(); + assertThat(createdIDP.getName()).isEqualTo(identityProvider.getName()); + assertThat(createdIDP.getOriginKey()).isEqualTo(identityProvider.getOriginKey()); addScopeToIdentityClient("zones.*.idps.read"); user = MockMvcUtils.createAdminForZone(mockMvc, adminToken, "zones." + zone.getId() + ".idps.read", IdentityZone.getUaaZoneId()); @@ -518,7 +528,7 @@ void testReadIdentityProviderInOtherZone_Using_Zones_Token() throws Exception { MvcResult result = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn(); IdentityProvider retrieved = JsonUtils.readValue(result.getResponse().getContentAsString(), IdentityProvider.class); - assertEquals(createdIDP, retrieved); + assertThat(retrieved).isEqualTo(createdIDP); } @Test @@ -539,10 +549,11 @@ void testListIdpsInZone() throws Exception { .contentType(APPLICATION_JSON); MvcResult result = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn(); - List identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() { + List identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { }); - assertEquals(numberOfIdps + 1, identityProviderList.size()); - assertTrue(identityProviderList.contains(newIdp)); + assertThat(identityProviderList) + .hasSize(numberOfIdps + 1) + .contains(newIdp); } @Test @@ -559,10 +570,10 @@ void testListIdpsInOtherZoneFromDefaultZone() throws Exception { requestBuilder.header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId()); MvcResult result = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn(); - List identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() { + List identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { }); - assertTrue(identityProviderList.contains(otherZoneIdp)); - assertEquals(2, identityProviderList.size()); + assertThat(identityProviderList).contains(otherZoneIdp) + .hasSize(2); } @Test @@ -582,7 +593,7 @@ void testRetrieveIdpInZone() throws Exception { MvcResult result = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn(); IdentityProvider retrieved = JsonUtils.readValue(result.getResponse().getContentAsString(), IdentityProvider.class); - assertEquals(newIdp, retrieved); + assertThat(retrieved).isEqualTo(newIdp); } @Test @@ -608,7 +619,6 @@ void testListIdpsWithInsufficientScopes() { get("/identity-providers/") .header("Authorization", "Bearer" + lowPrivilegeToken) .contentType(APPLICATION_JSON); - } @Test @@ -635,15 +645,13 @@ void validateOauthProviderConfigDuringUpdate() throws Exception { ).andExpect(status().isCreated()).andReturn(); String response = mvcResult.getResponse().getContentAsString(); - assertThat(response, not(containsString("relyingPartySecret"))); - identityProvider = JsonUtils.readValue(response, new TypeReference>() { + assertThat(response).doesNotContain("relyingPartySecret"); + identityProvider = JsonUtils.readValue(response, new TypeReference<>() { }); - assertTrue(identityProvider.getConfig().isClientAuthInBody()); + assertThat(identityProvider.getConfig().isClientAuthInBody()).isTrue(); - assertTrue( - ((AbstractExternalOAuthIdentityProviderDefinition) webApplicationContext.getBean(JdbcIdentityProviderProvisioning.class).retrieve(identityProvider.getId(), identityProvider.getIdentityZoneId()).getConfig()) - .isClientAuthInBody() - ); + assertThat(((AbstractExternalOAuthIdentityProviderDefinition) webApplicationContext.getBean(JdbcIdentityProviderProvisioning.class).retrieve(identityProvider.getId(), identityProvider.getIdentityZoneId()).getConfig()) + .isClientAuthInBody()).isTrue(); identityProvider.getConfig().setClientAuthInBody(false); @@ -653,14 +661,12 @@ void validateOauthProviderConfigDuringUpdate() throws Exception { .contentType(APPLICATION_JSON) ).andExpect(status().isOk()).andReturn(); response = mvcResult.getResponse().getContentAsString(); - assertThat(response, not(containsString("relyingPartySecret"))); - identityProvider = JsonUtils.readValue(response, new TypeReference>() { + assertThat(response).doesNotContain("relyingPartySecret"); + identityProvider = JsonUtils.readValue(response, new TypeReference<>() { }); - assertFalse(identityProvider.getConfig().isClientAuthInBody()); - assertFalse( - ((AbstractExternalOAuthIdentityProviderDefinition) webApplicationContext.getBean(JdbcIdentityProviderProvisioning.class).retrieve(identityProvider.getId(), identityProvider.getIdentityZoneId()).getConfig()) - .isClientAuthInBody() - ); + assertThat(identityProvider.getConfig().isClientAuthInBody()).isFalse(); + assertThat(((AbstractExternalOAuthIdentityProviderDefinition) webApplicationContext.getBean(JdbcIdentityProviderProvisioning.class).retrieve(identityProvider.getId(), identityProvider.getIdentityZoneId()).getConfig()) + .isClientAuthInBody()).isFalse(); identityProvider.getConfig().setTokenUrl(null); @@ -687,7 +693,7 @@ void testUpdatePasswordPolicyWithPasswordNewerThan() throws Exception { ).andExpect(status().isOk()).andReturn(); IdentityProviderStatus updatedStatus = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), IdentityProviderStatus.class); - assertEquals(identityProviderStatus.getRequirePasswordChange(), updatedStatus.getRequirePasswordChange()); + assertThat(updatedStatus.getRequirePasswordChange()).isEqualTo(identityProviderStatus.getRequirePasswordChange()); } private IdentityProvider getOAuthProviderConfig() throws MalformedURLException { @@ -755,23 +761,24 @@ private void testRetrieveIdps(boolean retrieveActive) throws Exception { int numberOfIdps = identityProviderProvisioning.retrieveAll(retrieveActive, IdentityZone.getUaaZoneId()).size(); MvcResult result = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn(); - List identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() { + List identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { }); - assertEquals(numberOfIdps, identityProviderList.size()); - assertTrue(identityProviderList.contains(createdIDP)); + assertThat(identityProviderList).hasSize(numberOfIdps) + .contains(createdIDP); createdIDP.setActive(false); createdIDP = JsonUtils.readValue(updateIdentityProvider(null, createdIDP, accessToken, status().isOk()).getResponse().getContentAsString(), IdentityProvider.class); result = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn(); - identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() { + identityProviderList = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { }); if (!retrieveActive) { - assertEquals(numberOfIdps, identityProviderList.size()); - assertTrue(identityProviderList.contains(createdIDP)); + assertThat(identityProviderList).hasSize(numberOfIdps) + .contains(createdIDP); } else { - assertEquals(numberOfIdps - 1, identityProviderList.size()); - assertFalse(identityProviderList.contains(createdIDP)); + assertThat(identityProviderList) + .hasSize(numberOfIdps - 1) + .doesNotContain(createdIDP); } } @@ -780,37 +787,36 @@ private IdentityProvider createAndUpdateIdentityProvider(String accessToken) thr // create // check response IdentityProvider createdIDP = createIdentityProvider(null, identityProvider, accessToken, status().isCreated()); - assertNotNull(createdIDP.getId()); - assertEquals(identityProvider.getName(), createdIDP.getName()); - assertEquals(identityProvider.getOriginKey(), createdIDP.getOriginKey()); + assertThat(createdIDP.getId()).isNotNull(); + assertThat(createdIDP.getName()).isEqualTo(identityProvider.getName()); + assertThat(createdIDP.getOriginKey()).isEqualTo(identityProvider.getOriginKey()); // check audit - assertEquals(1, eventListener.getEventCount()); + assertThat(eventListener.getEventCount()).isOne(); IdentityProviderModifiedEvent event = eventListener.getLatestEvent(); - assertEquals(AuditEventType.IdentityProviderCreatedEvent, event.getAuditEvent().getType()); + assertThat(event.getAuditEvent().getType()).isEqualTo(AuditEventType.IdentityProviderCreatedEvent); // check db IdentityProvider persisted = identityProviderProvisioning.retrieve(createdIDP.getId(), createdIDP.getIdentityZoneId()); - assertNotNull(persisted.getId()); - assertEquals(identityProvider.getName(), persisted.getName()); - assertEquals(identityProvider.getOriginKey(), persisted.getOriginKey()); + assertThat(persisted.getId()).isNotNull(); + assertThat(persisted.getName()).isEqualTo(identityProvider.getName()); + assertThat(persisted.getOriginKey()).isEqualTo(identityProvider.getOriginKey()); // update -// String newConfig = RandomStringUtils.randomAlphanumeric(1024); createdIDP.setConfig(new UaaIdentityProviderDefinition(null, null)); updateIdentityProvider(null, createdIDP, accessToken, status().isOk()); // check db persisted = identityProviderProvisioning.retrieve(createdIDP.getId(), createdIDP.getIdentityZoneId()); - assertEquals(createdIDP.getId(), persisted.getId()); - assertEquals(createdIDP.getName(), persisted.getName()); - assertEquals(createdIDP.getOriginKey(), persisted.getOriginKey()); - assertEquals(createdIDP.getConfig(), persisted.getConfig()); + assertThat(persisted.getId()).isEqualTo(createdIDP.getId()); + assertThat(persisted.getName()).isEqualTo(createdIDP.getName()); + assertThat(persisted.getOriginKey()).isEqualTo(createdIDP.getOriginKey()); + assertThat(persisted.getConfig()).isEqualTo(createdIDP.getConfig()); // check audit - assertEquals(2, eventListener.getEventCount()); + assertThat(eventListener.getEventCount()).isEqualTo(2); event = eventListener.getLatestEvent(); - assertEquals(AuditEventType.IdentityProviderModifiedEvent, event.getAuditEvent().getType()); + assertThat(event.getAuditEvent().getType()).isEqualTo(AuditEventType.IdentityProviderModifiedEvent); return identityProvider; } @@ -827,7 +833,7 @@ private void addScopeToIdentityClient(String scope) { update = true; } if (update) { - assertEquals(1, template.update("UPDATE oauth_client_details SET scope=? WHERE identity_zone_id='uaa' AND client_id='identity'", scopes)); + assertThat(template.update("UPDATE oauth_client_details SET scope=? WHERE identity_zone_id='uaa' AND client_id='identity'", scopes)).isOne(); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlAuthenticationMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlAuthenticationMockMvcTests.java index adf18616cb3..bf22de3cc8f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlAuthenticationMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlAuthenticationMockMvcTests.java @@ -7,50 +7,77 @@ import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Configurator; import org.cloudfoundry.identity.uaa.DefaultTestContext; -import org.cloudfoundry.identity.uaa.audit.LoggingAuditService; -import org.cloudfoundry.identity.uaa.authentication.SamlResponseLoggerBinding; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.mock.util.InterceptingLogger; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; +import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.junit.jupiter.api.*; +import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.opensaml.saml.saml2.core.Response; import org.owasp.esapi.ESAPI; import org.owasp.esapi.reference.DefaultSecurityConfiguration; -import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.web.context.WebApplicationContext; +import org.xmlunit.assertj.XmlAssert; -import java.util.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; import java.util.function.Consumer; import static org.apache.logging.log4j.Level.DEBUG; import static org.apache.logging.log4j.Level.WARN; -import static org.cloudfoundry.identity.uaa.authentication.SamlResponseLoggerBinding.X_VCAP_REQUEST_ID_HEADER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.authentication.MalformedSamlResponseLogger.X_VCAP_REQUEST_ID_HEADER; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.responseWithAssertions; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.serializedResponse; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.xmlNamespaces; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2Utils.samlDecode; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2Utils.samlDecodeAndInflate; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.notNullValue; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_DIGEST_SHA256; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.HttpHeaders.HOST; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DefaultTestContext class SamlAuthenticationMockMvcTests { + private static final String SAML_REQUEST = "SAMLRequest"; + private static final String SAML_RESPONSE = "SAMLResponse"; + private static final String RELAY_STATE = "RelayState"; + private static final String SIG_ALG = "SigAlg"; + private static final String SIGNATURE = "Signature"; private RandomValueStringGenerator generator; - private IdentityZone spZone; private IdentityZone idpZone; private String spZoneEntityId; @@ -64,12 +91,15 @@ class SamlAuthenticationMockMvcTests { private JdbcIdentityProviderProvisioning jdbcIdentityProviderProvisioning; - @Autowired - private LoggingAuditService loggingAuditService; - private InterceptingLogger testLogger; - private Logger originalAuditServiceLogger; + private static void createUser( + JdbcScimUserProvisioning jdbcScimUserProvisioning, + IdentityZone identityZone + ) { + ScimUser user = new ScimUser(null, "marissa", "first", "last"); + user.setPrimaryEmail("test@test.org"); + jdbcScimUserProvisioning.createUser(user, "secret", identityZone.getId()); + } - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @BeforeEach void createSamlRelationship( @Autowired JdbcIdentityProviderProvisioning jdbcIdentityProviderProvisioning, @@ -79,17 +109,19 @@ void createSamlRelationship( generator = new RandomValueStringGenerator(); UaaClientDetails adminClient = new UaaClientDetails("admin", "", "", "client_credentials", "uaa.admin"); adminClient.setClientSecret("adminsecret"); - spZone = createZone("uaa-acting-as-saml-proxy-zone-", adminClient); - idpZone = createZone("uaa-acting-as-saml-idp-zone-", adminClient); + + String spZoneSubdomain = "uaa-acting-as-saml-proxy-zone-" + generator.generate(); + spZone = createZoneWithSamlSpConfig(spZoneSubdomain, adminClient, true, true, spZoneSubdomain + "-entity-id"); + + String idpZoneSubdomain = "uaa-acting-as-saml-idp-zone-" + generator.generate(); + idpZone = createZoneWithSamlSpConfig(idpZoneSubdomain, adminClient, true, true, idpZoneSubdomain + "-entity-id"); + spZoneEntityId = spZone.getSubdomain() + ".cloudfoundry-saml-login"; createUser(jdbcScimUserProvisioning, idpZone); } @BeforeEach - void installTestLogger() { - testLogger = new InterceptingLogger(); - originalAuditServiceLogger = loggingAuditService.getLogger(); - loggingAuditService.setLogger(testLogger); + void setupEsapiProps() { Properties esapiProps = new Properties(); esapiProps.put("ESAPI.Logger", "org.owasp.esapi.logging.slf4j.Slf4JLogFactory"); esapiProps.put("ESAPI.Encoder", "org.owasp.esapi.reference.DefaultEncoder"); @@ -99,34 +131,416 @@ void installTestLogger() { esapiProps.put("Logger.ApplicationName", "uaa"); esapiProps.put("Logger.LogApplicationName", Boolean.FALSE.toString()); esapiProps.put("Logger.LogServerIP", Boolean.FALSE.toString()); - ESAPI.override( new DefaultSecurityConfiguration(esapiProps)); + ESAPI.override(new DefaultSecurityConfiguration(esapiProps)); + } + + @Test + void sendAuthnRequestToIdpRedirectBindingMode() throws Exception { + MvcResult mvcResult = mockMvc.perform( + get("/uaa/saml2/authenticate/%s".formatted("testsaml-redirect-binding")) + .contextPath("/uaa") + .header(HOST, "localhost:8080") + ) + .andDo(print()) + .andExpect(status().is3xxRedirection()) + .andReturn(); + + String samlRequestUrl = mvcResult.getResponse().getRedirectedUrl(); + Map parameterMap = UaaUrlUtils.getParameterMap(samlRequestUrl); + // In the redirect binding, the encoded SAMLRequest, RelayState, + // SigAlg, Signature are all passed as query parameters + MatcherAssert.assertThat("SAMLRequest is missing", parameterMap.get(SAML_REQUEST), notNullValue()); + assertThat("SigAlg is missing", parameterMap.get(SIG_ALG)[0], containsString(ALGO_ID_SIGNATURE_RSA_SHA256)); + assertThat("Signature is missing", parameterMap.get(SIGNATURE), notNullValue()); + assertThat("RelayState is missing", parameterMap.get(RELAY_STATE), notNullValue()); + + // Decode & Inflate the SAMLRequest and check the AssertionConsumerServiceURL + String samlRequestXml = samlDecodeAndInflate(parameterMap.get(SAML_REQUEST)[0]); + assertThat(samlRequestXml) + .contains(" parameterMap = UaaUrlUtils.getParameterMap(samlRequestUrl); + MatcherAssert.assertThat("SAMLRequest is missing", parameterMap.get(SAML_REQUEST), notNullValue()); + assertThat("SigAlg is missing", parameterMap.get(SIG_ALG), notNullValue()); + assertThat("Signature is missing", parameterMap.get(SIGNATURE), notNullValue()); + assertThat("RelayState is missing", parameterMap.get(RELAY_STATE), notNullValue()); + + // Decode & Inflate the SAMLRequest and check the AssertionConsumerServiceURL + String samlRequestXml = samlDecodeAndInflate(parameterMap.get(SAML_REQUEST)[0]); + XmlAssert xmlAssert = XmlAssert.assertThat(samlRequestXml).withNamespaceContext(xmlNamespaces()); + xmlAssert.valueByXPath("//saml2p:AuthnRequest/@AssertionConsumerServiceURL") + .isEqualTo("http://%1$s.localhost:8080/uaa/saml/SSO/alias/%1$s.integration-saml-entity-id".formatted(spZone.getSubdomain())); + xmlAssert.valueByXPath("//saml2p:AuthnRequest/saml2:Issuer") + .isEqualTo(spZone.getConfig().getSamlConfig().getEntityID()); // should match zone config's samlConfig.entityID + xmlAssert.valueByXPath("//saml2p:AuthnRequest/saml2p:NameIDPolicy/@Format") + .isEqualTo("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"); // matches login.saml.nameID + } + + @Test + void sendAuthnRequestFromNonDefaultZoneToIdpRedirectBindingMode_ZoneConfigSamlEntityIDNotSet() throws Exception { + // create a new zone without zone saml entity ID not set + UaaClientDetails adminClient = new UaaClientDetails("admin", "", "", "client_credentials", "uaa.admin"); + adminClient.setClientSecret("adminsecret"); + String spZoneSubdomain = "uaa-acting-as-saml-proxy-zone-" + generator.generate(); + spZone = createZoneWithSamlSpConfig(spZoneSubdomain, adminClient, true, true, null); + + // create IDP in non-default zone + createMockSamlIdpInSpZone("classpath:test-saml-idp-metadata-redirect-binding.xml", "testsaml-redirect-binding"); + + // trigger saml login in the non-default zone + MvcResult mvcResult = mockMvc.perform(get("/uaa/saml2/authenticate/%s".formatted("testsaml-redirect-binding")) + .contextPath("/uaa") + .header(HOST, "%s.localhost:8080".formatted(spZone.getSubdomain())) + ) + .andDo(print()) + .andExpect(status().is3xxRedirection()) + .andReturn(); + + String samlRequestUrl = mvcResult.getResponse().getRedirectedUrl(); + Map parameterMap = UaaUrlUtils.getParameterMap(samlRequestUrl); + MatcherAssert.assertThat("SAMLRequest is missing", parameterMap.get(SAML_REQUEST), notNullValue()); + assertThat("SigAlg is missing", parameterMap.get(SIG_ALG), notNullValue()); + assertThat("Signature is missing", parameterMap.get(SIGNATURE), notNullValue()); + assertThat("RelayState is missing", parameterMap.get(RELAY_STATE), notNullValue()); + + // Decode & Inflate the SAMLRequest and check the AssertionConsumerServiceURL + String samlRequestXml = samlDecodeAndInflate(parameterMap.get(SAML_REQUEST)[0]); + XmlAssert xmlAssert = XmlAssert.assertThat(samlRequestXml).withNamespaceContext(xmlNamespaces()); + xmlAssert.valueByXPath("//saml2p:AuthnRequest/@AssertionConsumerServiceURL") + .isEqualTo("http://%1$s.localhost:8080/uaa/saml/SSO/alias/%1$s.integration-saml-entity-id".formatted(spZone.getSubdomain())); + xmlAssert.valueByXPath("//saml2p:AuthnRequest/saml2:Issuer") + .isEqualTo("%s.%s".formatted(spZone.getSubdomain(), "integration-saml-entity-id")); // should match zone config's samlConfig.entityID; if not set, fail over to zone-subdomain.uaa-wide-saml-entity-id + xmlAssert.valueByXPath("//saml2p:AuthnRequest/saml2p:NameIDPolicy/@Format") + .isEqualTo("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"); // matches login.saml.nameID + } + + @Test + void sendAuthnRequestFromNonDefaultZoneToIdpPostBindingMode() throws Exception { + // create IDP in non-default zone + createMockSamlIdpInSpZone("classpath:test-saml-idp-metadata-post-binding.xml", "testsaml-post-binding"); + + final String samlRequestMatch = "name=\"SAMLRequest\" value=\""; + + MvcResult mvcResult = mockMvc.perform(get("/uaa/saml2/authenticate/%s".formatted("testsaml-post-binding")) + .contextPath("/uaa") + .header(HOST, "%s.localhost:8080".formatted(spZone.getSubdomain())) + ) + .andDo(print()) + .andExpectAll( + status().isOk(), + content().string(containsString("name=\"SAMLRequest\""))) + .andReturn(); + + // Decode the SAMLRequest and check the AssertionConsumerServiceURL + String contentHtml = mvcResult.getResponse().getContentAsString(); + contentHtml = contentHtml.substring(contentHtml.indexOf(samlRequestMatch) + samlRequestMatch.length()); + contentHtml = contentHtml.substring(0, contentHtml.indexOf("\"")); + String samlRequestXml = new String(samlDecode(contentHtml), StandardCharsets.UTF_8); + assertThat(samlRequestXml).contains(" additionalConfigCallback) throws Exception { + idp = new IdentityProvider() + .setType(OriginKeys.SAML) + .setOriginKey(idpZone.getSubdomain()) + .setActive(true) + .setName("SAML IDP for Mock Tests") + .setIdentityZoneId(spZone.getId()); + SamlIdentityProviderDefinition idpDefinition = new SamlIdentityProviderDefinition() + .setMetaDataLocation(getSamlMetadata(idpZone.getSubdomain(), "/saml/idp/metadata")) + .setIdpEntityAlias(idp.getOriginKey()) + .setLinkText(idp.getName()) + .setZoneId(spZone.getId()); + + if (additionalConfigCallback != null) { + additionalConfigCallback.accept(idpDefinition); + } + + idp.setConfig(idpDefinition); + idp = jdbcIdentityProviderProvisioning.create(idp, spZone.getId()); + } + + private void createMockSamlIdpInSpZone(String metadataLocation, String idpOriginKey) { + idp = new IdentityProvider() + .setType(OriginKeys.SAML) + .setOriginKey(idpOriginKey) + .setActive(true) + .setName("SAML IDP for Mock Tests") + .setIdentityZoneId(spZone.getId()); + SamlIdentityProviderDefinition idpDefinition = new SamlIdentityProviderDefinition() + .setMetaDataLocation(metadataLocation) + .setIdpEntityAlias(idp.getOriginKey()) + .setLinkText(idp.getName()) + .setZoneId(spZone.getId()); + + idp.setConfig(idpDefinition); + idp = jdbcIdentityProviderProvisioning.create(idp, spZone.getId()); + } + + private IdentityZone createZoneWithSamlSpConfig(String zoneSubdomain, UaaClientDetails adminClient, Boolean samlRequestSigned, Boolean samlWantAssertionSigned, String samlZoneEntityID) throws Exception { + IdentityZone identityZone = MultitenancyFixture.identityZone(zoneSubdomain, zoneSubdomain); + identityZone.getConfig().getSamlConfig().setRequestSigned(samlRequestSigned); + identityZone.getConfig().getSamlConfig().setWantAssertionSigned(samlWantAssertionSigned); + identityZone.getConfig().getSamlConfig().setEntityID(samlZoneEntityID); + return MockMvcUtils.createOtherIdentityZoneAndReturnResult(mockMvc, webApplicationContext, adminClient, identityZone, true, IdentityZoneHolder.getCurrentZoneId()).getIdentityZone(); + } + + @Nested + @DefaultTestContext + @TestPropertySource(properties = {"login.saml.signRequest = false"}) + class UnsignedConfigMockMvcTests { + @Autowired + private MockMvc mockMvc; + + @Test + void unsignedAuthnRequestViaIdpRedirectBindingMode() throws Exception { + MvcResult mvcResult = mockMvc.perform(get("/uaa/saml2/authenticate/%s".formatted("testsaml-redirect-binding")) + .contextPath("/uaa") + .header(HOST, "localhost:8080") + ) + .andDo(print()) + .andExpect(status().is3xxRedirection()) + .andReturn(); + + String samlRequestUrl = mvcResult.getResponse().getRedirectedUrl(); + Map parameterMap = UaaUrlUtils.getParameterMap(samlRequestUrl); + // In the redirect binding, the encoded SAMLRequest, RelayState, + // SigAlg, Signature are all passed as query parameters + MatcherAssert.assertThat("SAMLRequest is missing", parameterMap.get(SAML_REQUEST), notNullValue()); + assertThat("SigAlg exists, but SAMLRequest should not be signed", parameterMap.get(SIG_ALG), nullValue()); + assertThat("Signature exists, but SAMLRequest should not be signed", parameterMap.get(SIGNATURE), nullValue()); + } + + @Test + void unsignedAuthnRequestViaIdpPostBindingMode() throws Exception { + final String samlRequestMatch = "name=\"SAMLRequest\" value=\""; + + MvcResult mvcResult = mockMvc.perform(get("/uaa/saml2/authenticate/%s".formatted("testsaml-post-binding")) + .contextPath("/uaa") + .header(HOST, "localhost:8080") + ) + .andDo(print()) + .andExpectAll( + status().isOk(), + content().string(containsString("name=\"SAMLRequest\""))) + .andReturn(); + + // Decode the SAMLRequest and check the AssertionConsumerServiceURL + String contentHtml = mvcResult.getResponse().getContentAsString(); + contentHtml = contentHtml.substring(contentHtml.indexOf(samlRequestMatch) + samlRequestMatch.length()); + contentHtml = contentHtml.substring(0, contentHtml.indexOf("\"")); + String samlRequestXml = new String(samlDecode(contentHtml), StandardCharsets.UTF_8); + assertThat(samlRequestXml).contains(" parameterMap = UaaUrlUtils.getParameterMap(samlRequestUrl); + + // Decode & Inflate the SAMLRequest and check the AssertionConsumerServiceURL + String samlRequestXml = samlDecodeAndInflate(parameterMap.get(SAML_REQUEST)[0]); + assertThat(samlRequestXml).contains(" logEvents; private AbstractAppender appender; private Level originalLevel; @@ -137,7 +551,7 @@ void setupLogger() throws Exception { appender = new AbstractAppender("", null, null) { @Override public void append(LogEvent event) { - if (SamlResponseLoggerBinding.class.getName().equals(event.getLoggerName())) { + if (LOGGER_NAME.equals(event.getLoggerName())) { logEvents.add(event); } } @@ -183,93 +597,23 @@ void malformedSamlRequestWithRepeatedParams() throws Exception { assertThatMessageWasLogged(logEvents, DEBUG, "Method: POST, Params (name/size): (foo/1) (foo/2) (foo/9) (SAMLResponse/0), Content-type: application/x-www-form-urlencoded, Request-size: 0, X-Vcap-Request-Id: "); } - private void assertThatMessageWasLogged( - final List logEvents, - final Level expectedLevel, - final String expectedMessage - ) { - assertThat(logEvents, hasItem(new MatchesLogEvent(expectedLevel, expectedMessage))); - } - } - - private static class MatchesLogEvent extends BaseMatcher { - - private final Level expectedLevel; - private final String expectedMessage; - - public MatchesLogEvent( - final Level expectedLevel, - final String expectedMessage - ) { - this.expectedLevel = expectedLevel; - this.expectedMessage = expectedMessage; - } - - @Override - public boolean matches(Object actual) { - if (!(actual instanceof LogEvent)) { - return false; - } - LogEvent logEvent = (LogEvent) actual; - - return expectedLevel.equals(logEvent.getLevel()) - && expectedMessage.equals(logEvent.getMessage().getFormattedMessage()); - } + @Test + void malformedSamlRequest() throws Exception { + postSamlResponse("", "?foo=a", "", ""); - @Override - public void describeTo(Description description) { - description.appendText(String.format("LogEvent with level of {%s} and message of {%s}", this.expectedLevel, this.expectedMessage)); + assertThatMessageWasLogged(logEvents, WARN, "Malformed SAML response. More details at log level DEBUG."); + assertThatMessageWasLogged(logEvents, DEBUG, "Method: POST, Params (name/size): (foo/1) (SAMLResponse/4), Content-type: application/x-www-form-urlencoded, Request-size: 0, X-Vcap-Request-Id: "); } - } - private String getSamlMetadata(String subdomain, String url) throws Exception { - return mockMvc.perform( - get(url) - .header("Host", subdomain + ".localhost") - ) - .andReturn().getResponse().getContentAsString(); - } - - private static void createUser( - JdbcScimUserProvisioning jdbcScimUserProvisioning, - IdentityZone identityZone - ) { - ScimUser user = new ScimUser(null, "marissa", "first", "last"); - user.setPrimaryEmail("test@test.org"); - jdbcScimUserProvisioning.createUser(user, "secret", identityZone.getId()); - } - - void createIdp() throws Exception { - createIdp(null); - } - - private void createIdp(Consumer additionalConfigCallback) throws Exception { - idp = new IdentityProvider<>() - .setType(OriginKeys.SAML) - .setOriginKey(idpZone.getSubdomain()) - .setActive(true) - .setName("SAML IDP for Mock Tests") - .setIdentityZoneId(spZone.getId()); - SamlIdentityProviderDefinition idpDefinition = new SamlIdentityProviderDefinition() - .setMetaDataLocation(getSamlMetadata(idpZone.getSubdomain(), "/saml/idp/metadata")) - .setIdpEntityAlias(idp.getOriginKey()) - .setLinkText(idp.getName()) - .setZoneId(spZone.getId()); + private void assertThatMessageWasLogged( + final List logEvents, + final Level expectedLevel, + final String expectedMessage) { - if (additionalConfigCallback != null) { - additionalConfigCallback.accept(idpDefinition); + assertThat(logEvents).filteredOn(l -> l.getLevel().equals(expectedLevel)) + .isNotEmpty() + .first() + .returns(expectedMessage, l -> l.getMessage().getFormattedMessage()); } - - idp.setConfig(idpDefinition); - idp = jdbcIdentityProviderProvisioning.create(idp, spZone.getId()); - } - - private IdentityZone createZone(String zoneIdPrefix, UaaClientDetails adminClient) throws Exception { - return MockMvcUtils.createOtherIdentityZoneAndReturnResult( - zoneIdPrefix + generator.generate(), - mockMvc, - webApplicationContext, - adminClient, IdentityZoneHolder.getCurrentZoneId() - ).getIdentityZone(); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlKeyRotationMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlKeyRotationMockMvcTests.java index a4049fbf3a8..d14bc4490f4 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlKeyRotationMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlKeyRotationMockMvcTests.java @@ -14,148 +14,153 @@ package org.cloudfoundry.identity.uaa.mock.saml; import org.cloudfoundry.identity.uaa.DefaultTestContext; +import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils; -import org.cloudfoundry.identity.uaa.saml.SamlKey; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; +import org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.SamlConfig; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.context.WebApplicationContext; -import org.w3c.dom.NodeList; +import org.xmlunit.assertj.XmlAssert; +import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; -import static org.cloudfoundry.identity.uaa.provider.saml.SamlKeyManagerFactoryTests.*; -import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils.getCertificates; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.*; +import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.xmlNamespaces; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.certificate1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.certificate2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.formatCert; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.keyName2; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyCertificate; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyKey; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyPassphrase; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey1; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.samlKey2; import static org.springframework.http.MediaType.APPLICATION_XML; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DefaultTestContext class SamlKeyRotationMockMvcTests { + private static final String METADATA_URL = "/saml/metadata"; + private static final String SIGNATURE_CERTIFICATE_XPATH_FORMAT = "//ds:Signature//ds:X509Certificate"; + public static final String KEY_DESCRIPTOR_CERTIFICATE_XPATH_FORMAT = "//md:SPSSODescriptor/md:KeyDescriptor[@use='%s']//ds:X509Certificate"; private IdentityZone zone; - private SamlKey samlKey2; + @Autowired private MockMvc mockMvc; - @BeforeEach - void createZone( - @Autowired WebApplicationContext webApplicationContext, - @Autowired MockMvc mockMvc - ) throws Exception { - this.mockMvc = mockMvc; + @Autowired + WebApplicationContext webApplicationContext; + @BeforeEach + void createZone() throws Exception { String id = new RandomValueStringGenerator().generate().toLowerCase(); IdentityZone identityZone = new IdentityZone(); identityZone.setId(id); identityZone.setSubdomain(id); identityZone.setName("Test Saml Key Zone"); identityZone.setDescription("Testing SAML Key Rotation"); - Map keys = new HashMap<>(); - keys.put("exampleKeyId", "s1gNiNg.K3y/t3XT"); + Map keys = Map.of("exampleKeyId", "s1gNiNg.K3y/t3XT"); identityZone.getConfig().getTokenPolicy().setKeys(keys); SamlConfig samlConfig = new SamlConfig(); - samlConfig.setCertificate(legacyCertificate); - samlConfig.setPrivateKey(legacyKey); - samlConfig.setPrivateKeyPassword(legacyPassphrase); - SamlKey samlKey1 = new SamlKey(key1, passphrase1, certificate1); - samlConfig.addKey("key1", samlKey1); - samlKey2 = new SamlKey(key2, passphrase2, certificate2); - samlConfig.addKey("key2", samlKey2); + samlConfig.setCertificate(legacyCertificate()); + samlConfig.setPrivateKey(legacyKey()); + samlConfig.setPrivateKeyPassword(legacyPassphrase()); + samlConfig.addKey(keyName1(), samlKey1()); + samlConfig.addKey(keyName2(), samlKey2()); identityZone.getConfig().setSamlConfig(samlConfig); UaaClientDetails zoneAdminClient = new UaaClientDetails("admin", null, - "openid", - "client_credentials,authorization_code", - "clients.admin,scim.read,scim.write", - "http://test.redirect.com"); + "openid", + "client_credentials,authorization_code", + "clients.admin,scim.read,scim.write", + "http://test.redirect.com"); zoneAdminClient.setClientSecret("admin-secret"); MockMvcUtils.IdentityZoneCreationResult identityZoneCreationResult = MockMvcUtils - .createOtherIdentityZoneAndReturnResult(mockMvc, webApplicationContext, zoneAdminClient, identityZone, false, id); + .createOtherIdentityZoneAndReturnResult(mockMvc, webApplicationContext, zoneAdminClient, identityZone, false, id); zone = identityZoneCreationResult.getIdentityZone(); } - @ParameterizedTest - @ValueSource(strings = {"/saml/metadata"}) - void key_rotation(String url) throws Exception { + @Test + void key_rotation() throws Exception { //default with three keys - String metadata = getMetadata(url); - List signatureVerificationKeys = getCertificates(metadata, "signing"); - assertThat(signatureVerificationKeys, containsInAnyOrder(clean(legacyCertificate), clean(certificate1), clean(certificate2))); - List encryptionKeys = getCertificates(metadata, "encryption"); - assertThat(encryptionKeys, containsInAnyOrder(clean(legacyCertificate))); - evaluateSignatureKey(metadata, legacyCertificate); + XmlAssert metadataAssert = getMetadataAssert(); + assertThatSigningKeyHasValues(metadataAssert, legacyCertificate(), certificate1(), certificate2()); + assertThatEncryptionKeyHasValues(metadataAssert, legacyCertificate()); + assertSignatureKeyHasValue(metadataAssert, legacyCertificate()); //activate key1 - zone.getConfig().getSamlConfig().setActiveKeyId("key1"); + zone.getConfig().getSamlConfig().setActiveKeyId(keyName1()); zone = MockMvcUtils.updateZone(mockMvc, zone); - metadata = getMetadata(url); - signatureVerificationKeys = getCertificates(metadata, "signing"); - assertThat(signatureVerificationKeys, containsInAnyOrder(clean(legacyCertificate), clean(certificate1), clean(certificate2))); - encryptionKeys = getCertificates(metadata, "encryption"); - evaluateSignatureKey(metadata, certificate1); - assertThat(encryptionKeys, containsInAnyOrder(clean(certificate1))); + metadataAssert = getMetadataAssert(); + assertThatSigningKeyHasValues(metadataAssert, legacyCertificate(), certificate1(), certificate2()); + assertThatEncryptionKeyHasValues(metadataAssert, certificate1()); + assertSignatureKeyHasValue(metadataAssert, certificate1()); //remove all but key2 zone.getConfig().getSamlConfig().setKeys(new HashMap<>()); - zone.getConfig().getSamlConfig().addAndActivateKey("key2", samlKey2); + zone.getConfig().getSamlConfig().addAndActivateKey(keyName2(), samlKey2()); zone = MockMvcUtils.updateZone(mockMvc, zone); - metadata = getMetadata(url); - signatureVerificationKeys = getCertificates(metadata, "signing"); - assertThat(signatureVerificationKeys, containsInAnyOrder(clean(certificate2))); - evaluateSignatureKey(metadata, certificate2); - encryptionKeys = getCertificates(metadata, "encryption"); - assertThat(encryptionKeys, containsInAnyOrder(clean(certificate2))); + metadataAssert = getMetadataAssert(); + assertThatSigningKeyHasValues(metadataAssert, certificate2()); + assertThatEncryptionKeyHasValues(metadataAssert, certificate2()); + assertSignatureKeyHasValue(metadataAssert, certificate2()); } - @ParameterizedTest - @ValueSource(strings = {"/saml/metadata"}) - void check_metadata_signature_key(String url) throws Exception { - String metadata = getMetadata(url); + @Test + void check_metadata_signature_key() throws Exception { + XmlAssert metadataAssert = getMetadataAssert(); + assertSignatureKeyHasValue(metadataAssert, legacyCertificate()); - evaluateSignatureKey(metadata, legacyCertificate); - - zone.getConfig().getSamlConfig().setActiveKeyId("key1"); + zone.getConfig().getSamlConfig().setActiveKeyId(keyName1()); zone = MockMvcUtils.updateZone(mockMvc, zone); - metadata = getMetadata(url); - - evaluateSignatureKey(metadata, certificate1); + metadataAssert = getMetadataAssert(); + assertSignatureKeyHasValue(metadataAssert, certificate1()); } - private String getMetadata(String uri) throws Exception { - return mockMvc.perform( - get(uri) - .header("Host", zone.getSubdomain() + ".localhost") - .accept(APPLICATION_XML) - ) + private XmlAssert getMetadataAssert() throws Exception { + String metadata = mockMvc.perform( + get(METADATA_URL) + .header("Host", zone.getSubdomain() + ".localhost") + .accept(APPLICATION_XML) + ) + .andDo(print()) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); + + return XmlAssert.assertThat(metadata).withNamespaceContext(xmlNamespaces()); } - private String clean(String cert) { - return cert.replace("-----BEGIN CERTIFICATE-----", "") - .replace("-----END CERTIFICATE-----", "") - .replace("\n", ""); + private void assertSignatureKeyHasValue(XmlAssert metadata, String expectedKey) { + metadata.hasXPath(SIGNATURE_CERTIFICATE_XPATH_FORMAT) + .isNotEmpty() + .extractingText() + .containsOnly(formatCert(expectedKey)); } - private void evaluateSignatureKey(String metadata, String expectedKey) throws Exception { - String xpath = "//*[local-name() = 'Signature']//*[local-name() = 'X509Certificate']/text()"; - NodeList nodeList = SamlTestUtils.evaluateXPathExpression(SamlTestUtils.getMetadataDoc(metadata), xpath); - assertNotNull(nodeList); - assertEquals(1, nodeList.getLength()); - assertEquals(clean(expectedKey), clean(nodeList.item(0).getNodeValue())); + private void assertThatSigningKeyHasValues(XmlAssert xmlAssert, String... certificates) { + assertThatXmlKeysOfTypeHasValues(xmlAssert, "signing", certificates); } + private void assertThatEncryptionKeyHasValues(XmlAssert xmlAssert, String... certificates) { + assertThatXmlKeysOfTypeHasValues(xmlAssert, "encryption", certificates); + } + + private void assertThatXmlKeysOfTypeHasValues(XmlAssert xmlAssert, String type, String... certificates) { + String[] cleanCerts = Arrays.stream(certificates).map(TestCredentialObjects::bare).toArray(String[]::new); + xmlAssert.hasXPath(KEY_DESCRIPTOR_CERTIFICATE_XPATH_FORMAT.formatted(type)) + .isNotEmpty() + .extractingText() + .containsExactlyInAnyOrder(cleanCerts); + } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlMetadataEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlMetadataEndpointMockMvcTests.java new file mode 100644 index 00000000000..2e06e1783df --- /dev/null +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlMetadataEndpointMockMvcTests.java @@ -0,0 +1,196 @@ +package org.cloudfoundry.identity.uaa.mock.saml; + +import org.cloudfoundry.identity.uaa.DefaultTestContext; +import org.cloudfoundry.identity.uaa.client.UaaClientDetails; +import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.context.WebApplicationContext; + +import java.net.URI; + +import static org.hamcrest.Matchers.containsString; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_DIGEST_SHA256; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; +import static org.springframework.http.HttpHeaders.HOST; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; + +@DefaultTestContext +class SamlMetadataEndpointMockMvcTests { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private WebApplicationContext webApplicationContext; + + @Test + void testSamlMetadataRootNoEndingSlash() throws Exception { + mockMvc.perform(get(new URI("/saml/metadata"))) + .andExpect(status().isOk()); + } + + @Test + void testSamlMetadataRootWithEndingSlash() throws Exception { + mockMvc.perform(get(new URI("/saml/metadata/"))) + .andExpect(status().isOk()); + } + + @Test + void testSamlMetadataDefaultNoEndingSlash() throws Exception { + mockMvc.perform(get(new URI("/saml/metadata/example"))) + .andExpect(status().isOk()); + } + + @Test + void testSamlMetadataDefaultWithEndingSlash() throws Exception { + mockMvc.perform(get(new URI("/saml/metadata/example/"))) + .andExpect(status().isOk()); + } + + @Test + void testSamlMetadataXMLValidation() throws Exception { + + mockMvc.perform(get(new URI("/saml/metadata"))) + .andDo(print()) + .andExpectAll( + status().isOk(), + header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename=\"saml-sp.xml\";")), + xpath("/EntityDescriptor/@entityID").string("integration-saml-entity-id"), // matches UAA config login.entityID + xpath("/EntityDescriptor/SPSSODescriptor/@AuthnRequestsSigned").booleanValue(true), // matches UAA config login.saml.signRequest + xpath("/EntityDescriptor/SPSSODescriptor/@WantAssertionsSigned").booleanValue(true), // matches UAA config login.saml.wantAssertionSigned + xpath("/EntityDescriptor/SPSSODescriptor/NameIDFormat").string("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"), // matches UAA config login.saml.NameID + xpath("/EntityDescriptor/SPSSODescriptor/KeyDescriptor[@use='signing']/KeyInfo/X509Data/X509Certificate").string("MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0="), + xpath("/EntityDescriptor/SPSSODescriptor/KeyDescriptor[@use='encryption']/KeyInfo/X509Data/X509Certificate").string("MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0="), + xpath("/EntityDescriptor/SPSSODescriptor/AssertionConsumerService/@Location") + .string(containsString("/saml/SSO/alias/integration-saml-entity-id")), // last path is the UAA-wide entity ID alias, set by login.saml.entityIDAlias; is not set, fall back to login.entityID + xpath("/EntityDescriptor/Signature").exists(), + xpath("/EntityDescriptor/Signature/SignedInfo/SignatureMethod/@Algorithm").string(containsString(ALGO_ID_SIGNATURE_RSA_SHA256)), + xpath("/EntityDescriptor/Signature/SignatureValue").exists(), + xpath("/EntityDescriptor/Signature/SignedInfo/Reference/DigestValue").exists(), + xpath("/EntityDescriptor/Signature/SignedInfo/Reference/DigestMethod/@Algorithm").string(containsString(ALGO_ID_DIGEST_SHA256)) + ); + } + + @Test + void testNonDefaultZoneSamlMetadataXMLValidation() throws Exception { + IdentityZone spZone = setupIdentityZone(true); + String subdomain = spZone.getSubdomain(); + + mockMvc.perform(get(new URI("/saml/metadata")) + .header(HOST, subdomain + ".localhost:8080")) + .andDo(print()) + .andExpectAll( + status().isOk(), + header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename=\"saml-%s-sp.xml\";".formatted(subdomain))), + xpath("/EntityDescriptor/@entityID").string(spZone.getConfig().getSamlConfig().getEntityID()), // matches zone config samlConfig.entityID, or fall back on UAA config login.entityID + xpath("/EntityDescriptor/SPSSODescriptor/@AuthnRequestsSigned").booleanValue(false), // matches zone config samlConfig.requestSigned + xpath("/EntityDescriptor/SPSSODescriptor/@WantAssertionsSigned").booleanValue(false), // matches zone config samlConfig.wantAssertionSigned + xpath("/EntityDescriptor/SPSSODescriptor/NameIDFormat").string("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"), // matches UAA config login.saml.NameID??? + xpath("/EntityDescriptor/SPSSODescriptor/KeyDescriptor[@use='signing']/KeyInfo/X509Data/X509Certificate").string("MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0="), + xpath("/EntityDescriptor/SPSSODescriptor/KeyDescriptor[@use='encryption']/KeyInfo/X509Data/X509Certificate").string("MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0="), + xpath("/EntityDescriptor/SPSSODescriptor/AssertionConsumerService/@Location").string(containsString("/saml/SSO/alias/%s.integration-saml-entity-id".formatted(subdomain))) // this needs to be: /saml/SSO/alias/[zone-subdomain].[UAA-wide SAML entity ID, aka UAA.yml's login.saml.entityIDAlias, or fall back on login.entityID in this case] + ); + } + + @Nested + @DefaultTestContext + @TestPropertySource(properties = {"login.saml.signRequest = false", + "login.saml.signMetaData=false", + "login.saml.wantAssertionSigned = false", + "login.saml.entityIDAlias = integration-saml-entity-id-alias"}) + class SamlMetadataAlternativeConfigsMockMvcTests { + @Autowired + private MockMvc mockMvc; + + @Test + void testSamlMetadataXMLValidation() throws Exception { + + mockMvc.perform(get(new URI("/saml/metadata"))) + .andDo(print()) + .andExpectAll( + status().isOk(), + header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename=\"saml-sp.xml\";")), + xpath("/EntityDescriptor/@entityID").string("integration-saml-entity-id"), // matches UAA config login.entityID + xpath("/EntityDescriptor/SPSSODescriptor/@AuthnRequestsSigned").booleanValue(false), // matches UAA config login.saml.signRequest + xpath("/EntityDescriptor/SPSSODescriptor/@WantAssertionsSigned").booleanValue(false), // matches UAA config login.saml.wantAssertionSigned + xpath("/EntityDescriptor/SPSSODescriptor/AssertionConsumerService/@Location").string(containsString("/saml/SSO/alias/integration-saml-entity-id-alias")), // path contains login.saml.entityIDAlias + xpath("/EntityDescriptor/SPSSODescriptor/Signature").doesNotExist() // login.saml.sign-meta-data=false + ); + } + + @Test + void testNonDefaultZoneSamlMetadataXMLValidation() throws Exception { + IdentityZone spZone = setupIdentityZone(true); + String subdomain = spZone.getSubdomain(); + + mockMvc.perform(get(new URI("/saml/metadata")) + .header(HOST, subdomain + ".localhost:8080")) + .andDo(print()) + .andExpectAll( + status().isOk(), + header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename=\"saml-%s-sp.xml\";".formatted(subdomain))), + xpath("/EntityDescriptor/@entityID").string(spZone.getConfig().getSamlConfig().getEntityID()), // matches zone config samlConfig.entityID, or fall back on UAA config login.entityID + xpath("/EntityDescriptor/SPSSODescriptor/@AuthnRequestsSigned").booleanValue(false), // matches zone config samlConfig.requestSigned + xpath("/EntityDescriptor/SPSSODescriptor/@WantAssertionsSigned").booleanValue(false), // matches zone config samlConfig.wantAssertionSigned + xpath("/EntityDescriptor/SPSSODescriptor/NameIDFormat").string("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"), // matches UAA config login.saml.NameID??? + xpath("/EntityDescriptor/SPSSODescriptor/KeyDescriptor[@use='signing']/KeyInfo/X509Data/X509Certificate").string("MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0="), + xpath("/EntityDescriptor/SPSSODescriptor/KeyDescriptor[@use='encryption']/KeyInfo/X509Data/X509Certificate").string("MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0="), + xpath("/EntityDescriptor/SPSSODescriptor/AssertionConsumerService/@Location").string(containsString("/saml/SSO/alias/%s.integration-saml-entity-id-alias".formatted(subdomain))), // this needs to be: /saml/SSO/alias/[zone-subdomain].[UAA-wide SAML entity ID, aka UAA.yml's login.saml.entityIDAlias, or fall back on login.entityID] + xpath("/EntityDescriptor/Signature").doesNotExist() // No signature with login.saml.sign-meta-data=false + ); + } + + @Test + void testNonDefaultZoneSamlMetadataXMLValidation_ZoneSamlEntityIDNotSet() throws Exception { + IdentityZone alternativeSpZone = setupIdentityZone(false); + String zoneSubdomain = alternativeSpZone.getSubdomain(); + + mockMvc.perform(get(new URI("/saml/metadata")) + .header(HOST, zoneSubdomain + ".localhost:8080")) + .andDo(print()) + .andExpectAll( + status().isOk(), + header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename=\"saml-%s-sp.xml\";".formatted(zoneSubdomain))), + xpath("/EntityDescriptor/@entityID").string("%s.integration-saml-entity-id".formatted(zoneSubdomain)), // matches zone config samlConfig.entityID, or fall back on UAA config login.entityID + xpath("/EntityDescriptor/SPSSODescriptor/@AuthnRequestsSigned").booleanValue(false), // matches zone config samlConfig.requestSigned + xpath("/EntityDescriptor/SPSSODescriptor/@WantAssertionsSigned").booleanValue(false), // matches zone config samlConfig.wantAssertionSigned + xpath("/EntityDescriptor/SPSSODescriptor/NameIDFormat").string("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"), // matches UAA config login.saml.NameID??? + xpath("/EntityDescriptor/SPSSODescriptor/KeyDescriptor[@use='signing']/KeyInfo/X509Data/X509Certificate").string("MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0="), + xpath("/EntityDescriptor/SPSSODescriptor/KeyDescriptor[@use='encryption']/KeyInfo/X509Data/X509Certificate").string("MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0="), + xpath("/EntityDescriptor/SPSSODescriptor/AssertionConsumerService/@Location").string(containsString("/saml/SSO/alias/%s.integration-saml-entity-id-alias".formatted(zoneSubdomain))), // this needs to be: /saml/SSO/alias/[zone-subdomain].[UAA-wide SAML entity ID, aka UAA.yml's login.saml.entityIDAlias, or fall back on login.entityID] + xpath("/EntityDescriptor/Signature").doesNotExist() // No signature with login.saml.sign-meta-data=false + ); + } + } + + private IdentityZone setupIdentityZone(boolean hasEntityId) throws Exception { + UaaClientDetails adminClient = new UaaClientDetails("admin", "", "", "client_credentials", "uaa.admin"); + adminClient.setClientSecret("adminsecret"); + + RandomValueStringGenerator generator = new RandomValueStringGenerator(); + String zoneSubdomain = "testzone-" + generator.generate(); + String entityId = hasEntityId ? zoneSubdomain + "-entity-id" : null; + return createZone(zoneSubdomain, adminClient, false, false, entityId); + } + + private IdentityZone createZone(String zoneSubdomain, UaaClientDetails adminClient, Boolean samlRequestSigned, Boolean samlWantAssertionSigned, String samlZoneEntityID) throws Exception { + IdentityZone identityZone = MultitenancyFixture.identityZone(zoneSubdomain, zoneSubdomain); + identityZone.getConfig().getSamlConfig().setRequestSigned(samlRequestSigned); + identityZone.getConfig().getSamlConfig().setWantAssertionSigned(samlWantAssertionSigned); + identityZone.getConfig().getSamlConfig().setEntityID(samlZoneEntityID); + return MockMvcUtils.createOtherIdentityZoneAndReturnResult(mockMvc, webApplicationContext, adminClient, identityZone, true, IdentityZoneHolder.getCurrentZoneId()).getIdentityZone(); + } +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/Saml2BearerGrantMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/Saml2BearerGrantMockMvcTests.java index 73d1a3357de..a0b8817915e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/Saml2BearerGrantMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/Saml2BearerGrantMockMvcTests.java @@ -1,143 +1,65 @@ package org.cloudfoundry.identity.uaa.mock.token; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils; +import org.cloudfoundry.identity.uaa.provider.saml.TestOpenSamlObjects; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.opensaml.saml2.core.NameID; +import org.opensaml.saml.saml2.core.NameID; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import java.security.Security; + import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_SAML2_BEARER; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyCertificate; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyKey; +import static org.cloudfoundry.identity.uaa.provider.saml.TestCredentialObjects.legacyPassphrase; import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils.createLocalSamlIdpDefinition; import static org.springframework.http.HttpHeaders.HOST; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -public class Saml2BearerGrantMockMvcTests extends AbstractTokenMockMvcTests { +class Saml2BearerGrantMockMvcTests extends AbstractTokenMockMvcTests { + + @BeforeAll + static void beforeAll() { + Security.addProvider(new BouncyCastleFipsProvider()); + } + + @BeforeEach + void beforeEach() { + IdentityZone.getUaa().getConfig().getSamlConfig().setPrivateKey(legacyKey()); + IdentityZone.getUaa().getConfig().getSamlConfig().setPrivateKeyPassword(legacyPassphrase()); + IdentityZone.getUaa().getConfig().getSamlConfig().setCertificate(legacyCertificate()); + } + @Test void getTokenUsingSaml2BearerGrant() throws Exception { - SamlTestUtils samlTestUtils = new SamlTestUtils(); - samlTestUtils.initializeSimple(); - final String subdomain = "68uexx"; - //all our SAML defaults use :8080/uaa/ so we have to use that here too - final String host = subdomain + ".localhost"; - final String fullPath = "/uaa/oauth/token/alias/" + subdomain + - ".cloudfoundry-saml-login"; - final String origin = subdomain + ".cloudfoundry-saml-login"; - + // all our SAML defaults use `:8080/uaa/` so we have to use that here too + final String host = "%s.localhost".formatted(subdomain); + final String fullPath = "/uaa/oauth/token/alias/%s.integration-saml-entity-id".formatted(subdomain); + final String origin = "%s.integration-saml-entity-id".formatted(subdomain); MockMvcUtils.IdentityZoneCreationResult testZone = MockMvcUtils.createOtherIdentityZoneAndReturnResult( - subdomain, mockMvc, this.webApplicationContext, null, + subdomain, mockMvc, this.webApplicationContext, null, IdentityZoneHolder.getCurrentZoneId()); - //Mock an IDP metadata - String idpMetadata = "\n" + - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MNO5mOgijKliauTLhxL1pqT15s4=\n" + - " \n" + - " \n" + - " \n" + - " CwxB189hOth7P4g+jswYiG1XHyy0a8Pci6LahimDi0sSuWF5ui1Dw8MSamNDfi2GC5QGArrupPdxgX5F8BFFuio3XkmcQqRhsC01R2u1/NhpabGTgczrk1LYMpCaIOitaXRM2cEkqrmf/s6S3zXDQkQJTcJefc/0NrYgFN6Pisc=\n" + - " \n" + - " \n" + - " \n" + - " MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - " YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - " BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - " MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - " ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - " HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - " gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - " 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - " xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - " GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - " MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - " EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - " MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - " 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - " ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - " YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - " BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - " MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - " ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - " HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - " gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - " 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - " xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - " GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - " MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - " EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - " MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - " 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - " ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF\n" + - " YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM\n" + - " BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2\n" + - " MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE\n" + - " ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx\n" + - " HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\n" + - " gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR\n" + - " 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY\n" + - " xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy\n" + - " GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3\n" + - " MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL\n" + - " EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA\n" + - " MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am\n" + - " 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o\n" + - " ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\n" + - " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n" + - " \n" + - " \n" + - " \n" + - ""; - //create an IDP in the test zone + String idpMetadata = getIdpMetadata(host, origin); SamlIdentityProviderDefinition idpDef = createLocalSamlIdpDefinition( origin, testZone.getIdentityZone().getId(), idpMetadata); - IdentityProvider provider = new IdentityProvider(); + IdentityProvider provider = new IdentityProvider<>(); provider.setConfig(idpDef); provider.setActive(true); provider.setIdentityZoneId(testZone.getIdentityZone().getId()); @@ -145,18 +67,14 @@ void getTokenUsingSaml2BearerGrant() throws Exception { provider.setOriginKey(origin); IdentityZoneHolder.set(testZone.getIdentityZone()); - identityProviderProvisioning.create(provider, - testZone.getIdentityZone().getId()); + identityProviderProvisioning.create(provider, testZone.getIdentityZone().getId()); IdentityZoneHolder.clear(); - String assertion = samlTestUtils.mockAssertionEncoded( - origin, - NameID.UNSPECIFIED, - "Saml2BearerIntegrationUser", - "http://" + host + ":8080/uaa/oauth/token/alias/" + origin, - origin); + String spEndpoint = "http://%s:8080/uaa/oauth/token/alias/%s".formatted(host, origin); + String assertionStr = TestOpenSamlObjects.getEncodedAssertion("68uexx.cloudfoundry-saml-login", NameID.UNSPECIFIED, + "Saml2BearerIntegrationUser", spEndpoint, origin, true); - //create client in test zone + // create a client in the test zone String clientId = "testclient" + generator.generate(); setUpClients(clientId, "uaa.none", "uaa.user,openid", GRANT_TYPE_SAML2_BEARER + ",password,refresh_token", true, @@ -178,12 +96,77 @@ void getTokenUsingSaml2BearerGrant() throws Exception { .param("client_secret", "secret") .param("client_assertion", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjU4ZDU1YzUwMGNjNmI1ODM3OTYxN2UwNmU3ZGVjNmNhIn0.eyJzdWIiOiJsb2dpbiIsImlzcyI6ImxvZ2luIiwianRpIjoiNThkNTVjNTAwY2M2YjU4Mzc5NjE3ZTA2ZTdhZmZlZSIsImV4cCI6MTIzNDU2NzgsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4ifQ.jwWw0OKZecd4ZjtwQ_ievqBVrh2SieqMF6vY74Oo5H6v-Ibcmumq96NLNtoUEwaAEQQOHb8MWcC8Gwi9dVQdCrtpomC86b_LKkihRBSKuqpw0udL9RMH5kgtC04ctsN0yZNifUWMP85VHn97Ual5eZ2miaBFob3H5jUe98CcBj1TSRehr64qBFYuwt9vD19q6U-ONhRt0RXBPB7ayHAOMYtb1LFIzGAiKvqWEy9f-TBPXSsETjKkAtSuM-WVWi4EhACMtSvI6iJN15f7qlverRSkGIdh1j2vPXpKKBJoRhoLw6YqbgcUC9vAr17wfa_POxaRHvh9JPty0ZXLA4XPtA") .param("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") - .param("assertion", assertion) + .param("assertion", assertionStr) .param("scope", "openid"); mockMvc.perform(post) + .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").exists()) .andExpect(jsonPath("$.scope").value("openid")); } + + private String getIdpMetadata(String host, String origin) { + // Mock an IDP metadata: %1$s is the host; %2$s is the origin + // Maps to TestCredentialObjects.legacyCertificate + return """ + + + + + + + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF + YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM + BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2 + MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE + ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx + HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB + gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR + 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY + xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy + GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3 + MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL + EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA + MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am + 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o + ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= + + + + + + + + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEOMAwGA1UECBMF + YXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEOMAwGA1UECxMFYXJ1YmExDjAM + BgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5hcnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2 + MjdaFw0xNjExMTkyMjI2MjdaMHwxCzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UE + ChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmEx + HTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB + gQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR + 4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCY + xhMol6ZnTbSsFW6VZjFMjQIDAQABo4HaMIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1sy + GDCBpwYDVR0jBIGfMIGcgBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3 + MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYDVQQL + EwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyggEA + MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ0HOZbbHClXmGUjGs+GS+xC1FO/am + 2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxCKdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3o + ePe84k8jm3A7EvH5wi5hvCkKRpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + + + """.formatted(host, origin); + } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index 782d85bba3e..34713d532a2 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.RandomStringUtils; -import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; @@ -28,6 +27,9 @@ import org.cloudfoundry.identity.uaa.invitations.InvitationsResponse; import org.cloudfoundry.identity.uaa.login.Prompt; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; +import org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; +import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetails; import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants; import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; @@ -60,8 +62,6 @@ import org.cloudfoundry.identity.uaa.zone.Links; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.cloudfoundry.identity.uaa.zone.MultitenantJdbcClientDetailsService; -import org.junit.Assert; -import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.context.ApplicationContext; @@ -80,9 +80,6 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; -import org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetails; import org.springframework.security.web.PortResolverImpl; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.csrf.CsrfToken; @@ -100,8 +97,8 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import java.io.File; +import java.io.Serial; import java.net.URL; import java.util.Arrays; import java.util.Collection; @@ -115,12 +112,11 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat.OPAQUE; import static org.cloudfoundry.identity.uaa.scim.ScimGroupMember.Type.USER; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; -import static org.springframework.http.HttpHeaders.HOST; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -130,60 +126,45 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.util.StringUtils.hasText; -import static org.springframework.util.StringUtils.isEmpty; public final class MockMvcUtils { private MockMvcUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); } public static final String IDP_META_DATA = - "\n" + - "\n" + - " \n" + - " \n" + - " begl1WVCsXSn7iHixtWPP8d/X+k=BmbKqA3A0oSLcn5jImz/l5WbpVXj+8JIpT/ENWjOjSd/gcAsZm1QvYg+RxYPBk+iV2bBxD+/yAE/w0wibsHrl0u9eDhoMRUJBUSmeyuN1lYzBuoVa08PdAGtb5cGm4DMQT5Rzakb1P0hhEPPEDDHgTTxop89LUu6xx97t2Q03Khy8mXEmBmNt2NlFxJPNt0FwHqLKOHRKBOE/+BpswlBocjOQKFsI9tG3TyjFC68mM2jo0fpUQCgj5ZfhzolvS7z7c6V201d9Tqig0/mMFFJLTN8WuZPavw22AJlMjsDY9my+4R9HKhK5U53DhcTeECs9fb4gd7p5BJy4vVp7tqqOg==\n" + - "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + - " \n" + - " \n" + - " \n" + - " Filip\n" + - " Hanik\n" + - " fhanik@pivotal.io\n" + - " \n" + - ""; - - - public static T getEventOfType(ArgumentCaptor captor, Class type) { - for (AbstractUaaEvent event : captor.getAllValues()) { - if (event.getClass().equals(type)) { - return (T) event; - } - } - return null; - } - - public static UaaAuthentication getUaaAuthentication(HttpSession session) { - SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); - return (UaaAuthentication) context.getAuthentication(); - } + "\n" + + "\n" + + " \n" + + " \n" + + " begl1WVCsXSn7iHixtWPP8d/X+k=BmbKqA3A0oSLcn5jImz/l5WbpVXj+8JIpT/ENWjOjSd/gcAsZm1QvYg+RxYPBk+iV2bBxD+/yAE/w0wibsHrl0u9eDhoMRUJBUSmeyuN1lYzBuoVa08PdAGtb5cGm4DMQT5Rzakb1P0hhEPPEDDHgTTxop89LUu6xx97t2Q03Khy8mXEmBmNt2NlFxJPNt0FwHqLKOHRKBOE/+BpswlBocjOQKFsI9tG3TyjFC68mM2jo0fpUQCgj5ZfhzolvS7z7c6V201d9Tqig0/mMFFJLTN8WuZPavw22AJlMjsDY9my+4R9HKhK5U53DhcTeECs9fb4gd7p5BJy4vVp7tqqOg==\n" + + "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + + " \n" + + " \n" + + " \n" + + " Filip\n" + + " Hanik\n" + + " fhanik@pivotal.io\n" + + " \n" + + ""; public static File getLimitedModeStatusFile(ApplicationContext context) { return context.getBean(LimitedModeUaaFilter.class).getStatusFile(); @@ -199,24 +180,6 @@ public static void resetLimitedModeStatusFile(ApplicationContext context, File f context.getBean(LimitedModeUaaFilter.class).setStatusFile(file); } - public static String getSPMetadata(MockMvc mockMvc, String subdomain) throws Exception { - return mockMvc.perform( - get("/saml/metadata") - .accept(MediaType.APPLICATION_XML) - .header(HOST, hasText(subdomain) ? subdomain + ".localhost" : "localhost") - ).andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); - } - - public static String getIDPMetaData(MockMvc mockMvc, String subdomain) throws Exception { - return mockMvc.perform( - get("/saml/idp/metadata") - .accept(MediaType.APPLICATION_XML) - .header(HOST, hasText(subdomain) ? subdomain + ".localhost" : "localhost") - ).andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); - } - public static MockHttpSession getSavedRequestSession() { MockHttpSession session = new MockHttpSession(); SavedRequest savedRequest = new MockSavedRequest(); @@ -226,100 +189,15 @@ public static MockHttpSession getSavedRequestSession() { public static ScimUser getUserByUsername(MockMvc mockMvc, String username, String accessToken) throws Exception { MockHttpServletRequestBuilder get = get("/Users?filter=userName eq \"" + username + "\"") - .header("Authorization", "Bearer " + accessToken) - .header("Accept", APPLICATION_JSON); + .header("Authorization", "Bearer " + accessToken) + .header("Accept", APPLICATION_JSON); MvcResult userResult = mockMvc.perform(get) - .andExpect(status().isOk()).andReturn(); + .andExpect(status().isOk()).andReturn(); SearchResults results = JsonUtils.readValue(userResult.getResponse().getContentAsString(), - new TypeReference>(){}); + new TypeReference<>() {}); return results.getResources().get(0); } - public static class MockSavedRequest extends DefaultSavedRequest { - - public MockSavedRequest() { - super(new MockHttpServletRequest(), new PortResolverImpl()); - } - - @Override - public String getRedirectUrl() { - return "http://test/redirect/oauth/authorize"; - } - - @Override - public String[] getParameterValues(String name) { - if ("client_id".equals(name)) { - return new String[]{"admin"}; - } - return new String[0]; - } - - @Override - public List getCookies() { - return null; - } - - @Override - public String getMethod() { - return null; - } - - @Override - public List getHeaderValues(String name) { - return null; - } - - @Override - public Collection getHeaderNames() { - return null; - } - - @Override - public List getLocales() { - return null; - } - - @Override - public Map getParameterMap() { - return null; - } - - } - - public static class ZoneScimInviteData { - private final IdentityZoneCreationResult zone; - private final String adminToken; - private final ClientDetails scimInviteClient; - private final String defaultZoneAdminToken; - - public ZoneScimInviteData(String adminToken, - IdentityZoneCreationResult zone, - ClientDetails scimInviteClient, - String defaultZoneAdminToken) { - this.adminToken = adminToken; - this.zone = zone; - this.scimInviteClient = scimInviteClient; - this.defaultZoneAdminToken = defaultZoneAdminToken; - } - - public ClientDetails getScimInviteClient() { - return scimInviteClient; - } - - public String getDefaultZoneAdminToken() { - return defaultZoneAdminToken; - } - - public IdentityZoneCreationResult getZone() { - return zone; - } - - public String getAdminToken() { - return adminToken; - } - } - - public static String extractInvitationCode(String inviteLink) { Pattern p = Pattern.compile("accept\\?code=(.*)"); Matcher m = p.matcher(inviteLink); @@ -393,31 +271,31 @@ public static InvitationsResponse sendRequestWithTokenAndReturnResponse(Applicat String requestBody = JsonUtils.writeValueAsString(invitations); MockHttpServletRequestBuilder post = post("/invite_users") - .param(OAuth2Utils.CLIENT_ID, clientId) - .param(OAuth2Utils.REDIRECT_URI, redirectUri) - .header("Authorization", "Bearer " + token) - .contentType(APPLICATION_JSON) - .content(requestBody); + .param(OAuth2Utils.CLIENT_ID, clientId) + .param(OAuth2Utils.REDIRECT_URI, redirectUri) + .header("Authorization", "Bearer " + token) + .contentType(APPLICATION_JSON) + .content(requestBody); if (hasText(subdomain)) { post.header("Host", (subdomain + ".localhost")); } MvcResult result = mockMvc.perform( - post - ) - .andExpect(status().isOk()) - .andReturn(); + post + ) + .andExpect(status().isOk()) + .andReturn(); return JsonUtils.readValue(result.getResponse().getContentAsString(), InvitationsResponse.class); } public static URL inviteUser(ApplicationContext context, MockMvc mockMvc, String email, String userInviteToken, String subdomain, String clientId, String expectedOrigin, String REDIRECT_URI) throws Exception { InvitationsResponse response = sendRequestWithTokenAndReturnResponse(context, mockMvc, userInviteToken, subdomain, clientId, REDIRECT_URI, email); - assertEquals(1, response.getNewInvites().size()); - assertEquals(expectedOrigin, context.getBean(JdbcTemplate.class).queryForObject("SELECT origin FROM users WHERE username='" + email + "'", String.class)); + assertThat(response.getNewInvites()).hasSize(1); + assertThat(context.getBean(JdbcTemplate.class).queryForObject("SELECT origin FROM users WHERE username='" + email + "'", String.class)).isEqualTo(expectedOrigin); return response.getNewInvites().get(0).getInviteLink(); } - public static IdentityProvider createIdentityProvider(MockMvc mockMvc, IdentityZoneCreationResult zone, String nameAndOriginKey, AbstractIdentityProviderDefinition definition) throws Exception { - IdentityProvider provider = new IdentityProvider(); + public static IdentityProvider createIdentityProvider(MockMvc mockMvc, IdentityZoneCreationResult zone, String nameAndOriginKey, T definition) throws Exception { + IdentityProvider provider = new IdentityProvider<>(); provider.setConfig(definition); provider.setActive(true); provider.setIdentityZoneId(zone.getIdentityZone().getId()); @@ -431,10 +309,10 @@ public static IdentityProvider createIdentityProvider(MockMvc mockMvc, IdentityZ provider.setType(OriginKeys.UAA); } provider = MockMvcUtils.createIdpUsingWebRequest(mockMvc, - zone.getIdentityZone().getId(), - zone.getZoneAdminToken(), - provider, - status().isCreated()); + zone.getIdentityZone().getId(), + zone.getZoneAdminToken(), + provider, + status().isCreated()); return provider; } @@ -448,17 +326,16 @@ public static ZoneScimInviteData createZoneForInvites(MockMvc mockMvc, Applicati appClient.setClientSecret("secret"); appClient = MockMvcUtils.createClient(mockMvc, zone.getZoneAdminToken(), appClient, zone.getIdentityZone(), - status().isCreated()); + status().isCreated()); appClient.setClientSecret("secret"); String adminToken = MockMvcUtils.getClientCredentialsOAuthAccessToken( - mockMvc, - appClient.getClientId(), - appClient.getClientSecret(), - "", - zone.getIdentityZone().getSubdomain() + mockMvc, + appClient.getClientId(), + appClient.getClientSecret(), + "", + zone.getIdentityZone().getSubdomain() ); - String username = new AlphanumericRandomValueStringGenerator().generate().toLowerCase() + "@example.com"; ScimUser user = new ScimUser(userId, username, "given-name", "family-name"); user.setPrimaryEmail(username); @@ -470,10 +347,10 @@ public static ZoneScimInviteData createZoneForInvites(MockMvc mockMvc, Applicati group.setMembers(Collections.singletonList(new ScimGroupMember(user.getId(), USER))); return new ZoneScimInviteData( - adminToken, - zone, - appClient, - superAdmin + adminToken, + zone, + appClient, + superAdmin ); } @@ -494,37 +371,13 @@ public static IdentityZone createZoneUsingWebRequest(MockMvc mockMvc, String acc IdentityZone identityZone = MultitenancyFixture.identityZone(zoneId, zoneId); MvcResult result = mockMvc.perform(post("/identity-zones") - .header("Authorization", "Bearer " + accessToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) - .andExpect(status().isCreated()).andReturn(); + .header("Authorization", "Bearer " + accessToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) + .andExpect(status().isCreated()).andReturn(); return JsonUtils.readValue(result.getResponse().getContentAsString(), IdentityZone.class); } - public static class IdentityZoneCreationResult { - private final IdentityZone identityZone; - private final UaaPrincipal zoneAdmin; - private final String zoneAdminToken; - - public IdentityZoneCreationResult(IdentityZone identityZone, UaaPrincipal zoneAdmin, String zoneAdminToken) { - this.identityZone = identityZone; - this.zoneAdmin = zoneAdmin; - this.zoneAdminToken = zoneAdminToken; - } - - public IdentityZone getIdentityZone() { - return identityZone; - } - - public UaaPrincipal getZoneAdminUser() { - return zoneAdmin; - } - - public String getZoneAdminToken() { - return zoneAdminToken; - } - } - public static IdentityZoneCreationResult createOtherIdentityZoneAndReturnResult( MockMvc mockMvc, ApplicationContext webApplicationContext, @@ -532,11 +385,11 @@ public static IdentityZoneCreationResult createOtherIdentityZoneAndReturnResult( IdentityZone identityZone, String zoneId) throws Exception { return createOtherIdentityZoneAndReturnResult(mockMvc, - webApplicationContext, - bootstrapClient, - identityZone, - true, - zoneId); + webApplicationContext, + bootstrapClient, + identityZone, + true, + zoneId); } public static IdentityZoneCreationResult createOtherIdentityZoneAndReturnResult(MockMvc mockMvc, @@ -546,18 +399,18 @@ public static IdentityZoneCreationResult createOtherIdentityZoneAndReturnResult( boolean useWebRequests, String zoneId) throws Exception { String identityToken = getClientCredentialsOAuthAccessToken(mockMvc, "identity", "identitysecret", - "zones.write,scim.zones", null); + "zones.write,scim.zones", null); if (useWebRequests) { mockMvc.perform(post("/identity-zones") - .header("Authorization", "Bearer " + identityToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) - .andExpect(status().isCreated()); + .header("Authorization", "Bearer " + identityToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) + .andExpect(status().isCreated()); } else { webApplicationContext.getBean(IdentityZoneProvisioning.class).create(identityZone); - IdentityProvider defaultIdp = new IdentityProvider(); + IdentityProvider defaultIdp = new IdentityProvider<>(); defaultIdp.setName(OriginKeys.UAA); defaultIdp.setType(OriginKeys.UAA); defaultIdp.setOriginKey(OriginKeys.UAA); @@ -577,32 +430,32 @@ public static IdentityZoneCreationResult createOtherIdentityZoneAndReturnResult( group.setMembers(Collections.singletonList(new ScimGroupMember(marissa.getId()))); if (useWebRequests) { mockMvc.perform(post("/Groups/zones") - .header("Authorization", "Bearer " + identityToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(group))) - .andExpect(status().isCreated()); + .header("Authorization", "Bearer " + identityToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group))) + .andExpect(status().isCreated()); } else { webApplicationContext.getBean(ScimGroupEndpoints.class).addZoneManagers(group, Mockito.mock(HttpServletResponse.class)); } // use that user to create an admin client in the new zone String zoneAdminAuthcodeToken = getUserOAuthAccessTokenAuthCode(mockMvc, "identity", "identitysecret", - marissa.getId(), "marissa", "koala", zoneAdminScope, zoneId); + marissa.getId(), "marissa", "koala", zoneAdminScope, zoneId); if (bootstrapClient != null) { if (useWebRequests) { mockMvc.perform(post("/oauth/clients") - .header("Authorization", "Bearer " + zoneAdminAuthcodeToken) - .header("X-Identity-Zone-Id", identityZone.getId()) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(bootstrapClient))) - .andExpect(status().isCreated()); + .header("Authorization", "Bearer " + zoneAdminAuthcodeToken) + .header("X-Identity-Zone-Id", identityZone.getId()) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(bootstrapClient))) + .andExpect(status().isCreated()); } else { webApplicationContext.getBean(MultitenantJdbcClientDetailsService.class).addClientDetails( - bootstrapClient, - identityZone.getId() + bootstrapClient, + identityZone.getId() ); } } @@ -623,7 +476,7 @@ public static IdentityZoneCreationResult createOtherIdentityZoneAndReturnResult( public static IdentityZoneCreationResult createOtherIdentityZoneAndReturnResult(String subdomain, MockMvc mockMvc, ApplicationContext webApplicationContext, - ClientDetails bootstrapClient, + ClientDetails bootstrapClient, String zoneId) throws Exception { return createOtherIdentityZoneAndReturnResult(subdomain, mockMvc, webApplicationContext, bootstrapClient, true, zoneId); @@ -632,14 +485,14 @@ public static IdentityZoneCreationResult createOtherIdentityZoneAndReturnResult( public static IdentityZone createOtherIdentityZone(String subdomain, MockMvc mockMvc, ApplicationContext webApplicationContext, - ClientDetails bootstrapClient, String zoneId) throws Exception { + ClientDetails bootstrapClient, String zoneId) throws Exception { return createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, bootstrapClient, true, zoneId); } public static IdentityZone createOtherIdentityZone(String subdomain, MockMvc mockMvc, ApplicationContext webApplicationContext, - ClientDetails bootstrapClient, + ClientDetails bootstrapClient, boolean useWebRequests, String zoneId) throws Exception { return createOtherIdentityZoneAndReturnResult(subdomain, mockMvc, webApplicationContext, bootstrapClient, useWebRequests, zoneId).getIdentityZone(); @@ -658,7 +511,7 @@ public static IdentityZone createOtherIdentityZone(String subdomain, String zoneId) throws Exception { UaaClientDetails client = new UaaClientDetails("admin", null, null, "client_credentials", - "clients.admin,scim.read,scim.write,idps.write,uaa.admin", "http://redirect.url"); + "clients.admin,scim.read,scim.write,idps.write,uaa.admin", "http://redirect.url"); client.setClientSecret("admin-secret"); return createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, client, useWebRequests, zoneId); @@ -670,13 +523,13 @@ public static IdentityZone updateIdentityZone(IdentityZone zone, ApplicationCont public static void deleteIdentityZone(String zoneId, MockMvc mockMvc) throws Exception { String identityToken = getClientCredentialsOAuthAccessToken(mockMvc, "identity", "identitysecret", - "zones.write,scim.zones", null); + "zones.write,scim.zones", null); mockMvc.perform(delete("/identity-zones/" + zoneId) - .header("Authorization", "Bearer " + identityToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON)) - .andExpect(status().isOk()); + .header("Authorization", "Bearer " + identityToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON)) + .andExpect(status().isOk()); } public static IdentityProvider createIdpUsingWebRequest(MockMvc mockMvc, String zoneId, String token, @@ -687,24 +540,24 @@ public static IdentityProvider createIdpUsingWebRequest(MockMvc mockMvc, String public static IdentityProvider createIdpUsingWebRequest(MockMvc mockMvc, String zoneId, String token, IdentityProvider identityProvider, ResultMatcher resultMatcher, boolean update) throws Exception { MockHttpServletRequestBuilder requestBuilder = - update ? - put("/identity-providers/" + identityProvider.getId()) - .header("Authorization", "Bearer " + token) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityProvider)) - : - post("/identity-providers/") - .header("Authorization", "Bearer " + token) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityProvider)); + update ? + put("/identity-providers/" + identityProvider.getId()) + .header("Authorization", "Bearer " + token) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityProvider)) + : + post("/identity-providers/") + .header("Authorization", "Bearer " + token) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityProvider)); if (zoneId != null) { requestBuilder.header(IdentityZoneSwitchingFilter.HEADER, zoneId); } MvcResult result = mockMvc.perform(requestBuilder) - .andExpect(resultMatcher) - .andReturn(); + .andExpect(resultMatcher) + .andReturn(); if (hasText(result.getResponse().getContentAsString())) { try { return JsonUtils.readValue(result.getResponse().getContentAsString(), IdentityProvider.class); @@ -725,31 +578,31 @@ public static ScimUser createUserInZone(MockMvc mockMvc, String accessToken, Sci } public static ScimUser createUserInZone(MockMvc mockMvc, String accessToken, ScimUser user, String subdomain, String zoneId) throws Exception { - String requestDomain = subdomain.equals("") ? "localhost" : subdomain + ".localhost"; + String requestDomain = subdomain.isEmpty() ? "localhost" : subdomain + ".localhost"; MockHttpServletRequestBuilder post = post("/Users"); post.header("Authorization", "Bearer " + accessToken) - .with(new SetServerNameRequestPostProcessor(requestDomain)) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsBytes(user)); + .with(new SetServerNameRequestPostProcessor(requestDomain)) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsBytes(user)); if (hasText(zoneId)) { post.header(IdentityZoneSwitchingFilter.HEADER, zoneId); } MvcResult userResult = mockMvc.perform(post) - .andExpect(status().isCreated()).andReturn(); + .andExpect(status().isCreated()).andReturn(); return JsonUtils.readValue(userResult.getResponse().getContentAsString(), ScimUser.class); } public static ScimUser readUserInZone(MockMvc mockMvc, String accessToken, String userId, String subdomain, String zoneId) throws Exception { - String requestDomain = subdomain.equals("") ? "localhost" : subdomain + ".localhost"; + String requestDomain = subdomain.isEmpty() ? "localhost" : subdomain + ".localhost"; MockHttpServletRequestBuilder get = get("/Users/" + userId); get.header("Authorization", "Bearer " + accessToken) - .with(new SetServerNameRequestPostProcessor(requestDomain)) - .accept(APPLICATION_JSON); + .with(new SetServerNameRequestPostProcessor(requestDomain)) + .accept(APPLICATION_JSON); if (hasText(zoneId)) { get.header(IdentityZoneSwitchingFilter.HEADER, zoneId); } MvcResult userResult = mockMvc.perform(get) - .andExpect(status().isOk()).andReturn(); + .andExpect(status().isOk()).andReturn(); return JsonUtils.readValue(userResult.getResponse().getContentAsString(), ScimUser.class); } @@ -770,7 +623,7 @@ public static ScimUser createAdminForZone(MockMvc mockMvc, String accessToken, S group.setMembers(Collections.singletonList(new ScimGroupMember(createdUser.getId()))); createGroup(mockMvc, accessToken, group); } else { - List members = new LinkedList(group.getMembers()); + List members = new LinkedList<>(group.getMembers()); members.add(new ScimGroupMember(createdUser.getId())); group.setMembers(members); updateGroup(mockMvc, accessToken, group); @@ -790,13 +643,12 @@ public static ScimGroup getGroup(MockMvc mockMvc, String accessToken, String dis builder.header("Host", subdomain + ".localhost"); } SearchResults results = JsonUtils.readValue( - mockMvc.perform(builder - .header("Authorization", "Bearer " + accessToken) - .contentType(APPLICATION_JSON) - .param("filter", filter)) - .andReturn().getResponse().getContentAsString(), - new TypeReference>() { - }); + mockMvc.perform(builder + .header("Authorization", "Bearer " + accessToken) + .contentType(APPLICATION_JSON) + .param("filter", filter)) + .andReturn().getResponse().getContentAsString(), + new TypeReference<>() {}); if (results == null || results.getResources() == null || results.getResources().isEmpty()) { return null; } else { @@ -814,7 +666,7 @@ public static SearchResults getGroups(final MockMvc mockMvc, final St .header("Authorization", "Bearer " + accessToken) .contentType(APPLICATION_JSON)) .andReturn().getResponse().getContentAsString(), - new TypeReference>() { + new TypeReference<>() { }); if (results == null || results.getResources() == null || results.getResources().isEmpty()) { return null; @@ -828,33 +680,32 @@ public static ScimGroup createGroup(MockMvc mockMvc, String accessToken, ScimGro public static ScimGroup createGroup(MockMvc mockMvc, String accessToken, String subdomain, ScimGroup group) throws Exception { MockHttpServletRequestBuilder post = post("/Groups") - .header("Authorization", "Bearer " + accessToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(group)); + .header("Authorization", "Bearer " + accessToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group)); if (hasText(subdomain)) { post.header("Host", subdomain + ".localhost"); } return JsonUtils.readValue( - mockMvc.perform(post) - .andExpect(status().isCreated()) - .andReturn().getResponse().getContentAsString(), - ScimGroup.class); + mockMvc.perform(post) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(), + ScimGroup.class); } - public static ScimGroup createGroup(MockMvc mockMvc, String accessToken, ScimGroup group, String zoneId) throws Exception { MockHttpServletRequestBuilder post = post("/Groups") - .header("Authorization", "Bearer " + accessToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(group)); + .header("Authorization", "Bearer " + accessToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group)); if (hasText(zoneId)) { post.header(IdentityZoneSwitchingFilter.HEADER, zoneId); } return JsonUtils.readValue( - mockMvc.perform(post) - .andExpect(status().isCreated()) - .andReturn().getResponse().getContentAsString(), - ScimGroup.class); + mockMvc.perform(post) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(), + ScimGroup.class); } public static ScimGroup updateGroup(MockMvc mockMvc, String accessToken, ScimGroup group) throws Exception { @@ -867,13 +718,13 @@ public static ScimGroup updateGroup(MockMvc mockMvc, String accessToken, ScimGro put.header("Host", zone.getSubdomain() + ".localhost"); } return JsonUtils.readValue( - mockMvc.perform(put.header("If-Match", group.getVersion()) - .header("Authorization", "Bearer " + accessToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(group))) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(), - ScimGroup.class); + mockMvc.perform(put.header("If-Match", group.getVersion()) + .header("Authorization", "Bearer " + accessToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group))) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + ScimGroup.class); } public static UaaClientDetails createClient(MockMvc mockMvc, String accessToken, UaaClientDetails clientDetails) throws Exception { @@ -886,30 +737,30 @@ public static UaaClientDetails createClient(MockMvc mockMvc, IdentityZone identi public static void deleteClient(MockMvc mockMvc, String accessToken, String clientId, String zoneSubdomain) throws Exception { MockHttpServletRequestBuilder createClientDelete = delete("/oauth/clients/" + clientId) - .header("Authorization", "Bearer " + accessToken) - .accept(APPLICATION_JSON); + .header("Authorization", "Bearer " + accessToken) + .accept(APPLICATION_JSON); if (!zoneSubdomain.equals(IdentityZone.getUaa())) { createClientDelete = createClientDelete.header(IdentityZoneSwitchingFilter.SUBDOMAIN_HEADER, zoneSubdomain); } mockMvc.perform(createClientDelete) - .andExpect(status().is(not(500))); + .andExpect(status().is(not(500))); } public static UaaClientDetails createClient(MockMvc mockMvc, String accessToken, UaaClientDetails clientDetails, - IdentityZone zone, ResultMatcher status) - throws Exception { + IdentityZone zone, ResultMatcher status) + throws Exception { MockHttpServletRequestBuilder createClientPost = post("/oauth/clients") - .header("Authorization", "Bearer " + accessToken) - .accept(APPLICATION_JSON) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(clientDetails)); + .header("Authorization", "Bearer " + accessToken) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(clientDetails)); if (!zone.isUaa()) { createClientPost = createClientPost.header(IdentityZoneSwitchingFilter.HEADER, zone.getId()); } return JsonUtils.readValue( - mockMvc.perform(createClientPost) - .andExpect(status) - .andReturn().getResponse().getContentAsString(), UaaClientDetails.class); + mockMvc.perform(createClientPost) + .andExpect(status) + .andReturn().getResponse().getContentAsString(), UaaClientDetails.class); } public static UaaClientDetails createClient(ApplicationContext context, UaaClientDetails clientDetails, IdentityZone zone) { @@ -925,14 +776,14 @@ public static UaaClientDetails createClient(ApplicationContext context, UaaClien public static ClientDetails createClient(MockMvc mockMvc, String adminAccessToken, String id, String secret, Collection resourceIds, List scopes, List grantTypes, String authorities) throws Exception { return createClient(mockMvc, adminAccessToken, - id, - secret, - resourceIds, - scopes, - grantTypes, - authorities, - Collections.singleton("http://redirect.url"), - IdentityZone.getUaa()); + id, + secret, + resourceIds, + scopes, + grantTypes, + authorities, + Collections.singleton("http://redirect.url"), + IdentityZone.getUaa()); } public static ClientDetails createClient(MockMvc mockMvc, String adminAccessToken, String id, String secret, Collection resourceIds, Collection scopes, Collection grantTypes, String authorities, Set redirectUris, IdentityZone zone) throws Exception { @@ -959,38 +810,38 @@ public static UaaClientDetails updateClient(ApplicationContext context, UaaClien } public static UaaClientDetails updateClient(MockMvc mockMvc, String accessToken, UaaClientDetails clientDetails, IdentityZone zone) - throws Exception { + throws Exception { MockHttpServletRequestBuilder updateClientPut = - put("/oauth/clients/" + clientDetails.getClientId()) - .header("Authorization", "Bearer " + accessToken) - .accept(APPLICATION_JSON) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(clientDetails)); + put("/oauth/clients/" + clientDetails.getClientId()) + .header("Authorization", "Bearer " + accessToken) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(clientDetails)); if (!zone.isUaa()) { updateClientPut = updateClientPut.header(IdentityZoneSwitchingFilter.HEADER, zone.getId()); } return JsonUtils.readValue( - mockMvc.perform(updateClientPut) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(), UaaClientDetails.class); + mockMvc.perform(updateClientPut) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), UaaClientDetails.class); } public static UaaClientDetails getClient(MockMvc mockMvc, String accessToken, String clientId, IdentityZone zone) - throws Exception { + throws Exception { MockHttpServletRequestBuilder readClientGet = - get("/oauth/clients/" + clientId) - .header("Authorization", "Bearer " + accessToken) - .accept(APPLICATION_JSON) - .contentType(APPLICATION_JSON); + get("/oauth/clients/" + clientId) + .header("Authorization", "Bearer " + accessToken) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON); if (!zone.isUaa()) { readClientGet = readClientGet.header(IdentityZoneSwitchingFilter.HEADER, zone.getId()); } return JsonUtils.readValue( - mockMvc.perform(readClientGet) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(), UaaClientDetails.class); + mockMvc.perform(readClientGet) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), UaaClientDetails.class); } public static String getZoneAdminToken(MockMvc mockMvc, String adminToken, String zoneId) throws Exception { @@ -1008,15 +859,14 @@ public static String getZoneAdminToken(MockMvc mockMvc, String adminToken, Strin group.setMembers(Collections.singletonList(new ScimGroupMember(user.getId()))); MockMvcUtils.createGroup(mockMvc, adminToken, group); return getUserOAuthAccessTokenAuthCode(mockMvc, - "identity", - "identitysecret", - user.getId(), - user.getUserName(), - "secr3T", - group.getDisplayName(), - zoneId + "identity", + "identitysecret", + user.getId(), + user.getUserName(), + "secr3T", + group.getDisplayName(), + zoneId ); - } public static String getUserOAuthAccessToken(MockMvc mockMvc, @@ -1036,13 +886,13 @@ public static String getUserOAuthAccessToken(MockMvc mockMvc, String scope, IdentityZone zone) throws Exception { return getUserOAuthAccessToken(mockMvc, - clientId, - clientSecret, - username, - password, - scope, - zone, - false); + clientId, + clientSecret, + username, + password, + scope, + zone, + false); } public static String getUserOAuthAccessToken(MockMvc mockMvc, @@ -1054,15 +904,15 @@ public static String getUserOAuthAccessToken(MockMvc mockMvc, IdentityZone zone, boolean opaque) throws Exception { String basicDigestHeaderValue = "Basic " - + new String(Base64.encodeBase64((clientId + ":" + clientSecret).getBytes())); + + new String(Base64.encodeBase64((clientId + ":" + clientSecret).getBytes())); MockHttpServletRequestBuilder oauthTokenPost = - post("/oauth/token") - .header("Authorization", basicDigestHeaderValue) - .param("grant_type", "password") - .param("client_id", clientId) - .param("username", username) - .param("password", password) - .param("scope", scope); + post("/oauth/token") + .header("Authorization", basicDigestHeaderValue) + .param("grant_type", "password") + .param("client_id", clientId) + .param("username", username) + .param("password", password) + .param("scope", scope); if (zone != null) { oauthTokenPost.header("Host", zone.getSubdomain() + ".localhost"); } @@ -1072,7 +922,7 @@ public static String getUserOAuthAccessToken(MockMvc mockMvc, MvcResult result = mockMvc.perform(oauthTokenPost).andDo(print()).andExpect(status().isOk()).andReturn(); OAuthToken oauthToken = JsonUtils.readValue(result.getResponse().getContentAsString(), - OAuthToken.class); + OAuthToken.class); return oauthToken.accessToken; } @@ -1099,30 +949,30 @@ public static String getUserOAuthAccessTokenAuthCode(MockMvc mockMvc, String cli public static String getUserOAuthAccessTokenAuthCode(MockMvc mockMvc, String clientId, String clientSecret, String userId, String username, String password, String scope, String zoneId, TokenFormat tokenFormat) throws Exception { String basicDigestHeaderValue = "Basic " - + new String(org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + clientSecret) - .getBytes())); + + new String(org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + clientSecret) + .getBytes())); UaaPrincipal p = new UaaPrincipal(userId, username, "test@test.org", OriginKeys.UAA, "", zoneId); UaaAuthentication auth = new UaaAuthentication(p, UaaAuthority.USER_AUTHORITIES, null); - Assert.assertTrue(auth.isAuthenticated()); + assertThat(auth.isAuthenticated()).isTrue(); SecurityContextHolder.getContext().setAuthentication(auth); MockHttpSession session = new MockHttpSession(); session.setAttribute( - HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, - new MockSecurityContext(auth) + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + new MockSecurityContext(auth) ); String state = new AlphanumericRandomValueStringGenerator().generate(); MockHttpServletRequestBuilder authRequest = get("/oauth/authorize") - .header("Authorization", basicDigestHeaderValue) - .header("Accept", MediaType.APPLICATION_JSON_VALUE) - .session(session) - .param(OAuth2Utils.GRANT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE) - .param(OAuth2Utils.RESPONSE_TYPE, "code") - .param(TokenConstants.REQUEST_TOKEN_FORMAT, tokenFormat.getStringValue()) - .param(OAuth2Utils.STATE, state) - .param(OAuth2Utils.CLIENT_ID, clientId) - .param(OAuth2Utils.REDIRECT_URI, "http://localhost/test"); + .header("Authorization", basicDigestHeaderValue) + .header("Accept", MediaType.APPLICATION_JSON_VALUE) + .session(session) + .param(OAuth2Utils.GRANT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE) + .param(OAuth2Utils.RESPONSE_TYPE, "code") + .param(TokenConstants.REQUEST_TOKEN_FORMAT, tokenFormat.getStringValue()) + .param(OAuth2Utils.STATE, state) + .param(OAuth2Utils.CLIENT_ID, clientId) + .param(OAuth2Utils.REDIRECT_URI, "http://localhost/test"); if (StringUtils.hasText(scope)) { authRequest.param(OAuth2Utils.SCOPE, scope); } @@ -1133,28 +983,27 @@ public static String getUserOAuthAccessTokenAuthCode(MockMvc mockMvc, String cli String code = builder.build().getQueryParams().get("code").get(0); authRequest = post("/oauth/token") - .header("Authorization", basicDigestHeaderValue) - .header("Accept", MediaType.APPLICATION_JSON_VALUE) - .param(OAuth2Utils.GRANT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE) - .param("code", code) - .param(OAuth2Utils.CLIENT_ID, clientId) - .param(OAuth2Utils.REDIRECT_URI, "http://localhost/test"); + .header("Authorization", basicDigestHeaderValue) + .header("Accept", MediaType.APPLICATION_JSON_VALUE) + .param(OAuth2Utils.GRANT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE) + .param("code", code) + .param(OAuth2Utils.CLIENT_ID, clientId) + .param(OAuth2Utils.REDIRECT_URI, "http://localhost/test"); if (StringUtils.hasText(scope)) { authRequest.param(OAuth2Utils.SCOPE, scope); } result = mockMvc.perform(authRequest).andExpect(status().is2xxSuccessful()).andReturn(); OAuthToken oauthToken = JsonUtils.readValue(result.getResponse().getContentAsString(), - OAuthToken.class); + OAuthToken.class); return oauthToken.accessToken; - } public static String getScimInviteUserToken(MockMvc mockMvc, String clientId, String clientSecret, IdentityZone zone, String adminClientId, String adminClientSecret) throws Exception { String adminToken = getClientCredentialsOAuthAccessToken(mockMvc, adminClientId, adminClientSecret, - "", - zone == null ? null : zone.getSubdomain() + "", + zone == null ? null : zone.getSubdomain() ); // create a user (with the required permissions) to perform the actual /invite_users action String username = new AlphanumericRandomValueStringGenerator().generate().toLowerCase() + "@example.com"; @@ -1171,9 +1020,9 @@ public static String getScimInviteUserToken(MockMvc mockMvc, String clientId, St createGroup(mockMvc, adminToken, zone.getSubdomain(), inviteGroup); } ScimGroup group = getGroup(mockMvc, - adminToken, - scope, - zone == null ? null : zone.getSubdomain() + adminToken, + scope, + zone == null ? null : zone.getSubdomain() ); group.getMembers().add(member); updateGroup(mockMvc, adminToken, group, zone); @@ -1181,16 +1030,15 @@ public static String getScimInviteUserToken(MockMvc mockMvc, String clientId, St // get a bearer token for the user return getUserOAuthAccessToken(mockMvc, - clientId, - clientSecret, - user.getUserName(), - "password", - "scim.invite", - zone + clientId, + clientSecret, + user.getUserName(), + "password", + "scim.invite", + zone ); } - public static String getClientCredentialsOAuthAccessToken(MockMvc mockMvc, String clientId, String clientSecret, @@ -1207,22 +1055,22 @@ public static String getClientCredentialsOAuthAccessToken(MockMvc mockMvc, boolean opaque) throws Exception { MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token") .with(httpBasic(clientId, clientSecret)) - .param("grant_type", "client_credentials") - .param("client_id", clientId) - .param("recovable", "true"); - if (!isEmpty(scope)) { + .param("grant_type", "client_credentials") + .param("client_id", clientId) + .param("revocable", "true"); + if (!hasText(scope)) { oauthTokenPost.param("scope", scope); } - if (subdomain != null && !subdomain.equals("")) { + if (subdomain != null && !subdomain.isEmpty()) { oauthTokenPost.with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")); } if (opaque) { oauthTokenPost.param(TokenConstants.REQUEST_TOKEN_FORMAT, OPAQUE.getStringValue()); } MvcResult result = mockMvc.perform(oauthTokenPost) - .andDo(print()) - .andExpect(status().isOk()) - .andReturn(); + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); OAuthToken oauthToken = JsonUtils.readValue(result.getResponse().getContentAsString(), OAuthToken.class); return oauthToken.accessToken; } @@ -1265,8 +1113,135 @@ public static void removeEventListener(ListableBeanFactory applicationContext, A } } + public static RequestPostProcessor httpBearer(String authorization) { + return new HttpBearerAuthRequestPostProcessor(authorization); + } + + public static IdentityZone updateZone(MockMvc mockMvc, IdentityZone updatedZone) throws Exception { + String token = + getClientCredentialsOAuthAccessToken(mockMvc, "admin", "adminsecret", "uaa.admin", null); + + String responseAsString = + mockMvc.perform(put("/identity-zones/" + updatedZone.getId()) + .header("Authorization", "Bearer " + token) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(updatedZone))) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + return JsonUtils.readValue(responseAsString, IdentityZone.class); + } + + public static class MockSavedRequest extends DefaultSavedRequest { + + public MockSavedRequest() { + super(new MockHttpServletRequest(), new PortResolverImpl()); + } + + @Override + public String getRedirectUrl() { + return "http://test/redirect/oauth/authorize"; + } + + @Override + public String[] getParameterValues(String name) { + if ("client_id".equals(name)) { + return new String[]{"admin"}; + } + return new String[0]; + } + + @Override + public List getCookies() { + return null; + } + + @Override + public String getMethod() { + return null; + } + + @Override + public List getHeaderValues(String name) { + return null; + } + + @Override + public Collection getHeaderNames() { + return null; + } + + @Override + public List getLocales() { + return null; + } + + @Override + public Map getParameterMap() { + return null; + } + } + + public static class ZoneScimInviteData { + private final IdentityZoneCreationResult zone; + private final String adminToken; + private final ClientDetails scimInviteClient; + private final String defaultZoneAdminToken; + + public ZoneScimInviteData(String adminToken, + IdentityZoneCreationResult zone, + ClientDetails scimInviteClient, + String defaultZoneAdminToken) { + this.adminToken = adminToken; + this.zone = zone; + this.scimInviteClient = scimInviteClient; + this.defaultZoneAdminToken = defaultZoneAdminToken; + } + + public ClientDetails getScimInviteClient() { + return scimInviteClient; + } + + public String getDefaultZoneAdminToken() { + return defaultZoneAdminToken; + } + + public IdentityZoneCreationResult getZone() { + return zone; + } + + public String getAdminToken() { + return adminToken; + } + } + + public static class IdentityZoneCreationResult { + private final IdentityZone identityZone; + private final UaaPrincipal zoneAdmin; + private final String zoneAdminToken; + + public IdentityZoneCreationResult(IdentityZone identityZone, UaaPrincipal zoneAdmin, String zoneAdminToken) { + this.identityZone = identityZone; + this.zoneAdmin = zoneAdmin; + this.zoneAdminToken = zoneAdminToken; + } + + public IdentityZone getIdentityZone() { + return identityZone; + } + + public UaaPrincipal getZoneAdminUser() { + return zoneAdmin; + } + + public String getZoneAdminToken() { + return zoneAdminToken; + } + } + public static class MockSecurityContext implements SecurityContext { + @Serial private static final long serialVersionUID = -1386535243513362694L; private Authentication authentication; @@ -1290,6 +1265,10 @@ public static class CookieCsrfPostProcessor implements RequestPostProcessor { private boolean useInvalidToken = false; + public static CookieCsrfPostProcessor cookieCsrf() { + return new CookieCsrfPostProcessor(); + } + public CookieCsrfPostProcessor useInvalidToken() { useInvalidToken = true; return this; @@ -1329,18 +1308,10 @@ protected void addCsrfCookie(MockHttpServletRequest request, Cookie cookie, Cook request.setCookies(newcookies); } } - - public static CookieCsrfPostProcessor cookieCsrf() { - return new CookieCsrfPostProcessor(); - } - } - - public static RequestPostProcessor httpBearer(String authorization) { - return new HttpBearerAuthRequestPostProcessor(authorization); } private static class HttpBearerAuthRequestPostProcessor implements RequestPostProcessor { - private String headerValue; + private final String headerValue; private HttpBearerAuthRequestPostProcessor(String authorization) { this.headerValue = "Bearer " + authorization; @@ -1361,19 +1332,4 @@ public String generate() { return "test" + counter.incrementAndGet(); } } - - public static IdentityZone updateZone(MockMvc mockMvc, IdentityZone updatedZone) throws Exception { - String token = - getClientCredentialsOAuthAccessToken(mockMvc, "admin", "adminsecret", "uaa.admin", null); - - String responseAsString = - mockMvc.perform(put("/identity-zones/" + updatedZone.getId()) - .header("Authorization", "Bearer " + token) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(updatedZone))) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); - return JsonUtils.readValue(responseAsString, IdentityZone.class); - } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointDocs.java index 236b29ecca6..b6d0370e646 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointDocs.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointDocs.java @@ -120,40 +120,43 @@ class IdentityZoneEndpointDocs extends EndpointDocs { private static final String SECRET_POLICY_LOWERCASE = "Minimum number of lowercase characters required for secret to be considered valid (defaults to 0)."; private static final String SECRET_POLICY_DIGIT = "Minimum number of digits required for secret to be considered valid (defaults to 0)."; private static final String SECRET_POLICY_SPECIAL_CHAR = "Minimum number of special characters required for secret to be considered valid (defaults to 0)."; - private static final String SECRET_POLICY_EXPIRE_MONTHS = "Number of months after which current secret expires (defaults to 0)."; private static final String USER_CONFIG_USER_LIMIT_DESCRIPTION = "Number of users in the zone. If more than 0, it limits the amount of users in the zone. (defaults to -1, no limit)."; private static final String USER_CONFIG_USER_LIMIT_CONSTRAINT = "Optional number, default -1, no limit."; private static final String USER_CONFIG_CHECK_ORIGIN_ENABLED = "Flag for switching on the check if origin is valid when creating or updating users (defaults to false)"; private static final String USER_CONFIG_ALLOW_ORIGIN_LOOP = "Flag for switching off the loop over all origins in a zone (defaults to true)"; private static final String SERVICE_PROVIDER_KEY = - "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIIBOwIBAAJBAJv8ZpB5hEK7qxP9K3v43hUS5fGT4waKe7ix4Z4mu5UBv+cw7WSF\n" + - "At0Vaag0sAbsPzU8Hhsrj/qPABvfB8asUwcCAwEAAQJAG0r3ezH35WFG1tGGaUOr\n" + - "QA61cyaII53ZdgCR1IU8bx7AUevmkFtBf+aqMWusWVOWJvGu2r5VpHVAIl8nF6DS\n" + - "kQIhAMjEJ3zVYa2/Mo4ey+iU9J9Vd+WoyXDQD4EEtwmyG1PpAiEAxuZlvhDIbbce\n" + - "7o5BvOhnCZ2N7kYb1ZC57g3F+cbJyW8CIQCbsDGHBto2qJyFxbAO7uQ8Y0UVHa0J\n" + - "BO/g900SAcJbcQIgRtEljIShOB8pDjrsQPxmI1BLhnjD1EhRSubwhDw5AFUCIQCN\n" + - "A24pDtdOHydwtSB5+zFqFLfmVZplQM/g5kb4so70Yw==\n" + - "-----END RSA PRIVATE KEY-----\n"; + """ + -----BEGIN RSA PRIVATE KEY----- + MIIBOwIBAAJBAJv8ZpB5hEK7qxP9K3v43hUS5fGT4waKe7ix4Z4mu5UBv+cw7WSF + At0Vaag0sAbsPzU8Hhsrj/qPABvfB8asUwcCAwEAAQJAG0r3ezH35WFG1tGGaUOr + QA61cyaII53ZdgCR1IU8bx7AUevmkFtBf+aqMWusWVOWJvGu2r5VpHVAIl8nF6DS + kQIhAMjEJ3zVYa2/Mo4ey+iU9J9Vd+WoyXDQD4EEtwmyG1PpAiEAxuZlvhDIbbce + 7o5BvOhnCZ2N7kYb1ZC57g3F+cbJyW8CIQCbsDGHBto2qJyFxbAO7uQ8Y0UVHa0J + BO/g900SAcJbcQIgRtEljIShOB8pDjrsQPxmI1BLhnjD1EhRSubwhDw5AFUCIQCN + A24pDtdOHydwtSB5+zFqFLfmVZplQM/g5kb4so70Yw== + -----END RSA PRIVATE KEY----- + """; private static final String SERVICE_PROVIDER_KEY_PASSWORD = "password"; private static final String SERVICE_PROVIDER_CERTIFICATE = - "-----BEGIN CERTIFICATE-----\n" + - "MIICEjCCAXsCAg36MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG\n" + - "A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE\n" + - "MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl\n" + - "YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw\n" + - "ODIyMDUyNjU0WhcNMTcwODIxMDUyNjU0WjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE\n" + - "CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs\n" + - "ZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAm/xmkHmEQrurE/0re/jeFRLl\n" + - "8ZPjBop7uLHhnia7lQG/5zDtZIUC3RVpqDSwBuw/NTweGyuP+o8AG98HxqxTBwID\n" + - "AQABMA0GCSqGSIb3DQEBBQUAA4GBABS2TLuBeTPmcaTaUW/LCB2NYOy8GMdzR1mx\n" + - "8iBIu2H6/E2tiY3RIevV2OW61qY2/XRQg7YPxx3ffeUugX9F4J/iPnnu1zAxxyBy\n" + - "2VguKv4SWjRFoRkIfIlHX0qVviMhSlNy2ioFLy7JcPZb+v3ftDGywUqcBiVDoea0\n" + - "Hn+GmxZA\n" + - "-----END CERTIFICATE-----\n"; + """ + -----BEGIN CERTIFICATE----- + MIICEjCCAXsCAg36MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG + A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE + MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl + YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw + ODIyMDUyNjU0WhcNMTcwODIxMDUyNjU0WjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE + CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs + ZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAm/xmkHmEQrurE/0re/jeFRLl + 8ZPjBop7uLHhnia7lQG/5zDtZIUC3RVpqDSwBuw/NTweGyuP+o8AG98HxqxTBwID + AQABMA0GCSqGSIb3DQEBBQUAA4GBABS2TLuBeTPmcaTaUW/LCB2NYOy8GMdzR1mx + 8iBIu2H6/E2tiY3RIevV2OW61qY2/XRQg7YPxx3ffeUugX9F4J/iPnnu1zAxxyBy + 2VguKv4SWjRFoRkIfIlHX0qVviMhSlNy2ioFLy7JcPZb+v3ftDGywUqcBiVDoea0 + Hn+GmxZA + -----END CERTIFICATE----- + """; private static final String SAML_ACTIVE_KEY_ID_DESC = "The ID of the key that should be used for signing metadata and assertions."; private static final String DEFAULT_ZONE_GROUPS_DESC = "Default groups each user in the zone inherits."; @@ -397,7 +400,7 @@ void getAllIdentityZones() throws Exception { fieldWithPath("[].config.samlConfig.wantAssertionSigned").description(WANT_ASSERTION_SIGNED_DESC), fieldWithPath("[].config.samlConfig.requestSigned").description(REQUEST_SIGNED_DESC), fieldWithPath("[].config.samlConfig.entityID").optional().type(STRING).description(ENTITY_ID_DESC), - fieldWithPath("[].config.samlConfig.certificate").type(STRING).description(CERTIFICATE_DESC).attributes(key("constraints").value("Deprecated")), + fieldWithPath("[].config.samlConfig.certificate").optional().type(STRING).description(CERTIFICATE_DESC).attributes(key("constraints").value("Deprecated")), fieldWithPath("[].config.samlConfig.activeKeyId").type(STRING).description(SAML_ACTIVE_KEY_ID_DESC), fieldWithPath("[].config.samlConfig.keys").ignored().type(OBJECT).description(CERTIFICATE_DESC), diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 4e5eed50f78..c1215cde525 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -19,9 +19,15 @@ import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.scim.*; +import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.scim.ScimGroup; +import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; +import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; +import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager; +import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.event.GroupModifiedEvent; import org.cloudfoundry.identity.uaa.scim.event.UserModifiedEvent; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; @@ -32,11 +38,22 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.KeyWithCertTest; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.zone.*; +import org.cloudfoundry.identity.uaa.zone.BrandingInformation; import org.cloudfoundry.identity.uaa.zone.BrandingInformation.Banner; +import org.cloudfoundry.identity.uaa.zone.Consent; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; +import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; +import org.cloudfoundry.identity.uaa.zone.UserConfig; +import org.cloudfoundry.identity.uaa.zone.ZoneManagementScopes; import org.cloudfoundry.identity.uaa.zone.event.IdentityZoneModifiedEvent; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; @@ -56,84 +73,97 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.context.WebApplicationContext; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat.OPAQUE; import static org.cloudfoundry.identity.uaa.util.UaaStringUtils.EMPTY_STRING; -import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsInstanceOf.instanceOf; -import static org.junit.Assert.*; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.TEXT_HTML_VALUE; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.util.StringUtils.hasText; // TODO: This class has a lot of helpers, why? @DefaultTestContext class IdentityZoneEndpointsMockMvcTests { - private final String serviceProviderKey = - "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5\n" + - "L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vA\n" + - "fpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQAB\n" + - "AoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges\n" + - "7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGu\n" + - "lhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3Xp\n" + - "ubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYX\n" + - "kKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyL\n" + - "gu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWK\n" + - "vw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCe\n" + - "A2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHS\n" + - "N+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMB\n" + - "qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/\n" + - "-----END RSA PRIVATE KEY-----"; + private final String serviceProviderKey = """ + -----BEGIN RSA PRIVATE KEY----- + MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5 + L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vA + fpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQAB + AoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges + 7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGu + lhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3Xp + ubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYX + kKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyL + gu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWK + vw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCe + A2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHS + N+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMB + qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ + -----END RSA PRIVATE KEY-----"""; private final String serviceProviderKeyPassword = "password"; - private final String serviceProviderCertificate = - "-----BEGIN CERTIFICATE-----\n" + - "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO\n" + - "MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO\n" + - "MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h\n" + - "cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx\n" + - "CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM\n" + - "BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb\n" + - "BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN\n" + - "ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W\n" + - "qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw\n" + - "znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha\n" + - "MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc\n" + - "gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD\n" + - "VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD\n" + - "VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh\n" + - "QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ\n" + - "0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC\n" + - "KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK\n" + - "RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + - "-----END CERTIFICATE-----\n"; + private final String serviceProviderCertificate = """ + -----BEGIN CERTIFICATE----- + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO + MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO + MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h + cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx + CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM + BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb + BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN + ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W + qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw + znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha + MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc + gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD + VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD + VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh + QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ + 0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC + KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK + RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= + -----END CERTIFICATE-----"""; + + private final AlphanumericRandomValueStringGenerator generator = new AlphanumericRandomValueStringGenerator(); private String identityClientToken = null; private String identityClientZonesReadToken = null; private String identityClientZonesWriteToken = null; private String adminToken = null; - private AlphanumericRandomValueStringGenerator generator = new AlphanumericRandomValueStringGenerator(); private TestApplicationEventListener zoneModifiedEventListener; private TestApplicationEventListener clientCreateEventListener; private TestApplicationEventListener clientDeleteEventListener; private TestApplicationEventListener groupModifiedEventListener; private TestApplicationEventListener userModifiedEventListener; private TestApplicationEventListener uaaEventListener; - private String lowPriviledgeToken; + private String lowPrivilegeToken; private JdbcIdentityZoneProvisioning provisioning; private String uaaAdminClientToken; private String uaaAdminUserToken; @@ -169,12 +199,10 @@ void setUp( "secret", "uaa.admin"); - ScimUser uaaAdminUser = createUser(uaaAdminClientToken, null); String groupId = scimGroupProvisioning.getByName("uaa.admin", IdentityZone.getUaaZoneId()).getId(); - scimGroupMembershipManager.addMember(groupId, new ScimGroupMember(uaaAdminUser.getId()), IdentityZone.getUaaZoneId()); - + scimGroupMembershipManager.addMember(groupId, new ScimGroupMember<>(uaaAdminUser.getId()), IdentityZone.getUaaZoneId()); uaaAdminUserToken = testClient.getUserOAuthAccessToken( uaaAdminClient.getClientId(), @@ -209,7 +237,7 @@ void setUp( "admin", "adminsecret", ""); - lowPriviledgeToken = testClient.getClientCredentialsOAuthAccessToken( + lowPrivilegeToken = testClient.getClientCredentialsOAuthAccessToken( "admin", "adminsecret", "scim.read"); @@ -246,16 +274,16 @@ void read_zone_as_with_uaa_admin() throws Exception { IdentityZone zone = createZoneUsingToken(uaaAdminClientToken); for (String token : Arrays.asList(uaaAdminClientToken, uaaAdminUserToken)) { mockMvc.perform( - get("/identity-zones") - .header("Authorization", "Bearer " + token) - .header("Accept", MediaType.APPLICATION_JSON_VALUE) - ) + get("/identity-zones") + .header("Authorization", "Bearer " + token) + .header("Accept", MediaType.APPLICATION_JSON_VALUE) + ) .andExpect(status().isOk()); mockMvc.perform( - get("/identity-zones/{id}", zone.getId()) - .header("Authorization", "Bearer " + token) - .header("Accept", MediaType.APPLICATION_JSON_VALUE) - ) + get("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + token) + .header("Accept", MediaType.APPLICATION_JSON_VALUE) + ) .andExpect(status().isOk()); } } @@ -281,9 +309,9 @@ void delete_zone_as_with_uaa_admin() throws Exception { for (String token : Arrays.asList(uaaAdminClientToken, uaaAdminUserToken)) { IdentityZone zone = createZoneUsingToken(token); mockMvc.perform( - delete("/identity-zones/{id}", zone.getId()) - .header("Authorization", "Bearer " + token) - .accept(APPLICATION_JSON)) + delete("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + token) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()); } } @@ -299,8 +327,8 @@ void readWithoutTokenShouldFail(String url) throws Exception { @ArgumentsSource(IdentityZonesBaseUrlsArgumentsSource.class) void readWith_Write_TokenShouldNotFail(String url) throws Exception { mockMvc.perform( - get(url) - .header("Authorization", "Bearer " + identityClientZonesWriteToken)) + get(url) + .header("Authorization", "Bearer " + identityClientZonesWriteToken)) .andExpect(status().isOk()); } @@ -308,8 +336,8 @@ void readWith_Write_TokenShouldNotFail(String url) throws Exception { @ArgumentsSource(IdentityZonesBaseUrlsArgumentsSource.class) void readWith_Read_TokenShouldSucceed(String url) throws Exception { mockMvc.perform( - get(url) - .header("Authorization", "Bearer " + identityClientZonesReadToken)) + get(url) + .header("Authorization", "Bearer " + identityClientZonesReadToken)) .andExpect(status().isOk()); } @@ -318,13 +346,13 @@ void create_zone_no_links() throws Exception { String id = generator.generate().toLowerCase(); IdentityZoneConfiguration zoneConfiguration = new IdentityZoneConfiguration(); IdentityZone created = createZone(id, HttpStatus.CREATED, identityClientToken, zoneConfiguration); - assertNull(created.getConfig().getLinks().getHomeRedirect()); - assertNull(created.getConfig().getLinks().getSelfService().getSignup()); - assertNull(created.getConfig().getLinks().getSelfService().getPasswd()); + assertThat(created.getConfig().getLinks().getHomeRedirect()).isNull(); + assertThat(created.getConfig().getLinks().getSelfService().getSignup()).isNull(); + assertThat(created.getConfig().getLinks().getSelfService().getPasswd()).isNull(); IdentityZone retrieved = getIdentityZone(id, HttpStatus.OK, identityClientToken); - assertNull(retrieved.getConfig().getLinks().getHomeRedirect()); - assertNull(retrieved.getConfig().getLinks().getSelfService().getSignup()); - assertNull(retrieved.getConfig().getLinks().getSelfService().getPasswd()); + assertThat(retrieved.getConfig().getLinks().getHomeRedirect()).isNull(); + assertThat(retrieved.getConfig().getLinks().getSelfService().getSignup()).isNull(); + assertThat(retrieved.getConfig().getLinks().getSelfService().getPasswd()).isNull(); } @Test @@ -335,22 +363,22 @@ void create_and_update_with_links() throws Exception { zoneConfiguration.getLinks().getSelfService().setSignup("/signup"); zoneConfiguration.getLinks().getSelfService().setPasswd("/passwd"); IdentityZone created = createZone(id, HttpStatus.CREATED, identityClientToken, zoneConfiguration); - assertEquals("/home", created.getConfig().getLinks().getHomeRedirect()); - assertEquals("/signup", created.getConfig().getLinks().getSelfService().getSignup()); - assertEquals("/passwd", created.getConfig().getLinks().getSelfService().getPasswd()); + assertThat(created.getConfig().getLinks().getHomeRedirect()).isEqualTo("/home"); + assertThat(created.getConfig().getLinks().getSelfService().getSignup()).isEqualTo("/signup"); + assertThat(created.getConfig().getLinks().getSelfService().getPasswd()).isEqualTo("/passwd"); IdentityZone retrieved = getIdentityZone(id, HttpStatus.OK, identityClientToken); - assertEquals("/home", retrieved.getConfig().getLinks().getHomeRedirect()); - assertEquals("/signup", retrieved.getConfig().getLinks().getSelfService().getSignup()); - assertEquals("/passwd", retrieved.getConfig().getLinks().getSelfService().getPasswd()); + assertThat(retrieved.getConfig().getLinks().getHomeRedirect()).isEqualTo("/home"); + assertThat(retrieved.getConfig().getLinks().getSelfService().getSignup()).isEqualTo("/signup"); + assertThat(retrieved.getConfig().getLinks().getSelfService().getPasswd()).isEqualTo("/passwd"); zoneConfiguration = created.getConfig(); zoneConfiguration.getLinks().setHomeRedirect(null); zoneConfiguration.getLinks().getSelfService().setSignup(null); zoneConfiguration.getLinks().getSelfService().setPasswd(null); IdentityZone updated = updateZone(created, HttpStatus.OK, identityClientToken); - assertNull(updated.getConfig().getLinks().getHomeRedirect()); - assertNull(updated.getConfig().getLinks().getSelfService().getSignup()); - assertNull(updated.getConfig().getLinks().getSelfService().getPasswd()); + assertThat(updated.getConfig().getLinks().getHomeRedirect()).isNull(); + assertThat(updated.getConfig().getLinks().getSelfService().getSignup()).isNull(); + assertThat(updated.getConfig().getLinks().getSelfService().getPasswd()).isNull(); } @Test @@ -358,10 +386,10 @@ void testGetZoneAsIdentityClient() throws Exception { String id = generator.generate(); IdentityZone created = createZone(id, HttpStatus.CREATED, identityClientToken, new IdentityZoneConfiguration()); IdentityZone retrieved = getIdentityZone(id, HttpStatus.OK, identityClientToken); - assertEquals(created.getId(), retrieved.getId()); - assertEquals(created.getName(), retrieved.getName()); - assertEquals(created.getSubdomain(), retrieved.getSubdomain()); - assertEquals(created.getDescription(), retrieved.getDescription()); + assertThat(retrieved.getId()).isEqualTo(created.getId()); + assertThat(retrieved.getName()).isEqualTo(created.getName()); + assertThat(retrieved.getSubdomain()).isEqualTo(created.getSubdomain()); + assertThat(retrieved.getDescription()).isEqualTo(created.getDescription()); } @Test @@ -369,14 +397,9 @@ void test_bootstrapped_system_scopes() throws Exception { String id = generator.generate(); createZone(id, HttpStatus.CREATED, identityClientToken, new IdentityZoneConfiguration()); List groups = webApplicationContext.getBean(JdbcScimGroupProvisioning.class) - .retrieveAll(id).stream().map(ScimGroup::getDisplayName).collect(Collectors.toList()); - - ZoneManagementScopes.getSystemScopes() - .forEach( - scope -> - assertTrue("Scope:" + scope + " should have been bootstrapped into the new zone", groups.contains(scope)) - ); + .retrieveAll(id).stream().map(ScimGroup::getDisplayName).toList(); + assertThat(groups).as("Scopes should have been bootstrapped into the new zone").containsAll(ZoneManagementScopes.getSystemScopes()); } @Test @@ -385,18 +408,18 @@ void testGetZonesAsIdentityClient() throws Exception { IdentityZone created = createZone(id, HttpStatus.CREATED, identityClientToken, new IdentityZoneConfiguration()); mockMvc.perform( - get("/identity-zones/") - .header("Authorization", "Bearer " + lowPriviledgeToken)) + get("/identity-zones/") + .header("Authorization", "Bearer " + lowPrivilegeToken)) .andExpect(status().isForbidden()); MvcResult result = mockMvc.perform( - get("/identity-zones/") - .header("Authorization", "Bearer " + identityClientToken)) + get("/identity-zones/") + .header("Authorization", "Bearer " + identityClientToken)) .andExpect(status().isOk()) .andReturn(); - List zones = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() { + List zones = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { }); IdentityZone retrieved = null; for (IdentityZone identityZone : zones) { @@ -405,10 +428,11 @@ void testGetZonesAsIdentityClient() throws Exception { } } - assertEquals(created.getId(), retrieved.getId()); - assertEquals(created.getName(), retrieved.getName()); - assertEquals(created.getSubdomain(), retrieved.getSubdomain()); - assertEquals(created.getDescription(), retrieved.getDescription()); + assertThat(retrieved) + .returns(created.getId(), IdentityZone::getId) + .returns(created.getName(), IdentityZone::getName) + .returns(created.getSubdomain(), IdentityZone::getSubdomain) + .returns(created.getDescription(), IdentityZone::getDescription); } @Test @@ -425,7 +449,7 @@ void testCreateZone() throws Exception { @Test void updateZoneCreatesGroups() throws Exception { IdentityZone zone = createZoneReturn(); - List zoneGroups = new LinkedList(zone.getConfig().getUserConfig().getDefaultGroups()); + List zoneGroups = new LinkedList<>(zone.getConfig().getUserConfig().getDefaultGroups()); //test two times with the same groups zone = updateZone(zone, HttpStatus.OK, identityClientToken); @@ -438,7 +462,7 @@ void updateZoneCreatesGroups() throws Exception { //validate that default groups got created ScimGroupProvisioning groupProvisioning = webApplicationContext.getBean(ScimGroupProvisioning.class); for (String g : zoneGroups) { - assertNotNull(groupProvisioning.getByName(g, zone.getId())); + assertThat(groupProvisioning.getByName(g, zone.getId())).isNotNull(); } } @@ -449,15 +473,15 @@ void createZoneWithNoNameFailsWithUnprocessableEntity() throws Exception { zone.setName(null); mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isUnprocessableEntity()) .andExpect(jsonPath("$.error").value("invalid_identity_zone")) .andExpect(jsonPath("$.error_description").value("The identity zone must be given a name.")); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test @@ -467,15 +491,15 @@ void createZoneWithNoSubdomainFailsWithUnprocessableEntity() throws Exception { zone.setSubdomain(null); mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isUnprocessableEntity()) .andExpect(jsonPath("$.error").value("invalid_identity_zone")) .andExpect(jsonPath("$.error_description").value("The subdomain must be provided.")); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test @@ -486,16 +510,16 @@ void createZoneWithNoAllowedGroupsFailsWithUnprocessableEntity() throws Exceptio zone.getConfig().getUserConfig().setAllowedGroups(Collections.emptyList()); // no groups allowed mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isUnprocessableEntity()) .andExpect(jsonPath("$.error").value("invalid_identity_zone")) .andExpect(jsonPath("$.error_description").value("The identity zone details are invalid. " + - "The zone configuration is invalid. At least one group must be allowed")); + "The zone configuration is invalid. At least one group must be allowed")); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test @@ -517,19 +541,18 @@ void createZone_ShouldOnlyCreateGroupsForSystemScopesThatAreInAllowList() throws final String zoneAdminUserToken = createZoneAdminAndGetToken(idzId); final SearchResults groupsResult = MockMvcUtils.getGroups(mockMvc, zoneAdminUserToken, idzId); - Assertions.assertNotNull(groupsResult); - Assertions.assertEquals(2, groupsResult.getTotalResults()); + assertThat(groupsResult).isNotNull(); + assertThat(groupsResult.getTotalResults()).isEqualTo(2); final Set groupNamesInZone = groupsResult.getResources().stream() .map(ScimGroup::getDisplayName) .collect(Collectors.toSet()); - Assertions.assertEquals(2, groupNamesInZone.size()); - Assertions.assertTrue(groupNamesInZone.contains("scim.read")); - Assertions.assertTrue(groupNamesInZone.contains("scim.write")); + assertThat(groupNamesInZone) + .containsExactlyInAnyOrder("scim.read", "scim.write"); } private String createZoneAdminAndGetToken(final String idzId) throws Exception { - final String adminToken = MockMvcUtils.getClientOAuthAccessToken( + final String myAdminToken = MockMvcUtils.getClientOAuthAccessToken( mockMvc, "admin", "adminsecret", @@ -539,7 +562,7 @@ private String createZoneAdminAndGetToken(final String idzId) throws Exception { final String zoneAdminScope = "zones.%s.admin".formatted(idzId); final ScimUser zoneAdminUser = MockMvcUtils.createAdminForZone( mockMvc, - adminToken, + myAdminToken, zoneAdminScope, IdentityZone.getUaaZoneId() ); @@ -555,13 +578,12 @@ private String createZoneAdminAndGetToken(final String idzId) throws Exception { ); } - @Test void testCreateZoneInsufficientScope() throws Exception { String id = new AlphanumericRandomValueStringGenerator().generate(); - createZone(id, HttpStatus.FORBIDDEN, lowPriviledgeToken, new IdentityZoneConfiguration()); + createZone(id, HttpStatus.FORBIDDEN, lowPrivilegeToken, new IdentityZoneConfiguration()); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test @@ -569,13 +591,13 @@ void testCreateZoneNoToken() throws Exception { String id = new AlphanumericRandomValueStringGenerator().generate(); createZone(id, HttpStatus.UNAUTHORIZED, "", new IdentityZoneConfiguration()); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test void testCreateZoneWithoutID() throws Exception { IdentityZone zone = createZone("", HttpStatus.CREATED, identityClientToken, new IdentityZoneConfiguration()); - assertTrue(hasText(zone.getId())); + assertThat(hasText(zone.getId())).isTrue(); checkZoneAuditEventInUaa(1, AuditEventType.IdentityZoneCreatedEvent); } @@ -584,15 +606,15 @@ void testUpdateNonExistentReturns403() throws Exception { String id = new AlphanumericRandomValueStringGenerator().generate(); IdentityZone identityZone = createSimpleIdentityZone(id); //zone doesn't exist and we don't have the token scope - updateZone(identityZone, HttpStatus.FORBIDDEN, lowPriviledgeToken); + updateZone(identityZone, HttpStatus.FORBIDDEN, lowPrivilegeToken); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test void testUpdateUaaIsForbidden() throws Exception { updateZone(IdentityZone.getUaa(), HttpStatus.FORBIDDEN, identityClientToken); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test @@ -601,7 +623,7 @@ void testUpdateNonExistentReturns404() throws Exception { IdentityZone identityZone = createSimpleIdentityZone(id); updateZone(identityZone, HttpStatus.NOT_FOUND, identityClientToken); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test @@ -627,8 +649,8 @@ void testUpdateWithDifferentDataReturns200() throws Exception { created.setConfig(definition); IdentityZone updated = updateZone(created, HttpStatus.OK, identityClientToken); - assertEquals("updated description", updated.getDescription()); - assertEquals(JsonUtils.writeValueAsString(definition), JsonUtils.writeValueAsString(updated.getConfig())); + assertThat(updated.getDescription()).isEqualTo("updated description"); + assertThat(JsonUtils.writeValueAsString(updated.getConfig())).isEqualTo(JsonUtils.writeValueAsString(definition)); checkZoneAuditEventInUaa(2, AuditEventType.IdentityZoneModifiedEvent); } @@ -637,11 +659,11 @@ void testCreateAndUpdateDoesNotReturnKeys() throws Exception { String id = generator.generate(); IdentityZone created = createZone(id, HttpStatus.CREATED, identityClientToken, new IdentityZoneConfiguration()); - assertEquals(Collections.EMPTY_MAP, created.getConfig().getTokenPolicy().getKeys()); - assertEquals("kid", created.getConfig().getTokenPolicy().getActiveKeyId()); - assertNull(created.getConfig().getSamlConfig().getPrivateKey()); - assertNull(created.getConfig().getSamlConfig().getPrivateKeyPassword()); - assertNotNull(created.getConfig().getSamlConfig().getCertificate()); + assertThat(created.getConfig().getTokenPolicy().getKeys()).isEqualTo(emptyMap()); + assertThat(created.getConfig().getTokenPolicy().getActiveKeyId()).isEqualTo("kid"); + assertThat(created.getConfig().getSamlConfig().getPrivateKey()).isNull(); + assertThat(created.getConfig().getSamlConfig().getPrivateKeyPassword()).isNull(); + assertThat(created.getConfig().getSamlConfig().getCertificate()).isNotNull(); checkZoneAuditEventInUaa(1, AuditEventType.IdentityZoneCreatedEvent); created.setDescription("updated description"); TokenPolicy tokenPolicy = new TokenPolicy(3600, 7200); @@ -658,12 +680,12 @@ void testCreateAndUpdateDoesNotReturnKeys() throws Exception { created.setConfig(definition); IdentityZone updated = updateZone(created, HttpStatus.OK, identityClientToken); - assertEquals("updated description", updated.getDescription()); - assertEquals(Collections.EMPTY_MAP, updated.getConfig().getTokenPolicy().getKeys()); - assertEquals("key1", updated.getConfig().getTokenPolicy().getActiveKeyId()); - assertNull(updated.getConfig().getSamlConfig().getPrivateKey()); - assertNull(updated.getConfig().getSamlConfig().getPrivateKeyPassword()); - assertEquals(serviceProviderCertificate, updated.getConfig().getSamlConfig().getCertificate()); + assertThat(updated.getDescription()).isEqualTo("updated description"); + assertThat(updated.getConfig().getTokenPolicy().getKeys()).isEqualTo(emptyMap()); + assertThat(updated.getConfig().getTokenPolicy().getActiveKeyId()).isEqualTo("key1"); + assertThat(updated.getConfig().getSamlConfig().getPrivateKey()).isNull(); + assertThat(updated.getConfig().getSamlConfig().getPrivateKeyPassword()).isNull(); + assertThat(updated.getConfig().getSamlConfig().getCertificate()).isEqualTo(serviceProviderCertificate); } @Test @@ -676,14 +698,14 @@ void testUpdateIgnoresKeysWhenNotPresentInPayload() throws Exception { Map keys = new HashMap<>(); keys.put("kid", "key"); - assertEquals(keys.get("kid"), retrieve.getConfig().getTokenPolicy().getKeys().get("kid").getSigningKey()); + assertThat(retrieve.getConfig().getTokenPolicy().getKeys().get("kid").getSigningKey()).isEqualTo(keys.get("kid")); created.setDescription("updated description"); created.getConfig().getTokenPolicy().setKeys(null); updateZone(created, HttpStatus.OK, identityClientToken); retrieve = provisioning.retrieve(created.getId()); String keyId = retrieve.getConfig().getTokenPolicy().getActiveKeyId(); - assertEquals(keys.get(keyId), retrieve.getConfig().getTokenPolicy().getKeys().get(keyId).getSigningKey()); + assertThat(retrieve.getConfig().getTokenPolicy().getKeys().get(keyId).getSigningKey()).isEqualTo(keys.get(keyId)); } @Test @@ -692,52 +714,55 @@ void testUpdateWithInvalidSamlKeyCertPair() throws Exception { IdentityZone created = createZone(id, HttpStatus.CREATED, identityClientToken, new IdentityZoneConfiguration()); - String samlPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + - "\n" + - "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + - "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + - "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + - "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + - "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + - "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + - "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + - "NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi\n" + - "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + - "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + - "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + - "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + - "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + - "-----END RSA PRIVATE KEY-----\n"; + String samlPrivateKey = """ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: DES-EDE3-CBC,5771044F3450A262 + + VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe + aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v + CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh + DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B + +KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3 + KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU + o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6 + NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi + 7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI + 0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu + h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9 + zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb + dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw== + -----END RSA PRIVATE KEY-----"""; + String samlKeyPassphrase = "password"; - String samlCertificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2\n" + - "MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg\n" + - "U1NMIENBMB4XDTEzMDczMDAwMDAwMFoXDTE2MDcyOTIzNTk1OVowPzEhMB8GA1UE\n" + - "CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRowGAYDVQQDExFlZHVyb2FtLmJi\n" + - "ay5hYy51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrSBWTl56O2\n" + - "VJbahURgPznums43Nnn/smJ6cGywPu4mtJHUHSmONlBDTAWFS1fLkh8YHIQmdwYg\n" + - "FY4pHjZmKVtJ6ZOFhDNN1R2VMka4ZtREWn3XX8pUacol5KjEIh6U/FvMHyRv7sV5\n" + - "9J6JUK+n5R7ZsSu7XRi6TrT3xhfu0KoWo8RM/salKo2theIcyqLPHiFLEtA7ISLV\n" + - "q7I49uj9h9Hni/iCpBey+Gn5yDub4nrv81aDfD6zDoW/vXIOrcXFYRK3lXWOOFi4\n" + - "cfmu4SQQwMV1jBOer8JgfsQ3EQMgwauSMLUR31wPM83eMbOC72HhW9SJUtFDj42c\n" + - "PIEWd+rTA8ECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFAy9k2gM896ro0lrKzdX\n" + - "R+qQ47ntMB0GA1UdDgQWBBQgoU+Pbgk2MthczZt7TviUiIWyrjAOBgNVHQ8BAf8E\n" + - "BAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH\n" + - "AwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICHTAIBgZngQwBAgEwOgYDVR0fBDMw\n" + - "MTAvoC2gK4YpaHR0cDovL2NybC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5j\n" + - "cmwwbQYIKwYBBQUHAQEEYTBfMDUGCCsGAQUFBzAChilodHRwOi8vY3J0LnRjcy50\n" + - "ZXJlbmEub3JnL1RFUkVOQVNTTENBLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL29j\n" + - "c3AudGNzLnRlcmVuYS5vcmcwHAYDVR0RBBUwE4IRZWR1cm9hbS5iYmsuYWMudWsw\n" + - "DQYJKoZIhvcNAQEFBQADggEBAHTw5b1lrTBqnx/QSO50Mww+OPYgV4b4NSu2rqxG\n" + - "I2hHLiD4l7Sk3WOdXPAQMmTlo6N10Lt6p8gLLxKsOAw+nK+z9aLcgKk9/kYoe4C8\n" + - "jHzwTy6eO+sCKnJfTqEX8p3b8l736lUWwPgMjjEN+d49ZegqCwH6SEz7h0+DwGmF\n" + - "LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl\n" + - "r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi\n" + - "yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c=\n" + - "-----END CERTIFICATE-----\n"; + String samlCertificate = """ + -----BEGIN CERTIFICATE----- + MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2 + MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg + U1NMIENBMB4XDTEzMDczMDAwMDAwMFoXDTE2MDcyOTIzNTk1OVowPzEhMB8GA1UE + CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRowGAYDVQQDExFlZHVyb2FtLmJi + ay5hYy51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrSBWTl56O2 + VJbahURgPznums43Nnn/smJ6cGywPu4mtJHUHSmONlBDTAWFS1fLkh8YHIQmdwYg + FY4pHjZmKVtJ6ZOFhDNN1R2VMka4ZtREWn3XX8pUacol5KjEIh6U/FvMHyRv7sV5 + 9J6JUK+n5R7ZsSu7XRi6TrT3xhfu0KoWo8RM/salKo2theIcyqLPHiFLEtA7ISLV + q7I49uj9h9Hni/iCpBey+Gn5yDub4nrv81aDfD6zDoW/vXIOrcXFYRK3lXWOOFi4 + cfmu4SQQwMV1jBOer8JgfsQ3EQMgwauSMLUR31wPM83eMbOC72HhW9SJUtFDj42c + PIEWd+rTA8ECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFAy9k2gM896ro0lrKzdX + R+qQ47ntMB0GA1UdDgQWBBQgoU+Pbgk2MthczZt7TviUiIWyrjAOBgNVHQ8BAf8E + BAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH + AwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICHTAIBgZngQwBAgEwOgYDVR0fBDMw + MTAvoC2gK4YpaHR0cDovL2NybC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5j + cmwwbQYIKwYBBQUHAQEEYTBfMDUGCCsGAQUFBzAChilodHRwOi8vY3J0LnRjcy50 + ZXJlbmEub3JnL1RFUkVOQVNTTENBLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL29j + c3AudGNzLnRlcmVuYS5vcmcwHAYDVR0RBBUwE4IRZWR1cm9hbS5iYmsuYWMudWsw + DQYJKoZIhvcNAQEFBQADggEBAHTw5b1lrTBqnx/QSO50Mww+OPYgV4b4NSu2rqxG + I2hHLiD4l7Sk3WOdXPAQMmTlo6N10Lt6p8gLLxKsOAw+nK+z9aLcgKk9/kYoe4C8 + jHzwTy6eO+sCKnJfTqEX8p3b8l736lUWwPgMjjEN+d49ZegqCwH6SEz7h0+DwGmF + LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl + r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi + yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c= + -----END CERTIFICATE-----"""; SamlConfig samlConfig = created.getConfig().getSamlConfig(); samlConfig.setPrivateKey(samlPrivateKey); @@ -780,9 +805,9 @@ void testUpdateWithEmptySamlKeyCertPairRetainsCurrentValue() throws Exception { IdentityZone updated = provisioning.retrieve(created.getId()); SamlConfig updatedSamlConfig = updated.getConfig().getSamlConfig(); - assertEquals(serviceProviderCertificate, updatedSamlConfig.getCertificate()); - assertEquals(serviceProviderKey, updatedSamlConfig.getPrivateKey()); - assertEquals(serviceProviderKeyPassword, updatedSamlConfig.getPrivateKeyPassword()); + assertThat(updatedSamlConfig.getCertificate()).isEqualTo(serviceProviderCertificate); + assertThat(updatedSamlConfig.getPrivateKey()).isEqualTo(serviceProviderKey); + assertThat(updatedSamlConfig.getPrivateKeyPassword()).isEqualTo(serviceProviderKeyPassword); } @Test @@ -793,7 +818,7 @@ void testUpdateWithNewSamlCertNoKeyIsUnprocessableEntity() throws Exception { SamlConfig samlConfig = created.getConfig().getSamlConfig(); - samlConfig.setCertificate(KeyWithCertTest.invalidCert); + samlConfig.setCertificate(KeyWithCertTest.INVALID_CERT); samlConfig.setPrivateKey(null); samlConfig.setPrivateKeyPassword(null); updateZone(created, HttpStatus.UNPROCESSABLE_ENTITY, identityClientToken); @@ -834,16 +859,16 @@ void testUpdateZoneNoToken() throws Exception { IdentityZone identityZone = createSimpleIdentityZone(id); updateZone(identityZone, HttpStatus.UNAUTHORIZED, ""); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test void testUpdateZoneInsufficientScope() throws Exception { String id = new AlphanumericRandomValueStringGenerator().generate(); IdentityZone identityZone = createSimpleIdentityZone(id); - updateZone(identityZone, HttpStatus.FORBIDDEN, lowPriviledgeToken); + updateZone(identityZone, HttpStatus.FORBIDDEN, lowPrivilegeToken); - assertEquals(0, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isZero(); } @Test @@ -855,7 +880,7 @@ void testCreateDuplicateZoneReturns409() throws Exception { createZone(id, HttpStatus.CONFLICT, identityClientToken, new IdentityZoneConfiguration()); - assertEquals(1, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isOne(); } @ParameterizedTest @@ -872,46 +897,49 @@ void testCreateZoneAndIdentityProvider(String url) throws Exception { SamlConfig samlConfig = new SamlConfig(); - String samlPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa\n" + - "H45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX\n" + - "H85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB\n" + - "AoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp\n" + - "oUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o\n" + - "XDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9\n" + - "vuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW\n" + - "2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W\n" + - "2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA\n" + - "oVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9\n" + - "0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx\n" + - "dFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U\n" + - "Ow3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4=\n" + - "-----END RSA PRIVATE KEY-----\n"; + String samlPrivateKey = """ + -----BEGIN RSA PRIVATE KEY----- + MIICXAIBAAKBgQCpnqPQiDCfJY1hVaQUZG6Rs1Wd3FmP1EStN71hXeXOLog5nvpa + H45P3v79EGpaO06vH5qSu/xr6kQRBOA4h9OqXGS72BGQBH8jMNCoHqgJrIADQTHX + H85RYF38bH6Ycp18jch0KVmYwKeiaLNfMDngnAv6wMDONJz761GBtrG1/wIDAQAB + AoGAPjYeNSzOUICwcyO7E3Omji/tVgHso3EiYznPbvfGgrHUavXhMs7iHm9WrLCp + oUChYl/ADNOACICayHc2WeWPfxJ26BF0ahTzOX1fJsg++JDweCYCNN2WrrYcyA9o + XDU18IFh2dY2CvPL8G7ex5WEq9nYTASQzRfC899nTvUSTyECQQDZddRhqF9g6Zc9 + vuSjwQf+dMztsvhLVPAPaSdgE4LMa4nE2iNC/sLq1uUEwrrrOKGaFB9IXeIU7hPW + 2QmgJewxAkEAx65IjpesMEq+zE5qRPYkfxjdaa0gNBCfATEBGI4bTx37cKskf49W + 2qFlombE9m9t/beYXVC++2W40i53ov+pLwJALRp0X4EFr1sjxGnIkHJkDxH4w0CA + oVdPp1KfGR1S3sVbQNohwC6JDR5fR/p/vHP1iLituFvInaC3urMvfOkAsQJBAJg9 + 0gYdr+O16Vi95JoljNf2bkG3BJmNnp167ln5ZurgcieJ5K7464CPk3zJnBxEAvlx + dFKZULM98DcXxJFbGXMCQC2ZkPFgzMlRwYu4gake2ruOQR9N3HzLoau1jqDrgh6U + Ow3ylw8RWPq4zmLkDPn83DFMBquYsg3yzBPi7PANBO4= + -----END RSA PRIVATE KEY-----"""; + String samlKeyPassphrase = "password"; - String samlCertificate = "-----BEGIN CERTIFICATE-----\n" + - "MIID4zCCA0ygAwIBAgIJAJdmwmBdhEydMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD\n" + - "VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xJzAl\n" + - "BgNVBAoTHkNsb3VkIEZvdW5kcnkgRm91bmRhdGlvbiwgSW5jLjEMMAoGA1UECxMD\n" + - "VUFBMRIwEAYDVQQDEwlsb2NhbGhvc3QxKTAnBgkqhkiG9w0BCQEWGmNmLWlkZW50\n" + - "aXR5LWVuZ0BwaXZvdGFsLmlvMB4XDTE2MDIxNjIyMTMzN1oXDTE2MDMxNzIyMTMz\n" + - "N1owgagxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZy\n" + - "YW5jaXNjbzEnMCUGA1UEChMeQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMu\n" + - "MQwwCgYDVQQLEwNVQUExEjAQBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJ\n" + - "ARYaY2YtaWRlbnRpdHktZW5nQHBpdm90YWwuaW8wgZ8wDQYJKoZIhvcNAQEBBQAD\n" + - "gY0AMIGJAoGBAKmeo9CIMJ8ljWFVpBRkbpGzVZ3cWY/URK03vWFd5c4uiDme+lof\n" + - "jk/e/v0Qalo7Tq8fmpK7/GvqRBEE4DiH06pcZLvYEZAEfyMw0KgeqAmsgANBMdcf\n" + - "zlFgXfxsfphynXyNyHQpWZjAp6Jos18wOeCcC/rAwM40nPvrUYG2sbX/AgMBAAGj\n" + - "ggERMIIBDTAdBgNVHQ4EFgQUdiixDfiZ61ljk7J/uUYcay26n5swgd0GA1UdIwSB\n" + - "1TCB0oAUdiixDfiZ61ljk7J/uUYcay26n5uhga6kgaswgagxCzAJBgNVBAYTAlVT\n" + - "MQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEnMCUGA1UEChMe\n" + - "Q2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMuMQwwCgYDVQQLEwNVQUExEjAQ\n" + - "BgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJARYaY2YtaWRlbnRpdHktZW5n\n" + - "QHBpdm90YWwuaW+CCQCXZsJgXYRMnTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\n" + - "BQUAA4GBAAPf/SPl/LuVYrl0HDUU8YDR3N7Fi4OjhF3+n+uBYRhO+9IbQ/t1sC1p\n" + - "enWhiAfyZtgFv2OmjvtFyty9YqHhIPAg9Ceod37Q7HNSG04vbYHNJ6XhGUzacMj8\n" + - "hQ1ZzQBv+CaKWZarBIql/TsxtpvvXhaE4QqR4NvUDnESHtxefriv\n" + - "-----END CERTIFICATE-----\n"; + String samlCertificate = """ + -----BEGIN CERTIFICATE----- + MIID4zCCA0ygAwIBAgIJAJdmwmBdhEydMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD + VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xJzAl + BgNVBAoTHkNsb3VkIEZvdW5kcnkgRm91bmRhdGlvbiwgSW5jLjEMMAoGA1UECxMD + VUFBMRIwEAYDVQQDEwlsb2NhbGhvc3QxKTAnBgkqhkiG9w0BCQEWGmNmLWlkZW50 + aXR5LWVuZ0BwaXZvdGFsLmlvMB4XDTE2MDIxNjIyMTMzN1oXDTE2MDMxNzIyMTMz + N1owgagxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZy + YW5jaXNjbzEnMCUGA1UEChMeQ2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMu + MQwwCgYDVQQLEwNVQUExEjAQBgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJ + ARYaY2YtaWRlbnRpdHktZW5nQHBpdm90YWwuaW8wgZ8wDQYJKoZIhvcNAQEBBQAD + gY0AMIGJAoGBAKmeo9CIMJ8ljWFVpBRkbpGzVZ3cWY/URK03vWFd5c4uiDme+lof + jk/e/v0Qalo7Tq8fmpK7/GvqRBEE4DiH06pcZLvYEZAEfyMw0KgeqAmsgANBMdcf + zlFgXfxsfphynXyNyHQpWZjAp6Jos18wOeCcC/rAwM40nPvrUYG2sbX/AgMBAAGj + ggERMIIBDTAdBgNVHQ4EFgQUdiixDfiZ61ljk7J/uUYcay26n5swgd0GA1UdIwSB + 1TCB0oAUdiixDfiZ61ljk7J/uUYcay26n5uhga6kgaswgagxCzAJBgNVBAYTAlVT + MQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEnMCUGA1UEChMe + Q2xvdWQgRm91bmRyeSBGb3VuZGF0aW9uLCBJbmMuMQwwCgYDVQQLEwNVQUExEjAQ + BgNVBAMTCWxvY2FsaG9zdDEpMCcGCSqGSIb3DQEJARYaY2YtaWRlbnRpdHktZW5n + QHBpdm90YWwuaW+CCQCXZsJgXYRMnTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB + BQUAA4GBAAPf/SPl/LuVYrl0HDUU8YDR3N7Fi4OjhF3+n+uBYRhO+9IbQ/t1sC1p + enWhiAfyZtgFv2OmjvtFyty9YqHhIPAg9Ceod37Q7HNSG04vbYHNJ6XhGUzacMj8 + hQ1ZzQBv+CaKWZarBIql/TsxtpvvXhaE4QqR4NvUDnESHtxefriv + -----END CERTIFICATE-----"""; samlConfig.setCertificate(samlCertificate); samlConfig.setPrivateKey(samlPrivateKey); @@ -921,32 +949,32 @@ void testCreateZoneAndIdentityProvider(String url) throws Exception { identityZone.setConfig(definition.setSamlConfig(samlConfig)); mockMvc.perform( - post(url) - .header("Authorization", "Bearer " + identityClientZonesReadToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) + post(url) + .header("Authorization", "Bearer " + identityClientZonesReadToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) .andExpect(status().isForbidden()); mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) .andExpect(status().isCreated()); checkZoneAuditEventInUaa(1, AuditEventType.IdentityZoneCreatedEvent); IdentityProviderProvisioning idpp = (IdentityProviderProvisioning) webApplicationContext.getBean("identityProviderProvisioning"); - IdentityProvider idp1 = idpp.retrieveByOrigin(UAA, identityZone.getId()); - IdentityProvider idp2 = idpp.retrieveByOrigin(UAA, IdentityZone.getUaaZoneId()); - assertNotEquals(idp1, idp2); + IdentityProvider idp1 = idpp.retrieveByOrigin(UAA, identityZone.getId()); + IdentityProvider idp2 = idpp.retrieveByOrigin(UAA, IdentityZone.getUaaZoneId()); + assertThat(idp2).isNotEqualTo(idp1); IdentityZoneProvisioning identityZoneProvisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); IdentityZone createdZone = identityZoneProvisioning.retrieve(id); - assertEquals(JsonUtils.writeValueAsString(definition), JsonUtils.writeValueAsString(createdZone.getConfig())); - assertEquals(samlCertificate, createdZone.getConfig().getSamlConfig().getCertificate()); - assertEquals(samlPrivateKey, createdZone.getConfig().getSamlConfig().getPrivateKey()); + assertThat(JsonUtils.writeValueAsString(createdZone.getConfig())).isEqualTo(JsonUtils.writeValueAsString(definition)); + assertThat(createdZone.getConfig().getSamlConfig().getCertificate()).isEqualTo(samlCertificate); + assertThat(createdZone.getConfig().getSamlConfig().getPrivateKey()).isEqualTo(samlPrivateKey); } @Test @@ -961,10 +989,10 @@ void testCreateZoneWithInvalidPrimarySigningKeyId() throws Exception { tokenPolicy.setActiveKeyId("nonexistent_key"); mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) .andExpect(status().isUnprocessableEntity()); } @@ -980,10 +1008,10 @@ void testCreateZoneWithNoActiveKeyId() throws Exception { tokenPolicy.setKeys(jwtKeys); mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) .andExpect(status().isCreated()); } @@ -997,10 +1025,10 @@ void testCreateZoneWithRefreshTokenConfig() throws Exception { tokenPolicy.setRefreshTokenRotate(true); mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.config.tokenPolicy.refreshTokenUnique").value(true)) .andExpect(jsonPath("$.config.tokenPolicy.refreshTokenRotate").value(true)) @@ -1008,9 +1036,9 @@ void testCreateZoneWithRefreshTokenConfig() throws Exception { IdentityZone createdZone = provisioning.retrieve(id); - assertEquals(OPAQUE.getStringValue(), createdZone.getConfig().getTokenPolicy().getRefreshTokenFormat()); - assertTrue(createdZone.getConfig().getTokenPolicy().isRefreshTokenUnique()); - assertTrue(createdZone.getConfig().getTokenPolicy().isRefreshTokenRotate()); + assertThat(createdZone.getConfig().getTokenPolicy().getRefreshTokenFormat()).isEqualTo(OPAQUE.getStringValue()); + assertThat(createdZone.getConfig().getTokenPolicy().isRefreshTokenUnique()).isTrue(); + assertThat(createdZone.getConfig().getTokenPolicy().isRefreshTokenRotate()).isTrue(); } @Test @@ -1029,17 +1057,18 @@ void testCreateZoneWithCustomBrandingBanner() throws Exception { zone.getConfig().setBranding(branding); String contentAsString = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andReturn().getResponse().getContentAsString(); IdentityZone createdZone = JsonUtils.readValue(contentAsString, IdentityZone.class); Banner zoneBanner = createdZone.getConfig().getBranding().getBanner(); - assertEquals(text, zoneBanner.getText()); - assertEquals(link, zoneBanner.getLink()); - assertEquals(backgroundColor, zoneBanner.getBackgroundColor()); + assertThat(zoneBanner) + .returns(text, Banner::getText) + .returns(link, Banner::getLink) + .returns(backgroundColor, Banner::getBackgroundColor); } @Test @@ -1053,16 +1082,17 @@ void testCreateZoneWithConsentTextAndLink() throws Exception { zone.getConfig().setBranding(branding); String contentAsString = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andReturn().getResponse().getContentAsString(); IdentityZone createdZone = JsonUtils.readValue(contentAsString, IdentityZone.class); Consent createdZoneConsent = createdZone.getConfig().getBranding().getConsent(); - assertThat(createdZoneConsent.getText(), is("some consent text")); - assertThat(createdZoneConsent.getLink(), is("http://localhost")); + assertThat(createdZoneConsent) + .returns("some consent text", Consent::getText) + .returns("http://localhost", Consent::getLink); } @Test @@ -1076,16 +1106,17 @@ void testCreateZoneWithOnlyConsentText() throws Exception { zone.getConfig().setBranding(branding); String contentAsString = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andReturn().getResponse().getContentAsString(); IdentityZone createdZone = JsonUtils.readValue(contentAsString, IdentityZone.class); Consent createdZoneConsent = createdZone.getConfig().getBranding().getConsent(); - assertThat(createdZoneConsent.getText(), is("some consent text")); - assertThat(createdZoneConsent.getLink(), is((Object) null)); + assertThat(createdZoneConsent) + .returns("some consent text", Consent::getText) + .returns(null, Consent::getLink); } @Test @@ -1099,14 +1130,14 @@ void testCreateZoneWithNoConsentText() throws Exception { zone.getConfig().setBranding(branding); String contentAsString = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isUnprocessableEntity()) .andReturn().getResponse().getContentAsString(); - assertThat(contentAsString, containsString("Consent text must be set if configuring consent")); + assertThat(contentAsString).contains("Consent text must be set if configuring consent"); } @Test @@ -1123,14 +1154,14 @@ void testCreateZoneWithIncorrectBrandingBannerLink() throws Exception { zone.getConfig().setBranding(branding); MockHttpServletResponse response = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isUnprocessableEntity()) .andReturn().getResponse(); - assertThat(response.getContentAsString(), containsString("Invalid banner link: " + invalidUrl + ". Must be a properly formatted URI beginning with http:// or https://")); + assertThat(response.getContentAsString()).contains("Invalid banner link: " + invalidUrl + ". Must be a properly formatted URI beginning with http:// or https://"); } @Test @@ -1147,10 +1178,10 @@ void testUpdateZoneWithIncorrectBrandingBannerLink() throws Exception { zone.getConfig().setBranding(branding); String response = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); @@ -1159,15 +1190,15 @@ void testUpdateZoneWithIncorrectBrandingBannerLink() throws Exception { createdZone.getConfig().getBranding().getBanner().setLink(invalidUrl); MockHttpServletResponse mvcResult = mockMvc.perform( - put("/identity-zones/" + createdZone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(createdZone))) + put("/identity-zones/" + createdZone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(createdZone))) .andExpect(status().isUnprocessableEntity()) .andReturn() .getResponse(); - assertThat(mvcResult.getContentAsString(), containsString("Invalid banner link: " + invalidUrl + ". Must be a properly formatted URI beginning with http:// or https://")); + assertThat(mvcResult.getContentAsString()).contains("Invalid banner link: " + invalidUrl + ". Must be a properly formatted URI beginning with http:// or https://"); } @Test @@ -1180,10 +1211,10 @@ void testUpdateZoneWithConsent() throws Exception { zone.getConfig().setBranding(branding); String response = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); @@ -1193,10 +1224,10 @@ void testUpdateZoneWithConsent() throws Exception { createdZone.getConfig().getBranding().getConsent().setLink("http://localhost/some-updated-link"); MockHttpServletResponse mvcResult = mockMvc.perform( - put("/identity-zones/" + createdZone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(createdZone))) + put("/identity-zones/" + createdZone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(createdZone))) .andExpect(status().isOk()) .andReturn() .getResponse(); @@ -1204,8 +1235,8 @@ void testUpdateZoneWithConsent() throws Exception { IdentityZone updatedZone = JsonUtils.readValue(mvcResult.getContentAsString(), IdentityZone.class); Consent createdZoneConsent = updatedZone.getConfig().getBranding().getConsent(); - assertThat(createdZoneConsent.getText(), is("some updated text")); - assertThat(createdZoneConsent.getLink(), is("http://localhost/some-updated-link")); + assertThat(createdZoneConsent.getText()).isEqualTo("some updated text"); + assertThat(createdZoneConsent.getLink()).isEqualTo("http://localhost/some-updated-link"); } @Test @@ -1218,10 +1249,10 @@ void testUpdateZoneWithOnlyConsentText() throws Exception { zone.getConfig().setBranding(branding); String response = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); @@ -1230,10 +1261,10 @@ void testUpdateZoneWithOnlyConsentText() throws Exception { createdZone.getConfig().getBranding().getConsent().setLink(null); MockHttpServletResponse mvcResult = mockMvc.perform( - put("/identity-zones/" + createdZone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(createdZone))) + put("/identity-zones/" + createdZone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(createdZone))) .andExpect(status().isOk()) .andReturn() .getResponse(); @@ -1241,8 +1272,8 @@ void testUpdateZoneWithOnlyConsentText() throws Exception { IdentityZone updatedZone = JsonUtils.readValue(mvcResult.getContentAsString(), IdentityZone.class); Consent createdZoneConsent = updatedZone.getConfig().getBranding().getConsent(); - assertThat(createdZoneConsent.getText(), is("some text")); - assertThat(createdZoneConsent.getLink(), is((Object) null)); + assertThat(createdZoneConsent.getText()).isEqualTo("some text"); + assertThat(createdZoneConsent.getLink()).isEqualTo((Object) null); } @Test @@ -1255,10 +1286,10 @@ void testUpdateZoneWithNoConsentText() throws Exception { zone.getConfig().setBranding(branding); String response = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); @@ -1267,15 +1298,15 @@ void testUpdateZoneWithNoConsentText() throws Exception { createdZone.getConfig().getBranding().getConsent().setText(null); MockHttpServletResponse mvcResult = mockMvc.perform( - put("/identity-zones/" + createdZone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(createdZone))) + put("/identity-zones/" + createdZone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(createdZone))) .andExpect(status().isUnprocessableEntity()) .andReturn() .getResponse(); - assertThat(mvcResult.getContentAsString(), containsString("Consent text must be set if configuring consent")); + assertThat(mvcResult.getContentAsString()).contains("Consent text must be set if configuring consent"); } @Test @@ -1288,10 +1319,10 @@ void testUpdateZoneWithInvalidConsentLink() throws Exception { zone.getConfig().setBranding(branding); String response = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); @@ -1302,15 +1333,15 @@ void testUpdateZoneWithInvalidConsentLink() throws Exception { createdZone.getConfig().getBranding().getConsent().setLink(invalidConsentLink); MockHttpServletResponse mvcResult = mockMvc.perform( - put("/identity-zones/" + createdZone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(createdZone))) + put("/identity-zones/" + createdZone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(createdZone))) .andExpect(status().isUnprocessableEntity()) .andReturn() .getResponse(); - assertThat(mvcResult.getContentAsString(), containsString("Invalid consent link: " + invalidConsentLink + ". Must be a properly formatted URI beginning with http:// or https://")); + assertThat(mvcResult.getContentAsString()).contains("Invalid consent link: " + invalidConsentLink + ". Must be a properly formatted URI beginning with http:// or https://"); } @Test @@ -1327,14 +1358,14 @@ void testCreateZoneWithInvalidBannerBackgroundColor() throws Exception { zone.getConfig().setBranding(branding); MockHttpServletResponse mvcResult = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isUnprocessableEntity()) .andReturn().getResponse(); - assertThat(mvcResult.getContentAsString(), containsString("Invalid banner background color: " + invalidColor + ". Must be a properly formatted hexadecimal color code.")); + assertThat(mvcResult.getContentAsString()).contains("Invalid banner background color: " + invalidColor + ". Must be a properly formatted hexadecimal color code."); } @Test @@ -1351,10 +1382,10 @@ void testUpdateZoneWithInvalidBannerBackgroundColor() throws Exception { zone.getConfig().setBranding(branding); String response = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); @@ -1363,15 +1394,15 @@ void testUpdateZoneWithInvalidBannerBackgroundColor() throws Exception { createdZone.getConfig().getBranding().getBanner().setBackgroundColor(invalidColor); MockHttpServletResponse mvcResult = mockMvc.perform( - put("/identity-zones/" + createdZone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(createdZone))) + put("/identity-zones/" + createdZone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(createdZone))) .andExpect(status().isUnprocessableEntity()) .andReturn() .getResponse(); - assertThat(mvcResult.getContentAsString(), containsString("Invalid banner background color: " + invalidColor + ". Must be a properly formatted hexadecimal color code.")); + assertThat(mvcResult.getContentAsString()).contains("Invalid banner background color: " + invalidColor + ". Must be a properly formatted hexadecimal color code."); } @Test @@ -1388,14 +1419,14 @@ void testCreateZoneWithInvalidBannerTextColor() throws Exception { zone.getConfig().setBranding(branding); MockHttpServletResponse mvcResult = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isUnprocessableEntity()) .andReturn().getResponse(); - assertThat(mvcResult.getContentAsString(), containsString("Invalid banner text color: " + invalidColor + ". Must be a properly formatted hexadecimal color code.")); + assertThat(mvcResult.getContentAsString()).contains("Invalid banner text color: " + invalidColor + ". Must be a properly formatted hexadecimal color code."); } @Test @@ -1412,10 +1443,10 @@ void testUpdateZoneWithInvalidBannerTextColor() throws Exception { zone.getConfig().setBranding(branding); String response = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); @@ -1424,15 +1455,15 @@ void testUpdateZoneWithInvalidBannerTextColor() throws Exception { createdZone.getConfig().getBranding().getBanner().setTextColor(invalidColor); MockHttpServletResponse mvcResult = mockMvc.perform( - put("/identity-zones/" + createdZone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(createdZone))) + put("/identity-zones/" + createdZone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(createdZone))) .andExpect(status().isUnprocessableEntity()) .andReturn() .getResponse(); - assertThat(mvcResult.getContentAsString(), containsString("Invalid banner text color: " + invalidColor + ". Must be a properly formatted hexadecimal color code.")); + assertThat(mvcResult.getContentAsString()).contains("Invalid banner text color: " + invalidColor + ". Must be a properly formatted hexadecimal color code."); } @Test @@ -1450,14 +1481,14 @@ void testCreateZoneWithInvalidBannerLogo() throws Exception { zone.getConfig().setBranding(branding); MockHttpServletResponse mvcResult = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isUnprocessableEntity()) .andReturn().getResponse(); - assertThat(mvcResult.getContentAsString(), containsString("Invalid banner logo. Must be in BASE64 format.")); + assertThat(mvcResult.getContentAsString()).contains("Invalid banner logo. Must be in BASE64 format."); } @Test @@ -1476,10 +1507,10 @@ void testUpdateZoneWithInvalidBannerLogo() throws Exception { zone.getConfig().setBranding(branding); String response = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(zone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); @@ -1488,15 +1519,15 @@ void testUpdateZoneWithInvalidBannerLogo() throws Exception { createdZone.getConfig().getBranding().getBanner().setLogo(invalidLogo); MockHttpServletResponse mvcResult = mockMvc.perform( - put("/identity-zones/" + createdZone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(createdZone))) + put("/identity-zones/" + createdZone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(createdZone))) .andExpect(status().isUnprocessableEntity()) .andReturn() .getResponse(); - assertThat(mvcResult.getContentAsString(), containsString("Invalid banner logo. Must be in BASE64 format.")); + assertThat(mvcResult.getContentAsString()).contains("Invalid banner logo. Must be in BASE64 format."); } @Test @@ -1513,52 +1544,55 @@ void testCreateZoneWithInvalidSamlKeyCertPair() throws Exception { SamlConfig samlConfig = new SamlConfig(); - String samlPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + - "Proc-Type: 4,ENCRYPTED\n" + - "DEK-Info: DES-EDE3-CBC,5771044F3450A262\n" + - "\n" + - "VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe\n" + - "aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v\n" + - "CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh\n" + - "DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B\n" + - "+KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3\n" + - "KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU\n" + - "o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6\n" + - "NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi\n" + - "7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI\n" + - "0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu\n" + - "h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9\n" + - "zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb\n" + - "dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw==\n" + - "-----END RSA PRIVATE KEY-----\n"; + String samlPrivateKey = """ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: DES-EDE3-CBC,5771044F3450A262 + + VfRgIdzq/TUFdIwTOxochDs02sSQXA/Z6mRnffYTQMwXpQ5f5nRuqcY8zECGMaDe + aLrndpWzGbxiePKgN5AxuIDYNnKMrDRgyCzaaPx66rb87oMwtuq1HM18qqs+yN5v + CdsoS2uz57fCDI24BuJkIDSIeumLXc5MdN0HUeaxOVzmpbpsbBXjRYa24gW38mUh + DzmOAsNDxfoSTox02Cj+GV024e+PiWR6AMA7RKhsKPf9F4ctWwozvEHrV8fzTy5B + +KM361P7XwJYueiV/gMZW2DXSujNRBEVfC1CLaxDV3eVsFX5iIiUbc4JQYOM6oQ3 + KxGPImcRQPY0asKgEDIaWtysUuBoDSbfQ/FxGWeqwR6P/Vth4dXzVGheYLu1V1CU + o6M+EXC/VUhERKwi13EgqXLKrDI352/HgEKG60EhM6xIJy9hLHy0UGjdHDcA+cF6 + NEl6E3CivddMHIPQWil5x4AMaevGa3v/gcZI0DN8t7L1g4fgjtSPYzvwmOxoxHGi + 7V7PdzaD4GWV75fv99sBlq2e0KK9crNUzs7vbFA/m6tgNA628SGhU1uAc/5xOskI + 0Ez6kjgHoh4U7t/fu7ey1MbFQt6byHY9lk27nW1ub/QMAaRJ+EDnrReB/NN6q5Vu + h9eQNniNOeQfflzFyPB9omLNsVJkENn+lZNNrrlbn8OmJ0pT58Iaetfh79rDZPw9 + zmHVqmMynmecTWAcA9ATf7+lh+xV88JDjQkLcG/3WEXNH7HXKO00pUa8+JtyxbAb + dAwGgrjJkbbk1qLLScOqY4mA5WXa5+80LMkCYO44vVTp2VKmnxj8Mw== + -----END RSA PRIVATE KEY-----"""; + String samlKeyPassphrase = "password"; - String samlCertificate = "-----BEGIN CERTIFICATE-----\n" + - "MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2\n" + - "MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg\n" + - "U1NMIENBMB4XDTEzMDczMDAwMDAwMFoXDTE2MDcyOTIzNTk1OVowPzEhMB8GA1UE\n" + - "CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRowGAYDVQQDExFlZHVyb2FtLmJi\n" + - "ay5hYy51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrSBWTl56O2\n" + - "VJbahURgPznums43Nnn/smJ6cGywPu4mtJHUHSmONlBDTAWFS1fLkh8YHIQmdwYg\n" + - "FY4pHjZmKVtJ6ZOFhDNN1R2VMka4ZtREWn3XX8pUacol5KjEIh6U/FvMHyRv7sV5\n" + - "9J6JUK+n5R7ZsSu7XRi6TrT3xhfu0KoWo8RM/salKo2theIcyqLPHiFLEtA7ISLV\n" + - "q7I49uj9h9Hni/iCpBey+Gn5yDub4nrv81aDfD6zDoW/vXIOrcXFYRK3lXWOOFi4\n" + - "cfmu4SQQwMV1jBOer8JgfsQ3EQMgwauSMLUR31wPM83eMbOC72HhW9SJUtFDj42c\n" + - "PIEWd+rTA8ECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFAy9k2gM896ro0lrKzdX\n" + - "R+qQ47ntMB0GA1UdDgQWBBQgoU+Pbgk2MthczZt7TviUiIWyrjAOBgNVHQ8BAf8E\n" + - "BAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH\n" + - "AwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICHTAIBgZngQwBAgEwOgYDVR0fBDMw\n" + - "MTAvoC2gK4YpaHR0cDovL2NybC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5j\n" + - "cmwwbQYIKwYBBQUHAQEEYTBfMDUGCCsGAQUFBzAChilodHRwOi8vY3J0LnRjcy50\n" + - "ZXJlbmEub3JnL1RFUkVOQVNTTENBLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL29j\n" + - "c3AudGNzLnRlcmVuYS5vcmcwHAYDVR0RBBUwE4IRZWR1cm9hbS5iYmsuYWMudWsw\n" + - "DQYJKoZIhvcNAQEFBQADggEBAHTw5b1lrTBqnx/QSO50Mww+OPYgV4b4NSu2rqxG\n" + - "I2hHLiD4l7Sk3WOdXPAQMmTlo6N10Lt6p8gLLxKsOAw+nK+z9aLcgKk9/kYoe4C8\n" + - "jHzwTy6eO+sCKnJfTqEX8p3b8l736lUWwPgMjjEN+d49ZegqCwH6SEz7h0+DwGmF\n" + - "LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl\n" + - "r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi\n" + - "yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c=\n" + - "-----END CERTIFICATE-----\n"; + String samlCertificate = """ + -----BEGIN CERTIFICATE----- + MIIEbzCCA1egAwIBAgIQCTPRC15ZcpIxJwdwiMVDSjANBgkqhkiG9w0BAQUFADA2 + MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg + U1NMIENBMB4XDTEzMDczMDAwMDAwMFoXDTE2MDcyOTIzNTk1OVowPzEhMB8GA1UE + CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRowGAYDVQQDExFlZHVyb2FtLmJi + ay5hYy51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrSBWTl56O2 + VJbahURgPznums43Nnn/smJ6cGywPu4mtJHUHSmONlBDTAWFS1fLkh8YHIQmdwYg + FY4pHjZmKVtJ6ZOFhDNN1R2VMka4ZtREWn3XX8pUacol5KjEIh6U/FvMHyRv7sV5 + 9J6JUK+n5R7ZsSu7XRi6TrT3xhfu0KoWo8RM/salKo2theIcyqLPHiFLEtA7ISLV + q7I49uj9h9Hni/iCpBey+Gn5yDub4nrv81aDfD6zDoW/vXIOrcXFYRK3lXWOOFi4 + cfmu4SQQwMV1jBOer8JgfsQ3EQMgwauSMLUR31wPM83eMbOC72HhW9SJUtFDj42c + PIEWd+rTA8ECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFAy9k2gM896ro0lrKzdX + R+qQ47ntMB0GA1UdDgQWBBQgoU+Pbgk2MthczZt7TviUiIWyrjAOBgNVHQ8BAf8E + BAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH + AwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICHTAIBgZngQwBAgEwOgYDVR0fBDMw + MTAvoC2gK4YpaHR0cDovL2NybC50Y3MudGVyZW5hLm9yZy9URVJFTkFTU0xDQS5j + cmwwbQYIKwYBBQUHAQEEYTBfMDUGCCsGAQUFBzAChilodHRwOi8vY3J0LnRjcy50 + ZXJlbmEub3JnL1RFUkVOQVNTTENBLmNydDAmBggrBgEFBQcwAYYaaHR0cDovL29j + c3AudGNzLnRlcmVuYS5vcmcwHAYDVR0RBBUwE4IRZWR1cm9hbS5iYmsuYWMudWsw + DQYJKoZIhvcNAQEFBQADggEBAHTw5b1lrTBqnx/QSO50Mww+OPYgV4b4NSu2rqxG + I2hHLiD4l7Sk3WOdXPAQMmTlo6N10Lt6p8gLLxKsOAw+nK+z9aLcgKk9/kYoe4C8 + jHzwTy6eO+sCKnJfTqEX8p3b8l736lUWwPgMjjEN+d49ZegqCwH6SEz7h0+DwGmF + LLfFM8J1SozgPVXgmfCv0XHpFyYQPhXligeWk39FouC2DfhXDTDOgc0n/UQjETNl + r2Jawuw1VG6/+EFf4qjwr0/hIrxc/0XEd9+qLHKef1rMjb9pcZA7Dti+DoKHsxWi + yl3DnNZlj0tFP0SBcwjg/66VAekmFtJxsLx3hKxtYpO3m8c= + -----END CERTIFICATE-----"""; samlConfig.setCertificate(samlCertificate); samlConfig.setPrivateKey(samlPrivateKey); @@ -1568,10 +1602,10 @@ void testCreateZoneWithInvalidSamlKeyCertPair() throws Exception { identityZone.setConfig(definition.setSamlConfig(samlConfig)); mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) .andExpect(status().isUnprocessableEntity()); } @@ -1596,40 +1630,41 @@ void test_delete_zone_cleans_db() throws Exception { client.addAdditionalInformation("foo", "bar"); for (String url : Arrays.asList("", "/")) { mockMvc.perform( - post("/identity-zones/" + zone.getId() + "/clients" + url) - .header("Authorization", "Bearer " + identityClientZonesReadToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(client))) + post("/identity-zones/" + zone.getId() + "/clients" + url) + .header("Authorization", "Bearer " + identityClientZonesReadToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(client))) .andExpect(status().isForbidden()); } //create client without token mockMvc.perform(post("/identity-zones/" + zone.getId() + "/clients") - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(client))) - .andExpect(status().isUnauthorized()); - - MvcResult result = mockMvc.perform( - post("/identity-zones/" + zone.getId() + "/clients") - .header("Authorization", "Bearer " + identityClientToken) .contentType(APPLICATION_JSON) .accept(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(client))) + .andExpect(status().isUnauthorized()); + + MvcResult result = mockMvc.perform( + post("/identity-zones/" + zone.getId() + "/clients") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(client))) .andExpect(status().isCreated()).andReturn(); UaaClientDetails created = JsonUtils.readValue(result.getResponse().getContentAsString(), UaaClientDetails.class); - assertNull(created.getClientSecret()); - assertEquals("zones.write", created.getAdditionalInformation().get(ClientConstants.CREATED_WITH)); - assertEquals(Collections.singletonList(UAA), created.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS)); - assertEquals("bar", created.getAdditionalInformation().get("foo")); + assertThat(created.getClientSecret()).isNull(); + assertThat(created.getAdditionalInformation()) + .containsEntry(ClientConstants.CREATED_WITH, "zones.write") + .containsEntry(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(UAA)) + .containsEntry("foo", "bar"); //ensure that UAA provider is there - assertNotNull(idpp.retrieveByOrigin(UAA, zone.getId())); - assertEquals(UAA, idpp.retrieveByOrigin(UAA, zone.getId()).getOriginKey()); + assertThat(idpp.retrieveByOrigin(UAA, zone.getId())).isNotNull(); + assertThat(idpp.retrieveByOrigin(UAA, zone.getId()).getOriginKey()).isEqualTo(UAA); //create login-server provider - IdentityProvider provider = new IdentityProvider() + IdentityProvider provider = new IdentityProvider<>() .setOriginKey(LOGIN_SERVER) .setActive(true) .setIdentityZoneId(zone.getId()) @@ -1637,45 +1672,45 @@ void test_delete_zone_cleans_db() throws Exception { .setType(LOGIN_SERVER); IdentityZoneHolder.set(zone); provider = idpp.create(provider, provider.getIdentityZoneId()); - assertNotNull(idpp.retrieveByOrigin(LOGIN_SERVER, zone.getId())); - assertEquals(provider.getId(), idpp.retrieveByOrigin(LOGIN_SERVER, zone.getId()).getId()); + assertThat(idpp.retrieveByOrigin(LOGIN_SERVER, zone.getId())).isNotNull(); + assertThat(idpp.retrieveByOrigin(LOGIN_SERVER, zone.getId()).getId()).isEqualTo(provider.getId()); //create user and add user to group ScimUser user = getScimUser(); user.setOrigin(LOGIN_SERVER); user = userProvisioning.createUser(user, "", IdentityZoneHolder.get().getId()); - assertNotNull(userProvisioning.retrieve(user.getId(), IdentityZoneHolder.get().getId())); - assertEquals(zone.getId(), user.getZoneId()); + assertThat(userProvisioning.retrieve(user.getId(), IdentityZoneHolder.get().getId())).isNotNull(); + assertThat(user.getZoneId()).isEqualTo(zone.getId()); //create group ScimGroup group = new ScimGroup("Delete Test Group"); group.setZoneId(zone.getId()); group = groupProvisioning.create(group, IdentityZoneHolder.get().getId()); membershipManager.addMember(group.getId(), new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER), IdentityZoneHolder.get().getId()); - assertEquals(zone.getId(), group.getZoneId()); - assertNotNull(groupProvisioning.retrieve(group.getId(), IdentityZoneHolder.get().getId())); - assertEquals("Delete Test Group", groupProvisioning.retrieve(group.getId(), IdentityZoneHolder.get().getId()).getDisplayName()); - assertEquals(1, membershipManager.getMembers(group.getId(), false, IdentityZoneHolder.get().getId()).size()); + assertThat(group.getZoneId()).isEqualTo(zone.getId()); + assertThat(groupProvisioning.retrieve(group.getId(), IdentityZoneHolder.get().getId())).isNotNull(); + assertThat(groupProvisioning.retrieve(group.getId(), IdentityZoneHolder.get().getId()).getDisplayName()).isEqualTo("Delete Test Group"); + assertThat(membershipManager.getMembers(group.getId(), false, IdentityZoneHolder.get().getId())).hasSize(1); //failed authenticated user mockMvc.perform( - post("/login.do") - .header("Host", zone.getSubdomain() + ".localhost") - .with(cookieCsrf()) - .accept(TEXT_HTML_VALUE) - .param("username", user.getUserName()) - .param("password", "adasda") - ) + post("/login.do") + .header("Host", zone.getSubdomain() + ".localhost") + .with(cookieCsrf()) + .accept(TEXT_HTML_VALUE) + .param("username", user.getUserName()) + .param("password", "adasda") + ) .andExpect(status().isFound()); //ensure we have some audit records //this doesn't work yet - //assertThat(template.queryForObject("select count(*) from sec_audit where identity_zone_id=?", new Object[] {user.getZoneId()}, Integer.class), greaterThan(0)); + // assertThat(template.queryForObject("select count(*) from sec_audit where identity_zone_id=?", new Object[] {user.getZoneId()}, Integer.class), greaterThan(0)) //create an external group map IdentityZoneHolder.set(zone); externalMembershipManager.mapExternalGroup(group.getId(), "externalDeleteGroup", LOGIN_SERVER, IdentityZoneHolder.get().getId()); - assertEquals(1, externalMembershipManager.getExternalGroupMapsByGroupId(group.getId(), LOGIN_SERVER, IdentityZoneHolder.get().getId()).size()); - assertThat(template.queryForObject("select count(*) from external_group_mapping where origin=?", new Object[]{LOGIN_SERVER}, Integer.class), is(1)); + assertThat(externalMembershipManager.getExternalGroupMapsByGroupId(group.getId(), LOGIN_SERVER, IdentityZoneHolder.get().getId())).hasSize(1); + assertThat(template.queryForObject("select count(*) from external_group_mapping where origin=?", new Object[]{LOGIN_SERVER}, Integer.class)).isOne(); //add user approvals approvalStore.addApproval( @@ -1685,40 +1720,36 @@ void test_delete_zone_cleans_db() throws Exception { .setStatus(Approval.ApprovalStatus.APPROVED) .setUserId(user.getId()), IdentityZoneHolder.get().getId() ); - assertEquals(1, approvalStore.getApprovals(user.getId(), client.getClientId(), IdentityZoneHolder.get().getId()).size()); + assertThat(approvalStore.getApprovals(user.getId(), client.getClientId(), IdentityZoneHolder.get().getId())).hasSize(1); //perform zone delete mockMvc.perform( - delete("/identity-zones/{id}", zone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .accept(APPLICATION_JSON)) + delete("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()); mockMvc.perform( - delete("/identity-zones/{id}", zone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .accept(APPLICATION_JSON)) + delete("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) .andExpect(status().isNotFound()); - assertThat(template.queryForObject("select count(*) from identity_zone where id=?", new Object[]{zone.getId()}, Integer.class), is(0)); - - assertThat(template.queryForObject("select count(*) from oauth_client_details where identity_zone_id=?", new Object[]{zone.getId()}, Integer.class), is(0)); - - assertThat(template.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[]{zone.getId()}, Integer.class), is(0)); - - assertThat(template.queryForObject("select count(*) from sec_audit where identity_zone_id=?", new Object[]{zone.getId()}, Integer.class), is(0)); + assertThat(template.queryForObject("select count(*) from identity_zone where id=?", new Object[]{zone.getId()}, Integer.class)).isZero(); + assertThat(template.queryForObject("select count(*) from oauth_client_details where identity_zone_id=?", new Object[]{zone.getId()}, Integer.class)).isZero(); + assertThat(template.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[]{zone.getId()}, Integer.class)).isZero(); + assertThat(template.queryForObject("select count(*) from sec_audit where identity_zone_id=?", new Object[]{zone.getId()}, Integer.class)).isZero(); + assertThat(template.queryForObject("select count(*) from users where identity_zone_id=?", new Object[]{zone.getId()}, Integer.class)).isZero(); + assertThat(template.queryForObject("select count(*) from external_group_mapping where origin=?", new Object[]{LOGIN_SERVER}, Integer.class)).isZero(); - assertThat(template.queryForObject("select count(*) from users where identity_zone_id=?", new Object[]{zone.getId()}, Integer.class), is(0)); + final String groupId = group.getId(); + String zoneId = IdentityZoneHolder.get().getId(); + assertThatThrownBy(() -> externalMembershipManager.getExternalGroupMapsByGroupId(groupId, LOGIN_SERVER, zoneId)) + .isInstanceOf(ScimResourceNotFoundException.class) + .hasMessageContainingAll("Group", " does not exist"); - assertThat(template.queryForObject("select count(*) from external_group_mapping where origin=?", new Object[]{LOGIN_SERVER}, Integer.class), is(0)); - try { - externalMembershipManager.getExternalGroupMapsByGroupId(group.getId(), LOGIN_SERVER, IdentityZoneHolder.get().getId()); - fail("no external groups should be found"); - } catch (ScimResourceNotFoundException ignored) { - } - - assertThat(template.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[]{user.getId()}, Integer.class), is(0)); - assertEquals(0, approvalStore.getApprovals(user.getId(), client.getClientId(), IdentityZoneHolder.get().getId()).size()); + assertThat(template.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[]{user.getId()}, Integer.class)).isZero(); + assertThat(approvalStore.getApprovals(user.getId(), client.getClientId(), IdentityZoneHolder.get().getId())).isEmpty(); } @Test @@ -1729,27 +1760,27 @@ void testDeleteZonePublishesEvent() throws Exception { uaaEventListener.clearEvents(); ResultActions result = mockMvc.perform( - delete("/identity-zones/{id}", zone.getId()) - .header("Authorization", "Bearer " + identityClientToken) - .accept(APPLICATION_JSON)) + delete("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()); IdentityZone deletedZone = JsonUtils.readValue(result.andReturn().getResponse().getContentAsString(), IdentityZone.class); - assertEquals(Collections.EMPTY_MAP, deletedZone.getConfig().getTokenPolicy().getKeys()); - assertNull(deletedZone.getConfig().getSamlConfig().getPrivateKey()); - assertNull(deletedZone.getConfig().getSamlConfig().getPrivateKeyPassword()); - assertEquals(serviceProviderCertificate, deletedZone.getConfig().getSamlConfig().getCertificate()); + assertThat(deletedZone.getConfig().getTokenPolicy().getKeys()).isEqualTo(emptyMap()); + assertThat(deletedZone.getConfig().getSamlConfig().getPrivateKey()).isNull(); + assertThat(deletedZone.getConfig().getSamlConfig().getPrivateKeyPassword()).isNull(); + assertThat(deletedZone.getConfig().getSamlConfig().getCertificate()).isEqualTo(serviceProviderCertificate); - assertThat(uaaEventListener.getEventCount(), is(1)); + assertThat(uaaEventListener.getEventCount()).isOne(); AbstractUaaEvent event = uaaEventListener.getLatestEvent(); - assertThat(event, instanceOf(EntityDeletedEvent.class)); - EntityDeletedEvent deletedEvent = (EntityDeletedEvent) event; - assertThat(deletedEvent.getDeleted(), instanceOf(IdentityZone.class)); + assertThat(event).isInstanceOf(EntityDeletedEvent.class); + EntityDeletedEvent deletedEvent = (EntityDeletedEvent) event; + assertThat(deletedEvent.getDeleted()).isInstanceOf(IdentityZone.class); - deletedZone = (IdentityZone) deletedEvent.getDeleted(); - assertThat(deletedZone.getId(), is(id)); - assertThat(deletedEvent.getIdentityZoneId(), is(id)); + deletedZone = deletedEvent.getDeleted(); + assertThat(deletedZone.getId()).isEqualTo(id); + assertThat(deletedEvent.getIdentityZoneId()).isEqualTo(id); String auditedIdentityZone = deletedEvent.getAuditEvent().getData(); - assertThat(auditedIdentityZone, containsString(id)); + assertThat(auditedIdentityZone).contains(id); } @Test @@ -1790,39 +1821,40 @@ void testCreateAndDeleteLimitedClientInNewZoneUsingZoneEndpoint() throws Excepti client.addAdditionalInformation("foo", "bar"); for (String url : Arrays.asList("", "/")) { mockMvc.perform( - post("/identity-zones/" + zone.getId() + "/clients" + url) - .header("Authorization", "Bearer " + identityClientZonesReadToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(client))) + post("/identity-zones/" + zone.getId() + "/clients" + url) + .header("Authorization", "Bearer " + identityClientZonesReadToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(client))) .andExpect(status().isForbidden()); } MvcResult result = mockMvc.perform( - post("/identity-zones/" + zone.getId() + "/clients") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(client))) + post("/identity-zones/" + zone.getId() + "/clients") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(client))) .andExpect(status().isCreated()).andReturn(); UaaClientDetails created = JsonUtils.readValue(result.getResponse().getContentAsString(), UaaClientDetails.class); - assertNull(created.getClientSecret()); - assertEquals("zones.write", created.getAdditionalInformation().get(ClientConstants.CREATED_WITH)); - assertEquals(Collections.singletonList(UAA), created.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS)); - assertEquals("bar", created.getAdditionalInformation().get("foo")); + assertThat(created.getClientSecret()).isNull(); + assertThat(created.getAdditionalInformation()) + .containsEntry(ClientConstants.CREATED_WITH, "zones.write") + .containsEntry(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(UAA)) + .containsEntry("foo", "bar"); checkAuditEventListener(1, AuditEventType.ClientCreateSuccess, clientCreateEventListener, id, "http://localhost:8080/uaa/oauth/token", "identity"); for (String url : Arrays.asList("", "/")) { mockMvc.perform( - delete("/identity-zones/" + zone.getId() + "/clients/" + created.getClientId(), IdentityZone.getUaaZoneId() + url) - .header("Authorization", "Bearer " + identityClientZonesReadToken) - .accept(APPLICATION_JSON)) + delete("/identity-zones/" + zone.getId() + "/clients/" + created.getClientId(), IdentityZone.getUaaZoneId() + url) + .header("Authorization", "Bearer " + identityClientZonesReadToken) + .accept(APPLICATION_JSON)) .andExpect(status().isForbidden()); } mockMvc.perform( - delete("/identity-zones/" + zone.getId() + "/clients/" + created.getClientId(), IdentityZone.getUaaZoneId()) - .header("Authorization", "Bearer " + identityClientToken) - .accept(APPLICATION_JSON)) + delete("/identity-zones/" + zone.getId() + "/clients/" + created.getClientId(), IdentityZone.getUaaZoneId()) + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()); checkAuditEventListener(1, AuditEventType.ClientDeleteSuccess, clientDeleteEventListener, id, "http://localhost:8080/uaa/oauth/token", "identity"); @@ -1835,21 +1867,21 @@ void testCreateAndDeleteLimitedClientInUAAZoneReturns403() throws Exception { client.setClientSecret("secret"); client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(UAA)); mockMvc.perform( - post("/identity-zones/uaa/clients") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(client))) + post("/identity-zones/uaa/clients") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(client))) .andExpect(status().isForbidden()); - assertEquals(0, clientCreateEventListener.getEventCount()); + assertThat(clientCreateEventListener.getEventCount()).isZero(); mockMvc.perform( - delete("/identity-zones/uaa/clients/admin") - .header("Authorization", "Bearer " + identityClientToken) - .accept(APPLICATION_JSON)) + delete("/identity-zones/uaa/clients/admin") + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) .andExpect(status().isForbidden()); - assertEquals(0, clientDeleteEventListener.getEventCount()); + assertThat(clientDeleteEventListener.getEventCount()).isZero(); } @Test @@ -1860,11 +1892,11 @@ void testCreateAdminClientInNewZoneUsingZoneEndpointReturns400() throws Exceptio new UaaClientDetails("admin-client", null, null, "client_credentials", "clients.write"); client.setClientSecret("secret"); mockMvc.perform( - post("/identity-zones/" + zone.getId() + "/clients") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(client))) + post("/identity-zones/" + zone.getId() + "/clients") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(client))) .andExpect(status().isBadRequest()); } @@ -1876,24 +1908,24 @@ void testCreatesZonesWithDuplicateSubdomains() throws Exception { IdentityZone identityZone1 = MultitenancyFixture.identityZone(id1, subdomain); IdentityZone identityZone2 = MultitenancyFixture.identityZone(id2, subdomain); mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone1))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone1))) .andExpect(status().isCreated()); checkZoneAuditEventInUaa(1, AuditEventType.IdentityZoneCreatedEvent); mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone2))) + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone2))) .andExpect(status().isConflict()); - assertEquals(1, zoneModifiedEventListener.getEventCount()); + assertThat(zoneModifiedEventListener.getEventCount()).isEqualTo(1); } @Test @@ -1905,47 +1937,47 @@ void testZoneAdminTokenAgainstZoneEndpoints() throws Exception { IdentityZoneCreationResult result2 = MockMvcUtils.createOtherIdentityZoneAndReturnResult(zone2, mockMvc, webApplicationContext, null, IdentityZoneHolder.getCurrentZoneId()); MvcResult result = mockMvc.perform( - get("/identity-zones") - .header("Authorization", "Bearer " + result1.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) - .accept(APPLICATION_JSON)) + get("/identity-zones") + .header("Authorization", "Bearer " + result1.getZoneAdminToken()) + .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); //test read your own zone only List zones = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference>() { }); - assertEquals(1, zones.size()); - assertEquals(zone1, zones.get(0).getSubdomain()); + assertThat(zones).hasSize(1); + assertThat(zones.get(0).getSubdomain()).isEqualTo(zone1); //test write your own mockMvc.perform( - put("/identity-zones/" + result1.getIdentityZone().getId()) - .header("Authorization", "Bearer " + result1.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(result1.getIdentityZone()))) + put("/identity-zones/" + result1.getIdentityZone().getId()) + .header("Authorization", "Bearer " + result1.getZoneAdminToken()) + .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(result1.getIdentityZone()))) .andExpect(status().isOk()); //test write someone elses mockMvc.perform( - put("/identity-zones/" + result2.getIdentityZone().getId()) - .header("Authorization", "Bearer " + result1.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(result2.getIdentityZone()))) + put("/identity-zones/" + result2.getIdentityZone().getId()) + .header("Authorization", "Bearer " + result1.getZoneAdminToken()) + .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(result2.getIdentityZone()))) .andExpect(status().isForbidden()); //test create as zone admin mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + result1.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(result2.getIdentityZone()))) + post("/identity-zones") + .header("Authorization", "Bearer " + result1.getZoneAdminToken()) + .header(IdentityZoneSwitchingFilter.HEADER, result1.getIdentityZone().getId()) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(result2.getIdentityZone()))) .andExpect(status().isForbidden()); } @@ -1982,8 +2014,7 @@ void testSuccessfulUserManagementInZoneUsingAdminClient() throws Exception { checkAuditEventListener(2, AuditEventType.UserModifiedEvent, userModifiedEventListener, identityZone.getId(), "http://" + subdomain + ".localhost:8080/uaa/oauth/token", "admin"); user = JsonUtils.readValue(result.getResponse().getContentAsString(), ScimUser.class); List users = getUsersInZone(subdomain, scimAdminToken); - assertTrue(users.contains(user)); - assertEquals(1, users.size()); + assertThat(users).containsExactly(user); MockHttpServletRequestBuilder delete = delete("/Users/" + user.getId()) .header("Authorization", "Bearer " + scimAdminToken) @@ -1998,7 +2029,7 @@ void testSuccessfulUserManagementInZoneUsingAdminClient() throws Exception { checkAuditEventListener(3, AuditEventType.UserDeletedEvent, userModifiedEventListener, identityZone.getId(), "http://" + subdomain + ".localhost:8080/uaa/oauth/token", "admin"); users = getUsersInZone(subdomain, scimAdminToken); - assertEquals(0, users.size()); + assertThat(users).isEmpty(); } @Test @@ -2075,49 +2106,51 @@ void userCanReadAZone_withZoneZoneIdReadToken() throws Exception { group.setDisplayName(zoneReadScope); group.setMembers(Collections.singletonList(new ScimGroupMember(user.getId()))); mockMvc.perform( - post("/Groups/zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .accept(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(group))) + post("/Groups/zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group))) .andExpect(status().isCreated()); } String userAccessToken = MockMvcUtils.getUserOAuthAccessTokenAuthCode(mockMvc, "identity", "identitysecret", user.getId(), user.getUserName(), user.getPassword(), "zones." + identityZone.getId() + ".read", IdentityZoneHolder.getCurrentZoneId()); MvcResult result = mockMvc.perform( - get("/identity-zones/" + identityZone.getId()) - .header("Authorization", "Bearer " + userAccessToken) - .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId()) - .accept(APPLICATION_JSON)) + get("/identity-zones/" + identityZone.getId()) + .header("Authorization", "Bearer " + userAccessToken) + .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId()) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); IdentityZone zoneResult = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference() { }); - assertEquals(identityZone, zoneResult); - assertNull(zoneResult.getConfig().getSamlConfig().getPrivateKey()); - assertEquals(Collections.EMPTY_MAP, zoneResult.getConfig().getTokenPolicy().getKeys()); + assertThat(zoneResult).isEqualTo(identityZone); + assertThat(zoneResult.getConfig().getSamlConfig().getPrivateKey()).isNull(); + assertThat(zoneResult.getConfig().getTokenPolicy().getKeys()).isEqualTo(emptyMap()); String userAccessTokenReadAndAdmin = MockMvcUtils.getUserOAuthAccessTokenAuthCode(mockMvc, "identity", "identitysecret", user.getId(), user.getUserName(), user.getPassword(), "zones." + identityZone.getId() + ".read " + "zones." + identityZone.getId() + ".admin ", IdentityZoneHolder.getCurrentZoneId()); result = mockMvc.perform( - get("/identity-zones/" + identityZone.getId()) - .header("Authorization", "Bearer " + userAccessTokenReadAndAdmin) - .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId()) - .accept(APPLICATION_JSON)) + get("/identity-zones/" + identityZone.getId()) + .header("Authorization", "Bearer " + userAccessTokenReadAndAdmin) + .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId()) + .accept(APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); zoneResult = JsonUtils.readValue(result.getResponse().getContentAsString(), new TypeReference() { }); - assertEquals(identityZone, zoneResult); - assertNull(zoneResult.getConfig().getSamlConfig().getPrivateKey()); - assertEquals(serviceProviderCertificate, zoneResult.getConfig().getSamlConfig().getCertificate()); - assertNull(zoneResult.getConfig().getSamlConfig().getPrivateKeyPassword()); - assertEquals(Collections.EMPTY_MAP, zoneResult.getConfig().getTokenPolicy().getKeys()); - assertEquals("kid", zoneResult.getConfig().getTokenPolicy().getActiveKeyId()); + assertThat(zoneResult).isEqualTo(identityZone); + assertThat(zoneResult.getConfig().getSamlConfig()) + .returns(null, SamlConfig::getPrivateKey) + .returns(null, SamlConfig::getPrivateKeyPassword) + .returns(serviceProviderCertificate, SamlConfig::getCertificate); + assertThat(zoneResult.getConfig().getTokenPolicy()) + .returns("kid", TokenPolicy::getActiveKeyId) + .returns(emptyMap(), TokenPolicy::getKeys); } @Test @@ -2256,34 +2289,23 @@ void testCreateZoneWithDefaultIdp() throws Exception { uaaAdminClientToken, identityZoneConfiguration ); - assertEquals("originkey", zone.getConfig().getDefaultIdentityProvider()); - } - - private static class IdentityZonesBaseUrlsArgumentsSource implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of( - Arguments.of("/identity-zones"), - Arguments.of("/identity-zones/") - ); - } + assertThat(zone.getConfig().getDefaultIdentityProvider()).isEqualTo("originkey"); } private IdentityZone createZoneReturn() throws Exception { String id = generator.generate(); IdentityZone zone = createZone(id, HttpStatus.CREATED, identityClientToken, new IdentityZoneConfiguration()); - assertEquals(id, zone.getId()); - assertEquals(id.toLowerCase(), zone.getSubdomain()); - assertFalse(zone.getConfig().getTokenPolicy().isRefreshTokenUnique()); - assertFalse(zone.getConfig().getTokenPolicy().isRefreshTokenRotate()); - assertEquals(OPAQUE.getStringValue(), zone.getConfig().getTokenPolicy().getRefreshTokenFormat()); + assertThat(zone.getId()).isEqualTo(id); + assertThat(zone.getSubdomain()).isEqualTo(id.toLowerCase()); + assertThat(zone.getConfig().getTokenPolicy().isRefreshTokenUnique()).isFalse(); + assertThat(zone.getConfig().getTokenPolicy().isRefreshTokenRotate()).isFalse(); + assertThat(zone.getConfig().getTokenPolicy().getRefreshTokenFormat()).isEqualTo(OPAQUE.getStringValue()); checkAuditEventListener(1, AuditEventType.IdentityZoneCreatedEvent, zoneModifiedEventListener, IdentityZone.getUaaZoneId(), "http://localhost:8080/uaa/oauth/token", "identity"); //validate that default groups got created ScimGroupProvisioning groupProvisioning = webApplicationContext.getBean(ScimGroupProvisioning.class); for (String g : UserConfig.DEFAULT_ZONE_GROUPS) { - assertNotNull(groupProvisioning.getByName(g, id)); + assertThat(groupProvisioning.getByName(g, id)).isNotNull(); } return zone; } @@ -2296,7 +2318,7 @@ private ScimUser createUser(String token, String subdomain) throws Exception { .header("Authorization", "Bearer " + token) .contentType(APPLICATION_JSON) .content(requestBody); - if (subdomain != null && !subdomain.equals("")) + if (subdomain != null && !subdomain.isEmpty()) post.with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")); MvcResult result = mockMvc.perform(post) @@ -2330,8 +2352,8 @@ private IdentityZone createZoneUsingToken(String token) throws Exception { private IdentityZone getIdentityZone(String id, HttpStatus expect, String token) throws Exception { MvcResult result = mockMvc.perform( - get("/identity-zones/" + id) - .header("Authorization", "Bearer " + token)) + get("/identity-zones/" + id) + .header("Authorization", "Bearer " + token)) .andExpect(status().is(expect.value())) .andReturn(); @@ -2359,10 +2381,10 @@ private IdentityZone createZone(String id, HttpStatus expect, String expectedCon identityZone.getConfig().getSamlConfig().setCertificate(serviceProviderCertificate); MvcResult result = mockMvc.perform( - post("/identity-zones") - .header("Authorization", "Bearer " + token) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) + post("/identity-zones") + .header("Authorization", "Bearer " + token) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) .andExpect(status().is(expect.value())) .andExpect(content().string(containsString(expectedContent))) .andReturn(); @@ -2375,10 +2397,10 @@ private IdentityZone createZone(String id, HttpStatus expect, String expectedCon private IdentityZone updateZone(String id, IdentityZone identityZone, HttpStatus expect, String expectedContent, String token) throws Exception { MvcResult result = mockMvc.perform( - put("/identity-zones/" + id) - .header("Authorization", "Bearer " + token) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) + put("/identity-zones/" + id) + .header("Authorization", "Bearer " + token) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(identityZone))) .andDo(print()) .andExpect(status().is(expect.value())) .andExpect(content().string(containsString(expectedContent))) @@ -2404,14 +2426,15 @@ private void checkZoneAuditEventInUaa(int eventCoun private void checkAuditEventListener(int eventCount, AuditEventType eventType, TestApplicationEventListener eventListener, String identityZoneId, String issuer, String subject) { T event = eventListener.getLatestEvent(); - assertEquals(eventCount, eventListener.getEventCount()); + assertThat(eventListener.getEventCount()).isEqualTo(eventCount); if (eventCount > 0) { - assertEquals(eventType, event.getAuditEvent().getType()); - assertEquals(identityZoneId, event.getAuditEvent().getIdentityZoneId()); + assertThat(event.getAuditEvent().getType()).isEqualTo(eventType); + assertThat(event.getAuditEvent().getIdentityZoneId()).isEqualTo(identityZoneId); String origin = event.getAuditEvent().getOrigin(); if (hasText(origin) && !origin.contains("opaque-token=present")) { - assertTrue(origin.contains("iss=" + issuer)); - assertTrue(origin.contains("sub=" + subject)); + assertThat(origin) + .contains("iss=" + issuer) + .contains("sub=" + subject); } } } @@ -2436,4 +2459,15 @@ private List getUsersInZone(String subdomain, String token) throws Exc return JsonUtils.readValue(root.get("resources").toString(), new TypeReference>() { }); } + + private static class IdentityZonesBaseUrlsArgumentsSource implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of("/identity-zones"), + Arguments.of("/identity-zones/") + ); + } + } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlInitializationMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlInitializationMockMvcTests.java deleted file mode 100644 index 13e308fc592..00000000000 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlInitializationMockMvcTests.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.cloudfoundry.identity.uaa.DefaultTestContext; -import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.opensaml.saml2.metadata.provider.MetadataProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; -import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; -import org.springframework.security.saml.metadata.MetadataMemoryProvider; -import org.springframework.web.context.WebApplicationContext; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -@DefaultTestContext -class SamlInitializationMockMvcTests { - private NonSnarlMetadataManager spManager; - private String entityID; - private String entityAlias; - private IdentityZoneProvisioning zoneProvisioning; - - @BeforeEach - void setUp(@Autowired WebApplicationContext webApplicationContext) { - zoneProvisioning = webApplicationContext.getBean(IdentityZoneProvisioning.class); - spManager = webApplicationContext.getBean(NonSnarlMetadataManager.class); - entityID = webApplicationContext.getBean("samlEntityID", String.class); - entityAlias = webApplicationContext.getBean("samlSPAlias", String.class); - } - - @Test - void sp_initialized_in_non_snarl_metadata_manager() throws Exception { - ExtendedMetadataDelegate localServiceProvider = spManager.getLocalServiceProvider(); - assertNotNull(localServiceProvider); - MetadataProvider provider = localServiceProvider.getDelegate(); - assertNotNull(provider); - assertTrue(provider instanceof MetadataMemoryProvider); - String providerSpAlias = spManager.getProviderSpAlias(localServiceProvider); - assertEquals(entityAlias, providerSpAlias); - assertEquals(entityID, spManager.getEntityIdForAlias(providerSpAlias)); - } - - @Test - void sp_initialization_in_non_snarl_metadata_manager() throws Exception { - String subdomain = new RandomValueStringGenerator().generate().toLowerCase(); - IdentityZone zone = new IdentityZone(); - zone.setConfig(new IdentityZoneConfiguration()); - zone.setSubdomain(subdomain); - zone.setId(subdomain); - zone.setName(subdomain); - zone = zoneProvisioning.create(zone); - IdentityZoneHolder.set(zone); - ExtendedMetadataDelegate localServiceProvider = spManager.getLocalServiceProvider(); - assertNotNull(localServiceProvider); - MetadataProvider provider = localServiceProvider.getDelegate(); - assertNotNull(provider); - assertTrue(provider instanceof MetadataMemoryProvider); - String providerSpAlias = spManager.getProviderSpAlias(localServiceProvider); - assertEquals(subdomain + "." + entityAlias, providerSpAlias); - assertEquals(addSubdomainToEntityId(entityID, subdomain), spManager.getEntityIdForAlias(providerSpAlias)); - } - - String addSubdomainToEntityId(String entityId, String subdomain) { - if (UaaUrlUtils.isUrl(entityId)) { - return UaaUrlUtils.addSubdomainToUrl(entityId, subdomain); - } else { - return subdomain + "." + entityId; - } - } -} diff --git a/uaa/src/test/resources/integration_test_properties.yml b/uaa/src/test/resources/integration_test_properties.yml index 93c3a0e31a9..f6f3517e642 100644 --- a/uaa/src/test/resources/integration_test_properties.yml +++ b/uaa/src/test/resources/integration_test_properties.yml @@ -4,13 +4,13 @@ servlet: encryption: active_key_label: CHANGE-THIS-KEY encryption_keys: - - label: CHANGE-THIS-KEY - passphrase: CHANGEME + - label: CHANGE-THIS-KEY + passphrase: CHANGEME issuer: uri: http://localhost:8080/uaa -#The secret that an external login server will use to authenticate to the uaa using the id `login` +# The secret that an external login server will use to authenticate to the uaa using the id `login` LOGIN_SECRET: loginsecret jwt: @@ -51,62 +51,71 @@ jwt: rotate: false unique: false login: - serviceProviderKey: | - -----BEGIN RSA PRIVATE KEY----- - MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5 - L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vA - fpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQAB - AoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges - 7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGu - lhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3Xp - ubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYX - kKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyL - gu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWK - vw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCe - A2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHS - N+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMB - qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - -----END RSA PRIVATE KEY----- - serviceProviderKeyPassword: password - serviceProviderCertificate: | - -----BEGIN CERTIFICATE----- - MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO - MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO - MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h - cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx - CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM - BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb - BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN - ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W - qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw - znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha - MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc - gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD - VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD - VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh - QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ - 0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC - KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK - RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= - -----END CERTIFICATE----- url: http://localhost:8080/uaa entityBaseURL: http://localhost:8080/uaa - entityID: cloudfoundry-saml-login + entityID: integration-saml-entity-id saml: - #Entity ID Alias to login at /saml/SSO/alias/{login.saml.entityIDAlias} + activeKeyId: key1 + keys: + key1: + key: | + -----BEGIN PRIVATE KEY----- + MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMe0LmBRfEEqkSpl + MuQ28XA0ac0iSCA07A5BU1uk7RZUciK+KDkvf1apL27SGcD47swID8qWsBHhtdp5 + VWHB9Q9gEoilppNYVBHlxNHVQVkkv84X28B+k7DOegProMMKdBWlsKO0NhZf7HqK + bGfwcJjGEyiXpmdNtKwVbpVmMUyNAgMBAAECgYBU6PZi+6KCLrAkP30A7Z+AXrix + gKb8EqRfd0UTDS/FM8iHnySJE/nnhe3mB6ztkKov1CmqsKFSKQ7iQn6cHxSrYC9p + kdukIRSZuKnToiWB0DjjSjSvQI+jWaA3Ea6WGJZE3jcuu7N+H2oo+GV6DZ2/IUZD + HLkaTopb+3whLjHivQJBAP91x4ED412Pdem5sd/Go3PgZM7Gd1z8BcCjA8amB7bO + mAhzt/nS3w4eEbpeGx8nTDJAQS+h0OFk9heQoGdc0fMCQQDIIDvq8DC7GoB3cw5u + GJ5ueIBTdKc9/a7h90vUQ6b3Z3IWzUnHLIuC78OyM+PHq1G2fhgBqXELOYAxcVJv + Wod/AkEAzerHfPSAYptQRa08dw/sE2yudYq/DoHLtULxuT99+lo/bJiylLrot71/ + NsXCgPMxVVQ790QtVnIGeGpJEehdBwJBAJ4DZYvxLmjtWfX2sLQZWC7dkmVSvCJk + RUtB2Wu2JwU9doWufcx3zYgLDDeOZRFoodI36XiWcx1rv15Knc4yar0CQDSnv0G9 + iwuyFQkme9uTvqkKNHbw8rh1XWBINQSpAwGrLjmm13AkuoskJ42hHQlRwM0hGE4K + 4480Pulwy1fqEj8= + -----END PRIVATE KEY----- + passphrase: password + certificate: | + -----BEGIN CERTIFICATE----- + MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO + MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO + MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h + cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx + CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM + BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb + BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN + ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W + qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw + znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha + MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc + gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD + VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD + VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh + QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ + 0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC + KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK + RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0= + -----END CERTIFICATE----- + # Entity ID Alias to login at /saml/SSO/alias/{login.saml.entityIDAlias} #entityIDAlias: cloudfoundry-saml-login - #Default nameID if IDP nameID is not set - nameID: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified' - #Default assertionConsumerIndex if IDP value is not set + # Default nameID if IDP nameID is not set + nameID: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' + # Default assertionConsumerIndex if IDP value is not set assertionConsumerIndex: 0 - #Local/SP metadata - sign metadata + # Local/SP metadata - sign metadata signMetaData: true - #Local/SP metadata - requests signed + # Local/SP metadata - requests signed signRequest: true - #Local/SP metadata - want incoming assertions signed - #wantAssertionSigned: true - #Algorithm for SAML signatures. Defaults to SHA1. Accepts SHA1, SHA256, SHA512 + # Local/SP metadata - want incoming assertions signed + wantAssertionSigned: true + # Algorithm for SAML signatures. Defaults to SHA256. Accepts SHA1, SHA256, SHA512 #signatureAlgorithm: SHA256 + providers: + testsaml-redirect-binding: + idpMetadata: classpath:test-saml-idp-metadata-redirect-binding.xml + testsaml-post-binding: + idpMetadata: classpath:test-saml-idp-metadata-post-binding.xml socket: # URL metadata fetch - pool timeout connectionManagerTimeout: 10000 diff --git a/uaa/src/test/resources/sample-okta-localhost.xml b/uaa/src/test/resources/sample-okta-localhost.xml index 0aa024a150e..7b3bbcd1d7a 100644 --- a/uaa/src/test/resources/sample-okta-localhost.xml +++ b/uaa/src/test/resources/sample-okta-localhost.xml @@ -11,15 +11,54 @@ ~ subcomponent's license, as noted in the LICENSE file. ~ ****************************************************************************** --> -MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG - A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU - MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu - Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC - VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM - BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN - AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU - WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O - Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL - 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk - vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6 - GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified \ No newline at end of file + + + + + + MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu + Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC + VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM + BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN + AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU + WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O + Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL + 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk + vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6 + GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFb + + + + + + + + MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu + Zm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC + VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM + BE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN + AQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU + WWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O + Bw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL + 3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk + vvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6 + GFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFb + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + + + \ No newline at end of file diff --git a/uaa/src/test/resources/test.saml.metadata b/uaa/src/test/resources/test.saml.metadata deleted file mode 100644 index b629f1ff259..00000000000 --- a/uaa/src/test/resources/test.saml.metadata +++ /dev/null @@ -1,73 +0,0 @@ - - - - - 2014-05-14T19:05:47Z - Exported by VMware Identity Server (c) 2012 - - - - - - - MIIDMDCCAhigAwIBAgIJAMEIUKk4K5cQMA0GCSqGSIb3DQEBCwUAMEAxMTAvBgNVBAMTKENBLCBD - Tj13aW4yMDEyLXNzbzIsIGRjPXZzcGhlcmUsZGM9bG9jYWwxCzAJBgNVBAYTAlVTMB4XDTE0MDEw - MzA2MjkyNVoXDTI0MDEwMTA2MjkyNVowOTEqMCgGA1UEAxMhc3Nvc2VydmVyU2lnbixkYz12c3Bo - ZXJlLGRjPWxvY2FsMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB - ANJxS8rqX5H4J49v9rMuOx0YSb3HnPDiHV5P16xBtnSUVnFfhqS8oOirnETsKROVLMlud0jH/0G1 - hcAqF6MgKogpHQrP9gnQOs+W7rgsEDt8kCC6CMbuhEOks22jRWyU0o24F+JYIJviJxPpmMUJp+yL - WDT/uwZ8O0PuaW42yyKcnAUdGbgLVukTe+Q0EcQDe3xBZnI0nrEjCCu1MGmb74sDhQMY+CcYGc6c - Cf03fDvt3RgGQCqNRztGOw1r3cwSvKma4XZOR8vlZsVk+s5t0QoUJVm452rDyyrj7EN1tIKjV8mL - dz2nBBo6mM6YIdeNQiG12qEW2xQj/6zxUl2RZykCAwEAAaM0MDIwCwYDVR0PBAQDAgTwMCMGA1Ud - EQQcMBqCGHdpbjIwMTItc3NvMi5sb2NhbGRvbWFpbjANBgkqhkiG9w0BAQsFAAOCAQEAUvcs2S0S - TmUjqpjdr9xJzPDHnwVodmkxdVLFSTsu6pCdadX654im07uRjUpoa4AAKpj7T7beUavM30yIgskE - 3XCT/e5bht7oeh5dtNmm2Dj0CGsnbqVfO82aT2/v4N8zc94fGtz3Cb23l3D/z0jf9cg+Q/fgBx6X - ZrgQLPVYGh65BMvXq8o3AOBV5+WfPTlLCgE70ayISpgp3C/8pi2zaUSSR56nkbK/z9660cRaAjP/ - 6HV9E8na81gIG+O4OideyzuHDEyjEAWN5EUsobYCSSUG7A6F1vH5Bu2o/5HwBm2D4S2Qm+cfu/3U - gfxRJIOvd3al3XL/nzI8IhX1lWIj3Q== - MIIDJjCCAg6gAwIBAgIJAPwKvFMGehwIMA0GCSqGSIb3DQEBCwUAMEAxMTAvBgNVBAMTKENBLCBD - Tj13aW4yMDEyLXNzbzIsIGRjPXZzcGhlcmUsZGM9bG9jYWwxCzAJBgNVBAYTAlVTMB4XDTE0MDEw - MzA2MjkxNVoXDTI0MDEwMTA2MjkxNVowQDExMC8GA1UEAxMoQ0EsIENOPXdpbjIwMTItc3NvMiwg - ZGM9dnNwaGVyZSxkYz1sb2NhbDELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw - ggEKAoIBAQD44ne1tQH3IskIC3f0z07KBIDREvGkyIIaBBs8ArtIhSVZeWGftX7RaEqrVe+qOdGh - ubOHQdv7Z+meq9jWaJ4mrGERWeKJM6ctGm0razJrxyo1Xw8sQZQAc84Q8dneFfd9pTA5hqCovYp5 - Hyv0guS8Xzhc64wQIRAELiPDh1xmjeOYway1x5zmYF3MJMmrHUTxnmkVn3H8oJ1FpvAzDN5FW1D6 - nr7L2CsmWujqOrupC/Rt4TVmfZw5Raxf3NnYLk0Ec9LgR/Iqg/fpOfRzBGKt37AJH5GUGav85+O2 - hXro1HqWr4d0QLfl9sfdIXYPiUNZB2fWrrykMEB8xfWj8e9XAgMBAAGjIzAhMA4GA1UdDwEB/wQE - AwICBDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAi7fVQM3Bzm3IsxPDw8tC/ - k9nDyjt1vh1vYPw5qjZ0R+tnF992Y4WO97nYvLispbTzh6d84V7vS/vG3PipBdLoFtu41dfG4pmR - mzHyAu1iNJ+YtmOHrU3l1J3xcM9QKEyhlSz2ZKC+ZQ7FNTbb+HF4OEdAXatksFS/AThwiVWOMV79 - mVIcizl+PpfGxqChlusdiGRrWdcg8u4O2ysOvsy9cRWUlhrDhHEI39mYSqfvLcgsbsaob3h/NJis - GA2/KCiZWwsCmjYq0W+7CgtonmYJPhRSWAASq7AarSiTy6gpFGdIXcvM7CE5gj5KAKV9eil3S3P9 - Vz/wY3LzufrcD22h - - - - - - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent - - - - - - - - - - - diff --git a/uaa/src/test/resources/test/config/saml-algorithm-invalid.yml b/uaa/src/test/resources/test/config/saml-algorithm-invalid.yml new file mode 100644 index 00000000000..84b266cd17c --- /dev/null +++ b/uaa/src/test/resources/test/config/saml-algorithm-invalid.yml @@ -0,0 +1,3 @@ +login: + saml: + signatureAlgorithm: FOO123 diff --git a/uaa/src/test/resources/test/config/saml-algorithm-sha1.yml b/uaa/src/test/resources/test/config/saml-algorithm-sha1.yml new file mode 100644 index 00000000000..9178588a5c2 --- /dev/null +++ b/uaa/src/test/resources/test/config/saml-algorithm-sha1.yml @@ -0,0 +1,3 @@ +login: + saml: + signatureAlgorithm: SHA1