From ac9485532d0020aa1b073456b736c7b630c098dc Mon Sep 17 00:00:00 2001
From: Endi Sukma Dewata <edewata@redhat.com>
Date: Sat, 19 May 2012 11:16:53 -0500
Subject: [PATCH] Added user CLI.

The user CLI provides a tool to manage users and user certificates.

Ticket #160
---
 base/common/src/CMakeLists.txt                     |   13 +
 base/common/src/com/netscape/cms/cli/CLI.java      |   88 ++++++
 base/common/src/com/netscape/cms/cli/MainCLI.java  |  287 ++++++++++++++++++++
 .../netscape/cms/servlet/base/CMSException.java    |    2 +-
 .../cms/servlet/csadmin/CMSErrorInterceptor.java   |    2 +-
 .../cms/servlet/csadmin/CMSRestClient.java         |   32 +--
 .../com/netscape/cms/user/client/AddUserCLI.java   |  104 +++++++
 .../netscape/cms/user/client/AddUserCertCLI.java   |   95 +++++++
 .../com/netscape/cms/user/client/FindUserCLI.java  |   98 +++++++
 .../netscape/cms/user/client/FindUserCertCLI.java  |  104 +++++++
 .../netscape/cms/user/client/ModifyUserCLI.java    |  105 +++++++
 .../netscape/cms/user/client/RemoveUserCLI.java    |   53 ++++
 .../cms/user/client/RemoveUserCertCLI.java         |   61 ++++
 .../com/netscape/cms/user/client/ShowUserCLI.java  |   53 ++++
 .../netscape/cms/user/client/ShowUserCertCLI.java  |   87 ++++++
 .../src/com/netscape/cms/user/client/UserCLI.java  |  152 +++++++++++
 .../netscape/cms/user/client/UserRestClient.java   |   81 ++++++
 base/setup/CMakeLists.txt                          |    1 +
 base/setup/pki                                     |  105 +++++++
 base/setup/pkicreate                               |   14 +
 specs/pki-core.spec                                |    6 +-
 21 files changed, 1522 insertions(+), 21 deletions(-)
 create mode 100644 base/common/src/com/netscape/cms/cli/CLI.java
 create mode 100644 base/common/src/com/netscape/cms/cli/MainCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/AddUserCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/AddUserCertCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/FindUserCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/FindUserCertCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/ModifyUserCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/RemoveUserCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/RemoveUserCertCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/ShowUserCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/ShowUserCertCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/UserCLI.java
 create mode 100644 base/common/src/com/netscape/cms/user/client/UserRestClient.java
 create mode 100755 base/setup/pki

diff --git a/base/common/src/CMakeLists.txt b/base/common/src/CMakeLists.txt
index cbd738b14beaab89be57a6b8b6707720305f1e54..0b14bc6930033fd46e4666518d898a5954f4e92d 100644
--- a/base/common/src/CMakeLists.txt
+++ b/base/common/src/CMakeLists.txt
@@ -446,6 +446,8 @@ set(pki-certsrv_java_SRCS
 )
 
 set(pki-cms_java_SRCS
+    com/netscape/cms/cli/CLI.java
+    com/netscape/cms/cli/MainCLI.java
     com/netscape/cms/listeners/PinRemovalListener.java
     com/netscape/cms/listeners/RequestInQListener.java
     com/netscape/cms/listeners/CertificateIssuedListener.java
@@ -777,6 +779,17 @@ set(pki-cms_java_SRCS
     com/netscape/cms/user/UserCollection.java
     com/netscape/cms/user/UserData.java
     com/netscape/cms/user/UserResource.java
+    com/netscape/cms/user/client/AddUserCertCLI.java
+    com/netscape/cms/user/client/AddUserCLI.java
+    com/netscape/cms/user/client/FindUserCertCLI.java
+    com/netscape/cms/user/client/FindUserCLI.java
+    com/netscape/cms/user/client/ModifyUserCLI.java
+    com/netscape/cms/user/client/RemoveUserCertCLI.java
+    com/netscape/cms/user/client/RemoveUserCLI.java
+    com/netscape/cms/user/client/ShowUserCertCLI.java
+    com/netscape/cms/user/client/ShowUserCLI.java
+    com/netscape/cms/user/client/UserCLI.java
+    com/netscape/cms/user/client/UserRestClient.java
     com/netscape/cms/user/server/UserCertResourceService.java
     com/netscape/cms/user/server/UserResourceService.java
     com/netscape/cms/authentication/AgentCertAuthentication.java
diff --git a/base/common/src/com/netscape/cms/cli/CLI.java b/base/common/src/com/netscape/cms/cli/CLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..e03c88a618e89e0bae06123fded73b4f2dba8e9d
--- /dev/null
+++ b/base/common/src/com/netscape/cms/cli/CLI.java
@@ -0,0 +1,88 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.cli;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.PosixParser;
+
+
+/**
+ * @author Endi S. Dewata
+ */
+public class CLI {
+
+    public static boolean verbose;
+
+    public static CommandLineParser parser = new PosixParser();
+    public static HelpFormatter formatter = new HelpFormatter();
+
+    public String name;
+    public String description;
+
+    public Options options = new Options();
+    public Map<String, CLI> modules = new LinkedHashMap<String, CLI>();
+
+    public CLI(String name, String description) {
+        this.name = name;
+        this.description = description;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public void addModule(CLI module) {
+        modules.put(module.getName(), module);
+    }
+
+    public CLI getModule(String name) {
+        return modules.get(name);
+    }
+
+    public void execute(String[] args) throws Exception {
+    }
+
+    public void printHelp() {
+    }
+
+    public static boolean isVerbose() {
+        return verbose;
+    }
+
+    public static void setVerbose(boolean verbose) {
+        CLI.verbose = verbose;
+    }
+}
diff --git a/base/common/src/com/netscape/cms/cli/MainCLI.java b/base/common/src/com/netscape/cms/cli/MainCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc65b93b96a9e53bd36fe0b2a97b4a7117564c41
--- /dev/null
+++ b/base/common/src/com/netscape/cms/cli/MainCLI.java
@@ -0,0 +1,287 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.cli;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.lang.StringUtils;
+import org.mozilla.jss.CryptoManager;
+import org.mozilla.jss.crypto.AlreadyInitializedException;
+import org.mozilla.jss.crypto.CryptoToken;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cms.user.client.UserCLI;
+
+
+
+/**
+ * @author Endi S. Dewata
+ */
+public class MainCLI extends CLI {
+
+    public String protocol;
+    public String hostname;
+    public String port;
+    public String type;
+
+    public String tokenDBPassword;
+    public String tokenDBDirectory;
+
+    public String url;
+
+    public MainCLI() throws Exception {
+        super("pki", "PKI command-line interface");
+
+        addModule(new UserCLI(this));
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public void setProtocol(String protocol) {
+        this.protocol = protocol;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public void setHostname(String hostname) {
+        this.hostname = hostname;
+    }
+
+    public String getPort() {
+        return port;
+    }
+
+    public void setPort(String port) {
+        this.port = port;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getTokenDBPassword() {
+        return tokenDBPassword;
+    }
+
+    public void setTokenDBPassword(String tokenPassword) {
+        this.tokenDBPassword = tokenPassword;
+    }
+
+    public String getTokenDBDirectory() {
+        return tokenDBDirectory;
+    }
+
+    public void setTokenDBDirectory(String tokenDBDirectory) {
+        this.tokenDBDirectory = tokenDBDirectory;
+    }
+
+    public void printHelp() {
+
+        formatter.printHelp(getName()+" [OPTIONS..] <command> [ARGS..]", options);
+
+        System.out.println();
+        System.out.println("Commands:");
+
+        int leftPadding = 1;
+        int rightPadding = 18;
+
+        for (CLI plugin : modules.values()) {
+            String label = plugin.getName();
+
+            int padding = rightPadding - leftPadding - label.length();
+            if (padding < 1) padding = 1;
+
+            System.out.print(StringUtils.repeat(" ", leftPadding));
+            System.out.print(label);
+            System.out.print(StringUtils.repeat(" ", padding));
+            System.out.println(plugin.getDescription());
+        }
+    }
+
+    public void printHelpCommand(String pluginName) {
+        CLI plugin = getModule(pluginName);
+        plugin.printHelp();
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option("U", true, "URL");
+        option.setArgName("url");
+        options.addOption(option);
+
+        option = new Option("P", true, "Protocol (default: http)");
+        option.setArgName("protocol");
+        options.addOption(option);
+
+        option = new Option("h", true, "Hostname (default: localhost)");
+        option.setArgName("hostname");
+        options.addOption(option);
+
+        option = new Option("p", true, "Port (default: 9180)");
+        option.setArgName("port");
+        options.addOption(option);
+
+        option = new Option("t", true, "Subsystem type (default: ca)");
+        option.setArgName("type");
+        options.addOption(option);
+
+        option = new Option("w", true, "Token database password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option("d", true, "Token database directory");
+        option.setArgName("directory");
+        options.addOption(option);
+
+        options.addOption("v", false, "Verbose");
+        options.addOption(null, "help", false, "Help");
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args, true);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmd.hasOption("help") || cmdArgs.length == 0) {
+            printHelp();
+            System.exit(1);
+        }
+
+        verbose = cmd.hasOption("v");
+
+        tokenDBDirectory = cmd.getOptionValue("d");
+        tokenDBPassword = cmd.getOptionValue("w");
+
+        if (tokenDBDirectory != null && tokenDBPassword != null) {
+
+            if (verbose) System.out.println("Token DB: "+tokenDBDirectory);
+
+            try {
+                CryptoManager.initialize(tokenDBDirectory);
+            } catch (AlreadyInitializedException e) {
+                // ignore
+            }
+
+            CryptoManager manager = CryptoManager.getInstance();
+            CryptoToken token = manager.getInternalKeyStorageToken();
+            Password password = new Password(tokenDBPassword.toCharArray());
+
+            try {
+                token.login(password);
+            } catch (Exception e) {
+                System.err.println("Error: " + e.getMessage());
+                if (!token.isLoggedIn()) {
+                    token.initPassword(password, password);
+                }
+            }
+        }
+
+        url = cmd.getOptionValue("U");
+        protocol = cmd.getOptionValue("P", "http");
+        hostname = cmd.getOptionValue("h", "localhost");
+        port = cmd.getOptionValue("p", "9180");
+        type = cmd.getOptionValue("t", "ca");
+
+        if (url == null) {
+            url = protocol + "://" + hostname + ":" + port + "/" + type + "/pki";
+        }
+
+        if (verbose) System.out.println("Server URL: "+url);
+
+        if (verbose) {
+            System.out.print("Command:");
+            for (String arg : cmdArgs) {
+                System.out.print(" "+arg);
+            }
+            System.out.println();
+        }
+
+        // command-line args: <command> [command args...]
+        if (cmdArgs.length == 0) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String command = cmdArgs[0];
+
+        String moduleName;
+        String moduleCommand;
+
+        // parse command: <module name>-<module command>
+        int i = command.indexOf('-');
+        if (i >= 0) {
+            moduleName = command.substring(0, i);
+            moduleCommand = command.substring(i+1);
+        } else {
+            moduleName = command;
+            moduleCommand = null;
+        }
+
+        // get command module
+        CLI module = getModule(moduleName);
+        if (module == null) {
+            System.err.println("Error: Invalid command \"" + command + "\"");
+            printHelp();
+            System.exit(1);
+        }
+
+        // prepare module arguments
+        String[] moduleArgs = new String[cmdArgs.length];
+        moduleArgs[0] = moduleCommand;
+        System.arraycopy(cmdArgs, 1, moduleArgs, 1, cmdArgs.length-1);
+
+        // execute module command
+        try {
+            module.execute(moduleArgs);
+
+        } catch (Throwable t) {
+            if (verbose) {
+                t.printStackTrace();
+            } else {
+                System.err.println(t.getMessage());
+            }
+        }
+    }
+
+    public static void printMessage(String message) {
+        System.out.println(StringUtils.repeat("-", message.length()));
+        System.out.println(message);
+        System.out.println(StringUtils.repeat("-", message.length()));
+    }
+
+    public static void main(String args[]) throws Exception {
+        MainCLI cli = new MainCLI();
+        cli.execute(args);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/servlet/base/CMSException.java b/base/common/src/com/netscape/cms/servlet/base/CMSException.java
index 44a9a23dc7edc8135341c827586b049d418b81a4..5ceb30ee124c2b45f1eb608092999fdbd9fd2251 100644
--- a/base/common/src/com/netscape/cms/servlet/base/CMSException.java
+++ b/base/common/src/com/netscape/cms/servlet/base/CMSException.java
@@ -161,7 +161,7 @@ public class CMSException extends RuntimeException {
             return Response
                     .status(exception.getCode())
                     .entity(exception.getData())
-                    .type(MediaType.TEXT_XML)
+                    .type(MediaType.APPLICATION_XML)
                     .build();
         }
     }
diff --git a/base/common/src/com/netscape/cms/servlet/csadmin/CMSErrorInterceptor.java b/base/common/src/com/netscape/cms/servlet/csadmin/CMSErrorInterceptor.java
index b751fb091217f8fec7b0aabebc68dcef1aa68d9d..8704223910797ec6055f02292872994a41bcfa25 100644
--- a/base/common/src/com/netscape/cms/servlet/csadmin/CMSErrorInterceptor.java
+++ b/base/common/src/com/netscape/cms/servlet/csadmin/CMSErrorInterceptor.java
@@ -38,7 +38,7 @@ public class CMSErrorInterceptor implements ClientErrorInterceptor {
         String contentType = headers.getFirst("Content-Type");
 
         // handle XML content only
-        if (!contentType.startsWith(MediaType.TEXT_XML))
+        if (contentType == null || !contentType.startsWith(MediaType.APPLICATION_XML))
             return;
 
         CMSException exception;
diff --git a/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java b/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java
index 37db06bd5b8b71f1477f10c30c9cd4e82aa2eabd..ff028660a382e410fc11915b9584bf8a9d7b8f30 100644
--- a/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java
+++ b/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java
@@ -17,12 +17,23 @@ import org.apache.http.conn.scheme.SchemeSocketFactory;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.params.HttpParams;
 import org.jboss.resteasy.client.ClientExecutor;
+import org.jboss.resteasy.client.ProxyFactory;
 import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.mozilla.jss.ssl.SSLCertificateApprovalCallback;
 import org.mozilla.jss.ssl.SSLSocket;
 
 public abstract class CMSRestClient {
+
+    protected String clientCertNickname;
+    protected ResteasyProviderFactory providerFactory;
+    protected ClientExecutor executor;
+    protected URI uri;
+
+    public CMSRestClient(String baseUri) throws URISyntaxException {
+        this(baseUri, null);
+    }
+
     // Callback to approve or deny returned SSL server certs
     // Right now, simply approve the cert.
     // ToDO: Look into taking this JSS http client code and move it into
@@ -58,25 +69,11 @@ public abstract class CMSRestClient {
             //For now lets just accept the server cert. This is a test tool, being
             // pointed at a well know kra instance.
 
-            if (servercert != null) {
-                System.out.println("Peer SSL Servercert details: " +
-                        "\n     subject: " + servercert.getSubjectDN().toString() +
-                        "\n     issuer:  " + servercert.getIssuerDN().toString() +
-                        "\n     serial:  " + servercert.getSerialNumber().toString()
-                        );
-            }
-
             SSLCertificateApprovalCallback.ValidityItem item;
 
             Enumeration<?> errors = status.getReasons();
-            int i = 0;
             while (errors.hasMoreElements()) {
-                i++;
                 item = (SSLCertificateApprovalCallback.ValidityItem) errors.nextElement();
-                System.out.println("item " + i +
-                        " reason=" + item.getReason() +
-                        " depth=" + item.getDepth());
-
                 int reason = item.getReason();
 
                 if (reason ==
@@ -168,8 +165,7 @@ public abstract class CMSRestClient {
 
     }
 
-    protected static String clientCertNickname;
-    protected ResteasyProviderFactory providerFactory;
-    protected ClientExecutor executor;
-    protected URI uri;
+    public <T> T createProxy(Class<T> clazz) {
+        return ProxyFactory.create(clazz, uri, executor, providerFactory);
+    }
 }
diff --git a/base/common/src/com/netscape/cms/user/client/AddUserCLI.java b/base/common/src/com/netscape/cms/user/client/AddUserCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..10469e34a3e8ba66717e63f3170edab34feddf73
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/AddUserCLI.java
@@ -0,0 +1,104 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.cli.MainCLI;
+import com.netscape.cms.user.UserData;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class AddUserCLI extends CLI {
+
+    public UserCLI parent;
+
+    public AddUserCLI(UserCLI parent) {
+        super("add", "Add user");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "fullName", true, "Full name");
+        option.setArgName("fullName");
+        option.setRequired(true);
+        options.addOption(option);
+
+        option = new Option(null, "email", true, "Email");
+        option.setArgName("email");
+        options.addOption(option);
+
+        option = new Option(null, "password", true, "Password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "phone", true, "Phone");
+        option.setArgName("phone");
+        options.addOption(option);
+
+        option = new Option(null, "type", true, "Type");
+        option.setArgName("type");
+        options.addOption(option);
+
+        option = new Option(null, "state", true, "State");
+        option.setArgName("state");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userId = cmdArgs[0];
+
+        UserData userData = new UserData();
+        userData.setID(userId);
+        userData.setFullName(cmd.getOptionValue("fullName"));
+        userData.setEmail(cmd.getOptionValue("email"));
+        userData.setPassword(cmd.getOptionValue("password"));
+        userData.setPhone(cmd.getOptionValue("phone"));
+        userData.setType(cmd.getOptionValue("type"));
+        userData.setState(cmd.getOptionValue("state"));
+
+        parent.client.addUser(userData);
+
+        MainCLI.printMessage("Added user \"" + userId + "\"");
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/AddUserCertCLI.java b/base/common/src/com/netscape/cms/user/client/AddUserCertCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..193229cfe5aa369e420cbd98b56716bda445766c
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/AddUserCertCLI.java
@@ -0,0 +1,95 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import java.io.File;
+import java.util.Scanner;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.cli.MainCLI;
+import com.netscape.cms.user.UserCertData;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class AddUserCertCLI extends CLI {
+
+    public UserCLI parent;
+
+    public AddUserCertCLI(UserCLI parent) {
+        super("add-cert", "Add user cert");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "input", true, "Input file");
+        option.setArgName("file");
+        option.setRequired(true);
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userId = cmdArgs[0];
+        String file = cmd.getOptionValue("input");
+
+        // get cert from file
+        if (verbose) {
+            System.out.println("Reading cert from "+file+".");
+        }
+        String encoded = new Scanner(new File(file)).useDelimiter("\\A").next();
+        if (verbose) {
+            System.out.println(encoded);
+        }
+
+        UserCertData userCertData = new UserCertData();
+        userCertData.setEncoded(encoded);
+
+        if (verbose) {
+            System.out.println(userCertData);
+        }
+
+        parent.client.addUserCert(userId, userCertData);
+
+        MainCLI.printMessage("Number of certs added 1");
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/FindUserCLI.java b/base/common/src/com/netscape/cms/user/client/FindUserCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b282aa19ed689aeda542a7fbbd20191c69dab5b
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/FindUserCLI.java
@@ -0,0 +1,98 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import java.util.Collection;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.cli.MainCLI;
+import com.netscape.cms.user.UserCollection;
+import com.netscape.cms.user.UserData;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class FindUserCLI extends CLI {
+
+    public UserCLI parent;
+
+    public FindUserCLI(UserCLI parent) {
+        super("find", "Find users");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " [FILTER] [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "start", true, "Page start");
+        option.setArgName("start");
+        options.addOption(option);
+
+        option = new Option(null, "size", true, "Page size");
+        option.setArgName("size");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();;
+        String filter = cmdArgs.length > 0 ? cmdArgs[0] : null;
+
+        String s = cmd.getOptionValue("start");
+        Integer start = s == null ? null : Integer.valueOf(s);
+
+        s = cmd.getOptionValue("size");
+        Integer size = s == null ? null : Integer.valueOf(s);
+
+        UserCollection response = parent.client.findUsers(filter, start, size);
+
+        Collection<UserData> entries = response.getUsers();
+
+        MainCLI.printMessage(entries.size() + " user(s) matched");
+
+        boolean first = true;
+
+        for (UserData userData : entries) {
+
+            if (first) {
+                first = false;
+            } else {
+                System.out.println();
+            }
+
+            UserCLI.printUser(userData);
+        }
+
+        MainCLI.printMessage("Number of entries returned " + entries.size());
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/FindUserCertCLI.java b/base/common/src/com/netscape/cms/user/client/FindUserCertCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf88bbf31043648ff891cf107f4e249b11220923
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/FindUserCertCLI.java
@@ -0,0 +1,104 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import java.util.Collection;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.cli.MainCLI;
+import com.netscape.cms.user.UserCertCollection;
+import com.netscape.cms.user.UserCertData;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class FindUserCertCLI extends CLI {
+
+    public UserCLI parent;
+
+    public FindUserCertCLI(UserCLI parent) {
+        super("find-cert", "Find user certs");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "start", true, "Page start");
+        option.setArgName("start");
+        options.addOption(option);
+
+        option = new Option(null, "size", true, "Page size");
+        option.setArgName("size");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userID = cmdArgs[0];
+
+        String s = cmd.getOptionValue("start");
+        Integer start = s == null ? null : Integer.valueOf(s);
+
+        s = cmd.getOptionValue("size");
+        Integer size = s == null ? null : Integer.valueOf(s);
+
+        UserCertCollection response = parent.client.findUserCerts(userID, start, size);
+
+        Collection<UserCertData> entries = response.getCerts();
+
+        MainCLI.printMessage(entries.size() + " user cert(s) matched");
+
+        boolean first = true;
+
+        for (UserCertData userCertData : entries) {
+
+            if (first) {
+                first = false;
+            } else {
+                System.out.println();
+            }
+
+            UserCLI.printCert(userCertData);
+        }
+
+        MainCLI.printMessage("Number of entries returned " + entries.size());
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/ModifyUserCLI.java b/base/common/src/com/netscape/cms/user/client/ModifyUserCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..9634e0d2c2c772452a0e5d6272d97cb5011912c6
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/ModifyUserCLI.java
@@ -0,0 +1,105 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.cli.MainCLI;
+import com.netscape.cms.user.UserData;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class ModifyUserCLI extends CLI {
+
+    public UserCLI parent;
+
+    public ModifyUserCLI(UserCLI parent) {
+        super("mod", "Modify user");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "fullName", true, "Full name");
+        option.setArgName("fullName");
+        options.addOption(option);
+
+        option = new Option(null, "email", true, "Email");
+        option.setArgName("email");
+        options.addOption(option);
+
+        option = new Option(null, "password", true, "Password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "phone", true, "Phone");
+        option.setArgName("phone");
+        options.addOption(option);
+
+        // type cannot be modified
+        // option = new Option(null, "type", true, "Type");
+        // option.setArgName("type");
+        // options.addOption(option);
+
+        option = new Option(null, "state", true, "State");
+        option.setArgName("state");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userId = cmdArgs[0];
+
+        UserData userData = new UserData();
+        userData.setID(userId);
+        userData.setFullName(cmd.getOptionValue("fullName"));
+        userData.setEmail(cmd.getOptionValue("email"));
+        userData.setPassword(cmd.getOptionValue("password"));
+        userData.setPhone(cmd.getOptionValue("phone"));
+        // type cannot be modified
+        // userData.setType(cmd.getOptionValue("type"));
+        userData.setState(cmd.getOptionValue("state"));
+
+        parent.client.modifyUser(userId, userData);
+
+        MainCLI.printMessage("Modified user \"" + userId + "\"");
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/RemoveUserCLI.java b/base/common/src/com/netscape/cms/user/client/RemoveUserCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..3da23ad3fcf571c8e3d5e4387678f0eb9e8cfba5
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/RemoveUserCLI.java
@@ -0,0 +1,53 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class RemoveUserCLI extends CLI {
+
+    public UserCLI parent;
+
+    public RemoveUserCLI(UserCLI parent) {
+        super("del", "Remove user");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        if (args.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userID = args[0];
+
+        parent.client.removeUser(userID);
+
+        MainCLI.printMessage("Deleted user \"" + userID + "\"");
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/RemoveUserCertCLI.java b/base/common/src/com/netscape/cms/user/client/RemoveUserCertCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..14d659fa16c85f9c40a9147ad84d3cb578c402b1
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/RemoveUserCertCLI.java
@@ -0,0 +1,61 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import java.net.URLEncoder;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.cli.MainCLI;
+
+
+/**
+ * @author Endi S. Dewata
+ */
+public class RemoveUserCertCLI extends CLI {
+
+    public UserCLI parent;
+
+    public RemoveUserCertCLI(UserCLI parent) {
+        super("remove-cert", "Remove user cert");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> <Cert ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        if (args.length != 2) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userID = args[0];
+        String certID = args[1];
+
+        if (verbose) {
+            System.out.println("Removing cert "+certID+" from user "+userID+".");
+        }
+
+        parent.client.removeUserCert(userID, URLEncoder.encode(certID, "UTF-8"));
+
+        MainCLI.printMessage("Number of certs removed 1");
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/ShowUserCLI.java b/base/common/src/com/netscape/cms/user/client/ShowUserCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..d75773b9a65fd3fb3e03a446fcc26f7b2d638ed9
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/ShowUserCLI.java
@@ -0,0 +1,53 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.user.UserData;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class ShowUserCLI extends CLI {
+
+    public UserCLI parent;
+
+    public ShowUserCLI(UserCLI parent) {
+        super("show", "Show user");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        if (args.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userId = args[0];
+
+        UserData userData = parent.client.getUser(userId);
+
+        UserCLI.printUser(userData);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/ShowUserCertCLI.java b/base/common/src/com/netscape/cms/user/client/ShowUserCertCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c6cb4ced6733e601a69f1ab52beac303e9b6071
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/ShowUserCertCLI.java
@@ -0,0 +1,87 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.net.URLEncoder;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.user.UserCertData;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class ShowUserCertCLI extends CLI {
+
+    public UserCLI parent;
+
+    public ShowUserCertCLI(UserCLI parent) {
+        super("show-cert", "Show user cert");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> <Cert ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "output", true, "Output file");
+        option.setArgName("file");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 2) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userID = cmdArgs[0];
+        String certID = cmdArgs[1];
+        String file = cmd.getOptionValue("output");
+
+        UserCertData userCertData = parent.client.getUserCert(userID, URLEncoder.encode(certID, "UTF-8"));
+
+        String encoded = userCertData.getEncoded();
+        if (encoded != null && file != null) {
+            // store cert to file
+            PrintWriter out = new PrintWriter(new FileWriter(file));
+            out.print(encoded);
+            out.close();
+        }
+
+        UserCLI.printCert(userCertData);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/UserCLI.java b/base/common/src/com/netscape/cms/user/client/UserCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..86ba6630b5047bbe20ad7e737da4b4b2b487a08f
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/UserCLI.java
@@ -0,0 +1,152 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.user.client;
+
+import java.util.Arrays;
+
+import org.apache.commons.lang.StringUtils;
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+import com.netscape.cms.cli.CLI;
+import com.netscape.cms.cli.MainCLI;
+import com.netscape.cms.user.UserCertData;
+import com.netscape.cms.user.UserData;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserCLI extends CLI {
+
+    public MainCLI parent;
+    public UserRestClient client;
+
+    public UserCLI(MainCLI parent) {
+        super("user", "User management commands");
+        this.parent = parent;
+
+        addModule(new FindUserCLI(this));
+        addModule(new ShowUserCLI(this));
+        addModule(new AddUserCLI(this));
+        addModule(new ModifyUserCLI(this));
+        addModule(new RemoveUserCLI(this));
+
+        addModule(new FindUserCertCLI(this));
+        addModule(new ShowUserCertCLI(this));
+        addModule(new AddUserCertCLI(this));
+        addModule(new RemoveUserCertCLI(this));
+    }
+
+    public void printHelp() {
+
+        System.out.println("Commands:");
+
+        int leftPadding = 1;
+        int rightPadding = 25;
+
+        for (CLI module : modules.values()) {
+            String label = name + "-" + module.getName();
+
+            int padding = rightPadding - leftPadding - label.length();
+            if (padding < 1)
+                padding = 1;
+
+            System.out.print(StringUtils.repeat(" ", leftPadding));
+            System.out.print(label);
+            System.out.print(StringUtils.repeat(" ", padding));
+            System.out.println(module.getDescription());
+        }
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        client = new UserRestClient(parent.url);
+
+        String command = args[0];
+        String[] commandArgs = Arrays.copyOfRange(args, 1, args.length);
+
+        if (command == null) {
+            printHelp();
+            System.exit(1);
+        }
+
+        CLI module = getModule(command);
+        if (module != null) {
+            module.execute(commandArgs);
+
+        } else {
+            System.err.println("Error: Invalid command \"" + command + "\"");
+            printHelp();
+            System.exit(1);
+        }
+    }
+
+    public static void printUser(UserData userData) {
+        System.out.println("  User ID: " + userData.getID());
+
+        String fullName = userData.getFullName();
+        if (!StringUtils.isEmpty(fullName))
+            System.out.println("  Full name: " + fullName);
+
+        String email = userData.getEmail();
+        if (!StringUtils.isEmpty(email))
+            System.out.println("  Email: " + email);
+
+        String phone = userData.getPhone();
+        if (!StringUtils.isEmpty(phone))
+            System.out.println("  Phone: " + phone);
+
+        String type = userData.getType();
+        if (!StringUtils.isEmpty(type))
+            System.out.println("  Type: " + type);
+
+        String state = userData.getState();
+        if (!StringUtils.isEmpty(state))
+            System.out.println("  State: " + state);
+
+        Link link = userData.getLink();
+        if (verbose && link != null) {
+            System.out.println("  Link: " + link.getHref());
+        }
+    }
+
+    public static void printCert(UserCertData userCertData) throws Exception {
+        System.out.println("  Cert ID: " + userCertData.getID());
+        System.out.println("  Serial Number: " + userCertData.getSerialNumber());
+        System.out.println("  Version: " + userCertData.getVersion());
+        System.out.println("  Issuer DN: " + userCertData.getIssuerDN());
+        System.out.println("  Subject DN: " + userCertData.getSubjectDN());
+
+        String prettyPrint = userCertData.getPrettyPrint();
+        if (prettyPrint != null) {
+            System.out.println();
+            System.out.println(prettyPrint);
+        }
+
+        String encoded = userCertData.getEncoded();
+        if (encoded != null) {
+            System.out.println();
+            System.out.println(encoded);
+        }
+
+        Link link = userCertData.getLink();
+        if (verbose && link != null) {
+            System.out.println("  Link: " + link.getHref());
+        }
+    }
+}
diff --git a/base/common/src/com/netscape/cms/user/client/UserRestClient.java b/base/common/src/com/netscape/cms/user/client/UserRestClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..46d770739314cf081fea06cb98cb5ae9a62a8d0d
--- /dev/null
+++ b/base/common/src/com/netscape/cms/user/client/UserRestClient.java
@@ -0,0 +1,81 @@
+//--- BEGIN COPYRIGHT BLOCK ---
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; version 2 of the License.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//GNU General Public License for more details.
+//
+//You should have received a copy of the GNU General Public License along
+//with this program; if not, write to the Free Software Foundation, Inc.,
+//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+//(C) 2012 Red Hat, Inc.
+//All rights reserved.
+//--- END COPYRIGHT BLOCK ---
+package com.netscape.cms.user.client;
+
+import java.net.URISyntaxException;
+
+import com.netscape.cms.servlet.csadmin.CMSRestClient;
+import com.netscape.cms.user.UserCertCollection;
+import com.netscape.cms.user.UserCertData;
+import com.netscape.cms.user.UserCertResource;
+import com.netscape.cms.user.UserCollection;
+import com.netscape.cms.user.UserData;
+import com.netscape.cms.user.UserResource;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserRestClient extends CMSRestClient {
+
+    public UserResource userClient;
+    public UserCertResource userCertClient;
+
+    public UserRestClient(String baseUri) throws URISyntaxException {
+
+        super(baseUri);
+
+        userClient = createProxy(UserResource.class);
+        userCertClient = createProxy(UserCertResource.class);
+    }
+
+    public UserCollection findUsers(String filter, Integer start, Integer size) {
+        return userClient.findUsers(filter, start, size);
+    }
+
+    public UserData getUser(String userID) {
+        return userClient.getUser(userID);
+    }
+
+    public void addUser(UserData userData) {
+        userClient.addUser(userData);
+    }
+
+    public void modifyUser(String userID, UserData userData) {
+        userClient.modifyUser(userID, userData);
+    }
+
+    public void removeUser(String userID) {
+        userClient.removeUser(userID);
+    }
+
+    public UserCertCollection findUserCerts(String userID, Integer start, Integer size) {
+        return userCertClient.findUserCerts(userID, start, size);
+    }
+
+    public UserCertData getUserCert(String userID, String certID) {
+        return userCertClient.getUserCert(userID, certID);
+    }
+
+    public void addUserCert(String userID, UserCertData userCertData) {
+        userCertClient.addUserCert(userID, userCertData);
+    }
+
+    public void removeUserCert(String userID, String certID) {
+        userCertClient.removeUserCert(userID, certID);
+    }
+}
diff --git a/base/setup/CMakeLists.txt b/base/setup/CMakeLists.txt
index 05f18332ddddd8170709d97d969b109b41b0d0d0..54fe3b7f6515d31bef209e36021e61beb6bfd827 100644
--- a/base/setup/CMakeLists.txt
+++ b/base/setup/CMakeLists.txt
@@ -2,6 +2,7 @@ project(setup)
 
 install(
     FILES
+        pki
         pkicreate
         pkiremove
         pki-setup-proxy
diff --git a/base/setup/pki b/base/setup/pki
new file mode 100755
index 0000000000000000000000000000000000000000..da1f0fadf64f9092b9b2ce913fc6077547ad64af
--- /dev/null
+++ b/base/setup/pki
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+# --- BEGIN COPYRIGHT BLOCK ---
+# This library 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
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA  02110-1301  USA
+#
+# Copyright (C) 2007 Red Hat, Inc.
+# All rights reserved.
+# --- END COPYRIGHT BLOCK ---
+
+###############################################################################
+##  (1) Specify variables used by this script.                               ##
+###############################################################################
+
+my $PRODUCT="pki";
+my $libpath="";
+
+
+###############################################################################
+##  (2) Define helper functions.                                             ##
+###############################################################################
+
+sub invalid_architecture()
+{
+    print "\n";
+    print "ERROR:  pkisilent does not execute on this architecture\n";
+    print "ERROR:  check to make sure pki-native-tools package is installed!\n";
+    print "\n";
+}
+
+
+###############################################################################
+##  (3) Set the LD_LIBRARY_PATH environment variable                         ##
+##      (as well as the ${libpath} java property) to determine the           ##
+##      search order this command uses to find shared libraries.             ##
+###############################################################################
+
+my $ARCHITECTURE=`uname -i`;
+chop( $ARCHITECTURE );
+
+if( $ARCHITECTURE eq "i386" ) {
+    $libpath="/usr/lib";
+
+    $ENV{LD_LIBRARY_PATH} = "/usr/lib/jss:"
+                          . "/usr/lib:/lib";
+} elsif($ARCHITECTURE eq "x86_64") {
+    $libpath="/usr/lib64";
+
+    $ENV{LD_LIBRARY_PATH} = "/usr/lib64/jss:"
+                          . "/usr/lib64:/lib64:"
+                          . "/usr/lib/jss:"
+                          . "/usr/lib:/lib";
+} else {
+    invalid_architecture();
+    exit(255);
+}
+
+
+###############################################################################
+##  (4) Set the CP environment variable to determine the search              ##
+##      order this command wrapper uses to find jar files.                   ##
+###############################################################################
+
+$ENV{CLASSPATH} = "/usr/share/java/${PRODUCT}/pki-cms.jar:"
+                . "/usr/share/java/apache-commons-cli.jar:"
+                . "/usr/share/java/apache-commons-lang.jar:"
+                . "/usr/share/java/commons-httpclient.jar:"
+                . "/usr/share/java/httpcomponents/httpclient.jar:"
+                . "/usr/share/java/resteasy/jaxrs-api.jar:"
+                . "/usr/share/java/resteasy/resteasy-atom-provider.jar:"
+                . "/usr/share/java/resteasy/resteasy-jaxb-provider.jar:"
+                . "/usr/share/java/resteasy/resteasy-jaxrs.jar:"
+                . "/usr/share/java/servlet.jar:";
+
+if( $ARCHITECTURE eq "x86_64" ) {
+    $ENV{CLASSPATH} = $ENV{CLASSPATH}
+                    . "/usr/lib64/java/jss4.jar:"
+                    . "/usr/lib/java/jss4.jar:";
+} else {
+    $ENV{CLASSPATH} = $ENV{CLASSPATH}
+                    . "/usr/lib/java/jss4.jar:";
+}
+
+
+###############################################################################
+##  (5) Execute the java command specified by this java command wrapper      ##
+##      based upon the preset LD_LIBRARY_PATH and CP environment variables.  ##
+###############################################################################
+
+my @args = ();
+foreach (@ARGV) {
+    push(@args, quotemeta($_));
+}
+
+system("java -cp $ENV{CLASSPATH} com.netscape.cms.cli.MainCLI @args");
diff --git a/base/setup/pkicreate b/base/setup/pkicreate
index b5453f2f6e3ecd31d64f621b6bfb2f532b881ca6..6d3ebb978a0d71646e043c00d3f64d4efc6fd97f 100755
--- a/base/setup/pkicreate
+++ b/base/setup/pkicreate
@@ -193,6 +193,7 @@ my $resteasy_jaxb_provider_jar_base_name = "resteasy-jaxb-provider.jar";
 my $resteasy_jaxrs_jar_base_name  = "resteasy-jaxrs.jar";                          # CA, KRA, OCSP, TKS
 my $scannotation_jar_base_name    = "scannotation.jar";                            # CA, KRA, OCSP, TKS
 my $jettison_jar_base_name        = "jettison.jar";                                # CA, KRA, OCSP, TKS
+my $resteasy_atom_provider_jar_base_name = "resteasy-atom-provider.jar";           # CA, KRA, OCSP, TKS
 my $resteasy_jettison_provider_jar_base_name = "resteasy-jettison-provider.jar";   # CA, KRA, OCSP, TKS
 
 my $apache_commons_collections_jar_base_name  = "apache-commons-collections.jar";
@@ -487,6 +488,8 @@ my $scannotation_jar_file_path                   = undef;  # CA, KRA, OCSP, TKS
 my $scannotation_jar_symlink_path                = undef;  # CA, KRA, OCSP, TKS
 my $jettison_jar_file_path                       = undef;  # CA, KRA, OCSP, TKS
 my $jettison_jar_symlink_path                    = undef;  # CA, KRA, OCSP, TKS
+my $resteasy_atom_provider_jar_file_path         = undef;  # CA, KRA, OCSP, TKS
+my $resteasy_atom_provider_jar_symlink_path      = undef;  # CA, KRA, OCSP, TKS
 my $resteasy_jettison_provider_jar_file_path     = undef;  # CA, KRA, OCSP, TKS
 my $resteasy_jettison_provider_jar_symlink_path  = undef;  # CA, KRA, OCSP, TKS
 my $commons_collections_jar_file_path            = undef;  # CA, KRA, OCSP, TKS
@@ -1888,6 +1891,11 @@ sub initialize_subdirectory_paths
             return 0;
         }
 
+        if (!defined($resteasy_atom_provider_jar_file_path = find_jar($resteasy_atom_provider_jar_base_name))) {
+            emit("could not find jar: $resteasy_atom_provider_jar_base_name", "error");
+            return 0;
+        }
+
         if (!defined($resteasy_jettison_provider_jar_file_path = find_jar($resteasy_jettison_provider_jar_base_name))) {
             emit("could not find jar: $resteasy_jettison_provider_jar_base_name", "error");
             return 0;
@@ -1967,6 +1975,8 @@ sub initialize_subdirectory_paths
                                                      . "/" . $resteasy_jaxrs_jar_base_name;
         $scannotation_jar_symlink_path               = $webinf_lib_instance_path
                                                      . "/" . $scannotation_jar_base_name;
+        $resteasy_atom_provider_jar_symlink_path     = $webinf_lib_instance_path
+                                                     . "/" . $resteasy_atom_provider_jar_base_name;
         $resteasy_jettison_provider_jar_symlink_path = $webinf_lib_instance_path
                                                      . "/" . $resteasy_jettison_provider_jar_base_name;
         $jettison_jar_symlink_path                   = $webinf_lib_instance_path
@@ -3001,6 +3011,10 @@ sub process_pki_files_and_symlinks
         return 0 if !create_symlink($scannotation_jar_symlink_path, $scannotation_jar_file_path,
                                     $pki_user, $pki_group);
 
+        # create instance symlink to "pki-resteasy_atom_provider.jar"
+        return 0 if !create_symlink($resteasy_atom_provider_jar_symlink_path, $resteasy_atom_provider_jar_file_path,
+                                    $pki_user, $pki_group);
+
         # create instance symlink to "pki-resteasy_jettison_provider.jar"
         return 0 if !create_symlink($resteasy_jettison_provider_jar_symlink_path, $resteasy_jettison_provider_jar_file_path,
                                     $pki_user, $pki_group);
diff --git a/specs/pki-core.spec b/specs/pki-core.spec
index a38b904972fe594a77fac232500af1375a7a0499..9718c304914026965711189fb81a671ef455fb7a 100644
--- a/specs/pki-core.spec
+++ b/specs/pki-core.spec
@@ -14,7 +14,7 @@ distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
 
 Name:             pki-core
 Version:          10.0.0
-Release:          %{?relprefix}15%{?prerel}%{?dist}
+Release:          %{?relprefix}16%{?prerel}%{?dist}
 Summary:          Certificate System - PKI Core Components
 URL:              http://pki.fedoraproject.org/
 License:          GPLv2
@@ -1104,6 +1104,7 @@ fi
 %files -n pki-setup
 %defattr(-,root,root,-)
 %doc base/setup/LICENSE
+%{_bindir}/pki
 %{_bindir}/pkicreate
 %{_bindir}/pkiremove
 %{_bindir}/pki-setup-proxy
@@ -1349,6 +1350,9 @@ fi
 
 
 %changelog
+* Mon May 21 2012 Endi S. Dewata <edewata@redhat.com> 10.0.0-0.16.a1
+- Added CLI for REST services
+
 * Mon Apr 16 2012 Ade Lee <alee@redhat.com> 10.0.0-0.15.a1
 - BZ 813075 - selinux denial for file size access
 
-- 
1.7.7.6

