[java-idp-plugin-metadatagen] branch main updated: JMETAGEN-5 - Metadata generation
Scott Cantor
cantor.2 at osu.edu
Tue Jul 18 16:10:41 UTC 2023
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-idp-plugin-metadatagen.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-metadatagen.git;a=commit;h=b1028d03350e161692c44102a1ff2889b7c5133f
The following commit(s) were added to refs/heads/main by this push:
new b1028d0 JMETAGEN-5 - Metadata generation
b1028d0 is described below
commit b1028d03350e161692c44102a1ff2889b7c5133f
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Jul 18 12:10:38 2023 -0400
JMETAGEN-5 - Metadata generation
https://shibboleth.atlassian.net/browse/JMETAGEN-5
Adjust module name.
Add layout template feature to auto-populate endpoints.
Bug fixes.
---
metadatagen-impl/pom.xml | 43 ++-
.../plugin/metadatagen/impl/MetadataGenCLI.java | 4 +
.../impl/MetadataGenCommandLineArguments.java | 378 +++++++++++++++------
.../metadatagen/conf/examples/shibmd-layout.xml | 51 +++
.../idp/plugin/metadatagen/conf/velocity.xml | 52 +--
.../idp/plugin/metadatagen/impl/module.properties | 29 +-
.../plugin/metadatagen/impl/MetadataGenTest.java | 18 +
.../idp/plugin/metadatagen/impl/batch.txt | 2 +-
8 files changed, 427 insertions(+), 150 deletions(-)
diff --git a/metadatagen-impl/pom.xml b/metadatagen-impl/pom.xml
index dc0b8a8..76f62c7 100644
--- a/metadatagen-impl/pom.xml
+++ b/metadatagen-impl/pom.xml
@@ -49,34 +49,33 @@
<dependency>
<groupId>${shib-shared.groupId}</groupId>
- <artifactId>shib-profile-impl</artifactId>
+ <artifactId>shib-metadata-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>${shib-shared.groupId}</groupId>
- <artifactId>shib-metadata-api</artifactId>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-core-api</artifactId>
<scope>provided</scope>
</dependency>
-
<dependency>
<groupId>${opensaml.groupId}</groupId>
- <artifactId>opensaml-saml-api</artifactId>
+ <artifactId>opensaml-core-impl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${opensaml.groupId}</groupId>
- <artifactId>opensaml-saml-impl</artifactId>
+ <artifactId>opensaml-saml-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${opensaml.groupId}</groupId>
- <artifactId>opensaml-security-api</artifactId>
+ <artifactId>opensaml-saml-impl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${opensaml.groupId}</groupId>
- <artifactId>opensaml-xmlsec-api</artifactId>
+ <artifactId>opensaml-security-api</artifactId>
<scope>provided</scope>
</dependency>
@@ -95,6 +94,22 @@
<artifactId>shib-velocity</artifactId>
<scope>provided</scope>
</dependency>
+
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-beans</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-context</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
@@ -102,6 +117,18 @@
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.beust</groupId>
+ <artifactId>jcommander</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
<!-- Test dependencies. -->
<dependency>
<groupId>${idp.groupId}</groupId>
diff --git a/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCLI.java b/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCLI.java
index 7cea70d..de767c0 100644
--- a/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCLI.java
+++ b/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCLI.java
@@ -201,6 +201,8 @@ public final class MetadataGenCLI extends AbstractIdPHomeAwareCommandLine<Metada
jc.parse(args.toArray(new String[args.size()]));
batchList.add(argObject);
args.clear();
+ } else if (line.isBlank()) {
+ continue;
} else {
addBatchArguments(args, line);
}
@@ -234,10 +236,12 @@ public final class MetadataGenCLI extends AbstractIdPHomeAwareCommandLine<Metada
if (batchList.isEmpty()) {
assert args != null;
+ args.loadLayoutObject();
generator.generate(args, sink);
} else {
sink.write("<md:EntitiesDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" xmlns:mdattr=\"urn:oasis:names:tc:SAML:metadata:attribute\" xmlns:mdui=\"urn:oasis:names:tc:SAML:metadata:ui\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:shibmd=\"urn:mace:shibboleth:metadata:1.0\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n\n");
for (final MetadataGenCommandLineArguments batchEntry : batchList) {
+ batchEntry.loadLayoutObject();
generator.generate(batchEntry, sink);
}
sink.write("\n</md:EntitiesDescriptor>\n");
diff --git a/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCommandLineArguments.java b/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCommandLineArguments.java
index 6716e88..59e7e7e 100644
--- a/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCommandLineArguments.java
+++ b/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCommandLineArguments.java
@@ -17,6 +17,9 @@
package net.shibboleth.idp.plugin.metadatagen.impl;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.PrintStream;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@@ -33,8 +36,12 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.core.xml.Namespace;
+import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.core.xml.util.XMLObjectSupport;
import org.opensaml.saml.common.SAMLObjectBuilder;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.ext.saml2mdui.Logo;
@@ -42,16 +49,23 @@ import org.opensaml.saml.metadata.generator.impl.ArtifactResolutionServiceConver
import org.opensaml.saml.metadata.generator.impl.AttributeServiceConverter;
import org.opensaml.saml.metadata.generator.impl.SingleSignOnServiceConverter;
import org.opensaml.saml.metadata.generator.impl.TemplateMetadataGeneratorParameters;
+import org.opensaml.saml.saml2.metadata.ArtifactResolutionService;
+import org.opensaml.saml.saml2.metadata.AssertionConsumerService;
import org.opensaml.saml.saml2.metadata.AttributeAuthorityDescriptor;
+import org.opensaml.saml.saml2.metadata.AttributeService;
import org.opensaml.saml.saml2.metadata.ContactPerson;
import org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration;
import org.opensaml.saml.saml2.metadata.EmailAddress;
import org.opensaml.saml.saml2.metadata.Endpoint;
+import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.GivenName;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml.saml2.metadata.IndexedEndpoint;
import org.opensaml.saml.saml2.metadata.NameIDFormat;
+import org.opensaml.saml.saml2.metadata.RoleDescriptor;
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.saml.saml2.metadata.SingleLogoutService;
+import org.opensaml.saml.saml2.metadata.SingleSignOnService;
import org.opensaml.saml.saml2.metadata.SurName;
import org.opensaml.security.x509.X509Support;
import org.slf4j.Logger;
@@ -71,6 +85,8 @@ import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.codec.EncodingException;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.xml.ParserPool;
+import net.shibboleth.shared.xml.XMLParserException;
/**
* Command line arguments for Metadata Generation.
@@ -84,6 +100,9 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
/** Logger. */
@Nullable private Logger log;
+ /** Endpoint layout template object */
+ @Nullable private XMLObject layoutObject;
+
/** Cached IdP role. */
@Nullable private IDPSSODescriptor idpRole;
@@ -201,7 +220,7 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
/** Tag assignment expressions. */
@Parameter(names = {"--entity-attr", "--tag"}, description="EntityAttribute tag=value expression")
- @Nonnull private List<String> tagAssignments = new ArrayList();
+ @Nonnull private List<String> tagAssignments = new ArrayList<>();
/** Omit namespaces from root element? */
@Parameter(names = {"--omit-namespaces"}, description="Omit namespace declarations (assumes surrounding document context)")
@@ -215,6 +234,14 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
@Parameter(names = {"--batch-file", "--batch"}, description="Path to a batch of commands to run")
@Nullable private String batchFile;
+ /** Endpoint layout template. */
+ @Parameter(names = {"--layout-file", "--layout"}, description="Path to an EntityDescriptor endpoint layout template")
+ @Nullable private String layoutFile;
+
+ /** Hostname(s) to plug into layout template. */
+ @Parameter(names = {"--hostname", "-h"}, description="Hostname(s) to plug into layout template")
+ @Nonnull private List<String> hostnames = new ArrayList<>();
+
/** {@inheritDoc} */
@Nullable public String getEntityID() {
return entityID;
@@ -233,28 +260,27 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
return getLanguageRanges().get(0).getRange();
}
- /** {@inheritDoc} */
- @Nullable public String getDisplayName() {
+ /** {@inheritDoc} */
+ @Nullable public String getDisplayName() {
return displayName;
}
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Nullable public String getDescription() {
return description;
}
/** {@inheritDoc} */
@Nullable public Logo getLogo() {
+
+ if (logoObject != null) {
+ return logoObject;
+ }
+
if (logoURL == null) {
return null;
}
-
- synchronized(this) {
- if (logoObject != null) {
- return logoObject;
- }
- }
-
+
final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
final SAMLObjectBuilder<Logo> builder =
@@ -270,10 +296,7 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
logo.setHeight(logoHeight);
}
- synchronized(this) {
- logoObject = logo;
- }
-
+ logoObject = logo;
return logo;
}
@@ -289,32 +312,70 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
/** {@inheritDoc} */
@Nullable public SPSSODescriptor getSPSSODescriptor() {
+
+ if (spRole != null) {
+ return spRole;
+ }
+
if (!sp) {
return null;
}
-
- synchronized(this) {
- if (spRole != null) {
- return spRole;
+
+ final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
+
+ SPSSODescriptor role = null;
+ if (layoutObject instanceof SPSSODescriptor downcast) {
+ role = downcast;
+ } else if (layoutObject instanceof EntityDescriptor entity) {
+ final List<RoleDescriptor> children = entity.getRoleDescriptors(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
+ if (!children.isEmpty()) {
+ role = (SPSSODescriptor) children.get(0);
+ } else {
+ throw new IllegalArgumentException("Layout object did not contain an SPSSODescriptor role.");
}
}
- final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
-
- final SAMLObjectBuilder<SPSSODescriptor> builder =
- (SAMLObjectBuilder<SPSSODescriptor>) bf.<SPSSODescriptor>ensureBuilder(
- SPSSODescriptor.DEFAULT_ELEMENT_NAME);
- final SPSSODescriptor role = builder.buildObject();
-
- final List<String> protocols = new ArrayList<>();
-
- role.getSingleLogoutServices().addAll(
- convertEndpoints(new SingleLogoutServiceConverter(), protocols, logoutServices));
- role.getArtifactResolutionServices().addAll(
- convertEndpoints(new ArtifactResolutionServiceConverter(), protocols, artifactServices));
- role.getAssertionConsumerServices().addAll(
- convertEndpoints(new AssertionConsumerServiceConverter(), protocols, acServices));
-
+ if (role != null) {
+ if (!hostnames.isEmpty()) {
+ // Process the various endpoints for hostname replacement.
+ try {
+ final Collection<SingleLogoutService> slo = convertEndpoints(role.getSingleLogoutServices(),
+ hostnames);
+ role.getSingleLogoutServices().clear();
+ role.getSingleLogoutServices().addAll(slo);
+
+ final Collection<ArtifactResolutionService> art = convertEndpoints(role.getArtifactResolutionServices(),
+ hostnames);
+ role.getArtifactResolutionServices().clear();
+ role.getArtifactResolutionServices().addAll(art);
+
+ final Collection<AssertionConsumerService> acs = convertEndpoints(role.getAssertionConsumerServices(),
+ hostnames);
+ role.getAssertionConsumerServices().clear();
+ role.getAssertionConsumerServices().addAll(acs);
+
+ } catch (final MarshallingException | UnmarshallingException e) {
+ throw new IllegalArgumentException("Error cloning endpoints for hostname replacement.", e);
+ }
+ }
+ } else {
+ final SAMLObjectBuilder<SPSSODescriptor> builder =
+ (SAMLObjectBuilder<SPSSODescriptor>) bf.<SPSSODescriptor>ensureBuilder(
+ SPSSODescriptor.DEFAULT_ELEMENT_NAME);
+ role = builder.buildObject();
+
+ final List<String> protocols = new ArrayList<>();
+
+ role.getSingleLogoutServices().addAll(
+ convertEndpoints(new SingleLogoutServiceConverter(), protocols, logoutServices));
+ role.getArtifactResolutionServices().addAll(
+ convertEndpoints(new ArtifactResolutionServiceConverter(), protocols, artifactServices));
+ role.getAssertionConsumerServices().addAll(
+ convertEndpoints(new AssertionConsumerServiceConverter(), protocols, acServices));
+
+ protocols.forEach(role::addSupportedProtocol);
+ }
+
int index = 1;
for (final IndexedEndpoint e : role.getArtifactResolutionServices()) {
e.setIndex(index++);
@@ -324,9 +385,7 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
for (final IndexedEndpoint e : role.getAssertionConsumerServices()) {
e.setIndex(index++);
}
-
- protocols.forEach(role::addSupportedProtocol);
-
+
if (wantAssertionsSigned) {
role.setWantAssertionsSigned(wantAssertionsSigned);
}
@@ -335,94 +394,145 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
final SAMLObjectBuilder<NameIDFormat> formatBuilder =
(SAMLObjectBuilder<NameIDFormat>) bf.<NameIDFormat>ensureBuilder(
NameIDFormat.DEFAULT_ELEMENT_NAME);
-
+
+ // Need to stash role in final variable for lambda.
+ final SPSSODescriptor roleCopy = role;
StringSupport.normalizeStringCollection(nameIDFormats).stream()
.forEach(format -> {
final NameIDFormat obj = formatBuilder.buildObject();
obj.setURI(format);
- role.getNameIDFormats().add(obj);
+ roleCopy.getNameIDFormats().add(obj);
});
}
- synchronized(this) {
- spRole = role;
- }
-
+ spRole = role;
return role;
}
/** {@inheritDoc} */
@Nullable public IDPSSODescriptor getIDPSSODescriptor() {
+
+ if (idpRole != null) {
+ return idpRole;
+ }
+
if (!idp) {
return null;
}
- synchronized(this) {
- if (idpRole != null) {
- return idpRole;
+ IDPSSODescriptor role = null;
+
+ if (layoutObject instanceof IDPSSODescriptor downcast) {
+ role = downcast;
+ } else if (layoutObject instanceof EntityDescriptor entity) {
+ final List<RoleDescriptor> children = entity.getRoleDescriptors(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
+ if (!children.isEmpty()) {
+ role = (IDPSSODescriptor) children.get(0);
}
}
- final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
-
- final SAMLObjectBuilder<IDPSSODescriptor> builder =
- (SAMLObjectBuilder<IDPSSODescriptor>) bf.<IDPSSODescriptor>ensureBuilder(
- IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
- final IDPSSODescriptor role = builder.buildObject();
-
- final List<String> protocols = new ArrayList<>();
-
- role.getSingleLogoutServices().addAll(
- convertEndpoints(new SingleLogoutServiceConverter(), protocols, logoutServices));
- role.getSingleSignOnServices().addAll(
- convertEndpoints(new SingleSignOnServiceConverter(), protocols, ssoServices));
- role.getArtifactResolutionServices().addAll(
- convertEndpoints(new ArtifactResolutionServiceConverter(), protocols, artifactServices));
+ if (role != null) {
+ if (!hostnames.isEmpty()) {
+ // Process the various endpoints for hostname replacement.
+ try {
+ final Collection<SingleLogoutService> slo = convertEndpoints(role.getSingleLogoutServices(),
+ CollectionSupport.singletonList(hostnames.get(0)));
+ role.getSingleLogoutServices().clear();
+ role.getSingleLogoutServices().addAll(slo);
+
+ final Collection<SingleSignOnService> sso = convertEndpoints(role.getSingleSignOnServices(),
+ CollectionSupport.singletonList(hostnames.get(0)));
+ role.getSingleSignOnServices().clear();
+ role.getSingleSignOnServices().addAll(sso);
+
+ final Collection<ArtifactResolutionService> art = convertEndpoints(role.getArtifactResolutionServices(),
+ CollectionSupport.singletonList(hostnames.get(0)));
+ role.getArtifactResolutionServices().clear();
+ role.getArtifactResolutionServices().addAll(art);
+ } catch (final MarshallingException | UnmarshallingException e) {
+ throw new IllegalArgumentException("Error cloning endpoints for hostname replacement.", e);
+ }
+ }
+ } else {
+ final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
+ final SAMLObjectBuilder<IDPSSODescriptor> builder =
+ (SAMLObjectBuilder<IDPSSODescriptor>) bf.<IDPSSODescriptor>ensureBuilder(
+ IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
+ role = builder.buildObject();
+
+ final List<String> protocols = new ArrayList<>();
+
+ role.getSingleLogoutServices().addAll(
+ convertEndpoints(new SingleLogoutServiceConverter(), protocols, logoutServices));
+ role.getSingleSignOnServices().addAll(
+ convertEndpoints(new SingleSignOnServiceConverter(), protocols, ssoServices));
+ role.getArtifactResolutionServices().addAll(
+ convertEndpoints(new ArtifactResolutionServiceConverter(), protocols, artifactServices));
+
+ protocols.forEach(role::addSupportedProtocol);
+ }
int index = 1;
for (final IndexedEndpoint e : role.getArtifactResolutionServices()) {
e.setIndex(index++);
}
-
- protocols.forEach(role::addSupportedProtocol);
- synchronized(this) {
- idpRole = role;
- }
-
+ idpRole = role;
return role;
}
/** {@inheritDoc} */
@Nullable public AttributeAuthorityDescriptor getAttributeAuthorityDescriptor() {
+
+ if (aaRole != null) {
+ return aaRole;
+ }
+
if (!aa) {
return null;
}
+
+ AttributeAuthorityDescriptor role = null;
- synchronized(this) {
- if (aaRole != null) {
- return aaRole;
+ if (layoutObject instanceof AttributeAuthorityDescriptor downcast) {
+ role = downcast;
+ } else if (layoutObject instanceof EntityDescriptor entity) {
+ final List<RoleDescriptor> children = entity.getRoleDescriptors(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
+ if (!children.isEmpty()) {
+ role = (AttributeAuthorityDescriptor) children.get(0);
+ } else {
+ throw new IllegalArgumentException("Layout object did not contain an AttributeAuthorityDescriptor role.");
}
}
-
- final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
-
- final SAMLObjectBuilder<AttributeAuthorityDescriptor> builder =
- (SAMLObjectBuilder<AttributeAuthorityDescriptor>) bf.<AttributeAuthorityDescriptor>ensureBuilder(
- AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
- final AttributeAuthorityDescriptor role = builder.buildObject();
-
- final List<String> protocols = new ArrayList<>();
-
- role.getAttributeServices().addAll(
- convertEndpoints(new AttributeServiceConverter(), protocols, attributeServices));
- protocols.forEach(role::addSupportedProtocol);
-
- synchronized(this) {
- aaRole = role;
+ if (role != null) {
+ if (!hostnames.isEmpty()) {
+ // Process the various endpoints for hostname replacement.
+ try {
+ final Collection<AttributeService> attr = convertEndpoints(role.getAttributeServices(),
+ CollectionSupport.singletonList(hostnames.get(0)));
+ role.getAttributeServices().clear();
+ role.getAttributeServices().addAll(attr);
+ } catch (final MarshallingException | UnmarshallingException e) {
+ throw new IllegalArgumentException("Error cloning endpoints for hostname replacement.", e);
+ }
+ }
+ } else {
+ final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
+ final SAMLObjectBuilder<AttributeAuthorityDescriptor> builder =
+ (SAMLObjectBuilder<AttributeAuthorityDescriptor>) bf.<AttributeAuthorityDescriptor>ensureBuilder(
+ AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
+ role = builder.buildObject();
+
+ final List<String> protocols = new ArrayList<>();
+
+ role.getAttributeServices().addAll(
+ convertEndpoints(new AttributeServiceConverter(), protocols, attributeServices));
+
+ protocols.forEach(role::addSupportedProtocol);
}
+ aaRole = role;
return role;
}
@@ -449,16 +559,15 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
/** {@inheritDoc} */
@Nonnull @Unmodifiable @NotLive public Map<String,Collection<String>> getTagAssignments() {
+
+ if (tagMap != null) {
+ return tagMap.asMap();
+ }
+
if (tagAssignments.isEmpty()) {
return CollectionSupport.emptyMap();
}
-
- synchronized(this) {
- if (tagMap != null) {
- return tagMap.asMap();
- }
- }
-
+
final Multimap<String,String> map = ArrayListMultimap.create();
for (final String assignment : tagAssignments) {
@@ -476,12 +585,8 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
}
}
- synchronized(this) {
- tagMap = map;
- }
-
- assert tagMap != null;
- return tagMap.asMap();
+ tagMap = map;
+ return map.asMap();
}
/** {@inheritDoc} */
@@ -573,14 +678,17 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
if (!sp && !idp && !aa) {
sp = true;
}
+
+ if (!hostnames.isEmpty() && layoutFile == null) {
+ throw new IllegalArgumentException("Hostname option requires a layout template.");
+ }
}
-
+
/** {@inheritDoc} */
@Override
public void printHelp(@Nonnull final PrintStream out) {
super.printHelp(out);
-
out.println(String.format(" %-20s %s", "--entityID, --client_id, --id", "The entityID (or client_id, etc.)"));
out.println(String.format(" %-20s %s", "--scope", "Scope extension value"));
@@ -619,10 +727,37 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
out.println(String.format(" %-20s %s", "--omit-namespaces", "Omit namespaces on root element."));
out.println(String.format(" %-20s %s", "--output, --out", "Output file path."));
+ out.println(String.format(" %-20s %s", "--layout-file, --layout", "Endpoint layout file path"));
+ out.println(String.format(" %-20s %s", "--hostname, -h", "Hostname(s) to plug into layout template"));
+
out.println(String.format(" %-20s %s", "--batch-file, --batch", "Batch file path."));
out.println();
}
+ /**
+ * Load the layout object if necessary.
+ *
+ * @throws IOException if unable to load from file
+ * @throws UnmarshallingException if unable to parse the file
+ * @throws XMLParserException if unable to parse the file
+ */
+ public void loadLayoutObject() throws IOException, XMLParserException, UnmarshallingException {
+ if (layoutObject != null || layoutFile == null) {
+ return;
+ }
+
+ final ParserPool parser = XMLObjectProviderRegistrySupport.getParserPool();
+ if (parser == null) {
+ throw new IllegalArgumentException("ParserPool unavailable, cannot accomodate layout file parameter.");
+ }
+ try (final FileInputStream in = new FileInputStream(layoutFile)) {
+ layoutObject = XMLObjectSupport.unmarshallFromInputStream(parser, in);
+ if (!(layoutObject instanceof EntityDescriptor || layoutObject instanceof RoleDescriptor)) {
+ throw new IllegalArgumentException("Unmarshalled layout object was not of a supported type");
+ }
+ }
+ }
+
/**
* Load a certificate from a file in a standard format and produce a base64-encoded DER string.
*
@@ -641,7 +776,42 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
}
/**
- * Convert the expressions into endpoints.
+ * If the input endpoints contain a location requiring hostname replacement, they're cloned,
+ * the location adjusted for each possible hostname, and the clones are returned.
+ *
+ * @param <T> type of endpoint
+ * @param inputs endpoint template(s)
+ * @param hostnames hostname(s) to replace with
+ *
+ * @return the final collection of endpoints to include
+ *
+ * @throws UnmarshallingException if cloning fails
+ * @throws MarshallingException if cloning fails
+ */
+ @Nonnull @Unmodifiable @NotLive private <T extends Endpoint> Collection<T> convertEndpoints(
+ @Nonnull final Collection<T> inputs, @Nonnull final Collection<String> hostnames)
+ throws MarshallingException, UnmarshallingException {
+
+ final Collection<T> accumulator = new ArrayList<>();
+
+ for (final T originalEndpoint : inputs) {
+ final String originalLocation = originalEndpoint.getLocation();
+ if (originalLocation != null && originalLocation.contains("${hostname}")) {
+ for (final String hostname : hostnames) {
+ final T cloned = XMLObjectSupport.cloneXMLObject(originalEndpoint);
+ cloned.setLocation(originalLocation.replace("${hostname}", hostname));
+ accumulator.add(cloned);
+ }
+ } else {
+ accumulator.add(originalEndpoint);
+ }
+ }
+
+ return accumulator;
+ }
+
+ /**
+ * Convert binding/location expressions into endpoints.
*
* @param <T> endpoint type
* @param converter endpoint converter
@@ -657,7 +827,7 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
.map(s -> converter.apply(s, protocols))
.collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableList())).get();
}
-
+
/**
* Parse string into a {@link ContactPerson} object.
*
diff --git a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/examples/shibmd-layout.xml b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/examples/shibmd-layout.xml
new file mode 100644
index 0000000..f44f0cb
--- /dev/null
+++ b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/examples/shibmd-layout.xml
@@ -0,0 +1,51 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
+
+ <!--
+ These include a full range of endpoints that likely exceeds what you will actually want to include.
+ In particular: The POST-SimpleSign and Artifact bindings are generally unused, and the SAML 1.1 endpoints
+ should usually be omitted along with the non-SAML 2.0 protocol values.
+ -->
+
+ <IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0">
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://${hostname}/idp/profile/SAML2/SOAP/ArtifactResolution" />
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding" Location="https://${hostname}/idp/profile/SAML1/SOAP/ArtifactResolution" />
+
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://${hostname}/idp/profile/SAML2/Redirect/SLO"/>
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://${hostname}/idp/profile/SAML2/POST/SLO"/>
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://${hostname}/idp/profile/SAML2/POST-SimpleSign/SLO"/>
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://${hostname}/idp/profile/SAML2/Artifact/SLO"/>
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://${hostname}/idp/profile/SAML2/SOAP/SLO"/>
+
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://${hostname}/idp/profile/SAML2/Redirect/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://${hostname}/idp/profile/SAML2/POST/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://${hostname}/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://${hostname}/idp/profile/SAML2/Artifact/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://${hostname}/idp/profile/SAML2/SOAP/ECP"/>
+ <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="https://${hostname}/idp/profile/Shibboleth/SSO"/>
+ </IDPSSODescriptor>
+
+ <AttributeAuthorityDescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol">
+ <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://${hostname}/idp/profile/SAML2/SOAP/AttributeQuery"/>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding" Location="https://${hostname}/idp/profile/SAML1/SOAP/AttributeQuery"/>
+ </AttributeAuthorityDescriptor>
+
+ <!-- These conform to a Shibboleth SP, *not* the IdP's proxying endpoint. -->
+
+ <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol">
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://${hostname}/Shibboleth.sso/Artifact/SOAP" />
+
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://${hostname}/Shibboleth.sso/SLO/SOAP"/>
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://${hostname}/Shibboleth.sso/SLO/Redirect"/>
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://${hostname}/Shibboleth.sso/SLO/POST"/>
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://${hostname}/Shibboleth.sso/SLO/POST-SimpleSign" />
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://${hostname}/Shibboleth.sso/SLO/Artifact"/>
+
+ <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://${hostname}/Shibboleth.sso/SAML2/POST" />
+ <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://${hostname}/Shibboleth.sso/SAML2/POST-SimpleSign" />
+ <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://${hostname}/Shibboleth.sso/SAML2/Artifact" />
+ <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="https://${hostname}/Shibboleth.sso/SAML2/ECP" />
+ <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post" Location="https://${hostname}/Shibboleth.sso/SAML/POST" />
+ <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:artifact-01" Location="https://${hostname}/Shibboleth.sso/SAML/Artifact" />
+ </SPSSODescriptor>
+
+</EntityDescriptor>
\ No newline at end of file
diff --git a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/velocity.xml b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/velocity.xml
index b4471ec..79af972 100644
--- a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/velocity.xml
+++ b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/velocity.xml
@@ -12,32 +12,34 @@
default-init-method="initialize"
default-destroy-method="destroy">
- <!-- Needed to allow for auto-wiring of descriptors and other components. -->
- <context:annotation-config />
-
- <bean id="shibboleth.PropertySourcesPlaceholderConfigurer"
- class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
- p:placeholderPrefix="%{" p:placeholderSuffix="}" />
-
- <!-- This bean MUST be called "conversionService" to work properly. -->
- <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
- <property name="converters">
- <set>
- <bean class="net.shibboleth.shared.spring.config.StringToIPRangeConverter" />
- <bean class="net.shibboleth.shared.spring.config.BooleanToPredicateConverter" />
- <bean class="net.shibboleth.shared.spring.config.StringBooleanToPredicateConverter" />
- <bean class="net.shibboleth.shared.spring.config.StringToResourceConverter" />
- <bean class="net.shibboleth.shared.spring.config.StringToDurationConverter" />
- <bean class="net.shibboleth.shared.spring.config.StringToPeriodConverter" />
- </set>
+ <bean id="shibboleth.OpenSAMLConfig" class="net.shibboleth.profile.spring.impl.OpenSAMLConfigBean"
+ p:parserPool-ref="shibboleth.DefaultParserPool" />
+
+ <bean id="shibboleth.DefaultParserPool" class="net.shibboleth.shared.xml.impl.BasicParserPool"
+ p:maxPoolSize="100"
+ p:coalescing="true"
+ p:ignoreComments="true"
+ p:ignoreElementContentWhitespace="true"
+ p:namespaceAware="true"
+ p:entityResolver-ref="shibboleth.ThrowingEntityResolver"
+ p:securityManagerAttributeName="http://apache.org/xml/properties/security-manager">
+ <property name="builderFeatures">
+ <map>
+ <entry key="http://apache.org/xml/features/disallow-doctype-decl">
+ <util:constant static-field="java.lang.Boolean.TRUE" />
+ </entry>
+ <entry key="http://apache.org/xml/features/validation/schema/normalized-value">
+ <util:constant static-field="java.lang.Boolean.FALSE" />
+ </entry>
+ <entry key="http://javax.xml.XMLConstants/feature/secure-processing">
+ <util:constant static-field="java.lang.Boolean.TRUE" />
+ </entry>
+ </map>
</property>
</bean>
-
- <!-- This BeanPostProcessor auto-sets identifiable beans with the bean name (if not already set). -->
- <bean id="shibboleth.IdentifiableBeanPostProcessor"
- class="net.shibboleth.shared.spring.config.IdentifiableBeanPostProcessor" />
-
- <bean id="shibboleth.OpenSAMLConfig" class="net.shibboleth.profile.spring.impl.OpenSAMLConfigBean" />
+
+ <bean id="shibboleth.ThrowingEntityResolver"
+ class="net.shibboleth.shared.xml.impl.ThrowingEntityResolver" />
<util:map id="shibboleth.DefaultVelocityEngineProperties">
<entry key="parser.space_gobbling" value="%{idp.velocity.space.gobbling:bc}" />
@@ -51,6 +53,6 @@
<bean id="shibboleth.VelocityEngine" class="net.shibboleth.shared.spring.velocity.VelocityEngineFactoryBean"
depends-on="shibboleth.OpenSAMLConfig"
- p:velocityPropertiesMap="#{getObject('shibboleth.VelocityEngineProperties') ?: getObject('shibboleth.DefaultVelocityEngineProperties')}" />
+ p:velocityPropertiesMap-ref="shibboleth.DefaultVelocityEngineProperties" />
</beans>
diff --git a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/impl/module.properties b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/impl/module.properties
index e8589a8..8c85e1e 100644
--- a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/impl/module.properties
+++ b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/impl/module.properties
@@ -1,18 +1,23 @@
# Example Properties defining an authentication module.
# Class to Module ID mappings
-net.shibboleth.idp.plugin.metadatagen.impl.MetadataGenModule = idp.metadatagen
+net.shibboleth.idp.plugin.metadatagen.impl.MetadataGenModule = idp.plugin.MetadataGen
# Module Owner
-idp.metadatagen.plugin = net.shibboleth.idp.plugin.metadatagen
+idp.plugin.MetadataGen.plugin = net.shibboleth.idp.plugin.metadatagen
-idp.metadatagen.name = Metadata Generator (Beta)
-idp.metadatagen.desc = A test Metadata Generator
-idp.metadatagen.url = /Metadatagen
-idp.metadatagen.1.src = /net/shibboleth/idp/plugin/metadatagen/bin/metadatagen.sh
-idp.metadatagen.1.dest = bin/metadatagen.sh
-idp.metadatagen.1.replace = true
-idp.metadatagen.1.exec = true
-idp.metadatagen.2.src = /net/shibboleth/idp/plugin/metadatagen/bin/metadatagen.bat
-idp.metadatagen.2.dest = bin/metadatagen.bat
-idp.metadatagen.2.replace = true
+idp.plugin.MetadataGen.name = Metadata Generator (Beta)
+idp.plugin.MetadataGen.desc = A test Metadata Generator
+idp.plugin.MetadataGen.url = /Metadatagen
+idp.plugin.MetadataGen.1.src = /net/shibboleth/idp/plugin/metadatagen/bin/metadatagen.sh
+idp.plugin.MetadataGen.1.dest = bin/metadatagen.sh
+idp.plugin.MetadataGen.1.replace = true
+idp.plugin.MetadataGen.1.exec = true
+idp.plugin.MetadataGen.1.windows = false
+idp.plugin.MetadataGen.2.src = /net/shibboleth/idp/plugin/metadatagen/bin/metadatagen.bat
+idp.plugin.MetadataGen.2.dest = bin/metadatagen.bat
+idp.plugin.MetadataGen.2.replace = true
+idp.plugin.MetadataGen.2.nonwindows = false
+idp.plugin.MetadataGen.3.src = /net/shibboleth/idp/plugin/metadatagen/conf/examples/shibmd-layout.xml
+idp.plugin.MetadataGen.3.dest = conf/examples/shibmd-layout.xml
+idp.plugin.MetadataGen.3.replace = true
diff --git a/metadatagen-impl/src/test/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenTest.java b/metadatagen-impl/src/test/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenTest.java
index 11d2965..6938de7 100644
--- a/metadatagen-impl/src/test/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenTest.java
+++ b/metadatagen-impl/src/test/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenTest.java
@@ -33,6 +33,7 @@ public class MetadataGenTest {
// src/test/resources/net/shibboleth/idp/plugin/metadatagen/impl/extra2
private final static String IDP_HOME = "/Users/scantor/Documents/shibboleth5/java-identity-provider/idp-conf-impl/src/main/resources/net/shibboleth/idp/module";
private final static String BATCH_PATH = "src/test/resources/net/shibboleth/idp/plugin/metadatagen/impl/batch.txt";
+ private final static String LAYOUT_PATH = "src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/examples/shibmd-layout.xml";
private final boolean enabled = false;
@Test(enabled = enabled) public void testSimple() {
@@ -80,4 +81,21 @@ public class MetadataGenTest {
AbstractCommandLine.RC_OK);
}
+ @Test(enabled = enabled) public void testLayoutSP() {
+ assertEquals(MetadataGenCLI.runMain(
+ new String[] {
+ "--home", IDP_HOME,
+ "--layout", LAYOUT_PATH,
+ "--entityID", "https://sp.example.org",
+ "--sp",
+ "--idp",
+ "--aa",
+ "--cert", "/Users/scantor/Desktop/webauth2.crt",
+ "-h", "sp.example.org",
+ "-h", "sp2.example.org",
+ "--tag", "urn:oasis:names:tc:SAML:profiles:subject-id:req=subject-id",
+ }),
+ AbstractCommandLine.RC_OK);
+ }
+
}
\ No newline at end of file
diff --git a/metadatagen-impl/src/test/resources/net/shibboleth/idp/plugin/metadatagen/impl/batch.txt b/metadatagen-impl/src/test/resources/net/shibboleth/idp/plugin/metadatagen/impl/batch.txt
index 8737aee..2b9a0a7 100644
--- a/metadatagen-impl/src/test/resources/net/shibboleth/idp/plugin/metadatagen/impl/batch.txt
+++ b/metadatagen-impl/src/test/resources/net/shibboleth/idp/plugin/metadatagen/impl/batch.txt
@@ -11,7 +11,7 @@
--aa
--entityID https://sp.example.org
--scope example.org
---nameid-format NameIDType.EMAIL
+--nameid-format urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
--cert /Users/scantor/Desktop/webauth2.crt
--sso Redirect1/idp.example.org/idp/profile/SAML/SSO/Redirect
--sso Redirect/idp.example.org/idp/profile/SAML2/SSO/Redirect
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list