From 67bcf1881f5c351684900bd871e565564b487cab Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Thu, 18 Dec 2025 13:54:05 +0000 Subject: [PATCH 1/2] AB#72068 Allow requests to admin endpoint This allows a frontend to make requests to the tool support admin endpoints. --- .../uk/ac/ox/ctl/admin/AdminProperties.java | 22 +++++++++++++++ .../uk/ac/ox/ctl/admin/AdminWebSecurity.java | 27 ++++++++++++++++++- .../ox/ctl/admin/AdminControllerWebTest.java | 2 +- 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/main/java/uk/ac/ox/ctl/admin/AdminProperties.java diff --git a/src/main/java/uk/ac/ox/ctl/admin/AdminProperties.java b/src/main/java/uk/ac/ox/ctl/admin/AdminProperties.java new file mode 100644 index 0000000..d9cbe00 --- /dev/null +++ b/src/main/java/uk/ac/ox/ctl/admin/AdminProperties.java @@ -0,0 +1,22 @@ +package uk.ac.ox.ctl.admin; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.Collections; +import java.util.List; + +@Configuration +@ConfigurationProperties(prefix = "admin") +public class AdminProperties { + + private List corsOrigins = Collections.emptyList(); + + public List getCorsOrigins() { + return corsOrigins; + } + + public void setCorsOrigins(List corsOrigins) { + this.corsOrigins = corsOrigins; + } +} \ No newline at end of file diff --git a/src/main/java/uk/ac/ox/ctl/admin/AdminWebSecurity.java b/src/main/java/uk/ac/ox/ctl/admin/AdminWebSecurity.java index 9c483ea..e5df5c8 100644 --- a/src/main/java/uk/ac/ox/ctl/admin/AdminWebSecurity.java +++ b/src/main/java/uk/ac/ox/ctl/admin/AdminWebSecurity.java @@ -14,6 +14,9 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.util.StringUtils; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.List; import java.util.regex.Pattern; @@ -53,12 +56,34 @@ private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder } return NOOP_PASSWORD_PREFIX + password; } + + public CorsConfigurationSource corsConfigurationSource(AdminProperties adminProperties) { + if (adminProperties.getCorsOrigins().contains(CorsConfiguration.ALL)) { + log.warn("CORS allowed origins is set to '*', this is not recommended for production environments."); + } + if (adminProperties.getCorsOrigins().isEmpty()) { + log.info("No Admin CORS allowed origins configured."); + } else { + log.info("Admin CORS allowed origins: {}", String.join(", ", adminProperties.getCorsOrigins())); + } + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedHeader(CorsConfiguration.ALL); + config.addAllowedMethod(CorsConfiguration.ALL); + for (String origin : adminProperties.getCorsOrigins()) { + config.addAllowedOrigin(origin); + } + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/admin/**", config); + return source; + } @Bean @Order(1) - public SecurityFilterChain adminConfiguration(HttpSecurity http) throws Exception { + public SecurityFilterChain adminConfiguration(HttpSecurity http, AdminProperties adminProperties) throws Exception { return http.securityMatcher("/admin/**") .csrf(AbstractHttpConfigurer::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource(adminProperties))) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .httpBasic(withDefaults()) .authorizeHttpRequests(authorize -> authorize.anyRequest().hasRole("admin")) diff --git a/src/test/java/uk/ac/ox/ctl/admin/AdminControllerWebTest.java b/src/test/java/uk/ac/ox/ctl/admin/AdminControllerWebTest.java index 19034a9..6642eb7 100644 --- a/src/test/java/uk/ac/ox/ctl/admin/AdminControllerWebTest.java +++ b/src/test/java/uk/ac/ox/ctl/admin/AdminControllerWebTest.java @@ -39,7 +39,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(properties = {"spring.security.user.name=user", "spring.security.user.password=pass1234"}, controllers = AdminController.class, excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "uk\\.ac\\.ox\\.ctl\\.(canvasproxy|ltiauth)\\..*")) -@Import({AdminWebSecurity.class}) +@Import({AdminWebSecurity.class, AdminProperties.class}) @ImportAutoConfiguration(exclude = { OAuth2ClientAutoConfiguration.class }) From 71fa44bb98177855fd84fa9ea7345e606c2b9e22 Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Tue, 6 Jan 2026 09:11:45 +0000 Subject: [PATCH 2/2] AB#72068 We don't need to allow credentials As we're manually managing the Authorization header we don't need to allow credentials through CORS. --- src/main/java/uk/ac/ox/ctl/admin/AdminWebSecurity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/uk/ac/ox/ctl/admin/AdminWebSecurity.java b/src/main/java/uk/ac/ox/ctl/admin/AdminWebSecurity.java index e5df5c8..ff5f611 100644 --- a/src/main/java/uk/ac/ox/ctl/admin/AdminWebSecurity.java +++ b/src/main/java/uk/ac/ox/ctl/admin/AdminWebSecurity.java @@ -67,7 +67,6 @@ public CorsConfigurationSource corsConfigurationSource(AdminProperties adminProp log.info("Admin CORS allowed origins: {}", String.join(", ", adminProperties.getCorsOrigins())); } CorsConfiguration config = new CorsConfiguration(); - config.setAllowCredentials(true); config.addAllowedHeader(CorsConfiguration.ALL); config.addAllowedMethod(CorsConfiguration.ALL); for (String origin : adminProperties.getCorsOrigins()) {