From 000a505eb572ab5f9384d1dea9a9b94dbbd92a00 Mon Sep 17 00:00:00 2001
From: Daniel Gerhardt <code@dgerhardt.net>
Date: Sat, 28 Sep 2019 18:52:12 +0200
Subject: [PATCH] Make auth callback handling more consistent

Callback URIs for SSO authentication have been adjusted to use a common
pattern: `/auth/callback/*`. The Pac4j client name is now based on the
provider id an is part of the path instead of a query parameter.
---
 .../de/thm/arsnova/config/SecurityConfig.java | 46 ++++++++++++-------
 .../v2/AuthenticationController.java          |  2 +-
 .../security/pac4j/OauthCallbackFilter.java   |  7 ++-
 3 files changed, 34 insertions(+), 21 deletions(-)

diff --git a/src/main/java/de/thm/arsnova/config/SecurityConfig.java b/src/main/java/de/thm/arsnova/config/SecurityConfig.java
index 16842ace1..6f43c5b03 100644
--- a/src/main/java/de/thm/arsnova/config/SecurityConfig.java
+++ b/src/main/java/de/thm/arsnova/config/SecurityConfig.java
@@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletResponse;
 import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
 import org.pac4j.core.client.Client;
 import org.pac4j.core.config.Config;
+import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver;
 import org.pac4j.oauth.client.FacebookClient;
 import org.pac4j.oauth.client.TwitterClient;
 import org.pac4j.oidc.client.GoogleOidcClient;
@@ -109,10 +110,10 @@ import de.thm.arsnova.security.pac4j.OauthCallbackFilter;
 		SecurityProperties.class})
 @Profile("!test")
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
-	public static final String OAUTH_CALLBACK_PATH_SUFFIX = "/auth/oauth_callback";
-	private static final String OIDC_DISCOVERY_PATH_SUFFIX = "/.well-known/openid-configuration";
-	public static final String CAS_LOGIN_PATH_SUFFIX = "/auth/login/cas";
-	public static final String CAS_LOGOUT_PATH_SUFFIX = "/auth/logout/cas";
+	public static final String AUTH_CALLBACK_PATH = "/auth/callback";
+	public static final String OAUTH_CALLBACK_PATH = AUTH_CALLBACK_PATH + "/oauth";
+	public static final String CAS_CALLBACK_PATH = OAUTH_CALLBACK_PATH + "/cas";
+	public static final String CAS_LOGOUT_PATH = "/auth/logout/cas";
 	public static final String RUN_AS_KEY_PREFIX = "RUN_AS_KEY";
 	public static final String INTERNAL_PROVIDER_ID = "user-db";
 	public static final String LDAP_PROVIDER_ID = "ldap";
@@ -121,6 +122,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 	public static final String GOOGLE_PROVIDER_ID = "google";
 	public static final String TWITTER_PROVIDER_ID = "twitter";
 	public static final String FACEBOOK_PROVIDER_ID = "facebook";
+	private static final String OIDC_DISCOVERY_PATH = "/.well-known/openid-configuration";
 	private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
 
 	private ServletContext servletContext;
@@ -207,7 +209,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 		@Override
 		protected void configure(final HttpSecurity http) throws Exception {
 			super.configure(http);
-			http.antMatcher("/v2/**");
+			http.requestMatchers().antMatchers(AUTH_CALLBACK_PATH + "/**", "/v2/**");
 			http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
 		}
 	}
@@ -485,7 +487,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 			havingValue = "true")
 	public ServiceProperties casServiceProperties() {
 		final ServiceProperties properties = new ServiceProperties();
-		properties.setService(rootUrl + apiPath + CAS_LOGIN_PATH_SUFFIX);
+		properties.setService(rootUrl + apiPath + CAS_CALLBACK_PATH);
 		properties.setSendRenew(false);
 
 		return properties;
@@ -522,7 +524,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 		final CasAuthenticationFilter filter = new CasAuthenticationFilter();
 		filter.setAuthenticationManager(authenticationManager());
 		filter.setServiceProperties(casServiceProperties());
-		filter.setFilterProcessesUrl("/**" + CAS_LOGIN_PATH_SUFFIX);
+		filter.setFilterProcessesUrl("/**" + CAS_CALLBACK_PATH);
 		filter.setAuthenticationSuccessHandler(successHandler());
 		filter.setAuthenticationFailureHandler(failureHandler());
 
@@ -536,7 +538,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 			havingValue = "true")
 	public LogoutFilter casLogoutFilter() {
 		final LogoutFilter filter = new LogoutFilter(casLogoutSuccessHandler(), logoutHandler());
-		filter.setLogoutRequestMatcher(new AntPathRequestMatcher("/**" + CAS_LOGOUT_PATH_SUFFIX));
+		filter.setLogoutRequestMatcher(new AntPathRequestMatcher("/**" + CAS_LOGOUT_PATH));
 
 		return filter;
 	}
@@ -575,14 +577,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 			clients.add(twitterClient());
 		}
 
-		return new Config(rootUrl + apiPath + OAUTH_CALLBACK_PATH_SUFFIX, clients);
+		return new Config(rootUrl + apiPath + OAUTH_CALLBACK_PATH, clients);
 	}
 
 	@Bean
 	public OauthCallbackFilter oauthCallbackFilter() throws Exception {
-		final OauthCallbackFilter callbackFilter = new OauthCallbackFilter(oauthConfig());
+		final OauthCallbackFilter callbackFilter = new OauthCallbackFilter(oauthConfig(), OAUTH_CALLBACK_PATH);
 		callbackFilter.setAuthenticationManager(authenticationManager());
-		callbackFilter.setFilterProcessesUrl("/**" + OAUTH_CALLBACK_PATH_SUFFIX);
 
 		return callbackFilter;
 	}
@@ -592,6 +593,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 		return new OauthAuthenticationProvider();
 	}
 
+	@Bean
+	public PathParameterCallbackUrlResolver pathParameterCallbackUrlResolver() {
+		return new PathParameterCallbackUrlResolver();
+	}
+
 	@Bean
 	@ConditionalOnProperty(
 			name = "oidc[0].enabled",
@@ -600,12 +606,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 	public OidcClient oidcClient() {
 		final AuthenticationProviderProperties.Oidc oidcProperties = providerProperties.getOidc().get(0);
 		final OidcConfiguration config = new OidcConfiguration();
-		config.setDiscoveryURI(oidcProperties.getIssuer() + OIDC_DISCOVERY_PATH_SUFFIX);
+		config.setDiscoveryURI(oidcProperties.getIssuer() + OIDC_DISCOVERY_PATH);
 		config.setClientId(oidcProperties.getClientId());
 		config.setSecret(oidcProperties.getSecret());
 		config.setScope("openid");
 		final OidcClient client = new OidcClient(config);
-		client.setCallbackUrl(rootUrl + apiPath + OAUTH_CALLBACK_PATH_SUFFIX + "?client_name=OidcClient");
+		client.setName(OIDC_PROVIDER_ID);
+		client.setCallbackUrl(rootUrl + apiPath + OAUTH_CALLBACK_PATH);
+		client.setCallbackUrlResolver(pathParameterCallbackUrlResolver());
 
 		return client;
 	}
@@ -619,7 +627,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 		final AuthenticationProviderProperties.Oauth oauthProperties =
 				providerProperties.getOauth().get(FACEBOOK_PROVIDER_ID);
 		final FacebookClient client = new FacebookClient(oauthProperties.getKey(), oauthProperties.getSecret());
-		client.setCallbackUrl(rootUrl + apiPath + OAUTH_CALLBACK_PATH_SUFFIX + "?client_name=FacebookClient");
+		client.setName(FACEBOOK_PROVIDER_ID);
+		client.setCallbackUrl(rootUrl + apiPath + OAUTH_CALLBACK_PATH);
+		client.setCallbackUrlResolver(pathParameterCallbackUrlResolver());
 
 		return client;
 	}
@@ -633,7 +643,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 		final AuthenticationProviderProperties.Oauth oauthProperties =
 				providerProperties.getOauth().get(TWITTER_PROVIDER_ID);
 		final TwitterClient client = new TwitterClient(oauthProperties.getKey(), oauthProperties.getSecret());
-		client.setCallbackUrl(rootUrl + apiPath + OAUTH_CALLBACK_PATH_SUFFIX + "?client_name=TwitterClient");
+		client.setName(TWITTER_PROVIDER_ID);
+		client.setCallbackUrl(rootUrl + apiPath + OAUTH_CALLBACK_PATH);
+		client.setCallbackUrlResolver(pathParameterCallbackUrlResolver());
 
 		return client;
 	}
@@ -651,7 +663,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 		config.setSecret(oauthProperties.getSecret());
 		config.setScope("openid email");
 		final GoogleOidcClient client = new GoogleOidcClient(config);
-		client.setCallbackUrl(rootUrl + apiPath + OAUTH_CALLBACK_PATH_SUFFIX + "?client_name=GoogleOidcClient");
+		client.setName(GOOGLE_PROVIDER_ID);
+		client.setCallbackUrl(rootUrl + apiPath + OAUTH_CALLBACK_PATH);
+		client.setCallbackUrlResolver(pathParameterCallbackUrlResolver());
 
 		return client;
 	}
diff --git a/src/main/java/de/thm/arsnova/controller/v2/AuthenticationController.java b/src/main/java/de/thm/arsnova/controller/v2/AuthenticationController.java
index 265c42bd5..9f2782e52 100644
--- a/src/main/java/de/thm/arsnova/controller/v2/AuthenticationController.java
+++ b/src/main/java/de/thm/arsnova/controller/v2/AuthenticationController.java
@@ -250,7 +250,7 @@ public class AuthenticationController extends AbstractController {
 		request.getSession().invalidate();
 		SecurityContextHolder.clearContext();
 		if (auth instanceof CasAuthenticationToken) {
-			return "redirect:" + apiPath + SecurityConfig.CAS_LOGOUT_PATH_SUFFIX;
+			return "redirect:" + apiPath + SecurityConfig.CAS_LOGOUT_PATH;
 		}
 		return "redirect:" + (request.getHeader("referer") != null ? request.getHeader("referer") : "/");
 	}
diff --git a/src/main/java/de/thm/arsnova/security/pac4j/OauthCallbackFilter.java b/src/main/java/de/thm/arsnova/security/pac4j/OauthCallbackFilter.java
index 61d39df76..846977078 100644
--- a/src/main/java/de/thm/arsnova/security/pac4j/OauthCallbackFilter.java
+++ b/src/main/java/de/thm/arsnova/security/pac4j/OauthCallbackFilter.java
@@ -51,8 +51,8 @@ public class OauthCallbackFilter extends AbstractAuthenticationProcessingFilter
 	private final ClientFinder clientFinder = new DefaultCallbackClientFinder();
 	private Config config;
 
-	public OauthCallbackFilter(final Config pac4jConfig) {
-		super(new AntPathRequestMatcher("/login/oauth"));
+	public OauthCallbackFilter(final Config pac4jConfig, final String callbackPath) {
+		super(new AntPathRequestMatcher("/**" + callbackPath + "/**"));
 		this.config = pac4jConfig;
 	}
 
@@ -60,8 +60,7 @@ public class OauthCallbackFilter extends AbstractAuthenticationProcessingFilter
 	public Authentication attemptAuthentication(
 			final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse)
 			throws AuthenticationException {
-		final String clientName = httpServletRequest.getParameter("client_name");
-		final CommonProfile profile = retrieveProfile(new J2EContext(httpServletRequest, httpServletResponse), clientName);
+		final CommonProfile profile = retrieveProfile(new J2EContext(httpServletRequest, httpServletResponse), null);
 		return getAuthenticationManager().authenticate(new OAuthToken(null, profile, Collections.emptyList()));
 	}
 
-- 
GitLab