Java tutorial
/* * 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 edu.internet2.middleware.shibboleth.common.attribute; import jargs.gnu.CmdLineParser; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.opensaml.Configuration; import org.opensaml.common.SAMLObject; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.metadata.SPSSODescriptor; import org.opensaml.saml2.metadata.provider.MetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.util.resource.FilesystemResource; import org.opensaml.util.resource.Resource; import org.opensaml.util.resource.ResourceException; import org.opensaml.xml.io.Marshaller; import org.opensaml.xml.io.MarshallingException; import org.opensaml.xml.util.XMLHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.w3c.dom.Element; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.status.ErrorStatus; import ch.qos.logback.core.status.InfoStatus; import ch.qos.logback.core.status.StatusManager; import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML1AttributeAuthority; import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority; import edu.internet2.middleware.shibboleth.common.config.SpringConfigurationUtils; import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext; import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager; /** * A command line tool that allows individuals to invoke an attribute authority and inspect the resultant attribute * statement. * * This tool expects to retrieve the {@link MetadataProvider} it uses under the bean name SAMLMetadataProvider, a * {@link SAML1AttributeAuthority} under the bean name SAML1AttributeAuthority, and a {@link SAML2AttributeAuthority} * under the bean name SAML2AttributeAuthority. */ public class AttributeAuthorityCLI { /** Class logger. */ private static Logger log = LoggerFactory.getLogger(AttributeAuthorityCLI.class); /** List of assumed Spring configuration files used with the AACLI. */ private static String[] aacliConfigs = { "internal.xml", "service.xml", }; /** Loaded SAML 1 Attribute Authority. */ private static SAML1AttributeAuthority saml1AA; /** Loaded SAML 2 Attribute Authority. */ private static SAML2AttributeAuthority saml2AA; /** Protocol String. */ private static String protocol; /** * Runs this application. Help message prints if no arguments are given or if the "help" argument is given. * * @param args command line arguments * * @throws Exception thrown if there is a problem during program execution */ public static void main(String[] args) throws Exception { CmdLineParser parser = parseCommandArguments(args); ApplicationContext appCtx = loadConfigurations( (String) parser.getOptionValue(CLIParserBuilder.CONFIG_DIR_ARG), (String) parser.getOptionValue(CLIParserBuilder.SPRING_EXTS_ARG)); saml1AA = (SAML1AttributeAuthority) appCtx.getBean("shibboleth.SAML1AttributeAuthority"); saml2AA = (SAML2AttributeAuthority) appCtx.getBean("shibboleth.SAML2AttributeAuthority"); SAMLObject attributeStatement; Boolean saml1 = (Boolean) parser.getOptionValue(CLIParserBuilder.SAML1_ARG, Boolean.FALSE); if (saml1.booleanValue()) { protocol = SAMLConstants.SAML11P_NS; attributeStatement = performSAML1AttributeResolution(parser, appCtx); } else { protocol = SAMLConstants.SAML20P_NS; attributeStatement = performSAML2AttributeResolution(parser, appCtx); } printAttributeStatement(attributeStatement); } /** * Parses the command line arguments * * @param args command line arguments * * @return parsed command line arguments * * @throws Exception thrown if the underlying libraries could not be initialized */ private static CmdLineParser parseCommandArguments(String[] args) throws Exception { if (args.length < 2) { printHelp(System.out); System.out.flush(); System.exit(0); } CmdLineParser parser = CLIParserBuilder.buildParser(); try { parser.parse(args); } catch (CmdLineParser.OptionException e) { errorAndExit(e.getMessage(), e); } Boolean helpEnabled = (Boolean) parser.getOptionValue(CLIParserBuilder.HELP_ARG); if (helpEnabled != null) { printHelp(System.out); System.out.flush(); System.exit(0); } return parser; } /** * Loads the configuration files into a Spring application context. * * @param configDir directory containing spring configuration files * @param springExts colon-separated list of spring extension files * * @return loaded application context * * @throws java.io.IOException throw if there is an error loading the configuration files * @throws ResourceException if there is an error loading the configuration files */ private static ApplicationContext loadConfigurations(String configDir, String springExts) throws IOException, ResourceException { File configDirectory; if (configDir != null) { configDirectory = new File(configDir); } else { configDirectory = new File(System.getenv("IDP_HOME") + "/conf"); } if (!configDirectory.exists() || !configDirectory.isDirectory() || !configDirectory.canRead()) { errorAndExit("Configuration directory " + configDir + " does not exist, is not a directory, or is not readable", null); } loadLoggingConfiguration(configDirectory.getAbsolutePath()); File config; List<String> configFiles = new ArrayList<String>(); List<Resource> configs = new ArrayList<Resource>(); // Add built-in files. for (String i : aacliConfigs) { configFiles.add(i); } // Add extensions, if any. if (springExts != null && !springExts.isEmpty()) { String[] extFiles = springExts.split(":"); for (String extFile : extFiles) { configFiles.add(extFile); } } for (String cfile : configFiles) { config = new File(configDirectory.getPath() + File.separator + cfile); if (config.isDirectory() || !config.canRead()) { errorAndExit( "Configuration file " + config.getAbsolutePath() + " is a directory or is not readable", null); } configs.add(new FilesystemResource(config.getPath())); } GenericApplicationContext gContext = new GenericApplicationContext(); SpringConfigurationUtils.populateRegistry(gContext, configs); gContext.refresh(); return gContext; } /** * Loads the logging configuration. * * @param configDir IdP configuration directory */ private static void loadLoggingConfiguration(String configDir) { String loggingConfig = configDir + File.separator + "logging.xml"; LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); StatusManager statusManager = loggerContext.getStatusManager(); statusManager.add(new InfoStatus("Loading logging configuration file: " + loggingConfig, null)); try { // loggerContext.stop(); loggerContext.reset(); JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(loggerContext); configurator.doConfigure(new FileInputStream(loggingConfig)); loggerContext.start(); } catch (JoranException e) { statusManager.add(new ErrorStatus("Error loading logging configuration file: " + configDir, null, e)); } catch (IOException e) { statusManager.add(new ErrorStatus("Error loading logging configuration file: " + configDir, null, e)); } } /** * Constructs a SAML 1 attribute statement with the retrieved and filtered attributes. * * @param parser command line arguments * @param appCtx spring application context with loaded attribute authority * * @return SAML 1 attribute statement */ private static SAMLObject performSAML1AttributeResolution(CmdLineParser parser, ApplicationContext appCtx) { BaseSAMLProfileRequestContext requestCtx = buildAttributeRequestContext(parser, appCtx); try { Map<String, BaseAttribute> attributes = saml1AA.getAttributes(requestCtx); return saml1AA.buildAttributeStatement(null, attributes.values()); } catch (AttributeRequestException e) { errorAndExit("Error encountered during attribute resolution and filtering", e); } return null; } /** * Constructs a SAML 2 attribute statement with the retrieved and filtered attributes. * * @param parser command line arguments * @param appCtx spring application context with loaded attribute authority * * @return SAML 2 attribute statement */ private static SAMLObject performSAML2AttributeResolution(CmdLineParser parser, ApplicationContext appCtx) { BaseSAMLProfileRequestContext requestCtx = buildAttributeRequestContext(parser, appCtx); try { Map<String, BaseAttribute> attributes = saml2AA.getAttributes(requestCtx); return saml2AA.buildAttributeStatement(null, attributes.values()); } catch (AttributeRequestException e) { errorAndExit("Error encountered during attribute resolution and filtering", e); } return null; } /** * Builds the attribute request context from the command line arguments. * * @param parser command line argument parser * @param appCtx spring application context * * @return attribute request context */ private static BaseSAMLProfileRequestContext buildAttributeRequestContext(CmdLineParser parser, ApplicationContext appCtx) { BaseSAMLProfileRequestContext requestContext = new BaseSAMLProfileRequestContext(); String[] rpConfigManagerNames = appCtx.getBeanNamesForType(SAMLMDRelyingPartyConfigurationManager.class); SAMLMDRelyingPartyConfigurationManager rpConfigManager = (SAMLMDRelyingPartyConfigurationManager) appCtx .getBean(rpConfigManagerNames[0]); requestContext.setMetadataProvider(rpConfigManager.getMetadataProvider()); String requester = (String) parser.getOptionValue(CLIParserBuilder.REQUESTER_ARG); if (requester != null) { requestContext.setRelyingPartyConfiguration(rpConfigManager.getRelyingPartyConfiguration(requester)); } else { requester = rpConfigManager.getAnonymousRelyingConfiguration().getRelyingPartyId(); requestContext.setRelyingPartyConfiguration(rpConfigManager.getAnonymousRelyingConfiguration()); } try { requestContext.setInboundMessageIssuer(requester); requestContext.setPeerEntityId(requester); requestContext .setPeerEntityMetadata(requestContext.getMetadataProvider().getEntityDescriptor(requester)); if (requestContext.getPeerEntityMetadata() != null) { requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); requestContext.setPeerEntityRoleMetadata( requestContext.getPeerEntityMetadata().getSPSSODescriptor(protocol)); } } catch (MetadataProviderException e) { errorAndExit("Unable to query for metadata for requester " + requester, e); } try { String issuer = requestContext.getRelyingPartyConfiguration().getProviderId(); requestContext.setOutboundMessageIssuer(issuer); requestContext.setLocalEntityId(issuer); requestContext.setLocalEntityMetadata(requestContext.getMetadataProvider().getEntityDescriptor(issuer)); } catch (MetadataProviderException e) { errorAndExit("Unable to query for metadata for issuer " + requester, e); } String principal = (String) parser.getOptionValue(CLIParserBuilder.PRINCIPAL_ARG); requestContext.setPrincipalName(principal); String authnMethod = (String) parser.getOptionValue(CLIParserBuilder.AUTHN_METHOD_ARG); requestContext.setPrincipalAuthenticationMethod(authnMethod); return requestContext; } /** * Prints the given attribute statement to system output. * * @param attributeStatement attribute statement to print */ private static void printAttributeStatement(SAMLObject attributeStatement) { if (attributeStatement == null) { System.out.println("No attribute statement."); return; } Marshaller statementMarshaller = Configuration.getMarshallerFactory().getMarshaller(attributeStatement); try { Element statement = statementMarshaller.marshall(attributeStatement); System.out.println(XMLHelper.prettyPrintXML(statement)); } catch (MarshallingException e) { errorAndExit("Unable to marshall attribute statement", e); } } /** * Prints a help message to the given output stream. * * @param out output to print the help message to */ private static void printHelp(PrintStream out) { out.println("Attribute Authority, Command Line Interface"); out.println(" This tools provides a command line interface to the Shibboleth Attribute Authority,"); out.println(" providing deployers a means to test their attribute resolution and configurations."); out.println(); out.println("usage:"); out.println(" On Unix systems: ./aacli.sh <PARAMETERS>"); out.println(" On Windows systems: .\\aacli.bat <PARAMETERS>"); out.println(); out.println("Required Parameters:"); out.println(String.format(" --%-16s %s", CLIParserBuilder.CONFIG_DIR, "Directory containing attribute authority configuration files")); out.println(String.format(" --%-16s %s", CLIParserBuilder.PRINCIPAL, "Principal name (user id) of the person whose attributes will be retrieved")); out.println(); out.println("Optional Parameters:"); out.println(String.format(" --%-16s %s", CLIParserBuilder.HELP, "Print this message")); out.println(String.format(" --%-16s %s", CLIParserBuilder.SPRING_EXTS, "Colon-delimited list of files containing Spring extension configurations")); out.println(String.format(" --%-16s %s", CLIParserBuilder.REQUESTER, "SAML entity ID of the relying party requesting the attributes. For example, the SPs entity ID. " + "If not provided, requester is treated as anonymous.")); out.println(String.format(" --%-16s %s", CLIParserBuilder.AUTHN_METHOD, "Method used to authenticate the user")); out.println( String.format(" --%-16s %s", CLIParserBuilder.SAML1, "No-value parameter indicating the attribute " + "authority should answer as if it received a SAML 1 request")); out.println(); } /** * Logs, as an error, the error message and exits the program. * * @param errorMessage error message * @param e exception that caused it */ private static void errorAndExit(String errorMessage, Exception e) { if (e == null) { log.error(errorMessage); } else { log.error(errorMessage, e); } System.out.flush(); System.exit(1); } /** * Helper class that creates the command line argument parser. */ private static class CLIParserBuilder { // Command line arguments public static final String HELP = "help"; public static final String CONFIG_DIR = "configDir"; public static final String SPRING_EXTS = "springExts"; public static final String REQUESTER = "requester"; public static final String ISSUER = "issuer"; public static final String PRINCIPAL = "principal"; public static final String AUTHN_METHOD = "authnMethod"; public static final String SAML1 = "saml1"; // Command line parser arguments public static CmdLineParser.Option HELP_ARG; public static CmdLineParser.Option CONFIG_DIR_ARG; public static CmdLineParser.Option SPRING_EXTS_ARG; public static CmdLineParser.Option REQUESTER_ARG; // ISSUER arg no longer used public static CmdLineParser.Option ISSUER_ARG; public static CmdLineParser.Option PRINCIPAL_ARG; public static CmdLineParser.Option AUTHN_METHOD_ARG; public static CmdLineParser.Option SAML1_ARG; /** * Create a new command line parser. * * @return command line parser */ public static CmdLineParser buildParser() { CmdLineParser parser = new CmdLineParser(); HELP_ARG = parser.addBooleanOption(HELP); CONFIG_DIR_ARG = parser.addStringOption(CONFIG_DIR); SPRING_EXTS_ARG = parser.addStringOption(SPRING_EXTS); REQUESTER_ARG = parser.addStringOption(REQUESTER); ISSUER_ARG = parser.addStringOption(ISSUER); PRINCIPAL_ARG = parser.addStringOption(PRINCIPAL); AUTHN_METHOD_ARG = parser.addStringOption(AUTHN_METHOD); SAML1_ARG = parser.addBooleanOption(SAML1); return parser; } } }