>From d4348403cbe9dcc8984f580da325a14a680076fb Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Wed, 25 May 2016 06:27:46 +0200
Subject: [PATCH] Added TPS token state transition validation.

The TPSSubsystem has been modified to load and validate the token
state transition lists during initialization. If any of the lists
is empty or any of the transitions is invalid, the initialization
will fail and the subsystem will not start.

https://fedorahosted.org/pki/ticket/2334
---
 .../src/org/dogtagpki/server/tps/TPSSubsystem.java | 124 +++++++++++++++++----
 .../src/org/dogtagpki/server/tps/TPSTokendb.java   |   2 +-
 .../org/dogtagpki/server/tps/engine/TPSEngine.java |  51 ---------
 .../server/tps/processor/TPSEnrollProcessor.java   |   3 +-
 .../server/tps/processor/TPSProcessor.java         |   7 +-
 .../dogtagpki/server/tps/rest/TokenService.java    |  17 ++-
 6 files changed, 122 insertions(+), 82 deletions(-)

diff --git a/base/tps/src/org/dogtagpki/server/tps/TPSSubsystem.java b/base/tps/src/org/dogtagpki/server/tps/TPSSubsystem.java
index 2d415c16c7da0b4ad19ecc7d4b2ac3e2d329aa70..7146eb4cfe0b3a1f48743c807db9f03b3c4b63a9 100644
--- a/base/tps/src/org/dogtagpki/server/tps/TPSSubsystem.java
+++ b/base/tps/src/org/dogtagpki/server/tps/TPSSubsystem.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
 
+import org.apache.commons.lang.StringUtils;
 import org.dogtagpki.server.tps.authentication.AuthenticationManager;
 import org.dogtagpki.server.tps.cms.ConnectionManager;
 import org.dogtagpki.server.tps.config.AuthenticatorDatabase;
@@ -36,6 +37,7 @@ import org.dogtagpki.server.tps.dbs.TokenDatabase;
 import org.dogtagpki.server.tps.dbs.TokenRecord;
 import org.dogtagpki.server.tps.engine.TPSEngine;
 import org.dogtagpki.server.tps.mapping.MappingResolverManager;
+import org.dogtagpki.tps.main.TPSException;
 import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.CryptoManager.NotInitializedException;
 import org.mozilla.jss.crypto.ObjectNotFoundException;
@@ -51,6 +53,7 @@ import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.request.IRequestListener;
 import com.netscape.certsrv.request.IRequestQueue;
 import com.netscape.certsrv.tps.token.TokenStatus;
+import com.netscape.cmscore.base.FileConfigStore;
 import com.netscape.cmscore.dbs.DBSubsystem;
 
 /**
@@ -81,7 +84,9 @@ public class TPSSubsystem implements IAuthority, ISubsystem {
 
     public TPSEngine engine;
     public TPSTokendb tdb;
-    public Map<TokenStatus, Collection<TokenStatus>> allowedTransitions = new HashMap<TokenStatus, Collection<TokenStatus>>();
+
+    public Map<TokenStatus, Collection<TokenStatus>> uiTransitions;
+    public Map<TokenStatus, Collection<TokenStatus>> operationTransitions;
 
     @Override
     public String getId() {
@@ -116,45 +121,109 @@ public class TPSSubsystem implements IAuthority, ISubsystem {
         profileDatabase = new ProfileDatabase();
         profileMappingDatabase = new ProfileMappingDatabase();
 
-        CMS.debug("TokenSubsystem: allowed transitions:");
+        FileConfigStore defaultConfig = new FileConfigStore("/usr/share/pki/tps/conf/CS.cfg");
 
-        // initialize allowed token state transitions with empty containers
-        for (TokenStatus state : TokenStatus.values()) {
-            allowedTransitions.put(state, new LinkedHashSet<TokenStatus>());
-        }
+        uiTransitions = loadAndValidateTokenStateTransitions(
+                defaultConfig, cs, TPSEngine.CFG_TOKENDB_ALLOWED_TRANSITIONS);
 
-        // load allowed token state transitions from TPS configuration
-        for (String transition : cs.getString(TPSEngine.CFG_TOKENDB_ALLOWED_TRANSITIONS).split(",")) {
-            String states[] = transition.split(":");
-
-            TokenStatus fromState = TokenStatus.fromInt(Integer.valueOf(states[0]));
-            TokenStatus toState = TokenStatus.fromInt(Integer.valueOf(states[1]));
-            CMS.debug("TokenSubsystem:  - " + fromState + " to " + toState);
-
-            Collection<TokenStatus> nextStates = allowedTransitions.get(fromState);
-            nextStates.add(toState);
-        }
+        operationTransitions = loadAndValidateTokenStateTransitions(
+                defaultConfig, cs, TPSEngine.CFG_OPERATIONS_ALLOWED_TRANSITIONS);
 
         tdb = new TPSTokendb(this);
 
         engine = new TPSEngine();
         engine.init();
+    }
+
+    public Map<TokenStatus, Collection<TokenStatus>> loadTokenStateTransitions(IConfigStore cs, String property) throws EBaseException {
+
+        String value = cs.getString(property);
+
+        if (StringUtils.isEmpty(value)) {
+            CMS.debug("Missing token state transitions in " + property);
+            throw new EBaseException("Missing token state transition in " + property);
+        }
+
+        Map<TokenStatus, Collection<TokenStatus>> transitions = new HashMap<TokenStatus, Collection<TokenStatus>>();
+
+        // initialize list with empty containers
+        for (TokenStatus state : TokenStatus.values()) {
+            transitions.put(state, new LinkedHashSet<TokenStatus>());
+        }
+
+        for (String transition : value.split(",")) {
+
+            String states[] = transition.split(":");
+            if (states.length < 2) {
+                CMS.debug("Invalid token state transition in " + property + ": " + transition);
+                throw new EBaseException("Invalid token state transition in " + property + ": " + transition);
+            }
+
+            TokenStatus currentState = TokenStatus.fromInt(Integer.valueOf(states[0]));
+            TokenStatus nextState = TokenStatus.fromInt(Integer.valueOf(states[1]));
+
+            String info = currentState + " to " + nextState +
+                    " (" + currentState.getValue() + ":" + nextState.getValue() + ")";
+            CMS.debug("TokenSubsystem:   - " + info);
+
+            Collection<TokenStatus> nextStates = transitions.get(currentState);
+            nextStates.add(nextState);
+        }
 
+        return transitions;
     }
 
+    public void validateTokenStateTransitions(
+            Map<TokenStatus, Collection<TokenStatus>> defaultConfig,
+            Map<TokenStatus, Collection<TokenStatus>> userConfig) throws EBaseException {
+
+        for (TokenStatus currentState : userConfig.keySet()) {
+            Collection<TokenStatus> nextStates = userConfig.get(currentState);
+            Collection<TokenStatus> defaultNextStates = defaultConfig.get(currentState);
+
+            for (TokenStatus nextState : nextStates) {
+                if (!defaultNextStates.contains(nextState)) {
+                    String info = currentState + " to " + nextState +
+                            " (" + currentState.getValue() + ":" + nextState.getValue() + ")";
+                    throw new EBaseException("Unsupported token state transition: " + info);
+                }
+            }
+        }
+    }
+
+    public Map<TokenStatus, Collection<TokenStatus>> loadAndValidateTokenStateTransitions(
+            IConfigStore defaultConfig,
+            IConfigStore userDefinedConfig,
+            String property) throws EBaseException {
+
+        CMS.debug("TokenSubsystem: Loading transitions in " + property);
+
+        CMS.debug("TokenSubsystem: * default transitions:");
+        Map<TokenStatus, Collection<TokenStatus>> defaultTransitions =
+                loadTokenStateTransitions(defaultConfig, property);
+
+        CMS.debug("TokenSubsystem: * user-defined transitions:");
+        Map<TokenStatus, Collection<TokenStatus>> userDefinedTransitions =
+                loadTokenStateTransitions(userDefinedConfig, property);
+
+        CMS.debug("TokenSubsystem: Validating transitions in " + property);
+        validateTokenStateTransitions(defaultTransitions, userDefinedTransitions);
+
+        return userDefinedTransitions;
+    }
     /**
-     * Return the allowed next states for a given token based on TPS configuration.
+     * Return the allowed next states for changing token state via Web UI or CLI.
      *
-     * If the current state is SUSPENDED, token will be allowed transition to either
-     * FORMATTED or ACTIVE depending on whether the token has certificates.
+     * If the current state is SUSPENDED, token will be allowed to transition to
+     * either FORMATTED or ACTIVE depending on whether the token has certificates.
      *
      * @param tokenRecord
      * @return A non-null collection of allowed next token states.
      */
-    public Collection<TokenStatus> getNextTokenStates(TokenRecord tokenRecord) throws Exception {
+    public Collection<TokenStatus> getUINextTokenStates(TokenRecord tokenRecord) throws TPSException {
 
         TokenStatus currentState = tokenRecord.getTokenStatus();
-        Collection<TokenStatus> nextStates = allowedTransitions.get(currentState);
+        Collection<TokenStatus> nextStates = uiTransitions.get(currentState);
 
         if (currentState == TokenStatus.SUSPENDED) {
 
@@ -180,6 +249,17 @@ public class TPSSubsystem implements IAuthority, ISubsystem {
         return nextStates;
     }
 
+    /**
+     * Return the allowed next states for TPS token operations (i.e. format and enrollment).
+     *
+     * @param tokenRecord
+     * @return A non-null collection of allowed next token states.
+     */
+    public Collection<TokenStatus> getOperationNextTokenStates(TokenRecord tokenRecord) throws TPSException {
+        TokenStatus currentState = tokenRecord.getTokenStatus();
+        return operationTransitions.get(currentState);
+    }
+
     @Override
     public void startup() throws EBaseException {
         CMS.debug("TPSSubsystem: startup() begins");
diff --git a/base/tps/src/org/dogtagpki/server/tps/TPSTokendb.java b/base/tps/src/org/dogtagpki/server/tps/TPSTokendb.java
index 2e46b037c73a09719d39168041ad649919cc0819..df62a717f42467e14f653fd9062d5060d2bf4cfb 100644
--- a/base/tps/src/org/dogtagpki/server/tps/TPSTokendb.java
+++ b/base/tps/src/org/dogtagpki/server/tps/TPSTokendb.java
@@ -64,7 +64,7 @@ public class TPSTokendb {
         TokenStatus currentTokenStatus = tokenRecord.getTokenStatus();
 
         CMS.debug("TokenRecord.isTransitionAllowed(): current status: " + currentTokenStatus);
-        Collection<TokenStatus> nextStatuses = tps.getNextTokenStates(tokenRecord);
+        Collection<TokenStatus> nextStatuses = tps.getUINextTokenStates(tokenRecord);
 
         CMS.debug("TokenRecord.isTransitionAllowed(): allowed next statuses: " + nextStatuses);
         if (!nextStatuses.contains(newState)) {
diff --git a/base/tps/src/org/dogtagpki/server/tps/engine/TPSEngine.java b/base/tps/src/org/dogtagpki/server/tps/engine/TPSEngine.java
index a577a76199a51e37f8bbcd7b7477f39aad85c8e8..a5fbc3b7dfe4d55262baccb0ebfbf05077acbd1f 100644
--- a/base/tps/src/org/dogtagpki/server/tps/engine/TPSEngine.java
+++ b/base/tps/src/org/dogtagpki/server/tps/engine/TPSEngine.java
@@ -37,8 +37,6 @@ import org.dogtagpki.tps.msg.EndOpMsg.TPSStatus;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
-import com.netscape.certsrv.base.IConfigStore;
-import com.netscape.certsrv.tps.token.TokenStatus;
 
 public class TPSEngine {
 
@@ -195,8 +193,6 @@ public class TPSEngine {
     public static final String ENROLL_MODE_RECOVERY = RECOVERY_OP;
     public static final String ERNOLL_MODE_RENEWAL = RENEWAL_OP;
 
-    private static String transitionList;
-
     public void init() {
         //ToDo
     }
@@ -589,51 +585,4 @@ public class TPSEngine {
         return resp;
 
     }
-
-    //Check to see if special operations transition is allowed
-
-    public boolean isOperationTransitionAllowed(TokenStatus oldState, TokenStatus newState) throws TPSException {
-        boolean allowed = true;
-
-        if (transitionList == null) {
-
-            IConfigStore configStore = CMS.getConfigStore();
-
-            String transConfig = CFG_OPERATIONS_ALLOWED_TRANSITIONS;
-
-            CMS.debug("TPSEngine.isOperationTransistionAllowed: getting config: " + transConfig);
-            try {
-                transitionList = configStore.getString(transConfig, null);
-            } catch (EBaseException e) {
-                throw new TPSException(
-                        "TPSProcessor.isOperationTransitionAllowed: Internal error getting config value for operations transition list!",
-                        TPSStatus.STATUS_ERROR_MISCONFIGURATION);
-            }
-
-            if (transitionList == null) {
-                throw new TPSException(
-                        "TPSProcessor.isOperationTransitionAllowed: Can't find non null config value for operations transition list!",
-                        TPSStatus.STATUS_ERROR_MISCONFIGURATION);
-            }
-
-            CMS.debug("TPSEngine.isOperationTransistionAllowed: transitionList is: " + transitionList);
-
-        }
-
-        String transition = oldState.getValue() + ":" + newState.getValue();
-
-        CMS.debug("TPSEngine.isOperationTransistionAllowed: checking for transition: " + transition);
-
-        if (transitionList.indexOf(transition) == -1) {
-            CMS.debug("TPSEngine.isOperationTransistionAllowed: checking for transition: " + transition);
-            allowed = false;
-        }
-
-        CMS.debug("TPSEngine.isOperationTransistionAllowed: checking for transition: " + transition + " allowed: "
-                + allowed);
-
-        return allowed;
-
-    }
-
 }
diff --git a/base/tps/src/org/dogtagpki/server/tps/processor/TPSEnrollProcessor.java b/base/tps/src/org/dogtagpki/server/tps/processor/TPSEnrollProcessor.java
index bbdc15b86456077a5ff7c64c0b8d79b281fa098c..5f29381c586807efad8454524e58d90cd90265a5 100644
--- a/base/tps/src/org/dogtagpki/server/tps/processor/TPSEnrollProcessor.java
+++ b/base/tps/src/org/dogtagpki/server/tps/processor/TPSEnrollProcessor.java
@@ -282,7 +282,8 @@ public class TPSEnrollProcessor extends TPSProcessor {
             TokenStatus newState = TokenStatus.ACTIVE;
             // Check for transition to ACTIVE status.
 
-            if (!tps.engine.isOperationTransitionAllowed(tokenRecord.getTokenStatus(), newState)) {
+            Collection<TokenStatus> nextStates = tps.getOperationNextTokenStates(tokenRecord);
+            if (!nextStates.contains(newState)) {
                 CMS.debug(method + " token transition disallowed " +
                         tokenRecord.getTokenStatus() +
                         " to " + newState);
diff --git a/base/tps/src/org/dogtagpki/server/tps/processor/TPSProcessor.java b/base/tps/src/org/dogtagpki/server/tps/processor/TPSProcessor.java
index aa2f24260d813bac423af0e1d5015402a38bb51c..0946328f3503e994da9a000159e0d39d192e816e 100644
--- a/base/tps/src/org/dogtagpki/server/tps/processor/TPSProcessor.java
+++ b/base/tps/src/org/dogtagpki/server/tps/processor/TPSProcessor.java
@@ -33,8 +33,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import netscape.security.x509.RevocationReason;
-
 import org.dogtagpki.server.tps.TPSSession;
 import org.dogtagpki.server.tps.TPSSubsystem;
 import org.dogtagpki.server.tps.authentication.AuthUIParameter;
@@ -98,6 +96,8 @@ import com.netscape.certsrv.tps.token.TokenStatus;
 import com.netscape.cms.servlet.tks.SecureChannelProtocol;
 import com.netscape.symkey.SessionKey;
 
+import netscape.security.x509.RevocationReason;
+
 public class TPSProcessor {
 
     public static final int RESULT_NO_ERROR = 0;
@@ -2088,7 +2088,8 @@ public class TPSProcessor {
             TokenStatus newState = TokenStatus.FORMATTED;
             // Check for transition to FORMATTED status.
 
-            if (!tps.engine.isOperationTransitionAllowed(tokenRecord.getTokenStatus(), newState)) {
+            Collection<TokenStatus> nextStates = tps.getOperationNextTokenStates(tokenRecord);
+            if (!nextStates.contains(newState)) {
                 String info = " illegal transition attempted: " + tokenRecord.getTokenStatus() +
                         " to " + newState;
                 CMS.debug("TPSProcessor.format: token transition: " + info);
diff --git a/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java b/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java
index 2104f292b66d76ac166df0a4da67d2fc2091a388..e30ac4c8250c1dfbab79ff9ef49e6c7c209c45c0 100644
--- a/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java
+++ b/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java
@@ -26,6 +26,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 
 import javax.servlet.http.HttpServletRequest;
@@ -185,15 +186,23 @@ public class TokenService extends PKIService implements TokenResource {
         TokenStatus status = tokenRecord.getTokenStatus();
         TokenStatusData statusData = new TokenStatusData();
         statusData.name = status;
-        statusData.label = labels.getString(status.toString());
+        try {
+            statusData.label = labels.getString(status.toString());
+        } catch (MissingResourceException e) {
+            statusData.label = status.toString();
+        }
         tokenData.setStatus(statusData);
 
-        Collection<TokenStatus> nextStates = subsystem.getNextTokenStates(tokenRecord);
+        Collection<TokenStatus> nextStates = subsystem.getUINextTokenStates(tokenRecord);
         Collection<TokenStatusData> nextStatesData = new ArrayList<TokenStatusData>();
         for (TokenStatus nextState : nextStates) {
             TokenStatusData nextStateData = new TokenStatusData();
             nextStateData.name = nextState;
-            nextStateData.label = labels.getString(status + "." + nextState);
+            try {
+                nextStateData.label = labels.getString(status + "." + nextState);
+            } catch (MissingResourceException e) {
+                nextStateData.label = nextState.toString();
+            }
             nextStatesData.add(nextStateData);
         }
         tokenData.setNextStates(nextStatesData);
@@ -646,7 +655,7 @@ public class TokenService extends PKIService implements TokenResource {
             msg = msg + " from " + currentTokenStatus + " to " + tokenStatus;
 
             // make sure transition is allowed
-            Collection<TokenStatus> nextStatuses = subsystem.getNextTokenStates(tokenRecord);
+            Collection<TokenStatus> nextStatuses = subsystem.getUINextTokenStates(tokenRecord);
             CMS.debug("TokenService.changeTokenStatus(): allowed next statuses: " + nextStatuses);
 
             if (!nextStatuses.contains(tokenStatus)) {
-- 
2.4.11

