[Pki-devel] CLI for editing profiles

Fraser Tweedale ftweedal at redhat.com
Fri Sep 19 07:46:44 UTC 2014


Third cut of the LDAP profiles feature.  All comments from previous
reviews have been addressed in this patchset.

The switch from byte[] to Properties in the ProfileClient API (item
#13 from Endi's first review) is currently in a separte patch
(#0014) to make it clear what had to happen for this change, and to
make it easy to change or remove if there are problems.  If it's
preferred to squash it when acked, let me know (or it can stay
separate - I don't mind either way).

Cheers,

Fraser
-------------- next part --------------
>From 7efcd8d7182edebdd47ff4529498cf51fddcae50 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Mon, 7 Jul 2014 23:35:35 -0400
Subject: [PATCH 04/10] Add schema for LDAP-based profiles

---
 base/ca/shared/conf/db.ldif     |  5 ++++-
 base/ca/shared/conf/schema.ldif | 15 +++++++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif
index 00fa919b7df38ed97f0bc21b5616a9998845c7d4..8a2e0b07274a83b317fb1ba56e8ef32b96857118 100644
--- a/base/ca/shared/conf/db.ldif
+++ b/base/ca/shared/conf/db.ldif
@@ -160,4 +160,7 @@ objectClass: top
 objectClass: organizationalUnit
 ou: certificateRepository
 
-
+dn: ou=certificateProfiles,ou=ca,{rootSuffix}
+objectClass: top
+objectClass: organizationalUnit
+ou: certificateProfiles
diff --git a/base/ca/shared/conf/schema.ldif b/base/ca/shared/conf/schema.ldif
index 70578e21ce4e102909a1b7b45fa84c184a997bdf..9d62f4e88fef1b79f2b2e7df6439add428b37af0 100644
--- a/base/ca/shared/conf/schema.ldif
+++ b/base/ca/shared/conf/schema.ldif
@@ -487,3 +487,18 @@ dn: cn=schema
 changetype: modify
 add: objectClasses
 objectClasses: ( securityDomainSessionEntry-oid NAME 'securityDomainSessionEntry' DESC 'CMS defined class' SUP top STRUCTURAL MUST ( cn $ host $ uid $ cmsUserGroup $ dateOfCreate ) X-ORIGIN 'user defined' ) 
+
+dn: cn=schema
+changetype: modify
+add: attributeTypes
+attributeTypes: ( classId-oid NAME 'classId' DESC 'CMS defined attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'user defined' )
+
+dn: cn=schema
+changetype: modify
+add: attributeTypes
+attributeTypes: ( certProfileConfig-oid NAME 'certProfileConfig' DESC 'CMS defined attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' )
+
+dn: cn=schema
+changetype: modify
+add: objectClasses
+objectClasses: ( certProfile-oid NAME 'certProfile' DESC 'CMS defined class' SUP top STRUCTURAL MUST cn MAY ( classId $ certProfileConfig ) X-ORIGIN 'user defined' )
-- 
1.9.3

-------------- next part --------------
>From 3f0b927d1f179afebba44e2b77e282ae5914fc46 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Tue, 15 Jul 2014 02:48:35 -0400
Subject: [PATCH 05/10] Add LDAPConfigStore class

The LDAPConfigStore class is an IConfigStore that reads and writes
its configuration to a given attribute and DN in an LDAP database.
---
 .../com/netscape/cmscore/base/LDAPConfigStore.java | 195 +++++++++++++++++++++
 1 file changed, 195 insertions(+)
 create mode 100644 base/server/cmscore/src/com/netscape/cmscore/base/LDAPConfigStore.java

diff --git a/base/server/cmscore/src/com/netscape/cmscore/base/LDAPConfigStore.java b/base/server/cmscore/src/com/netscape/cmscore/base/LDAPConfigStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..5aeb459508d73557a165a6a8cde4696746bfa47d
--- /dev/null
+++ b/base/server/cmscore/src/com/netscape/cmscore/base/LDAPConfigStore.java
@@ -0,0 +1,195 @@
+// --- 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) 2007, 2014 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmscore.base;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Map;
+
+import netscape.ldap.LDAPAttribute;
+import netscape.ldap.LDAPAttributeSet;
+import netscape.ldap.LDAPConnection;
+import netscape.ldap.LDAPEntry;
+import netscape.ldap.LDAPException;
+import netscape.ldap.LDAPModification;
+
+import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.IConfigStore;
+import com.netscape.certsrv.ldap.ILdapConnFactory;
+
+/**
+ * LDAPConfigStore:
+ * Extends PropConfigStore with methods to load/save from/to file for
+ * persistent storage. This is a configuration store agent who
+ * reads data from an LDAP entry.
+ * <P>
+ *
+ * @version $Revision$, $Date$
+ * @see PropConfigStore
+ */
+public class LDAPConfigStore extends PropConfigStore implements IConfigStore {
+
+    private ILdapConnFactory dbFactory;
+    private String dn;
+    private String attr;
+    private LDAPAttribute[] createAttrs;
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 3642124526598175633L;
+
+    /**
+     * Constructs an LDAP configuration store.
+     * <P>
+     *
+     * @param dbFactory Database connection factory
+     * @param dn Distinguished name of record containing config store
+     * @param attr Name of attribute containing config store
+     * @param createAttrs Set of initial attributes if creating the entry.  Should
+     *              contain cn, objectclass and possibly other attributes.
+     *
+     * @exception EBaseException failed to create file configuration
+     */
+    public LDAPConfigStore(
+        ILdapConnFactory dbFactory,
+        String dn, LDAPAttribute[] createAttrs, String attr
+    ) throws EBaseException {
+        super(null);  // top-level store without a name
+
+        this.dbFactory = dbFactory;
+        this.dn = dn;
+        this.createAttrs = createAttrs;
+        this.attr = attr;
+
+        LDAPConnection conn = dbFactory.getConn();
+
+        String[] readAttrs = {attr};
+        try {
+            LDAPEntry ldapEntry = conn.read(dn, readAttrs);
+
+            Enumeration<String> vals = ldapEntry.getAttribute(attr).getStringValues();
+            InputStream data = new ByteArrayInputStream(vals.nextElement().getBytes());
+            load(data);
+        } catch (LDAPException e) {
+            // if there is no such object, we will create it on commit()
+            if (e.getLDAPResultCode() != LDAPException.NO_SUCH_OBJECT) {
+                throw new EBaseException(
+                    "Error reading LDAPConfigStore '"
+                    + dn + "': " + e.toString()
+                );
+            }
+        } catch (IOException e) {
+            throw new EBaseException(
+                "Error reading LDAPConfigStore '"
+                + dn + "': " + e.toString()
+            );
+        } finally {
+            dbFactory.returnConn(conn);
+        }
+    }
+
+    @Override
+    public void save(OutputStream out, String header) {
+        try (PrintWriter writer = new PrintWriter(out)) {
+            Map<String, String> map = getProperties();
+            for (String k : map.keySet()) {
+                writer.println(k + "=" + map.get(k));
+            }
+        }
+    }
+
+    /**
+     * Commit the configuration to the database.
+     *
+     * All uses of LDAPProfileStore at time of writing call with
+     * backup=false, so the argument is ignored.
+     *
+     * If backup becomes necessary, the constructor should be
+     * modified to take a String backupAttr, and the existing
+     * content be copied to that attribute.
+     *
+     * @param backup Ignored.
+     */
+    public void commit(boolean createBackup) throws EBaseException {
+        ByteArrayOutputStream data = new ByteArrayOutputStream();
+        save(data, null);
+
+        LDAPAttribute configAttr = new LDAPAttribute(attr, data.toByteArray());
+
+        LDAPConnection conn = dbFactory.getConn();
+
+        // first attempt to modify; if modification fails (due
+        // to no such object), try and add the entry instead.
+        try {
+            try {
+                commitModify(conn, configAttr);
+            } catch (LDAPException e) {
+                if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) {
+                    commitAdd(conn, configAttr);
+                } else {
+                    throw e;
+                }
+            }
+        } catch (LDAPException e) {
+            throw new EBaseException(
+                "Error writing LDAPConfigStore '"
+                + dn + "': " + e.toString()
+            );
+        } finally {
+            dbFactory.returnConn(conn);
+        }
+    }
+
+    /**
+     * Update the record via an LDAPModification.
+     *
+     * @param conn LDAP connection.
+     * @param configAttr Config store attribute.
+     * @return true on success, false if the entry does not exist.
+     */
+    private void commitModify(LDAPConnection conn, LDAPAttribute configAttr)
+        throws LDAPException
+    {
+        LDAPModification ldapMod =
+            new LDAPModification(LDAPModification.REPLACE, configAttr);
+        conn.modify(dn, ldapMod);
+    }
+
+    /**
+     * Add the LDAPEntry via LDAPConnection.add.
+     *
+     * @param conn LDAP connection.
+     * @param configAttr Config store attribute.
+     * @return true on success, false if the entry already exists.
+     */
+    private void commitAdd(LDAPConnection conn, LDAPAttribute configAttr)
+        throws LDAPException
+    {
+        LDAPAttributeSet attrSet = new LDAPAttributeSet(createAttrs);
+        attrSet.add(configAttr);
+        LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet);
+        conn.add(ldapEntry);
+    }
+}
-- 
1.9.3

-------------- next part --------------
>From 5821f04ec7af44375812b282dbf49d8de8b4b184 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Thu, 17 Jul 2014 00:24:06 -0400
Subject: [PATCH 06/10] Change ProfileSubsystem to use LDAP database

---
 .../dogtagpki/server/ca/rest/ProfileService.java   |  16 +-
 .../certsrv/profile/IProfileSubsystem.java         |  19 +--
 .../cms/servlet/admin/ProfileAdminServlet.java     |  30 +---
 base/server/cmsbundle/src/UserMessages.properties  |   2 +
 .../com/netscape/cmscore/base/FileConfigStore.java |   4 +-
 .../netscape/cmscore/profile/ProfileSubsystem.java | 184 ++++++++++++---------
 6 files changed, 114 insertions(+), 141 deletions(-)

diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java b/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java
index 3b2f8a50ebcd18fe0098b2e92e0300645b904fa3..d3f08b270fd66154da880d47be30ea48716b75bd 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java
@@ -18,7 +18,6 @@
 
 package org.dogtagpki.server.ca.rest;
 
-import java.io.File;
 import java.io.IOException;
 import java.net.URI;
 import java.security.Principal;
@@ -244,7 +243,7 @@ public class ProfileService extends PKIService implements ProfileResource {
 
         data.setAuthenticatorId(profile.getAuthenticatorId());
         data.setAuthzAcl(profile.getAuthzAcl());
-        data.setClassId(cs.getString(profileId + ".class_id"));
+        data.setClassId(ps.getProfileClassId(profileId));
         data.setDescription(profile.getDescription(getLocale(headers)));
         data.setEnabled(ps.isProfileEnable(profileId));
         data.setEnabledBy(ps.getProfileEnableBy(profileId));
@@ -472,18 +471,13 @@ public class ProfileService extends PKIService implements ProfileResource {
             auditParams.put("description", data.getDescription());
             auditParams.put("visible", Boolean.toString(data.isVisible()));
 
-            String config = CMS.getConfigStore().getString("instanceRoot") + "/ca/profiles/ca/" +
-                    profileId + ".cfg";
-            File configFile = new File(config);
-            configFile.createNewFile();
             IPluginInfo info = registry.getPluginInfo("profile", data.getClassId());
 
-            profile = ps.createProfile(profileId, data.getClassId(), info.getClassName(), config);
+            profile = ps.createProfile(profileId, data.getClassId(), info.getClassName());
             profile.setName(getLocale(headers), data.getName());
             profile.setDescription(getLocale(headers), data.getDescription());
             profile.setVisible(data.isVisible());
             profile.getConfigStore().commit(false);
-            ps.createProfileConfig(profileId, data.getClassId(), config);
 
             if (profile instanceof IProfileEx) {
                 // populates profile specific plugins such as
@@ -504,7 +498,7 @@ public class ProfileService extends PKIService implements ProfileResource {
 
             return createCreatedResponse(profileData, profileData.getLink().getHref());
 
-        } catch (EBaseException | IOException e) {
+        } catch (EBaseException e) {
             CMS.debug("createProfile: error in creating profile: " + e);
             e.printStackTrace();
 
@@ -983,9 +977,7 @@ public class ProfileService extends PKIService implements ProfileResource {
                         "`.  Profile must be disabled first.");
             }
 
-            String configFile = CMS.getConfigStore().getString("profile." + profileId + ".config");
-
-            ps.deleteProfile(profileId, configFile);
+            ps.deleteProfile(profileId);
 
             auditProfileChange(
                     ScopeDef.SC_PROFILE_RULES,
diff --git a/base/common/src/com/netscape/certsrv/profile/IProfileSubsystem.java b/base/common/src/com/netscape/certsrv/profile/IProfileSubsystem.java
index 3238fb2e686cd3f9aa42dac7997cacd8f7c4c06c..b7071fe7526132d7f9ff1945819f0d1f67c18719 100644
--- a/base/common/src/com/netscape/certsrv/profile/IProfileSubsystem.java
+++ b/base/common/src/com/netscape/certsrv/profile/IProfileSubsystem.java
@@ -61,33 +61,18 @@ public interface IProfileSubsystem extends ISubsystem {
      * @param id profile id
      * @param classid implementation id
      * @param className class Name
-     * @param configFile configuration file
      * @exception EProfileException failed to create profile
      */
-    public IProfile createProfile(String id, String classid,
-            String className, String configFile)
+    public IProfile createProfile(String id, String classid, String className)
             throws EProfileException;
 
     /**
      * Deletes profile.
      *
      * @param id profile id
-     * @param configFile configuration file
      * @exception EProfileException failed to delete profile
      */
-    public void deleteProfile(String id, String configFile)
-            throws EProfileException;
-
-    /**
-     * Creates a new profile configuration file.
-     *
-     * @param id profile id
-     * @param classId implementation id
-     * @param configPath location to create the configuration file
-     * @exception failed to create profile
-     */
-    public void createProfileConfig(String id, String classId,
-            String configPath) throws EProfileException;
+    public void deleteProfile(String id) throws EProfileException;
 
     /**
      * Enables a profile.
diff --git a/base/server/cms/src/com/netscape/cms/servlet/admin/ProfileAdminServlet.java b/base/server/cms/src/com/netscape/cms/servlet/admin/ProfileAdminServlet.java
index 3d25b8b71bc7f71a7d579aa6d27bcdc623e49e28..b418baf41a0f84ce5d5ac9da56efd191e1a20316 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/admin/ProfileAdminServlet.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/admin/ProfileAdminServlet.java
@@ -17,7 +17,6 @@
 // --- END COPYRIGHT BLOCK ---
 package com.netscape.cms.servlet.admin;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.Enumeration;
 import java.util.StringTokenizer;
@@ -2290,26 +2289,8 @@ public class ProfileAdminServlet extends AdminServlet {
                 return;
             }
 
-            String config = null;
-
             try {
-                config = CMS.getConfigStore().getString("profile." + id + ".config");
-            } catch (EBaseException e) {
-                // store a message in the signed audit log file
-                auditMessage = CMS.getLogMessage(
-                        LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE,
-                        auditSubjectID,
-                        ILogger.FAILURE,
-                        auditParams(req));
-
-                audit(auditMessage);
-
-                sendResponse(ERROR, null, null, resp);
-                return;
-            }
-
-            try {
-                mProfileSub.deleteProfile(id, config);
+                mProfileSub.deleteProfile(id);
             } catch (EProfileException e) {
                 // store a message in the signed audit log file
                 auditMessage = CMS.getLogMessage(
@@ -2475,16 +2456,10 @@ public class ProfileAdminServlet extends AdminServlet {
 
             IProfile profile = null;
 
-            // create configuration file
-            File configFile = new File(config);
-
-            configFile.createNewFile();
-
             // create profile
             try {
                 profile = mProfileSub.createProfile(id, impl,
-                            info.getClassName(),
-                            config);
+                            info.getClassName());
                 profile.setName(getLocale(req), name);
                 profile.setDescription(getLocale(req), name);
                 if (visible != null && visible.equals("true")) {
@@ -2495,7 +2470,6 @@ public class ProfileAdminServlet extends AdminServlet {
                 profile.setAuthenticatorId(auth);
                 profile.getConfigStore().commit(false);
 
-                mProfileSub.createProfileConfig(id, impl, config);
                 if (profile instanceof IProfileEx) {
                     // populates profile specific plugins such as
                     // policies, inputs and outputs
diff --git a/base/server/cmsbundle/src/UserMessages.properties b/base/server/cmsbundle/src/UserMessages.properties
index fe43094e6b2a0531502570bc626da557fc9061ae..cd7fa18bfb1e17bc4ab4aa9e0dac06f815861291 100644
--- a/base/server/cmsbundle/src/UserMessages.properties
+++ b/base/server/cmsbundle/src/UserMessages.properties
@@ -754,6 +754,8 @@ CMS_PROFILE_CONFIG_KEY_USAGE_EXTENSION_CHECKING=Allow duplicate subject names wi
 CMS_PROFILE_INTERNAL_ERROR=Profile internal error: {0}
 CMS_PROFILE_DENY_OPERATION=Not authorized to do this operation.
 CMS_PROFILE_DELETE_ENABLEPROFILE=Cannot delete enabled profile: {0}
+CMS_PROFILE_DELETE_UNKNOWNPROFILE=Cannot delete unknown profile: {0}
+CMS_PROFILE_DELETE_DATABASEERROR=Failed to delete profile: {0}
 CMS_PROFILE_INVALID_REQUEST=Invalid Request
 CMS_PROFILE_EMPTY_REQUEST_TYPE=Request type is not specified. Check your profile input.
 CMS_PROFILE_CREATE_POLICY_FAILED=Failed to create profile policy: {0}
diff --git a/base/server/cmscore/src/com/netscape/cmscore/base/FileConfigStore.java b/base/server/cmscore/src/com/netscape/cmscore/base/FileConfigStore.java
index b77f86d781995e27bb0fe16135fc45a7d6fc4da3..4f8cb2743fdecc354338042a5219a9aaf6e27880 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/base/FileConfigStore.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/base/FileConfigStore.java
@@ -33,12 +33,10 @@ import com.netscape.cmsutil.util.Utils;
 
 /**
  * FileConfigStore:
- * Extends HashConfigStore with methods to load/save from/to file for
+ * Extends PropConfigStore with methods to load/save from/to file for
  * persistent storage. This is a configuration store agent who
  * reads data from a file.
  * <P>
- * Note that a LdapConfigStore can be implemented so that it reads the configuration stores from the Ldap directory.
- * <P>
  *
  * @version $Revision$, $Date$
  * @see PropConfigStore
diff --git a/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java b/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java
index 27e72352ef22c742b5ea09a180d440d58452dd49..ca0e09b785877da2a98fd2dd54977b8d3ebeaa24 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java
@@ -17,26 +17,30 @@
 // --- END COPYRIGHT BLOCK ---
 package com.netscape.cmscore.profile;
 
-import java.io.File;
 import java.util.Enumeration;
 import java.util.Hashtable;
-import java.util.StringTokenizer;
 import java.util.Vector;
 
+import netscape.ldap.LDAPAttribute;
+import netscape.ldap.LDAPConnection;
+import netscape.ldap.LDAPEntry;
+import netscape.ldap.LDAPException;
+import netscape.ldap.LDAPSearchResults;
+
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.base.ISubsystem;
+import com.netscape.certsrv.ldap.ELdapException;
+import com.netscape.certsrv.ldap.ILdapConnFactory;
 import com.netscape.certsrv.profile.EProfileException;
 import com.netscape.certsrv.profile.IProfile;
 import com.netscape.certsrv.profile.IProfileSubsystem;
 import com.netscape.certsrv.registry.IPluginInfo;
 import com.netscape.certsrv.registry.IPluginRegistry;
+import com.netscape.cmscore.base.LDAPConfigStore;
 
 public class ProfileSubsystem implements IProfileSubsystem {
-    private static final String PROP_LIST = "list";
-    private static final String PROP_CLASS_ID = "class_id";
-    private static final String PROP_CONFIG = "config";
     private static final String PROP_CHECK_OWNER = "checkOwner";
 
     private static final String PROP_ENABLE = "enable";
@@ -45,9 +49,11 @@ public class ProfileSubsystem implements IProfileSubsystem {
     private IConfigStore mConfig = null;
     @SuppressWarnings("unused")
     private ISubsystem mOwner;
-    private Vector<String> mProfileIds = new Vector<String>();
-    private Hashtable<String, IProfile> mProfiles = new Hashtable<String, IProfile>();
-    private Hashtable<String, String> mProfileClassIds = new Hashtable<String, String>();
+    private Vector<String> mProfileIds;
+    private Hashtable<String, IProfile> mProfiles;
+    private Hashtable<String, String> mProfileClassIds;
+
+    private ILdapConnFactory dbFactory;
 
     /**
      * Retrieves the name of this subsystem.
@@ -74,9 +80,20 @@ public class ProfileSubsystem implements IProfileSubsystem {
     public void init(ISubsystem owner, IConfigStore config)
             throws EBaseException {
         CMS.debug("ProfileSubsystem: start init");
+
+        // (re)init member collections
+        mProfileIds = new Vector<String>();
+        mProfiles = new Hashtable<String, IProfile>();
+        mProfileClassIds = new Hashtable<String, String>();
+
         IPluginRegistry registry = (IPluginRegistry)
                 CMS.getSubsystem(CMS.SUBSYSTEM_REGISTRY);
 
+        IConfigStore cs = CMS.getConfigStore();
+        IConfigStore dbCfg = cs.getSubStore("internaldb");
+        dbFactory = CMS.getLdapBoundConnFactory();
+        dbFactory.init(dbCfg);
+
         mConfig = config;
         mOwner = owner;
 
@@ -88,24 +105,46 @@ public class ProfileSubsystem implements IProfileSubsystem {
         // *.profile2.config=config/profiles/profile2.cfg
 
         // read profile id, implementation, and its configuration files
-        String ids = config.getString(PROP_LIST, "");
-        StringTokenizer st = new StringTokenizer(ids, ",");
+        String basedn = cs.getString("internaldb.basedn");
+        String dn = "ou=certificateProfiles,ou=ca," + basedn;
+        LDAPConnection conn = dbFactory.getConn();
 
-        while (st.hasMoreTokens()) {
-            String id = st.nextToken();
-            IConfigStore subStore = config.getSubStore(id);
-            String classid = subStore.getString(PROP_CLASS_ID);
-            IPluginInfo info = registry.getPluginInfo("profile", classid);
-            if (info == null) {
-                throw new EBaseException("No plugins for type : profile, with id " + classid);
-            }
-            String configPath = subStore.getString(PROP_CONFIG);
+        String[] attrs = {"cn", "classId"};
+        try {
+            LDAPSearchResults ldapProfiles = conn.search(
+                dn, LDAPConnection.SCOPE_ONE, "(objectclass=*)", attrs, false);
+
+            while (ldapProfiles.hasMoreElements()) {
+                String id = "<unknown>";
+                try {
+                    LDAPEntry ldapProfile = ldapProfiles.next();
+
+                    id = (String)
+                        ldapProfile.getAttribute("cn").getStringValues().nextElement();
 
-            CMS.debug("Start Profile Creation - " + id + " " + classid + " " + info.getClassName());
-            createProfile(id, classid, info.getClassName(),
-                    configPath);
+                    String classid = (String)
+                        ldapProfile.getAttribute("classId").getStringValues().nextElement();
 
-            CMS.debug("Done Profile Creation - " + id);
+                    IPluginInfo info = registry.getPluginInfo("profile", classid);
+                    if (info == null) {
+                        CMS.debug("Error loading profile: No plugins for type : profile, with id " + classid);
+                    } else {
+                        CMS.debug("Start Profile Creation - " + id + " " + classid + " " + info.getClassName());
+                        createProfile(id, classid, info.getClassName());
+                        CMS.debug("Done Profile Creation - " + id);
+                    }
+                } catch (LDAPException e) {
+                    CMS.debug("Error reading profile '" + id + "'; skipping.");
+                }
+            }
+        } catch (LDAPException e) {
+            throw new EBaseException("Error reading profiles: " + e.toString());
+        } finally {
+            try {
+                dbFactory.returnConn(conn);
+            } catch (Exception e) {
+                throw new EProfileException("Error releasing the ldap connection" + e.toString());
+            }
         }
 
         Enumeration<String> ee = getProfileIds();
@@ -120,16 +159,21 @@ public class ProfileSubsystem implements IProfileSubsystem {
     /**
      * Creates a profile instance.
      */
-    public IProfile createProfile(String id, String classid, String className,
-            String configPath)
+    public IProfile createProfile(String id, String classid, String className)
             throws EProfileException {
-        IProfile profile = null;
-
         try {
-            profile = (IProfile) Class.forName(className).newInstance();
-            IConfigStore subStoreConfig = CMS.createFileConfigStore(configPath);
+            String[] objectClasses = {"top", "certProfile"};
+            LDAPAttribute[] createAttrs = {
+                new LDAPAttribute("objectclass", objectClasses),
+                new LDAPAttribute("cn", id),
+                new LDAPAttribute("classId", classid)
+            };
+
+            IConfigStore subStoreConfig = new LDAPConfigStore(
+                dbFactory, createProfileDN(id), createAttrs, "certProfileConfig");
 
             CMS.debug("ProfileSubsystem: initing " + className);
+            IProfile profile = (IProfile) Class.forName(className).newInstance();
             profile.setId(id);
             profile.init(this, subStoreConfig);
             mProfileIds.addElement(id);
@@ -144,63 +188,32 @@ public class ProfileSubsystem implements IProfileSubsystem {
         return null;
     }
 
-    public void deleteProfile(String id, String configPath) throws EProfileException {
-
+    public void deleteProfile(String id) throws EProfileException {
         if (isProfileEnable(id)) {
             throw new EProfileException("CMS_PROFILE_DELETE_ENABLEPROFILE");
         }
 
-        String ids = "";
+        LDAPConnection conn;
         try {
-            ids = mConfig.getString(PROP_LIST, "");
-        } catch (Exception e) {
+            conn = dbFactory.getConn();
+        } catch (ELdapException e) {
+            throw new EProfileException("Error acquiring the ldap connection" + e.toString());
         }
-
-        StringTokenizer tokenizer = new StringTokenizer(ids, ",");
-        StringBuffer list = new StringBuffer();
-
-        while (tokenizer.hasMoreTokens()) {
-            String element = tokenizer.nextToken();
-
-            if (!element.equals(id)) {
-                list.append(element + ",");
+        try {
+            conn.delete(createProfileDN(id));
+        } catch (LDAPException e) {
+            throw new EProfileException("CMS_PROFILE_DELETE_DATABASEERROR");
+        } finally {
+            try {
+                dbFactory.returnConn(conn);
+            } catch (Exception e) {
+                throw new EProfileException("Error releasing the ldap connection" + e.toString());
             }
         }
-        if (list.length() != 0)
-            list.deleteCharAt(list.length() - 1);
 
-        mConfig.putString(PROP_LIST, list.toString());
-        mConfig.removeSubStore(id);
-        File file1 = new File(configPath);
-
-        if (!file1.delete()) {
-            CMS.debug("ProfileSubsystem: deleteProfile: Cannot delete the configuration file : " + configPath);
-        }
         mProfileIds.removeElement(id);
         mProfiles.remove(id);
         mProfileClassIds.remove(id);
-        try {
-            CMS.getConfigStore().commit(false);
-        } catch (Exception e) {
-        }
-    }
-
-    public void createProfileConfig(String id, String classId,
-            String configPath)
-            throws EProfileException {
-        try {
-            if (mProfiles.size() > 0) {
-                mConfig.putString(PROP_LIST,
-                        mConfig.getString(PROP_LIST) + "," + id);
-            } else {
-                mConfig.putString(PROP_LIST, id);
-            }
-            mConfig.putString(id + "." + PROP_CLASS_ID, classId);
-            mConfig.putString(id + "." + PROP_CONFIG, configPath);
-            CMS.getConfigStore().commit(true);
-        } catch (EBaseException e) {
-            CMS.debug(e.toString());
-        }
     }
 
     /**
@@ -231,13 +244,6 @@ public class ProfileSubsystem implements IProfileSubsystem {
         return mConfig;
     }
 
-    /**
-     * Adds a profile.
-     */
-    public void addProfile(String id, IProfile profile)
-            throws EProfileException {
-    }
-
     public boolean isProfileEnable(String id) {
         IProfile profile = mProfiles.get(id);
         String enable = null;
@@ -326,4 +332,20 @@ public class ProfileSubsystem implements IProfileSubsystem {
             return false;
         }
     }
+
+    /**
+     * Compute the profile DN given an ID.
+     */
+    private String createProfileDN(String id) throws EProfileException {
+        if (id == null) {
+            throw new EProfileException("CMS_PROFILE_DELETE_UNKNOWNPROFILE");
+        }
+        String basedn;
+        try {
+            basedn = CMS.getConfigStore().getString("internaldb.basedn");
+        } catch (EBaseException e) {
+            throw new EProfileException("CMS_PROFILE_DELETE_UNKNOWNPROFILE");
+        }
+        return "cn=" + id + ",ou=certificateProfiles,ou=ca," + basedn;
+    }
 }
-- 
1.9.3

-------------- next part --------------
>From 34b16e2dff3357c3173308dd8910a8b29b8d3bb2 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Tue, 22 Jul 2014 00:03:47 -0400
Subject: [PATCH 07/10] Add ability to enable/disable dynamic subsystems

The CA installation process requires starting with the profile
subsystem disabled, then enabling it once profiles have been loaded
into the database.  Accordingly, to avoid hacks with hardcoded
offsets, add the "enabled" CS.cfg configuration parameter along with
methods to enable or disable a subsystem based on the subsystem ID.

Subsystems are enabled by default.

This commit also removes an assumption that the subsystem config
sub-store names are sequential numbers beginning at `0'.
---
 base/common/src/com/netscape/certsrv/apps/CMS.java | 22 ++++++++
 .../src/com/netscape/certsrv/apps/ICMSEngine.java  |  9 +++
 .../src/com/netscape/cmscore/apps/CMSEngine.java   | 64 ++++++++++++++++------
 .../netscape/cmscore/app/CMSEngineDefaultStub.java |  3 +
 4 files changed, 82 insertions(+), 16 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/apps/CMS.java b/base/common/src/com/netscape/certsrv/apps/CMS.java
index 63c1a2cbde2ffdb0ce116c664ab373aeba91f5a3..38a69945be2100cf3538c3924f00fa9e7c409cc7 100644
--- a/base/common/src/com/netscape/certsrv/apps/CMS.java
+++ b/base/common/src/com/netscape/certsrv/apps/CMS.java
@@ -511,6 +511,28 @@ public final class CMS {
     }
 
     /**
+     * Enable the subsystem with the given ID.
+     *
+     * Does not start the subsystem.
+     *
+     * @param id Subsystem ID.
+     */
+    public static void enableSubsystem(String id) throws EBaseException {
+        _engine.setSubsystemEnabled(id, true);
+    }
+
+    /**
+     * Disable the subsystem with the given ID.
+     *
+     * Does not stop the subsystem.
+     *
+     * @param id Subsystem ID.
+     */
+    public static void disableSubsystem(String id) throws EBaseException {
+        _engine.setSubsystemEnabled(id, false);
+    }
+
+    /**
      * Retrieves the localized user message from UserMessages.properties.
      *
      * @param msgID message id defined in UserMessages.properties
diff --git a/base/common/src/com/netscape/certsrv/apps/ICMSEngine.java b/base/common/src/com/netscape/certsrv/apps/ICMSEngine.java
index 74fa090038b892713c872025470e4c4d9cb87760..5c78a7c0f9c4144df8369a6017876f900e57605d 100644
--- a/base/common/src/com/netscape/certsrv/apps/ICMSEngine.java
+++ b/base/common/src/com/netscape/certsrv/apps/ICMSEngine.java
@@ -171,6 +171,15 @@ public interface ICMSEngine extends ISubsystem {
     public Enumeration<ISubsystem> getSubsystems();
 
     /**
+     * Set whether the given subsystem is enabled.
+     *
+     * @param id The subsystem ID.
+     * @param enabled Whether the subsystem is enabled
+     */
+    public void setSubsystemEnabled(String id, boolean enabled)
+        throws EBaseException;
+
+    /**
      * Retrieves the registered subsytem with the given name.
      *
      * @param name subsystem name
diff --git a/base/server/cmscore/src/com/netscape/cmscore/apps/CMSEngine.java b/base/server/cmscore/src/com/netscape/cmscore/apps/CMSEngine.java
index 68c64824e37bcad282a5bbeabf6b943fabf39481..2641057d9ad6d4de751f7215d52374b08eceb1f8 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/apps/CMSEngine.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/apps/CMSEngine.java
@@ -29,6 +29,7 @@ import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509CRL;
 import java.security.cert.X509Certificate;
 import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.Hashtable;
@@ -181,6 +182,7 @@ public class CMSEngine implements ICMSEngine {
     private static final String PROP_SUBSYSTEM = "subsystem";
     private static final String PROP_ID = "id";
     private static final String PROP_CLASS = "class";
+    private static final String PROP_ENABLED = "enabled";
     private static final String SERVER_XML = "server.xml";
 
     public static final SubsystemRegistry mSSReg = SubsystemRegistry.getInstance();
@@ -866,32 +868,34 @@ public class CMSEngine implements ICMSEngine {
         }
     }
 
+    private ArrayList<String> getDynSubsystemNames() throws EBaseException {
+        IConfigStore ssconfig = mConfig.getSubStore(PROP_SUBSYSTEM);
+        Enumeration<String> ssNames = ssconfig.getSubStoreNames();
+        ArrayList<String> ssNamesList = new ArrayList<String>();
+        while (ssNames.hasMoreElements())
+            ssNamesList.add(ssNames.nextElement());
+        return ssNamesList;
+    }
+
     /**
      * load dynamic subsystems
      */
     private void loadDynSubsystems()
             throws EBaseException {
-        IConfigStore ssconfig = mConfig.getSubStore(PROP_SUBSYSTEM);
-
-        // count number of dyn loaded subsystems.
-        Enumeration<String> ssnames = ssconfig.getSubStoreNames();
-        int nsubsystems = 0;
-
-        for (nsubsystems = 0; ssnames.hasMoreElements(); nsubsystems++)
-            ssnames.nextElement();
+        ArrayList<String> ssNames = getDynSubsystemNames();
         if (Debug.ON) {
-            Debug.trace(nsubsystems + " dyn subsystems loading..");
+            Debug.trace(ssNames.size() + " dyn subsystems loading..");
         }
-        if (nsubsystems == 0)
-            return;
 
         // load dyn subsystems.
-        mDynSubsystems = new SubsystemInfo[nsubsystems];
-        for (int i = 0; i < mDynSubsystems.length; i++) {
-            IConfigStore config =
-                    ssconfig.getSubStore(String.valueOf(i));
+        IConfigStore ssconfig = mConfig.getSubStore(PROP_SUBSYSTEM);
+        mDynSubsystems = new SubsystemInfo[ssNames.size()];
+        int i = 0;
+        for (String ssName : ssNames) {
+            IConfigStore config = ssconfig.getSubStore(ssName);
             String id = config.getString(PROP_ID);
             String classname = config.getString(PROP_CLASS);
+            boolean enabled = config.getBoolean(PROP_ENABLED, true);
             ISubsystem ss = null;
 
             try {
@@ -906,11 +910,29 @@ public class CMSEngine implements ICMSEngine {
                 throw new EBaseException(
                         CMS.getUserMessage("CMS_BASE_LOAD_FAILED_1", id, e.toString()));
             }
-            mDynSubsystems[i] = new SubsystemInfo(id, ss);
+            mDynSubsystems[i++] = new SubsystemInfo(id, ss, enabled);
             Debug.trace("loaded dyn subsystem " + id);
         }
     }
 
+    /**
+     * Set whether the given subsystem is enabled.
+     *
+     * @param id The subsystem ID.
+     * @param enabled Whether the subsystem is enabled
+     */
+    public void setSubsystemEnabled(String id, boolean enabled)
+            throws EBaseException {
+        IConfigStore ssconfig = mConfig.getSubStore(PROP_SUBSYSTEM);
+        for (String ssName : getDynSubsystemNames()) {
+            IConfigStore config = ssconfig.getSubStore(ssName);
+            if (id.equalsIgnoreCase(config.getString(PROP_ID))) {
+                config.putBoolean(PROP_ENABLED, enabled);
+                break;
+            }
+        }
+    }
+
     public LDAPConnection getBoundConnection(String host, int port,
                int version, LDAPSSLSocketFactoryExt fac, String bindDN,
                String bindPW) throws LDAPException {
@@ -928,6 +950,10 @@ public class CMSEngine implements ICMSEngine {
         IConfigStore ssConfig = mConfig.getSubStore(id);
 
         CMS.debug("CMSEngine: initSubsystem id=" + id);
+        if (!ssinfo.enabled) {
+            CMS.debug("CMSEngine: subsystem disabled id=" + id);
+            return;
+        }
         if (doSetId)
             ss.setId(id);
         CMS.debug("CMSEngine: ready to init id=" + id);
@@ -2000,10 +2026,16 @@ class WarningListener implements ILogEventListener {
 class SubsystemInfo {
     public final String mId;
     public final ISubsystem mInstance;
+    public final boolean enabled;
 
     public SubsystemInfo(String id, ISubsystem ssInstance) {
+        this(id, ssInstance, true);
+    }
+
+    public SubsystemInfo(String id, ISubsystem ssInstance, boolean enabled) {
         mId = id;
         mInstance = ssInstance;
+        this.enabled = enabled;
     }
 
 }
diff --git a/base/server/test/com/netscape/cmscore/app/CMSEngineDefaultStub.java b/base/server/test/com/netscape/cmscore/app/CMSEngineDefaultStub.java
index db39964f2411d169492cad8dc817a0fe4163b765..0b7518d81c516b2073cc54ae69a9b9fb0262c669 100644
--- a/base/server/test/com/netscape/cmscore/app/CMSEngineDefaultStub.java
+++ b/base/server/test/com/netscape/cmscore/app/CMSEngineDefaultStub.java
@@ -135,6 +135,9 @@ public class CMSEngineDefaultStub implements ICMSEngine {
         return null;
     }
 
+    public void setSubsystemEnabled(String id, boolean enabled) {
+    };
+
     public ISubsystem getSubsystem(String name) {
         return null;
     }
-- 
1.9.3

-------------- next part --------------
>From a9746ae92d49ad1129623631a456795562cfa62a Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Fri, 18 Jul 2014 02:01:58 -0400
Subject: [PATCH 08/10] Import profiles when spawning CA instance

---
 base/ca/shared/conf/CS.cfg.in                      |   1 +
 .../server/ca/rest/CAInstallerService.java         | 115 +++++++++++++++++++++
 2 files changed, 116 insertions(+)

diff --git a/base/ca/shared/conf/CS.cfg.in b/base/ca/shared/conf/CS.cfg.in
index 4ab8974e6340d81d23bb7f5ea05a07b0936b6463..13ed9c4b9f43f8e76c6f5faffbc78c4a61ccf718 100644
--- a/base/ca/shared/conf/CS.cfg.in
+++ b/base/ca/shared/conf/CS.cfg.in
@@ -1139,6 +1139,7 @@ subsystem.0.class=com.netscape.ca.CertificateAuthority
 subsystem.0.id=ca
 subsystem.1.class=com.netscape.cmscore.profile.ProfileSubsystem
 subsystem.1.id=profile
+subsystem.1.enabled=false
 subsystem.2.class=com.netscape.cmscore.selftests.SelfTestSubsystem
 subsystem.2.id=selftests
 subsystem.3.class=com.netscape.cmscore.cert.CrossCertPairSubsystem
diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CAInstallerService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAInstallerService.java
index bb823eece4729599b6badd9ca0e24ef560b9f279..9d4f0792aaa1bf60b5b2bde8915c077a227330ed 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/CAInstallerService.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/CAInstallerService.java
@@ -17,13 +17,27 @@
 // --- END COPYRIGHT BLOCK ---
 package org.dogtagpki.server.ca.rest;
 
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.StringTokenizer;
+
+import netscape.ldap.LDAPAttribute;
+
 import org.dogtagpki.server.rest.SystemConfigService;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.base.PKIException;
+import com.netscape.certsrv.ldap.ELdapException;
+import com.netscape.certsrv.ldap.ILdapConnFactory;
+import com.netscape.certsrv.registry.IPluginInfo;
+import com.netscape.certsrv.registry.IPluginRegistry;
 import com.netscape.certsrv.system.ConfigurationRequest;
 import com.netscape.cms.servlet.csadmin.ConfigurationUtils;
+import com.netscape.cmscore.base.LDAPConfigStore;
+
 
 /**
  * @author alee
@@ -64,5 +78,106 @@ public class CAInstallerService extends SystemConfigService {
             CMS.debug(e);
             throw new PKIException("Errors in determining if security domain host is a master CA");
         }
+
+        try {
+            CMS.enableSubsystem("profile");
+        } catch (Exception e) {
+            CMS.debug(e);
+            throw new PKIException("Error enabling ProfileSubsystem");
+        }
+    }
+
+    @Override
+    public void initializeDatabase(ConfigurationRequest data) {
+        super.initializeDatabase(data);
+
+        if (!data.isClone()) {
+            try {
+                importProfiles("/usr/share/pki");
+            } catch (Exception e) {
+                throw new PKIException("Error importing profiles.");
+            }
+        }
+    }
+
+    /**
+     * Import profiles from the filesystem into the database.
+     *
+     * @param configRoot Where to look for the profile files.  For a
+     *                   fresh installation this should be
+     *                   "/usr/share/pki".  For existing installations it
+     *                   should be CMS.getConfigStore().getString("instanceRoot").
+     *
+     */
+    public void importProfiles(String configRoot)
+            throws EBaseException, ELdapException {
+        IPluginRegistry registry = (IPluginRegistry)
+            CMS.getSubsystem(CMS.SUBSYSTEM_REGISTRY);
+        IConfigStore profileCfg = cs.getSubStore("profile");
+        String profileIds = profileCfg.getString("list", "");
+        StringTokenizer st = new StringTokenizer(profileIds, ",");
+
+        IConfigStore dbCfg = cs.getSubStore("internaldb");
+        ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory();
+        dbFactory.init(dbCfg);
+
+        while (st.hasMoreTokens()) {
+            String profileId = st.nextToken();
+            IConfigStore profileSubCfg = profileCfg.getSubStore(profileId);
+            String classId = profileSubCfg.getString("class_id", "");
+            try {
+                IPluginInfo info = registry.getPluginInfo("profile", classId);
+                if (info == null) {
+                    throw new EBaseException("No plugins for type : profile, with id " + classId);
+                }
+                String className = info.getClassName();
+
+                String profilePath = configRoot + "/ca/profiles/ca/" + profileId + ".cfg";
+                CMS.debug("Importing profile '" + profileId + "' from " + profilePath);
+                importProfile(dbFactory, classId, profileId, profilePath);
+            } catch (EBaseException e) {
+                CMS.debug("Error importing profile '" + profileId + "': " + e.toString());
+                CMS.debug("  Continuing with profile import procedure...");
+            }
+        }
+    }
+
+    /**
+     * Import one profile from the filesystem into the database.
+     *
+     * @param dbFactory     LDAP connection factory.
+     * @param classId       The profile class of the profile to import.
+     * @param profileId     The ID of the profile to import.
+     * @param profilePath   Path to the on-disk profile configuration.
+     */
+    public void importProfile(
+            ILdapConnFactory dbFactory, String classId,
+            String profileId, String profilePath)
+            throws EBaseException {
+
+        String basedn = cs.getString("internaldb.basedn", "");
+
+        String dn = "cn=" + profileId + ",ou=certificateProfiles,ou=ca," + basedn;
+
+        String[] objectClasses = {"top", "certProfile"};
+        LDAPAttribute[] createAttrs = {
+            new LDAPAttribute("objectclass", objectClasses),
+            new LDAPAttribute("cn", profileId),
+            new LDAPAttribute("classId", classId)
+        };
+
+        IConfigStore configStore = new LDAPConfigStore(
+            dbFactory, dn, createAttrs, "certProfileConfig");
+
+        try {
+            FileInputStream input = new FileInputStream(profilePath);
+            configStore.load(input);
+        } catch (FileNotFoundException e) {
+            throw new EBaseException("Could not find file for profile: " + profileId);
+        } catch (IOException e) {
+            throw new EBaseException("Error loading data for profile: " + profileId);
+        }
+
+        configStore.commit(false /* no backup */);
     }
 }
-- 
1.9.3

-------------- next part --------------
>From cfead8f46972848265ac3be0a6ec01eed51da3a7 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Wed, 23 Jul 2014 02:40:07 -0400
Subject: [PATCH 09/10] Update pki-profile CLI commands to work with "raw"
 format

Update CLI commands for working with the (now LDAP-based)
profiles in the same format as was used by the files, by way of the
--raw option.

Also add the "edit" command to interactively edit a profile.
---
 .../dogtagpki/server/ca/rest/ProfileService.java   | 160 ++++++++++++++++++---
 .../netscape/certsrv/profile/ProfileClient.java    |  15 ++
 .../netscape/certsrv/profile/ProfileResource.java  |  20 ++-
 .../netscape/cmstools/profile/ProfileAddCLI.java   |  37 ++++-
 .../com/netscape/cmstools/profile/ProfileCLI.java  |   1 +
 .../netscape/cmstools/profile/ProfileEditCLI.java  | 102 +++++++++++++
 .../cmstools/profile/ProfileModifyCLI.java         |  35 ++++-
 .../netscape/cmstools/profile/ProfileShowCLI.java  |  31 ++--
 8 files changed, 359 insertions(+), 42 deletions(-)
 create mode 100644 base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java

diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java b/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java
index d3f08b270fd66154da880d47be30ea48716b75bd..2017a6c426cc3e49e76bc4e6b52de721fd8999dd 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java
@@ -18,6 +18,8 @@
 
 package org.dogtagpki.server.ca.rest;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.URI;
 import java.security.Principal;
@@ -27,6 +29,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Vector;
 
 import javax.servlet.http.HttpServletRequest;
@@ -163,9 +166,7 @@ public class ProfileService extends PKIService implements ProfileResource {
         return createOKResponse(infos);
     }
 
-    @Override
-    public Response retrieveProfile(String profileId) throws ProfileNotFoundException {
-        ProfileData data = null;
+    private IProfile getProfile(String profileId) throws ProfileNotFoundException {
         boolean visibleOnly = true;
 
         if (profileId == null) {
@@ -185,24 +186,12 @@ public class ProfileService extends PKIService implements ProfileResource {
             visibleOnly = false;
         }
 
-        Enumeration<String> profileIds = ps.getProfileIds();
-
-        IProfile profile = null;
-        if (profileIds != null) {
-            while (profileIds.hasMoreElements()) {
-                String id = profileIds.nextElement();
-
-                if (id.equals(profileId)) {
-
-                    try {
-                        profile = ps.getProfile(profileId);
-                    } catch (EProfileException e) {
-                        e.printStackTrace();
-                        throw new ProfileNotFoundException(profileId);
-                    }
-                    break;
-                }
-            }
+        IProfile profile;
+        try {
+            profile = ps.getProfile(profileId);
+        } catch (EProfileException e) {
+            e.printStackTrace();
+            throw new ProfileNotFoundException(profileId);
         }
 
         if (profile == null) {
@@ -213,6 +202,14 @@ public class ProfileService extends PKIService implements ProfileResource {
             throw new ProfileNotFoundException(profileId);
         }
 
+        return profile;
+    }
+
+    @Override
+    public Response retrieveProfile(String profileId) throws ProfileNotFoundException {
+        IProfile profile = getProfile(profileId);
+
+        ProfileData data = null;
         try {
             data = createProfileData(profileId);
         } catch (EBaseException e) {
@@ -228,6 +225,19 @@ public class ProfileService extends PKIService implements ProfileResource {
         return createOKResponse(data);
     }
 
+    @Override
+    public Response retrieveProfileRaw(String profileId)
+            throws ProfileNotFoundException {
+        IProfile profile = getProfile(profileId);
+        ByteArrayOutputStream data = new ByteArrayOutputStream();
+        // add profileId and classId "virtual" properties
+        profile.getConfigStore().put("profileId", profileId);
+        profile.getConfigStore().put("classId", ps.getProfileClassId(profileId));
+        profile.getConfigStore().save(data, null);
+        return createOKResponse(data.toByteArray());
+    }
+
+
     public ProfileData createProfileData(String profileId) throws EBaseException {
 
         IProfile profile;
@@ -514,6 +524,75 @@ public class ProfileService extends PKIService implements ProfileResource {
     }
 
     @Override
+    public Response createProfileRaw(byte[] data) {
+        if (data == null) {
+            CMS.debug("createProfileRaw: profile data is null");
+            throw new BadRequestException("Unable to create profile: Invalid profile data.");
+        }
+
+        if (ps == null) {
+            CMS.debug("createProfile: ps is null");
+            throw new PKIException("Error creating profile.  Profile Service not available");
+        }
+
+        Map<String, String> auditParams = new LinkedHashMap<String, String>();
+        String profileId = null;
+        String classId = null;
+        try {
+            // load data as properties and read profileId and classId
+            Properties properties = new Properties();
+            properties.load(new ByteArrayInputStream(data));
+            profileId = properties.getProperty("profileId");
+            classId = properties.getProperty("classId");
+        } catch (IOException e) {
+            throw new BadRequestException("Could not parse raw profile data.");
+        }
+        if (profileId == null) {
+            throw new BadRequestException("Profile data did not contain profileId attribute.");
+        }
+        if (classId == null) {
+            throw new BadRequestException("Profile data did not contain classId attribute.");
+        }
+
+        try {
+            IProfile profile = ps.getProfile(profileId);
+            if (profile != null) {
+                throw new BadRequestException("Profile already exists");
+            }
+
+            auditParams.put("class_id", classId);
+
+            IPluginInfo info = registry.getPluginInfo("profile", classId);
+
+            profile = ps.createProfile(profileId, classId, info.getClassName());
+            profile.getConfigStore().commit(false);
+            profile.getConfigStore().load(new ByteArrayInputStream(data));
+            ps.disableProfile(profileId);
+
+            auditProfileChange(
+                    ScopeDef.SC_PROFILE_RULES,
+                    OpDef.OP_ADD,
+                    profileId,
+                    ILogger.SUCCESS,
+                    auditParams);
+
+            return createCreatedResponse(data, uriInfo.getAbsolutePath());
+        } catch (EBaseException | IOException e) {
+            CMS.debug("createProfile: error in creating profile: " + e);
+            e.printStackTrace();
+
+            auditProfileChange(
+                    ScopeDef.SC_PROFILE_RULES,
+                    OpDef.OP_ADD,
+                    profileId,
+                    ILogger.FAILURE,
+                    auditParams);
+
+            throw new PKIException("Error in creating profile");
+        }
+    }
+
+    @Override
     public Response modifyProfile(String profileId, ProfileData data) {
         if (profileId == null) {
             CMS.debug("modifyProfile: invalid request. profileId is null");
@@ -550,6 +629,45 @@ public class ProfileService extends PKIService implements ProfileResource {
         }
     }
 
+    @Override
+    public Response modifyProfileRaw(String profileId, byte[] data) {
+        if (profileId == null) {
+            CMS.debug("modifyProfile: invalid request. profileId is null");
+            throw new BadRequestException("Unable to modify profile: Invalid Profile Id");
+        }
+
+        if (data == null) {
+            CMS.debug("modifyProfile: invalid request. data is null");
+            throw new BadRequestException("Unable to modify profile: Invalid profile data");
+        }
+
+        if (ps == null) {
+            CMS.debug("modifyProfile: ps is null");
+            throw new PKIException("Error modifying profile.  Profile Service not available");
+        }
+
+        if (ps.isProfileEnable(profileId)) {
+            throw new BadRequestException("Cannot change profile data.  Profile must be disabled");
+        }
+
+        try {
+            IProfile profile = ps.getProfile(profileId);
+            if (profile == null) {
+                throw new ProfileNotFoundException(profileId);
+            }
+
+            profile.getConfigStore().load(new ByteArrayInputStream(data));
+            ps.disableProfile(profileId);
+            profile.getConfigStore().commit(false);
+
+            return createOKResponse(data);
+        } catch (EBaseException | IOException e) {
+            CMS.debug("modifyProfile: error modifying profile `" + profileId + "`: " + e);
+            e.printStackTrace();
+            throw new PKIException("Error modifying profile.");
+        }
+    }
+
     private void changeProfileData(ProfileData data, IProfile profile) {
         String profileId = data.getId();
         if (profile == null) {
diff --git a/base/common/src/com/netscape/certsrv/profile/ProfileClient.java b/base/common/src/com/netscape/certsrv/profile/ProfileClient.java
index 51d159aca687719a2dace939da5b09c4809872ec..f3ffd29e03c1da39518e3928eab7a781ec38eaff 100644
--- a/base/common/src/com/netscape/certsrv/profile/ProfileClient.java
+++ b/base/common/src/com/netscape/certsrv/profile/ProfileClient.java
@@ -45,6 +45,11 @@ public class ProfileClient extends Client {
         return client.getEntity(response, ProfileData.class);
     }
 
+    public byte[] retrieveProfileRaw(String id) {
+        Response response = profileClient.retrieveProfileRaw(id);
+        return client.getEntity(response, byte[].class);
+    }
+
     public ProfileDataInfos listProfiles(Integer start, Integer size) {
         Response response =  profileClient.listProfiles(start, size);
         return client.getEntity(response, ProfileDataInfos.class);
@@ -65,11 +70,21 @@ public class ProfileClient extends Client {
         return client.getEntity(response, ProfileData.class);
     }
 
+    public byte[] createProfileRaw(byte[] data) {
+        Response response = profileClient.createProfileRaw(data);
+        return client.getEntity(response, byte[].class);
+    }
+
     public ProfileData modifyProfile(ProfileData data) {
         Response response = profileClient.modifyProfile(data.getId(), data);
         return client.getEntity(response, ProfileData.class);
     }
 
+    public byte[] modifyProfileRaw(String profileId, byte[] data) {
+        Response response = profileClient.modifyProfileRaw(profileId, data);
+        return client.getEntity(response, byte[].class);
+    }
+
     public void deleteProfile(String id) {
         Response response = profileClient.deleteProfile(id);
         client.getEntity(response, Void.class);
diff --git a/base/common/src/com/netscape/certsrv/profile/ProfileResource.java b/base/common/src/com/netscape/certsrv/profile/ProfileResource.java
index 87449b27e749c6088f65b53192eb5ac101263f1e..410f98a468dbfca0bf587623935f2a63e97923e9 100644
--- a/base/common/src/com/netscape/certsrv/profile/ProfileResource.java
+++ b/base/common/src/com/netscape/certsrv/profile/ProfileResource.java
@@ -31,12 +31,24 @@ public interface ProfileResource {
     @ACLMapping("profiles.read")
     public Response retrieveProfile(@PathParam("id") String id);
 
+    @GET
+    @Path("{id}/raw")
+    @ClientResponseType(entityType=byte[].class)
+    @ACLMapping("profiles.read")
+    public Response retrieveProfileRaw(@PathParam("id") String id);
+
     @POST
     @ClientResponseType(entityType=ProfileData.class)
     @ACLMapping("profiles.create")
     public Response createProfile(ProfileData data);
 
     @POST
+    @Path("raw")
+    @ClientResponseType(entityType=byte[].class)
+    @ACLMapping("profiles.create")
+    public Response createProfileRaw(byte[] data);
+
+    @POST
     @Path("{id}")
     @ClientResponseType(entityType=Void.class)
     @ACLMapping("profiles.approve")
@@ -48,9 +60,15 @@ public interface ProfileResource {
     @ACLMapping("profiles.modify")
     public Response modifyProfile(@PathParam("id") String id, ProfileData data);
 
+    @PUT
+    @Path("{id}/raw")
+    @ClientResponseType(entityType=byte[].class)
+    @ACLMapping("profiles.modify")
+    public Response modifyProfileRaw(@PathParam("id") String id, byte[] data);
+
     @DELETE
     @Path("{id}")
     @ClientResponseType(entityType=Void.class)
     @ACLMapping("profiles.delete")
     public Response deleteProfile(@PathParam("id") String id);
-}
\ No newline at end of file
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java
index 62bd145261a798fd89b3b8aaed389d0b2fb886fa..74690810c20d5b8a2271f46ba2877f15aba6e001 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java
@@ -1,11 +1,16 @@
 package com.netscape.cmstools.profile;
 
-import java.io.FileNotFoundException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.Properties;
 
 import javax.xml.bind.JAXBException;
 
 import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
 import org.apache.commons.cli.ParseException;
 
 import com.netscape.certsrv.profile.ProfileData;
@@ -19,6 +24,10 @@ public class ProfileAddCLI extends CLI {
     public ProfileAddCLI(ProfileCLI profileCLI) {
         super("add", "Add profiles", profileCLI);
         this.profileCLI = profileCLI;
+
+        Option optRaw = new Option(null, "raw", false, "Use raw format");
+        optRaw.setArgName("raw");
+        options.addOption(optRaw);
     }
 
     public void printHelp() {
@@ -59,13 +68,29 @@ public class ProfileAddCLI extends CLI {
         }
 
         try {
-            ProfileData data = ProfileCLI.readProfileFromFile(filename);
-            data = profileCLI.profileClient.createProfile(data);
+            if (cmd.hasOption("raw")) {
+                byte[] data = Files.readAllBytes(Paths.get(filename));
+                Properties cs = new Properties();
+                cs.load(new ByteArrayInputStream(data));
+                String profileId = cs.getProperty("profileId");
+                if (profileId == null) {
+                    System.err.println("Error: Missing profileId property in profile data.");
+                    System.exit(-1);
+                }
 
-            MainCLI.printMessage("Added profile " + data.getId());
+                byte[] profileConfig =
+                    profileCLI.profileClient.createProfileRaw(data);
+                System.out.println(new String(profileConfig));
+                MainCLI.printMessage("Added profile " + profileId);
+            } else {
+                ProfileData data = ProfileCLI.readProfileFromFile(filename);
+                data = profileCLI.profileClient.createProfile(data);
 
-            ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI());
-        } catch (FileNotFoundException | JAXBException  e) {
+                MainCLI.printMessage("Added profile " + data.getId());
+
+                ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI());
+            }
+        } catch (IOException | JAXBException  e) {
             System.err.println("Error: " + e.getMessage());
             System.exit(-1);
         }
diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java
index 732b597afd1dbb5b9440d451f34b2f39e20fb904..1b69a4a457efb8af1e58c60f711a4ec7055ad066 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java
@@ -32,6 +32,7 @@ public class ProfileCLI extends CLI {
         addModule(new ProfileShowCLI(this));
         addModule(new ProfileAddCLI(this));
         addModule(new ProfileModifyCLI(this));
+        addModule(new ProfileEditCLI(this));
         addModule(new ProfileRemoveCLI(this));
         addModule(new ProfileEnableCLI(this));
         addModule(new ProfileDisableCLI(this));
diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d8b9a50eb1fd23199b4e893906d5170e1ff791e
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java
@@ -0,0 +1,102 @@
+//--- 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) 2014 Red Hat, Inc.
+//All rights reserved.
+//--- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.profile;
+
+import java.lang.ProcessBuilder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.ParseException;
+
+import com.netscape.cmstools.cli.CLI;
+
+public class ProfileEditCLI extends CLI {
+
+    public ProfileCLI profileCLI;
+
+    public ProfileEditCLI(ProfileCLI profileCLI) {
+        super("edit", "Edit profiles (config-store format)", profileCLI);
+        this.profileCLI = profileCLI;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <Profile ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+        // Always check for "--help" prior to parsing
+        if (Arrays.asList(args).contains("--help")) {
+            // Display usage
+            printHelp();
+            System.exit(0);
+        }
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length < 1) {
+            System.err.println("Error: No Profile ID specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String profileId = cmdArgs[0];
+
+        // read profile into temporary file
+        byte[] orig = profileCLI.profileClient.retrieveProfileRaw(profileId);
+        Path tempFile = Files.createTempFile("pki", ".cfg");
+        Files.write(tempFile, orig);
+
+        // invoke editor on temporary file
+        String editor = System.getenv("EDITOR");
+        String[] command;
+        if (editor == null || editor.trim().isEmpty()) {
+            command = new String[] {"/usr/bin/env", "vi", tempFile.toString()};
+        } else {
+            command = new String[] {editor.trim(), tempFile.toString()};
+        }
+        ProcessBuilder pb = new ProcessBuilder(command);
+        pb.inheritIO();
+        int exitCode = pb.start().waitFor();
+        if (exitCode != 0) {
+            System.err.println("Error: editor exited abnormally.");
+            System.exit(-1);
+        }
+
+        // read data from temporary file and modify if changed
+        byte[] cur = Files.readAllBytes(tempFile);
+        Files.delete(tempFile);
+        String curString = new String(cur);
+        if (!curString.equals(new String(orig))) {
+            profileCLI.profileClient.modifyProfileRaw(profileId, cur);
+        }
+        System.out.println(curString);
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java
index bbeb91981e79b6690efa55c8f293b017ea4ace31..1cf1991d6a22887ce141f317094bdf9209311a44 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java
@@ -1,11 +1,16 @@
 package com.netscape.cmstools.profile;
 
-import java.io.FileNotFoundException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.Properties;
 
 import javax.xml.bind.JAXBException;
 
 import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
 import org.apache.commons.cli.ParseException;
 
 import com.netscape.certsrv.profile.ProfileData;
@@ -19,6 +24,10 @@ public class ProfileModifyCLI extends CLI {
     public ProfileModifyCLI(ProfileCLI profileCLI) {
         super("mod", "Modify profiles", profileCLI);
         this.profileCLI = profileCLI;
+
+        Option optRaw = new Option(null, "raw", false, "Use raw format");
+        optRaw.setArgName("raw");
+        options.addOption(optRaw);
     }
 
     public void printHelp() {
@@ -59,14 +68,28 @@ public class ProfileModifyCLI extends CLI {
         }
 
         try {
-            ProfileData data = ProfileCLI.readProfileFromFile(filename);
-            data = profileCLI.profileClient.modifyProfile(data);
+            if (cmd.hasOption("raw")) {
+                byte[] data = Files.readAllBytes(Paths.get(filename));
+                Properties cs = new Properties();
+                cs.load(new ByteArrayInputStream(data));
+                String profileId = cs.getProperty("profileId");
+                if (profileId == null) {
+                    System.err.println("Error: Missing profileId property in profile data.");
+                    System.exit(-1);
+                }
 
-            MainCLI.printMessage("Modified profile " + data.getId());
+                byte[] profileConfig =
+                    profileCLI.profileClient.modifyProfileRaw(profileId, data);
+                System.out.println(new String(profileConfig));
+            } else {
+                ProfileData data = ProfileCLI.readProfileFromFile(filename);
+                data = profileCLI.profileClient.modifyProfile(data);
 
-            ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI());
+                MainCLI.printMessage("Modified profile " + data.getId());
 
-        } catch (FileNotFoundException | JAXBException  e) {
+                ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI());
+            }
+        } catch (IOException | JAXBException  e) {
             System.err.println("Error: " + e.getMessage());
             System.exit(-1);
         }
diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java
index f5b636c1a8f9dbfe60d29fc07bae373be3ea966e..7776cbd86a0ede6d2b19a9adb3618dc4652bd8ae 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java
@@ -1,5 +1,6 @@
 package com.netscape.cmstools.profile;
 
+import java.io.FileOutputStream;
 import java.util.Arrays;
 
 import org.apache.commons.cli.CommandLine;
@@ -26,9 +27,13 @@ public class ProfileShowCLI extends CLI {
     }
 
     public void createOptions() {
-        Option option = new Option(null, "output", true, "Output filename");
-        option.setArgName("filename");
-        options.addOption(option);
+        Option optFilename = new Option(null, "output", true, "Output filename");
+        optFilename.setArgName("filename");
+        options.addOption(optFilename);
+
+        Option optRaw = new Option(null, "raw", false, "Use raw format");
+        optRaw.setArgName("raw");
+        options.addOption(optRaw);
     }
 
     public void execute(String[] args) throws Exception {
@@ -70,14 +75,24 @@ public class ProfileShowCLI extends CLI {
             }
         }
 
-        ProfileData profileData = profileCLI.profileClient.retrieveProfile(profileId);
-
         MainCLI.printMessage("Profile \"" + profileId + "\"");
+        if (cmd.hasOption("raw")) {
+            byte[] profileConfig = profileCLI.profileClient.retrieveProfileRaw(profileId);
 
-        if (filename != null) {
-            ProfileCLI.saveProfileToFile(filename, profileData);
+            if (filename != null) {
+                (new FileOutputStream(filename)).write(profileConfig);
+                MainCLI.printMessage("Saved profile " + profileId + " to " + filename);
+            } else {
+                System.out.println(new String(profileConfig));
+            }
         } else {
-            ProfileCLI.printProfile(profileData, profileCLI.getClient().getConfig().getServerURI());
+            ProfileData profileData = profileCLI.profileClient.retrieveProfile(profileId);
+
+            if (filename != null) {
+                ProfileCLI.saveProfileToFile(filename, profileData);
+            } else {
+                ProfileCLI.printProfile(profileData, profileCLI.getClient().getConfig().getServerURI());
+            }
         }
     }
 
-- 
1.9.3

-------------- next part --------------
>From c9072b0908eae0fc6e2c1b735cda67147cf0e13f Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Fri, 19 Sep 2014 02:45:48 -0400
Subject: [PATCH 10/10] Change ProfileClient to use Properties for the "raw"
 format

Also update the 'ca-profile-edit' command to exit prior to editing
when the profile is not disabled, so that the user doesn't find this
out *after* editing the profile.
---
 .../netscape/certsrv/profile/ProfileClient.java    | 43 ++++++++++++++++++----
 .../netscape/cmstools/profile/ProfileAddCLI.java   | 12 ++----
 .../netscape/cmstools/profile/ProfileEditCLI.java  | 19 +++++++---
 .../cmstools/profile/ProfileModifyCLI.java         | 13 +++----
 .../netscape/cmstools/profile/ProfileShowCLI.java  |  7 ++--
 5 files changed, 61 insertions(+), 33 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/profile/ProfileClient.java b/base/common/src/com/netscape/certsrv/profile/ProfileClient.java
index f3ffd29e03c1da39518e3928eab7a781ec38eaff..7f0b08f0e40d9a5314bcf741c2dc2fc514802ad5 100644
--- a/base/common/src/com/netscape/certsrv/profile/ProfileClient.java
+++ b/base/common/src/com/netscape/certsrv/profile/ProfileClient.java
@@ -17,10 +17,15 @@
 //--- END COPYRIGHT BLOCK ---
 package com.netscape.certsrv.profile;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.net.URISyntaxException;
+import java.util.Properties;
 
 import javax.ws.rs.core.Response;
 
+import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.client.Client;
 import com.netscape.certsrv.client.PKIClient;
 
@@ -45,9 +50,9 @@ public class ProfileClient extends Client {
         return client.getEntity(response, ProfileData.class);
     }
 
-    public byte[] retrieveProfileRaw(String id) {
+    public Properties retrieveProfileRaw(String id) {
         Response response = profileClient.retrieveProfileRaw(id);
-        return client.getEntity(response, byte[].class);
+        return byteArrayToProperties(client.getEntity(response, byte[].class));
     }
 
     public ProfileDataInfos listProfiles(Integer start, Integer size) {
@@ -70,9 +75,10 @@ public class ProfileClient extends Client {
         return client.getEntity(response, ProfileData.class);
     }
 
-    public byte[] createProfileRaw(byte[] data) {
-        Response response = profileClient.createProfileRaw(data);
-        return client.getEntity(response, byte[].class);
+    public Properties createProfileRaw(Properties properties) {
+        Response response =
+            profileClient.createProfileRaw(propertiesToByteArray(properties));
+        return byteArrayToProperties(client.getEntity(response, byte[].class));
     }
 
     public ProfileData modifyProfile(ProfileData data) {
@@ -80,13 +86,34 @@ public class ProfileClient extends Client {
         return client.getEntity(response, ProfileData.class);
     }
 
-    public byte[] modifyProfileRaw(String profileId, byte[] data) {
-        Response response = profileClient.modifyProfileRaw(profileId, data);
-        return client.getEntity(response, byte[].class);
+    public Properties modifyProfileRaw(String profileId, Properties properties) {
+        Response response =
+            profileClient.modifyProfileRaw(profileId, propertiesToByteArray(properties));
+        return byteArrayToProperties(client.getEntity(response, byte[].class));
     }
 
     public void deleteProfile(String id) {
         Response response = profileClient.deleteProfile(id);
         client.getEntity(response, Void.class);
     }
+
+    private Properties byteArrayToProperties(byte[] data) throws PKIException {
+        Properties properties = new Properties();
+        try {
+            properties.load(new ByteArrayInputStream(data));
+        } catch (IOException e) {
+            throw new PKIException("Failed to decode profile Properties: " + e.toString());
+        }
+        return properties;
+    }
+
+    private byte[] propertiesToByteArray(Properties properties) throws PKIException {
+        ByteArrayOutputStream data = new ByteArrayOutputStream();
+        try {
+            properties.store(data, null);
+        } catch (IOException e) {
+            throw new PKIException("Failed to encode profile Properties: " + e.toString());
+        }
+        return data.toByteArray();
+    }
 }
diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java
index 74690810c20d5b8a2271f46ba2877f15aba6e001..82adf644dc2044480ed79f92e569e376195799bd 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java
@@ -1,6 +1,5 @@
 package com.netscape.cmstools.profile;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
@@ -69,18 +68,15 @@ public class ProfileAddCLI extends CLI {
 
         try {
             if (cmd.hasOption("raw")) {
-                byte[] data = Files.readAllBytes(Paths.get(filename));
-                Properties cs = new Properties();
-                cs.load(new ByteArrayInputStream(data));
-                String profileId = cs.getProperty("profileId");
+                Properties properties = new Properties();
+                properties.load(Files.newInputStream(Paths.get(filename)));
+                String profileId = properties.getProperty("profileId");
                 if (profileId == null) {
                     System.err.println("Error: Missing profileId property in profile data.");
                     System.exit(-1);
                 }
 
-                byte[] profileConfig =
-                    profileCLI.profileClient.createProfileRaw(data);
-                System.out.println(new String(profileConfig));
+                profileCLI.profileClient.createProfileRaw(properties).store(System.out, null);
                 MainCLI.printMessage("Added profile " + profileId);
             } else {
                 ProfileData data = ProfileCLI.readProfileFromFile(filename);
diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java
index 5d8b9a50eb1fd23199b4e893906d5170e1ff791e..f8ce89fb18544f774de48c9027a014bd8d450e02 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java
@@ -22,6 +22,7 @@ import java.lang.ProcessBuilder;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
+import java.util.Properties;
 
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.ParseException;
@@ -70,9 +71,14 @@ public class ProfileEditCLI extends CLI {
         String profileId = cmdArgs[0];
 
         // read profile into temporary file
-        byte[] orig = profileCLI.profileClient.retrieveProfileRaw(profileId);
+        Properties orig = profileCLI.profileClient.retrieveProfileRaw(profileId);
+        String enabled = orig.getProperty("enable");
+        if (enabled == null || !enabled.equalsIgnoreCase("false")) {
+            System.err.println("Error: Cannot edit profile. Profile must be disabled.");
+            System.exit(-1);
+        }
         Path tempFile = Files.createTempFile("pki", ".cfg");
-        Files.write(tempFile, orig);
+        orig.store(Files.newOutputStream(tempFile), null);
 
         // invoke editor on temporary file
         String editor = System.getenv("EDITOR");
@@ -91,12 +97,13 @@ public class ProfileEditCLI extends CLI {
         }
 
         // read data from temporary file and modify if changed
-        byte[] cur = Files.readAllBytes(tempFile);
+        Properties cur = new Properties();
+        cur.load(Files.newInputStream(tempFile));
         Files.delete(tempFile);
-        String curString = new String(cur);
-        if (!curString.equals(new String(orig))) {
+
+        if (!cur.equals(orig)) {
             profileCLI.profileClient.modifyProfileRaw(profileId, cur);
         }
-        System.out.println(curString);
+        cur.store(System.out, null);
     }
 }
diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java
index 1cf1991d6a22887ce141f317094bdf9209311a44..6234bb8c6f1b7d3592bd2b9cd629c4717e8a847e 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java
@@ -1,6 +1,5 @@
 package com.netscape.cmstools.profile;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
@@ -69,18 +68,16 @@ public class ProfileModifyCLI extends CLI {
 
         try {
             if (cmd.hasOption("raw")) {
-                byte[] data = Files.readAllBytes(Paths.get(filename));
-                Properties cs = new Properties();
-                cs.load(new ByteArrayInputStream(data));
-                String profileId = cs.getProperty("profileId");
+                Properties properties = new Properties();
+                properties.load(Files.newInputStream(Paths.get(filename)));
+                String profileId = properties.getProperty("profileId");
                 if (profileId == null) {
                     System.err.println("Error: Missing profileId property in profile data.");
                     System.exit(-1);
                 }
 
-                byte[] profileConfig =
-                    profileCLI.profileClient.modifyProfileRaw(profileId, data);
-                System.out.println(new String(profileConfig));
+                profileCLI.profileClient.modifyProfileRaw(profileId, properties).store(System.out, null);
+                MainCLI.printMessage("Modified profile " + profileId);
             } else {
                 ProfileData data = ProfileCLI.readProfileFromFile(filename);
                 data = profileCLI.profileClient.modifyProfile(data);
diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java
index 7776cbd86a0ede6d2b19a9adb3618dc4652bd8ae..2dd627145b66a7ece890cba9922e8d4ec2e27503 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java
@@ -2,6 +2,7 @@ package com.netscape.cmstools.profile;
 
 import java.io.FileOutputStream;
 import java.util.Arrays;
+import java.util.Properties;
 
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Option;
@@ -77,13 +78,13 @@ public class ProfileShowCLI extends CLI {
 
         MainCLI.printMessage("Profile \"" + profileId + "\"");
         if (cmd.hasOption("raw")) {
-            byte[] profileConfig = profileCLI.profileClient.retrieveProfileRaw(profileId);
+            Properties profileConfig = profileCLI.profileClient.retrieveProfileRaw(profileId);
 
             if (filename != null) {
-                (new FileOutputStream(filename)).write(profileConfig);
+                profileConfig.store(new FileOutputStream(filename), null);
                 MainCLI.printMessage("Saved profile " + profileId + " to " + filename);
             } else {
-                System.out.println(new String(profileConfig));
+                profileConfig.store(System.out, null);
             }
         } else {
             ProfileData profileData = profileCLI.profileClient.retrieveProfile(profileId);
-- 
1.9.3



More information about the Pki-devel mailing list