[java-identity-provider] 01/01: IDP-1757 Command line tool to emit IdP Metadata
Rod Widdowson
rdw at steadingsoftware.com
Mon May 31 15:10:24 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=9c073d3e55746d0808ca2d57bbf0ade0f796ca07
commit 9c073d3e55746d0808ca2d57bbf0ade0f796ca07
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Mon May 31 16:09:14 2021 +0100
IDP-1757 Command line tool to emit IdP Metadata
https://issues.shibboleth.net/jira/browse/IDP-1757
---
.../installer/metadatagen/impl/MetadataGenCLI.java | 553 +++++++++++++++++++++
.../impl/MetadataGenCommandLineArguments.java | 236 +++++++++
.../metadatagen/impl/MetadataGenParameters.java | 195 ++++++++
.../installer/metadatagen/impl/package-info.java | 21 +
.../idp/installer/metadatagen/metadatagen.xml | 23 +
.../metadatagen/impl/MetadataGenTest.java | 78 +++
6 files changed, 1106 insertions(+)
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..51f18d75d
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenCLI.java
@@ -0,0 +1,553 @@
+/*
+ * 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.env.Environment;
+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.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 MetadataGenParameters parameters;
+
+ /** Where we are outputting to? */
+ private PrintWriter output;
+
+ /** The processed arguments. */
+ private MetadataGenCommandLineArguments args;
+
+ /** The DnsName (cached because we need it often). */
+ private String dnsName;
+
+ /** {@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/metadatagen/metadatagen.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.format(" <!-- 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.format("\n");
+ }
+
+ /**
+ * 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.format("%s\n",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 the MDUI for one language.
+ * @param lang the language to emit
+ *
+ */
+ private void outputMDUI(final String lang) {
+ final Environment env = getApplicationContext().getEnvironment();
+ final String displayName = env.getProperty(MetadataGenCommandLineArguments.MDUI_DISPLAY_NAME_PREFIX+lang);
+ if (displayName != null) {
+ output.format(" <%s:%s %s:%s=\"%s\">%s<%s:%s>\n",
+ SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME,
+ XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
+ lang, displayName,
+ SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME);
+ }
+ final String description = env.getProperty(MetadataGenCommandLineArguments.MDUI_DESCRIPTION_PREFIX+lang);
+ if (description != null) {
+ output.format(" <%s:%s %s:%s=\"%s\">%s<%s:%s>\n",
+ SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME,
+ XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
+ lang, description,
+ SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME);
+ }
+ }
+
+ /** Output Scope and MDUI.
+ * @param includeMDUI do we output the MDUI
+ */
+ private void outputExtensions(final boolean includeMDUI) {
+ output.format(" <%s>\n", Extensions.DEFAULT_ELEMENT_LOCAL_NAME);
+ final Environment env = getApplicationContext().getEnvironment();
+ final String scope = StringSupport.trimOrNull(env.getProperty("idp.scope"));
+ 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(" <%s:%s>\n",
+ SAMLConstants.SAML20MDUI_PREFIX, UIInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+ final String mduiLangs = env.getProperty(MetadataGenCommandLineArguments.MDUI_LANGS_PROPERTY);
+ if (mduiLangs == null) {
+ output.format("\n<!--\n Fill in the details for your IdP here\n\n");
+ 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 Description 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);
+ } else {
+ for (final String lang:mduiLangs.split(" ")) {
+ outputMDUI(lang);
+ }
+ }
+ final String logoY = env.getProperty(MetadataGenCommandLineArguments.MDUI_LOGO_HEIGHT, "80");
+ final String logoX = env.getProperty(MetadataGenCommandLineArguments.MDUI_LOGO_WIDTH, "80");
+ final String logoPath = env.getProperty(MetadataGenCommandLineArguments.MDUI_LOGO_PATH, "/path/to/logo");
+ output.format(" <%s:%s height=\"%s\" width=\"%s\">https://%s%s</%s:%s>\n",
+ SAMLConstants.SAML20MDUI_PREFIX, Logo.DEFAULT_ELEMENT_LOCAL_NAME,
+ logoY, logoX, getDnsName(), logoPath,
+ SAMLConstants.SAML20MDUI_PREFIX, Logo.DEFAULT_ELEMENT_LOCAL_NAME);
+ if (mduiLangs == null) {
+ output.format("-->\n\n");
+ }
+ output.format(" </%s:%s>\n", SAMLConstants.SAML20MDUI_PREFIX, UIInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+ }
+ output.format("\n </%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\"/>\n",
+ SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+ SAMLConstants.SAML2_REDIRECT_BINDING_URI,
+ getDnsName());
+ output.format(" <%s Binding=\"%s\""
+ + " Location=\"https://%s/idp/profile/SAML2/POST/SLO\"/>\n",
+ SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+ SAMLConstants.SAML2_POST_BINDING_URI,
+ getDnsName());
+ output.format(" <%s Binding=\"%s\""
+ + " Location=\"https://%s/idp/profile/SAML2/POST/SLO-SimpleSign\"/>\n",
+ SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+ SAMLConstants.SAML2_POST_SIMPLE_SIGN_BINDING_URI,
+ getDnsName());
+
+ if (parameters.getBackchannelCert() != null && !parameters.getBackchannelCert().isEmpty()) {
+ output.format(" <%s Binding=\"%s\""
+ + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/SLO\"/>\n",
+ SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+ SAMLConstants.SAML2_SOAP11_BINDING_URI,
+ getDnsName());
+ }
+ }
+
+ /** Output Artifact Endpoints. */
+ private void outputArtifactEndpoints() {
+ 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,
+ 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,
+ getDnsName());
+ }
+
+ /** Output SSO 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",
+ 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,
+ 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,
+ 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,
+ 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.isArtifact()) {
+ outputArtifactEndpoints();
+ }
+
+ 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,
+ 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,
+ getDnsName());
+ }
+ output.format(" </%s>\n", AttributeAuthorityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+ }
+
+ /**
+ * Write the <SPSSODescriptor>.
+ */
+ private void outputSPSSO() {
+ if (!args.isSamlSP()) {
+ return;
+ }
+ 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,
+ getDnsName());
+
+ output.format(" </%s>\n", SPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+ }
+
+
+ /** Output the metadata.
+ * @return true iff this worked.
+ */
+ private int outputMetadata() {
+ output.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ output.format(" <!--\n");
+ output.format(" This is example metadata only. Do *NOT* supply it as is without review,\n");
+ output.format(" and do *NOT* provide it in real time to your partners.\n");
+ output.format(" This metadata is not dynamic - run metadatagen again to recreate.\n");
+ 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()),
+ getApplicationContext().getEnvironment().getProperty("idp.entityID", "idp.example.org"));
+ outputIDPSSO();
+ outputAtttributeAuthorityDescriptor();
+ outputSPSSO();
+ output.format("</EntityDescriptor>\n");
+ output.flush();
+ output.close();
+ return RC_OK;
+ }
+
+ /** Lookup the dns name with a default and cache it.
+ * @return the dns name
+ */
+ @Nonnull String getDnsName() {
+ if (dnsName == null) {
+ dnsName = getApplicationContext().
+ getEnvironment().
+ getProperty(MetadataGenCommandLineArguments.DNS_NAME_PROPERTY, "idp.example.org");
+ }
+ return dnsName;
+ }
+
+ /** Build the {@link MetadataGenCLI#parameters} object.
+ * @return true iff this worked and if everything needed was there.
+ */
+ // Checkstyle: CyclomaticComplexity OFF
+ private boolean populateParameters() {
+ try {
+ parameters = getApplicationContext().getBean(MetadataGenParameters.class);
+ } catch (final NoSuchBeanDefinitionException e) {
+ getLogger().error("Could not locate IdPConfiguration");
+ return false;
+ }
+ final boolean hasBackChannel = parameters.getBackchannelCert() != null
+ && !parameters.getBackchannelCert().isEmpty();
+ boolean worked = true;
+ if (args.isArtifact() && !hasBackChannel) {
+ getLogger().error("Must specify --backChannel <path> if +artifact speificied");
+ worked = false;
+ }
+ if (args.isAttributeFetch() && !hasBackChannel) {
+ getLogger().error("Must specify --backChannel <path> if +attributeFetch speificied");
+ worked = false;
+ }
+ if (hasBackChannel && !args.isAttributeFetch() && !args.isArtifact() && !args.isSaml1()) {
+ getLogger().error("--backChannel <path> requires +artifact and/or +attributeFetch and/or +saml1");
+ worked = false;
+ }
+ return worked;
+ }
+ // Checkstyle: CyclomaticComplexity ON
+
+
+ /** 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..58f6afeba
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenCommandLineArguments.java
@@ -0,0 +1,236 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.Properties;
+
+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 {
+
+ /** Property name for the back channel certificate. */
+ public static final String BACKCHANNEL_PROPERTY = "idp.metadata.backchannel.cert";
+
+ /** Property name for the back channel certificate. */
+ public static final String DNS_NAME_PROPERTY = "idp.metadata.dnsname";
+
+ /** Property name for the MDUI languages. */
+ public static final String MDUI_LANGS_PROPERTY = "idp.metadata.idpsso.mdui.langs";
+
+ /** Property prefix for DisplayName. */
+ public static final String MDUI_DISPLAY_NAME_PREFIX = "idp.metadata.idpsso.mdui.displayname.";
+
+ /** Property prefix for Description. */
+ public static final String MDUI_DESCRIPTION_PREFIX = "idp.metadata.idpsso.mdui.description.";
+
+ /** Property for logo y. */
+ public static final String MDUI_LOGO_HEIGHT = "idp.metadata.idpsso.mdui.logo.height";
+
+ /** Property for logo x. */
+ public static final String MDUI_LOGO_WIDTH = "idp.metadata.idpsso.mdui.logo.width";
+
+ /** Property for logo path. */
+ public static final String MDUI_LOGO_PATH = "idp.metadata.idpsso.mdui.logo.path";
+
+ /** 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 Artifact.*/
+ @Parameter(names = { "+artifact", "+artefact"})
+ @Nullable private boolean artifact;
+
+ /** Do we output for an Attribute Fetch.*/
+ @Parameter(names = { "+attributeFetch"})
+ @Nullable private boolean attributeFetch;
+
+ /** Certificate for (IdP) BackChannel (attribute, artifact, logout).*/
+ @Parameter(names = { "--backChannel", "-bc"})
+ @Nullable private String backChannelPath;
+
+ /** DNS name (for back channel addresses). */
+ @Parameter(names = { "--DNSName", "-d"})
+ @Nullable private String dnsName;
+
+ /** 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 Artifact metadata?
+ * @return what.
+ */
+ public boolean isArtifact() {
+ return artifact;
+ }
+
+ /** 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;
+ }
+
+ /** {@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> fromCmdline = super.getPropertyFiles();
+ if (dnsName == null && backChannelPath == null) {
+ return fromCmdline;
+ }
+
+ final Properties props = new Properties(2);
+ if (dnsName != null) {
+ props.setProperty(DNS_NAME_PROPERTY, dnsName);
+ }
+ if (backChannelPath != null) {
+ props.setProperty(BACKCHANNEL_PROPERTY, backChannelPath);
+ }
+
+ 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);
+ }
+ if (fromCmdline.isEmpty()) {
+ return Collections.singletonList(file.getAbsolutePath());
+ }
+ final List<String> result = new ArrayList<>(fromCmdline.size() + 1);
+ result.addAll(fromCmdline);
+ result.add(file.getAbsolutePath());
+ return result;
+ }
+
+ @Override
+ public synchronized Logger getLog() {
+ if (log == null) {
+ log = LoggerFactory.getLogger(MetadataGenCommandLineArguments.class);
+ }
+ return log;
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ if (!saml2 && noSaml2) {
+ saml2 = false;
+ } else {
+ saml2 = true;
+ }
+ }
+ // 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", "+artifact",
+ "Output SAML artifact 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/MetadataGenParameters.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenParameters.java
new file mode 100644
index 000000000..9b8dff50e
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenParameters.java
@@ -0,0 +1,195 @@
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.springframework.core.io.Resource;
+
+import net.shibboleth.idp.installer.metadata.impl.MetadataGeneratorParametersImpl;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * A simple bean for processing certificates in an easy to digest manner.
+ * This is a cut down version of {@link MetadataGeneratorParametersImpl}.
+ */
+public class MetadataGenParameters extends AbstractInitializableComponent {
+
+ /**
+ * The file with the certificate the IDP uses to encrypt.
+ */
+ private File encryptionCert;
+
+ /**
+ * The strings with the encryption cert in them (to allow for multiline output).
+ */
+ private List<String> encryptionCerts;
+
+ /**
+ * The file with the certificate that TLS uses to 'sign'.
+ */
+ @Nullable private File backChannelCert;
+
+ /**
+ * The strings with the back channel cert in them (to allow for multiline output).
+ */
+ private List<String> backChannelCerts;
+
+ /**
+ * The file with the certificate the IDP uses to sign.
+ */
+ private File signingCert;
+
+ /**
+ * The strings with the signing certs in them (to allow for multiline output).
+ */
+ private List<String> signingCerts;
+
+ /** {@inheritDoc} */
+ protected void doInitialize() throws ComponentInitializationException {
+ try {
+ encryptionCerts = getCertificateContents(encryptionCert);
+ signingCerts = getCertificateContents(signingCert);
+ backChannelCerts = getCertificateContents(backChannelCert);
+ } catch (final IOException e) {
+ throw new ComponentInitializationException(e);
+ }
+ }
+
+ /**
+ * Get the (mutli-line) string representations of the encryption certs.
+ *
+ * @return Returns the encryption cert or null if none available.
+ */
+ @Nullable public List<String> getEncryptionCert() {
+ return encryptionCerts;
+ }
+
+ /**
+ * Set the encryption Certificate file.
+ *
+ * @param resource what to set.
+ */
+ public void setEncryptionCertResource(final Resource resource) {
+
+ try {
+ encryptionCert = resource.getFile();
+ } catch (final IOException e) {
+ encryptionCert = null;
+ }
+ }
+
+ /**
+ * Get the (mutli-line) string representation of the signing cert.
+ *
+ * @return Returns the signing cert or null if none available.
+ */
+ @Nullable public List<String> getSigningCert() {
+ return signingCerts;
+ }
+
+ /**
+ * Set the signing Certificate file.
+ *
+ * @param resource what to set.
+ */
+ public void setSigningCertResource(final Resource resource) {
+ try {
+ signingCert = resource.getFile();
+ } catch (final IOException e) {
+ signingCert = null;
+ }
+ }
+
+ /**
+ * Get the (mutli-line)string representation of the back channel cert.
+ *
+ * @return Returns the back channel cert or null if non available.
+ */
+ @Nullable public List<String> getBackchannelCert() {
+ return backChannelCerts;
+ }
+
+ /**
+ * Set the Backchannel Certificate file.
+ *
+ * @param file what to set.
+ */
+ public void setBackchannelCert(final File file) {
+ backChannelCert = file;
+ }
+
+ /**
+ * Set the Backchannel Certificate.
+ *
+ * @param resource what to set.
+ */
+ public void setBackchannelCertResource(@Nullable final Resource resource) {
+ if (resource == null) {
+ backChannelCert = null;
+ } else {
+ try {
+ backChannelCert = resource.getFile();
+ } catch (final IOException e) {
+ backChannelCert = null;
+ }
+ }
+ }
+
+ /**
+ * Open the file and return the contents and a list of lines.
+ *
+ * @param file the file
+ * @return the contents
+ * @throws IOException if we have issues with reading an existing file.
+ */
+ private List<String> getCertificateContents(@Nullable final File file) throws IOException {
+ if (null == file || !file.exists()) {
+ return null;
+ }
+
+ try (final FileReader fr = new FileReader(file);
+ final BufferedReader reader = new BufferedReader(fr)) {
+ final List<String> output = new ArrayList<>();
+ String s = reader.readLine();
+ while (s != null) {
+ output.add(s);
+ s = reader.readLine();
+ }
+ if ((output.size() > 0) && output.get(0).startsWith("----")) {
+ output.remove(0);
+ }
+ final int last = output.size() - 1;
+ if (last <= 0) {
+ return null;
+ }
+ if (output.get(last).startsWith("----")) {
+ output.remove(last);
+ }
+ return output;
+ }
+ }
+}
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/metadatagen/metadatagen.xml b/idp-installer/src/main/resources/net/shibboleth/idp/installer/metadatagen/metadatagen.xml
new file mode 100644
index 000000000..b7080ec54
--- /dev/null
+++ b/idp-installer/src/main/resources/net/shibboleth/idp/installer/metadatagen/metadatagen.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <bean
+ class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+ <context:property-placeholder />
+
+ <bean id="IdPConfiguration"
+ class="net.shibboleth.idp.installer.metadatagen.impl.MetadataGenParameters"
+ p:encryptionCertResource="%{idp.encryption.cert}"
+ p:signingCertResource="%{idp.signing.cert}"
+ p:backchannelCertResource="#{ environment.containsProperty('idp.metadata.backchannel.cert') ? '%{idp.metadata.backchannel.cert:0}' : null}" />
+</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..1f3047704
--- /dev/null
+++ b/idp-installer/src/test/java/net/shibboleth/idp/installer/metadatagen/impl/MetadataGenTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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",
+ "--DNSName", "my.idp.example.org",
+ "+saml1",
+ "--backChannel", "H:/Downloads/idp/credentials/idp-backchannel.crt",
+ "+attributeFetch","+artifact", "+logout",
+ "--output", "C:/Users/rdw/Desktop/logs/Mdbc.txt"}),
+ AbstractCommandLine.RC_OK);
+ }
+
+ @Test(enabled = false) public void testProps() throws IOException {
+ assertEquals(MetadataGenCLI.runMain(
+ new String[] {
+ "--home", "H:/Downloads/idp",
+ "--verbose",
+ "+saml1",
+ "--propertyFiles", "c:/Users/rdw/Desktop/logs/idp.extraprops,c:/Users/rdw/Desktop/logs/idp.extraprops2",
+ "+attributeFetch","+artifact", "+logout",
+ "--output", "C:/Users/rdw/Desktop/logs/MdbcProps.txt"}),
+ AbstractCommandLine.RC_OK);
+ }
+
+
+ @Test(enabled = false) public void noBc() throws IOException {
+ assertEquals(MetadataGenCLI.runMain(
+ new String[] {
+ "--home", "H:/Downloads/idp",
+ "--verbose", "+logout",
+ "+saml1",
+ "--output", "C:/Users/rdw/Desktop/logs/Md.txt"}),
+ AbstractCommandLine.RC_OK);
+ }
+
+
+ @Test(enabled = true) public void help() throws IOException {
+ assertEquals(MetadataGenCLI.runMain(
+ new String[] {
+ "--home", "H:/Downloads/idp",
+ "--help"}),
+ AbstractCommandLine.RC_OK);
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list