Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[NOTASK]
updates for pac4j 5.7
use accessors for configuration
updated configuration for pac4j 5.7 and CAS
documentation
Jj! committed Nov 3, 2023
1 parent f88664e commit 7d40f06
Showing 10 changed files with 161 additions and 39 deletions.
84 changes: 84 additions & 0 deletions 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
1 change: 0 additions & 1 deletion README.md

This file was deleted.

14 changes: 7 additions & 7 deletions pom.xml
@@ -32,10 +32,10 @@
<packaging>bundle</packaging>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<pac4j.version>4.3.1</pac4j.version>
<jee-pac4j.version>5.0.0</jee-pac4j.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<pac4j.version>5.7.1</pac4j.version>
<javaee-pac4j.version>7.1.0</javaee-pac4j.version>
<grouper.version>4.7.0</grouper.version>
</properties>

@@ -74,12 +74,12 @@
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>jee-pac4j</artifactId>
<version>${jee-pac4j.version}</version>
<artifactId>javaee-pac4j</artifactId>
<version>${javaee-pac4j.version}</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-saml-opensamlv3</artifactId>
<artifactId>pac4j-saml</artifactId>
<version>${pac4j.version}</version>
<exclusions>
<exclusion>
@@ -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,13 +11,17 @@
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;
import java.util.HashMap;
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" : {
@@ -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<ClaimAsUsernameOidcConfiguration> {
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();
}
}
@@ -6,17 +6,17 @@
import org.pac4j.oidc.config.OidcConfiguration;
import org.pac4j.oidc.profile.creator.OidcProfileCreator;

public class ClaimAsUsernameProfileCreator extends OidcProfileCreator<ClaimAsUsernameProfile> {
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);
}
}
@@ -2,7 +2,7 @@

import org.pac4j.oidc.profile.OidcProfileDefinition;

public class ClaimAsUsernameProfileDefinition extends OidcProfileDefinition<ClaimAsUsernameProfile> {
public class ClaimAsUsernameProfileDefinition extends OidcProfileDefinition {
public ClaimAsUsernameProfileDefinition(final String claimAsUsername) {
super();
setProfileFactory(x -> new ClaimAsUsernameProfile(claimAsUsername));
4 changes: 2 additions & 2 deletions 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:
22 changes: 13 additions & 9 deletions 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
#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
@@ -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<String, String> 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"));
}
}

0 comments on commit 7d40f06

Please sign in to comment.