[java-identity-provider] 01/01: IDP-1757 Command line tool to emit IdP Metadata

Rod Widdowson rdw at steadingsoftware.com
Tue May 18 14:21:50 UTC 2021


This is an automated email from the git hooks/post-receive script.

rdw pushed a commit to branch dev/IDP-1757
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=60942fe7286fa31c1242c36bf84f76a3dc5e1c42

commit 60942fe7286fa31c1242c36bf84f76a3dc5e1c42
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Tue Apr 27 10:53:59 2021 +0100

    IDP-1757 Command line tool to emit IdP Metadata
    
    https://issues.shibboleth.net/jira/browse/IDP-1757
---
 .../impl/MetadataGeneratorParametersImpl.java      |  20 +-
 .../installer/metadatagen/impl/MetadataGenCLI.java | 478 +++++++++++++++++++++
 .../impl/MetadataGenCommandLineArguments.java      | 234 ++++++++++
 .../installer/metadatagen/impl/package-info.java   |  21 +
 .../idp/installer/metadata-generator.xml           |   5 +-
 .../metadatagen/impl/MetadataGenTest.java          |  45 ++
 idp-parent/pom.xml                                 |   2 +-
 7 files changed, 794 insertions(+), 11 deletions(-)

diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/metadata/impl/MetadataGeneratorParametersImpl.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadata/impl/MetadataGeneratorParametersImpl.java
index ea4f3de36..4b3fc7fa8 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/metadata/impl/MetadataGeneratorParametersImpl.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadata/impl/MetadataGeneratorParametersImpl.java
@@ -51,7 +51,7 @@ public class MetadataGeneratorParametersImpl extends AbstractInitializableCompon
     /**
      * The file with the certificate that TLS uses to 'sign'.
      */
-    private File backChannelCert;
+    @Nullable private File backChannelCert;
 
     /**
      * The strings with the back channel cert in them (to allow for multiline output).
@@ -144,12 +144,16 @@ public class MetadataGeneratorParametersImpl extends AbstractInitializableCompon
      *
      * @param resource what to set.
      */
-    public void setBackchannelCertResource(final Resource resource) {
-        try {
-            backChannelCert = resource.getFile();
-        } catch (final IOException e) {
-            backChannelCert = null;
-        } 
+    public void setBackchannelCertResource(@Nullable final Resource resource) {
+        if (resource == null) {
+           backChannelCert = null;
+        } else {
+            try {
+                backChannelCert = resource.getFile();
+            } catch (final IOException e) {
+                backChannelCert = null;
+            }
+        }
     }
 
 
@@ -160,7 +164,7 @@ public class MetadataGeneratorParametersImpl extends AbstractInitializableCompon
      * @return the contents
      * @throws IOException if badness occurrs.
      */
-    private List<String> getCertificateContents(final File file) throws IOException {
+    private List<String> getCertificateContents(@Nullable final File file) throws IOException {
         if (null == file || !file.exists()) {
             return null;
         }
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenCLI.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenCLI.java
new file mode 100644
index 000000000..6f7896717
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenCLI.java
@@ -0,0 +1,478 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.installer.metadatagen.impl;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.xml.LangBearing;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.ext.reqattr.RequestedAttributes;
+import org.opensaml.saml.ext.saml2mdui.Description;
+import org.opensaml.saml.ext.saml2mdui.DisplayName;
+import org.opensaml.saml.ext.saml2mdui.Logo;
+import org.opensaml.saml.ext.saml2mdui.UIInfo;
+import org.opensaml.saml.saml2.core.Extensions;
+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.EntityDescriptor;
+import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
+import org.opensaml.saml.saml2.metadata.KeyDescriptor;
+import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.saml.saml2.metadata.SingleLogoutService;
+import org.opensaml.saml.saml2.metadata.SingleSignOnService;
+import org.opensaml.xmlsec.signature.KeyInfo;
+import org.opensaml.xmlsec.signature.X509Certificate;
+import org.opensaml.xmlsec.signature.X509Data;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import net.shibboleth.ext.spring.cli.AbstractCommandLine;
+import net.shibboleth.idp.Version;
+import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLine;
+import net.shibboleth.idp.installer.metadata.impl.MetadataGeneratorParametersImpl;
+import net.shibboleth.idp.saml.xmlobject.ExtensionsConstants;
+import net.shibboleth.idp.saml.xmlobject.Scope;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.xml.DOMTypeSupport;
+import net.shibboleth.utilities.java.support.xml.XMLConstants;
+
+/**
+ * Command Line to generate Metadata.
+ */
+public class MetadataGenCLI extends AbstractIdPHomeAwareCommandLine<MetadataGenCommandLineArguments> {
+
+    /** Class logger. */
+    @Nullable private Logger log;
+
+    /** Certificate and other property driven data. */
+    private MetadataGeneratorParametersImpl parameters;
+
+    /** Where we are outputting to? */
+    private PrintWriter output;
+
+    /** The processed arguments. */
+    private MetadataGenCommandLineArguments args;
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull protected synchronized Logger getLogger() {
+        if (log == null) {
+            log = LoggerFactory.getLogger(MetadataGenCLI.class);
+        }
+        return log;
+    }
+
+    /** {@inheritDoc} */
+    protected Class<MetadataGenCommandLineArguments> getArgumentClass() {
+        return MetadataGenCommandLineArguments.class;
+    }
+
+    /** {@inheritDoc} */
+    protected String getVersion() {
+        return Version.getVersion();
+    }
+
+    /** {@inheritDoc} */
+    @Nonnull @NonnullElements protected List<Resource> getAdditionalSpringResources() {
+        return List.of(
+               new ClassPathResource("net/shibboleth/idp/installer/metadata-generator.xml"));
+    }
+
+    /**
+     * Write out any <KeyDescriptor>Elements.
+     * @param outputBackChannel Do we output the back channel certificates?
+     */
+    private void outputKeyDescriptors(final boolean outputBackChannel) {
+        final List<List<String>> signing = new ArrayList<>(2);
+        if (outputBackChannel &&
+            parameters.getBackchannelCert() != null &&
+            !parameters.getBackchannelCert().isEmpty()) {
+            output.println("        <!--  First signing certificate is BackChannel, the Second is FrontChannel-->\n");
+            signing.add(parameters.getBackchannelCert());
+        }
+        if (parameters.getSigningCert() != null && !parameters.getSigningCert().isEmpty()) {
+            signing.add(parameters.getSigningCert());
+        }
+        outputKeyDescriptors(signing, "signing");
+        outputKeyDescriptors(Collections.singletonList(parameters.getEncryptionCert()), "encryption");
+        output.println();
+    }
+
+    /**
+     * Write out <KeyDescriptor>Elements. of a specific type
+     *
+     * @param certs the certificates
+     * @param use the type - signing or encryption
+     */
+    private void outputKeyDescriptors(@Nullable final List<List<String>> certs, @Nonnull @NotEmpty final String use) {
+
+        if (null == certs || certs.isEmpty()) {
+            return;
+        }
+        for (final List<String> cert : certs) {
+            output.format("        <%s use=\"%s\">\n",KeyDescriptor.DEFAULT_ELEMENT_LOCAL_NAME, use);
+            output.format("          <%s:%s>\n", SignatureConstants.XMLSIG_PREFIX, KeyInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.format("            <%s:%s>\n",
+                    SignatureConstants.XMLSIG_PREFIX, X509Data.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.format("              <%s:%s>\n",
+                    SignatureConstants.XMLSIG_PREFIX, X509Certificate.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.println(String.join("\n", cert));
+            output.format("              </%s:%s>\n",
+                    SignatureConstants.XMLSIG_PREFIX, X509Certificate.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.format("            </%s:%s>\n",
+                    SignatureConstants.XMLSIG_PREFIX, X509Data.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.format("          </%s:%s>\n",SignatureConstants.XMLSIG_PREFIX, KeyInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.format("        </%s>\n\n",KeyDescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+        }
+    }
+
+    /** Output Scope and MDUI.
+     * @param includeMDUI do we outpyut the MDUI
+     */
+    private void outputExtensions(final boolean includeMDUI) {
+        output.format("        <%s>\n", Extensions.DEFAULT_ELEMENT_LOCAL_NAME);
+        final String scope = StringSupport.trimOrNull(parameters.getScope());
+        if (scope != null) {
+            output.format("          <%s:%s regexp=\"false\">%s</%s:%s>\n",
+                    ExtensionsConstants.SHIB_MDEXT10_PREFIX, Scope.DEFAULT_ELEMENT_LOCAL_NAME,
+                    scope,
+                    ExtensionsConstants.SHIB_MDEXT10_PREFIX, Scope.DEFAULT_ELEMENT_LOCAL_NAME);
+        }
+
+        if (includeMDUI) {
+            output.format("<!--\n          Fill in the details for your IdP here\n\n          <%s:%s>\n",
+                    SAMLConstants.SAML20MDUI_PREFIX, UIInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.format("            <%s:%s %s:%s=\"en\">A Name for the IdP<%s:%s>\n",
+                    SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME,
+                    XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
+                    SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.format("            <%s:%s %s:%s=\"en\">A Descriptiom for the IdP<%s:%s>\n",
+                    SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME,
+                    XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
+                    SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.format("            <%s:%s height=\"80\" width=\"80\">https://%s/path/to/logo.png</%s:%s>\n",
+                    SAMLConstants.SAML20MDUI_PREFIX, Logo.DEFAULT_ELEMENT_LOCAL_NAME,
+                    parameters.getDnsName(),
+                    SAMLConstants.SAML20MDUI_PREFIX, Logo.DEFAULT_ELEMENT_LOCAL_NAME);
+            output.format("          </%s:%s>\n-->\n",
+                    SAMLConstants.SAML20MDUI_PREFIX, UIInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+        }
+        output.format("        </%s>\n\n", Extensions.DEFAULT_ELEMENT_LOCAL_NAME);
+    }
+
+    /** Output Logout end points. */
+    private void outputLogoutEndpoints() {
+        output.format("        <%s Binding=\"%s\""
+                + " Location=\"https://%s/idp/profile/SAML2/Redirect/SLO\"/>\b",
+                SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML2_REDIRECT_BINDING_URI,
+                parameters.getDnsName());
+        output.format("        <%s Binding=\"%s\""
+                + " Location=\"https://%s/idp/profile/SAML2/POST/SLO\"/>\b",
+                SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML2_POST_BINDING_URI,
+                parameters.getDnsName());
+        output.format("        <%s Binding=\"%s\""
+                + " Location=\"https://%s/idp/profile/SAML2/POST/SLO-SimpleSign\"/>\b",
+                SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML2_POST_SIMPLE_SIGN_BINDING_URI,
+                parameters.getDnsName());
+
+        if (args.isBackChannel()) {
+            output.format("        <%s Binding=\"%s\""
+                    + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/SLO\"/>\b",
+                    SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+                    SAMLConstants.SAML2_SOAP11_BINDING_URI,
+                    parameters.getDnsName());
+        }
+    }
+
+    /** Output Artefact Endpoints. */
+    private void outputArtefactEndpoints() {
+        if (args.isSaml1()) {
+            output.format("        <%s Binding=\"%s\""
+                    + " Location=\"https://%s:8443/idp/profile/SAML1/SOAP/ArtifactResolution\" index=\"1\"/>\n",
+                    ArtifactResolutionService.DEFAULT_ELEMENT_LOCAL_NAME,
+                    SAMLConstants.SAML1_SOAP11_BINDING_URI,
+                    parameters.getDnsName());
+        }
+        output.format("        <%s Binding=\"%s\""
+                + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/ArtifactResolution\" index=\"2\"/>\n",
+                ArtifactResolutionService.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML2_SOAP11_BINDING_URI,
+                parameters.getDnsName());
+    }
+
+    /** Output Artefact Endpoints. */
+    private void outputSSOEndpoints() {
+        if (args.isSaml1()) {
+            output.format("        <%s Binding=\"%s\""
+                    + " Location=\"https://%s/idp/profile/Shibboleth/SSO\"/>\n",
+                    SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
+                    "urn:mace:shibboleth:1.0:profiles:AuthnRequest",
+                    parameters.getDnsName());
+
+        }
+        output.format("        <%s Binding=\"%s\""
+                + " %s:%s=\"true\""
+                + " Location=\"https://%s/idp/profile/SAML2/POST/SSO\"/>\n",
+                SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML2_POST_BINDING_URI,
+                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME,
+                parameters.getDnsName());
+        output.format("        <%s Binding=\"%s\""
+                + " %s:%s=\"true\""
+                + " Location=\"https://%s/idp/profile/SAML2/POST-SimpleSign/SSO\"/>\n",
+                SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML2_POST_SIMPLE_SIGN_BINDING_URI,
+                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME,
+                parameters.getDnsName());
+        output.format("        <%s Binding=\"%s\""
+                + " %s:%s=\"true\""
+                + " Location=\"https://%s/idp/profile/SAML2/Redirect/SSO\"/>\n",
+                SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML2_REDIRECT_BINDING_URI,
+                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME,
+                parameters.getDnsName());
+    }
+
+    /**
+     * Write the <IDPSSODescriptor>.
+     */
+    private void outputIDPSSO() {
+        final List<String> protocols = new ArrayList<>(4);
+        if (!args.isSaml1() && !args.isSaml2()) {
+            return;
+        }
+        if (args.isSaml1()) {
+            protocols.add(SAMLConstants.SAML20P_NS);
+        }
+        if (args.isSaml2()) {
+            protocols.add(SAMLConstants.SAML20P_NS);
+            protocols.add(SAMLConstants.SAML11P_NS);
+            protocols.add("urn:mace:shibboleth:1.0");
+        }
+        output.format("    <%s protocolSupportEnumeration=\"%s\">\n",
+                IDPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
+                String.join(" ", protocols));
+
+        outputExtensions(true);
+
+        outputKeyDescriptors(true);
+
+        if (args.isArtefact()) {
+            outputArtefactEndpoints();
+        }
+
+        if (args.isLogout()) {
+            outputLogoutEndpoints();
+        }
+
+        outputSSOEndpoints();
+
+        output.format("    </%s>\n", IDPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+    }
+
+    /**
+     * Write the <AttributeAuthorityDescriptor>.*/
+    private void outputAtttributeAuthorityDescriptor() {
+        final List<String> protocols;
+
+        if (args.isSaml1()) {
+            if (args.isAttributeFetch()) {
+                // Both
+                protocols = Arrays.asList(SAMLConstants.SAML20P_NS, SAMLConstants.SAML11P_NS);
+            } else {
+                // SAML1 only
+                protocols = Collections.singletonList(SAMLConstants.SAML11P_NS);
+            }
+        } else if (args.isAttributeFetch()) {
+            // SAML2 only
+            protocols = Collections.singletonList(SAMLConstants.SAML20P_NS);
+        } else {
+            // Neither
+            return;
+        }
+
+        output.format("    <%s protocolSupportEnumeration=\"%s\">\n",
+                AttributeAuthorityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
+                String.join(" ", protocols));
+
+        outputExtensions(false);
+        outputKeyDescriptors(true);
+        if (args.isSaml1()) {
+            output.format("        <%s Binding=\"%s\""
+                    + " Location=\"https://%s:8443/idp/profile/SAML1/SOAP/AttributeQuery\"/>\n",
+                    AttributeService.DEFAULT_ELEMENT_LOCAL_NAME,
+                    SAMLConstants.SAML1_SOAP11_BINDING_URI,
+                    parameters.getDnsName());
+        }
+        if (args.isAttributeFetch()) {
+            output.format("        <%s Binding=\"%s\""
+                    + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/AttributeQuery\"/>\n",
+                    AttributeService.DEFAULT_ELEMENT_LOCAL_NAME,
+                    SAMLConstants.SAML2_SOAP11_BINDING_URI,
+                    parameters.getDnsName());
+        }
+        output.format("    </%s>\n", AttributeAuthorityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+    }
+
+    /**
+     * Write the <SPSSODescriptor>.
+     */
+    private void outputSPSSO() {
+        output.format("    <%s protocolSupportEnumeration=\"%s\">\n",
+                SPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML20P_NS);
+
+        outputKeyDescriptors(false);
+
+        output.format("        <%s Binding=\"%s\""
+                + " Location=\"https://%s/idp/profile/Authn/SAML2/POST/SSO\" index=\"0\"/>\n",
+                AssertionConsumerService.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML2_POST_BINDING_URI,
+                parameters.getDnsName());
+
+        output.format("    </%s>\n", SPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+    }
+
+
+    /** Output the metadata.
+     * @return true iff this worked.
+     */
+    private int outputMetadata() {
+        output.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        output.println("   <!--");
+        output.println("     This is example metadata only. Do *NOT* supply it as is without review,");
+        output.println("     and do *NOT* provide it in real time to your partners.");
+        output.println("     This metadata is not dynamic - run metadatagen again to recreate.");
+        output.format("     Created: %s\n   -->\n", Instant.now().toString());
+        output.format("<%s  xmlns=\"%s\" xmlns:%s=\"%s\"\n", EntityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
+                SAMLConstants.SAML20MD_NS,
+                SignatureConstants.XMLSIG_PREFIX, SignatureConstants.XMLSIG_NS);
+        output.format("      xmlns:%s=\"%s\" xmlns:%s=\"%s\"\n",
+                ExtensionsConstants.SHIB_MDEXT10_PREFIX, ExtensionsConstants.SHIB_MDEXT10_NS,
+                XMLConstants.XML_PREFIX, XMLConstants.XML_NS);
+        output.format("      xmlns:%s=\"%s\" xmlns:%s=\"%s\"\n",
+                SAMLConstants.SAML20MDUI_PREFIX, SAMLConstants.SAML20MDUI_NS,
+                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, SAMLConstants.SAML20PREQ_ATTR_NS);
+        output.format(" validUntil=\"%s\" entityID=\"%s\">\n\n",
+                DOMTypeSupport.instantToString(Instant.now()), parameters.getEntityID());
+        outputIDPSSO();
+        outputAtttributeAuthorityDescriptor();
+        outputSPSSO();
+        output.println("</EntityDescriptor>");
+        output.flush();
+        output.close();
+        return RC_OK;
+    }
+
+    /** Build the {@link MetadataGenCLI#parameters} object.
+     * @return true iff this worked.
+     */
+    private boolean populateParameters() {
+        try {
+            parameters = getApplicationContext().getBean(MetadataGeneratorParametersImpl.class);
+        } catch (final NoSuchBeanDefinitionException e) {
+            getLogger().error("Could not locate IdPConfiguration", 3);
+            return false;
+        }
+        return true;
+    }
+
+    /** Set up {@link MetadataGenCLI#output}.
+     * @return true iff this worked.
+     */
+    private boolean setupWriter() {
+        if (args.getOutput() == null) {
+            output = System.console().writer();
+        } else {
+            final File out = new File(args.getOutput());
+            try {
+                final FileOutputStream outStream = new FileOutputStream(out);
+                output = new PrintWriter(new BufferedOutputStream(outStream));
+            } catch (final IOException e) {
+                getLogger().error("Could not open {}", args.getOutput(), e);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int doRun(@Nonnull final MetadataGenCommandLineArguments arguments) {
+
+        args = arguments;
+        final int ret = super.doRun(args);
+        if (ret != RC_OK) {
+            return ret;
+        }
+        if (!setupWriter()) {
+            return RC_IO;
+        }
+        if (!populateParameters()) {
+            return RC_IO;
+        }
+        final int i = outputMetadata();
+        this.output.close();
+        return i;
+    }
+
+    /** Shim for CLI entry point: Allows the code to be run from a test.
+    *
+    * @return one of the predefines {@link AbstractCommandLine#RC_INIT},
+    * {@link AbstractCommandLine#RC_IO}, {@link AbstractCommandLine#RC_OK}
+    * or {@link AbstractCommandLine#RC_UNKNOWN}
+    *
+    * @param args arguments
+    */
+   public static int runMain(@Nonnull final String[] args) {
+       final MetadataGenCLI cli = new MetadataGenCLI();
+
+       return cli.run(args);
+   }
+
+   /**
+    * CLI entry point.
+    * @param args arguments
+    */
+   public static void main(@Nonnull final String[] args) {
+       System.exit(runMain(args));
+   }
+}
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenCommandLineArguments.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenCommandLineArguments.java
new file mode 100644
index 000000000..7719eb094
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenCommandLineArguments.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.shibboleth.idp.installer.metadatagen.impl;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.beust.jcommander.Parameter;
+
+import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLineArguments;
+
+/**
+ * Command line arguments for Metadata Generation.
+ */
+public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommandLineArguments {
+
+    /** Logger. */
+    private Logger log;
+
+    /** Do we output SAML2. */
+    @Parameter(names = { "+saml2", "+2", "+SAML2"} )
+    @Nullable private boolean saml2;
+
+    /** Do we NOT output SAML2. */
+    @Parameter(names = { "-saml2", "-2", "-SAML2"} )
+    @Nullable private boolean noSaml2;
+
+    /** Do we output SAM1.?*/
+    @Parameter(names = { "+saml1", "+1", "+SAML1"})
+    @Nullable private boolean saml1;
+
+    /** Do we output for an SP.*/
+    @Parameter(names = { "+samlSP", "+sp", "+SP", "+SAMLSP"})
+    @Nullable private boolean samlSP;
+
+    /** Do we output logout.*/
+    @Parameter(names = { "+logout", "+lo"})
+    @Nullable private boolean logout;
+
+    /** Do we output Artefact.*/
+    @Parameter(names = { "+artefact"})
+    @Nullable private boolean artefact;
+
+    /** Do we output for an Attribute Fetch.*/
+    @Parameter(names = { "+attributeFetch"})
+    @Nullable private boolean attributeFetch;
+
+    /** Certificate for (IdP) BackChannel (attribute and artefact).*/
+    @Parameter(names = { "--backChannel", "-bc"})
+    @Nullable private String backChannelPath;
+
+    /** DNS name (for back channel addresses). */
+    @Parameter(names = { "--DNSName", "-d"})
+    @Nonnull private String dnsName = "idp.example.org";
+
+    /** Output.*/
+    @Parameter(names = { "--output", "-o"})
+    @Nullable private String output;
+
+    /** Do we output SAML2 metadata?
+     * @return what.
+     */
+    public boolean isSaml2() {
+        return saml2;
+    }
+
+    /** Do we output SAML1 metadata?
+     * @return what.
+     */
+    public boolean isSaml1() {
+        return saml1;
+    }
+
+    /** Do we output SAML SP metadata.
+     * @return what.
+     */
+    public boolean isSamlSP() {
+        return samlSP;
+    }
+
+    /** Do we output Logout metadata?
+     * @return what.
+     */
+    public boolean isLogout() {
+        return logout;
+    }
+
+    /** Do we output Artefact metadata?
+     * @return what.
+     */
+    public boolean isArtefact() {
+        return artefact;
+    }
+
+    /** Do we output Attribute Fetch metadata?
+     * @return what.
+     */
+    public boolean isAttributeFetch() {
+        return attributeFetch;
+    }
+
+    /** Where to put the data.
+     * @return where
+     */
+    @Nullable public String getOutput() {
+        return output;
+    }
+
+    /** Returns the backChannelPath.
+     * @return the path.
+     */
+    @Nullable public String getBackChannelPath() {
+        return backChannelPath;
+    }
+
+    /** Are we outputting backchannel info?
+     * @return whether we are or not.
+     */
+    public boolean isBackChannel() {
+        return backChannelPath != null;
+    }
+
+    /** Returns the dnsName.
+     * @return the name.
+     */
+    @Nonnull public String getDnsName() {
+        return dnsName;
+    }
+
+    /** {@inheritDoc}
+     * We override this to add a property file of our own making for
+     * the backchannel (if needed) and dnsname.
+     * */
+    public List<String> getPropertyFiles() {
+        final List<String> parentProps = super.getPropertyFiles();
+        final Properties props = new Properties(2);
+        props.setProperty("idp.dnsname", getDnsName());
+        if (getBackChannelPath() != null) {
+            props.setProperty("idp.backchannel.cert", getBackChannelPath());
+        }
+        File file = null;
+        try {
+            file = File.createTempFile("MetadataGen", ".properties");
+            file.deleteOnExit();
+            try (final FileOutputStream out = new FileOutputStream(file)) {
+                props.store(out, "created");
+            }
+        } catch (final IOException e) {
+            getLog().error("Could not generate property file", e);
+        }
+        final List<String> result = new ArrayList<>(parentProps.size() + 1);
+        result.addAll(parentProps);
+        result.add(file.getAbsolutePath());
+        return result;
+    }
+
+    @Override
+    public synchronized Logger getLog() {
+        if (log == null) {
+            log = LoggerFactory.getLogger(MetadataGenCommandLineArguments.class);
+        }
+        return log;
+    }
+
+    // Checkstyle: CyclomaticComplexity OFF
+    @Override
+    public void validate() throws IllegalArgumentException {
+        if (!saml2 && noSaml2) {
+            saml2 = false;
+        } else {
+            saml2 = true;
+        }
+        if (artefact && backChannelPath == null) {
+            throw new IllegalArgumentException("Must specify --backChannel <path> if +artefact speificied");
+        }
+        if (attributeFetch && backChannelPath == null) {
+            throw new IllegalArgumentException("Must specify --backChannel <path> if +attributeFetch speificied");
+        }
+        if (backChannelPath != null && !attributeFetch && !artefact && !saml1) {
+            throw new IllegalArgumentException("--backChannel <path> requires +artefact" +
+                    " and/or +attributeFetch and/or +saml1");
+        }
+    }
+    // Checkstyle: CyclomaticComplcity ON
+
+    @Override
+    public void printHelp(final PrintStream out) {
+        super.printHelp(out);
+        out.println(String.format("  +%-20s %s", "SAML1, +1",
+                                  "Output SAML1 Metadata."));
+        out.println(String.format("  -%-20s %s", "SAML2, -2",
+                                  "do NOT Output SAML2 Metadata."));
+        out.println(String.format("  +%-20s %s", "SP, +SAMLSP",
+                                  "Output SAML2 SP Metadata."));
+        out.println(String.format("  +%-20s %s", "logout",
+                                  "Output Logout Metadata."));
+        out.println(String.format("  +%-20s %s", "artefact",
+                                  "Output SAML artefact Metadata (requires -bc),"));
+        out.println(String.format("  +%-20s %s", "attributeFetch",
+                                  "Output SAML attributeFetch Metadata  (requires -bc)."));
+        out.println(String.format("  -%-20s %s", "bc, --backchannel <Path>",
+                                  "Path to backchannel certificate"));
+        out.println(String.format("  -%-20s %s", "d, --DNSName name",
+                "DNS name to use in back channel addresses (default idp.example.org)"));
+        out.println(String.format("  --%-20s %s", "output, -o",
+                                  "Output location."));
+        out.println();
+    }
+}
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/package-info.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/package-info.java
new file mode 100644
index 000000000..db32ceda2
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Package to contain classes to do with the metadata generation command line.
+ */
+
+package net.shibboleth.idp.installer.metadatagen.impl;
diff --git a/idp-installer/src/main/resources/net/shibboleth/idp/installer/metadata-generator.xml b/idp-installer/src/main/resources/net/shibboleth/idp/installer/metadata-generator.xml
index 1fe209a62..6797e3b9b 100644
--- a/idp-installer/src/main/resources/net/shibboleth/idp/installer/metadata-generator.xml
+++ b/idp-installer/src/main/resources/net/shibboleth/idp/installer/metadata-generator.xml
@@ -19,7 +19,8 @@
 		class="net.shibboleth.idp.installer.metadata.impl.MetadataGeneratorParametersImpl"
 		p:encryptionCertResource="%{idp.encryption.cert}"
         p:signingCertResource="%{idp.signing.cert}"
-        p:backchannelCertResource="%{idp.backchannel.cert}"
+        p:backchannelCertResource="#{ environment.containsProperty('idp.backchannel.cert')  ? '%{idp.backchannel.cert:0}' : null}"
         p:dnsName="%{idp.dnsname}"
-		p:entityID="%{idp.entityID}" p:scope="%{idp.scope}" />
+		p:entityID="%{idp.entityID}"
+        p:scope="%{idp.scope}" />
 </beans>
\ No newline at end of file
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenTest.java b/idp-installer/src/test/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenTest.java
new file mode 100644
index 000000000..a98425f88
--- /dev/null
+++ b/idp-installer/src/test/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.installer.metadatagen.impl;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.ext.spring.cli.AbstractCommandLine;
+
+/**
+ *
+ */
+ at SuppressWarnings("javadoc")
+public class MetadataGenTest {
+    @Test(enabled = false) public void test() throws IOException {
+        assertEquals(MetadataGenCLI.runMain(
+                    new String[] { 
+                            "--home", "H:/Downloads/idp",
+                            "--verbose",
+                            "+saml1",
+                            "--backChannel", "H:/Downloads/idp/credentials/idp-backchannel.crt",
+                            "+attributeFetch",
+                            "--output", "C:/Users/rdw/Desktop/logs/Md.txt"}),
+                AbstractCommandLine.RC_OK);
+    }
+
+}
diff --git a/idp-parent/pom.xml b/idp-parent/pom.xml
index b60274710..38f1a7299 100644
--- a/idp-parent/pom.xml
+++ b/idp-parent/pom.xml
@@ -71,7 +71,7 @@
         <java-support.version>8.2.0</java-support.version>
         <opensaml.groupId>org.opensaml</opensaml.groupId>
         <opensaml.version>4.1.0</opensaml.version>
-        <spring-extensions.version>6.1.0</spring-extensions.version>
+        <spring-extensions.version>6.1.1-SNAPSHOT</spring-extensions.version>
         <checkstyle.configLocation>${project.basedir}/../idp-parent/resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
         <idp-parent.site.url>${shibboleth.site.url}java-identity-provider/${project.version}/</idp-parent.site.url>
         <idp-module.site.url>${idp-parent.site.url}${project.artifactId}</idp-module.site.url>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list