Skip to content

Commit

Permalink
Merge branch 'release/1.1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
LEDfan committed Jun 20, 2024
2 parents 67477c5 + 78160ff commit c85509d
Show file tree
Hide file tree
Showing 24 changed files with 374 additions and 46 deletions.
22 changes: 11 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

<groupId>eu.openanalytics</groupId>
<artifactId>containerproxy</artifactId>
<version>1.1.0</version>
<version>1.1.1</version>
<name>ContainerProxy</name>
<packaging>jar</packaging>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<version>3.2.6</version>
<relativePath/>
</parent>

Expand All @@ -21,7 +21,7 @@
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring-boot.version>3.2.2</spring-boot.version>
<spring-boot.version>3.2.6</spring-boot.version>
<aws.java.sdk.version>2.23.12</aws.java.sdk.version>
</properties>

Expand Down Expand Up @@ -178,12 +178,17 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
Expand Down Expand Up @@ -262,7 +267,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
<version>42.7.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
Expand Down Expand Up @@ -352,11 +357,6 @@
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>2.3.0</version>
</dependency>

<dependency>
<groupId>jakarta.mail</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
package eu.openanalytics.containerproxy;

import com.fasterxml.jackson.datatype.jsr353.JSR353Module;
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.ProxySharingDispatcher;
import eu.openanalytics.containerproxy.model.spec.ProxySpec;
import eu.openanalytics.containerproxy.service.hearbeat.ActiveProxiesService;
import eu.openanalytics.containerproxy.service.hearbeat.HeartbeatService;
import eu.openanalytics.containerproxy.service.hearbeat.IHeartbeatProcessor;
import eu.openanalytics.containerproxy.spec.IProxySpecProvider;
import eu.openanalytics.containerproxy.util.LoggingConfigurer;
import eu.openanalytics.containerproxy.util.ProxyMappingManager;
import io.undertow.Handlers;
Expand Down Expand Up @@ -106,13 +109,19 @@ public class ContainerProxyApplication {
private DefaultCookieSerializer defaultCookieSerializer;
@Autowired(required = false)
private SessionManagerFactory sessionManagerFactory;
@Inject
private IProxySpecProvider proxySpecProvider;

public static void main(String[] args) {
SpringApplication app = new SpringApplication(ContainerProxyApplication.class);

app.addListeners(new LoggingConfigurer());

boolean hasExternalConfig = Files.exists(Paths.get(CONFIG_FILENAME));
boolean hasExternalConfig = Files.exists(Paths.get(CONFIG_FILENAME))
|| System.getProperty("spring.config.location") != null
|| System.getenv("SPRING_CONFIG_LOCATION") != null
|| Arrays.asList(args).contains("--spring.config.location");

if (!hasExternalConfig) {
app.setAdditionalProfiles(CONFIG_DEMO_PROFILE);
Logger log = LogManager.getLogger(ContainerProxyApplication.class);
Expand Down Expand Up @@ -243,6 +252,14 @@ public void init() {
log.warn("WARNING: Invalid configuration detected: user sessions are stored in Redis and App Recovery is enabled. Instead of using App Recovery, change store-mode so that app sessions are stored in Redis!");
}
}
if (environment.getProperty(PROPERTY_RECOVER_RUNNING_PROXIES, Boolean.class, false) ||
environment.getProperty(PROPERTY_RECOVER_RUNNING_PROXIES_FROM_DIFFERENT_CONFIG, Boolean.class, false)) {
for (ProxySpec proxySpec : proxySpecProvider.getSpecs()) {
if (ProxySharingDispatcher.supportSpec(proxySpec)) {
throw new IllegalStateException("Cannot use App Recovery together with container pre-initialization or sharing");
}
}
}

boolean hideSpecDetails = environment.getProperty(PROP_API_SECURITY_HIDE_SPEC_DETAILS, Boolean.class, true);
if (!hideSpecDetails) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,6 @@ public RedisTemplate<String, OAuth2AuthorizedClient> oAuth2AuthorizedClientRedis
return template;
}

@Bean
public RedisTemplate<String, String> unClaimSeatIdsTemplate(RedisConnectionFactory connectionFactory) {
return createRedisTemplate(connectionFactory, String.class);
}

@Bean
public RedisTemplate<String, Seat> seatsTemplate(RedisConnectionFactory connectionFactory) {
return createRedisTemplate(connectionFactory, Seat.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import eu.openanalytics.containerproxy.service.AsyncProxyService;
import eu.openanalytics.containerproxy.service.InvalidParametersException;
import eu.openanalytics.containerproxy.service.ProxyService;
import eu.openanalytics.containerproxy.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
Expand Down Expand Up @@ -66,6 +67,8 @@ public class ProxyStatusController {
private ProxyService proxyService;
@Inject
private AsyncProxyService asyncProxyService;
@Inject
private UserService userService;

@Operation(
summary = "Change the status of a proxy.", tags = "ContainerProxy",
Expand All @@ -74,7 +77,7 @@ public class ProxyStatusController {
mediaType = "application/json",
schema = @Schema(implementation = ChangeProxyStatusDto.class),
examples = {
@ExampleObject(name = "Stopping", description = "Stop a proxy.", value = "{\"status\": \"Stopping\"}"),
@ExampleObject(name = "Stopping", description = "Stop a proxy (allowed by admins).", value = "{\"status\": \"Stopping\"}"),
@ExampleObject(name = "Pausing", description = "Pause a proxy.", value = "{\"status\": \"Pausing\"}"),
@ExampleObject(name = "Resuming", description = "Resume a proxy.", value = "{\"status\": \"Resuming\"}"),
@ExampleObject(name = "Resuming with parameters", description = "Resume a proxy.", value = "{\"status\": \"Resuming\", \"parameters\":{\"resources\":\"2 CPU cores - 8G RAM\"," +
Expand Down Expand Up @@ -115,10 +118,14 @@ public class ProxyStatusController {
@ResponseBody
@RequestMapping(value = "/api/proxy/{proxyId}/status", method = RequestMethod.PUT)
public ResponseEntity<ApiResponse<Void>> changeProxyStatus(@PathVariable String proxyId, @RequestBody ChangeProxyStatusDto changeProxyStateDto) {
Proxy proxy = proxyService.getUserProxy(proxyId);
Proxy proxy = proxyService.getProxy(proxyId);
if (proxy == null) {
return ApiResponse.failForbidden();
}
if (!userService.isOwner(proxy) && !(changeProxyStateDto.getStatus().equals("Stopping") && userService.isAdmin())) {
// admin is allowed to stop app
return ApiResponse.failForbidden();
}

try {
switch (changeProxyStateDto.getStatus()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import eu.openanalytics.containerproxy.auth.IAuthenticationBackend;
import eu.openanalytics.containerproxy.auth.impl.saml.AuthenticationFailureHandler;
import eu.openanalytics.containerproxy.auth.impl.saml.DisableSaml2LogoutRequestFilterFilter;
import eu.openanalytics.containerproxy.auth.impl.saml.Saml2MetadataFilter;
import eu.openanalytics.containerproxy.util.ContextPathHelper;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -40,13 +41,15 @@
import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
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.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.CorsFilter;

import javax.inject.Inject;

Expand Down Expand Up @@ -117,11 +120,12 @@ public void configureHttpSecurity(HttpSecurity http) throws Exception {
.authenticationManager(new ProviderManager(samlAuthenticationProvider))
.failureHandler(failureHandler)
.successHandler(successHandler))
.saml2Logout(saml -> saml
.logoutUrl(SAML_LOGOUT_SERVICE_LOCATION_PATH)
.logoutResponse(r -> r.logoutUrl(SAML_LOGOUT_SERVICE_RESPONSE_LOCATION_PATH))
.logoutRequest(r -> r.logoutRequestResolver(saml2LogoutRequestResolver))
.addObjectPostProcessor(
.saml2Logout(saml -> {
saml.logoutUrl(SAML_LOGOUT_SERVICE_LOCATION_PATH)
.logoutResponse(r -> r.logoutUrl(SAML_LOGOUT_SERVICE_RESPONSE_LOCATION_PATH))
.logoutRequest(r -> r.logoutRequestResolver(saml2LogoutRequestResolver));

saml.addObjectPostProcessor(
new ObjectPostProcessor<LogoutFilter>() {
@Override
public <O extends LogoutFilter> O postProcess(O object) {
Expand All @@ -132,8 +136,21 @@ public <O extends LogoutFilter> O postProcess(O object) {
return object;
}
}
))
.addFilterBefore(metadataFilter, Saml2WebSsoAuthenticationFilter.class);
);

saml.addObjectPostProcessor(
new ObjectPostProcessor<Saml2LogoutRequestFilter>() {
@Override
public <O extends Saml2LogoutRequestFilter> O postProcess(O object) {
// override the name of the filter, so it can be used in DisableSaml2LogoutRequestFilterFilter
// See #33066.
object.setBeanName("Saml2LogoutRequestFilter");
return object;
}
});
})
.addFilterBefore(metadataFilter, Saml2WebSsoAuthenticationFilter.class)
.addFilterAfter(new DisableSaml2LogoutRequestFilterFilter(), CorsFilter.class);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;

Expand Down Expand Up @@ -88,6 +89,7 @@ public ClientRegistrationRepository clientRegistrationRepository() {
.clientId(environment.getProperty("proxy.openid.client-id"))
.clientSecret(environment.getProperty("proxy.openid.client-secret"))
.userInfoUri(environment.getProperty("proxy.openid.userinfo-url"))
.clientAuthenticationMethod(environment.getProperty("proxy.openid.client-authentication-method", ClientAuthenticationMethod.class))
.build();

return new InMemoryClientRegistrationRepository(Collections.singletonList(client));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* ContainerProxy
*
* Copyright (C) 2016-2024 Open Analytics
*
* ===========================================================================
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Apache License as published by
* The Apache Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Apache License for more details.
*
* You should have received a copy of the Apache License
* along with this program. If not, see <http://www.apache.org/licenses/>
*/
package eu.openanalytics.containerproxy.auth.impl.saml;

import eu.openanalytics.containerproxy.auth.impl.SAMLAuthenticationBackend;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.GenericFilterBean;

import java.io.IOException;

/**
* Filter that disables the {@link Saml2LogoutRequestFilter} filter, except for the SAML single logout endpoint.
* The SAML filter calls getParameter and thefore consumes the POST body.
* The name of {@link Saml2LogoutRequestFilter must be fixed in order for this to work (see {@link SAMLAuthenticationBackend}
* See #33066.
*/
public class DisableSaml2LogoutRequestFilterFilter extends GenericFilterBean {

private static final RequestMatcher REQUEST_MATCHER = new OrRequestMatcher(
new AntPathRequestMatcher("/logout/saml2/slo"),
new AntPathRequestMatcher("/logout/saml2/slo/*")
);

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!REQUEST_MATCHER.matches((HttpServletRequest) request)) {
// set the filtered as already being executed, this is the only way to disable the filter
request.setAttribute("Saml2LogoutRequestFilter.FILTERED", true);
}
chain.doFilter(request, response);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
Expand All @@ -42,6 +45,7 @@ public class ProxyDispatcherService {
private final IProxySharingStoreFactory storeFactory;
private final ConfigurableListableBeanFactory beanFactory;
private final DefaultProxyDispatcher defaultProxyDispatcher;
private final List<AutoCloseable> closeables = new ArrayList<>();

public ProxyDispatcherService(IProxySpecProvider proxySpecProvider,
IProxySharingStoreFactory storeFactory,
Expand All @@ -62,6 +66,7 @@ public void init() {

ProxySharingScaler proxySharingScaler = createProxySharingScaler(seatStore, proxySpec, delegateProxyStore);
createBean(proxySharingScaler, "proxySharingScaler_" + proxySpec.getId());
closeables.add(proxySharingScaler);

ProxySharingDispatcher proxySharingDispatcher = new ProxySharingDispatcher(proxySpec, delegateProxyStore, seatStore);
createBean(proxySharingDispatcher, "proxySharingDispatcher_" + proxySpec.getId());
Expand All @@ -87,4 +92,12 @@ private <T> void createBean(T bean, String beanName) {
beanFactory.registerSingleton(beanName, initializedBean);
}

@PreDestroy
public void shutdown() throws Exception {
// when using beanFactory.registerSingleton, @PreDestroy is not called
for (AutoCloseable closeable : closeables) {
closeable.close();
}
}

}
Loading

0 comments on commit c85509d

Please sign in to comment.