Cred delete fixes
[aaf/authz.git] / auth / auth-cass / src / main / java / org / onap / aaf / auth / dao / hl / Question.java
index 152412a..3abad1a 100644 (file)
@@ -27,7 +27,6 @@ import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
@@ -62,6 +61,7 @@ import org.onap.aaf.auth.dao.cass.PermDAO;
 import org.onap.aaf.auth.dao.cass.RoleDAO;
 import org.onap.aaf.auth.dao.cass.Status;
 import org.onap.aaf.auth.dao.cass.UserRoleDAO;
+import org.onap.aaf.auth.env.AuthzEnv;
 import org.onap.aaf.auth.env.AuthzTrans;
 import org.onap.aaf.auth.env.AuthzTrans.REQD_TYPE;
 import org.onap.aaf.auth.env.AuthzTransFilter;
@@ -89,7 +89,7 @@ import com.datastax.driver.core.Cluster;
 public class Question {
 
     // DON'T CHANGE FROM lower Case!!!
-    public static enum Type {
+    public enum Type {
         ns, role, perm, cred
     };
 
@@ -101,7 +101,7 @@ public class Question {
 
     static final String ASTERIX = "*";
 
-    public static enum Access {
+    public enum Access {
         read, write, create
     };
 
@@ -130,20 +130,66 @@ public class Question {
     private static Slot transIDSlot = null;
 
 
-    public final HistoryDAO historyDAO;
-    public final CachedNSDAO nsDAO;
-    public final CachedRoleDAO roleDAO;
-    public final CachedPermDAO permDAO;
-    public final CachedUserRoleDAO userRoleDAO;
-    public final CachedCredDAO credDAO;
-    public final CachedCertDAO certDAO;
-    public final DelegateDAO delegateDAO;
-    public final FutureDAO futureDAO;
-    public final ApprovalDAO approvalDAO;
-    private final CacheInfoDAO cacheInfoDAO;
+    private final HistoryDAO historyDAO;
+    public HistoryDAO historyDAO() {
+       return historyDAO;
+    }
+    
+    private final CachedNSDAO nsDAO;
+    public CachedNSDAO nsDAO() {
+       return nsDAO;
+    }
+    
+    private final CachedRoleDAO roleDAO;
+    public CachedRoleDAO roleDAO() {
+       return roleDAO;
+    }
+    
+    private final CachedPermDAO permDAO;
+    public CachedPermDAO permDAO() {
+       return permDAO;
+    }
+    
+    private final CachedUserRoleDAO userRoleDAO;
+    public CachedUserRoleDAO userRoleDAO() {
+       return userRoleDAO;
+    }
+    
+    private final CachedCredDAO credDAO;
+    public CachedCredDAO credDAO() {
+       return credDAO;
+    }
+    
+    private final CachedCertDAO certDAO;
+    public CachedCertDAO certDAO() {
+       return certDAO;
+    }
+    
+    private final DelegateDAO delegateDAO;
+    public DelegateDAO delegateDAO() {
+       return delegateDAO;
+    }
+    
+    private final FutureDAO futureDAO;
+    public FutureDAO futureDAO() {
+       return futureDAO;
+    }
+    
+    private final ApprovalDAO approvalDAO;
+    public ApprovalDAO approvalDAO() {
+       return approvalDAO;
+    }
+    
     public final LocateDAO locateDAO;
+    public LocateDAO locateDAO() {
+       return locateDAO;
+    }
+    
+    private final CacheInfoDAO cacheInfoDAO;
+       private final int cldays;
+       private final boolean alwaysSpecial;
 
-    public Question(AuthzTrans trans, Cluster cluster, String keyspace, boolean startClean) throws APIException, IOException {
+    public Question(AuthzTrans trans, Cluster cluster, String keyspace) throws APIException, IOException {
         PERMS = trans.slot("USER_PERMS");
         trans.init().log("Instantiating DAOs");
         long expiresIn = Long.parseLong(trans.getProperty(Config.AAF_USER_EXPIRES, Config.AAF_USER_EXPIRES_DEF));
@@ -164,26 +210,28 @@ public class Question {
         delegateDAO = new DelegateDAO(trans, historyDAO);
         approvalDAO = new ApprovalDAO(trans, historyDAO);
 
-        // Only want to aggressively cleanse User related Caches... The others,
-        // just normal refresh
-        if(startClean) {
-            CachedDAO.startCleansing(trans.env(), credDAO, userRoleDAO);
-            CachedDAO.startRefresh(trans.env(), cacheInfoDAO);
-        }
-        // Set a Timer to Check Caches to send messages for Caching changes
-        
-        if(specialLogSlot==null) {
+        if (specialLogSlot==null) {
             specialLogSlot = trans.slot(AuthzTransFilter.SPECIAL_LOG_SLOT);
         }
         
-        if(transIDSlot==null) {
+        if (transIDSlot==null) {
             transIDSlot = trans.slot(AuthzTransFilter.TRANS_ID_SLOT);
         }
         
         AbsCassDAO.primePSIs(trans);
+        
+        cldays = Integer.parseInt(trans.getProperty(Config.AAF_CRED_WARN_DAYS, Config.AAF_CRED_WARN_DAYS_DFT));
+        
+        alwaysSpecial = Boolean.parseBoolean(trans.getProperty("aaf_always_special", Boolean.FALSE.toString()));
     }
 
-
+    public void startTimers(AuthzEnv env) {
+        // Only want to aggressively cleanse User related Caches... The others,
+        // just normal refresh
+        CachedDAO.startCleansing(env, credDAO, userRoleDAO);
+        CachedDAO.startRefresh(env, cacheInfoDAO);
+    }
+    
     public void close(AuthzTrans trans) {
         historyDAO.close(trans);
         cacheInfoDAO.close(trans);
@@ -226,25 +274,25 @@ public class Question {
     public Result<List<PermDAO.Data>> getPermsByUserFromRolesFilter(AuthzTrans trans, String user, String forUser) {
         PermLookup plUser = PermLookup.get(trans, this, user);
         Result<Set<String>> plPermNames = plUser.getPermNames();
-        if(plPermNames.notOK()) {
+        if (plPermNames.notOK()) {
             return Result.err(plPermNames);
         }
         
         Set<String> nss;
-        if(forUser.equals(user)) {
+        if (forUser.equals(user)) {
             nss = null;
         } else {
             // Setup a TreeSet to check on Namespaces to 
             nss = new TreeSet<>();
             PermLookup fUser = PermLookup.get(trans, this, forUser);
             Result<Set<String>> forUpn = fUser.getPermNames();
-            if(forUpn.notOK()) {
+            if (forUpn.notOK()) {
                 return Result.err(forUpn);
             }
             
-            for(String pn : forUpn.value) {
+            for (String pn : forUpn.value) {
                 Result<String[]> decoded = PermDAO.Data.decodeToArray(trans, this, pn);
-                if(decoded.isOKhasData()) {
+                if (decoded.isOKhasData()) {
                     nss.add(decoded.value[0]);
                 } else {
                     trans.error().log(pn,", derived from a Role, is invalid:",decoded.errorString());
@@ -255,11 +303,11 @@ public class Question {
         List<PermDAO.Data> rlpUser = new ArrayList<>();
         Result<PermDAO.Data> rpdd;
         PermDAO.Data pdd;
-        for(String pn : plPermNames.value) {
+        for (String pn : plPermNames.value) {
             rpdd = PermDAO.Data.decode(trans, this, pn);
-            if(rpdd.isOKhasData()) {
+            if (rpdd.isOKhasData()) {
                 pdd=rpdd.value;
-                if(nss==null || nss.contains(pdd.ns)) {
+                if (nss==null || nss.contains(pdd.ns)) {
                     rlpUser.add(pdd);
                 }
             } else {
@@ -277,13 +325,22 @@ public class Question {
         return permDAO.readByType(trans, nss.value.ns, nss.value.name);
     }
 
-    public Result<List<PermDAO.Data>> getPermsByName(AuthzTrans trans,
-            String type, String instance, String action) {
-        Result<NsSplit> nss = deriveNsSplit(trans, type);
-        if (nss.notOK()) {
-            return Result.err(nss);
-        }
-        return permDAO.read(trans, nss.value.ns, nss.value.name, instance,action);
+    public Result<List<PermDAO.Data>> getPermsByName(AuthzTrans trans, String type, String instance, String action) {
+       if(type.indexOf('@') >= 0) {
+               int colon = type.indexOf(':');
+               if(colon>=0) {
+                       return permDAO.read(trans, type.substring(0, colon),type.substring(colon+1), instance,action);
+               } else {
+                       return Result.err(Result.ERR_BadData, "%s is malformed",type);
+               }
+       } else {
+               Result<NsSplit> nss = deriveNsSplit(trans, type);
+               if (nss.notOK()) {
+                   return Result.err(nss);
+               }
+               
+               return permDAO.read(trans, nss.value.ns, nss.value.name, instance,action);
+       }
     }
 
     public Result<List<PermDAO.Data>> getPermsByRole(AuthzTrans trans, String role, boolean lookup) {
@@ -314,7 +371,7 @@ public class Question {
                 return Result.err(pr);
             }
 
-            if(lookup) {
+            if (lookup) {
                 Result<List<PermDAO.Data>> rlpd = permDAO.read(trans, pr.value);
                 if (rlpd.isOKhasData()) {
                     for (PermDAO.Data pData : rlpd.value) {
@@ -329,8 +386,14 @@ public class Question {
         return Result.ok(perms);
     }
 
-    public Result<List<RoleDAO.Data>> getRolesByName(AuthzTrans trans,
-            String role) {
+    public Result<List<RoleDAO.Data>> getRolesByName(AuthzTrans trans, String role) {
+       if(role.startsWith(trans.user()) ) {
+               if(role.endsWith(":user")) {
+                       return roleDAO.read(trans,trans.user(), "user");
+               } else {
+                       return Result.err(Result.ERR_BadData,"%s is a badly formatted role",role);
+               }
+       }
         Result<NsSplit> nss = deriveNsSplit(trans, role);
         if (nss.notOK()) {
             return Result.err(nss);
@@ -367,12 +430,7 @@ public class Question {
         if (r.isOKhasData()) {
             return Result.ok(r.value.get(0));
         } else {
-            int dot;
-            if(child==null) {
-                return Result.err(Status.ERR_NsNotFound, "No Namespace");
-            } else {
-                dot = child.lastIndexOf('.');
-            }
+            int dot = child.lastIndexOf('.');
             if (dot < 0) {
                 return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", child);
             } else {
@@ -384,16 +442,30 @@ public class Question {
     public Result<NsDAO.Data> deriveFirstNsForType(AuthzTrans trans, String str, NsType type) {
         NsDAO.Data nsd;
 
-        for(String lookup = str;!".".equals(lookup) && lookup!=null;) {
+        for (String lookup = str;!".".equals(lookup) && lookup!=null;) {
             Result<List<NsDAO.Data>> rld = nsDAO.read(trans, lookup);
-            if(rld.isOKhasData()) {
+            if (rld.isOKhasData()) {
                 nsd=rld.value.get(0);
                 lookup = nsd.parent;
-                if(type.type == nsd.type) {
+                if (type.type == nsd.type) {
                     return Result.ok(nsd);
+                } else {
+                    int dot = str.lastIndexOf('.');
+                    
+                    if (dot < 0) {
+                        return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", str);
+                    } else {
+                        return deriveFirstNsForType(trans, str.substring(0, dot),type);
+                    }
                 }
             } else {
-                return Result.err(Status.ERR_NsNotFound,"There is no valid Company Namespace for %s",str);
+                int dot = str.lastIndexOf('.');
+                
+                if (dot < 0) {
+                    return Result.err(Status.ERR_NsNotFound,"There is no valid Company Namespace for %s",str);
+                } else {
+                    return deriveFirstNsForType(trans, str.substring(0, dot),type);
+                }
             }
         }
         return Result.err(Status.ERR_NotFound, str + " does not contain type " + type.name());
@@ -455,13 +527,13 @@ public class Question {
     public Result<NsDAO.Data> validNSOfDomain(AuthzTrans trans, String id) {
         // Take domain, reverse order, and check on NS
         String ns;
-        if(id.indexOf('@')<0) { // it's already an ns, not an ID
+        if (id.indexOf('@')<0) { // it's already an ns, not an ID
             ns = id;
         } else {
             ns = domain2ns(id);
         }
         if (ns.length() > 0) {
-            if(!trans.org().getDomain().equals(ns)) { 
+            if (!trans.org().getDomain().equals(ns)) { 
                 Result<List<NsDAO.Data>> rlnsd = nsDAO.read(trans, ns);
                 if (rlnsd.isOKhasData()) {
                     return Result.ok(rlnsd.value.get(0));
@@ -490,7 +562,7 @@ public class Question {
         Result<NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd, ":"    + ndd.name + ":ns", access.name());
         if (rv.isOK()) {
             return rv;
-        } else if(rv.status==Result.ERR_Backend) {
+        } else if (rv.status==Result.ERR_Backend) {
             return Result.err(rv);
         } else {
             return Result.err(Status.ERR_Denied, "[%s] may not %s in NS [%s]",
@@ -499,6 +571,9 @@ public class Question {
     }
 
     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user, RoleDAO.Data rdd, Access access) {
+       if(trans.user().equals(rdd.ns)) {
+               return Result.ok((NsDAO.Data)null);
+       }
         Result<NsDAO.Data> rnsd = deriveNs(trans, rdd.ns);
         if (rnsd.isOK()) {
             return mayUser(trans, user, rnsd.value, rdd, access);
@@ -532,7 +607,7 @@ public class Question {
                 + rdd.ns + roleInst, access.name());
         if (rnsd.isOK()) {
             return rnsd;
-        } else if(rnsd.status==Result.ERR_Backend) {
+        } else if (rnsd.status==Result.ERR_Backend) {
             return Result.err(rnsd);
         }
 
@@ -543,7 +618,7 @@ public class Question {
                 ":" + rdd.ns + ":ns", access.name());
         if (rv.isOK()) {
             return rv;
-        } else if(rnsd.status==Result.ERR_Backend) {
+        } else if (rnsd.status==Result.ERR_Backend) {
             return Result.err(rnsd);
         } else {
             return Result.err(Status.ERR_Denied, "[%s] may not %s Role [%s]",
@@ -553,6 +628,17 @@ public class Question {
     }
 
     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,PermDAO.Data pdd, Access access) {
+       if(pdd.ns.indexOf('@')>-1) {
+               if(user.equals(pdd.ns) || isGranted(trans,user,Define.ROOT_NS(),"access",pdd.instance,READ)) {
+                       NsDAO.Data ndd = new NsDAO.Data();
+                       ndd.name = user;
+                       ndd.type = NsDAO.USER;
+                       ndd.parent = "";
+                       return Result.ok(ndd);
+               } else {
+                       return Result.err(Result.ERR_Security,"Only a User may modify User");
+               }
+       }
         Result<NsDAO.Data> rnsd = deriveNs(trans, pdd.ns);
         if (rnsd.isOK()) {
             return mayUser(trans, user, rnsd.value, pdd, access);
@@ -582,7 +668,7 @@ public class Question {
         Result<NsDAO.Data> rnsd = mayUserVirtueOfNS(trans, user, ndd, ":" + pdd.ns + permInst, access.name());
         if (rnsd.isOK()) {
             return rnsd;
-        } else if(rnsd.status==Result.ERR_Backend) {
+        } else if (rnsd.status==Result.ERR_Backend) {
             return Result.err(rnsd);
         }
 
@@ -603,7 +689,7 @@ public class Question {
     public Result<Void> mayUser(AuthzTrans trans, DelegateDAO.Data dd, Access access) {
         try {
             Result<NsDAO.Data> rnsd = deriveNs(trans, domain2ns(trans.user()));
-            if(rnsd.isOKhasData() && mayUserVirtueOfNS(trans,trans.user(),rnsd.value, ":"    + rnsd.value.name + ":ns", access.name()).isOK()) {
+            if (rnsd.isOKhasData() && mayUserVirtueOfNS(trans,trans.user(),rnsd.value, ":"    + rnsd.value.name + ":ns", access.name()).isOK()) {
                 return Result.ok();
             }
             boolean isUser = trans.user().equals(dd.user);
@@ -663,7 +749,7 @@ public class Question {
         Result<List<UserRoleDAO.Data>> rurd;
         if ((rurd = userRoleDAO.readUserInRole(trans, user, ns+DOT_ADMIN)).isOKhasData()) {
             return Result.ok(nsd);
-        } else if(rurd.status==Result.ERR_Backend) {
+        } else if (rurd.status==Result.ERR_Backend) {
             return Result.err(rurd);
         }
         
@@ -679,12 +765,12 @@ public class Question {
             Result<NsDAO.Data> rnsd = deriveNs(trans, ns.substring(0, dot));
             if (rnsd.isOK()) {
                 rnsd = mayUserVirtueOfNS(trans, user, rnsd.value, ns_and_type,access);
-            } else if(rnsd.status==Result.ERR_Backend) {
+            } else if (rnsd.status==Result.ERR_Backend) {
                 return Result.err(rnsd);
             }
             if (rnsd.isOK()) {
                 return Result.ok(nsd);
-            } else if(rnsd.status==Result.ERR_Backend) {
+            } else if (rnsd.status==Result.ERR_Backend) {
                 return Result.err(rnsd);
             }
         }
@@ -712,7 +798,7 @@ public class Question {
                 if (ns.equals(pd.ns)) {
                     if (type.equals(pd.type)) {
                         if (PermEval.evalInstance(pd.instance, instance)) {
-                            if(PermEval.evalAction(pd.action, action)) { // don't return action here, might miss other action 
+                            if (PermEval.evalAction(pd.action, action)) { // don't return action here, might miss other action 
                                 return true;
                             }
                         }
@@ -733,31 +819,25 @@ public class Question {
         }
 
         Result<Date> rv = null;
-        if(result.isOK()) {
+        if (result.isOK()) {
             if (result.isEmpty()) {
                 rv = Result.err(Status.ERR_UserNotFound, user);
                 if (willSpecialLog(trans,user)) {
                     trans.audit().log("Special DEBUG:", user, " does not exist in DB");
                 }
             } else {
-                Date now = new Date();//long now = System.currentTimeMillis();
+                Date now = new Date();
                 // Bug noticed 6/22. Sorting on the result can cause Concurrency Issues.     
                 List<CredDAO.Data> cddl;
-                if(result.value.size() > 1) {
+                if (result.value.size() > 1) {
                     cddl = new ArrayList<>(result.value.size());
-                    for(CredDAO.Data old : result.value) {
-                        if(old.type==CredDAO.BASIC_AUTH || old.type==CredDAO.BASIC_AUTH_SHA256) {
+                    for (CredDAO.Data old : result.value) {
+                        if (old.type==CredDAO.BASIC_AUTH || old.type==CredDAO.BASIC_AUTH_SHA256) {
                             cddl.add(old);
                         }
                     }
-                    if(cddl.size()>1) {
-                        Collections.sort(cddl,new Comparator<CredDAO.Data>() {
-                            @Override
-                            public int compare(org.onap.aaf.auth.dao.cass.CredDAO.Data a,
-                                               org.onap.aaf.auth.dao.cass.CredDAO.Data b) {
-                                return b.expires.compareTo(a.expires);
-                            }
-                        });
+                    if (cddl.size()>1) {
+                       Collections.sort(cddl, (a, b) -> b.expires.compareTo(a.expires));
                     }
                 } else {
                     cddl = result.value;
@@ -766,7 +846,7 @@ public class Question {
                 Date expired = null;
                 StringBuilder debug = willSpecialLog(trans,user)?new StringBuilder():null;
                 for (CredDAO.Data cdd : cddl) {
-                    if(!cdd.id.equals(user)) {
+                    if (!cdd.id.equals(user)) {
                         trans.error().log("doesUserCredMatch DB call does not match for user: " + user);
                     }
                     if (cdd.expires.after(now)) {
@@ -776,8 +856,9 @@ public class Question {
                             switch(cdd.type) {
                                 case CredDAO.BASIC_AUTH:
                                     byte[] md5=Hash.hashMD5(cred);
-                                    if(Hash.compareTo(md5,dbcred)==0) {
-                                        checkLessThanDays(trans,7,now,cdd);
+                                    if (Hash.compareTo(md5,dbcred)==0) {
+                                        checkLessThanDays(trans,cldays,now,cdd);
+                                        trans.setTag(cdd.tag);
                                         return Result.ok(cdd.expires);
                                     } else if (debug!=null) {
                                         load(debug, cdd);
@@ -789,8 +870,9 @@ public class Question {
                                     bb.put(cred);
                                     byte[] hash = Hash.hashSHA256(bb.array());
     
-                                    if(Hash.compareTo(hash,dbcred)==0) {
-                                        checkLessThanDays(trans,7,now,cdd);
+                                    if (Hash.compareTo(hash,dbcred)==0) {
+                                        checkLessThanDays(trans,cldays,now,cdd);
+                                        trans.setTag(cdd.tag);
                                         return Result.ok(cdd.expires);
                                     } else if (debug!=null) {
                                         load(debug, cdd);
@@ -803,36 +885,43 @@ public class Question {
                             trans.error().log(e);
                         }
                     } else {
-                        if(expired==null || expired.before(cdd.expires)) {
+                        if (expired==null || expired.before(cdd.expires)) {
                             expired = cdd.expires;
+                            trans.setTag(cdd.tag);
                         }
                     }
                 } // end for each
-                if(debug==null) {
-                    trans.audit().printf("No cred matches ip=%s, user=%s\n",trans.ip(),user);
-                } else {
-                    trans.audit().printf("No cred matches ip=%s, user=%s %s\n",trans.ip(),user,debug.toString());
-                }
-                if(expired!=null) {
+                
+                if (expired!=null) {
                     // Note: this is only returned if there are no good Credentials
                     rv = Result.err(Status.ERR_Security,
-                            "Credentials %s from %s expired %s",trans.user(), trans.ip(), Chrono.dateTime(expired));
+                            "Credentials expired %s",Chrono.utcStamp(expired));
+                } else {
+                       if (debug==null && alwaysSpecial) {
+                               debug = new StringBuilder();
+                       }
+                       if (debug!=null) {
+                               debug.append(trans.env().encryptor().encrypt(new String(cred)));
+                               rv = Result.err(Status.ERR_Security,String.format("invalid password - %s",debug.toString()));
+                       }
                 }
             }
         } else {
             return Result.err(result);
         }
-        return rv == null ? Result.create((Date) null, Status.ERR_Security, "Wrong credential") : rv;
+        return rv == null ? Result.err(Status.ERR_Security, "Wrong credential") : rv;
     }
 
 
     private void load(StringBuilder debug, Data cdd) {
-        debug.append("DB Entry: user=");
+        debug.append("\nDB Entry: user=");
         debug.append(cdd.id);
         debug.append(",type=");
         debug.append(cdd.type);
         debug.append(",expires=");
         debug.append(Chrono.dateTime(cdd.expires));
+        debug.append(",tag=");
+        debug.append(cdd.tag);
         debug.append('\n');
     }
 
@@ -840,16 +929,17 @@ public class Question {
     private void checkLessThanDays(AuthzTrans trans, int days, Date now, Data cdd) {
         long close = now.getTime() + (days * 86400000);
         long cexp=cdd.expires.getTime();
-        if(cexp<close) {
+        if (cexp<close) {
             int daysLeft = days-(int)((close-cexp)/86400000);
-            trans.audit().printf("user=%s,ip=%s,expires=%s,days=%d,msg=\"Password expires in less than %d day%s\"",
-                cdd.id,trans.ip(),Chrono.dateOnlyStamp(cdd.expires),daysLeft, daysLeft,daysLeft==1?"":"s");
+            trans.audit().printf("user=%s,ip=%s,expires=%s,days=%d,tag=%s,msg=\"Password expires in less than %d day%s\"",
+                cdd.id,trans.ip(),Chrono.dateOnlyStamp(cdd.expires),daysLeft, cdd.tag, 
+                daysLeft,daysLeft==1?"":"s");
         }
     }
 
 
     public Result<CredDAO.Data> userCredSetup(AuthzTrans trans, CredDAO.Data cred) {
-        if(cred.type==CredDAO.RAW) {
+        if (cred.type==CredDAO.RAW) {
             TimeTaken tt = trans.start("Hash Cred", Env.SUB);
             try {
                 cred.type = CredDAO.BASIC_AUTH_SHA256;
@@ -866,6 +956,9 @@ public class Question {
                 tt.done();
             }
             
+        } else if (cred.type==CredDAO.FQI) {
+               cred.cred = null;
+               return Result.ok(cred);
         }
         return Result.err(Status.ERR_Security,"invalid/unreadable credential");
     }
@@ -881,6 +974,7 @@ public class Question {
                         return Result.ok(Hash.compareTo(orig.cred.array(),Hash.hashSHA256(bb.array()))==0);
                     case CredDAO.BASIC_AUTH:
                         return Result.ok( Hash.compareTo(orig.cred.array(), Hash.hashMD5(raw))==0);
+                    case CredDAO.FQI:
                     default:
                         return Result.ok(false);
                 }
@@ -934,33 +1028,33 @@ public class Question {
         Result<Void> rv = null;
 
         if (all || NsDAO.TABLE.equals(cname)) {
-            int seg[] = series(NsDAO.CACHE_SEG);
-            for(int i: seg) {cacheClear(trans, NsDAO.TABLE,i);}
+            int[] seg = series(NsDAO.CACHE_SEG);
+            for (int i: seg) {cacheClear(trans, NsDAO.TABLE,i);}
             rv = cacheInfoDAO.touch(trans, NsDAO.TABLE, seg);
         }
         if (all || PermDAO.TABLE.equals(cname)) {
-            int seg[] = series(NsDAO.CACHE_SEG);
-            for(int i: seg) {cacheClear(trans, PermDAO.TABLE,i);}
+            int[] seg = series(PermDAO.CACHE_SEG);
+            for (int i: seg) {cacheClear(trans, PermDAO.TABLE,i);}
             rv = cacheInfoDAO.touch(trans, PermDAO.TABLE,seg);
         }
         if (all || RoleDAO.TABLE.equals(cname)) {
-            int seg[] = series(NsDAO.CACHE_SEG);
-            for(int i: seg) {cacheClear(trans, RoleDAO.TABLE,i);}
+            int[] seg = series(RoleDAO.CACHE_SEG);
+            for (int i: seg) {cacheClear(trans, RoleDAO.TABLE,i);}
             rv = cacheInfoDAO.touch(trans, RoleDAO.TABLE,seg);
         }
         if (all || UserRoleDAO.TABLE.equals(cname)) {
-            int seg[] = series(NsDAO.CACHE_SEG);
-            for(int i: seg) {cacheClear(trans, UserRoleDAO.TABLE,i);}
+            int[] seg = series(UserRoleDAO.CACHE_SEG);
+            for (int i: seg) {cacheClear(trans, UserRoleDAO.TABLE,i);}
             rv = cacheInfoDAO.touch(trans, UserRoleDAO.TABLE,seg);
         }
         if (all || CredDAO.TABLE.equals(cname)) {
-            int seg[] = series(NsDAO.CACHE_SEG);
-            for(int i: seg) {cacheClear(trans, CredDAO.TABLE,i);}
+            int[] seg = series(CredDAO.CACHE_SEG);
+            for (int i: seg) {cacheClear(trans, CredDAO.TABLE,i);}
             rv = cacheInfoDAO.touch(trans, CredDAO.TABLE,seg);
         }
         if (all || CertDAO.TABLE.equals(cname)) {
-            int seg[] = series(NsDAO.CACHE_SEG);
-            for(int i: seg) {cacheClear(trans, CertDAO.TABLE,i);}
+            int[] seg = series(CertDAO.CACHE_SEG);
+            for (int i: seg) {cacheClear(trans, CertDAO.TABLE,i);}
             rv = cacheInfoDAO.touch(trans, CertDAO.TABLE,seg);
         }
 
@@ -999,11 +1093,11 @@ public class Question {
 
     public boolean isDelegated(AuthzTrans trans, String user, String approver, Map<String,Result<List<DelegateDAO.Data>>> rldd ) {
         Result<List<DelegateDAO.Data>> userDelegatedFor = rldd.get(user);
-        if(userDelegatedFor==null) {
+        if (userDelegatedFor==null) {
             userDelegatedFor=delegateDAO.readByDelegate(trans, user);
             rldd.put(user, userDelegatedFor);
         }
-        if(userDelegatedFor.isOKhasData()) {
+        if (userDelegatedFor.isOKhasData()) {
             for (DelegateDAO.Data curr : userDelegatedFor.value) {
                 if (curr.user.equals(approver) && curr.delegate.equals(user)
                         && curr.expires.after(new Date())) {
@@ -1016,8 +1110,8 @@ public class Question {
 
     public static boolean willSpecialLog(AuthzTrans trans, String user) {
         Boolean b = trans.get(specialLogSlot, null);
-        if(b==null) { // we haven't evaluated in this trans for Special Log yet
-            if(specialLog==null) {
+        if (b==null) { // we haven't evaluated in this trans for Special Log yet
+            if (specialLog==null) {
                 return false;
             } else {
                 b = specialLog.contains(user);
@@ -1042,21 +1136,21 @@ public class Question {
             specialLog = new HashSet<>();
         }
         boolean rc = specialLog.add(id);
-        if(rc) {
+        if (rc) {
             trans.trace().printf("Trace on for %s requested by %s",id,trans.user());            
         }
         return rc;
     }
 
     public static synchronized boolean specialLogOff(AuthzTrans trans, String id) {
-        if(specialLog==null) {
+        if (specialLog==null) {
             return false;
         }
         boolean rv = specialLog.remove(id);
         if (specialLog.isEmpty()) {
             specialLog = null;
         }
-        if(rv) {
+        if (rv) {
             trans.trace().printf("Trace off for %s requested by %s",id,trans.user());            
         }
         return rv;
@@ -1086,19 +1180,21 @@ public class Question {
     public boolean isAdmin(AuthzTrans trans, String user, String ns) {
         Date now = new Date();
         Result<List<UserRoleDAO.Data>> rur = userRoleDAO.read(trans, user,ns+DOT_ADMIN);
-        if(rur.isOKhasData()) {for(UserRoleDAO.Data urdd : rur.value){
-            if(urdd.expires.after(now)) {
-                return true;
-            }
-        }};
+        if (rur.isOKhasData()) {
+               for (UserRoleDAO.Data urdd : rur.value){
+                   if (urdd.expires.after(now)) {
+                       return true;
+                   }
+               }
+        };
         return false;
     }
     
     public boolean isOwner(AuthzTrans trans, String user, String ns) {
         Result<List<UserRoleDAO.Data>> rur = userRoleDAO.read(trans, user,ns+DOT_OWNER);
         Date now = new Date();
-        if(rur.isOKhasData()) {for(UserRoleDAO.Data urdd : rur.value){
-            if(urdd.expires.after(now)) {
+        if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
+            if (urdd.expires.after(now)) {
                 return true;
             }
         }};
@@ -1109,8 +1205,8 @@ public class Question {
         Result<List<UserRoleDAO.Data>> rur = userRoleDAO.readByRole(trans,ns+DOT_OWNER);
         Date now = new Date();
         int count = 0;
-        if(rur.isOKhasData()) {for(UserRoleDAO.Data urdd : rur.value){
-            if(urdd.expires.after(now)) {
+        if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
+            if (urdd.expires.after(now)) {
                 ++count;
             }
         }};
@@ -1129,7 +1225,7 @@ public class Question {
         byte[] from = name.getBytes();
         StringBuilder sb = new StringBuilder();
         byte f;
-        for(int i=0;i<from.length;++i) {
+        for (int i=0;i<from.length;++i) {
             f=(byte)(from[i]); // printables;
             sb.append((char)((f>>4)+0x61));
             sb.append((char)((f&0x0F)+0x61));
@@ -1141,7 +1237,7 @@ public class Question {
         byte[] from = name.getBytes();
         StringBuilder sb = new StringBuilder();
         char c;
-        for(int i=0;i<from.length;++i) {
+        for (int i=0;i<from.length;++i) {
             c = (char)((from[i]-0x61)<<4);
             c |= (from[++i]-0x61);
             sb.append(c);