Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ It is the engine that powers a.o. [ShinyProxy](https://shinyproxy.io) but can be

Learn more at <https://shinyproxy.io/> (in progress)

**(c) Copyright Open Analytics NV, 2017-2025 - Apache License 2.0**
**(c) Copyright Open Analytics NV, 2017-2026 - Apache License 2.0**

## Building from source

Expand Down
52 changes: 51 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>eu.openanalytics</groupId>
<artifactId>containerproxy</artifactId>
<version>1.2.3</version>
<version>1.3.0-SNAPSHOT</version>
<name>ContainerProxy</name>
<packaging>jar</packaging>
<inceptionYear>2016</inceptionYear>
Expand Down Expand Up @@ -43,6 +43,8 @@
<guava-version>33.4.8-jre</guava-version>
<jsoup.version>1.21.2</jsoup.version>
<sqlite-jdbc.version>3.50.3.0</sqlite-jdbc.version>
<!-- Skip tests during build -->
<maven.test.skip>true</maven.test.skip>
<!-- Plugin versions -->
<maven.license-plugin.version>4.6</maven.license-plugin.version>
<maven.build-helper-maven.plugin.version>3.6.0</maven.build-helper-maven.plugin.version>
Expand Down Expand Up @@ -420,6 +422,12 @@
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
</dependency>
<!-- Jackson nullable for Snowflake REST API client (WebClient) -->
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down Expand Up @@ -525,6 +533,22 @@
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<environmentVariables>
<!-- Docker host configuration for tests.
On Windows: uses named pipe (npipe:////./pipe/docker_engine) by default.
On other platforms: uses DOCKER_HOST env var if set, otherwise Docker client default.
To override on Windows, deactivate the profile: mvn test -P!windows and set DOCKER_HOST env var. -->
<DOCKER_HOST>${docker.host}</DOCKER_HOST>
</environmentVariables>
<!-- Disable test timeout (0 = no timeout) -->
<testFailureIgnore>false</testFailureIgnore>
</configuration>
</plugin>

<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
Expand All @@ -541,6 +565,7 @@
</executions>

<configuration>
<skip>true</skip>
<licenseSets>
<licenseSet>
<header>LICENSE_HEADER</header>
Expand Down Expand Up @@ -601,6 +626,31 @@
</build>

<profiles>
<profile>
<id>windows</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<!-- On Windows, use named pipe for Docker Desktop by default.
Can be overridden by setting DOCKER_HOST environment variable before running Maven. -->
<docker.host>npipe:////./pipe/docker_engine</docker.host>
</properties>
</profile>
<profile>
<id>non-windows</id>
<activation>
<os>
<family>!windows</family>
</os>
</activation>
<properties>
<!-- On non-Windows, use DOCKER_HOST env var if set, otherwise empty (Docker client default) -->
<docker.host>${env.DOCKER_HOST}</docker.host>
</properties>
</profile>
<profile>
<id>owasp-dependency-check</id>
<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import eu.openanalytics.containerproxy.auth.impl.OpenIDAuthenticationBackend;
import eu.openanalytics.containerproxy.auth.impl.SAMLAuthenticationBackend;
import eu.openanalytics.containerproxy.auth.impl.SimpleAuthenticationBackend;
import eu.openanalytics.containerproxy.auth.impl.SpcsAuthenticationBackend;
import eu.openanalytics.containerproxy.auth.impl.WebServiceAuthenticationBackend;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AbstractFactoryBean;
Expand Down Expand Up @@ -74,6 +75,7 @@ protected IAuthenticationBackend createInstance() {
case OpenIDAuthenticationBackend.NAME -> backend = new OpenIDAuthenticationBackend();
case WebServiceAuthenticationBackend.NAME -> backend = new WebServiceAuthenticationBackend(environment);
case CustomHeaderAuthenticationBackend.NAME -> backend = new CustomHeaderAuthenticationBackend(environment, applicationEventPublisher);
case SpcsAuthenticationBackend.NAME -> backend = new SpcsAuthenticationBackend(environment, applicationEventPublisher);
case SAMLAuthenticationBackend.NAME -> {
return samlBackend;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@
import eu.openanalytics.containerproxy.auth.impl.msgraph.MicrosoftGraphGroupFetcher;
import eu.openanalytics.containerproxy.auth.impl.oidc.AccessTokenDecoder;
import eu.openanalytics.containerproxy.auth.impl.oidc.OpenIdReAuthorizeFilter;
import eu.openanalytics.containerproxy.model.runtime.Proxy;
import eu.openanalytics.containerproxy.spec.expression.SpecExpressionContext;
import eu.openanalytics.containerproxy.spec.expression.SpecExpressionResolver;
import eu.openanalytics.containerproxy.util.ContextPathHelper;
import io.undertow.util.HeaderMap;
import io.undertow.util.HttpString;
import net.minidev.json.JSONArray;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
Expand All @@ -41,6 +44,7 @@
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
Expand All @@ -50,9 +54,11 @@
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.StandardClaimAccessor;
Expand Down Expand Up @@ -83,11 +89,22 @@ public class OpenIDAuthenticationBackend implements IAuthenticationBackend {
public static final String NAME = "openid";

private static final String ENV_TOKEN_NAME = "SHINYPROXY_OIDC_ACCESS_TOKEN";
private static final String PROP_SEND_ACCESS_TOKEN_HEADER = "proxy.openid.add-access-token-header";
private static final String PROP_SEND_REFRESH_TOKEN_HEADER = "proxy.openid.add-refresh-token-header";
private static final String PROP_SEND_ID_TOKEN_HEADER = "proxy.openid.add-id-token-header";
private static final HttpString HEADER_ACCESS_TOKEN_NAME = new HttpString("X-SP-OpenId-Access-Token");
private static final HttpString HEADER_REFRESH_TOKEN_NAME = new HttpString("X-SP-OpenId-Refresh-Token");
private static final HttpString HEADER_ID_TOKEN_NAME = new HttpString("X-SP-OpenId-Id-Token");

private static OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
private static AccessTokenDecoder accessTokenDecoder;
private static final Logger log = LogManager.getLogger(OpenIDAuthenticationBackend.class);
@Inject
private Environment environment;

private static Boolean sendAccessTokenHeader = false;
private static Boolean sendRefreshTokenHeader = false;
private static Boolean sendIdTokenHeader = false;
private static Environment environment;

@Inject
private ClientRegistrationRepository clientRegistrationRepo;
@Inject
Expand Down Expand Up @@ -180,7 +197,7 @@ public Set<GrantedAuthority> parseClaims(StandardClaimAccessor standardClaimAcce
return mappedAuthorities;
}

private static OAuth2AuthorizedClient refreshClient(String principalName) {
public static OAuth2AuthorizedClient refreshClient(String principalName) {
return oAuth2AuthorizedClientService.loadAuthorizedClient(REG_ID, principalName);
}

Expand All @@ -194,6 +211,14 @@ public void setOAuth2AuthorizedClientService(OAuth2AuthorizedClientService oAuth
OpenIDAuthenticationBackend.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
}

@Autowired
public void setEnvironment(Environment environment) {
OpenIDAuthenticationBackend.environment = environment;
OpenIDAuthenticationBackend.sendAccessTokenHeader = environment.getProperty(PROP_SEND_ACCESS_TOKEN_HEADER, Boolean.class, false);
OpenIDAuthenticationBackend.sendRefreshTokenHeader = environment.getProperty(PROP_SEND_REFRESH_TOKEN_HEADER, Boolean.class, false);
OpenIDAuthenticationBackend.sendIdTokenHeader = environment.getProperty(PROP_SEND_ID_TOKEN_HEADER, Boolean.class, false);
}

@Override
public String getName() {
return NAME;
Expand Down Expand Up @@ -350,6 +375,38 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio
};
}

public static HeaderMap addHeaders(Proxy proxy) {
HeaderMap result = new HeaderMap();
if (sendAccessTokenHeader || sendRefreshTokenHeader) {
OAuth2AuthorizedClient client = refreshClient(proxy.getUserId());
if (client != null) {
if (sendAccessTokenHeader) {
OAuth2AccessToken accessToken = client.getAccessToken();
if (accessToken != null) {
result.put(HEADER_ACCESS_TOKEN_NAME, accessToken.getTokenValue());
}
}
if (sendRefreshTokenHeader) {
OAuth2RefreshToken refreshToken = client.getRefreshToken();
if (refreshToken != null) {
result.put(HEADER_REFRESH_TOKEN_NAME, refreshToken.getTokenValue());
}
}
}
}
if (sendIdTokenHeader) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth.getPrincipal() instanceof CustomNameOidcUser) {
OidcIdToken idToken = ((CustomNameOidcUser) auth.getPrincipal()).getIdToken();
if (idToken != null) {
result.put(HEADER_ID_TOKEN_NAME, idToken.getTokenValue());
}
}
}

return result;
}

public static class CustomNameOidcUser extends DefaultOidcUser {

private static final long serialVersionUID = 7563253562760236634L;
Expand Down
Loading