diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..42e4a4a
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,84 @@
+= Grouper External Authentication Plugin
+
+Grouper plugin that provides configurable authentication. Features include
+
+* Authentication for UI
+* Multiple methods, including SAML2 and OIDC
+
+== Usage
+
+=== Version 4.x
+
+For a fully integrated sample configuration, see `src/test/docker` in the git repo at https://github.internet2.edu/internet2/grouper-ext-auth[]
+
+. Add plugin to Grouper image
++
+[source, dockerfile]
+----
+COPY grouper-authentication-plugin.jar /opt/grouper/plugins
+----
+
+. Enable Plugins
++
+In `grouper.properties`, add properties
++
+[source, properties]
+----
+grouper.osgi.enable = true
+grouper.osgi.jar.dir = /opt/grouper/plugins
+grouper.osgi.framework.boot.delegation=org.osgi.*,javax.*,org.apache.commons.logging,edu.internet2.middleware.grouperClient.*,edu.internet2.middleware.grouper.*,org.w3c.*,org.xml.*,sun.*
+
+grouperOsgiPlugin.0.jarName = grouper-authentication-plugin.jar
+----
++
+`grouper.osgi.jar.dir` should point to the directory you copied the file to in your image build file
++
+`grouperOsgiPlugin.0.jarName` should be the name of the file you copied in
+
+. Configure UI
++
+In `grouper-ui.properties, add properties appropriate for desired authentication. Note that only one can be used.
++
+Most of the configuration for the underlying authentication library is exposed to the Grouper configuration. Any field in the Java classes can be directly set using the field name or a setter used by using a related property (setting `attribute=value` will call `setAttribute(value)` )
+
+.. SAML2
++
+For SAML2, for example:
++
+[source,properties]
+----
+external.authentication.provider = saml
+external.authentication.saml.identityProviderEntityId = https://idp.unicon.local/idp/shibboleth
+external.authentication.saml.serviceProviderEntityId = http://localhost:8080/grouper
+external.authentication.saml.serviceProviderMetadataPath = file:/opt/grouper/sp-metadata.xml
+external.authentication.saml.identityProviderMetadataPath = file:/opt/grouper/idp-metadata.xml
+external.authentication.saml.keystorePath = file:/opt/grouper/here.key
+external.authentication.saml.keystorePassword = testme
+external.authentication.saml.privateKeyPassword = testme
+external.authentication.saml.attributeAsId = urn:oid:0.9.2342.19200300.100.1.1
+----
++
+For more information and more options, see https://www.pac4j.org/5.7.x/docs/clients/saml.html[] and https://github.com/pac4j/pac4j/blob/5.7.x/pac4j-saml/src/main/java/org/pac4j/saml/config/SAML2Configuration.java[]
+
+.. OIDC
++
+For OIDC, for example:
++
+[source,properties]
+----
+external.authentication.provider = oidc
+external.authentication.oidc.clientId = *****
+external.authentication.oidc.discoveryURI = https://unicon.okta.com/.well-known/openid-configuration
+external.authentication.oidc.secret = *****
+external.authentication.oidc.claimAsUsername = preferred_username
+----
++
+For more information and more options, see https://www.pac4j.org/5.7.x/docs/clients/openid-connect.html[] and https://github.com/pac4j/pac4j/blob/5.7.x/pac4j-oidc/src/main/java/org/pac4j/oidc/config/OidcConfiguration.java[]
+
+=== Version 5.x+
+
+TODO
+
+== More Information
+
+If assistance is needed (e.g., bugs, errors, configuration samples), feel free to open a ticket in the github repository or ask on the Slack channel
\ No newline at end of file
diff --git a/README.md b/README.md
deleted file mode 100644
index 56ae56c..0000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# grouper-ext-auth
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 634fcef..6bd8a8f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,10 +32,10 @@
bundle
- 1.8
- 1.8
- 4.3.1
- 5.0.0
+ 17
+ 17
+ 5.7.1
+ 7.1.0
4.7.0
@@ -74,12 +74,12 @@
org.pac4j
- jee-pac4j
- ${jee-pac4j.version}
+ javaee-pac4j
+ ${javaee-pac4j.version}
org.pac4j
- pac4j-saml-opensamlv3
+ pac4j-saml
${pac4j.version}
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
index fb0c971..9573a10 100644
--- a/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ConfigUtils.java
+++ b/src/main/java/edu/internet2/middleware/grouper/authentication/plugin/ConfigUtils.java
@@ -1,6 +1,7 @@
package edu.internet2.middleware.grouper.authentication.plugin;
import edu.internet2.middleware.grouperClient.config.ConfigPropertiesCascadeBase;
+import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
@@ -10,6 +11,8 @@
import org.springframework.core.io.ResourceLoader;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.time.Period;
import java.util.Arrays;
@@ -17,6 +20,8 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
public class ConfigUtils {
final static ResourceLoader resourceLoader = new DefaultResourceLoader();
@@ -50,23 +55,27 @@ public static void setProperties(BaseClientConfiguration configuration, String a
Class> clazz = configuration.getClass();
for (String name : grouperConfig.propertyNames()) {
if (name.startsWith("external.authentication." + authMechanism)) {
+ String fieldName = name.substring(name.lastIndexOf('.') + 1);
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);
+ Method method = getSetter(clazz, getMethodNameFromFieldName(fieldName));
+ method.invoke(configuration, getProperty(grouperConfig, method.getParameterTypes()[0], name));
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ try {
+ Field field = getField(clazz, fieldName);
+ field.setAccessible(true);
+ field.set(configuration, getProperty(grouperConfig, field.getType(), name));
+ } catch (NoSuchFieldException | IllegalAccessException ex) {
+ throw new RuntimeException("could not set " + fieldName, ex);
+ }
}
}
}
}
+ private static String getMethodNameFromFieldName(String fieldName) {
+ return "set" + StringUtils.capitalize(fieldName);
+ }
+
private static Field getField(Class clazz, String name) throws NoSuchFieldException {
try {
return clazz.getDeclaredField(name);
@@ -78,6 +87,11 @@ private static Field getField(Class clazz, String name) throws NoSuchFieldExcept
}
}
+ private static Method getSetter(Class clazz, String name) throws NoSuchMethodException {
+ //TODO: this is dangerous. currently there are no overloaded methods, but there could be in the future. need to decide best way to handle this (parameter type precedence?)
+ return Arrays.stream(clazz.getMethods()).filter(m -> m.getName().equals(name)).findFirst().orElseThrow(NoSuchMethodException::new);
+ }
+
private static Object getProperty(ConfigPropertiesCascadeBase configPropertiesCascadeBase, Type type, String propName) {
switch (type.getTypeName()) {
case "java.lang.String" : {
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
index 7cb2c3e..9f20d9d 100644
--- 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
@@ -4,15 +4,15 @@
import edu.internet2.middleware.grouper.authentication.plugin.oidc.profile.ClaimAsUsernameProfileCreator;
import org.pac4j.oidc.client.OidcClient;
-public class ClaimAsUsernameOidcClient extends OidcClient {
+public class ClaimAsUsernameOidcClient extends OidcClient {
public ClaimAsUsernameOidcClient(final ClaimAsUsernameOidcConfiguration claimAsUsernameOidcConfiguration) {
super(claimAsUsernameOidcConfiguration);
}
@Override
- protected void clientInit() {
+ public void init() {
this.defaultProfileCreator(new ClaimAsUsernameProfileCreator(this.getConfiguration(), this));
- super.clientInit();
+ super.init();
}
}
\ 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
index 163c983..2ad5cf7 100644
--- 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
@@ -6,17 +6,17 @@
import org.pac4j.oidc.config.OidcConfiguration;
import org.pac4j.oidc.profile.creator.OidcProfileCreator;
-public class ClaimAsUsernameProfileCreator extends OidcProfileCreator {
+public class ClaimAsUsernameProfileCreator extends OidcProfileCreator {
public ClaimAsUsernameProfileCreator(OidcConfiguration configuration, OidcClient client) {
super(configuration, client);
}
@Override
- protected void internalInit() {
+ protected void internalInit(boolean forceReinit) {
CommonHelper.assertNotNull("claimAsUsername", ((ClaimAsUsernameOidcConfiguration)this.configuration).getClaimAsUsername());
defaultProfileDefinition(new ClaimAsUsernameProfileDefinition(((ClaimAsUsernameOidcConfiguration)this.configuration).getClaimAsUsername()));
- super.internalInit();
+ super.internalInit(forceReinit);
}
}
\ 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
index 8981c5a..0bb5260 100644
--- 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
@@ -2,7 +2,7 @@
import org.pac4j.oidc.profile.OidcProfileDefinition;
-public class ClaimAsUsernameProfileDefinition extends OidcProfileDefinition {
+public class ClaimAsUsernameProfileDefinition extends OidcProfileDefinition {
public ClaimAsUsernameProfileDefinition(final String claimAsUsername) {
super();
setProfileFactory(x -> new ClaimAsUsernameProfile(claimAsUsername));
diff --git a/src/test/docker/docker-compose.yml b/src/test/docker/docker-compose.yml
index a2ac27f..93fa879 100644
--- a/src/test/docker/docker-compose.yml
+++ b/src/test/docker/docker-compose.yml
@@ -91,11 +91,11 @@ services:
GROUPER_DATABASE_USERNAME: "grouper"
GROUPER_DATABASE_PASSWORD: "grouper"
GROUPER_MORPHSTRING_ENCRYPT_KEY: "THISISSUPERSECRET!"
- GROUPER_AUTO_DDL_UPTOVERSION: "2.6.*"
+ GROUPER_AUTO_DDL_UPTOVERSION: "4.*.*"
GROUPER_RUN_TOMCAT_NOT_SUPERVISOR: "true"
GROUPER_UI_CONFIGURATION_EDITOR_SOURCEIPADDRESSES: "0.0.0.0/0"
RUN_SHIB_SP: "false"
- GROUPER_EXTRA_CATALINA_OPTS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:15005"
+ GROUPER_EXTRA_CATALINA_OPTS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:15005"
# GROUPER_UI_GROUPER_AUTH: "true"
# GROUPERSYSTEM_QUICKSTART_PASS: "letmein7"
networks:
diff --git a/src/test/docker/grouper/config/grouper-ui.properties b/src/test/docker/grouper/config/grouper-ui.properties
index dbb5467..3febaef 100644
--- a/src/test/docker/grouper/config/grouper-ui.properties
+++ b/src/test/docker/grouper/config/grouper-ui.properties
@@ -7,12 +7,16 @@ external.authentication.grouperContextUrl = https://grouper-ui.unicon.local/grou
#external.authentication.oidc.secret = *****
#external.authentication.oidc.claimAsUsername = preferred_username
-external.authentication.provider = saml
-external.authentication.saml.identityProviderEntityId = https://idp.unicon.local/idp/shibboleth
-external.authentication.saml.serviceProviderEntityId = http://localhost:8080/grouper
-external.authentication.saml.serviceProviderMetadataResource = file:/opt/grouper/sp-metadata.xml
-external.authentication.saml.identityProviderMetadataResource = file:/opt/grouper/idp-metadata.xml
-external.authentication.saml.keystoreResource = file:/opt/grouper/here.key
-external.authentication.saml.keystorePassword = testme
-external.authentication.saml.privateKeyPassword = testme
-external.authentication.saml.attributeAsId = urn:oid:0.9.2342.19200300.100.1.1
\ No newline at end of file
+#external.authentication.provider = saml
+#external.authentication.saml.identityProviderEntityId = https://idp.unicon.local/idp/shibboleth
+#external.authentication.saml.serviceProviderEntityId = http://localhost:8080/grouper
+#external.authentication.saml.serviceProviderMetadataPath = file:/opt/grouper/sp-metadata.xml
+#external.authentication.saml.identityProviderMetadataPath = file:/opt/grouper/idp-metadata.xml
+#external.authentication.saml.keystorePath = file:/opt/grouper/here.key
+#external.authentication.saml.keystorePassword = testme
+#external.authentication.saml.privateKeyPassword = testme
+#external.authentication.saml.attributeAsId = urn:oid:0.9.2342.19200300.100.1.1
+
+# Note for CAS: you'll need to make sure that the CAS server SSL certificate is available in the trust store
+#external.authentication.provider = cas
+#external.authentication.cas.loginUrl = https://idp.unicon.local/idp/profile/cas/login
\ No newline at end of file
diff --git a/src/test/java/edu/internet2/middleware/grouper/authentication/Pac4JConfigFactoryTest.java b/src/test/java/edu/internet2/middleware/grouper/authentication/Pac4JConfigFactoryTest.java
index c094936..0a4e739 100644
--- a/src/test/java/edu/internet2/middleware/grouper/authentication/Pac4JConfigFactoryTest.java
+++ b/src/test/java/edu/internet2/middleware/grouper/authentication/Pac4JConfigFactoryTest.java
@@ -27,7 +27,9 @@
import org.pac4j.oidc.config.OidcConfiguration;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.config.SAML2Configuration;
+import org.springframework.core.io.FileSystemResource;
+import java.io.IOException;
import java.time.Period;
import java.util.Arrays;
import java.util.Collections;
@@ -112,7 +114,7 @@ public void testPac4JConfigFactorCAS() {
properties.put("external.authentication.cas.postLogoutUrlParameter","logout");
properties.put("external.authentication.cas.customParams","param1=value1,param2=value2,param3=value3");
properties.put("external.authentication.cas.method","post");
- properties.put("external.authentication.cas.privateKeyPath","http://localhost/key");
+ properties.put("external.authentication.cas.privateKeyPath","file:/key");
properties.put("external.authentication.cas.privateKeyAlgorithm","AES");
Pac4jConfigFactory pac4jConfigFactory = new Pac4jConfigFactory();
@@ -191,7 +193,6 @@ public void testPac4JConfigFactorSAML() {
properties.put("external.authentication.saml.issuerFormat","urn:oasis:names:tc:SAML:2.0:nameid-format:entity");
properties.put("external.authentication.saml.nameIdPolicyAllowCreate","true");
properties.put("external.authentication.saml.supportedProtocols","urn:oasis:names:tc:SAML:2.0:protocol, urn:oasis:names:tc:SAML:1.0:protocol, urn:oasis:names:tc:SAML:1.1:protocol");
- properties.put("external.authentication.saml.normalizedCertificateName","ringo");
Pac4jConfigFactory pac4jConfigFactory = new Pac4jConfigFactory();
Config config = pac4jConfigFactory.build();
@@ -311,4 +312,24 @@ public void testPac4jForManualProvider() {
Assert.assertTrue(true);
}
+
+ @Test
+ public void testPac4jConfigMethodFind() throws IOException {
+ // external.authentication.saml.identityProviderMetadataPath = file:/opt/grouper/idp-metadata.xml
+ ConfigPropertiesCascadeBase grouperConfig = ConfigUtils.getConfigPropertiesCascadeBase("ui");
+
+ grouperConfig.propertiesOverrideMap().clear();
+ Map overrides = grouperConfig.propertiesOverrideMap();
+ overrides.put("external.authentication.provider","saml");
+ overrides.put("external.authentication.grouperContextUrl","localhost");
+ overrides.put("external.authentication.callbackUrl","callback");
+ overrides.put("external.authentication.saml.identityProviderMetadataPath", "file:/opt/grouper/idp-metadata.xml");
+
+ Pac4jConfigFactory pac4jConfigFactory = new Pac4jConfigFactory();
+ Config config = pac4jConfigFactory.build();
+
+ SAML2Configuration configuration = ((SAML2Client) config.getClients().getClients().get(0)).getConfiguration();
+
+ Assert.assertTrue(configuration.getIdentityProviderMetadataResource().isFile() && ((FileSystemResource)configuration.getIdentityProviderMetadataResource()).getPath().equals("/opt/grouper/idp-metadata.xml"));
+ }
}
\ No newline at end of file