diff --git a/.github/workflows/workflows.yaml b/.github/workflows/workflows.yaml
deleted file mode 100644
index 17d70d6f..00000000
--- a/.github/workflows/workflows.yaml
+++ /dev/null
@@ -1,77 +0,0 @@
-name: Tests
-
-on:
- push:
- workflow_dispatch:
-
-env:
- ITEST_ECS_REGION: dummy-region
- ITEST_ECS_NAME: dummy-name
- ITEST_ECS_SECURITY_GROUPS: dummy-sg
- ITEST_ECS_SUBNETS: dummy-subnets
-
-jobs:
- tests:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- java:
- - 17
- kubernetes:
- - 'v1.24.17'
- - 'v1.25.16'
- - 'v1.26.15'
- - 'v1.27.13'
- - 'v1.28.9'
- - 'v1.29.4'
- - 'v1.30.0'
-
- steps:
- - uses: actions/checkout@v2
- - name: Set up JDK
- uses: actions/setup-java@v1
- with:
- java-version: ${{ matrix.java }}
- - name: Cache Maven packages
- uses: actions/cache@v2
- with:
- path: ~/.m2
- key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- restore-keys: ${{ runner.os }}-m2
- - name: Setup Minikube
- uses: manusa/actions-setup-minikube@v2.7.2
- with:
- minikube version: 'v1.33.0'
- kubernetes version: ${{ matrix.kubernetes }}
- github token: ${{ secrets.GITHUB_TOKEN }}
- container runtime: containerd
- driver: docker
- - name: Setup Docker
- run: sudo apt-get -qq -y install conntrack socat ; nohup socat TCP-LISTEN:2375,reuseaddr,fork UNIX-CONNECT:/var/run/docker.sock &
- - name: Setup Docker Swarm
- run: docker swarm init
- - name: Pull Image
- run: docker pull openanalytics/shinyproxy-integration-test-app
- - name: Run redis
- run: docker run -d -p 6379:6379 redis
- - name: Build with Maven
- run: mvn -B -U clean install -DskipTests
- - name: Run Tests
- run: mvn -B test
-
- dependency:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v2
- - name: Set up JDK
- uses: actions/setup-java@v1
- with:
- java-version: 17
- - name: Run Dependency Check
- run: mvn -B -Powasp-dependency-check verify -DskipTests
- - name: Archive code coverage results
- uses: actions/upload-artifact@v2
- with:
- name: dependency-check-report
- path: target/dependency-check-report.html
diff --git a/.gitignore b/.gitignore
index f66ec70e..6f1559f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ logs
.project
.classpath
.settings
+.vscode
diff --git a/src/main/java/eu/openanalytics/containerproxy/auth/AuthenticationBackendFactory.java b/src/main/java/eu/openanalytics/containerproxy/auth/AuthenticationBackendFactory.java
index 6c1f5111..c159d46a 100644
--- a/src/main/java/eu/openanalytics/containerproxy/auth/AuthenticationBackendFactory.java
+++ b/src/main/java/eu/openanalytics/containerproxy/auth/AuthenticationBackendFactory.java
@@ -26,6 +26,7 @@
import eu.openanalytics.containerproxy.auth.impl.SAMLAuthenticationBackend;
import eu.openanalytics.containerproxy.auth.impl.SimpleAuthenticationBackend;
import eu.openanalytics.containerproxy.auth.impl.WebServiceAuthenticationBackend;
+import eu.openanalytics.containerproxy.auth.impl.CustomHeaderAuthenticationBackend;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.ApplicationContext;
@@ -72,6 +73,7 @@ protected IAuthenticationBackend createInstance() {
case LDAPAuthenticationBackend.NAME -> backend = new LDAPAuthenticationBackend();
case OpenIDAuthenticationBackend.NAME -> backend = new OpenIDAuthenticationBackend();
case WebServiceAuthenticationBackend.NAME -> backend = new WebServiceAuthenticationBackend(environment);
+ case CustomHeaderAuthenticationBackend.NAME -> backend = new CustomHeaderAuthenticationBackend(environment);
case SAMLAuthenticationBackend.NAME -> {
return samlBackend;
}
diff --git a/src/main/java/eu/openanalytics/containerproxy/auth/impl/CustomHeaderAuthenticationBackend.java b/src/main/java/eu/openanalytics/containerproxy/auth/impl/CustomHeaderAuthenticationBackend.java
new file mode 100644
index 00000000..ce724a4f
--- /dev/null
+++ b/src/main/java/eu/openanalytics/containerproxy/auth/impl/CustomHeaderAuthenticationBackend.java
@@ -0,0 +1,92 @@
+/**
+ * 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
+ */
+package eu.openanalytics.containerproxy.auth.impl;
+
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.AuthorizedUrl;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.stereotype.Component;
+
+import eu.openanalytics.containerproxy.auth.IAuthenticationBackend;
+import eu.openanalytics.containerproxy.auth.impl.customHeader.CustomHeaderAuthenticationFilter;
+import eu.openanalytics.containerproxy.auth.impl.customHeader.CustomHeaderAuthenticationToken;
+
+@Component
+public class CustomHeaderAuthenticationBackend implements IAuthenticationBackend{
+
+ public final static String NAME = "customHeader";
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public boolean hasAuthorization() {
+ return false;
+ }
+
+ @Override
+ public void configureHttpSecurity(HttpSecurity http) throws Exception {
+ http.formLogin().disable();
+
+ http.addFilterBefore(new CustomHeaderAuthenticationFilter(), BasicAuthenticationFilter.class);
+ }
+
+ @Override
+ public void configureAuthenticationManagerBuilder(AuthenticationManagerBuilder auth) throws Exception {
+ // Configure a custom Authentication Provider
+ CustomHeaderAuthenticationProvider authenticationProvider = new CustomHeaderAuthenticationProvider();
+
+ auth.authenticationProvider(authenticationProvider);
+ }
+
+
+ public class CustomHeaderAuthenticationProvider implements
+ org.springframework.security.authentication.AuthenticationProvider,
+ org.springframework.beans.factory.InitializingBean {
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ CustomHeaderAuthenticationToken token = (CustomHeaderAuthenticationToken) authentication;
+ if (token.isValid())
+ return new CustomHeaderAuthenticationToken(token.getPrincipal().toString());
+
+ throw new BadCredentialsException("Invalid username");
+
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return false;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+
+ }
+ }
+
+}
diff --git a/src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderAuthenticationFilter.java b/src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderAuthenticationFilter.java
new file mode 100644
index 00000000..15fe646b
--- /dev/null
+++ b/src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderAuthenticationFilter.java
@@ -0,0 +1,72 @@
+/**
+ * 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
+ */
+
+package eu.openanalytics.containerproxy.auth.impl.customHeader;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.springframework.context.annotation.Lazy;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+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.OncePerRequestFilter;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class CustomHeaderAuthenticationFilter extends OncePerRequestFilter {
+
+ private final Logger log = LogManager.getLogger(CustomHeaderAuthenticationFilter.class);
+
+ private final RequestMatcher requestMatcher = new OrRequestMatcher(
+ new AntPathRequestMatcher("/app/**"),
+ new AntPathRequestMatcher("/app_i/**"),
+ new AntPathRequestMatcher("/**"));
+
+ @Override
+ protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response,
+ @Nonnull FilterChain chain) throws ServletException, IOException {
+
+ log.debug(String.format("CustomHeaderAuthenticationFilter CALLED"));
+ if (requestMatcher.matches(request)) {
+ String remoteUser = request.getHeader("REMOTE_USER");
+ log.debug(String.format("CustomHeaderAuthenticationFilter REMOTE_USER: %s", remoteUser));
+ try {
+ Authentication authRequest = new CustomHeaderAuthenticationToken(remoteUser);
+ SecurityContextHolder.getContext().setAuthentication(authRequest);
+ } catch (AuthenticationException e) {
+ throw e;
+ }
+
+ }
+ chain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderAuthenticationToken.java b/src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderAuthenticationToken.java
new file mode 100644
index 00000000..0b843a46
--- /dev/null
+++ b/src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderAuthenticationToken.java
@@ -0,0 +1,57 @@
+/**
+ * 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
+ */
+package eu.openanalytics.containerproxy.auth.impl.customHeader;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+public class CustomHeaderAuthenticationToken extends AbstractAuthenticationToken{
+
+ private final String remoteUser;
+
+ public CustomHeaderAuthenticationToken(String userName) {
+ super(null);
+ this.remoteUser = userName;
+ }
+
+ public boolean isValid() {
+ if (remoteUser != null && !remoteUser.isEmpty())
+ return true;
+ return false;
+ }
+
+
+ @Override
+ public Object getPrincipal() {
+ return remoteUser;
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return this.remoteUser;
+ }
+
+}
+
diff --git a/src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderConfiguration.java b/src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderConfiguration.java
new file mode 100644
index 00000000..ea9217b9
--- /dev/null
+++ b/src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderConfiguration.java
@@ -0,0 +1,41 @@
+/**
+ * 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
+ */
+package eu.openanalytics.containerproxy.auth.impl.customHeader;
+
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnProperty(name = "proxy.authentication", havingValue = "customHeader")
+public class CustomHeaderConfiguration {
+
+ public static final String REG_ID = "shinyproxy";
+ public static final String PROP_CUSTOM_HEADER = "proxy.customHeader";
+
+
+ @Bean
+ public CustomHeaderAuthenticationFilter customHeaderAuthorizeFilter() {
+ return new CustomHeaderAuthenticationFilter();
+ }
+
+}
diff --git a/src/test/resources/application-test-customHeader-auth.yml b/src/test/resources/application-test-customHeader-auth.yml
new file mode 100644
index 00000000..d1c02c73
--- /dev/null
+++ b/src/test/resources/application-test-customHeader-auth.yml
@@ -0,0 +1,18 @@
+spring:
+ session:
+ store-type: none
+ data:
+ redis:
+ repositories:
+ enabled: false
+proxy:
+ authentication: customHeader
+ customHeader: REMOTE_USER
+ specs:
+ - id: 01_hello
+ container-specs:
+ - image: "openanalytics/shinyproxy-demo"
+ cmd: ["R", "-e", "shinyproxy::run_01_hello()"]
+ port-mapping:
+ - name: default
+ port: 3838