org.signserver.admin.cli.defaultimpl.token.QueryTokenEntriesCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.signserver.admin.cli.defaultimpl.token.QueryTokenEntriesCommand.java

Source

/*************************************************************************
 *                                                                       *
 *  SignServer: The OpenSource Automated Signing Server                  *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.signserver.admin.cli.defaultimpl.token;

import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.cesecore.audit.impl.integrityprotected.AuditRecordData;
import org.cesecore.dbprotection.DatabaseProtectionException;
import org.cesecore.util.query.Elem;
import org.cesecore.util.query.QueryCriteria;
import org.cesecore.util.query.elems.RelationalOperator;
import org.cesecore.util.query.elems.Term;
import org.signserver.admin.cli.AdminCLIUtils;
import org.signserver.admin.cli.defaultimpl.AbstractAdminCommand;
import org.signserver.admin.cli.defaultimpl.AdminCommandHelper;
import org.signserver.cli.spi.CommandFailureException;
import org.signserver.cli.spi.IllegalCommandArgumentsException;
import org.signserver.cli.spi.UnexpectedCommandFailureException;
import org.signserver.server.cryptotokens.TokenEntry;
import org.signserver.server.cryptotokens.TokenSearchResults;

/**
 * Command for printing key aliases in a crypto token and optionally other
 * information including the certificate chain.
 *
 * @author Markus Kils
 * @version $Id: QueryTokenEntriesCommand.java 5979 2015-03-27 15:44:41Z netmackan $
 */
public class QueryTokenEntriesCommand extends AbstractAdminCommand {

    private final AdminCommandHelper helper = new AdminCommandHelper();

    /** Option strings */
    public static final String TOKEN = "token";
    public static final String FROM = "from";
    public static final String LIMIT = "limit";
    public static final String CRITERIA = "criteria";
    public static final String VERBOSE = "v";

    /** The command line options */
    private static final Options OPTIONS;
    private static final Set<String> longFields;
    private static final Set<String> dateFields;
    private static final Set<RelationalOperator> noArgOps;
    private static final Set<String> allowedFields;

    private static final String INDENT = "   ";

    private String tokenIdOrName;
    private int from = 0;
    private int limit = 0;
    private boolean verbose = false;
    private List<Elem> terms;

    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");

    @Override
    public String getDescription() {
        return "Query the content of a token";
    }

    static {
        OPTIONS = new Options();
        OPTIONS.addOption(TOKEN, true, "Worker ID or name of CryptoToken");
        OPTIONS.addOption(CRITERIA, true, "Search criteria (can specify multiple criterias)");
        OPTIONS.addOption(FROM, true, "Lower index in search result (0-based)");
        OPTIONS.addOption(LIMIT, true, "Maximum number of search results");
        OPTIONS.addOption(VERBOSE, false,
                "Output the certificate chain and other information available in each entry");

        longFields = new HashSet<String>();
        longFields.add(AuditRecordData.FIELD_SEQUENCENUMBER);

        dateFields = new HashSet<String>();
        dateFields.add(AuditRecordData.FIELD_TIMESTAMP);

        noArgOps = new HashSet<RelationalOperator>();
        noArgOps.add(RelationalOperator.NULL);
        noArgOps.add(RelationalOperator.NOTNULL);

        // allowed fields
        allowedFields = new HashSet<String>();
        allowedFields.add("alias"); // TODO: Defined in CryptoTokenHelper.TokenEntryFields
    }

    @Override
    public String getUsages() {
        return "Usage: signserver querytokenentries -token <id or name> -limit <number> -operator <operator> [-criteria  \"<field> <op> <value>\" [-criteria...]] [-from <index>] [-v]\n"
                + "<field> is a field name from the token: alias\n"
                + "<op> is a relational operator: EQ, NEQ or LIKE\n"
                + "Example: signserver querytokenentries -token CryptoTokenHSM -from 0 -limit 10\n"
                + "Example: signserver querytokenentries -token CryptoTokenHSM -criteria \"alias EQ key123\n"
                + "Example: signserver querytokenentries -token CryptoTokenHSM -from 0 -limit 10 -criteria \"alias NEQ key1\" -criteria \"alias NEQ key4\"\n"
                + "Example: signserver querytokenentries -token CryptoTokenHSM -criteria \"alias LIKE key%\n\n";
    }

    @Override
    public int execute(String... args)
            throws IllegalCommandArgumentsException, CommandFailureException, UnexpectedCommandFailureException {

        try {
            // Parse the command line
            parseCommandLine(new GnuParser().parse(OPTIONS, args));
        } catch (ParseException ex) {
            throw new IllegalCommandArgumentsException(ex.getMessage());
        }

        try {
            final int tokenId = getWorkerId(tokenIdOrName);

            final QueryCriteria qc = QueryCriteria.create();

            if (terms != null && !terms.isEmpty()) {
                qc.add(AdminCLIUtils.andAll(terms, 0));
            }

            // Perform the query
            TokenSearchResults searchResults;

            int startIndex = from;
            final int max = limit < 1 ? 10 : limit;
            do {
                searchResults = helper.getWorkerSession().searchTokenEntries(tokenId, startIndex, max, qc, verbose,
                        Collections.<String, Object>emptyMap());

                int i = startIndex;
                for (TokenEntry entry : searchResults.getEntries()) {
                    renderEntry(i, entry, verbose);
                    i++;
                }
                startIndex = startIndex + searchResults.getEntries().size();
            } while (limit < 1 && searchResults.isMoreEntriesAvailable());

            if (searchResults.getNumMoreEntries() != null) {
                getOutputStream().println("... " + searchResults.getNumMoreEntries() + " more entries exists.");
            } else if (searchResults.isMoreEntriesAvailable() != null) {
                if (searchResults.isMoreEntriesAvailable()) {
                    getOutputStream().println("... more entries exists.");
                } else {
                    getOutputStream().println("... no more entries.");
                }
            } else {
                getOutputStream().println("... no information about more entries.");
            }

            out.println("\n\n");
            return 0;

        } catch (Exception e) {
            // Is it a verification failure?
            if (e.getCause() instanceof DatabaseProtectionException) {
                DatabaseProtectionException error = (DatabaseProtectionException) e.getCause();
                err.println(error.getMessage());
                // TODO: (or not): Doesn't seems like we can do more than printing this error message
                if (error.getEntity() != null) {
                    System.err.println(
                            "Entity: " + error.getEntity() + ", data: " + error.getEntity().getRowProtection());
                }

                return -1;
            } else {
                throw new UnexpectedCommandFailureException(e);
            }
        }
    }

    private void parseCommandLine(CommandLine line) throws ParseException {
        if (!line.hasOption(TOKEN)) {
            throw new ParseException("Missing required option: " + TOKEN);
        }
        tokenIdOrName = line.getOptionValue(TOKEN);

        final String fromString = line.getOptionValue(FROM);
        final String limitString = line.getOptionValue(LIMIT);

        verbose = line.hasOption(VERBOSE);

        if (fromString != null) {
            try {
                from = Integer.parseInt(fromString);
            } catch (NumberFormatException ex) {
                throw new ParseException("Invalid from index value: " + fromString);
            }
        }

        if (limitString != null) {
            try {
                limit = Integer.parseInt(limitString);

                if (limit <= 0) {
                    throw new ParseException("Too small value specified for limit: " + limit);
                }
            } catch (NumberFormatException ex) {
                throw new ParseException("Invalid limit value: " + limitString);
            }
        } else {
            limit = -1;
        }

        final String[] criterias = line.getOptionValues(CRITERIA);

        terms = new LinkedList<Elem>();
        if (criterias != null && criterias.length > 0) {
            for (final String criteria : criterias) {
                try {
                    final Term term = parseCriteria(criteria);
                    terms.add(term);
                } catch (NumberFormatException e) {
                    throw new ParseException("Invalid critera, expected a numeric value: " + criteria);
                } catch (IllegalArgumentException e) {
                    throw new ParseException("Invalid critera specified: " + e.getMessage() + ": " + criteria);
                } catch (java.text.ParseException e) {
                    throw new ParseException("Invalid date specified: " + criteria);
                }
            }
        }

    }

    static Term parseCriteria(final String criteria)
            throws IllegalArgumentException, NumberFormatException, java.text.ParseException {
        return AdminCLIUtils.parseCriteria(criteria, allowedFields, noArgOps, Collections.<String>emptySet(),
                longFields, dateFields);
    }

    private void renderEntry(int i, TokenEntry entry, boolean verbose) {
        getOutputStream().println(i + ": " + entry.getAlias());
        if (verbose) {
            final StringBuilder sb = new StringBuilder();
            sb.append(INDENT).append("Type: ").append(entry.getType()).append("\n");
            if (entry.getCreationDate() != null) {
                sb.append(INDENT).append("Creation date: ").append(entry.getCreationDate()).append("\n");
            }
            try {
                if (entry.getParsedChain() != null) {
                    sb.append(INDENT).append("Certificate chain: ").append("\n")
                            .append(Arrays.toString(entry.getParsedChain())).append("\n");
                }
            } catch (CertificateException ex) {
                sb.append(INDENT).append("Certificate chain: ").append("Unable to parse: ").append(ex.getMessage())
                        .append("\n");
            }
            try {
                if (entry.getParsedTrustedCertificate() != null) {
                    sb.append(INDENT).append("Trusted certificate: ").append("\n")
                            .append(entry.getParsedTrustedCertificate()).append("\n");
                }
            } catch (CertificateException ex) {
                sb.append(INDENT).append("Trusted certificate: ").append("Unable to parse: ")
                        .append(ex.getMessage()).append("\n");
            }
            if (entry.getInfo() != null && !entry.getInfo().isEmpty()) {
                sb.append(INDENT).append("Additional information:\n");
                for (Map.Entry<String, String> info : entry.getInfo().entrySet()) {
                    sb.append(INDENT).append(INDENT).append(info.getKey()).append(": ").append(info.getValue())
                            .append("\n");
                }
            }
            sb.append("\n");
            getOutputStream().println(sb.toString());
        }
    }

}