[Pki-devel] CLI for editing profiles

Fraser Tweedale ftweedal at redhat.com
Thu Aug 28 05:30:17 UTC 2014


All the suggested changes have been implemented except for
CLI-related changes 12 and 13, which are still in progress.
See also inline comments for item 7.

Patches 0004..0008 v2 attached, with updated 0009 to follow once the
CLI-related changes are complete.

Cheers,

Fraser

On Fri, Aug 08, 2014 at 02:14:21PM -0400, Ade Lee wrote:
> On Thu, 2014-08-07 at 17:15 +1000, Fraser Tweedale wrote:
> > On Wed, Jul 30, 2014 at 04:13:33PM -0500, Endi Sukma Dewata wrote:
> > > On 7/23/2014 2:09 AM, Fraser Tweedale wrote:
> > > >On Wed, Jul 23, 2014 at 11:27:11AM +1000, Fraser Tweedale wrote:
> > > >>Along with LDAP profiles, we will be adding modules to the CLI for
> > > >>adding and editing profiles in the ConfigStore format that was used
> > > >>for file-based profiles.  For more info, see:
> > > >>
> > > >>   http://pki.fedoraproject.org/wiki/LDAP_Profile_Storage#Command-line_utilities
> > > >>
> > > >>There is an existing CLI for adding and modifying profiles, in the
> > > >>XML format, e.g. ``pki ca profile add caCustomProfile.xml``.  The
> > > >>XML format carries information including the profile ID and
> > > >>class_id, but these data must be supplied out-of-band when dealing
> > > >>with the ConfigStore format.
> > > >>
> > > >>Because of this, I intend to:
> > > >>
> > > >>- add new commands to the existing profile CLI for working with the
> > > >>   "raw" (i.e., ConfigStore) format, e.g. "edit-raw", "add-raw".
> > > >>   Where necessary, these commands will take compulsory
> > > >>   ``--profile-id`` and/or ``--class-id`` arguments, to account for
> > > >>   the absense of such information in the profile ConfigStore format;
> > > >>
> > > >>   and
> > > >>
> > > >>- transport this information in the XML format - not in the "raw"
> > > >>   format - so that it will be unnecessary to make changes to
> > > >>   ProfileClient or the ProfileService API.
> > > >>
> > > >>As usual, I welcome feedback - especially if you feel I am going the
> > > >>wrong way ^_^
> > > >>
> > > >
> > > >An update: due to the various Profile and ProfileInput/Output/Policy
> > > >classes not being distributed in the pki-tools package (or
> > > >dependencies thereof), I /did/ end up adding methods to the REST
> > > >API.
> > > >
> > > >The `pki ca profile show-raw <profileId>` command is implemented in
> > > >the attached patch 0009.  I expected I will complete the edit-raw
> > > >and add-raw commands tomorrow, and after that will move on to
> > > >testing replication and polling/monitoring for changes to profiles.
> > > >
> > > >Cheers,
> > > >
> > > >Fraser
> > > 
> > > Some comments/questions:
> > > 
> > Thanks for the review/feedback Endi.  Responses inline.
> > 
> > > 1. About the the new ou=certProfiles entry, We probably should move  it into
> > > the ou=ca subtree because that subtree already contains other CA-specific
> > > LDAP entries such as ou=certificateRepository.
> > > 
> > This makes sense; shall do.
> > 
> 
> Lets also use the full name - ou=certificateProfiles, ou=ca, $baseDN
> 
> There are a few properties which should be specified, either as config
> parameters in CS.cfg or as constants in ProfileSubsystem.java.
> 
> The DN above for example, should probably be a config parameter in
> CS.cfg - something like profile.baseDN or better yet, a property of the
> ProfileSubsystem CS.cfg entries.
> 
> Things like the attribute used to store the data, the createAttrs etc.
> could be made constants in ProfileSubsystem.java.
> 
> > > 2. Is certProfilesLastModified really necessary? Each LDAP entry already has
> > > a modifyTimestamp attributes. To be accurate, the certProfilesLastModified
> > > has to be updated whenever the profiles are imported or updated. There seems
> > > to be no code for that yet, and there's no guarantee it won't get out of
> > > sync (e.g. server crash, direct LDAP modification).
> > > 
> > This attribute is intended for use by the mechanism that
> > monitors/polls LDAP for changes and reloads profiles as necessary.
> > There is no code for it yet; if I don't end up using it I will
> > remove it.
> > 
> 
> Yeah - I think we should use modifyTimeStamp attributes if we can,
> rather than relying on this extra attribute.
> 
> > > 3. If certProfilesLastModified is really necessary, instead of adding a
> > > separate cn=certProfilesInfo entry just to hold this attribute, we probably
> > > can move it into ou=certProfiles either by extending the organizationalUnit
> > > object class or adding an auxiliary object class. We probably should make it
> > > an optional attribute (i.e. no attribute means never updated).
> > > 
> > This is a nice idea.  If we keep it, I will do as you suggest.
> > 
> Agreed.
> 
> > > 4. I'm not sure if we really need the certProfileIsDefault attribute.
> > > There's no guarantee that the value will be accurate because someone could
> > > modify the certProfileConfig directly and forget to update
> > > certProfileIsDefault. Also, if we need to do any profile upgrade, it should
> > > be based on the actual configuration parameters in certProfileConfig, not
> > > based on the certProfileIsDefault.
> > > 
> > This attribute was intended for use by upgrade scripts.  I'm not
> > precious about keeping it, but it may be useful.  OTOH, default
> > profile updates are rare enough that all the information for a
> > three-way merge could be included in the upgrade helper.  So it can
> > probably be removed as you suggest.
> > 
> I agree on removing this attribute.
> 
> > > 5. In LDAPConfigStore let's not use the 'm' prefix for the field names. It's
> > > a legacy convention and we should not use it for newer code.
> > > 
> > OK
> yes - no  Hungarian notation please!
> 
> Also, perhaps rename attrs to createAttrs in LDAPConfigStore
> The comment is also wrong in line 61 -- not a File Config store.
> Line 65 decuments an argument cn which is not in the method signature.
> 
> > 
> > > 6. The mInDatabase in LDAPConfigStore doesn't seem to be used anywhere.
> > > 
> > Yep, I didn't end up using it.  Good catch.
> 
> Actually, I caught this too, by looking at the code using eclipse.  In
> general, you should make sure that any code that you are adding does not
> add any additional warnings.
> 
> > 
> > > 7. In LDAPConfigStore the certProfileConfig attribute is read as binary
> > > using getByteValues(). Is there a specific reason? Can we use
> > > getStringValues() since the content is a string (i.e. property file)?
> > > 
> > The load() function takes in InputStream, which is a byte stream
> > abstraction.  getByteValues() result in the least work.  If the
> > bytes/chars discrepancy is a cause for concern, I can change the
> > LDAP syntax to Octet String.
> > 
> +1 for using getStringValues().
> 
This change is included in the new patch.

> Do any of these InputStreams and OutputStreams require close()?
> I think we need to think about refactoring the DBManager/ LDAPConnection
> code so that the LDAPConnection implements Closeable.  Then we can
> manage all of these resources using try-with-resources constructions,
> and not have to worry about finally and nested finally blocks.
> 
> This should probably be a separate patch, but it would be nice to do
> that refactoring first, so that we could use the new cleaner code
> structure here.
> 

LDAPConnection is provided by ldapjdk so I don't think we can modify
that class.

We could define a "wrapper" class that implements the desired
behaviour.  It could contain an LDAPConnection and the factory and
in its close() method return the connection to the factory.  The
code that uses it could then just get the LDAPConnection out of the
wrapper, resulting in few changes, and ILdapConnFactory (and its
implementations) would learn a new method that returns one of these
"wrapped" connections instead of a "bare" LDAPConnection.

If this sounds like a reasonable approach, I will prototype this
design.

> Is there any synchronization required for the commit() for
> LDAPConfigStore()?
> 
The PropConfigStore load() and save() methods are synchronised.  I
hope that my expectation that the LDAP server will serialize
concurrent modifications to an entry is reasonably held.  Let me
know if I am wrong about that, and suggested approach(es) to
addressing it.

> > > 8. The ProfileService constructs the profile DN. I think this level of
> > > details should be hidden in the ProfileSubsystem layer. The ProfileService
> > > shouldn't need to know where/how the profiles are stored.
> > > 
> > This was the simply implementation based on existing usage of
> > ProfileService, i.e. the existing implementation told
> > ProfileSubsystem about file locations.  I will do this refactor.
> > 
> 
> Agreed.
> 
> > > 9. The ProfileAdminServlet calls ProfileSubsystem.createProfile() but it
> > > supplies a config path instead of a DN. As in #8, I think the caller should
> > > only specify the profile ID, it's up to the ProfileSubsystem to determine
> > > the actual location (i.e. profile DN).
> > > 
> > Whups, I missed this class.  Will update.
> > 
> 
> OK
> 
> > > 10. The mProfileDNs in ProfileSubsystem shouldn't be necessary because we
> > > can generate the DN from the profileId since it's a flat tree.
> > > 
> > This was motivated by the fact that the caller of
> > ProfileSubsystem.createProfile would specify the DN (see #8).  Will
> > remove in the refactor.
> +1
> 
> 
> ProfileSubsystem.createProfileConfig() does nothing now.  Lets remove it
> and its references.
> 
> > > 11. The profile subsystem index is hard-coded in the following line:
> > > 
> > >   cs.remove("subsystem.1.enabled");
> > > 
> > > Although this code is modifying the standard CS.cfg, it would be better to
> > > do a search the index, then make the modification with the index.
> > > 
> > >   int index = ... find subsystem index where id=profile ...
> > >   cs.remove("subsystem." + index + ".enabled");
> > > 
> > > This will avoid problems if we ever add a default subsystem in the future.
> > > 
> > Fair enough; will update.
> > 
> 
> Better still would be some methods to either get/enable/disable specific
> subsystems. Presumably, these methods would live in CMSEngine.java or
> some such.
> 
> > > 12. The profile-show-raw is essentially the same as profile-show, except the
> > > output format is different. I'd suggest merging them into profile-show and
> > > use an option like --raw to show the raw profile.
> > > 
> > > About the out-of-band class ID, I think we should just map it into a
> > > 'virtual' property in the raw profile (e.g. class_id=...). The property will
> > > only exist if the client pulls the raw profile. As already in done the
> > > patch, in the database it will be stored in classId attribute instead of
> > > certProfileConfig. In the long term all properties will be converted into
> > > separate attributes like this, and the 'raw' format should only exist as a
> > > legacy interface.
> > > 
> > Will assess impact of this change on the API (e.g., it may be
> > acceptable to have `null' classId, in which case it is looked for in
> > the config).  It will certainly simplify the "unified" CLI command
> > implementation you suggest.
> 
> Agreed - I prefer not to add another CLI command for a different output
> format.  We will already have two formats for profile-show -- xml or
> json.  raw is just another.
> 
> > 
> > > 13. The ProfileResource.retrieveProfileRaw() returns a byte[]. Although it
> > > works just fine for this purpose, I think returning a Properties would make
> > > it more developer-friendly. That way the output is readily usable, and it
> > > takes care of any CR/LF issue across different platforms. Maybe call it
> > > getProfileProperties()?
> > > 
> > I'll look into this implications and benefits of doing this... once
> > I've done all the other things :)
> > 
> +1 Lets make it more developer friendly.
> 
> > > 14. The CAInstallerService.importProfiles() and importProfile() don't need
> > > to be static. As virtual methods it will have access to other member
> > > attributes/methods already defined in the class and its super classes (e.g.
> > > config store). A long term goal is to eliminate the use of static methods in
> > > CMS (it's not very modular).
> > > 
> > Yep, there's a ConfigStore already available.  Will change to
> > non-static.
> > 
> > > 15. Minor. I see two styles of writing the try-catch block in the patch:
> > > 
> > >   try {
> > >       ...
> > >   } catch (...) {
> > >       ...
> > >   }
> > > 
> > > and
> > > 
> > >   try {
> > >       ...
> > >   }
> > >   catch (...) {
> > >       ...
> > >   }
> > > 
> > > I think the first one is more common.
> > >
> Yes - please use first version.
> 
> >  
> > > 16. Minor. Instead of adding the enabled parameter to the existing
> > > SubsystemInfo constructor (and having to modify all invocations), it's also
> > > possible to create a new constructor with the enabled parameter and call it
> > > from the old constructor with enabled set to true.
> > > 
> > Will do #15 and #16.
> > 
> 
> Otherwise, this looks like a great start (and a lot fewer changes than I
> would have expected).  Nice.
> 
> Ade
> 
> > > -- 
> > > Endi S. Dewata
> > 
> > _______________________________________________
> > Pki-devel mailing list
> > Pki-devel at redhat.com
> > https://www.redhat.com/mailman/listinfo/pki-devel
> 
> 
-------------- next part --------------
>From c82cb28f20ca329a4a4f6c4ea73e3ec65c120fcd 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/ca/shared/conf/schema.ldif | 15 +++++++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)

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

-------------- next part --------------
>From 2847bbde9b72616caca8b76275753ac717c21239 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 0000000..9fdf638
--- /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) {
+        PrintWriter writer = new PrintWriter(out);
+        Map<String, String> map = getProperties();
+        for (String k : map.keySet()) {
+            writer.println(k + "=" + map.get(k));
+        }
+        writer.close();
+    }
+
+    /**
+     * Commit the configuration to the database.
+     *
+     * All uses of LDAPProfileStore at time of writing call with
+     * backup=false, so the argument is ignored.
+     *
+     * If backup becomes necessary, the constructor should be
+     * modified to take a String backupAttr, and the existing
+     * content be copied to that attribute.
+     *
+     * @param backup Ignored.
+     */
+    public void commit(boolean createBackup) throws EBaseException {
+        ByteArrayOutputStream data = new ByteArrayOutputStream();
+        save(data, null);
+
+        LDAPAttribute configAttr = new LDAPAttribute(attr, data.toByteArray());
+
+        LDAPConnection conn = dbFactory.getConn();
+
+        // first attempt to modify; if modification fails (due
+        // to no such object), try and add the entry instead.
+        try {
+            try {
+                commitModify(conn, configAttr);
+            } catch (LDAPException e) {
+                if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) {
+                    commitAdd(conn, configAttr);
+                } else {
+                    throw e;
+                }
+            }
+        } catch (LDAPException e) {
+            throw new EBaseException(
+                "Error writing LDAPConfigStore '"
+                + dn + "': " + e.toString()
+            );
+        } finally {
+            dbFactory.returnConn(conn);
+        }
+    }
+
+    /**
+     * Update the record via an LDAPModification.
+     *
+     * @param conn LDAP connection.
+     * @param configAttr Config store attribute.
+     * @return true on success, false if the entry does not exist.
+     */
+    private void commitModify(LDAPConnection conn, LDAPAttribute configAttr)
+        throws LDAPException
+    {
+        LDAPModification ldapMod =
+            new LDAPModification(LDAPModification.REPLACE, configAttr);
+        conn.modify(dn, ldapMod);
+    }
+
+    /**
+     * Add the LDAPEntry via LDAPConnection.add.
+     *
+     * @param conn LDAP connection.
+     * @param configAttr Config store attribute.
+     * @return true on success, false if the entry already exists.
+     */
+    private void commitAdd(LDAPConnection conn, LDAPAttribute configAttr)
+        throws LDAPException
+    {
+        LDAPAttributeSet attrSet = new LDAPAttributeSet(createAttrs);
+        attrSet.add(configAttr);
+        LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet);
+        conn.add(ldapEntry);
+    }
+}
-- 
1.9.3

-------------- next part --------------
>From 3779e1ce85acfd73894efd102e1560766de17045 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] Change ProfileSubsystem to use LDAP database

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

diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java b/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java
index 3b2f8a5..d3f08b2 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 3238fb2..b7071fe 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 3d25b8b..b418baf 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 fe43094..cd7fa18 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 b77f86d..4f8cb27 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/base/FileConfigStore.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/base/FileConfigStore.java
@@ -33,12 +33,10 @@ import com.netscape.cmsutil.util.Utils;
 
 /**
  * FileConfigStore:
- * Extends HashConfigStore with methods to load/save from/to file for
+ * Extends PropConfigStore with methods to load/save from/to file for
  * persistent storage. This is a configuration store agent who
  * reads data from a file.
  * <P>
- * Note that a LdapConfigStore can be implemented so that it reads the configuration stores from the Ldap directory.
- * <P>
  *
  * @version $Revision$, $Date$
  * @see PropConfigStore
diff --git a/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java b/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java
index 27e7235..0be4d17 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/profile/ProfileSubsystem.java
@@ -17,26 +17,30 @@
 // --- END COPYRIGHT BLOCK ---
 package com.netscape.cmscore.profile;
 
-import java.io.File;
 import java.util.Enumeration;
 import java.util.Hashtable;
-import java.util.StringTokenizer;
 import java.util.Vector;
 
+import netscape.ldap.LDAPAttribute;
+import netscape.ldap.LDAPConnection;
+import netscape.ldap.LDAPEntry;
+import netscape.ldap.LDAPException;
+import netscape.ldap.LDAPSearchResults;
+
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.base.ISubsystem;
+import com.netscape.certsrv.ldap.ELdapException;
+import com.netscape.certsrv.ldap.ILdapConnFactory;
 import com.netscape.certsrv.profile.EProfileException;
 import com.netscape.certsrv.profile.IProfile;
 import com.netscape.certsrv.profile.IProfileSubsystem;
 import com.netscape.certsrv.registry.IPluginInfo;
 import com.netscape.certsrv.registry.IPluginRegistry;
+import com.netscape.cmscore.base.LDAPConfigStore;
 
 public class ProfileSubsystem implements IProfileSubsystem {
-    private static final String PROP_LIST = "list";
-    private static final String PROP_CLASS_ID = "class_id";
-    private static final String PROP_CONFIG = "config";
     private static final String PROP_CHECK_OWNER = "checkOwner";
 
     private static final String PROP_ENABLE = "enable";
@@ -45,9 +49,11 @@ public class ProfileSubsystem implements IProfileSubsystem {
     private IConfigStore mConfig = null;
     @SuppressWarnings("unused")
     private ISubsystem mOwner;
-    private Vector<String> mProfileIds = new Vector<String>();
-    private Hashtable<String, IProfile> mProfiles = new Hashtable<String, IProfile>();
-    private Hashtable<String, String> mProfileClassIds = new Hashtable<String, String>();
+    private Vector<String> mProfileIds;
+    private Hashtable<String, IProfile> mProfiles;
+    private Hashtable<String, String> mProfileClassIds;
+
+    private ILdapConnFactory dbFactory;
 
     /**
      * Retrieves the name of this subsystem.
@@ -74,9 +80,20 @@ public class ProfileSubsystem implements IProfileSubsystem {
     public void init(ISubsystem owner, IConfigStore config)
             throws EBaseException {
         CMS.debug("ProfileSubsystem: start init");
+
+        // (re)init member collections
+        mProfileIds = new Vector<String>();
+        mProfiles = new Hashtable<String, IProfile>();
+        mProfileClassIds = new Hashtable<String, String>();
+
         IPluginRegistry registry = (IPluginRegistry)
                 CMS.getSubsystem(CMS.SUBSYSTEM_REGISTRY);
 
+        IConfigStore cs = CMS.getConfigStore();
+        IConfigStore dbCfg = cs.getSubStore("internaldb");
+        dbFactory = CMS.getLdapBoundConnFactory();
+        dbFactory.init(dbCfg);
+
         mConfig = config;
         mOwner = owner;
 
@@ -88,24 +105,43 @@ public class ProfileSubsystem implements IProfileSubsystem {
         // *.profile2.config=config/profiles/profile2.cfg
 
         // read profile id, implementation, and its configuration files
-        String ids = config.getString(PROP_LIST, "");
-        StringTokenizer st = new StringTokenizer(ids, ",");
-
-        while (st.hasMoreTokens()) {
-            String id = st.nextToken();
-            IConfigStore subStore = config.getSubStore(id);
-            String classid = subStore.getString(PROP_CLASS_ID);
-            IPluginInfo info = registry.getPluginInfo("profile", classid);
-            if (info == null) {
-                throw new EBaseException("No plugins for type : profile, with id " + classid);
-            }
-            String configPath = subStore.getString(PROP_CONFIG);
+        String 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()) {
+                LDAPEntry ldapProfile = ldapProfiles.next();
+
+                String id = (String)
+                    ldapProfile.getAttribute("cn").getStringValues().nextElement();
+
+                String classid = (String)
+                    ldapProfile.getAttribute("classId").getStringValues().nextElement();
+
+                IPluginInfo info = registry.getPluginInfo("profile", classid);
+                if (info == null) {
+                    throw new EBaseException("No plugins for type : profile, with id " + classid);
+                }
+
+                CMS.debug("Start Profile Creation - " + id + " " + classid + " " + info.getClassName());
 
-            CMS.debug("Start Profile Creation - " + id + " " + classid + " " + info.getClassName());
-            createProfile(id, classid, info.getClassName(),
-                    configPath);
+                createProfile(id, classid, info.getClassName());
 
-            CMS.debug("Done Profile Creation - " + id);
+                CMS.debug("Done Profile Creation - " + id);
+            }
+        } catch (LDAPException e) {
+            throw new EBaseException("Error reading profiles: " + e.toString());
+        } finally {
+            try {
+                dbFactory.returnConn(conn);
+            } catch (Exception e) {
+                throw new EProfileException("Error releasing the ldap connection" + e.toString());
+            }
         }
 
         Enumeration<String> ee = getProfileIds();
@@ -120,16 +156,21 @@ public class ProfileSubsystem implements IProfileSubsystem {
     /**
      * Creates a profile instance.
      */
-    public IProfile createProfile(String id, String classid, String className,
-            String configPath)
+    public IProfile createProfile(String id, String classid, String className)
             throws EProfileException {
-        IProfile profile = null;
-
         try {
-            profile = (IProfile) Class.forName(className).newInstance();
-            IConfigStore subStoreConfig = CMS.createFileConfigStore(configPath);
+            String[] objectClasses = {"top", "certProfile"};
+            LDAPAttribute[] createAttrs = {
+                new LDAPAttribute("objectclass", objectClasses),
+                new LDAPAttribute("cn", id),
+                new LDAPAttribute("classId", classid)
+            };
+
+            IConfigStore subStoreConfig = new LDAPConfigStore(
+                dbFactory, profileDN(id), createAttrs, "certProfileConfig");
 
             CMS.debug("ProfileSubsystem: initing " + className);
+            IProfile profile = (IProfile) Class.forName(className).newInstance();
             profile.setId(id);
             profile.init(this, subStoreConfig);
             mProfileIds.addElement(id);
@@ -144,63 +185,32 @@ public class ProfileSubsystem implements IProfileSubsystem {
         return null;
     }
 
-    public void deleteProfile(String id, String configPath) throws EProfileException {
-
+    public void deleteProfile(String id) throws EProfileException {
         if (isProfileEnable(id)) {
             throw new EProfileException("CMS_PROFILE_DELETE_ENABLEPROFILE");
         }
 
-        String ids = "";
+        LDAPConnection conn;
         try {
-            ids = mConfig.getString(PROP_LIST, "");
-        } catch (Exception e) {
+            conn = dbFactory.getConn();
+        } catch (ELdapException e) {
+            throw new EProfileException("Error acquiring the ldap connection" + e.toString());
         }
-
-        StringTokenizer tokenizer = new StringTokenizer(ids, ",");
-        StringBuffer list = new StringBuffer();
-
-        while (tokenizer.hasMoreTokens()) {
-            String element = tokenizer.nextToken();
-
-            if (!element.equals(id)) {
-                list.append(element + ",");
+        try {
+            conn.delete(profileDN(id));
+        } catch (LDAPException e) {
+            throw new EProfileException("CMS_PROFILE_DELETE_DATABASEERROR");
+        } finally {
+            try {
+                dbFactory.returnConn(conn);
+            } catch (Exception e) {
+                throw new EProfileException("Error releasing the ldap connection" + e.toString());
             }
         }
-        if (list.length() != 0)
-            list.deleteCharAt(list.length() - 1);
-
-        mConfig.putString(PROP_LIST, list.toString());
-        mConfig.removeSubStore(id);
-        File file1 = new File(configPath);
 
-        if (!file1.delete()) {
-            CMS.debug("ProfileSubsystem: deleteProfile: Cannot delete the configuration file : " + configPath);
-        }
         mProfileIds.removeElement(id);
         mProfiles.remove(id);
         mProfileClassIds.remove(id);
-        try {
-            CMS.getConfigStore().commit(false);
-        } catch (Exception e) {
-        }
-    }
-
-    public void createProfileConfig(String id, String classId,
-            String configPath)
-            throws EProfileException {
-        try {
-            if (mProfiles.size() > 0) {
-                mConfig.putString(PROP_LIST,
-                        mConfig.getString(PROP_LIST) + "," + id);
-            } else {
-                mConfig.putString(PROP_LIST, id);
-            }
-            mConfig.putString(id + "." + PROP_CLASS_ID, classId);
-            mConfig.putString(id + "." + PROP_CONFIG, configPath);
-            CMS.getConfigStore().commit(true);
-        } catch (EBaseException e) {
-            CMS.debug(e.toString());
-        }
     }
 
     /**
@@ -231,13 +241,6 @@ public class ProfileSubsystem implements IProfileSubsystem {
         return mConfig;
     }
 
-    /**
-     * Adds a profile.
-     */
-    public void addProfile(String id, IProfile profile)
-            throws EProfileException {
-    }
-
     public boolean isProfileEnable(String id) {
         IProfile profile = mProfiles.get(id);
         String enable = null;
@@ -326,4 +329,20 @@ public class ProfileSubsystem implements IProfileSubsystem {
             return false;
         }
     }
+
+    /**
+     * Compute the profile DN given an ID.
+     */
+    private String profileDN(String id) throws EProfileException {
+        if (id == null) {
+            throw new EProfileException("CMS_PROFILE_DELETE_UNKNOWNPROFILE");
+        }
+        String basedn;
+        try {
+            basedn = CMS.getConfigStore().getString("internaldb.basedn");
+        } catch (EBaseException e) {
+            throw new EProfileException("CMS_PROFILE_DELETE_UNKNOWNPROFILE");
+        }
+        return "cn=" + id + ",ou=certificateProfiles,ou=ca," + basedn;
+    }
 }
-- 
1.9.3

-------------- next part --------------
>From d8aa387981de8af5e4a8be565ec36d953eb80092 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.

Subsystems are enabled by default.

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

diff --git a/base/common/src/com/netscape/certsrv/apps/CMS.java b/base/common/src/com/netscape/certsrv/apps/CMS.java
index 63c1a2c..38a6994 100644
--- a/base/common/src/com/netscape/certsrv/apps/CMS.java
+++ b/base/common/src/com/netscape/certsrv/apps/CMS.java
@@ -511,6 +511,28 @@ public final class CMS {
     }
 
     /**
+     * Enable the subsystem with the given ID.
+     *
+     * Does not start the subsystem.
+     *
+     * @param id Subsystem ID.
+     */
+    public static void enableSubsystem(String id) throws EBaseException {
+        _engine.setSubsystemEnabled(id, true);
+    }
+
+    /**
+     * Disable the subsystem with the given ID.
+     *
+     * Does not stop the subsystem.
+     *
+     * @param id Subsystem ID.
+     */
+    public static void disableSubsystem(String id) throws EBaseException {
+        _engine.setSubsystemEnabled(id, false);
+    }
+
+    /**
      * Retrieves the localized user message from UserMessages.properties.
      *
      * @param msgID message id defined in UserMessages.properties
diff --git a/base/common/src/com/netscape/certsrv/apps/ICMSEngine.java b/base/common/src/com/netscape/certsrv/apps/ICMSEngine.java
index 74fa090..5c78a7c 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 68c6482..1fbfade 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/apps/CMSEngine.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/apps/CMSEngine.java
@@ -181,6 +181,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 +867,34 @@ public class CMSEngine implements ICMSEngine {
         }
     }
 
+    private Vector<String> getDynSubsystemNames() throws EBaseException {
+        IConfigStore ssconfig = mConfig.getSubStore(PROP_SUBSYSTEM);
+        Enumeration<String> ssNames = ssconfig.getSubStoreNames();
+        Vector<String> ssNamesVector = new Vector<String>();
+        while (ssNames.hasMoreElements())
+            ssNamesVector.add(ssNames.nextElement());
+        return ssNamesVector;
+    }
+
     /**
      * 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();
+        Vector<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 +909,30 @@ 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);
+        Vector<String> ssNames = getDynSubsystemNames();
+        for (String ssName : ssNames) {
+            IConfigStore config = ssconfig.getSubStore(ssName);
+            if (id.equalsIgnoreCase(config.getString(PROP_ID))) {
+                config.putBoolean(PROP_ENABLED, enabled);
+                break;
+            }
+        }
+    }
+
     public LDAPConnection getBoundConnection(String host, int port,
                int version, LDAPSSLSocketFactoryExt fac, String bindDN,
                String bindPW) throws LDAPException {
@@ -928,6 +950,10 @@ public class CMSEngine implements ICMSEngine {
         IConfigStore ssConfig = mConfig.getSubStore(id);
 
         CMS.debug("CMSEngine: initSubsystem id=" + id);
+        if (!ssinfo.mEnabled) {
+            CMS.debug("CMSEngine: subsystem disabled id=" + id);
+            return;
+        }
         if (doSetId)
             ss.setId(id);
         CMS.debug("CMSEngine: ready to init id=" + id);
@@ -2000,10 +2026,16 @@ class WarningListener implements ILogEventListener {
 class SubsystemInfo {
     public final String mId;
     public final ISubsystem mInstance;
+    public final boolean mEnabled;
 
     public SubsystemInfo(String id, ISubsystem ssInstance) {
+        this(id, ssInstance, true);
+    }
+
+    public SubsystemInfo(String id, ISubsystem ssInstance, boolean enabled) {
         mId = id;
         mInstance = ssInstance;
+        mEnabled = enabled;
     }
 
 }
diff --git a/base/server/test/com/netscape/cmscore/app/CMSEngineDefaultStub.java b/base/server/test/com/netscape/cmscore/app/CMSEngineDefaultStub.java
index db39964..0b7518d 100644
--- a/base/server/test/com/netscape/cmscore/app/CMSEngineDefaultStub.java
+++ b/base/server/test/com/netscape/cmscore/app/CMSEngineDefaultStub.java
@@ -135,6 +135,9 @@ public class CMSEngineDefaultStub implements ICMSEngine {
         return null;
     }
 
+    public void setSubsystemEnabled(String id, boolean enabled) {
+    };
+
     public ISubsystem getSubsystem(String name) {
         return null;
     }
-- 
1.9.3

-------------- next part --------------
>From 961484b2ca845d3fbe40cf291d30041b114e2d66 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         | 115 +++++++++++++++++++++
 2 files changed, 116 insertions(+)

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



More information about the Pki-devel mailing list