Java tutorial
/** * Copyright (c) Members of the EGEE Collaboration. 2006-2009. * See http://www.eu-egee.org/partners/ for details on the copyright holders. * * Licensed 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 org.glite.authz.pap.ui.cli; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.rmi.RemoteException; import java.util.Collection; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.bouncycastle.openssl.PEMReader; import org.glite.authz.pap.client.ServiceClient; import org.glite.authz.pap.client.ServiceClientFactory; import org.glite.authz.pap.common.Pap; import org.glite.authz.pap.common.exceptions.PAPException; import org.glite.authz.pap.common.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class ServiceCLI { public static enum ExitStatus { // Attention keep that order because elements ordinal number is used. SUCCESS, // value = 0 PARTIAL_SUCCESS, // value = 1 FAILURE, // value = 2 INITIALIZATION_ERROR, // value = 3 PARSE_ERROR, // value = 4 REMOTE_EXCEPTION // value = 5 } private static final Logger log = LoggerFactory.getLogger(ServiceCLI.class); private static final HelpFormatter helpFormatter = new HelpFormatter(); private static final String OPT_PROXY_DESCRIPTION = "Specifies a user proxy to be used for authentication."; private static final String OPT_PROXY_LONG = "proxy"; private static final String OPT_CERT_DESCRIPTION = "Specifies non-standard user certificate."; private static final String OPT_CERT_LONG = "cert"; private static final String OPT_HOST_DESCRIPTION = "Specifies the target PAP hostname (default is localhost). " + "This option defines the PAP endpoint to be contacted as follows: https://arg:port" + Pap.DEFAULT_SERVICES_ROOT_PATH; private static final String OPT_HOST_LONG = "host"; private static final String OPT_KEY_DESCRIPTION = "Specifies non-standard user private key."; private static final String OPT_KEY_LONG = "key"; private static final String OPT_PORT = "p"; private static final String OPT_PORT_DESCRIPTION = "Specifies the port on which the target PAP is listening " + "(default is " + Pap.DEFAULT_PORT + ")"; private static final String OPT_PORT_LONG = "port"; private static final String OPT_URL_LONG = "url"; private static final String OPT_VERBOSE = "v"; private static final String OPT_VERBOSE_DESCRIPTION = "Verbose mode."; private static final String OPT_VERBOSE_LONG = "verbose"; protected static final String DEFAULT_SERVICE_URL = "https://%s:%s%s"; protected static final String OPT_HELP = "h"; protected static final String OPT_HELP_DESCRIPTION = "Print this message."; protected static final String OPT_HELP_LONG = "help"; protected static final String OPT_PRIVATE_LONG = "private"; protected static final String OPT_PUBLIC_LONG = "public"; protected static final String PAP_HOST_PROPERTY = "host"; protected static final String PAP_PORT_PROPERTY = "port"; protected static final CommandLineParser parser = new GnuParser(); private String[] commandNameValues; private Options commandOptions; private String descriptionText; private static Options globalOptions; private String longDescriptionText; private Options options; private final ServiceClient serviceClient; private String usageText; protected boolean verboseMode = false; static { globalOptions = defineGlobalOptions(); } @SuppressWarnings("unchecked") public ServiceCLI(String[] commandNameValues, String usage, String description, String longDescription) { ServiceClientFactory serviceClientFactory = ServiceClientFactory.getServiceClientFactory(); serviceClient = serviceClientFactory.createServiceClient(); helpFormatter.setWidth(80); helpFormatter.setLeftPadding(4); this.commandNameValues = commandNameValues; this.usageText = usage; this.descriptionText = description; this.longDescriptionText = longDescription; commandOptions = defineCommandOptions(); if (commandOptions == null) commandOptions = new Options(); commandOptions.addOption(OPT_HELP, OPT_HELP_LONG, false, OPT_HELP_DESCRIPTION); options = new Options(); Collection<Option> optionsList = commandOptions.getOptions(); for (Option opt : optionsList) { options.addOption(opt); } optionsList = globalOptions.getOptions(); for (Option opt : optionsList) { options.addOption(opt); } } public static Options getGlobalOptions() { return globalOptions; } @SuppressWarnings("static-access") private static Options defineGlobalOptions() { Options options = new Options(); // TODO: OPT_URL and (OPT_HOST, OPT_PORT) are mutually exclusive // options. Use OptionGroup. options.addOption(OptionBuilder.hasArg(true).withLongOpt(OPT_URL_LONG) .withDescription("Specifies the target PAP endpoint (default: " + String.format(DEFAULT_SERVICE_URL, Pap.DEFAULT_HOST, Pap.DEFAULT_PORT, Pap.DEFAULT_SERVICES_ROOT_PATH) + ").") .withArgName("url").create()); options.addOption(OptionBuilder.hasArg(true).withLongOpt(OPT_HOST_LONG) .withDescription(OPT_HOST_DESCRIPTION).withArgName("hostname").create()); options.addOption(OptionBuilder.hasArg(true).withLongOpt(OPT_PORT_LONG) .withDescription(OPT_PORT_DESCRIPTION).create(OPT_PORT)); options.addOption(OptionBuilder.hasArg(true).withLongOpt(OPT_PROXY_LONG) .withDescription(OPT_PROXY_DESCRIPTION).withArgName("file").create()); options.addOption(OptionBuilder.hasArg(true).withLongOpt(OPT_CERT_LONG) .withDescription(OPT_CERT_DESCRIPTION).withArgName("file").create()); options.addOption(OptionBuilder.hasArg(true).withLongOpt(OPT_KEY_LONG).withDescription(OPT_KEY_DESCRIPTION) .withArgName("file").create()); options.addOption(OptionBuilder.hasArg(false).withLongOpt(OPT_VERBOSE_LONG) .withDescription(OPT_VERBOSE_DESCRIPTION).create(OPT_VERBOSE)); return options; } public boolean commandMatch(String command) { for (String value : commandNameValues) { if (value.equals(command)) return true; } return false; } public int execute(String[] args) throws ParseException, HelpMessageException, RemoteException { CommandLine commandLine = parser.parse(options, args); if (commandLine.hasOption(OPT_HELP)) { throw new HelpMessageException(); } if (commandLine.hasOption(OPT_VERBOSE)) { verboseMode = true; } if (commandLine.hasOption(OPT_URL_LONG)) { serviceClient.setTargetEndpoint(commandLine.getOptionValue(OPT_URL_LONG)); } else { String host = Pap.DEFAULT_HOST; String papHostProperty = System.getProperty(PAP_HOST_PROPERTY); if (papHostProperty != null && !"".equals(papHostProperty.trim())) { host = papHostProperty; } String port = Pap.DEFAULT_PORT; String papPortProperty = System.getProperty(PAP_PORT_PROPERTY); if (papPortProperty != null && !"".equals(papPortProperty.trim())) { port = papPortProperty; } if (commandLine.hasOption(OPT_HOST_LONG)) { host = commandLine.getOptionValue(OPT_HOST_LONG); } if (commandLine.hasOption(OPT_PORT)) { port = commandLine.getOptionValue(OPT_PORT); } try { Integer.valueOf(port); } catch (NumberFormatException e) { throw new ParseException(String.format("Invalid port number \"%s\" (option -%s, --%s)", port, OPT_PORT, OPT_PORT_LONG)); } serviceClient.setTargetEndpoint( String.format(DEFAULT_SERVICE_URL, host, port, Pap.DEFAULT_SERVICES_ROOT_PATH)); } boolean credentialsNotRetrieved = true; if (commandLine.hasOption(OPT_PROXY_LONG)) { serviceClient.setClientProxy(commandLine.getOptionValue(OPT_PROXY_LONG)); credentialsNotRetrieved = false; } if (commandLine.hasOption(OPT_CERT_LONG)) { if (commandLine.hasOption(OPT_PROXY_LONG)) { throw new ParseException( String.format("Conflicting options --%s and --%s.", OPT_PROXY_LONG, OPT_CERT_LONG)); } else { if (!commandLine.hasOption(OPT_KEY_LONG)) { throw new ParseException( String.format("Option --%s requires also option --%s.", OPT_CERT_LONG, OPT_KEY_LONG)); } serviceClient.setClientCertificate(commandLine.getOptionValue(OPT_CERT_LONG)); credentialsNotRetrieved = false; } } if (commandLine.hasOption(OPT_KEY_LONG)) { if (commandLine.hasOption(OPT_PROXY_LONG)) { throw new ParseException( String.format("Conflicting options --%s and --%s.", OPT_PROXY_LONG, OPT_KEY_LONG)); } else { if (!commandLine.hasOption(OPT_CERT_LONG)) { throw new ParseException( String.format("Option --%s requires also option --%s.", OPT_KEY_LONG, OPT_CERT_LONG)); } serviceClient.setClientPrivateKey(commandLine.getOptionValue(OPT_KEY_LONG)); credentialsNotRetrieved = false; } } if (credentialsNotRetrieved) { // 1. if running as root take the cert /etc/grid-security/hostcert.pem // 2. check the env variable X509_USER_PROXY // 3. check the env variable X509_USER_CERT (and X509_USER_KEY) // 4. check the proxy /tmp/x509up_u<id_utente> // 5. check the cert $HOME/.globus/usercert.pem and key $HOME/.globus/userkey.pem String euid = getEUID(); if (euid == null) { log.error("Cannot enstabilish user's effective user id."); throw new PAPException(String.format( "Cannot enstabilish user's effective user id, please use the --%s or --%s, --%s options.", OPT_PROXY_LONG, OPT_CERT_LONG, OPT_KEY_LONG)); } String messageString = null; if ("0".equals(euid)) { // user: root if (setCertFromEnvironment()) { messageString = String.format( "Connecting to %s using %s and %s (from environment X509_USER_CERT and X509_USER_KEY)", serviceClient.getTargetEndpoint(), serviceClient.getClientCertificate(), serviceClient.getClientPrivateKey()); } else { serviceClient.setClientCertificate("/etc/grid-security/hostcert.pem"); serviceClient.setClientPrivateKey("/etc/grid-security/hostkey.pem"); messageString = String.format("Connecting to %s using %s and %s", serviceClient.getTargetEndpoint(), serviceClient.getClientCertificate(), serviceClient.getClientPrivateKey()); } } else { if (setProxyFromEnvironment()) { messageString = String.format( "Connecting to %s using proxy (from environment X509_USER_PROXY) %s", serviceClient.getTargetEndpoint(), serviceClient.getClientProxy()); } else if (setCertFromEnvironment()) { messageString = String.format( "Connecting to %s using %s and %s (from environment X509_USER_CERT and X509_USER_KEY)", serviceClient.getTargetEndpoint(), serviceClient.getClientCertificate(), serviceClient.getClientPrivateKey()); } else if (setProxyFromStandardLocation(euid)) { messageString = String.format("Connecting to %s using proxy %s", serviceClient.getTargetEndpoint(), serviceClient.getClientProxy()); } else if (setCertFromHomeDir()) { messageString = String.format("Connecting to %s using %s and %s", serviceClient.getTargetEndpoint(), serviceClient.getClientCertificate(), serviceClient.getClientPrivateKey()); } else { throw new ParseException(String.format( "Unable to find a certificate or a proxy, please specify a proxy file with option --%s or certificate and key with options --%s and --%s", OPT_PROXY_LONG, OPT_CERT_LONG, OPT_KEY_LONG)); } } log.info(messageString); if (verboseMode) { System.out.println(messageString); } } // Ask for certificate password if needed. The default private key (getClientPrivateKey() == // null) // is a host certificate key which doesn't need the password if (serviceClient.getClientPrivateKey() != null) { try { Reader reader = new FileReader(serviceClient.getClientPrivateKey()); String prompt = "Please enter the passphrase for the private key file " + serviceClient.getClientPrivateKey() + ": "; PasswordFinderImpl passwordFinder = new PasswordFinderImpl(prompt); PEMReader pm = new PEMReader(reader, passwordFinder); char[] password = null; try { pm.readObject(); } catch (IOException e) { // doesn't matter certificate stuff is managed later. // the purpose of this is just to set the password (if needed). } password = passwordFinder.getTypedPassword(); if (password != null) { serviceClient.setClientPrivateKeyPassword(new String(password)); } } catch (FileNotFoundException e) { throw new CLIException(e); } } return executeCommandService(commandLine, serviceClient); } public String[] getCommandNameValues() { return commandNameValues; } public ServiceClient getServiceClient() { return serviceClient; } public void printHelpMessage(PrintWriter pw) { String syntax = String.format("pap-admin [global-options] %s %s", commandNameValues[0], usageText); pw.println(); helpFormatter.printUsage(pw, helpFormatter.getWidth(), syntax); if (descriptionText != null) { pw.println(); helpFormatter.printWrapped(pw, helpFormatter.getWidth(), descriptionText); } if (longDescriptionText != null) { pw.println(); helpFormatter.printWrapped(pw, helpFormatter.getWidth(), longDescriptionText); } // command specific options pw.println(); helpFormatter.printWrapped(pw, helpFormatter.getWidth(), "Valid options:"); helpFormatter.printOptions(pw, helpFormatter.getWidth(), commandOptions, helpFormatter.getLeftPadding(), helpFormatter.getDescPadding()); // global options pw.println(); helpFormatter.printWrapped(pw, helpFormatter.getWidth(), "Global options:"); helpFormatter.printOptions(pw, helpFormatter.getWidth(), globalOptions, helpFormatter.getLeftPadding(), helpFormatter.getDescPadding()); } protected abstract Options defineCommandOptions(); protected abstract int executeCommandService(CommandLine commandLine, ServiceClient serviceClient) throws CLIException, ParseException, RemoteException; protected void printErrorMessage(String msg) { System.out.println(msg); } protected void printOutputMessage(String msg) { System.out.println(msg); } protected void printVerboseMessage(String msg) { if (verboseMode) System.out.println(msg); } /** * Return the effective user ID. * * @return the effective user ID or <code>null</code> if the effective user ID couldn't be established. */ private String getEUID() { String euid = System.getenv("EUID"); if (!Utils.isDefined(euid)) { String euidProperty = System.getProperty("effectiveUserId"); if (Utils.isDefined(euidProperty)) { euid = euidProperty; } else { euid = null; } } return euid; } /** * Set the certificate and the key in {@link ServiceCLI#serviceClient} using ~/.globus/user{cert,key}.pem. * * @return <code>true</code> if cert and key were found, <code>false</code> otherwise. * */ private boolean setCertFromHomeDir() throws ParseException { String baseDir = System.getProperty("user.home") + File.separator + ".globus" + File.separator; String cert = baseDir + "usercert.pem"; String key = baseDir + "userkey.pem"; File file = new File(cert); if (!file.exists()) { return false; } file = new File(key); if (!file.exists()) { return false; } serviceClient.setClientCertificate(cert); serviceClient.setClientPrivateKey(key); return true; } /** * Set the certificate the the key in {@link ServiceCLI#serviceClient} using the envirinment variables * X509_USER_CERT and X509_USER_KEY. * * @return <code>true</code> if the env variables are set, <code>false</code> otherwise. * @throws ParseException if only one of X509_USER_CERT or X509_USER_KEY is set. * */ private boolean setCertFromEnvironment() throws ParseException { String x509UserCert = System.getenv("X509_USER_CERT"); String x509UserKey = System.getenv("X509_USER_KEY"); if (!Utils.isDefined(x509UserCert) && (!Utils.isDefined(x509UserKey))) { return false; } if (!(Utils.isDefined(x509UserCert) && Utils.isDefined(x509UserKey))) { if (Utils.isDefined(x509UserCert)) { throw new ParseException( "Trying to use environment variable X509_USER_CERT for certificate but environment variable X509_USER_KEY is not set."); } throw new ParseException( "Trying to use environment variable X509_USER_KEY for private key but environment variable X509_USER_CERT is not set."); } serviceClient.setClientCertificate(x509UserCert); serviceClient.setClientPrivateKey(x509UserKey); return true; } /** * Set the proxy in {@link ServiceCLI#serviceClient} using the location defined in the environment * variable X509_USER_PROXY. * * @return <code>true</code> if the env variable is set, <code>false</code> otherwise. */ private boolean setProxyFromEnvironment() { String x509UserProxy = System.getenv("X509_USER_PROXY"); if (Utils.isDefined(x509UserProxy)) { serviceClient.setClientProxy(x509UserProxy); return true; } else { return false; } } /** * Set the proxy in {@link ServiceCLI#serviceClient} using the standard location (i.e. /tmp). * * @param euid the effective user ID to build the proxy file name. * @return <code>true</code> the proxy was found, <code>false</code> otherwise. */ private boolean setProxyFromStandardLocation(String euid) { String proxy = "/tmp/x509up_u" + euid; File file = new File(proxy); if (!file.exists()) { return false; } serviceClient.setClientProxy(proxy); return true; } }