From 6379816c01cf3e58f84d2330b17cbbd0a0fbcc7f Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Mon, 16 Dec 2013 04:13:11 -0500
Subject: [PATCH] Fixed TPS resource statuses.

TPS resources that are stored in CS.cfg have been refactored to update
their statuses properly. These resources include profiles, profile
mappings, connections, and authenticators.

Ticket #654
---
 .../tps/authenticator/AuthenticatorClient.java     |  6 ++
 .../tps/authenticator/AuthenticatorResource.java   | 24 ++++--
 .../certsrv/tps/connection/ConnectionClient.java   |  6 ++
 .../certsrv/tps/connection/ConnectionResource.java | 24 ++++--
 .../certsrv/tps/profile/ProfileMappingClient.java  |  6 ++
 .../tps/profile/ProfileMappingResource.java        | 24 ++++--
 .../certsrv/tps/profile/ProfileResource.java       |  1 -
 .../tps/authenticator/AuthenticatorModifyCLI.java  | 37 ++++++---
 .../cmstools/tps/connection/ConnectionAddCLI.java  |  2 +-
 .../tps/connection/ConnectionModifyCLI.java        | 37 ++++++---
 .../cmstools/tps/profile/ProfileAddCLI.java        |  2 +-
 .../tps/profile/ProfileMappingModifyCLI.java       | 37 ++++++---
 .../cmstools/tps/profile/ProfileModifyCLI.java     | 28 ++++---
 .../com/netscape/cmscore/dbs/CSCfgDatabase.java    | 80 ++++++++++++++++++
 .../src/com/netscape/cmscore/dbs/CSCfgRecord.java} |  6 +-
 base/tps-tomcat/shared/conf/acl.ldif               |  8 +-
 base/tps-tomcat/shared/conf/acl.properties         |  3 +
 .../tps/authenticator/AuthenticatorDatabase.java   | 33 +++++---
 .../tps/authenticator/AuthenticatorRecord.java     | 88 +-------------------
 .../tps/authenticator/AuthenticatorService.java    | 93 ++++++++++++++++++++-
 .../server/tps/connection/ConnectionDatabase.java  | 34 +++++---
 .../server/tps/connection/ConnectionRecord.java    | 88 +-------------------
 .../server/tps/connection/ConnectionService.java   | 93 ++++++++++++++++++++-
 .../server/tps/profile/ProfileDatabase.java        | 64 ++++-----------
 .../server/tps/profile/ProfileMappingDatabase.java | 33 +++++---
 .../server/tps/profile/ProfileMappingRecord.java   | 88 +-------------------
 .../server/tps/profile/ProfileMappingService.java  | 94 +++++++++++++++++++++-
 .../server/tps/profile/ProfileRecord.java          | 88 +-------------------
 .../server/tps/profile/ProfileService.java         | 17 ++--
 29 files changed, 632 insertions(+), 512 deletions(-)
 create mode 100644 base/server/cmscore/src/com/netscape/cmscore/dbs/CSCfgDatabase.java
 copy base/{tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileRecord.java => server/cmscore/src/com/netscape/cmscore/dbs/CSCfgRecord.java} (96%)

diff --git a/base/common/src/com/netscape/certsrv/tps/authenticator/AuthenticatorClient.java b/base/common/src/com/netscape/certsrv/tps/authenticator/AuthenticatorClient.java
index 893d98e7c97e820c70c2b5c8978e8448d3c743ae..c4468e5cf5f04932d00270dc6e7e261e4ba89f6a 100644
--- a/base/common/src/com/netscape/certsrv/tps/authenticator/AuthenticatorClient.java
+++ b/base/common/src/com/netscape/certsrv/tps/authenticator/AuthenticatorClient.java
@@ -60,6 +60,12 @@ public class AuthenticatorClient extends Client {
         return client.getEntity(response);
     }
 
+    public AuthenticatorData changeAuthenticatorStatus(String authenticatorID, String action) {
+        @SuppressWarnings("unchecked")
+        ClientResponse<AuthenticatorData> response = (ClientResponse<AuthenticatorData>)resource.changeAuthenticatorStatus(authenticatorID, action);
+        return client.getEntity(response);
+    }
+
     public void removeAuthenticator(String authenticatorID) {
         resource.removeAuthenticator(authenticatorID);
     }
diff --git a/base/common/src/com/netscape/certsrv/tps/authenticator/AuthenticatorResource.java b/base/common/src/com/netscape/certsrv/tps/authenticator/AuthenticatorResource.java
index 6a2ef0d9a5f94c9d16bc72342147a534941c5773..1f925314468c15ff2a1c7583d712f99089d17f1c 100644
--- a/base/common/src/com/netscape/certsrv/tps/authenticator/AuthenticatorResource.java
+++ b/base/common/src/com/netscape/certsrv/tps/authenticator/AuthenticatorResource.java
@@ -55,25 +55,35 @@ public interface AuthenticatorResource {
     public AuthenticatorData getAuthenticator(@PathParam("authenticatorID") String authenticatorID);
 
     @POST
-    @ClientResponseType(entityType=AuthenticatorData.class)
-    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @ACLMapping("authenticators.add")
+    @ClientResponseType(entityType=AuthenticatorData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public Response addAuthenticator(AuthenticatorData authenticatorData);
 
     @PUT
     @Path("{authenticatorID}")
-    @ClientResponseType(entityType=AuthenticatorData.class)
-    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @ACLMapping("authenticators.modify")
+    @ClientResponseType(entityType=AuthenticatorData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public Response updateAuthenticator(
             @PathParam("authenticatorID") String authenticatorID,
             AuthenticatorData authenticatorData);
 
+    @POST
+    @Path("{authenticatorID}")
+    @ACLMapping("authenticators.approve")
+    @ClientResponseType(entityType=AuthenticatorData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public Response changeAuthenticatorStatus(
+            @PathParam("authenticatorID") String authenticatorID,
+            @QueryParam("action") String action);
+
     @DELETE
     @Path("{authenticatorID}")
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @ACLMapping("authenticators.remove")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public void removeAuthenticator(@PathParam("authenticatorID") String authenticatorID);
 }
diff --git a/base/common/src/com/netscape/certsrv/tps/connection/ConnectionClient.java b/base/common/src/com/netscape/certsrv/tps/connection/ConnectionClient.java
index 6c67e6b2a23ec57dbd4e5cbb5c2f3350aeccc0b4..d7e1252d36754f0874e8c49c8aa363ccccd1b341 100644
--- a/base/common/src/com/netscape/certsrv/tps/connection/ConnectionClient.java
+++ b/base/common/src/com/netscape/certsrv/tps/connection/ConnectionClient.java
@@ -60,6 +60,12 @@ public class ConnectionClient extends Client {
         return client.getEntity(response);
     }
 
+    public ConnectionData changeConnectionStatus(String connectionID, String action) {
+        @SuppressWarnings("unchecked")
+        ClientResponse<ConnectionData> response = (ClientResponse<ConnectionData>)resource.changeConnectionStatus(connectionID, action);
+        return client.getEntity(response);
+    }
+
     public void removeConnection(String connectionID) {
         resource.removeConnection(connectionID);
     }
diff --git a/base/common/src/com/netscape/certsrv/tps/connection/ConnectionResource.java b/base/common/src/com/netscape/certsrv/tps/connection/ConnectionResource.java
index e1be5c6b40399daef3c5b7e871a9aba46d3df9b5..21f4c08e5d2aa1a4e421e47ea7e4812212a98e67 100644
--- a/base/common/src/com/netscape/certsrv/tps/connection/ConnectionResource.java
+++ b/base/common/src/com/netscape/certsrv/tps/connection/ConnectionResource.java
@@ -55,25 +55,35 @@ public interface ConnectionResource {
     public ConnectionData getConnection(@PathParam("connectionID") String connectionID);
 
     @POST
-    @ClientResponseType(entityType=ConnectionData.class)
-    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @ACLMapping("connections.add")
+    @ClientResponseType(entityType=ConnectionData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public Response addConnection(ConnectionData connectionData);
 
     @PUT
     @Path("{connectionID}")
-    @ClientResponseType(entityType=ConnectionData.class)
-    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @ACLMapping("connections.modify")
+    @ClientResponseType(entityType=ConnectionData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public Response updateConnection(
             @PathParam("connectionID") String connectionID,
             ConnectionData connectionData);
 
+    @POST
+    @Path("{connectionID}")
+    @ACLMapping("connections.approve")
+    @ClientResponseType(entityType=ConnectionData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public Response changeConnectionStatus(
+            @PathParam("connectionID") String connectionID,
+            @QueryParam("action") String action);
+
     @DELETE
     @Path("{connectionID}")
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @ACLMapping("connections.remove")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public void removeConnection(@PathParam("connectionID") String connectionID);
 }
diff --git a/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingClient.java b/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingClient.java
index 58f4e26733cfebcb4620bed9aee0557be0e96b72..cd1d8227020050cf16753f7d6afa40162e67d29a 100644
--- a/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingClient.java
+++ b/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingClient.java
@@ -60,6 +60,12 @@ public class ProfileMappingClient extends Client {
         return client.getEntity(response);
     }
 
+    public ProfileMappingData changeProfileMappingStatus(String profileMappingID, String action) {
+        @SuppressWarnings("unchecked")
+        ClientResponse<ProfileMappingData> response = (ClientResponse<ProfileMappingData>)resource.changeProfileMappingStatus(profileMappingID, action);
+        return client.getEntity(response);
+    }
+
     public void removeProfileMapping(String profileMappingID) {
         resource.removeProfileMapping(profileMappingID);
     }
diff --git a/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingResource.java b/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingResource.java
index 04e606b8af840bdba346a578b5a63334dce6a8bc..698a1b13b3b46222bd09bb8a5ba8ad5f985e5f49 100644
--- a/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingResource.java
+++ b/base/common/src/com/netscape/certsrv/tps/profile/ProfileMappingResource.java
@@ -55,25 +55,35 @@ public interface ProfileMappingResource {
     public ProfileMappingData getProfileMapping(@PathParam("profileMappingID") String profileMappingID);
 
     @POST
-    @ClientResponseType(entityType=ProfileMappingData.class)
-    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @ACLMapping("profile-mappings.add")
+    @ClientResponseType(entityType=ProfileMappingData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public Response addProfileMapping(ProfileMappingData profileMappingData);
 
     @PUT
     @Path("{profileMappingID}")
-    @ClientResponseType(entityType=ProfileMappingData.class)
-    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @ACLMapping("profile-mappings.modify")
+    @ClientResponseType(entityType=ProfileMappingData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public Response updateProfileMapping(
             @PathParam("profileMappingID") String profileMappingID,
             ProfileMappingData profileMappingData);
 
+    @POST
+    @Path("{profileMappingID}")
+    @ACLMapping("profiles-mappings.approve")
+    @ClientResponseType(entityType=ProfileMappingData.class)
+    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    public Response changeProfileMappingStatus(
+            @PathParam("profileMappingID") String profileMappingID,
+            @QueryParam("action") String action);
+
     @DELETE
     @Path("{profileMappingID}")
-    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     @ACLMapping("profile-mappings.remove")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public void removeProfileMapping(@PathParam("profileMappingID") String profileMappingID);
 }
diff --git a/base/common/src/com/netscape/certsrv/tps/profile/ProfileResource.java b/base/common/src/com/netscape/certsrv/tps/profile/ProfileResource.java
index d86a4d809a86340f7487e61518eab570463e8523..e8a33c18b75c72674e03ed5382627e7e586f5237 100644
--- a/base/common/src/com/netscape/certsrv/tps/profile/ProfileResource.java
+++ b/base/common/src/com/netscape/certsrv/tps/profile/ProfileResource.java
@@ -81,7 +81,6 @@ public interface ProfileResource {
             @PathParam("profileID") String profileID,
             @QueryParam("action") String action);
 
-
     @DELETE
     @Path("{profileID}")
     @ACLMapping("profiles.remove")
diff --git a/base/java-tools/src/com/netscape/cmstools/tps/authenticator/AuthenticatorModifyCLI.java b/base/java-tools/src/com/netscape/cmstools/tps/authenticator/AuthenticatorModifyCLI.java
index 292e03f7551facf8a99d4daad979b0514e0c8947..0f82d1e550ba0491fc8eebe08102c25581f1cb15 100644
--- a/base/java-tools/src/com/netscape/cmstools/tps/authenticator/AuthenticatorModifyCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/tps/authenticator/AuthenticatorModifyCLI.java
@@ -48,8 +48,8 @@ public class AuthenticatorModifyCLI extends CLI {
 
     public void execute(String[] args) throws Exception {
 
-        Option option = new Option(null, "status", true, "Status: ENABLED, DISABLED.");
-        option.setArgName("status");
+        Option option = new Option(null, "action", true, "Action: update (default), approve, reject, enable, disable.");
+        option.setArgName("action");
         options.addOption(option);
 
         option = new Option(null, "input", true, "Input file containing authenticator properties.");
@@ -75,25 +75,38 @@ public class AuthenticatorModifyCLI extends CLI {
         }
 
         String authenticatorID = cmdArgs[0];
-        String status = cmd.getOptionValue("status");
+        String action = cmd.getOptionValue("action", "update");
         String input = cmd.getOptionValue("input");
 
         AuthenticatorData authenticatorData;
 
-        try (BufferedReader in = new BufferedReader(new FileReader(input));
-            StringWriter sw = new StringWriter();
-            PrintWriter out = new PrintWriter(sw, true)) {
+        if (action.equals("update")) {
 
-            String line;
-            while ((line = in.readLine()) != null) {
-                out.println(line);
+            if (input == null) {
+                System.err.println("Error: Missing input file");
+                printHelp();
+                System.exit(1);
+                return;
             }
 
-            authenticatorData = AuthenticatorData.valueOf(sw.toString());
+            try (BufferedReader in = new BufferedReader(new FileReader(input));
+                StringWriter sw = new StringWriter();
+                PrintWriter out = new PrintWriter(sw, true)) {
+
+                String line;
+                while ((line = in.readLine()) != null) {
+                    out.println(line);
+                }
+
+                authenticatorData = AuthenticatorData.valueOf(sw.toString());
+            }
+
+            authenticatorData = authenticatorCLI.authenticatorClient.updateAuthenticator(authenticatorID, authenticatorData);
+
+        } else { // other actions
+            authenticatorData = authenticatorCLI.authenticatorClient.changeAuthenticatorStatus(authenticatorID, action);
         }
 
-        authenticatorData = authenticatorCLI.authenticatorClient.updateAuthenticator(authenticatorID, authenticatorData);
-
         MainCLI.printMessage("Modified authenticator \"" + authenticatorID + "\"");
 
         AuthenticatorCLI.printAuthenticatorData(authenticatorData, true);
diff --git a/base/java-tools/src/com/netscape/cmstools/tps/connection/ConnectionAddCLI.java b/base/java-tools/src/com/netscape/cmstools/tps/connection/ConnectionAddCLI.java
index f01823a4a3177542807cf67d4fdb4192e28a30e4..5b9cf8781cc3f5095aa7f9e10f793ebdc0a773dd 100644
--- a/base/java-tools/src/com/netscape/cmstools/tps/connection/ConnectionAddCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/tps/connection/ConnectionAddCLI.java
@@ -43,7 +43,7 @@ public class ConnectionAddCLI extends CLI {
     }
 
     public void printHelp() {
-        formatter.printHelp(getFullName() + " <Connection ID> [OPTIONS...]", options);
+        formatter.printHelp(getFullName() + " [OPTIONS...]", options);
     }
 
     public void execute(String[] args) throws Exception {
diff --git a/base/java-tools/src/com/netscape/cmstools/tps/connection/ConnectionModifyCLI.java b/base/java-tools/src/com/netscape/cmstools/tps/connection/ConnectionModifyCLI.java
index 704410daa29dd05bc588d9ace5046ec055e7f796..4c1d992d8e7b96251c90f1360dd04a9a4a77f520 100644
--- a/base/java-tools/src/com/netscape/cmstools/tps/connection/ConnectionModifyCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/tps/connection/ConnectionModifyCLI.java
@@ -48,8 +48,8 @@ public class ConnectionModifyCLI extends CLI {
 
     public void execute(String[] args) throws Exception {
 
-        Option option = new Option(null, "status", true, "Status: ENABLED, DISABLED.");
-        option.setArgName("status");
+        Option option = new Option(null, "action", true, "Action: update (default), approve, reject, enable, disable.");
+        option.setArgName("action");
         options.addOption(option);
 
         option = new Option(null, "input", true, "Input file containing connection properties.");
@@ -75,25 +75,38 @@ public class ConnectionModifyCLI extends CLI {
         }
 
         String connectionID = cmdArgs[0];
-        String status = cmd.getOptionValue("status");
+        String action = cmd.getOptionValue("action", "update");
         String input = cmd.getOptionValue("input");
 
         ConnectionData connectionData;
 
-        try (BufferedReader in = new BufferedReader(new FileReader(input));
-            StringWriter sw = new StringWriter();
-            PrintWriter out = new PrintWriter(sw, true)) {
+        if (action.equals("update")) {
 
-            String line;
-            while ((line = in.readLine()) != null) {
-                out.println(line);
+            if (input == null) {
+                System.err.println("Error: Missing input file");
+                printHelp();
+                System.exit(1);
+                return;
             }
 
-            connectionData = ConnectionData.valueOf(sw.toString());
+            try (BufferedReader in = new BufferedReader(new FileReader(input));
+                StringWriter sw = new StringWriter();
+                PrintWriter out = new PrintWriter(sw, true)) {
+
+                String line;
+                while ((line = in.readLine()) != null) {
+                    out.println(line);
+                }
+
+                connectionData = ConnectionData.valueOf(sw.toString());
+            }
+
+            connectionData = connectionCLI.connectionClient.updateConnection(connectionID, connectionData);
+
+        } else { // other actions
+            connectionData = connectionCLI.connectionClient.changeConnectionStatus(connectionID, action);
         }
 
-        connectionData = connectionCLI.connectionClient.updateConnection(connectionID, connectionData);
-
         MainCLI.printMessage("Modified connection \"" + connectionID + "\"");
 
         ConnectionCLI.printConnectionData(connectionData, true);
diff --git a/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileAddCLI.java b/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileAddCLI.java
index d44ddc08e045351d8f7a88463a2748cd84bc8aba..503e9c54d71cff08770887ff8c48e57de37f7dd9 100644
--- a/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileAddCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileAddCLI.java
@@ -43,7 +43,7 @@ public class ProfileAddCLI extends CLI {
     }
 
     public void printHelp() {
-        formatter.printHelp(getFullName() + " <Profile ID> [OPTIONS...]", options);
+        formatter.printHelp(getFullName() + " [OPTIONS...]", options);
     }
 
     public void execute(String[] args) throws Exception {
diff --git a/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileMappingModifyCLI.java b/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileMappingModifyCLI.java
index 4561695ff6df1c3ed068e58f90b4f391f4afb3b0..d7c82deddeba770acaed33d2cd97e16f5e2cdfa4 100644
--- a/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileMappingModifyCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileMappingModifyCLI.java
@@ -48,8 +48,8 @@ public class ProfileMappingModifyCLI extends CLI {
 
     public void execute(String[] args) throws Exception {
 
-        Option option = new Option(null, "status", true, "Status: ENABLED, DISABLED.");
-        option.setArgName("status");
+        Option option = new Option(null, "action", true, "Action: update (default), approve, reject, enable, disable.");
+        option.setArgName("action");
         options.addOption(option);
 
         option = new Option(null, "input", true, "Input file containing profile mapping properties.");
@@ -75,25 +75,38 @@ public class ProfileMappingModifyCLI extends CLI {
         }
 
         String profileMappingID = cmdArgs[0];
-        String status = cmd.getOptionValue("status");
+        String action = cmd.getOptionValue("action", "update");
         String input = cmd.getOptionValue("input");
 
         ProfileMappingData profileMappingData;
 
-        try (BufferedReader in = new BufferedReader(new FileReader(input));
-            StringWriter sw = new StringWriter();
-            PrintWriter out = new PrintWriter(sw, true)) {
+        if (action.equals("update")) {
 
-            String line;
-            while ((line = in.readLine()) != null) {
-                out.println(line);
+            if (input == null) {
+                System.err.println("Error: Missing input file");
+                printHelp();
+                System.exit(1);
+                return;
             }
 
-            profileMappingData = ProfileMappingData.valueOf(sw.toString());
+            try (BufferedReader in = new BufferedReader(new FileReader(input));
+                StringWriter sw = new StringWriter();
+                PrintWriter out = new PrintWriter(sw, true)) {
+
+                String line;
+                while ((line = in.readLine()) != null) {
+                    out.println(line);
+                }
+
+                profileMappingData = ProfileMappingData.valueOf(sw.toString());
+            }
+
+            profileMappingData = profileMappingCLI.profileMappingClient.updateProfileMapping(profileMappingID, profileMappingData);
+
+        } else { // other actions
+            profileMappingData = profileMappingCLI.profileMappingClient.changeProfileMappingStatus(profileMappingID, action);
         }
 
-        profileMappingData = profileMappingCLI.profileMappingClient.updateProfileMapping(profileMappingID, profileMappingData);
-
         MainCLI.printMessage("Modified profile mapping \"" + profileMappingID + "\"");
 
         ProfileMappingCLI.printProfileMappingData(profileMappingData, true);
diff --git a/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileModifyCLI.java b/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileModifyCLI.java
index b4db037063e35d7d264732d4ceca5aff068f95b2..8f907ab914c865bc066476fd9e56a32e4bd2614a 100644
--- a/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileModifyCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/tps/profile/ProfileModifyCLI.java
@@ -48,14 +48,14 @@ public class ProfileModifyCLI extends CLI {
 
     public void execute(String[] args) throws Exception {
 
-        Option option = new Option(null, "input", true, "Input file containing profile properties.");
-        option.setArgName("file");
-        options.addOption(option);
-
-        option = new Option(null, "action", true, "Action: approve, reject, enable, disable.");
+        Option option = new Option(null, "action", true, "Action: update (default), approve, reject, enable, disable.");
         option.setArgName("action");
         options.addOption(option);
 
+        option = new Option(null, "input", true, "Input file containing profile properties.");
+        option.setArgName("file");
+        options.addOption(option);
+
         CommandLine cmd = null;
 
         try {
@@ -75,18 +75,20 @@ public class ProfileModifyCLI extends CLI {
         }
 
         String profileID = cmdArgs[0];
+        String action = cmd.getOptionValue("action", "update");
         String input = cmd.getOptionValue("input");
-        String action = cmd.getOptionValue("action");
 
         ProfileData profileData;
 
-        if (input == null && action == null || input != null && action != null) {
-            System.err.println("Error: Either input file or action should be specified");
-            printHelp();
-            System.exit(1);
-            return;
+        if (action.equals("update")) {
+
+            if (input == null) {
+                System.err.println("Error: Missing input file");
+                printHelp();
+                System.exit(1);
+                return;
+            }
 
-        } else if (input != null) {
             try (BufferedReader in = new BufferedReader(new FileReader(input));
                 StringWriter sw = new StringWriter();
                 PrintWriter out = new PrintWriter(sw, true)) {
@@ -101,7 +103,7 @@ public class ProfileModifyCLI extends CLI {
 
             profileData = profileCLI.profileClient.updateProfile(profileID, profileData);
 
-        } else { // action != null
+        } else { // other actions
             profileData = profileCLI.profileClient.changeProfileStatus(profileID, action);
         }
 
diff --git a/base/server/cmscore/src/com/netscape/cmscore/dbs/CSCfgDatabase.java b/base/server/cmscore/src/com/netscape/cmscore/dbs/CSCfgDatabase.java
new file mode 100644
index 0000000000000000000000000000000000000000..51a32182c7148dd9053463a56680e39725adbfcb
--- /dev/null
+++ b/base/server/cmscore/src/com/netscape/cmscore/dbs/CSCfgDatabase.java
@@ -0,0 +1,80 @@
+// --- 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) 2013 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmscore.dbs;
+
+import java.security.Principal;
+import java.util.Arrays;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.netscape.certsrv.apps.CMS;
+import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.IConfigStore;
+import com.netscape.cms.realm.PKIPrincipal;
+
+
+/**
+ * This class implements in-memory database which is stored in CS.cfg.
+ *
+ * @author Endi S. Dewata
+ */
+public class CSCfgDatabase<E extends CSCfgRecord> extends Database<E> {
+
+    public IConfigStore configStore = CMS.getConfigStore();
+    public String substoreName;
+
+    public CSCfgDatabase(String name, String substoreName) {
+        super(name);
+        this.substoreName = substoreName;
+    }
+
+    public boolean requiresApproval() throws EBaseException {
+        String value = configStore.getString("target.agent_approve.list", "");
+        return Arrays.asList(StringUtils.split(value, ",")).contains(substoreName);
+    }
+
+    public boolean canApprove(Principal principal) {
+        if (!(principal instanceof PKIPrincipal)) {
+            return false;
+        }
+
+        PKIPrincipal pkiPrincipal = (PKIPrincipal)principal;
+        return pkiPrincipal.hasRole("TUS Agents");
+    }
+
+    public String getRecordStatus(String recordID) throws EBaseException {
+        return configStore.getString("config." + substoreName + "." + recordID + ".state", "Disabled");
+    }
+
+    public void createRecordStatus(String recordID, String status) throws EBaseException {
+        if (status == null || requiresApproval()) status = "Disabled";
+        setRecordStatus(recordID, status);
+    }
+
+    public void setRecordStatus(String recordID, String status) throws EBaseException {
+        configStore.put("config." + substoreName + "." + recordID + ".state", status);
+        configStore.put("config." + substoreName + "." + recordID + ".timestamp",
+                "" + (System.currentTimeMillis() * 1000));
+    }
+
+    public void removeRecordStatus(String recordID) {
+        configStore.remove("config." + substoreName + "." + recordID + ".state");
+        configStore.remove("config." + substoreName + "." + recordID + ".timestamp");
+    }
+}
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileRecord.java b/base/server/cmscore/src/com/netscape/cmscore/dbs/CSCfgRecord.java
similarity index 96%
copy from base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileRecord.java
copy to base/server/cmscore/src/com/netscape/cmscore/dbs/CSCfgRecord.java
index 4a35841a31d6008e0a671afa8e398d004ed99cf4..f8e07b43e650cf72204ed232ddd1e9e8a0ca1981 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileRecord.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/dbs/CSCfgRecord.java
@@ -16,7 +16,7 @@
 // All rights reserved.
 // --- END COPYRIGHT BLOCK ---
 
-package org.dogtagpki.server.tps.profile;
+package com.netscape.cmscore.dbs;
 
 import java.util.Collection;
 import java.util.Map;
@@ -26,7 +26,7 @@ import java.util.TreeMap;
 /**
  * @author Endi S. Dewata
  */
-public class ProfileRecord {
+public class CSCfgRecord {
 
     String id;
     String status;
@@ -91,7 +91,7 @@ public class ProfileRecord {
             return false;
         if (getClass() != obj.getClass())
             return false;
-        ProfileRecord other = (ProfileRecord) obj;
+        CSCfgRecord other = (CSCfgRecord) obj;
         if (id == null) {
             if (other.id != null)
                 return false;
diff --git a/base/tps-tomcat/shared/conf/acl.ldif b/base/tps-tomcat/shared/conf/acl.ldif
index 5b0e4cf3b7b3c3ff9d89ee1f323e76db3fb1bd85..63cd990b2b1776d9abe6ea31e89492e0600421ef 100644
--- a/base/tps-tomcat/shared/conf/acl.ldif
+++ b/base/tps-tomcat/shared/conf/acl.ldif
@@ -21,12 +21,12 @@ resourceACLS: certServer.admin.certificate:import:allow (import) user="anybody":
 resourceACLS: certServer.admin.request.enrollment:submit,read,execute:allow (submit) user="anybody":Anybody may submit an enrollment request
 resourceACLS: certServer.clone.configuration:read,modify:allow (modify,read) group="Enterprise CA Administrators" || group="Enterprise KRA Administrators" || group="Enterprise OCSP Administrators" || group="Enterprise TPS Administrators":Only Enterprise Administrators are allowed to clone the configuration.
 resourceACLS: certServer.tps.account:login,logout:allow (login,logout) user="anybody":Anybody can login and logout
-resourceACLS: certServer.tps.authenticators:read,add,modify,remove:allow (read,add,modify,remove) group="TUS Administrators":Only admins can access authenticators.
+resourceACLS: certServer.tps.authenticators:read,add,modify,approve,remove:allow (read,add,modify,approve,remove) group="TUS Administrators":Only admins can access authenticators.
 resourceACLS: certServer.tps.config:read,modify:allow (read,modify) group="TUS Administrators":Only admins can access configuration.
-resourceACLS: certServer.tps.connections:read,add,modify,remove:allow (read,add,modify,remove) group="TUS Administrators":Only admins can access configuration.
+resourceACLS: certServer.tps.connections:read,add,modify,approve,remove:allow (read,add,modify,approve,remove) group="TUS Administrators":Only admins can access connections.
 resourceACLS: certServer.tps.groups:execute:allow (execute) group="TUS Administrators":Admins may execute group operations
 resourceACLS: certServer.tps.users:execute:allow (execute) group="TUS Administrators":Admins may execute user operations
-resourceACLS: certServer.tps.profiles:read,add,modify,approve,remove:allow (read) group="TUS Administrators" || group="TUS Agents" ; allow (add,modify,remove) group="TUS Administrators" ; allow (approve) group="TUS Agents":Admins, agents, and operators can read profiles, but only admins can add, modify, and remove profiles, and only agents can approve profiles.
-resourceACLS: certServer.tps.profile-mappings:read,add,modify,remove:allow (read,add,modify,remove) group="TUS Administrators" :Only admins can access profile mappings.
+resourceACLS: certServer.tps.profiles:read,add,modify,approve,remove:allow (read) group="TUS Administrators" || group="TUS Agents" ; allow (add,modify,remove) group="TUS Administrators" ; allow (approve) group="TUS Agents":Admins and agents can read, but only admins can add, modify, and remove, and only agents can approve.
+resourceACLS: certServer.tps.profile-mappings:read,add,modify,approve,remove:allow (read,add,modify,approve,remove) group="TUS Administrators" :Only admins can access profile mappings.
 resourceACLS: certServer.tps.selftests:read,execute:allow (read,execute) group="TUS Administrators":Only admins can access selftests.
 resourceACLS: certServer.tps.tokens:read,add,modify,remove:allow (read) group="TUS Administrators" || group="TUS Agents" || group="TUS Operators"; allow (add,remove) group="TUS Administrators" ; allow (modify) group="TUS Agents":Admins, agents, operators can read tokens, but only admins can add and remove tokens, and only agents can modify tokens.
diff --git a/base/tps-tomcat/shared/conf/acl.properties b/base/tps-tomcat/shared/conf/acl.properties
index 3697f017104de2031a2fe63ba3e416ae0377568d..aa49ba0b6ffa756ec2f9664b4b9765ebc604eb5a 100644
--- a/base/tps-tomcat/shared/conf/acl.properties
+++ b/base/tps-tomcat/shared/conf/acl.properties
@@ -11,12 +11,14 @@ account.logout = certServer.tps.account,logout
 authenticators.read = certServer.tps.authenticators,read
 authenticators.add = certServer.tps.authenticators,add
 authenticators.modify = certServer.tps.authenticators,modify
+authenticators.approve = certServer.tps.authenticators,approve
 authenticators.remove = certServer.tps.authenticators,remove
 config.read = certServer.tps.config,read
 config.modify = certServer.tps.config,modify
 connections.read = certServer.tps.connections,read
 connections.add = certServer.tps.connections,add
 connections.modify = certServer.tps.connections,modify
+connections.approve = certServer.tps.connections,approve
 connections.remove = certServer.tps.connections,remove
 groups = certServer.tps.groups,execute
 profiles.read = certServer.tps.profiles,read
@@ -27,6 +29,7 @@ profiles.remove = certServer.tps.profiles,remove
 profile-mappings.read = certServer.tps.profile-mappings,read
 profile-mappings.add = certServer.tps.profile-mappings,add
 profile-mappings.modify = certServer.tps.profile-mappings,modify
+profile-mappings.approve = certServer.tps.profiles-mappings,approve
 profile-mappings.remove = certServer.tps.profile-mappings,remove
 selftests.read = certServer.tps.selftests,read
 selftests.execute = certServer.tps.selftests,execute
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorDatabase.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorDatabase.java
index ce390016a736436a9df8ac3a5e79b46fc96b94ff..a4f735b73bd5afc20c1275c9c8d094dfcc1b47eb 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorDatabase.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorDatabase.java
@@ -27,22 +27,26 @@ import org.dogtagpki.server.tps.config.ConfigRecord;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
-import com.netscape.cmscore.dbs.Database;
+import com.netscape.cmscore.dbs.CSCfgDatabase;
 
 /**
  * This class provides access to the authenticators in CS.cfg.
  *
  * @author Endi S. Dewata
  */
-public class AuthenticatorDatabase extends Database<AuthenticatorRecord> {
+public class AuthenticatorDatabase extends CSCfgDatabase<AuthenticatorRecord> {
 
     public AuthenticatorDatabase() {
-        super("Authenticator");
+        super("Authenticator", "Authentication_Sources");
     }
 
     public AuthenticatorRecord createAuthenticatorRecord(ConfigDatabase configDatabase, ConfigRecord configRecord, String authenticatorID) throws EBaseException {
         AuthenticatorRecord authenticatorRecord = new AuthenticatorRecord();
         authenticatorRecord.setID(authenticatorID);
+
+        String status = getRecordStatus(authenticatorID);
+        authenticatorRecord.setStatus(status);
+
         Map<String, String> properties = configDatabase.getProperties(configRecord, authenticatorID);
         authenticatorRecord.setProperties(properties);
         return authenticatorRecord;
@@ -53,7 +57,7 @@ public class AuthenticatorDatabase extends Database<AuthenticatorRecord> {
 
         Collection<AuthenticatorRecord> result = new ArrayList<AuthenticatorRecord>();
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Authentication_Sources");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         for (String authenticatorID : configRecord.getKeys()) {
             AuthenticatorRecord authenticatorRecord = createAuthenticatorRecord(configDatabase, configRecord, authenticatorID);
@@ -67,7 +71,7 @@ public class AuthenticatorDatabase extends Database<AuthenticatorRecord> {
     public AuthenticatorRecord getRecord(String authenticatorID) throws Exception {
 
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Authentication_Sources");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         return createAuthenticatorRecord(configDatabase, configRecord, authenticatorID);
     }
@@ -77,7 +81,7 @@ public class AuthenticatorDatabase extends Database<AuthenticatorRecord> {
 
         CMS.debug("AuthenticatorDatabase.addRecord(\"" + authenticatorID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Authentication_Sources");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // validate new properties
         Map<String, String> properties = authenticatorRecord.getProperties();
@@ -85,11 +89,14 @@ public class AuthenticatorDatabase extends Database<AuthenticatorRecord> {
 
         // add new connection
         configRecord.addKey(authenticatorID);
-        configDatabase.updateRecord("Authentication_Sources", configRecord);
+        configDatabase.updateRecord(substoreName, configRecord);
 
         // store new properties
         configDatabase.addProperties(configRecord, authenticatorID, properties);
 
+        // create status
+        createRecordStatus(authenticatorID, authenticatorRecord.getStatus());
+
         configDatabase.commit();
     }
 
@@ -98,7 +105,7 @@ public class AuthenticatorDatabase extends Database<AuthenticatorRecord> {
 
         CMS.debug("AuthenticatorDatabase.updateRecord(\"" + authenticatorID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Authentication_Sources");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // validate new properties
         Map<String, String> properties = authenticatorRecord.getProperties();
@@ -110,6 +117,9 @@ public class AuthenticatorDatabase extends Database<AuthenticatorRecord> {
         // add new properties
         configDatabase.addProperties(configRecord, authenticatorID, properties);
 
+        // update status
+        setRecordStatus(authenticatorID, authenticatorRecord.getStatus());
+
         configDatabase.commit();
     }
 
@@ -118,14 +128,17 @@ public class AuthenticatorDatabase extends Database<AuthenticatorRecord> {
 
         CMS.debug("AuthenticatorDatabase.removeRecord(\"" + authenticatorID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Authentication_Sources");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // remove properties
         configDatabase.removeProperties(configRecord, authenticatorID);
 
         // remove connection
         configRecord.removeKey(authenticatorID);
-        configDatabase.updateRecord("Authentication_Sources", configRecord);
+        configDatabase.updateRecord(substoreName, configRecord);
+
+        // remove status
+        removeRecordStatus(authenticatorID);
 
         configDatabase.commit();
     }
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorRecord.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorRecord.java
index 74591bb798bc1d790864d6e3c0f713e3473e8a48..a07c05aaa068a885fc58e4daeaa272d7ca6e3e94 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorRecord.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorRecord.java
@@ -18,95 +18,11 @@
 
 package org.dogtagpki.server.tps.authenticator;
 
-import java.util.Collection;
-import java.util.Map;
-import java.util.TreeMap;
+import com.netscape.cmscore.dbs.CSCfgRecord;
 
 
 /**
  * @author Endi S. Dewata
  */
-public class AuthenticatorRecord {
-
-    String id;
-    String status;
-    Map<String, String> properties = new TreeMap<String, String>();
-
-    public String getID() {
-        return id;
-    }
-
-    public void setID(String id) {
-        this.id = id;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public Map<String, String> getProperties() {
-        return properties;
-    }
-
-    public void setProperties(Map<String, String> properties) {
-        this.properties.clear();
-        this.properties.putAll(properties);
-    }
-
-    public Collection<String> getPropertyNames() {
-        return properties.keySet();
-    }
-
-    public String getProperty(String name) {
-        return properties.get(name);
-    }
-
-    public void setProperty(String name, String value) {
-        properties.put(name, value);
-    }
-
-    public String removeProperty(String name) {
-        return properties.remove(name);
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((id == null) ? 0 : id.hashCode());
-        result = prime * result + ((properties == null) ? 0 : properties.hashCode());
-        result = prime * result + ((status == null) ? 0 : status.hashCode());
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        AuthenticatorRecord other = (AuthenticatorRecord) obj;
-        if (id == null) {
-            if (other.id != null)
-                return false;
-        } else if (!id.equals(other.id))
-            return false;
-        if (properties == null) {
-            if (other.properties != null)
-                return false;
-        } else if (!properties.equals(other.properties))
-            return false;
-        if (status == null) {
-            if (other.status != null)
-                return false;
-        } else if (!status.equals(other.status))
-            return false;
-        return true;
-    }
+public class AuthenticatorRecord extends CSCfgRecord {
 }
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorService.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorService.java
index a7fd78521d6ac59db0339a0cb46f907f12a73cb9..47edf23205b8f0cfe9030af5a02afe82b4fd20ff 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorService.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/authenticator/AuthenticatorService.java
@@ -21,6 +21,7 @@ package org.dogtagpki.server.tps.authenticator;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URLEncoder;
+import java.security.Principal;
 import java.util.Iterator;
 
 import javax.servlet.http.HttpServletRequest;
@@ -36,6 +37,7 @@ import org.jboss.resteasy.plugins.providers.atom.Link;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.BadRequestException;
+import com.netscape.certsrv.base.ForbiddenException;
 import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.tps.authenticator.AuthenticatorCollection;
 import com.netscape.certsrv.tps.authenticator.AuthenticatorData;
@@ -204,7 +206,27 @@ public class AuthenticatorService extends PKIService implements AuthenticatorRes
             TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
             AuthenticatorDatabase database = subsystem.getAuthenticatorDatabase();
 
-            database.updateRecord(authenticatorID, createAuthenticatorRecord(authenticatorData));
+            AuthenticatorRecord record = database.getRecord(authenticatorID);
+
+            String status = record.getStatus();
+            if (!"Disabled".equals(status)) {
+                throw new ForbiddenException("Unable to update authenticator " + authenticatorID);
+            }
+
+            status = authenticatorData.getStatus();
+            if (!"Enabled".equals(status)) {
+                throw new ForbiddenException("Invalid authenticator status: " + status);
+            }
+
+            Principal principal = servletRequest.getUserPrincipal();
+            if (database.requiresApproval() && !database.canApprove(principal)) {
+                status = "Pending_Approval";
+            }
+
+            record.setStatus(status);
+            record.setProperties(authenticatorData.getProperties());
+            database.updateRecord(authenticatorID, record);
+
             authenticatorData = createAuthenticatorData(database.getRecord(authenticatorID));
 
             return Response
@@ -222,6 +244,67 @@ public class AuthenticatorService extends PKIService implements AuthenticatorRes
     }
 
     @Override
+    public Response changeAuthenticatorStatus(String authenticatorID, String action) {
+
+        if (authenticatorID == null) throw new BadRequestException("Authenticator ID is null.");
+        if (action == null) throw new BadRequestException("Action is null.");
+
+        CMS.debug("AuthenticatorService.changeAuthenticatorStatus(\"" + authenticatorID + "\")");
+
+        try {
+            TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
+            AuthenticatorDatabase database = subsystem.getAuthenticatorDatabase();
+
+            AuthenticatorRecord record = database.getRecord(authenticatorID);
+            String status = record.getStatus();
+
+            if ("Disabled".equals(status)) {
+                if ("enable".equals(action)) {
+                    status = "Enabled";
+                } else {
+                    throw new BadRequestException("Invalid action: " + action);
+                }
+
+            } else if ("Enabled".equals(status)) {
+                if ("disable".equals(action)) {
+                    status = "Disabled";
+                } else {
+                    throw new BadRequestException("Invalid action: " + action);
+                }
+
+            } else if ("Pending_Approval".equals(status)) {
+                if ("approve".equals(action)) {
+                    status = "Enabled";
+                } else if ("reject".equals(action)) {
+                    status = "Disabled";
+                } else {
+                    throw new BadRequestException("Invalid action: " + action);
+                }
+
+            } else {
+                throw new PKIException("Invalid authenticator status: " + status);
+            }
+
+            record.setStatus(status);
+            database.updateRecord(authenticatorID, record);
+
+            AuthenticatorData authenticatorData = createAuthenticatorData(database.getRecord(authenticatorID));
+
+            return Response
+                    .ok(authenticatorData)
+                    .type(MediaType.APPLICATION_XML)
+                    .build();
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    @Override
     public void removeAuthenticator(String authenticatorID) {
 
         if (authenticatorID == null) throw new BadRequestException("Authenticator ID is null.");
@@ -231,6 +314,14 @@ public class AuthenticatorService extends PKIService implements AuthenticatorRes
         try {
             TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
             AuthenticatorDatabase database = subsystem.getAuthenticatorDatabase();
+
+            AuthenticatorRecord record = database.getRecord(authenticatorID);
+            String status = record.getStatus();
+
+            if (!"Disabled".equals(status)) {
+                throw new ForbiddenException("Unable to delete authenticator " + authenticatorID);
+            }
+
             database.removeRecord(authenticatorID);
 
         } catch (PKIException e) {
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionDatabase.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionDatabase.java
index b12c6b395c1c5b1d47fb42e8857767b787d9f2bc..1aadc7a072ab0d1094d19b14eb2e90cf9951c8d9 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionDatabase.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionDatabase.java
@@ -27,22 +27,29 @@ import org.dogtagpki.server.tps.config.ConfigRecord;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
-import com.netscape.cmscore.dbs.Database;
+import com.netscape.certsrv.base.IConfigStore;
+import com.netscape.cmscore.dbs.CSCfgDatabase;
 
 /**
  * This class provides access to the connections in CS.cfg.
  *
  * @author Endi S. Dewata
  */
-public class ConnectionDatabase extends Database<ConnectionRecord> {
+public class ConnectionDatabase extends CSCfgDatabase<ConnectionRecord> {
+
+    IConfigStore configStore = CMS.getConfigStore();
 
     public ConnectionDatabase() {
-        super("Connection");
+        super("Connection", "Subsystem_Connections");
     }
 
     public ConnectionRecord createConnectionRecord(ConfigDatabase configDatabase, ConfigRecord configRecord, String connectionID) throws EBaseException {
         ConnectionRecord connectionRecord = new ConnectionRecord();
         connectionRecord.setID(connectionID);
+
+        String status = getRecordStatus(connectionID);
+        connectionRecord.setStatus(status);
+
         Map<String, String> properties = configDatabase.getProperties(configRecord, connectionID);
         connectionRecord.setProperties(properties);
         return connectionRecord;
@@ -67,7 +74,7 @@ public class ConnectionDatabase extends Database<ConnectionRecord> {
     public ConnectionRecord getRecord(String connectionID) throws Exception {
 
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Subsystem_Connections");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         return createConnectionRecord(configDatabase, configRecord, connectionID);
     }
@@ -77,7 +84,7 @@ public class ConnectionDatabase extends Database<ConnectionRecord> {
 
         CMS.debug("ConnectionDatabase.addRecord(\"" + connectionID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Subsystem_Connections");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // validate new properties
         Map<String, String> properties = connectionRecord.getProperties();
@@ -85,11 +92,14 @@ public class ConnectionDatabase extends Database<ConnectionRecord> {
 
         // add new connection
         configRecord.addKey(connectionID);
-        configDatabase.updateRecord("Subsystem_Connections", configRecord);
+        configDatabase.updateRecord(substoreName, configRecord);
 
         // store new properties
         configDatabase.addProperties(configRecord, connectionID, properties);
 
+        // create status
+        createRecordStatus(connectionID, connectionRecord.getStatus());
+
         configDatabase.commit();
     }
 
@@ -98,7 +108,7 @@ public class ConnectionDatabase extends Database<ConnectionRecord> {
 
         CMS.debug("ConnectionDatabase.updateRecord(\"" + connectionID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Subsystem_Connections");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // validate new properties
         Map<String, String> properties = connectionRecord.getProperties();
@@ -110,6 +120,9 @@ public class ConnectionDatabase extends Database<ConnectionRecord> {
         // add new properties
         configDatabase.addProperties(configRecord, connectionID, properties);
 
+        // update status
+        setRecordStatus(connectionID, connectionRecord.getStatus());
+
         configDatabase.commit();
     }
 
@@ -118,14 +131,17 @@ public class ConnectionDatabase extends Database<ConnectionRecord> {
 
         CMS.debug("ConnectionDatabase.removeRecord(\"" + connectionID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Subsystem_Connections");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // remove properties
         configDatabase.removeProperties(configRecord, connectionID);
 
         // remove connection
         configRecord.removeKey(connectionID);
-        configDatabase.updateRecord("Subsystem_Connections", configRecord);
+        configDatabase.updateRecord(substoreName, configRecord);
+
+        // remove status
+        removeRecordStatus(connectionID);
 
         configDatabase.commit();
     }
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionRecord.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionRecord.java
index 1219af98149d2c7e09db4165a58022ded240f1a9..71aac3139307b75e1085d30e25adc2dcde2e00ab 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionRecord.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionRecord.java
@@ -18,95 +18,11 @@
 
 package org.dogtagpki.server.tps.connection;
 
-import java.util.Collection;
-import java.util.Map;
-import java.util.TreeMap;
+import com.netscape.cmscore.dbs.CSCfgRecord;
 
 
 /**
  * @author Endi S. Dewata
  */
-public class ConnectionRecord {
-
-    String id;
-    String status;
-    Map<String, String> properties = new TreeMap<String, String>();
-
-    public String getID() {
-        return id;
-    }
-
-    public void setID(String id) {
-        this.id = id;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public Map<String, String> getProperties() {
-        return properties;
-    }
-
-    public void setProperties(Map<String, String> properties) {
-        this.properties.clear();
-        this.properties.putAll(properties);
-    }
-
-    public Collection<String> getPropertyNames() {
-        return properties.keySet();
-    }
-
-    public String getProperty(String name) {
-        return properties.get(name);
-    }
-
-    public void setProperty(String name, String value) {
-        properties.put(name, value);
-    }
-
-    public String removeProperty(String name) {
-        return properties.remove(name);
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((id == null) ? 0 : id.hashCode());
-        result = prime * result + ((properties == null) ? 0 : properties.hashCode());
-        result = prime * result + ((status == null) ? 0 : status.hashCode());
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        ConnectionRecord other = (ConnectionRecord) obj;
-        if (id == null) {
-            if (other.id != null)
-                return false;
-        } else if (!id.equals(other.id))
-            return false;
-        if (properties == null) {
-            if (other.properties != null)
-                return false;
-        } else if (!properties.equals(other.properties))
-            return false;
-        if (status == null) {
-            if (other.status != null)
-                return false;
-        } else if (!status.equals(other.status))
-            return false;
-        return true;
-    }
+public class ConnectionRecord extends CSCfgRecord {
 }
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionService.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionService.java
index d17d766dbaf6f81230ed3408a33515dd34034271..85c64f31dd75f341dc70b4fe87e17e44a0d2ecec 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionService.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/connection/ConnectionService.java
@@ -21,6 +21,7 @@ package org.dogtagpki.server.tps.connection;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URLEncoder;
+import java.security.Principal;
 import java.util.Iterator;
 
 import javax.servlet.http.HttpServletRequest;
@@ -36,6 +37,7 @@ import org.jboss.resteasy.plugins.providers.atom.Link;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.BadRequestException;
+import com.netscape.certsrv.base.ForbiddenException;
 import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.tps.connection.ConnectionCollection;
 import com.netscape.certsrv.tps.connection.ConnectionData;
@@ -204,7 +206,27 @@ public class ConnectionService extends PKIService implements ConnectionResource
             TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
             ConnectionDatabase database = subsystem.getConnectionDatabase();
 
-            database.updateRecord(connectionData.getID(), createConnectionRecord(connectionData));
+            ConnectionRecord record = database.getRecord(connectionID);
+
+            String status = record.getStatus();
+            if (!"Disabled".equals(status)) {
+                throw new ForbiddenException("Unable to update connection " + connectionID);
+            }
+
+            status = connectionData.getStatus();
+            if (!"Enabled".equals(status)) {
+                throw new ForbiddenException("Invalid connection status: " + status);
+            }
+
+            Principal principal = servletRequest.getUserPrincipal();
+            if (database.requiresApproval() && !database.canApprove(principal)) {
+                status = "Pending_Approval";
+            }
+
+            record.setStatus(status);
+            record.setProperties(connectionData.getProperties());
+            database.updateRecord(connectionID, record);
+
             connectionData = createConnectionData(database.getRecord(connectionID));
 
             return Response
@@ -222,6 +244,67 @@ public class ConnectionService extends PKIService implements ConnectionResource
     }
 
     @Override
+    public Response changeConnectionStatus(String connectionID, String action) {
+
+        if (connectionID == null) throw new BadRequestException("Connection ID is null.");
+        if (action == null) throw new BadRequestException("Action is null.");
+
+        CMS.debug("ConnectionService.changeConnectionStatus(\"" + connectionID + "\")");
+
+        try {
+            TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
+            ConnectionDatabase database = subsystem.getConnectionDatabase();
+
+            ConnectionRecord record = database.getRecord(connectionID);
+            String status = record.getStatus();
+
+            if ("Disabled".equals(status)) {
+                if ("enable".equals(action)) {
+                    status = "Enabled";
+                } else {
+                    throw new BadRequestException("Invalid action: " + action);
+                }
+
+            } else if ("Enabled".equals(status)) {
+                if ("disable".equals(action)) {
+                    status = "Disabled";
+                } else {
+                    throw new BadRequestException("Invalid action: " + action);
+                }
+
+            } else if ("Pending_Approval".equals(status)) {
+                if ("approve".equals(action)) {
+                    status = "Enabled";
+                } else if ("reject".equals(action)) {
+                    status = "Disabled";
+                } else {
+                    throw new BadRequestException("Invalid action: " + action);
+                }
+
+            } else {
+                throw new PKIException("Invalid connection status: " + status);
+            }
+
+            record.setStatus(status);
+            database.updateRecord(connectionID, record);
+
+            ConnectionData connectionData = createConnectionData(database.getRecord(connectionID));
+
+            return Response
+                    .ok(connectionData)
+                    .type(MediaType.APPLICATION_XML)
+                    .build();
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    @Override
     public void removeConnection(String connectionID) {
 
         if (connectionID == null) throw new BadRequestException("Connection ID is null.");
@@ -231,6 +314,14 @@ public class ConnectionService extends PKIService implements ConnectionResource
         try {
             TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
             ConnectionDatabase database = subsystem.getConnectionDatabase();
+
+            ConnectionRecord record = database.getRecord(connectionID);
+            String status = record.getStatus();
+
+            if (!"Disabled".equals(status)) {
+                throw new ForbiddenException("Unable to delete connection " + connectionID);
+            }
+
             database.removeRecord(connectionID);
 
         } catch (PKIException e) {
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileDatabase.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileDatabase.java
index 646822f67fa0c690907c4739664eeecce72dfa54..794661281a335fddf0a51abd12fcb1cb20c9cd5b 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileDatabase.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileDatabase.java
@@ -18,54 +18,33 @@
 
 package org.dogtagpki.server.tps.profile;
 
-import java.security.Principal;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Map;
 
-import org.apache.commons.lang.StringUtils;
 import org.dogtagpki.server.tps.config.ConfigDatabase;
 import org.dogtagpki.server.tps.config.ConfigRecord;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
-import com.netscape.certsrv.base.IConfigStore;
-import com.netscape.cms.realm.PKIPrincipal;
-import com.netscape.cmscore.dbs.Database;
+import com.netscape.cmscore.dbs.CSCfgDatabase;
 
 /**
  * This class provides access to the profiles in CS.cfg.
  *
  * @author Endi S. Dewata
  */
-public class ProfileDatabase extends Database<ProfileRecord> {
-
-    IConfigStore configStore = CMS.getConfigStore();
+public class ProfileDatabase extends CSCfgDatabase<ProfileRecord> {
 
     public ProfileDatabase() {
-        super("Profile");
-    }
-
-    public boolean requiresApproval() throws EBaseException {
-        String value = configStore.getString("target.agent_approve.list", "");
-        return Arrays.asList(StringUtils.split(value, ",")).contains("Profiles");
-    }
-
-    public boolean canApprove(Principal principal) {
-        if (!(principal instanceof PKIPrincipal)) {
-            return false;
-        }
-
-        PKIPrincipal pkiPrincipal = (PKIPrincipal)principal;
-        return pkiPrincipal.hasRole("TUS Agents");
+        super("Profile", "Profiles");
     }
 
     public ProfileRecord createProfileRecord(ConfigDatabase configDatabase, ConfigRecord configRecord, String profileID) throws EBaseException {
         ProfileRecord profileRecord = new ProfileRecord();
         profileRecord.setID(profileID);
 
-        String status = configStore.getString("config.Profiles." + profileID + ".state", "Disabled");
+        String status = getRecordStatus(profileID);
         profileRecord.setStatus(status);
 
         Map<String, String> properties = configDatabase.getProperties(configRecord, profileID);
@@ -79,7 +58,7 @@ public class ProfileDatabase extends Database<ProfileRecord> {
 
         Collection<ProfileRecord> result = new ArrayList<ProfileRecord>();
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profiles");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         for (String profileID : configRecord.getKeys()) {
             ProfileRecord profileRecord = createProfileRecord(configDatabase, configRecord, profileID);
@@ -93,7 +72,7 @@ public class ProfileDatabase extends Database<ProfileRecord> {
     public ProfileRecord getRecord(String profileID) throws Exception {
 
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profiles");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         return createProfileRecord(configDatabase, configRecord, profileID);
     }
@@ -103,7 +82,7 @@ public class ProfileDatabase extends Database<ProfileRecord> {
 
         CMS.debug("ProfileDatabase.addRecord(\"" + profileID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profiles");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // validate new properties
         Map<String, String> properties = profileRecord.getProperties();
@@ -111,20 +90,13 @@ public class ProfileDatabase extends Database<ProfileRecord> {
 
         // add new profile
         configRecord.addKey(profileID);
-        configDatabase.updateRecord("Profiles", configRecord);
+        configDatabase.updateRecord(substoreName, configRecord);
 
         // store new properties
         configDatabase.addProperties(configRecord, profileID, properties);
 
-        // store status
-        String status = profileRecord.getStatus();
-        if (status == null || requiresApproval()) {
-            status = "Disabled";
-        }
-
-        IConfigStore configStore = CMS.getConfigStore();
-        configStore.put("config.Profiles." + profileID + ".state", status);
-        configStore.put("config.Profiles." + profileID + ".timestamp", "" + (System.currentTimeMillis() * 1000));
+        // create status
+        createRecordStatus(profileID, profileRecord.getStatus());
 
         configDatabase.commit();
     }
@@ -134,7 +106,7 @@ public class ProfileDatabase extends Database<ProfileRecord> {
 
         CMS.debug("ProfileDatabase.updateRecord(\"" + profileID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profiles");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // validate new properties
         Map<String, String> properties = profileRecord.getProperties();
@@ -146,9 +118,8 @@ public class ProfileDatabase extends Database<ProfileRecord> {
         // add new properties
         configDatabase.addProperties(configRecord, profileID, properties);
 
-        IConfigStore configStore = CMS.getConfigStore();
-        configStore.put("config.Profiles." + profileID + ".state", profileRecord.getStatus());
-        configStore.put("config.Profiles." + profileID + ".timestamp", "" + (System.currentTimeMillis() * 1000));
+        // update status
+        setRecordStatus(profileID, profileRecord.getStatus());
 
         configDatabase.commit();
     }
@@ -158,18 +129,17 @@ public class ProfileDatabase extends Database<ProfileRecord> {
 
         CMS.debug("ProfileDatabase.removeRecord(\"" + profileID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profiles");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // remove properties
         configDatabase.removeProperties(configRecord, profileID);
 
         // remove profile
         configRecord.removeKey(profileID);
-        configDatabase.updateRecord("Profiles", configRecord);
+        configDatabase.updateRecord(substoreName, configRecord);
 
-        IConfigStore configStore = CMS.getConfigStore();
-        configStore.remove("config.Profiles." + profileID + ".state");
-        configStore.remove("config.Profiles." + profileID + ".timestamp");
+        // remove status
+        removeRecordStatus(profileID);
 
         configDatabase.commit();
     }
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingDatabase.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingDatabase.java
index 496cce5b3e454f4bdde9fd481afd706aaea31bc4..febb71857c3c4293ebc43640eb4acdd030e61125 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingDatabase.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingDatabase.java
@@ -27,22 +27,26 @@ import org.dogtagpki.server.tps.config.ConfigRecord;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
-import com.netscape.cmscore.dbs.Database;
+import com.netscape.cmscore.dbs.CSCfgDatabase;
 
 /**
  * This class provides access to the profileMappings in CS.cfg.
  *
  * @author Endi S. Dewata
  */
-public class ProfileMappingDatabase extends Database<ProfileMappingRecord> {
+public class ProfileMappingDatabase extends CSCfgDatabase<ProfileMappingRecord> {
 
     public ProfileMappingDatabase() {
-        super("Profile Mapping");
+        super("Profile Mapping", "Profile_Mappings");
     }
 
     public ProfileMappingRecord createProfileMappingRecord(ConfigDatabase configDatabase, ConfigRecord configRecord, String profileMappingID) throws EBaseException {
         ProfileMappingRecord profileMappingRecord = new ProfileMappingRecord();
         profileMappingRecord.setID(profileMappingID);
+
+        String status = getRecordStatus(profileMappingID);
+        profileMappingRecord.setStatus(status);
+
         Map<String, String> properties = configDatabase.getProperties(configRecord, profileMappingID);
         profileMappingRecord.setProperties(properties);
         return profileMappingRecord;
@@ -53,7 +57,7 @@ public class ProfileMappingDatabase extends Database<ProfileMappingRecord> {
 
         Collection<ProfileMappingRecord> result = new ArrayList<ProfileMappingRecord>();
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profile_Mappings");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         for (String profileMappingID : configRecord.getKeys()) {
             ProfileMappingRecord profileMappingRecord = createProfileMappingRecord(configDatabase, configRecord, profileMappingID);
@@ -67,7 +71,7 @@ public class ProfileMappingDatabase extends Database<ProfileMappingRecord> {
     public ProfileMappingRecord getRecord(String profileMappingID) throws Exception {
 
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profile_Mappings");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         return createProfileMappingRecord(configDatabase, configRecord, profileMappingID);
     }
@@ -78,7 +82,7 @@ public class ProfileMappingDatabase extends Database<ProfileMappingRecord> {
 
         CMS.debug("ProfileMappingDatabase.addRecord(\"" + profileMappingID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profile_Mappings");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // validate new properties
         Map<String, String> properties = profileMappingRecord.getProperties();
@@ -86,11 +90,14 @@ public class ProfileMappingDatabase extends Database<ProfileMappingRecord> {
 
         // add new profileMapping
         configRecord.addKey(profileMappingID);
-        configDatabase.updateRecord("Profile_Mappings", configRecord);
+        configDatabase.updateRecord(substoreName, configRecord);
 
         // store new properties
         configDatabase.addProperties(configRecord, profileMappingID, properties);
 
+        // create status
+        createRecordStatus(profileMappingID, profileMappingRecord.getStatus());
+
         configDatabase.commit();
     }
 
@@ -99,7 +106,7 @@ public class ProfileMappingDatabase extends Database<ProfileMappingRecord> {
 
         CMS.debug("ProfileMappingDatabase.updateRecord(\"" + profileMappingID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profile_Mappings");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // validate new properties
         Map<String, String> properties = profileMappingRecord.getProperties();
@@ -111,6 +118,9 @@ public class ProfileMappingDatabase extends Database<ProfileMappingRecord> {
         // add new properties
         configDatabase.addProperties(configRecord, profileMappingID, properties);
 
+        // update status
+        setRecordStatus(profileMappingID, profileMappingRecord.getStatus());
+
         configDatabase.commit();
     }
 
@@ -119,14 +129,17 @@ public class ProfileMappingDatabase extends Database<ProfileMappingRecord> {
 
         CMS.debug("ProfileMappingDatabase.removeRecord(\"" + profileMappingID + "\")");
         ConfigDatabase configDatabase = new ConfigDatabase();
-        ConfigRecord configRecord = configDatabase.getRecord("Profile_Mappings");
+        ConfigRecord configRecord = configDatabase.getRecord(substoreName);
 
         // remove properties
         configDatabase.removeProperties(configRecord, profileMappingID);
 
         // remove profileMapping
         configRecord.removeKey(profileMappingID);
-        configDatabase.updateRecord("Profile_Mappings", configRecord);
+        configDatabase.updateRecord(substoreName, configRecord);
+
+        // remove status
+        removeRecordStatus(profileMappingID);
 
         configDatabase.commit();
     }
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingRecord.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingRecord.java
index 6ba29dc9d2719c3f063c2c683c78412154b8b788..e44acd24a11b8079cf7920dafc6bb5779c1a866d 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingRecord.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingRecord.java
@@ -18,95 +18,11 @@
 
 package org.dogtagpki.server.tps.profile;
 
-import java.util.Collection;
-import java.util.Map;
-import java.util.TreeMap;
+import com.netscape.cmscore.dbs.CSCfgRecord;
 
 
 /**
  * @author Endi S. Dewata
  */
-public class ProfileMappingRecord {
-
-    String id;
-    String status;
-    Map<String, String> properties = new TreeMap<String, String>();
-
-    public String getID() {
-        return id;
-    }
-
-    public void setID(String id) {
-        this.id = id;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public Map<String, String> getProperties() {
-        return properties;
-    }
-
-    public void setProperties(Map<String, String> properties) {
-        this.properties.clear();
-        this.properties.putAll(properties);
-    }
-
-    public Collection<String> getPropertyNames() {
-        return properties.keySet();
-    }
-
-    public String getProperty(String name) {
-        return properties.get(name);
-    }
-
-    public void setProperty(String name, String value) {
-        properties.put(name, value);
-    }
-
-    public String removeProperty(String name) {
-        return properties.remove(name);
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((id == null) ? 0 : id.hashCode());
-        result = prime * result + ((properties == null) ? 0 : properties.hashCode());
-        result = prime * result + ((status == null) ? 0 : status.hashCode());
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        ProfileMappingRecord other = (ProfileMappingRecord) obj;
-        if (id == null) {
-            if (other.id != null)
-                return false;
-        } else if (!id.equals(other.id))
-            return false;
-        if (properties == null) {
-            if (other.properties != null)
-                return false;
-        } else if (!properties.equals(other.properties))
-            return false;
-        if (status == null) {
-            if (other.status != null)
-                return false;
-        } else if (!status.equals(other.status))
-            return false;
-        return true;
-    }
+public class ProfileMappingRecord extends CSCfgRecord {
 }
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingService.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingService.java
index 116dfc0d6609b0ab98687726c02a90adb288e1b0..e9ff801b057497691d7b113f6e1aece98e425127 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingService.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileMappingService.java
@@ -21,6 +21,7 @@ package org.dogtagpki.server.tps.profile;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URLEncoder;
+import java.security.Principal;
 import java.util.Iterator;
 
 import javax.servlet.http.HttpServletRequest;
@@ -35,6 +36,8 @@ import org.dogtagpki.server.tps.TPSSubsystem;
 import org.jboss.resteasy.plugins.providers.atom.Link;
 
 import com.netscape.certsrv.apps.CMS;
+import com.netscape.certsrv.base.BadRequestException;
+import com.netscape.certsrv.base.ForbiddenException;
 import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.tps.profile.ProfileMappingCollection;
 import com.netscape.certsrv.tps.profile.ProfileMappingData;
@@ -196,7 +199,27 @@ public class ProfileMappingService extends PKIService implements ProfileMappingR
             TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
             ProfileMappingDatabase database = subsystem.getProfileMappingDatabase();
 
-            database.updateRecord(profileMappingData.getID(), createProfileMappingRecord(profileMappingData));
+            ProfileMappingRecord record = database.getRecord(profileMappingID);
+
+            String status = record.getStatus();
+            if (!"Disabled".equals(status)) {
+                throw new ForbiddenException("Unable to update profile mapping " + profileMappingID);
+            }
+
+            status = profileMappingData.getStatus();
+            if (!"Enabled".equals(status)) {
+                throw new ForbiddenException("Invalid profile mapping status: " + status);
+            }
+
+            Principal principal = servletRequest.getUserPrincipal();
+            if (database.requiresApproval() && !database.canApprove(principal)) {
+                status = "Pending_Approval";
+            }
+
+            record.setStatus(status);
+            record.setProperties(profileMappingData.getProperties());
+            database.updateRecord(profileMappingID, record);
+
             profileMappingData = createProfileMappingData(database.getRecord(profileMappingID));
 
             return Response
@@ -214,6 +237,67 @@ public class ProfileMappingService extends PKIService implements ProfileMappingR
     }
 
     @Override
+    public Response changeProfileMappingStatus(String profileMappingID, String action) {
+
+        if (profileMappingID == null) throw new BadRequestException("Profile mapping ID is null.");
+        if (action == null) throw new BadRequestException("Action is null.");
+
+        CMS.debug("ProfileMappingService.changeProfileMappingStatus(\"" + profileMappingID + "\")");
+
+        try {
+            TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
+            ProfileMappingDatabase database = subsystem.getProfileMappingDatabase();
+
+            ProfileMappingRecord record = database.getRecord(profileMappingID);
+            String status = record.getStatus();
+
+            if ("Disabled".equals(status)) {
+                if ("enable".equals(action)) {
+                    status = "Enabled";
+                } else {
+                    throw new BadRequestException("Invalid action: " + action);
+                }
+
+            } else if ("Enabled".equals(status)) {
+                if ("disable".equals(action)) {
+                    status = "Disabled";
+                } else {
+                    throw new BadRequestException("Invalid action: " + action);
+                }
+
+            } else if ("Pending_Approval".equals(status)) {
+                if ("approve".equals(action)) {
+                    status = "Enabled";
+                } else if ("reject".equals(action)) {
+                    status = "Disabled";
+                } else {
+                    throw new BadRequestException("Invalid action: " + action);
+                }
+
+            } else {
+                throw new PKIException("Invalid profile mapping status: " + status);
+            }
+
+            record.setStatus(status);
+            database.updateRecord(profileMappingID, record);
+
+            ProfileMappingData profileMappingData = createProfileMappingData(database.getRecord(profileMappingID));
+
+            return Response
+                    .ok(profileMappingData)
+                    .type(MediaType.APPLICATION_XML)
+                    .build();
+
+        } catch (PKIException e) {
+            throw e;
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PKIException(e.getMessage());
+        }
+    }
+
+    @Override
     public void removeProfileMapping(String profileMappingID) {
 
         CMS.debug("ProfileMappingService.removeProfileMapping(\"" + profileMappingID + "\")");
@@ -221,6 +305,14 @@ public class ProfileMappingService extends PKIService implements ProfileMappingR
         try {
             TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
             ProfileMappingDatabase database = subsystem.getProfileMappingDatabase();
+
+            ProfileMappingRecord record = database.getRecord(profileMappingID);
+            String status = record.getStatus();
+
+            if (!"Disabled".equals(status)) {
+                throw new ForbiddenException("Unable to delete profile mapping " + profileMappingID);
+            }
+
             database.removeRecord(profileMappingID);
 
         } catch (PKIException e) {
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileRecord.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileRecord.java
index 4a35841a31d6008e0a671afa8e398d004ed99cf4..20c254d03dec51c65a74fd920c3c35c4f97f8859 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileRecord.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileRecord.java
@@ -18,95 +18,11 @@
 
 package org.dogtagpki.server.tps.profile;
 
-import java.util.Collection;
-import java.util.Map;
-import java.util.TreeMap;
+import com.netscape.cmscore.dbs.CSCfgRecord;
 
 
 /**
  * @author Endi S. Dewata
  */
-public class ProfileRecord {
-
-    String id;
-    String status;
-    Map<String, String> properties = new TreeMap<String, String>();
-
-    public String getID() {
-        return id;
-    }
-
-    public void setID(String id) {
-        this.id = id;
-    }
-
-    public String getStatus() {
-        return status;
-    }
-
-    public void setStatus(String status) {
-        this.status = status;
-    }
-
-    public Map<String, String> getProperties() {
-        return properties;
-    }
-
-    public void setProperties(Map<String, String> properties) {
-        this.properties.clear();
-        this.properties.putAll(properties);
-    }
-
-    public Collection<String> getPropertyNames() {
-        return properties.keySet();
-    }
-
-    public String getProperty(String name) {
-        return properties.get(name);
-    }
-
-    public void setProperty(String name, String value) {
-        properties.put(name, value);
-    }
-
-    public String removeProperty(String name) {
-        return properties.remove(name);
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((id == null) ? 0 : id.hashCode());
-        result = prime * result + ((properties == null) ? 0 : properties.hashCode());
-        result = prime * result + ((status == null) ? 0 : status.hashCode());
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        ProfileRecord other = (ProfileRecord) obj;
-        if (id == null) {
-            if (other.id != null)
-                return false;
-        } else if (!id.equals(other.id))
-            return false;
-        if (properties == null) {
-            if (other.properties != null)
-                return false;
-        } else if (!properties.equals(other.properties))
-            return false;
-        if (status == null) {
-            if (other.status != null)
-                return false;
-        } else if (!status.equals(other.status))
-            return false;
-        return true;
-    }
+public class ProfileRecord extends CSCfgRecord {
 }
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileService.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileService.java
index d834d78e53a05a98752328cd241398bf4e2e6dd8..5ecd1c82cf3e42f1ab2b3e95da2550967ee08a2a 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileService.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/profile/ProfileService.java
@@ -208,24 +208,23 @@ public class ProfileService extends PKIService implements ProfileResource {
             ProfileDatabase database = subsystem.getProfileDatabase();
 
             ProfileRecord record = database.getRecord(profileID);
-            String status = record.getStatus();
 
+            String status = record.getStatus();
             if (!"Disabled".equals(status)) {
                 throw new ForbiddenException("Unable to update profile " + profileID);
             }
 
             status = profileData.getStatus();
-            if ("Enabled".equals(status)) {
-                Principal principal = servletRequest.getUserPrincipal();
-                if (database.requiresApproval() && !database.canApprove(principal)) {
-                    status = "Pending_Approval";
-                }
-                record.setStatus(status);;
-
-            } else {
+            if (!"Enabled".equals(status)) {
                 throw new ForbiddenException("Invalid profile status: " + status);
             }
 
+            Principal principal = servletRequest.getUserPrincipal();
+            if (database.requiresApproval() && !database.canApprove(principal)) {
+                status = "Pending_Approval";
+            }
+
+            record.setStatus(status);
             record.setProperties(profileData.getProperties());
             database.updateRecord(profileID, record);
 
-- 
1.8.4.2

