From c3e1d443c9098a48165130e8b6c245a83ce5bd4a Mon Sep 17 00:00:00 2001
From: Endi Sukma Dewata <edewata@redhat.com>
Date: Sat, 7 Jul 2012 00:14:13 -0500
Subject: [PATCH] Added support for basic authentication.

The CMSRestClient has been modified to support basic authentication
and handle HTTP redirection. The basic authentication can be used as
follows:

  pki -U <server uri> -u <username> -w <password> user-find

Some protected REST services might require secure connection. If the
user tries to call these services over HTTP the CLI will handle the
redirection automatically to an HTTPS port.

Ticket #107
---
 .../com/netscape/cms/client/cli/ClientConfig.java  |  16 +++
 .../src/com/netscape/cms/client/cli/MainCLI.java   |   8 ++
 .../cms/servlet/csadmin/CMSRestClient.java         | 131 ++++++++++++++++++---
 3 files changed, 140 insertions(+), 15 deletions(-)

diff --git a/base/common/src/com/netscape/cms/client/cli/ClientConfig.java b/base/common/src/com/netscape/cms/client/cli/ClientConfig.java
index bad8ba62698229cac265f04497538bbba8e074fe..8b53808055b4a08da49af9884aa1d98c4583b98e 100644
--- a/base/common/src/com/netscape/cms/client/cli/ClientConfig.java
+++ b/base/common/src/com/netscape/cms/client/cli/ClientConfig.java
@@ -52,6 +52,7 @@ public class ClientConfig {
 
     String certDatabase;
     String certNickname;
+    String username;
     String password;
 
     @XmlElement(name="ServerURI")
@@ -85,6 +86,15 @@ public class ClientConfig {
         this.certNickname = certNickname;
     }
 
+    @XmlElement(name="Username")
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
     @XmlElement(name="Password")
     public String getPassword() {
         return password;
@@ -102,6 +112,7 @@ public class ClientConfig {
         result = prime * result + ((certNickname == null) ? 0 : certNickname.hashCode());
         result = prime * result + ((password == null) ? 0 : password.hashCode());
         result = prime * result + ((serverURI == null) ? 0 : serverURI.hashCode());
+        result = prime * result + ((username == null) ? 0 : username.hashCode());
         return result;
     }
 
@@ -134,6 +145,11 @@ public class ClientConfig {
                 return false;
         } else if (!serverURI.equals(other.serverURI))
             return false;
+        if (username == null) {
+            if (other.username != null)
+                return false;
+        } else if (!username.equals(other.username))
+            return false;
         return true;
     }
 
diff --git a/base/common/src/com/netscape/cms/client/cli/MainCLI.java b/base/common/src/com/netscape/cms/client/cli/MainCLI.java
index 0367cbbfde9052d1f9e3b3d71b4284ab017b7af3..50c90d8928c3bb95fb45389e286ad1427a8ca955 100644
--- a/base/common/src/com/netscape/cms/client/cli/MainCLI.java
+++ b/base/common/src/com/netscape/cms/client/cli/MainCLI.java
@@ -107,6 +107,10 @@ public class MainCLI extends CLI {
         option.setArgName("nickname");
         options.addOption(option);
 
+        option = new Option("u", true, "Username");
+        option.setArgName("username");
+        options.addOption(option);
+
         option = new Option("w", true, "Password");
         option.setArgName("password");
         options.addOption(option);
@@ -131,6 +135,7 @@ public class MainCLI extends CLI {
 
         String certDatabase = cmd.getOptionValue("d");
         String certNickname = cmd.getOptionValue("n");
+        String username = cmd.getOptionValue("u");
         String password = cmd.getOptionValue("w");
 
         // convert into absolute path
@@ -140,6 +145,9 @@ public class MainCLI extends CLI {
         if (certNickname != null)
             config.setCertNickname(certNickname);
 
+        if (username != null)
+            config.setUsername(username);
+
         if (password != null)
             config.setPassword(password);
     }
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 6a38464dc2c92b744141fd2c748e812e51c3e6f5..2317eac47021c53ab89ddeac4e98aae7ddf3ed70 100644
--- a/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java
+++ b/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java
@@ -1,5 +1,6 @@
 package com.netscape.cms.servlet.csadmin;
 
+import java.io.File;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -7,18 +8,33 @@ import java.net.Socket;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.List;
 
 import org.apache.commons.httpclient.ConnectTimeoutException;
+import org.apache.http.Header;
+import org.apache.http.HttpEntityEnclosingRequest;
 import org.apache.http.HttpException;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpRequestInterceptor;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.ProtocolException;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.auth.params.AuthPNames;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.client.params.HttpClientParams;
 import org.apache.http.conn.scheme.LayeredSchemeSocketFactory;
 import org.apache.http.conn.scheme.Scheme;
 import org.apache.http.conn.scheme.SchemeSocketFactory;
+import org.apache.http.impl.client.ClientParamsStack;
 import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.DefaultRedirectStrategy;
+import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
+import org.apache.http.impl.client.RequestWrapper;
 import org.apache.http.params.HttpParams;
 import org.apache.http.protocol.HttpContext;
 import org.jboss.resteasy.client.ClientExecutor;
@@ -29,6 +45,8 @@ import org.jboss.resteasy.client.core.BaseClientResponse;
 import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
 import org.jboss.resteasy.client.core.extractors.ClientErrorHandler;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.mozilla.jss.CryptoManager;
+import org.mozilla.jss.crypto.AlreadyInitializedException;
 import org.mozilla.jss.ssl.SSLCertificateApprovalCallback;
 import org.mozilla.jss.ssl.SSLSocket;
 
@@ -47,26 +65,87 @@ public abstract class CMSRestClient {
     public CMSRestClient(ClientConfig config) {
         this.config = config;
 
-        DefaultHttpClient httpclient = new DefaultHttpClient();
+        DefaultHttpClient httpClient = new DefaultHttpClient();
 
-        httpclient.addRequestInterceptor(new HttpRequestInterceptor() {
+        // Register https scheme.
+        Scheme scheme = new Scheme("https", 443, new JSSProtocolSocketFactory());
+        httpClient.getConnectionManager().getSchemeRegistry().register(scheme);
+
+        if (config.getUsername() != null && config.getPassword() != null) {
+            List<String> authPref = new ArrayList<String>();
+            authPref.add(AuthPolicy.BASIC);
+            httpClient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authPref);
+
+            httpClient.getCredentialsProvider().setCredentials(
+                    AuthScope.ANY,
+                    new UsernamePasswordCredentials(config.getUsername(), config.getPassword()));
+        }
+
+        httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
             @Override
             public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
-                if (verbose) System.out.println("HTTP Request: "+request.getRequestLine());
+                if (verbose) {
+                    System.out.println("HTTP request: "+request.getRequestLine());
+                    for (Header header : request.getAllHeaders()) {
+                        System.out.println("  "+header.getName()+": "+header.getValue());
+                    }
+                }
+
+                // Set the request parameter to follow redirections.
+                HttpParams params = request.getParams();
+                if (params instanceof ClientParamsStack) {
+                    ClientParamsStack paramsStack = (ClientParamsStack)request.getParams();
+                    params = paramsStack.getRequestParams();
+                }
+                HttpClientParams.setRedirecting(params, true);
             }
         });
 
-        httpclient.addResponseInterceptor(new HttpResponseInterceptor() {
+        httpClient.addResponseInterceptor(new HttpResponseInterceptor() {
+            @Override
             public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
-                if (verbose) System.out.println("HTTP Response: "+response.getStatusLine());
+                if (verbose) {
+                    System.out.println("HTTP response: "+response.getStatusLine());
+                    for (Header header : response.getAllHeaders()) {
+                        System.out.println("  "+header.getName()+": "+header.getValue());
+                    }
+                }
             }
         });
 
-        // register https scheme
-        Scheme scheme = new Scheme("https", 443, new JSSProtocolSocketFactory());
-        httpclient.getConnectionManager().getSchemeRegistry().register(scheme);
+        httpClient.setRedirectStrategy(new DefaultRedirectStrategy() {
+            @Override
+            public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context)
+                    throws ProtocolException {
 
-        executor = new ApacheHttpClient4Executor(httpclient);
+                HttpUriRequest uriRequest = super.getRedirect(request, response, context);
+
+                URI uri = uriRequest.getURI();
+                if (verbose) System.out.println("HTTP redirect: "+uri);
+
+                // Redirect the original request to the new URI.
+                RequestWrapper wrapper;
+                if (request instanceof HttpEntityEnclosingRequest) {
+                    wrapper = new EntityEnclosingRequestWrapper((HttpEntityEnclosingRequest)request);
+                } else {
+                    wrapper = new RequestWrapper(request);
+                }
+                wrapper.setURI(uri);
+
+                return wrapper;
+            }
+
+            @Override
+            public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context)
+                    throws ProtocolException {
+
+                // The default redirection policy does not redirect POST or PUT.
+                // This overrides the policy to follow redirections for all HTTP methods.
+                return response.getStatusLine().getStatusCode() == 302;
+            }
+        });
+
+        executor = new ApacheHttpClient4Executor(httpClient);
         providerFactory = ResteasyProviderFactory.getInstance();
         providerFactory.addClientErrorInterceptor(new CMSErrorInterceptor());
         errorHandler = new ClientErrorHandler(providerFactory.getClientErrorInterceptors());
@@ -98,7 +177,7 @@ public abstract class CMSRestClient {
                 }
             }
 
-            //For other errors return false
+            // For other errors return false.
 
             return false;
         }
@@ -120,12 +199,34 @@ public abstract class CMSRestClient {
                 UnknownHostException,
                 ConnectTimeoutException {
 
+            // Initialize JSS before using SSLSocket,
+            // otherwise it will throw UnsatisfiedLinkError.
+            if (config.getCertDatabase() == null) {
+                try {
+                    // No database specified, use $HOME/.pki/nssdb.
+                    File homeDir = new File(System.getProperty("user.home"));
+                    File pkiDir = new File(homeDir, ".pki");
+                    File nssdbDir = new File(pkiDir, "nssdb");
+                    nssdbDir.mkdirs();
+
+                    CryptoManager.initialize(nssdbDir.getAbsolutePath());
+
+                } catch (AlreadyInitializedException e) {
+                    // ignore
+
+                } catch (Exception e) {
+                    throw new Error(e);
+                }
+
+            } else {
+                // Database specified, already initialized by the main program.
+            }
+
             String hostName = null;
             int port = 0;
             if (remoteAddress != null) {
                 hostName = remoteAddress.getHostName();
                 port = remoteAddress.getPort();
-
             }
 
             int localPort = 0;
@@ -160,14 +261,14 @@ public abstract class CMSRestClient {
 
         @Override
         public boolean isSecure(Socket sock) {
-            //We only use this factory in the case of SSL Connections
+            // We only use this factory in the case of SSL Connections.
             return true;
         }
 
         @Override
-        public Socket createLayeredSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException,
-                UnknownHostException {
-            //This method implementation is required to get SSL working.
+        public Socket createLayeredSocket(Socket socket, String target, int port, boolean autoClose)
+                throws IOException, UnknownHostException {
+            // This method implementation is required to get SSL working.
             return null;
         }
 
-- 
1.7.11.2

