>From 1201f305c72131560adc4b1243122e6890c7515d Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Mon, 21 Mar 2016 21:40:02 +0100
Subject: [PATCH] Generating TEMP_LOST to UNINITIALIZED/ACTIVE transitions
 dynamically.

The TPS subsystem has been modified to generate the token state
transitions from TEMP_LOST to UNINITIALIZED or ACTIVE dynamically
depending on whether the token has certificates.

The TEMP_LOST to ACTIVE transition has been removed from the CS.cfg.

Duplicate code that loads the allowed transitions list has been
merged and moved into TPSSubsystem.

https://fedorahosted.org/pki/ticket/1808
---
 base/tps-client/doc/CS.cfg.in                      |   2 +-
 base/tps/shared/conf/CS.cfg.in                     |   2 +-
 base/tps/shared/conf/token-states.properties       |   1 +
 .../src/org/dogtagpki/server/tps/TPSSubsystem.java |  68 ++++++++++++
 .../src/org/dogtagpki/server/tps/TPSTokendb.java   |  41 ++------
 .../dogtagpki/server/tps/rest/TokenService.java    | 114 ++++-----------------
 6 files changed, 98 insertions(+), 130 deletions(-)

diff --git a/base/tps-client/doc/CS.cfg.in b/base/tps-client/doc/CS.cfg.in
index ddfab88242aa6edb0b88f6c0c53e1c2aefa0325d..f1a79032abb52660ef189aba3a91f6d4049614a2 100644
--- a/base/tps-client/doc/CS.cfg.in
+++ b/base/tps-client/doc/CS.cfg.in
@@ -1545,7 +1545,7 @@ tokendb.confirmDeleteConfigTemplate=confirmDeleteConfig.template
 log.instance.SignedAudit.selected.events=ROLE_ASSUME,CONFIG_CERT_POLICY,CONFIG_CERT_PROFILE,CONFIG_CRL_PROFILE,CONFIG_OCSP_PROFILE,CONFIG_AUTH,CONFIG_ROLE,CONFIG_ACL,CONFIG_SIGNED_AUDIT,CONFIG_ENCRYPTION,CONFIG_TRUSTED_PUBLIC_KEY,CONFIG_DRM,SELFTESTS_EXECUTION,AUDIT_LOG_DELETE,LOG_PATH_CHANGE,LOG_EXPIRATION_CHANGE,CONFIG,CONFIG_ROLE,CONFIG_TOKEN,CONFIG_PROFILE,CONFIG_AUDIT,APPLET_UPGRADE,KEY_CHANGEOVER,RENEWAL
 log.instance.SignedAudit.selectable.events=ROLE_ASSUME,CONFIG_CERT_POLICY,CONFIG_CERT_PROFILE,CONFIG_CRL_PROFILE,CONFIG_OCSP_PROFILE,CONFIG_AUTH,CONFIG_ROLE,CONFIG_ACL,CONFIG_SIGNED_AUDIT,CONFIG_ENCRYPTION,CONFIG_TRUSTED_PUBLIC_KEY,CONFIG_DRM,SELFTESTS_EXECUTION,AUDIT_LOG_DELETE,LOG_PATH_CHANGE,LOG_EXPIRATION_CHANGE,PRIVATE_KEY_ARCHIVE,PRIVATE_KEY_ARCHIVE_PROCESSED,KEY_RECOVERY_REQUEST,KEY_RECOVERY_AGENT_LOGIN,KEY_RECOVERY_PROCESSED,KEY_GEN_ASYMMETRIC,NON_PROFILE_CERT_REQUEST,CONFIG,CONFIG_ROLE,CONFIG_TOKEN,CONFIG_PROFILE,CONFIG_AUDIT,APPLET_UPGRADE,KEY_CHANGEOVER,RENEWAL
 log.instance.SignedAudit.nonselectable.events=AUDIT_LOG_STARTUP,AUDIT_LOG_SHUTDOWN,CERT_REQUEST_PROCESSED,CERT_STATUS_CHANGE_REQUEST,CERT_STATUS_CHANGE_REQUEST_PROCESSED,AUTHZ_SUCCESS,AUTHZ_FAIL,INTER_BOUNDARY,AUTH_FAIL,AUTH_SUCCESS,CERT_PROFILE_APPROVAL,PROOF_OF_POSSESSION,CRL_RETRIEVAL,CRL_VALIDATION,CMC_SIGNED_REQUEST_SIG_VERIFY,SERVER_SIDE_KEYGEN_PROCESSED,SERVER_SIDE_KEYGEN_REQUEST
-tokendb.allowedTransitions=0:1,0:2,0:3,0:6,3:2,3:4,3:6,4:1,4:2,4:3,4:6
+tokendb.allowedTransitions=0:1,0:2,0:3,0:6,3:2,3:6,4:1,4:2,4:3,4:6
 target._000=#########################################
 target._001=# entries to enable configuration of parameter sets through the TPS UI agent and admin tabs
 target._002=#
diff --git a/base/tps/shared/conf/CS.cfg.in b/base/tps/shared/conf/CS.cfg.in
index 2285300ec69bd093f9ac4d110582114af2310bf9..d7a10729dfcc0fbcd713fbf7aaa15608b8f5ab63 100644
--- a/base/tps/shared/conf/CS.cfg.in
+++ b/base/tps/shared/conf/CS.cfg.in
@@ -1966,7 +1966,7 @@ tokendb.addConfigTemplate=addConfig.template
 tokendb.addResultTemplate=addResults.template
 tokendb.agentSelectConfigTemplate=agentSelectConfig.template
 tokendb.agentViewConfigTemplate=agentViewConfig.template
-tokendb.allowedTransitions=0:1,0:2,0:3,0:6,3:2,3:4,3:6,4:1,4:2,4:3,4:6
+tokendb.allowedTransitions=0:1,0:2,0:3,0:6,3:2,3:6,4:1,4:2,4:3,4:6
 tokendb.auditAdminTemplate=auditAdmin.template
 tokendb.auditLog=[PKI_INSTANCE_PATH]/logs/tokendb-audit.log
 tokendb.baseDN=ou=Tokens,[TOKENDB_ROOT]
diff --git a/base/tps/shared/conf/token-states.properties b/base/tps/shared/conf/token-states.properties
index f67688d4b8a80a6a8314edeb734e48af48ff357b..a64d940c61ebf13dce097805f0a256b1cf991569 100644
--- a/base/tps/shared/conf/token-states.properties
+++ b/base/tps/shared/conf/token-states.properties
@@ -15,6 +15,7 @@ UNINITIALIZED.TERMINATED = This token has been terminated.
 TEMP_LOST.ACTIVE         = This temporarily lost token has been found.
 TEMP_LOST.PERM_LOST      = This temporarily lost token has become permanently lost.
 TEMP_LOST.TERMINATED     = This temporarily lost token has been terminated.
+TEMP_LOST.UNINITIALIZED  = This temporarily lost token has been found.
 ACTIVE.DAMAGED           = This token has been physically damaged.
 ACTIVE.PERM_LOST         = This token has been permanently lost.
 ACTIVE.TEMP_LOST         = This token has been temporarily lost.
diff --git a/base/tps/src/org/dogtagpki/server/tps/TPSSubsystem.java b/base/tps/src/org/dogtagpki/server/tps/TPSSubsystem.java
index e8734a9b879111319460d3bff3eb47b0672505a0..008f61333e099049462c02564b99294b110410d5 100644
--- a/base/tps/src/org/dogtagpki/server/tps/TPSSubsystem.java
+++ b/base/tps/src/org/dogtagpki/server/tps/TPSSubsystem.java
@@ -17,6 +17,11 @@
 // --- END COPYRIGHT BLOCK ---
 package org.dogtagpki.server.tps;
 
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+
 import org.dogtagpki.server.tps.authentication.AuthenticationManager;
 import org.dogtagpki.server.tps.cms.ConnectionManager;
 import org.dogtagpki.server.tps.config.AuthenticatorDatabase;
@@ -26,7 +31,9 @@ import org.dogtagpki.server.tps.config.ProfileDatabase;
 import org.dogtagpki.server.tps.config.ProfileMappingDatabase;
 import org.dogtagpki.server.tps.dbs.ActivityDatabase;
 import org.dogtagpki.server.tps.dbs.TPSCertDatabase;
+import org.dogtagpki.server.tps.dbs.TPSCertRecord;
 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.mozilla.jss.CryptoManager;
@@ -43,6 +50,7 @@ import com.netscape.certsrv.dbs.IDBSubsystem;
 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.dbs.DBSubsystem;
 
 /**
@@ -70,8 +78,10 @@ public class TPSSubsystem implements IAuthority, ISubsystem {
     public ConnectionManager connManager;
     public AuthenticationManager authManager;
     public MappingResolverManager mappingResolverManager;
+
     public TPSEngine engine;
     public TPSTokendb tdb;
+    public Map<TokenStatus, Collection<TokenStatus>> allowedTransitions = new HashMap<TokenStatus, Collection<TokenStatus>>();
 
     @Override
     public String getId() {
@@ -105,6 +115,26 @@ public class TPSSubsystem implements IAuthority, ISubsystem {
         connectorDatabase = new ConnectorDatabase();
         profileDatabase = new ProfileDatabase();
         profileMappingDatabase = new ProfileMappingDatabase();
+
+        CMS.debug("TokenSubsystem: allowed transitions:");
+
+        // initialize allowed token state transitions with empty containers
+        for (TokenStatus state : TokenStatus.values()) {
+            allowedTransitions.put(state, new LinkedHashSet<TokenStatus>());
+        }
+
+        // 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);
+        }
+
         tdb = new TPSTokendb(this);
 
         engine = new TPSEngine();
@@ -112,6 +142,44 @@ public class TPSSubsystem implements IAuthority, ISubsystem {
 
     }
 
+    /**
+     * Return the allowed next states for a given token based on TPS configuration.
+     *
+     * If the current state is TEMP_LOST, token will be allowed transition to either
+     * UNINITIALIZED 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 {
+
+        TokenStatus currentState = tokenRecord.getTokenStatus();
+        Collection<TokenStatus> nextStates = allowedTransitions.get(currentState);
+
+        if (currentState == TokenStatus.TEMP_LOST) {
+
+            Collection<TokenStatus> ns = new LinkedHashSet<TokenStatus>();
+
+            // check token certificates
+            Collection<TPSCertRecord> certRecords = tdb.tdbGetCertRecordsByCUID(tokenRecord.getId());
+
+            // if token has no certificates, allow token to become uninitialized again
+            if (certRecords.isEmpty()) {
+                ns.add(TokenStatus.UNINITIALIZED);
+
+            } else { // otherwise, allow token to become active again
+                ns.add(TokenStatus.ACTIVE);
+            }
+
+            // add the original allowed next states
+            ns.addAll(nextStates);
+
+            return ns;
+        }
+
+        return nextStates;
+    }
+
     @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 15e85fb32e0b515e7fd47f3fc6b77643c8abc181..51f4966526d23faaca6d1308f6b6261b4abac68f 100644
--- a/base/tps/src/org/dogtagpki/server/tps/TPSTokendb.java
+++ b/base/tps/src/org/dogtagpki/server/tps/TPSTokendb.java
@@ -22,7 +22,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 
@@ -31,7 +30,6 @@ import org.dogtagpki.server.tps.cms.CARevokeCertResponse;
 import org.dogtagpki.server.tps.dbs.ActivityDatabase;
 import org.dogtagpki.server.tps.dbs.TPSCertRecord;
 import org.dogtagpki.server.tps.dbs.TokenRecord;
-import org.dogtagpki.server.tps.engine.TPSEngine;
 import org.dogtagpki.server.tps.main.ExternalRegAttrs;
 import org.dogtagpki.server.tps.main.ExternalRegCertToRecover;
 import org.dogtagpki.tps.main.TPSException;
@@ -48,8 +46,8 @@ import netscape.security.x509.RevocationReason;
  * TPSTokendb class offers a collection of tokendb management convenience routines
  */
 public class TPSTokendb {
+
     private TPSSubsystem tps;
-    private Map<TokenStatus, Collection<TokenStatus>> allowedTransitions = new HashMap<TokenStatus, Collection<TokenStatus>>();
 
     public TPSTokendb(TPSSubsystem tps) throws EBaseException {
         if (tps == null) {
@@ -58,44 +56,17 @@ public class TPSTokendb {
             throw new EBaseException(msg);
         }
         this.tps = tps;
-        try {
-            initAllowedTransitions();
-        } catch (Exception e) {
-            CMS.debug("TPSTokendb: initAllowedTransitions() failed:" + e);
-            throw new EBaseException(e.toString());
-        }
     }
 
-    void initAllowedTransitions()
-            throws Exception {
-        CMS.debug("TPSTokendb.initAllowedTransitions()");
-        IConfigStore configStore = CMS.getConfigStore();
-
-        // load allowed token state transitions
-        CMS.debug("TPSTokendbs: allowed transitions:");
-
-        for (String transition : configStore.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("TPSTokendb:  - " + fromState + " to " + toState);
-
-            Collection<TokenStatus> nextStates = allowedTransitions.get(fromState);
-            if (nextStates == null) {
-                nextStates = new HashSet<TokenStatus>();
-                allowedTransitions.put(fromState, nextStates);
-            }
-            nextStates.add(toState);
-        }
-    }
-
-    public boolean isTransitionAllowed(TokenRecord tokenRecord, TokenStatus newState) {
+    public boolean isTransitionAllowed(TokenRecord tokenRecord, TokenStatus newState) throws Exception {
         boolean result = false;
         TokenStatus currentTokenStatus = tokenRecord.getTokenStatus();
+
         CMS.debug("TokenRecord.isTransitionAllowed(): current status: " + currentTokenStatus);
-        Collection<TokenStatus> nextStatuses = allowedTransitions.get(currentTokenStatus);
+        Collection<TokenStatus> nextStatuses = tps.getNextTokenStates(tokenRecord);
+
         CMS.debug("TokenRecord.isTransitionAllowed(): allowed next statuses: " + nextStatuses);
-        if (nextStatuses == null || !nextStatuses.contains(newState)) {
+        if (!nextStatuses.contains(newState)) {
             CMS.debug("TokenRecord.isTransitionAllowed(): next status not allowed: " + newState);
 
             result = false;
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 a0ca7add3e2dc2d2a6a215c6bb436459f69990f3..958843e5bb9d3b7cc750d1e2ee4a1184d4ce7525 100644
--- a/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java
+++ b/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java
@@ -24,7 +24,6 @@ import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.ResourceBundle;
@@ -39,15 +38,12 @@ import javax.ws.rs.core.UriInfo;
 import org.apache.commons.lang.StringUtils;
 import org.dogtagpki.server.tps.TPSSubsystem;
 import org.dogtagpki.server.tps.dbs.ActivityDatabase;
-import org.dogtagpki.server.tps.dbs.TPSCertRecord;
 import org.dogtagpki.server.tps.dbs.TokenDatabase;
 import org.dogtagpki.server.tps.dbs.TokenRecord;
-import org.dogtagpki.server.tps.engine.TPSEngine;
 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.IConfigStore;
 import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.dbs.EDBException;
 import com.netscape.certsrv.ldap.LDAPExceptionConverter;
@@ -77,58 +73,8 @@ public class TokenService extends PKIService implements TokenResource {
     @Context
     private HttpServletRequest servletRequest;
 
-    public Map<TokenStatus, Collection<TokenStatus>> transitions = new HashMap<TokenStatus, Collection<TokenStatus>>();
-
     public TokenService() throws Exception {
         CMS.debug("TokenService.<init>()");
-        IConfigStore configStore = CMS.getConfigStore();
-
-        // load allowed token state transitions
-        CMS.debug("TokenService: allowed transitions:");
-
-        for (String transition : configStore.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("TokenService:  - " + fromState + " to " + toState);
-
-            Collection<TokenStatus> nextStates = transitions.get(fromState);
-            if (nextStates == null) {
-                nextStates = new HashSet<TokenStatus>();
-                transitions.put(fromState, nextStates);
-            }
-            nextStates.add(toState);
-        }
-
-    }
-
-    public TokenStatus getTokenStatus(TokenRecord tokenRecord) {
-        String status = tokenRecord.getStatus();
-
-        if ("uninitialized".equals(status)) {
-            return TokenStatus.UNINITIALIZED;
-
-        } else if ("active".equals(status)) {
-            return TokenStatus.ACTIVE;
-
-        } else if ("lost".equals(status)) {
-            String reason = tokenRecord.getReason();
-
-            if ("keyCompromise".equals(reason)) {
-                return TokenStatus.PERM_LOST;
-
-            } else if ("destroyed".equals(reason)) {
-                return TokenStatus.DAMAGED;
-
-            } else if ("onHold".equals(reason)) {
-                return TokenStatus.TEMP_LOST;
-            }
-
-        } else if ("terminated".equals(status)) {
-            return TokenStatus.TERMINATED;
-        }
-
-        return TokenStatus.PERM_LOST;
     }
 
     public void setTokenStatus(TokenRecord tokenRecord, TokenStatus tokenState, String ipAddress, String remoteUser)
@@ -142,33 +88,14 @@ public class TokenService extends PKIService implements TokenResource {
             break;
 
         case ACTIVE:
-            String origStatus = tokenRecord.getStatus();
-            String origReason = tokenRecord.getReason();
-
-            if (origStatus.equalsIgnoreCase("lost") &&
-                    origReason.equalsIgnoreCase("onHold")) {
-
-                Collection<TPSCertRecord> certRecords = tps.tdb.tdbGetCertRecordsByCUID(tokenRecord.getId());
-                if (certRecords.isEmpty()) { // token was uninitialized
-                    // restore to uninitialized state
-                    tokenRecord.setStatus("uninitialized");
-                    tokenRecord.setReason(null);
-
-                } else { // token was active
-                    // unrevoke certs
-                    tps.tdb.unRevokeCertsByCUID(tokenRecord.getId(), ipAddress, remoteUser);
-
-                    // restore to active state
-                    tokenRecord.setStatus("active");
-                    tokenRecord.setReason(null);
-                }
-
-            } else {
-                // switch to active state
-                tokenRecord.setStatus("active");
-                tokenRecord.setReason(null);
+            if (tokenRecord.getTokenStatus() == TokenStatus.TEMP_LOST) {
+                // unrevoke certs
+                tps.tdb.unRevokeCertsByCUID(tokenRecord.getId(), ipAddress, remoteUser);
             }
 
+            tokenRecord.setStatus("active");
+            tokenRecord.setReason(null);
+
             break;
 
         case PERM_LOST:
@@ -221,6 +148,8 @@ public class TokenService extends PKIService implements TokenResource {
 
     public TokenData createTokenData(TokenRecord tokenRecord) throws Exception {
 
+        TPSSubsystem subsystem = (TPSSubsystem) CMS.getSubsystem(TPSSubsystem.ID);
+
         ResourceBundle labels = getResourceBundle("token-states");
 
         TokenData tokenData = new TokenData();
@@ -229,23 +158,21 @@ public class TokenService extends PKIService implements TokenResource {
         tokenData.setUserID(tokenRecord.getUserID());
         tokenData.setType(tokenRecord.getType());
 
-        TokenStatus status = getTokenStatus(tokenRecord);
+        TokenStatus status = tokenRecord.getTokenStatus();
         TokenStatusData statusData = new TokenStatusData();
         statusData.name = status;
         statusData.label = labels.getString(status.toString());
         tokenData.setStatus(statusData);
 
-        Collection<TokenStatus> nextStates = transitions.get(status);
-        if (nextStates != null) {
-            Collection<TokenStatusData> nextStatesData = new ArrayList<TokenStatusData>();
-            for (TokenStatus nextState : nextStates) {
-                TokenStatusData nextStateData = new TokenStatusData();
-                nextStateData.name = nextState;
-                nextStateData.label = labels.getString(status + "." + nextState);
-                nextStatesData.add(nextStateData);
-            }
-            tokenData.setNextStates(nextStatesData);
+        Collection<TokenStatus> nextStates = subsystem.getNextTokenStates(tokenRecord);
+        Collection<TokenStatusData> nextStatesData = new ArrayList<TokenStatusData>();
+        for (TokenStatus nextState : nextStates) {
+            TokenStatusData nextStateData = new TokenStatusData();
+            nextStateData.name = nextState;
+            nextStateData.label = labels.getString(status + "." + nextState);
+            nextStatesData.add(nextStateData);
         }
+        tokenData.setNextStates(nextStatesData);
 
         tokenData.setAppletID(tokenRecord.getAppletID());
         tokenData.setKeyInfo(tokenRecord.getKeyInfo());
@@ -588,7 +515,7 @@ public class TokenService extends PKIService implements TokenResource {
             TokenDatabase database = subsystem.getTokenDatabase();
 
             tokenRecord = database.getRecord(tokenID);
-            TokenStatus currentTokenStatus = getTokenStatus(tokenRecord);
+            TokenStatus currentTokenStatus = tokenRecord.getTokenStatus();
             CMS.debug("TokenService.changeTokenStatus(): current status: " + currentTokenStatus);
 
             if (currentTokenStatus == tokenStatus) {
@@ -601,9 +528,10 @@ public class TokenService extends PKIService implements TokenResource {
             msg = msg + " from " + currentTokenStatus + " to " + tokenStatus;
 
             // make sure transition is allowed
-            Collection<TokenStatus> nextStatuses = transitions.get(currentTokenStatus);
+            Collection<TokenStatus> nextStatuses = subsystem.getNextTokenStates(tokenRecord);
             CMS.debug("TokenService.changeTokenStatus(): allowed next statuses: " + nextStatuses);
-            if (nextStatuses == null || !nextStatuses.contains(tokenStatus)) {
+
+            if (!nextStatuses.contains(tokenStatus)) {
                 CMS.debug("TokenService.changeTokenStatus(): next status not allowed: " + tokenStatus);
                 throw new BadRequestException("Invalid token status transition");
             }
-- 
2.4.3

