From 6d54702ea1918a4cbe10f30eecf7d01310c88075 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 6 Jan 2025 17:02:16 -0700 Subject: [PATCH 1/4] SHIBUI-2646: MDQ fixes for trailing slashes --- .../ui/controller/EntitiesController.java | 19 +------- .../EntitiesControllerIntegrationTests.groovy | 44 ------------------- .../controller/EntitiesControllerTests.groovy | 34 -------------- gradle.properties | 2 +- 4 files changed, 3 insertions(+), 96 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesController.java index 833d3f71f..8ab82ac04 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesController.java @@ -49,7 +49,7 @@ public class EntitiesController { @Autowired private EntityDescriptorRepository entityDescriptorRepository; - @RequestMapping(value = "/entities", produces = "application/xml") + @RequestMapping(value = {"/entities", "/entities/"}, produces = {"application/xml", "application/xml"}) @Operation(description = "Endpoint based on the MDQ spec to return all entity's information. see: https://spaces.at.internet2.edu/display/MDQ/Metadata+Query+Protocol", summary = "Return all the entities from the entity's id", method = "GET") @Transactional(readOnly = true) @@ -72,28 +72,13 @@ public ResponseEntity getAllXml() throws MarshallingException, ResolverExcept return new ResponseEntity<>("" + xmlDeclarationClean, new HttpHeaders(), HttpStatus.OK); } - @RequestMapping(value = "/entities/{entityId:.*}") - @Operation(description = "Endpoint based on the MDQ spec to return a single entity's information. see: https://spaces.at.internet2.edu/display/MDQ/Metadata+Query+Protocol", - summary = "Return a single entity from the entity's id", method = "GET") - @Transactional(readOnly = true) - public ResponseEntity getOne(final @PathVariable String entityId, HttpServletRequest request) throws UnsupportedEncodingException, ResolverException { - EntityDescriptor entityDescriptor = this.getEntityDescriptor(entityId); - if (entityDescriptor == null) { - return ResponseEntity.notFound().build(); - } - EntityDescriptorRepresentation entityDescriptorRepresentation = entityDescriptorService.createRepresentationFromDescriptor(entityDescriptor); - HttpHeaders headers = new HttpHeaders(); - headers.set("Last-Modified", formatModifiedDate(entityDescriptorRepresentation)); - return new ResponseEntity<>(entityDescriptorRepresentation, headers, HttpStatus.OK); - } - private String formatModifiedDate(EntityDescriptorRepresentation entityDescriptorRepresentation) { Instant instant = entityDescriptorRepresentation.getModifiedDateAsDate().toInstant(ZoneOffset.UTC); Date date = Date.from(instant); return DateUtils.formatDate(date, DateUtils.PATTERN_RFC1123); } - @RequestMapping(value = "/entities/{entityId:.*}", produces = "application/xml") + @RequestMapping(value = {"/entities/{entityId:.*}", "/entities/{entityId:.*}/"}, produces = {"application/xml", "application/xml"}) @Operation(description = "Endpoint based on the MDQ spec to return a single entity's information. see: https://spaces.at.internet2.edu/display/MDQ/Metadata+Query+Protocol", summary = "Return a single entity from the entity's id", method = "GET") @Transactional(readOnly = true) diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy index c55ce871d..4afa6336e 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy @@ -99,50 +99,6 @@ class EntitiesControllerIntegrationTests extends AbstractBaseDataJpaTest { } } - @WithMockAdmin - def 'GET /entities/{resourceId} existing'() { - given: - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: "admingroup") - entityDescriptorRepository.save(entityDescriptorOne) - entityManager.flush() - entityManager.clear() - - when: - def result = mockMvc.perform(get("/entities/eid1")) - - then: - result.andExpect(status().isOk()) - .andExpect(jsonPath("\$.entityId").value("eid1")) - .andExpect(jsonPath("\$.serviceProviderName").value("sp1")) - .andExpect(jsonPath("\$.serviceEnabled").value(true)) - .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) - } - - @WithMockUser(value = "someUser", roles = ["USER"]) - def 'GET /entities/{resourceId} existing, validate group access'() { - given: - Group g = userService.getCurrentUserGroup() - - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: "someUser") - def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, idOfOwner: Group.ADMIN_GROUP.getOwnerId()) - - entityDescriptorRepository.saveAndFlush(entityDescriptorOne) - entityDescriptorRepository.saveAndFlush(entityDescriptorTwo) - - ownershipRepository.saveAndFlush(new Ownership(g, entityDescriptorOne)) - ownershipRepository.saveAndFlush(new Ownership(Group.ADMIN_GROUP, entityDescriptorTwo)) - - when: - def result = mockMvc.perform(get("/entities/eid1")) - - then: - result.andExpect(status().isOk()) - .andExpect(jsonPath("\$.entityId").value("eid1")) - .andExpect(jsonPath("\$.serviceProviderName").value("sp1")) - .andExpect(jsonPath("\$.serviceEnabled").value(true)) - .andExpect(jsonPath("\$.idOfOwner").value("someUser")) - } - @WithMockUser(value = "someUser", roles = ["USER"]) def 'GET /entities/{resourceId} existing, owned by some other user'() { when: diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy index 1b03ee6f8..c62c88eaa 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy @@ -94,40 +94,6 @@ class EntitiesControllerTests extends AbstractBaseDataJpaTest { result.andExpect(status().isNotFound()) } - def 'GET /api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1'() { - when: - def result = mockMvc.perform(get('/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1')) - - then: - // Response headers section 2.5 - // from the spec https://www.ietf.org/archive/id/draft-young-md-query-14.txt - result.andExpect(status().isOk()) - .andExpect(header().exists(HttpHeaders.CONTENT_TYPE)) // MUST HAVE -// .andExpect(header().exists(HttpHeaders.CONTENT_LENGTH)) // SHOULD HAVE - should end up from etag filter, so skipped for test -// .andExpect(header().exists(HttpHeaders.CACHE_CONTROL)) // SHOULD HAVE - should be included by Spring Security -// .andExpect(header().exists(HttpHeaders.ETAG)) // MUST HAVE - is done by filter, so skipped for test - .andExpect(header().exists(HttpHeaders.LAST_MODIFIED)) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath('$.entityId', is("http://test.scaldingspoon.org/test1"))) - } - - def 'GET /entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1'() { - when: - def result = mockMvc.perform(get('/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1')) - - then: - // Response headers section 2.5 - // from the spec https://www.ietf.org/archive/id/draft-young-md-query-14.txt - result.andExpect(status().isOk()) - .andExpect(header().exists(HttpHeaders.CONTENT_TYPE)) // MUST HAVE -// .andExpect(header().exists(HttpHeaders.CONTENT_LENGTH)) // SHOULD HAVE - should end up from etag filter, so skipped for test -// .andExpect(header().exists(HttpHeaders.CACHE_CONTROL)) // SHOULD HAVE - should be included by Spring Security -// .andExpect(header().exists(HttpHeaders.ETAG)) // MUST HAVE - is done by filter, so skipped for test - .andExpect(header().exists(HttpHeaders.LAST_MODIFIED)) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath('$.entityId').value("http://test.scaldingspoon.org/test1")) - } - def 'GET /api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1 XML'() { given: def expectedBody = ''' diff --git a/gradle.properties b/gradle.properties index 1ff4e5988..7e691b5f8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ name=shibui group=edu.internet2.tier.shibboleth.admin.ui -version=2.0.5 +version=2.0.6-SNAPSHOT ### library versions ### ## As of 2-23-23 From 58cb10dca91818d23c965fd39d1374eb3dad7a10 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 7 Jan 2025 11:18:55 -0700 Subject: [PATCH 2/4] SHIBUI-2646: MDQ fixes for trailing slashes for PAC4J config --- .../unicon/shibui/pac4j/Pac4jSpringSecurityConfig.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jSpringSecurityConfig.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jSpringSecurityConfig.java index f3c64ec4a..a27b6265a 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jSpringSecurityConfig.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jSpringSecurityConfig.java @@ -71,7 +71,9 @@ public AuditorAware pac4jAuditorAware() { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests().requestMatchers(new AntPathRequestMatcher("/unsecured/**/*"), - new AntPathRequestMatcher("/entities/**/*"), + new AntPathRequestMatcher("/entities*"), + new AntPathRequestMatcher("/entities/*"), + new AntPathRequestMatcher("/entities/**"), new AntPathRequestMatcher("/favicon.ico"), new AntPathRequestMatcher("/assets/**/*.png"), new AntPathRequestMatcher("/static/**/*"), @@ -127,7 +129,9 @@ public WebSecurityCustomizer webSecurityCustomizer() { firewall.setAllowSemicolon(true); return (web) -> web.ignoring().requestMatchers(new AntPathRequestMatcher("/unsecured/**/*"), - new AntPathRequestMatcher("/entities/**/*"), + new AntPathRequestMatcher("/entities*"), + new AntPathRequestMatcher("/entities/*"), + new AntPathRequestMatcher("/entities/**"), new AntPathRequestMatcher("/favicon.ico"), new AntPathRequestMatcher("/assets/**/*.png"), new AntPathRequestMatcher("/static/**/*"), From 66123adc40f4ba71fa06045278f559e67fed07a4 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 7 Jan 2025 11:52:33 -0700 Subject: [PATCH 3/4] SHIBUI-2646: fixes for trailing slashes for non-PAC4J config --- .../admin/ui/configuration/SpringSecurityConfig.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/SpringSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/SpringSecurityConfig.java index c5933f880..2f6a82a82 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/SpringSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/SpringSecurityConfig.java @@ -112,7 +112,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests() .requestMatchers(new AntPathRequestMatcher("/unsecured/**/*"), new AntPathRequestMatcher("/entities*"), - new AntPathRequestMatcher("/entities/**/*"), + new AntPathRequestMatcher("/entities/*"), + new AntPathRequestMatcher("/entities/**"), new AntPathRequestMatcher("/actuator/**"), new AntPathRequestMatcher("/api/beacon/send")).permitAll() .anyRequest().hasAnyRole(acceptedAuthenticationRoles) @@ -158,7 +159,9 @@ public InMemoryUserDetailsManager userDetailsManager() { @Profile("!no-auth") public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().requestMatchers(new AntPathRequestMatcher("/unsecured/**/*"), - new AntPathRequestMatcher("/entities/**/*"), + new AntPathRequestMatcher("/entities*"), + new AntPathRequestMatcher("/entities/*"), + new AntPathRequestMatcher("/entities/**"), new AntPathRequestMatcher("/favicon.ico"), new AntPathRequestMatcher("/assets/**/*.png"), new AntPathRequestMatcher("/static/**/*"), From fa8ae2c05122c59317d72fd66c53e2d945297297 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Wed, 8 Jan 2025 11:02:19 -0700 Subject: [PATCH 4/4] SHIBUI-2646: fixes for MDQ when not using pac4j and entity ids have a slash in the id --- .../admin/ui/configuration/SpringSecurityConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/SpringSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/SpringSecurityConfig.java index 2f6a82a82..ad34b277a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/SpringSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/SpringSecurityConfig.java @@ -191,7 +191,9 @@ public WebSecurityCustomizer noAuthWebSecurityCustomizer() { } private HttpFirewall defaultFirewall() { - return new DefaultHttpFirewall(); + DefaultHttpFirewall defaultFirewall = new DefaultHttpFirewall(); + defaultFirewall.setAllowUrlEncodedSlash(true); + return defaultFirewall; } class SimpleAuthenticationProvider implements AuthenticationProvider {