From 5e57087737a445bb600351f0f6e27f6f0736572d Mon Sep 17 00:00:00 2001 From: Jj! Date: Mon, 22 Aug 2022 09:59:16 -0500 Subject: [PATCH] [NOTASK] initial java code --- .../authentication/plugin/ConfigUtils.java | 145 ++++++++++++++++++ ...enticationServletContainerInitializer.java | 41 +++++ .../plugin/GrouperAuthentication.java | 30 ++++ .../plugin/Pac4jConfigFactory.java | 80 ++++++++++ .../plugin/config/CasClientProvider.java | 24 +++ .../plugin/config/ClientProvider.java | 8 + .../plugin/config/ClientProviders.java | 23 +++ .../plugin/config/OidcClientProvider.java | 68 ++++++++ .../plugin/config/SAML2ClientProvider.java | 24 +++ .../filter/CallbackFilterDecorator.java | 50 ++++++ .../plugin/filter/FilterDecoratorUtils.java | 10 ++ .../plugin/filter/Reinitializable.java | 5 + .../plugin/filter/ReinitializingTimer.java | 54 +++++++ .../filter/SecurityFilterDecorator.java | 48 ++++++ .../client/ClaimAsUsernameOidcClient.java | 18 +++ .../ClaimAsUsernameOidcConfiguration.java | 15 ++ .../oidc/profile/ClaimAsUsernameProfile.java | 16 ++ .../ClaimAsUsernameProfileCreator.java | 22 +++ .../ClaimAsUsernameProfileDefinition.java | 10 ++ 19 files changed, 691 insertions(+) create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ConfigUtils.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ExternalAuthenticationServletContainerInitializer.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/GrouperAuthentication.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/Pac4jConfigFactory.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/CasClientProvider.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/ClientProvider.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/ClientProviders.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/OidcClientProvider.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/SAML2ClientProvider.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/CallbackFilterDecorator.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/FilterDecoratorUtils.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/Reinitializable.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/ReinitializingTimer.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/SecurityFilterDecorator.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/client/ClaimAsUsernameOidcClient.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/config/ClaimAsUsernameOidcConfiguration.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfile.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfileCreator.java create mode 100644 src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfileDefinition.java diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ConfigUtils.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ConfigUtils.java new file mode 100644 index 0000000..341cba7 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ConfigUtils.java @@ -0,0 +1,145 @@ +package edu.internet2.middleware.grouper.authentication.plugin; + +import edu.internet2.middleware.grouper.cfg.GrouperHibernateConfig; +import edu.internet2.middleware.grouperClient.config.ConfigPropertiesCascadeBase; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.pac4j.core.client.config.BaseClientConfiguration; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; + +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.time.Period; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class ConfigUtils { + final static ResourceLoader resourceLoader = new DefaultResourceLoader(); + + final static BundleContext bundleContext = FrameworkUtil.getBundle(GrouperAuthentication.class).getBundleContext(); + + public static ConfigPropertiesCascadeBase getBestGrouperConfiguration() { + if (isGrouperUi()) { + return getConfigPropertiesCascadeBase("ui"); + } else if (isGrouperWs()) { + return getConfigPropertiesCascadeBase("ws"); + } else if (isGrouperDaemon()) { + return getConfigPropertiesCascadeBase("daemon"); + } else { + throw new RuntimeException("no appropriate configuration found"); + } + } + + public static ConfigPropertiesCascadeBase getConfigPropertiesCascadeBase(String type) { + try { + ServiceReference serviceReference = (ServiceReference) FrameworkUtil.getBundle(ConfigUtils.class.getClassLoader()).get().getBundleContext().getServiceReferences(ConfigPropertiesCascadeBase.class, "(type=" + type + ")").toArray()[0]; + return FrameworkUtil.getBundle(ConfigUtils.class.getClassLoader()).get().getBundleContext().getService(serviceReference); + } catch (InvalidSyntaxException e) { + throw new RuntimeException(e); + } + } + + public static void setProperties(BaseClientConfiguration configuration, String authMechanism) { + ConfigPropertiesCascadeBase grouperConfig = getBestGrouperConfiguration(); + + Class clazz = configuration.getClass(); + for (String name : grouperConfig.propertyNames()) { + if (name.startsWith("external.authentication." + authMechanism)) { + try { + String fieldName = name.substring(name.lastIndexOf('.') + 1); + Field field = getField(clazz, fieldName); + + //TODO: prefer setters + + field.setAccessible(true); + field.set(configuration, getProperty(grouperConfig, field.getType(), name)); + } catch (NoSuchFieldException e) { + throw new IllegalStateException("Unexpected property name: " + name); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Unable to access property name: " + name); + } + } + } + } + + private static Field getField(Class clazz, String name) throws NoSuchFieldException { + try { + return clazz.getDeclaredField(name); + } catch (NoSuchFieldException e) { + if (clazz.equals(Object.class)) { + throw new NoSuchFieldException(name); + } + return getField(clazz.getSuperclass(), name); + } + } + + private static Object getProperty(ConfigPropertiesCascadeBase configPropertiesCascadeBase, Type type, String propName) { + switch (type.getTypeName()) { + case "java.lang.String" : { + return configPropertiesCascadeBase.propertyValueString(propName); + } + case "int" : + case "java.lang.Integer" : { + return configPropertiesCascadeBase.propertyValueInt(propName); + } + case "long" : + case "java.lang.Long" : { + return Long.parseLong(configPropertiesCascadeBase.propertyValueString(propName)); + } + case "double" : + case "java.lang.Double" : { + return Double.parseDouble(configPropertiesCascadeBase.propertyValueString(propName)); + } + case "boolean" : + case "java.lang.Boolean" : { + return configPropertiesCascadeBase.propertyValueBoolean(propName); + } + case "java.util.List" : + case "java.util.Collection" :{ + return Arrays.asList(configPropertiesCascadeBase.propertyValueString(propName).split(",")); + } + case "java.util.Set" : { + Set set = new HashSet(); + for (String prop : configPropertiesCascadeBase.propertyValueString(propName).split(",")) { + set.add(prop); + } + return set; + } + case "java.util.Map" : { + Map map = new HashMap(); + for (String pairs : configPropertiesCascadeBase.propertyValueString(propName).split(",")) { + String [] keyValue = pairs.split("="); + map.put(keyValue[0].trim(),keyValue[1].trim()); + } + return map; + } + case "java.time.Period" : { + return Period.parse(configPropertiesCascadeBase.propertyValueString(propName)); + } + case "org.springframework.core.io.WritableResource": + case "org.springframework.core.io.Resource": { + return resourceLoader.getResource(configPropertiesCascadeBase.propertyValueString(propName)); + } + default: + throw new IllegalStateException("Unexpected type: " + type.getTypeName()); + } + } + + public static boolean isGrouperUi() { + return getConfigPropertiesCascadeBase("hibernate").propertyValueBoolean("grouper.is.ui", false); + } + + public static boolean isGrouperWs() { + return getConfigPropertiesCascadeBase("hibernate").propertyValueBoolean("grouper.is.ws", false); + } + + public static boolean isGrouperDaemon() { + return getConfigPropertiesCascadeBase("hibernate").propertyValueBoolean("grouper.is.daemon", false); + } +} diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ExternalAuthenticationServletContainerInitializer.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ExternalAuthenticationServletContainerInitializer.java new file mode 100644 index 0000000..c3f081c --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ExternalAuthenticationServletContainerInitializer.java @@ -0,0 +1,41 @@ +package edu.internet2.middleware.grouper.authentication.plugin; + +import edu.internet2.middleware.grouper.authentication.plugin.filter.CallbackFilterDecorator; +import edu.internet2.middleware.grouper.authentication.plugin.filter.SecurityFilterDecorator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +import javax.servlet.FilterRegistration; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import java.util.Set; + +public class ExternalAuthenticationServletContainerInitializer implements ServletContainerInitializer { + private final Log log; + + public ExternalAuthenticationServletContainerInitializer(BundleContext bundleContext) { + try { + //TODO: figure out why this is weird + ServiceReference logfactoryReference = (ServiceReference) bundleContext.getAllServiceReferences("org.apache.commons.logging.LogFactory", null)[0]; + log = bundleContext.getService(logfactoryReference).getInstance(ExternalAuthenticationServletContainerInitializer.class); + } catch (InvalidSyntaxException e) { + throw new RuntimeException(e); + } + } + + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException { + log.info("Initializing plugin security filters for external authentication"); + CallbackFilterDecorator callbackFilterDecorator = new CallbackFilterDecorator(); + FilterRegistration.Dynamic callbackFilter = ctx.addFilter("callbackFilter", callbackFilterDecorator); + callbackFilter.addMappingForUrlPatterns(null, false, "/*"); + + SecurityFilterDecorator securityFilterDecorator = new SecurityFilterDecorator(); + FilterRegistration.Dynamic securityFilter = ctx.addFilter("securityFilter", securityFilterDecorator); + securityFilter.addMappingForUrlPatterns(null, false, "/*"); + } +} diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/GrouperAuthentication.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/GrouperAuthentication.java new file mode 100644 index 0000000..ece3cb1 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/GrouperAuthentication.java @@ -0,0 +1,30 @@ +package edu.internet2.middleware.grouper.authentication.plugin; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; + +import javax.servlet.ServletContainerInitializer; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +public class GrouperAuthentication implements BundleActivator { + private Map referenceMap = new HashMap<>(); + private Map registrationMap = new HashMap<>(); + + @Override + public void start(BundleContext context) throws Exception { + ExternalAuthenticationServletContainerInitializer externalAuthenticationServletContainerInitializer = new ExternalAuthenticationServletContainerInitializer(context); + ServiceRegistration easciRegistration = context.registerService(ServletContainerInitializer.class, externalAuthenticationServletContainerInitializer, new Hashtable<>()); + registrationMap.put(ExternalAuthenticationServletContainerInitializer.class.getCanonicalName(), easciRegistration); + } + + @Override + public void stop(BundleContext context) throws Exception { + for (ServiceRegistration registration : registrationMap.values()) { + registration.unregister(); + } + } +} diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/Pac4jConfigFactory.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/Pac4jConfigFactory.java new file mode 100644 index 0000000..793f2b5 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/Pac4jConfigFactory.java @@ -0,0 +1,80 @@ +package edu.internet2.middleware.grouper.authentication.plugin; + +import edu.internet2.middleware.grouper.authentication.plugin.config.ClientProvider; +import edu.internet2.middleware.grouper.authentication.plugin.config.ClientProviders; +import edu.internet2.middleware.grouperClient.config.ConfigPropertiesCascadeBase; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.pac4j.core.client.Client; +import org.pac4j.core.client.Clients; +import org.pac4j.core.config.Config; +import org.pac4j.core.config.ConfigFactory; +import org.pac4j.core.matching.matcher.PathMatcher; + +public class Pac4jConfigFactory implements ConfigFactory { + // private static final Logger LOGGER = Logger.getLogger(Pac4jConfigFactory.class); + private static final Log LOGGER; + static { + try { + BundleContext bundleContext = FrameworkUtil.getBundle(Pac4jConfigFactory.class).getBundleContext(); + //TODO: figure out why this is weird + ServiceReference logfactoryReference = (ServiceReference) bundleContext.getAllServiceReferences("org.apache.commons.logging.LogFactory", null)[0]; + LOGGER = bundleContext.getService(logfactoryReference).getInstance(ExternalAuthenticationServletContainerInitializer.class); + } catch (InvalidSyntaxException e) { + throw new RuntimeException(e); + } + } + + @Override + public Config build(Object... parameters) { + try { + ConfigPropertiesCascadeBase grouperConfig = ConfigUtils.getBestGrouperConfiguration(); + + String provider; + if (grouperConfig.containsKey("external.authentication.mechanism")) { + LOGGER.warn("you're using the deprecated key `external.authentication.mechanism`; please update to `external.authentication.provider`"); + provider = grouperConfig.propertyValueString("external.authentication.mechanism"); + } else { + provider = grouperConfig.propertyValueString("external.authentication.provider"); + } + Client client = getClient(provider); + + String callbackUrl = grouperConfig.propertyValueString("external.authentication.grouperContextUrl") + + grouperConfig.propertyValueString("external.authentication.callbackUrl", "/callback"); + final Clients clients = new Clients(callbackUrl, client); + + final Config config = new Config(clients); + + PathMatcher pathMatcher = new PathMatcher(); + + for (String exclusion : grouperConfig.propertyValueString("external.authentication.exclusions", "/status").split(",")) { + pathMatcher.excludeBranch(StringUtils.trim(exclusion)); + } + + config.addMatcher("securityExclusions", pathMatcher); + return config; + } catch (IllegalAccessException|InstantiationException e) { + throw new RuntimeException("problem configuring pac4j", e); + } + } + + private static Client getClient(String provider) throws IllegalAccessException, InstantiationException { + Class providerClass; + //TODO: might be a better way of doing this + try { + providerClass = ClientProviders.fromString(provider).getProviderClass(); + } catch (IllegalArgumentException e) { + try { + providerClass = (Class) Class.forName(provider); + } catch (ClassNotFoundException classNotFoundException) { + throw new RuntimeException(classNotFoundException); + } + } + return providerClass.newInstance().getClient(); + } +} diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/CasClientProvider.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/CasClientProvider.java new file mode 100644 index 0000000..1a04a91 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/CasClientProvider.java @@ -0,0 +1,24 @@ +package edu.internet2.middleware.grouper.authentication.plugin.config; + +import edu.internet2.middleware.grouper.authentication.plugin.ConfigUtils; +import org.pac4j.cas.client.CasClient; +import org.pac4j.cas.config.CasConfiguration; +import org.pac4j.core.client.Client; + +public class CasClientProvider implements ClientProvider { + @Override + public boolean supports(String type) { + return "cas".equals(type); + } + + @Override + public Client getClient() { + final CasConfiguration configuration = new CasConfiguration(); + + ConfigUtils.setProperties(configuration, "cas"); + CasClient client = new CasClient(configuration); + //TODO: make configurable + client.setName("client"); + return client; + } +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/ClientProvider.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/ClientProvider.java new file mode 100644 index 0000000..d14f756 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/ClientProvider.java @@ -0,0 +1,8 @@ +package edu.internet2.middleware.grouper.authentication.plugin.config; + +import org.pac4j.core.client.Client; + +public interface ClientProvider { + boolean supports(String type); + Client getClient(); +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/ClientProviders.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/ClientProviders.java new file mode 100644 index 0000000..c107ba7 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/ClientProviders.java @@ -0,0 +1,23 @@ +package edu.internet2.middleware.grouper.authentication.plugin.config; + +import java.util.Locale; + +public enum ClientProviders { + CAS (CasClientProvider.class), + OIDC (OidcClientProvider.class), + SAML (SAML2ClientProvider.class); + + private final Class providerClass; + + ClientProviders(Class clazz) { + this.providerClass = clazz; + } + + public Class getProviderClass() { + return this.providerClass; + } + + public static ClientProviders fromString(String name) { + return ClientProviders.valueOf(name.toUpperCase(Locale.ENGLISH)); + } +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/OidcClientProvider.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/OidcClientProvider.java new file mode 100644 index 0000000..2bb8558 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/OidcClientProvider.java @@ -0,0 +1,68 @@ +package edu.internet2.middleware.grouper.authentication.plugin.config; + +import edu.internet2.middleware.grouper.authentication.plugin.ConfigUtils; +import edu.internet2.middleware.grouper.authentication.plugin.ExternalAuthenticationServletContainerInitializer; +import edu.internet2.middleware.grouper.authentication.plugin.Pac4jConfigFactory; +import edu.internet2.middleware.grouper.authentication.plugin.oidc.client.ClaimAsUsernameOidcClient; +import edu.internet2.middleware.grouper.authentication.plugin.oidc.config.ClaimAsUsernameOidcConfiguration; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Logger; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.pac4j.core.client.Client; +import org.pac4j.oidc.client.OidcClient; +import org.pac4j.oidc.config.OidcConfiguration; + +public class OidcClientProvider implements ClientProvider { + private static final Log LOGGER; + static { + try { + BundleContext bundleContext = FrameworkUtil.getBundle(Pac4jConfigFactory.class).getBundleContext(); + //TODO: figure out why this is weird + ServiceReference logfactoryReference = (ServiceReference) bundleContext.getAllServiceReferences("org.apache.commons.logging.LogFactory", null)[0]; + LOGGER = bundleContext.getService(logfactoryReference).getInstance(ExternalAuthenticationServletContainerInitializer.class); + } catch (InvalidSyntaxException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean supports(String type) { + return "oidc".equals(type); + } + + @Override + public Client getClient() { + OidcClient client; + String implementation = ConfigUtils.getBestGrouperConfiguration().propertyValueString("external.authentication.mechanism.oidc.clientImplementation"); + if (implementation != null && !implementation.isEmpty()) { + try { + OidcConfiguration configuration = (OidcConfiguration) Class.forName(implementation).newInstance(); + client = new OidcClient(configuration); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOGGER.warn("problem loading pac4j client implementation; using a default", e); + client = getClaimAsUsernameOidcClient(); + } + } else { + client = getClaimAsUsernameOidcClient(); + } + ConfigUtils.setProperties(client.getConfiguration(), "oidc"); + + //TODO: make configurable + client.setName("client"); + return client; + } + + private static ClaimAsUsernameOidcClient getClaimAsUsernameOidcClient() { + ClaimAsUsernameOidcConfiguration configuration = new ClaimAsUsernameOidcConfiguration(); + String claimAsUsername = ConfigUtils.getBestGrouperConfiguration().propertyValueString("external.authentication.oidc.claimAsUsername"); + if (claimAsUsername != null) { + configuration.setClaimAsUsername(claimAsUsername); + } + ClaimAsUsernameOidcClient client = new ClaimAsUsernameOidcClient(configuration); + return client; + } +} diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/SAML2ClientProvider.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/SAML2ClientProvider.java new file mode 100644 index 0000000..c76c62d --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/config/SAML2ClientProvider.java @@ -0,0 +1,24 @@ +package edu.internet2.middleware.grouper.authentication.plugin.config; + +import edu.internet2.middleware.grouper.authentication.plugin.ConfigUtils; +import org.pac4j.core.client.Client; +import org.pac4j.saml.client.SAML2Client; +import org.pac4j.saml.config.SAML2Configuration; + +public class SAML2ClientProvider implements ClientProvider { + @Override + public boolean supports(String type) { + return "saml".equals(type); + } + + @Override + public Client getClient() { + final SAML2Configuration configuration = new SAML2Configuration(); + ConfigUtils.setProperties(configuration, "saml"); + SAML2Client client = new SAML2Client(configuration); + + //TODO: make configurable + client.setName("client"); + return client; + } +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/CallbackFilterDecorator.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/CallbackFilterDecorator.java new file mode 100644 index 0000000..2c45f19 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/CallbackFilterDecorator.java @@ -0,0 +1,50 @@ +package edu.internet2.middleware.grouper.authentication.plugin.filter; + +import edu.internet2.middleware.grouper.authentication.plugin.ConfigUtils; +import edu.internet2.middleware.grouper.cfg.GrouperHibernateConfig; +import org.pac4j.jee.filter.CallbackFilter; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Timer; +import java.util.TimerTask; + +public class CallbackFilterDecorator extends CallbackFilter implements Reinitializable { + private Timer timer = new Timer(); + + private static boolean isCallbackUrlCalled(HttpServletRequest request) { + return getRequestPathInContext(request).matches(ConfigUtils.getBestGrouperConfiguration().propertyValueString("external.authentication.callbackUrl", "/callback")); + } + + private static String getRequestPathInContext(HttpServletRequest request) { + return request.getRequestURI().replaceFirst(request.getServletContext().getContextPath(), ""); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + this.initDecorator(); + TimerTask timerTask = new ReinitializingTimer(this); + int period = ConfigUtils.getBestGrouperConfiguration().propertyValueInt("external.authentication.config.reload.milliseconds", 60 * 1000); + this.timer.schedule(timerTask, period, period); + } + + public void initDecorator() { + this.setDefaultUrl(ConfigUtils.getBestGrouperConfiguration().propertyValueString("external.authentication.defaultUrl", "/")); + this.setRenewSession(true); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (FilterDecoratorUtils.isExternalAuthenticationEnabled() && isCallbackUrlCalled((HttpServletRequest) request)) { + super.doFilter(request, response, chain); + } else { + chain.doFilter(request, response); + } + } +} diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/FilterDecoratorUtils.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/FilterDecoratorUtils.java new file mode 100644 index 0000000..55db5e3 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/FilterDecoratorUtils.java @@ -0,0 +1,10 @@ +package edu.internet2.middleware.grouper.authentication.plugin.filter; + +import edu.internet2.middleware.grouper.authentication.plugin.ConfigUtils; + +public class FilterDecoratorUtils { + protected static boolean isExternalAuthenticationEnabled() { + return ConfigUtils.getBestGrouperConfiguration().propertyValueBoolean("grouper.is.extAuth.enabled", false); + } + +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/Reinitializable.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/Reinitializable.java new file mode 100644 index 0000000..60e8d2c --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/Reinitializable.java @@ -0,0 +1,5 @@ +package edu.internet2.middleware.grouper.authentication.plugin.filter; + +public interface Reinitializable { + void initDecorator(); +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/ReinitializingTimer.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/ReinitializingTimer.java new file mode 100644 index 0000000..9d98144 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/ReinitializingTimer.java @@ -0,0 +1,54 @@ +package edu.internet2.middleware.grouper.authentication.plugin.filter; + +import edu.internet2.middleware.grouper.authentication.plugin.ConfigUtils; +import edu.internet2.middleware.grouper.authentication.plugin.ExternalAuthenticationServletContainerInitializer; +import edu.internet2.middleware.grouper.authentication.plugin.Pac4jConfigFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +import java.util.Map; +import java.util.TimerTask; +import java.util.regex.Pattern; + +public class ReinitializingTimer extends TimerTask { + private static final Log LOGGER; + static { + try { + BundleContext bundleContext = FrameworkUtil.getBundle(Pac4jConfigFactory.class).getBundleContext(); + //TODO: figure out why this is weird + ServiceReference logfactoryReference = (ServiceReference) bundleContext.getAllServiceReferences("org.apache.commons.logging.LogFactory", null)[0]; + LOGGER = bundleContext.getService(logfactoryReference).getInstance(ExternalAuthenticationServletContainerInitializer.class); + } catch (InvalidSyntaxException e) { + throw new RuntimeException(e); + } + } + + private Map config; + private final Reinitializable initTarget; + + public ReinitializingTimer(Reinitializable initTarget) { + this.initTarget = initTarget; + config = ConfigUtils.getBestGrouperConfiguration().propertiesMap(Pattern.compile("^external\\.authentication\\.([^.]+)$")); + } + + protected static boolean areEqual(Map first, Map second) { + if (first.size() != second.size()) { + return false; + } + return first.entrySet().stream().allMatch(e -> e.getValue().equals(second.get(e.getKey()))); + } + + @Override + public void run() { + Map curConfig = ConfigUtils.getBestGrouperConfiguration().propertiesMap(Pattern.compile("^external\\.authentication\\.([^.]+)$")); + if (!areEqual(config, curConfig)) { + config = curConfig; + initTarget.initDecorator(); + LOGGER.info("Pac4j External Authentication configuration reloaded"); + } + } +} diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/SecurityFilterDecorator.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/SecurityFilterDecorator.java new file mode 100644 index 0000000..6f88fea --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/filter/SecurityFilterDecorator.java @@ -0,0 +1,48 @@ +package edu.internet2.middleware.grouper.authentication.plugin.filter; + +import edu.internet2.middleware.grouper.authentication.plugin.ConfigUtils; +import edu.internet2.middleware.grouper.authentication.plugin.Pac4jConfigFactory; +import org.pac4j.core.authorization.authorizer.DefaultAuthorizers; +import org.pac4j.core.config.ConfigBuilder; +import org.pac4j.core.util.Pac4jConstants; +import org.pac4j.jee.filter.SecurityFilter; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; +import java.util.Timer; +import java.util.TimerTask; + +public class SecurityFilterDecorator extends SecurityFilter implements Reinitializable { + private Timer timer = new Timer(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + this.initDecorator(); + TimerTask timerTask = new ReinitializingTimer(this); + int period = ConfigUtils.getBestGrouperConfiguration().propertyValueInt("external.authentication.config.reload.milliseconds", 60 * 1000); + this.timer.schedule(timerTask, period, period); + } + + public void initDecorator() { + if (ConfigUtils.isGrouperUi() && FilterDecoratorUtils.isExternalAuthenticationEnabled()) { + this.setSharedConfig(new Pac4jConfigFactory().build()); + this.setClients("client"); + this.setMatchers(String.join(Pac4jConstants.ELEMENT_SEPARATOR, "securityExclusions")); + this.setAuthorizers(DefaultAuthorizers.NONE); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (ConfigUtils.isGrouperUi() && FilterDecoratorUtils.isExternalAuthenticationEnabled()) { + super.doFilter(request, response, chain); + } else { + chain.doFilter(request, response); + } + } +} diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/client/ClaimAsUsernameOidcClient.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/client/ClaimAsUsernameOidcClient.java new file mode 100644 index 0000000..7cb2c3e --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/client/ClaimAsUsernameOidcClient.java @@ -0,0 +1,18 @@ +package edu.internet2.middleware.grouper.authentication.plugin.oidc.client; + +import edu.internet2.middleware.grouper.authentication.plugin.oidc.config.ClaimAsUsernameOidcConfiguration; +import edu.internet2.middleware.grouper.authentication.plugin.oidc.profile.ClaimAsUsernameProfileCreator; +import org.pac4j.oidc.client.OidcClient; + +public class ClaimAsUsernameOidcClient extends OidcClient { + public ClaimAsUsernameOidcClient(final ClaimAsUsernameOidcConfiguration claimAsUsernameOidcConfiguration) { + super(claimAsUsernameOidcConfiguration); + } + + @Override + protected void clientInit() { + this.defaultProfileCreator(new ClaimAsUsernameProfileCreator(this.getConfiguration(), this)); + + super.clientInit(); + } +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/config/ClaimAsUsernameOidcConfiguration.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/config/ClaimAsUsernameOidcConfiguration.java new file mode 100644 index 0000000..e20e2cf --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/config/ClaimAsUsernameOidcConfiguration.java @@ -0,0 +1,15 @@ +package edu.internet2.middleware.grouper.authentication.plugin.oidc.config; + +import org.pac4j.oidc.config.OidcConfiguration; + +public class ClaimAsUsernameOidcConfiguration extends OidcConfiguration { + private String claimAsUsername; + + public String getClaimAsUsername() { + return claimAsUsername; + } + + public void setClaimAsUsername(String claimAsUsername) { + this.claimAsUsername = claimAsUsername; + } +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfile.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfile.java new file mode 100644 index 0000000..fa2dfa9 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfile.java @@ -0,0 +1,16 @@ +package edu.internet2.middleware.grouper.authentication.plugin.oidc.profile; + +import org.pac4j.oidc.profile.OidcProfile; + +public class ClaimAsUsernameProfile extends OidcProfile { + private final String claimAsUsername; + + public ClaimAsUsernameProfile(final String claimAsUsername) { + this.claimAsUsername = claimAsUsername; + } + + @Override + public String getUsername() { + return this.getAttribute(this.claimAsUsername).toString(); + } +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfileCreator.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfileCreator.java new file mode 100644 index 0000000..163c983 --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfileCreator.java @@ -0,0 +1,22 @@ +package edu.internet2.middleware.grouper.authentication.plugin.oidc.profile; + +import edu.internet2.middleware.grouper.authentication.plugin.oidc.config.ClaimAsUsernameOidcConfiguration; +import org.pac4j.core.util.CommonHelper; +import org.pac4j.oidc.client.OidcClient; +import org.pac4j.oidc.config.OidcConfiguration; +import org.pac4j.oidc.profile.creator.OidcProfileCreator; + +public class ClaimAsUsernameProfileCreator extends OidcProfileCreator { + public ClaimAsUsernameProfileCreator(OidcConfiguration configuration, OidcClient client) { + super(configuration, client); + } + + @Override + protected void internalInit() { + CommonHelper.assertNotNull("claimAsUsername", ((ClaimAsUsernameOidcConfiguration)this.configuration).getClaimAsUsername()); + + defaultProfileDefinition(new ClaimAsUsernameProfileDefinition(((ClaimAsUsernameOidcConfiguration)this.configuration).getClaimAsUsername())); + + super.internalInit(); + } +} \ No newline at end of file diff --git a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfileDefinition.java b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfileDefinition.java new file mode 100644 index 0000000..8981c5a --- /dev/null +++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/oidc/profile/ClaimAsUsernameProfileDefinition.java @@ -0,0 +1,10 @@ +package edu.internet2.middleware.grouper.authentication.plugin.oidc.profile; + +import org.pac4j.oidc.profile.OidcProfileDefinition; + +public class ClaimAsUsernameProfileDefinition extends OidcProfileDefinition { + public ClaimAsUsernameProfileDefinition(final String claimAsUsername) { + super(); + setProfileFactory(x -> new ClaimAsUsernameProfile(claimAsUsername)); + } +} \ No newline at end of file