[Pki-devel] CLI for editing profiles

Fraser Tweedale ftweedal at redhat.com
Mon Mar 30 07:26:34 UTC 2015


New patchset for ldap profiles after a long time working on sub-CAs!

4, 5, 6, 7, 8 have been ACKed previously.  #6 deserves renewed
attention as the original (file-based) ProfileSubsystem has been
restored, and the new subsystem now appears as LDAPProfileSubsystem.
A new patch (separate email) includes the pkispawn logic to specify
whether to use file or ldap profiles.

Comments on patch 9 inline below.

Cheers,
Fraser

On Wed, Dec 03, 2014 at 01:59:24PM +0700, Endi Sukma Dewata wrote:
> On 11/20/2014 1:30 PM, Fraser Tweedale wrote:
> >>Fourth cut of the feature.
> >>
> >>Please review w.r.t. the issues below.
> >>
> >>The big issue mentioned in the review but *not* dealt with in this
> >>patchset is support for using file-based profiles OR LDAP-based
> >>profiles.  This will involve copying the new ProfileSystem (as it
> >>appears in the patches) to a new class ("LDAPProfileSubsystem" I
> >>suppose), restoring the original ProfileSubsystem with the
> >>file-based behaviour, providing config and mechanism for loading the
> >>preferred variant of the subsystem, and reconciling any conflicts.
> >>But the implementation of the LDAP-based version of ProfileSubsystem
> >>won't be changed once these patches are ACKed.
> >>
> >>(See also patch 0015-2 in the other thread)
> 
> Patches #4-4, #5-4, #7-4, #8-4 are ACKed.
> 
> Patch #6-4 is ACKed assuming the file-based profiles will be restored later.
> 
> Patch #9-4 has some issues:
> 
> 1. Since the profileId and classId are virtual properties and have their own
> LDAP attributes, they should not be stored in certProfileConfig attribute
> when a profile is added/modified.
> 
Done.  Because the profile configStore is written and read directly
by the ProfileService, the solution to achieve this is also in that
class.

> 2. Try exporting a profile, change the ID, then add it back to the server.
> If you use the XML format, the new profile will not have an enable and
> enableBy properties. If you use the RAW format, the new profile will have
> enable=false and retain the old enableBy. To be more consistent these
> properties should also be removed during add/modify. Probably the
> ProfileSubsystem.disableProfile() can be changed to remove PROP_ENABLE (and
> PROP_ENABLE_BY too) instead of setting it to "false".
> 
Deferred.  Will file ticket.

> Similarly, in CMSEngine.setSubsystemEnabled() you could also remove
> PROP_ENABLED instead of setting it to "false". That way the CS.cfg will be
> cleaner and more consistent.
> 
> 3. In ProfileEditCLI a missing "enable" property is incorrectly interpreted
> as enabled.
> 
>         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);
>         }
> 
> As a comparison, in ProfileSubsystem.isProfileEnable() a missing "enable" is
> considered disabled.
> 
>         if (enable == null || enable.equals("false"))
>             return false;
>         else
>             return true;
> 
> See also the ticket I opened below.
> 
Done.  Also fixed the ticket (#1220) while I was at it.

> 4. If an error happens in ProfileEditCLI, the temporary file may not be
> removed. To make sure it's removed we should use a finally block, or use
> File.deleteOnExit(). See
> https://docs.oracle.com/javase/7/docs/api/java/io/File.html#deleteOnExit().
> 
Done.

> 5. The Properties object used in ProfileClient doesn't order the properties
> since it's based on Hashtable so it will be hard to compare different
> outputs. Maybe we should use a Map<String, String> in the method signature,
> and use a TreeMap object to store the properties.
> 
Deferred.  Will file ticket.

> 6. The ProfileAddCLI swallows the exception so if there's an error (e.g.
> wrong file format) it will only print a message, it won't display the stack
> trace in verbose mode. It should let the exception be handled by the main
> program.
> 
Deferred.  Will address before merge.

> I think #1, #3, and #4 should be fixed. The others can be addressed later. I
> also opened some tickets for existing issues found during testing:
> 
> https://fedorahosted.org/pki/ticket/1218
> https://fedorahosted.org/pki/ticket/1220
> 
> -- 
> Endi S. Dewata
-------------- next part --------------
>From d54f649ea7e2e2f7bc318151e3da393bcecb43c1 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 4/9] Add schema for LDAP-based profiles

---
 base/ca/shared/conf/db.ldif        |  5 ++++-
 base/server/share/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/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif
index 16fdcd97118399b56da2f11a4385aa30a46ae645..475758c5d66bf681e589995505a561bf4e4c40ef 100644
--- a/base/server/share/conf/schema.ldif
+++ b/base/server/share/conf/schema.ldif
@@ -652,3 +652,18 @@ dn: cn=schema
 changetype: modify
 add: objectClasses
 objectClasses: ( tpsProfileID-oid NAME 'tpsProfileID' DESC 'CMS defined class' SUP top AUXILIARY MAY ( profileID ) X-ORIGIN 'user-defined' )
+
+dn: cn=schema
+changetype: modify
+add: attributeTypes
+attributeTypes: ( classId-oid NAME 'classId' DESC 'Certificate profile class ID' 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 'Certificate profile configuration' 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 'Certificate profile' SUP top STRUCTURAL MUST cn MAY ( classId $ certProfileConfig ) X-ORIGIN 'user defined' )
-- 
2.1.0

-------------- next part --------------
>From 5ff1c3b1fa0613080a27301260702e40a9ebe019 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 5/9] 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..788ef6bc9a26f62ed779586818525df56c555cea
--- /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
+     * createBackup=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 createBackup 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);
+    }
+}
-- 
2.1.0

-------------- next part --------------
>From 30519eeb9b44ead5b52b0bd95da9971b0c285a8c 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 6/9] Add LDAPProfileSubsystem to store profiles in LDAP

Add the LDAPProfileSubsystem as another IProfileSubsystem
implementation that can be used instead of ProfileSubsystem (which
stores profiles on the file system) to store files in LDAP so that
changes can be replicated.

Extract common behaviour in to new AbstractProfileSubsystem
superclass.

Also address the minor issue #1220.
---
 .../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 +-
 .../cmscore/profile/AbstractProfileSubsystem.java  | 151 ++++++++++++++
 .../cmscore/profile/LDAPProfileSubsystem.java      | 231 +++++++++++++++++++++
 .../netscape/cmscore/profile/ProfileSubsystem.java | 182 ++++------------
 8 files changed, 436 insertions(+), 199 deletions(-)
 create mode 100644 base/server/cmscore/src/com/netscape/cmscore/profile/AbstractProfileSubsystem.java
 create mode 100644 base/server/cmscore/src/com/netscape/cmscore/profile/LDAPProfileSubsystem.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 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 4861f2da556dd87147aa4840033078f4e7833350..94749cebde21b10e0ba354eb22bc770e7e9d3e8f 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/AbstractProfileSubsystem.java b/base/server/cmscore/src/com/netscape/cmscore/profile/AbstractProfileSubsystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..b7cd503a142fc0c3065c7d19de5229c7490613f4
--- /dev/null
+++ b/base/server/cmscore/src/com/netscape/cmscore/profile/AbstractProfileSubsystem.java
@@ -0,0 +1,151 @@
+// --- 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  Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.cmscore.profile;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.IConfigStore;
+import com.netscape.certsrv.base.ISubsystem;
+import com.netscape.certsrv.profile.EProfileException;
+import com.netscape.certsrv.profile.IProfile;
+import com.netscape.certsrv.profile.IProfileSubsystem;
+
+public abstract class AbstractProfileSubsystem implements IProfileSubsystem {
+    protected static final String PROP_CHECK_OWNER = "checkOwner";
+    protected static final String PROP_ENABLE = "enable";
+    protected static final String PROP_ENABLE_BY = "enableBy";
+
+    protected IConfigStore mConfig = null;
+    @SuppressWarnings("unused")
+    protected ISubsystem mOwner;
+    protected Vector<String> mProfileIds;
+    protected Hashtable<String, IProfile> mProfiles;
+    protected Hashtable<String, String> mProfileClassIds;
+
+    /**
+     * Returns the root configuration storage of this system.
+     * <P>
+     *
+     * @return configuration store of this subsystem
+     */
+    public IConfigStore getConfigStore() {
+        return mConfig;
+    }
+
+    /**
+     * Retrieves the name of this subsystem.
+     */
+    public String getId() {
+        return null;
+    }
+
+    /**
+     * Sets specific to this subsystem.
+     */
+    public void setId(String id) throws EBaseException {
+    }
+
+    public boolean isProfileEnable(String id) {
+        IProfile profile = mProfiles.get(id);
+        String enable = null;
+
+        try {
+            enable = profile.getConfigStore().getString(PROP_ENABLE);
+        } catch (EBaseException e) {
+        }
+        return Boolean.valueOf(enable);
+    }
+
+    public String getProfileEnableBy(String id) {
+        if (!isProfileEnable(id))
+            return null;
+        IProfile profile = mProfiles.get(id);
+        String enableBy = null;
+
+        try {
+            enableBy = profile.getConfigStore().getString(PROP_ENABLE_BY);
+        } catch (EBaseException e) {
+        }
+        return enableBy;
+    }
+
+    /**
+     * Enables a profile for execution.
+     */
+    public void enableProfile(String id, String enableBy)
+            throws EProfileException {
+        IProfile profile = mProfiles.get(id);
+
+        profile.getConfigStore().putString(PROP_ENABLE, "true");
+        profile.getConfigStore().putString(PROP_ENABLE_BY, enableBy);
+        try {
+            profile.getConfigStore().commit(false);
+        } catch (EBaseException e) {
+        }
+    }
+
+    /**
+     * Retrieves a profile by id.
+     */
+    public IProfile getProfile(String id)
+            throws EProfileException {
+        return mProfiles.get(id);
+    }
+
+    /**
+     * Disables a profile for execution.
+     */
+    public void disableProfile(String id)
+            throws EProfileException {
+        IProfile profile = mProfiles.get(id);
+
+        profile.getConfigStore().putString(PROP_ENABLE, "false");
+        try {
+            profile.getConfigStore().commit(false);
+        } catch (EBaseException e) {
+        }
+    }
+
+    public String getProfileClassId(String id) {
+        return mProfileClassIds.get(id);
+    }
+
+    /**
+     * Retrieves a list of profile ids. The return
+     * list is of type String.
+     */
+    public Enumeration<String> getProfileIds() {
+        return mProfileIds.elements();
+    }
+
+    /**
+     * Checks if owner id should be enforced during profile approval.
+     *
+     * @return true if approval should be checked
+     */
+    public boolean checkOwner() {
+        try {
+            return mConfig.getBoolean(PROP_CHECK_OWNER, false);
+        } catch (EBaseException e) {
+            return false;
+        }
+    }
+}
diff --git a/base/server/cmscore/src/com/netscape/cmscore/profile/LDAPProfileSubsystem.java b/base/server/cmscore/src/com/netscape/cmscore/profile/LDAPProfileSubsystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..810965d11a8f99635b7358b862ec620e0f651587
--- /dev/null
+++ b/base/server/cmscore/src/com/netscape/cmscore/profile/LDAPProfileSubsystem.java
@@ -0,0 +1,231 @@
+// --- 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, 2015  Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.cmscore.profile;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+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 LDAPProfileSubsystem
+        extends AbstractProfileSubsystem
+        implements IProfileSubsystem {
+
+    private String dn;
+    private ILdapConnFactory dbFactory;
+
+    /**
+     * Initializes this subsystem with the given configuration
+     * store.
+     * <P>
+     *
+     * @param owner owner of this subsystem
+     * @param config configuration store
+     * @exception EBaseException failed to initialize
+     */
+    public void init(ISubsystem owner, IConfigStore config)
+            throws EBaseException {
+        CMS.debug("LDAPProfileSubsystem: 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;
+
+        // Configuration File Format:
+        // *.list=profile1,profile2
+        // *.profile1.class=com.netscape.cms.profile.common.BasicProfile
+        // *.profile1.config=config/profiles/profile1.cfg
+        // *.profile2.class=com.netscape.cms.profile.common.BasicProfile
+        // *.profile2.config=config/profiles/profile2.cfg
+
+        // read profile id, implementation, and its configuration files
+        String basedn = cs.getString("internaldb.basedn");
+        String dn = "ou=certificateProfiles,ou=ca," + basedn;
+        LDAPConnection conn = dbFactory.getConn();
+
+        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();
+
+                    String classid = (String)
+                        ldapProfile.getAttribute("classId").getStringValues().nextElement();
+
+                    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();
+
+        while (ee.hasMoreElements()) {
+            String id = ee.nextElement();
+
+            CMS.debug("Registered Confirmation - " + id);
+        }
+    }
+
+    /**
+     * Creates a profile instance.
+     */
+    public IProfile createProfile(String id, String classid, String className)
+            throws EProfileException {
+        try {
+            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("LDAPProfileSubsystem: initing " + className);
+            IProfile profile = (IProfile) Class.forName(className).newInstance();
+            profile.setId(id);
+            profile.init(this, subStoreConfig);
+            mProfileIds.addElement(id);
+            mProfiles.put(id, profile);
+            mProfileClassIds.put(id, classid);
+            return profile;
+        } catch (Exception e) {
+            // throw exceptions
+            CMS.debug(e.toString());
+            CMS.debug(e);
+        }
+        return null;
+    }
+
+    public void deleteProfile(String id) throws EProfileException {
+        if (isProfileEnable(id)) {
+            throw new EProfileException("CMS_PROFILE_DELETE_ENABLEPROFILE");
+        }
+
+        LDAPConnection conn;
+        try {
+            conn = dbFactory.getConn();
+        } catch (ELdapException e) {
+            throw new EProfileException("Error acquiring the ldap connection" + e.toString());
+        }
+        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());
+            }
+        }
+
+        mProfileIds.removeElement(id);
+        mProfiles.remove(id);
+        mProfileClassIds.remove(id);
+    }
+
+    /**
+     * Notifies this subsystem if owner is in running mode.
+     */
+    public void startup() throws EBaseException {
+        CMS.debug("LDAPProfileSubsystem: startup");
+    }
+
+    /**
+     * Stops this system. The owner may call shutdown
+     * anytime after initialization.
+     * <P>
+     */
+    public void shutdown() {
+        mProfileIds.clear();
+        mProfiles.clear();
+        mProfileClassIds.clear();
+    }
+
+    /**
+     * 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;
+    }
+}
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..1c0d1b549cd0dee10feb32651189c58100d2439d 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java
@@ -33,34 +33,12 @@ import com.netscape.certsrv.profile.IProfileSubsystem;
 import com.netscape.certsrv.registry.IPluginInfo;
 import com.netscape.certsrv.registry.IPluginRegistry;
 
-public class ProfileSubsystem implements IProfileSubsystem {
+public class ProfileSubsystem
+        extends AbstractProfileSubsystem
+        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";
-    private static final String PROP_ENABLE_BY = "enableBy";
-
-    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>();
-
-    /**
-     * Retrieves the name of this subsystem.
-     */
-    public String getId() {
-        return null;
-    }
-
-    /**
-     * Sets specific to this subsystem.
-     */
-    public void setId(String id) throws EBaseException {
-    }
 
     /**
      * Initializes this subsystem with the given configuration
@@ -77,6 +55,10 @@ public class ProfileSubsystem implements IProfileSubsystem {
         IPluginRegistry registry = (IPluginRegistry)
                 CMS.getSubsystem(CMS.SUBSYSTEM_REGISTRY);
 
+        mProfileIds = new Vector<String>();
+        mProfiles = new Hashtable<String, IProfile>();
+        mProfileClassIds = new Hashtable<String, String>();
+
         mConfig = config;
         mOwner = owner;
 
@@ -102,8 +84,7 @@ public class ProfileSubsystem implements IProfileSubsystem {
             String configPath = subStore.getString(PROP_CONFIG);
 
             CMS.debug("Start Profile Creation - " + id + " " + classid + " " + info.getClassName());
-            createProfile(id, classid, info.getClassName(),
-                    configPath);
+            createProfile(id, classid, info.getClassName(), false);
 
             CMS.debug("Done Profile Creation - " + id);
         }
@@ -120,14 +101,27 @@ public class ProfileSubsystem implements IProfileSubsystem {
     /**
      * Creates a profile instance.
      */
-    public IProfile createProfile(String id, String classid, String className,
-            String configPath)
+    @Override
+    public IProfile createProfile(String id, String classid, String className)
             throws EProfileException {
+        return createProfile(id, classid, className, true);
+    }
+
+    private IProfile createProfile(String id, String classid, String className,
+            boolean isNew) throws EProfileException {
         IProfile profile = null;
 
+        String configPath;
+        try {
+            configPath = CMS.getConfigStore().getString("instanceRoot")
+                + "/ca/profiles/ca/" + id + ".cfg";
+        } catch (EBaseException e) {
+            throw new EProfileException("CMS_PROFILE_DELETE_DATABASEERROR");
+        }
+
         try {
-            profile = (IProfile) Class.forName(className).newInstance();
             IConfigStore subStoreConfig = CMS.createFileConfigStore(configPath);
+            profile = (IProfile) Class.forName(className).newInstance();
 
             CMS.debug("ProfileSubsystem: initing " + className);
             profile.setId(id);
@@ -135,6 +129,8 @@ public class ProfileSubsystem implements IProfileSubsystem {
             mProfileIds.addElement(id);
             mProfiles.put(id, profile);
             mProfileClassIds.put(id, classid);
+            if (isNew)
+                createProfileConfig(id, classid);
             return profile;
         } catch (Exception e) {
             // throw exceptions
@@ -144,7 +140,14 @@ public class ProfileSubsystem implements IProfileSubsystem {
         return null;
     }
 
-    public void deleteProfile(String id, String configPath) throws EProfileException {
+    public void deleteProfile(String id) throws EProfileException {
+        String configPath;
+        try {
+            configPath = CMS.getConfigStore().getString("instanceRoot")
+                + "/ca/profiles/ca/" + id + ".cfg";
+        } catch (EBaseException e) {
+            throw new EProfileException("CMS_PROFILE_DELETE_DATABASEERROR");
+        }
 
         if (isProfileEnable(id)) {
             throw new EProfileException("CMS_PROFILE_DELETE_ENABLEPROFILE");
@@ -185,9 +188,16 @@ public class ProfileSubsystem implements IProfileSubsystem {
         }
     }
 
-    public void createProfileConfig(String id, String classId,
-            String configPath)
+    private void createProfileConfig(String id, String classId)
             throws EProfileException {
+        String configPath;
+        try {
+            configPath = CMS.getConfigStore().getString("instanceRoot")
+                + "/ca/profiles/ca/" + id + ".cfg";
+        } catch (EBaseException e) {
+            throw new EProfileException("CMS_PROFILE_DELETE_DATABASEERROR");
+        }
+
         try {
             if (mProfiles.size() > 0) {
                 mConfig.putString(PROP_LIST,
@@ -220,110 +230,4 @@ public class ProfileSubsystem implements IProfileSubsystem {
         mProfiles.clear();
         mProfileClassIds.clear();
     }
-
-    /**
-     * Returns the root configuration storage of this system.
-     * <P>
-     *
-     * @return configuration store of this subsystem
-     */
-    public IConfigStore getConfigStore() {
-        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;
-
-        try {
-            enable = profile.getConfigStore().getString(PROP_ENABLE);
-        } catch (EBaseException e) {
-        }
-        if (enable == null || enable.equals("false"))
-            return false;
-        else
-            return true;
-    }
-
-    public String getProfileEnableBy(String id) {
-        if (!isProfileEnable(id))
-            return null;
-        IProfile profile = mProfiles.get(id);
-        String enableBy = null;
-
-        try {
-            enableBy = profile.getConfigStore().getString(PROP_ENABLE_BY);
-        } catch (EBaseException e) {
-        }
-        return enableBy;
-    }
-
-    /**
-     * Enables a profile for execution.
-     */
-    public void enableProfile(String id, String enableBy)
-            throws EProfileException {
-        IProfile profile = mProfiles.get(id);
-
-        profile.getConfigStore().putString(PROP_ENABLE, "true");
-        profile.getConfigStore().putString(PROP_ENABLE_BY, enableBy);
-        try {
-            profile.getConfigStore().commit(false);
-        } catch (EBaseException e) {
-        }
-    }
-
-    /**
-     * Disables a profile for execution.
-     */
-    public void disableProfile(String id)
-            throws EProfileException {
-        IProfile profile = mProfiles.get(id);
-
-        profile.getConfigStore().putString(PROP_ENABLE, "false");
-        try {
-            profile.getConfigStore().commit(false);
-        } catch (EBaseException e) {
-        }
-    }
-
-    /**
-     * Retrieves a profile by id.
-     */
-    public IProfile getProfile(String id)
-            throws EProfileException {
-        return mProfiles.get(id);
-    }
-
-    public String getProfileClassId(String id) {
-        return mProfileClassIds.get(id);
-    }
-
-    /**
-     * Retrieves a list of profile ids. The return
-     * list is of type String.
-     */
-    public Enumeration<String> getProfileIds() {
-        return mProfileIds.elements();
-    }
-
-    /**
-     * Checks if owner id should be enforced during profile approval.
-     *
-     * @return true if approval should be checked
-     */
-    public boolean checkOwner() {
-        try {
-            return mConfig.getBoolean(PROP_CHECK_OWNER, false);
-        } catch (EBaseException e) {
-            return false;
-        }
-    }
 }
-- 
2.1.0

-------------- next part --------------
>From daab6b8af0014929690edd7146de9ce779179fa6 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 7/9] 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.

A disabled subsystem does not have its `init` method called, but it
is still instantiated and added to the registry so that other code
can look up a subsystem by name and find out its class.

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   | 66 ++++++++++++++++------
 .../netscape/cmscore/app/CMSEngineDefaultStub.java |  3 +
 4 files changed, 83 insertions(+), 17 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/apps/CMS.java b/base/common/src/com/netscape/certsrv/apps/CMS.java
index 8b4bac2c0985637ceab6d55bf3d2b9a00b848412..85c8e58ca81c5bc3afc0b13fe3f391a047ba5a8e 100644
--- a/base/common/src/com/netscape/certsrv/apps/CMS.java
+++ b/base/common/src/com/netscape/certsrv/apps/CMS.java
@@ -509,6 +509,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 04ff5ec46cab59eaf8e32e709677fcae66a33420..b682130dd43810d7cd2122e5390a2a08889c372d 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,13 +950,17 @@ public class CMSEngine implements ICMSEngine {
         IConfigStore ssConfig = mConfig.getSubStore(id);
 
         CMS.debug("CMSEngine: initSubsystem id=" + id);
+        mSSReg.put(id, ss);
         if (doSetId)
             ss.setId(id);
+        if (!ssinfo.enabled) {
+            CMS.debug("CMSEngine: subsystem disabled id=" + id);
+            return;
+        }
         CMS.debug("CMSEngine: ready to init id=" + id);
         ss.init(this, ssConfig);
         // add to id - subsystem hash table.
         CMS.debug("CMSEngine: done init id=" + id);
-        mSSReg.put(id, ss);
         CMS.debug("CMSEngine: initialized " + id);
 
         if (id.equals("ca") || id.equals("ocsp") ||
@@ -2001,10 +2027,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;
     }
-- 
2.1.0

-------------- next part --------------
>From 9459bb3e60dc754fb7a7da6a91b7bdce426893d2 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 8/9] Import profiles when spawning CA instance

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

diff --git a/base/ca/shared/conf/CS.cfg.in b/base/ca/shared/conf/CS.cfg.in
index 1831f3c8c7edf4cc26de7233460d92bea6c49ebb..5b9f66680d14bbcc9f38133fd032bf2400e75ecc 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..883ab3779a811e35efe571e505d93def090137c5 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,28 @@
 // --- 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;
+import com.netscape.cmscore.profile.LDAPProfileSubsystem;
+
 
 /**
  * @author alee
@@ -64,5 +79,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 profile subsystem");
+        }
+    }
+
+    @Override
+    public void initializeDatabase(ConfigurationRequest data) {
+        super.initializeDatabase(data);
+
+        if (!data.isClone()
+                && CMS.getSubsystem("profile") instanceof LDAPProfileSubsystem) {
+            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 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 */);
     }
 }
-- 
2.1.0

-------------- next part --------------
>From c4e428c6f8cc90f446278ebcc47fce4bb2a21462 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 9/9] 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   | 179 ++++++++++++++++++---
 .../netscape/certsrv/profile/ProfileClient.java    |  42 +++++
 .../netscape/certsrv/profile/ProfileResource.java  |  20 ++-
 .../netscape/cmstools/profile/ProfileAddCLI.java   |  25 ++-
 .../com/netscape/cmstools/profile/ProfileCLI.java  |  15 ++
 .../netscape/cmstools/profile/ProfileEditCLI.java  | 113 +++++++++++++
 .../cmstools/profile/ProfileModifyCLI.java         |  26 ++-
 .../netscape/cmstools/profile/ProfileShowCLI.java  |  34 ++--
 8 files changed, 410 insertions(+), 44 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..692009eff4eab8e5e6e447e6f2304fbd229a395c 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;
@@ -499,6 +509,81 @@ public class ProfileService extends PKIService implements ProfileResource {
             return createCreatedResponse(profileData, profileData.getLink().getHref());
 
         } catch (EBaseException e) {
+            CMS.debug("createProfile: error creating profile");
+            CMS.debug(e);
+
+            auditProfileChange(
+                    ScopeDef.SC_PROFILE_RULES,
+                    OpDef.OP_ADD,
+                    profileId,
+                    ILogger.FAILURE,
+                    auditParams);
+
+            throw new PKIException("Error in creating profile");
+        }
+    }
+
+    @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;
+        Properties properties = new Properties();
+        try {
+            // load data and read profileId and classId
+            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.");
+        }
+        properties.remove("profileId");
+        properties.remove("classId");
+
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            properties.store(out, null);
+            data = out.toByteArray();  // original data sans profileId, classId
+
+            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();
 
@@ -550,6 +635,58 @@ 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");
+        }
+
+        Properties properties = new Properties();
+        try {
+            properties.load(new ByteArrayInputStream(data));
+        } catch (IOException e) {
+            throw new BadRequestException("Could not parse raw profile data.");
+        }
+        properties.remove("profileId");
+        properties.remove("classId");
+
+        try {
+            IProfile profile = ps.getProfile(profileId);
+            if (profile == null) {
+                throw new ProfileNotFoundException(profileId);
+            }
+
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            properties.store(out, null);
+            data = out.toByteArray();  // original data sans profileId, classId
+
+            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);
+            CMS.debug(e);
+            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..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,6 +50,11 @@ public class ProfileClient extends Client {
         return client.getEntity(response, ProfileData.class);
     }
 
+    public Properties retrieveProfileRaw(String id) {
+        Response response = profileClient.retrieveProfileRaw(id);
+        return byteArrayToProperties(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,13 +75,45 @@ public class ProfileClient extends Client {
         return client.getEntity(response, ProfileData.class);
     }
 
+    public Properties createProfileRaw(Properties properties) {
+        Response response =
+            profileClient.createProfileRaw(propertiesToByteArray(properties));
+        return byteArrayToProperties(client.getEntity(response, byte[].class));
+    }
+
     public ProfileData modifyProfile(ProfileData data) {
         Response response = profileClient.modifyProfile(data.getId(), data);
         return client.getEntity(response, ProfileData.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/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..7aaf09d8830fa02d52a5e5956e22fea4e45bf74e 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,13 @@
 package com.netscape.cmstools.profile;
 
-import java.io.FileNotFoundException;
+import java.io.IOException;
 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 +21,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 +65,20 @@ public class ProfileAddCLI extends CLI {
         }
 
         try {
-            ProfileData data = ProfileCLI.readProfileFromFile(filename);
-            data = profileCLI.profileClient.createProfile(data);
+            if (cmd.hasOption("raw")) {
+                Properties properties = ProfileCLI.readRawProfileFromFile(filename);
+                String profileId = properties.getProperty("profileId");
+                profileCLI.profileClient.createProfileRaw(properties).store(System.out, null);
+                MainCLI.printMessage("Added profile " + profileId);
+            } else {
+                ProfileData data = ProfileCLI.readProfileFromFile(filename);
+                data = profileCLI.profileClient.createProfile(data);
 
-            MainCLI.printMessage("Added profile " + data.getId());
+                MainCLI.printMessage("Added profile " + data.getId());
 
-            ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI());
-        } 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/ProfileCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java
index 6d8c6b655ced3807ac01364fe304b422986de567..e9e21596a0593d63b417d6d8560f3ff9b54caaf0 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java
@@ -3,8 +3,12 @@ package com.netscape.cmstools.profile;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.Locale;
+import java.util.Properties;
 
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
@@ -32,6 +36,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));
@@ -128,6 +133,16 @@ public class ProfileCLI extends CLI {
         return data;
     }
 
+    public static Properties readRawProfileFromFile(String filename)
+            throws IOException, RuntimeException {
+        Properties properties = new Properties();
+        properties.load(Files.newInputStream(Paths.get(filename)));
+        String profileId = properties.getProperty("profileId");
+        if (profileId == null)
+            throw new RuntimeException("Error: Missing profileId property in profile data.");
+        return properties;
+    }
+
     public static void saveEnrollmentTemplateToFile(String filename, CertEnrollmentRequest request)
             throws JAXBException, FileNotFoundException {
         JAXBContext context = JAXBContext.newInstance(CertEnrollmentRequest.class);
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..50600ba155b9b84edfdeb54e1177b4d0d46cc397
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java
@@ -0,0 +1,113 @@
+//--- 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 java.util.Properties;
+
+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
+        Properties orig = profileCLI.profileClient.retrieveProfileRaw(profileId);
+        String enabled = orig.getProperty("enable");
+        if (Boolean.valueOf(enabled)) {
+            System.err.println("Error: Cannot edit profile. Profile must be disabled.");
+            System.exit(-1);
+        }
+        Path tempFile = Files.createTempFile("pki", ".cfg");
+
+        try {
+            orig.store(Files.newOutputStream(tempFile), null);
+
+            // 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
+            Properties cur = new Properties();
+            cur.load(Files.newInputStream(tempFile));
+
+            if (!cur.equals(orig)) {
+                profileCLI.profileClient.modifyProfileRaw(profileId, cur);
+            }
+            cur.store(System.out, null);
+        } finally {
+            Files.delete(tempFile);
+        }
+    }
+}
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..cc0f415b78facbaa4cacb7ab4e914717586cfd0e 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,13 @@
 package com.netscape.cmstools.profile;
 
-import java.io.FileNotFoundException;
+import java.io.IOException;
 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 +21,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 +65,20 @@ public class ProfileModifyCLI extends CLI {
         }
 
         try {
-            ProfileData data = ProfileCLI.readProfileFromFile(filename);
-            data = profileCLI.profileClient.modifyProfile(data);
+            if (cmd.hasOption("raw")) {
+                Properties properties = ProfileCLI.readRawProfileFromFile(filename);
+                String profileId = properties.getProperty("profileId");
+                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);
 
-            MainCLI.printMessage("Modified profile " + data.getId());
+                MainCLI.printMessage("Modified profile " + data.getId());
 
-            ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI());
-
-        } 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..1dd85f43bf349c4dbca9b4b764f2d40fe7f421aa 100644
--- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java
@@ -1,6 +1,8 @@
 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;
@@ -26,9 +28,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 +76,24 @@ public class ProfileShowCLI extends CLI {
             }
         }
 
-        ProfileData profileData = profileCLI.profileClient.retrieveProfile(profileId);
+        if (cmd.hasOption("raw")) {
+            Properties profileConfig = profileCLI.profileClient.retrieveProfileRaw(profileId);
 
-        MainCLI.printMessage("Profile \"" + profileId + "\"");
-
-        if (filename != null) {
-            ProfileCLI.saveProfileToFile(filename, profileData);
+            if (filename != null) {
+                profileConfig.store(new FileOutputStream(filename), null);
+                MainCLI.printMessage("Saved profile " + profileId + " to " + filename);
+            } else {
+                profileConfig.store(System.out, null);
+            }
         } else {
-            ProfileCLI.printProfile(profileData, profileCLI.getClient().getConfig().getServerURI());
+            MainCLI.printMessage("Profile \"" + profileId + "\"");
+            ProfileData profileData = profileCLI.profileClient.retrieveProfile(profileId);
+
+            if (filename != null) {
+                ProfileCLI.saveProfileToFile(filename, profileData);
+            } else {
+                ProfileCLI.printProfile(profileData, profileCLI.getClient().getConfig().getServerURI());
+            }
         }
     }
 
-- 
2.1.0



More information about the Pki-devel mailing list