Clean LOCAL Dir based on VERSION
[aaf/authz.git] / auth / auth-cass / src / main / java / org / onap / aaf / auth / dao / hl / Question.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.auth.dao.hl;
23
24 import java.io.IOException;
25 import java.nio.ByteBuffer;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.SecureRandom;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Date;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Random;
35 import java.util.Set;
36 import java.util.TreeSet;
37
38 import org.onap.aaf.auth.common.Define;
39 import org.onap.aaf.auth.dao.AbsCassDAO;
40 import org.onap.aaf.auth.dao.CachedDAO;
41 import org.onap.aaf.auth.dao.DAOException;
42 import org.onap.aaf.auth.dao.cached.CachedCertDAO;
43 import org.onap.aaf.auth.dao.cached.CachedCredDAO;
44 import org.onap.aaf.auth.dao.cached.CachedNSDAO;
45 import org.onap.aaf.auth.dao.cached.CachedPermDAO;
46 import org.onap.aaf.auth.dao.cached.CachedRoleDAO;
47 import org.onap.aaf.auth.dao.cached.CachedUserRoleDAO;
48 import org.onap.aaf.auth.dao.cass.ApprovalDAO;
49 import org.onap.aaf.auth.dao.cass.CacheInfoDAO;
50 import org.onap.aaf.auth.dao.cass.CertDAO;
51 import org.onap.aaf.auth.dao.cass.CredDAO;
52 import org.onap.aaf.auth.dao.cass.CredDAO.Data;
53 import org.onap.aaf.auth.dao.cass.DelegateDAO;
54 import org.onap.aaf.auth.dao.cass.FutureDAO;
55 import org.onap.aaf.auth.dao.cass.HistoryDAO;
56 import org.onap.aaf.auth.dao.cass.LocateDAO;
57 import org.onap.aaf.auth.dao.cass.NsDAO;
58 import org.onap.aaf.auth.dao.cass.NsSplit;
59 import org.onap.aaf.auth.dao.cass.NsType;
60 import org.onap.aaf.auth.dao.cass.PermDAO;
61 import org.onap.aaf.auth.dao.cass.RoleDAO;
62 import org.onap.aaf.auth.dao.cass.Status;
63 import org.onap.aaf.auth.dao.cass.UserRoleDAO;
64 import org.onap.aaf.auth.env.AuthzTrans;
65 import org.onap.aaf.auth.env.AuthzTrans.REQD_TYPE;
66 import org.onap.aaf.auth.env.AuthzTransFilter;
67 import org.onap.aaf.auth.layer.Result;
68 import org.onap.aaf.auth.org.Organization;
69 import org.onap.aaf.cadi.Hash;
70 import org.onap.aaf.cadi.aaf.PermEval;
71 import org.onap.aaf.cadi.config.Config;
72 import org.onap.aaf.misc.env.APIException;
73 import org.onap.aaf.misc.env.Env;
74 import org.onap.aaf.misc.env.Slot;
75 import org.onap.aaf.misc.env.TimeTaken;
76 import org.onap.aaf.misc.env.util.Chrono;
77
78 import com.datastax.driver.core.Cluster;
79
80 /**
81  * Question HL DAO
82  * 
83  * A Data Access Combination Object which asks Security and other Questions
84  * 
85  * @author Jonathan
86  *
87  */
88 public class Question {
89
90     // DON'T CHANGE FROM lower Case!!!
91     public enum Type {
92         ns, role, perm, cred
93     };
94
95     public static final String OWNER="owner";
96     public static final String ADMIN="admin";
97     public static final String DOT_OWNER=".owner";
98     public static final String DOT_ADMIN=".admin";
99     public static final String ACCESS = "access";
100
101     static final String ASTERIX = "*";
102
103     public enum Access {
104         read, write, create
105     };
106
107     public static final String READ = Access.read.name();
108     public static final String WRITE = Access.write.name();
109     public static final String CREATE = Access.create.name();
110
111     public static final String ROLE = Type.role.name();
112     public static final String PERM = Type.perm.name();
113     public static final String NS = Type.ns.name();
114     public static final String CRED = Type.cred.name();
115     private static final String DELG = "delg";
116     public static final String ROOT_NS = Define.isInitialized() ? Define.ROOT_NS() : "undefined";
117     public static final String ATTRIB = "attrib";
118
119
120     public static final int MAX_SCOPE = 10;
121     public static final int APP_SCOPE = 3;
122     public static final int COMPANY_SCOPE = 2;
123     static Slot PERMS;
124
125     private static Set<String> specialLog = null;
126     public static final Random random = new SecureRandom();
127     private static long traceID = random.nextLong();
128     private static Slot specialLogSlot = null;
129     private static Slot transIDSlot = null;
130
131
132     public final HistoryDAO historyDAO;
133     public final CachedNSDAO nsDAO;
134     public  CachedRoleDAO roleDAO;
135     public final CachedPermDAO permDAO;
136     public CachedUserRoleDAO userRoleDAO;
137     public final CachedCredDAO credDAO;
138     public final CachedCertDAO certDAO;
139     public final DelegateDAO delegateDAO;
140     public final FutureDAO futureDAO;
141     public final ApprovalDAO approvalDAO;
142     private final CacheInfoDAO cacheInfoDAO;
143     public final LocateDAO locateDAO;
144
145     public Question(AuthzTrans trans, Cluster cluster, String keyspace, boolean startClean) throws APIException, IOException {
146         PERMS = trans.slot("USER_PERMS");
147         trans.init().log("Instantiating DAOs");
148         long expiresIn = Long.parseLong(trans.getProperty(Config.AAF_USER_EXPIRES, Config.AAF_USER_EXPIRES_DEF));
149         historyDAO = new HistoryDAO(trans, cluster, keyspace);
150
151         // Deal with Cached Entries
152         cacheInfoDAO = new CacheInfoDAO(trans, historyDAO);
153
154         nsDAO = new CachedNSDAO(new NsDAO(trans, historyDAO, cacheInfoDAO),cacheInfoDAO, expiresIn);
155         permDAO = new CachedPermDAO(new PermDAO(trans, historyDAO, cacheInfoDAO), cacheInfoDAO, expiresIn);
156         roleDAO = new CachedRoleDAO(new RoleDAO(trans, historyDAO, cacheInfoDAO), cacheInfoDAO, expiresIn);
157         userRoleDAO = new CachedUserRoleDAO(new UserRoleDAO(trans, historyDAO,cacheInfoDAO), cacheInfoDAO, expiresIn);
158         credDAO = new CachedCredDAO(new CredDAO(trans, historyDAO, cacheInfoDAO), cacheInfoDAO, expiresIn);
159         certDAO = new CachedCertDAO(new CertDAO(trans, historyDAO, cacheInfoDAO), cacheInfoDAO, expiresIn);
160
161         locateDAO = new LocateDAO(trans,historyDAO);
162         futureDAO = new FutureDAO(trans, historyDAO);
163         delegateDAO = new DelegateDAO(trans, historyDAO);
164         approvalDAO = new ApprovalDAO(trans, historyDAO);
165
166         // Only want to aggressively cleanse User related Caches... The others,
167         // just normal refresh
168         if (startClean) {
169             CachedDAO.startCleansing(trans.env(), credDAO, userRoleDAO);
170             CachedDAO.startRefresh(trans.env(), cacheInfoDAO);
171         }
172         // Set a Timer to Check Caches to send messages for Caching changes
173         
174         if (specialLogSlot==null) {
175             specialLogSlot = trans.slot(AuthzTransFilter.SPECIAL_LOG_SLOT);
176         }
177         
178         if (transIDSlot==null) {
179             transIDSlot = trans.slot(AuthzTransFilter.TRANS_ID_SLOT);
180         }
181         
182         AbsCassDAO.primePSIs(trans);
183     }
184
185
186     public void close(AuthzTrans trans) {
187         historyDAO.close(trans);
188         cacheInfoDAO.close(trans);
189         nsDAO.close(trans);
190         permDAO.close(trans);
191         roleDAO.close(trans);
192         userRoleDAO.close(trans);
193         credDAO.close(trans);
194         certDAO.close(trans);
195         delegateDAO.close(trans);
196         futureDAO.close(trans);
197         approvalDAO.close(trans);
198     }
199
200     public Result<PermDAO.Data> permFrom(AuthzTrans trans, String type,
201             String instance, String action) {
202         Result<NsDAO.Data> rnd = deriveNs(trans, type);
203         if (rnd.isOK()) {
204             return Result.ok(new PermDAO.Data(new NsSplit(rnd.value, type),
205                     instance, action));
206         } else {
207             return Result.err(rnd);
208         }
209     }
210
211     /**
212      * getPermsByUser
213      * 
214      * Because this call is frequently called internally, AND because we already
215      * look for it in the initial Call, we cache within the Transaction
216      * 
217      * @param trans
218      * @param user
219      * @return
220      */
221     public Result<List<PermDAO.Data>> getPermsByUser(AuthzTrans trans, String user, boolean lookup) {
222         return PermLookup.get(trans, this, user).getPerms(lookup);
223     }
224     
225     public Result<List<PermDAO.Data>> getPermsByUserFromRolesFilter(AuthzTrans trans, String user, String forUser) {
226         PermLookup plUser = PermLookup.get(trans, this, user);
227         Result<Set<String>> plPermNames = plUser.getPermNames();
228         if (plPermNames.notOK()) {
229             return Result.err(plPermNames);
230         }
231         
232         Set<String> nss;
233         if (forUser.equals(user)) {
234             nss = null;
235         } else {
236             // Setup a TreeSet to check on Namespaces to 
237             nss = new TreeSet<>();
238             PermLookup fUser = PermLookup.get(trans, this, forUser);
239             Result<Set<String>> forUpn = fUser.getPermNames();
240             if (forUpn.notOK()) {
241                 return Result.err(forUpn);
242             }
243             
244             for (String pn : forUpn.value) {
245                 Result<String[]> decoded = PermDAO.Data.decodeToArray(trans, this, pn);
246                 if (decoded.isOKhasData()) {
247                     nss.add(decoded.value[0]);
248                 } else {
249                     trans.error().log(pn,", derived from a Role, is invalid:",decoded.errorString());
250                 }
251             }
252         }
253
254         List<PermDAO.Data> rlpUser = new ArrayList<>();
255         Result<PermDAO.Data> rpdd;
256         PermDAO.Data pdd;
257         for (String pn : plPermNames.value) {
258             rpdd = PermDAO.Data.decode(trans, this, pn);
259             if (rpdd.isOKhasData()) {
260                 pdd=rpdd.value;
261                 if (nss==null || nss.contains(pdd.ns)) {
262                     rlpUser.add(pdd);
263                 }
264             } else {
265                 trans.error().log(pn,", derived from a Role, is invalid.  Run Data Cleanup:",rpdd.errorString());
266             }
267         }
268         return Result.ok(rlpUser); 
269     }
270
271     public Result<List<PermDAO.Data>> getPermsByType(AuthzTrans trans, String perm) {
272         Result<NsSplit> nss = deriveNsSplit(trans, perm);
273         if (nss.notOK()) {
274             return Result.err(nss);
275         }
276         return permDAO.readByType(trans, nss.value.ns, nss.value.name);
277     }
278
279     public Result<List<PermDAO.Data>> getPermsByName(AuthzTrans trans,
280             String type, String instance, String action) {
281         Result<NsSplit> nss = deriveNsSplit(trans, type);
282         if (nss.notOK()) {
283             return Result.err(nss);
284         }
285         return permDAO.read(trans, nss.value.ns, nss.value.name, instance,action);
286     }
287
288     public Result<List<PermDAO.Data>> getPermsByRole(AuthzTrans trans, String role, boolean lookup) {
289         Result<NsSplit> nss = deriveNsSplit(trans, role);
290         if (nss.notOK()) {
291             return Result.err(nss);
292         }
293
294         Result<List<RoleDAO.Data>> rlrd = roleDAO.read(trans, nss.value.ns,
295                 nss.value.name);
296         if (rlrd.notOKorIsEmpty()) {
297             return Result.err(rlrd);
298         }
299         // Using Set to avoid duplicates
300         Set<String> permNames = new HashSet<>();
301         if (rlrd.isOKhasData()) {
302             for (RoleDAO.Data drr : rlrd.value) {
303                 permNames.addAll(drr.perms(false));
304             }
305         }
306
307         // Note: It should be ok for a Valid user to have no permissions -
308         // Jonathan 8/12/2013
309         List<PermDAO.Data> perms = new ArrayList<>();
310         for (String perm : permNames) {
311             Result<PermDAO.Data> pr = PermDAO.Data.decode(trans, this, perm);
312             if (pr.notOK()) {
313                 return Result.err(pr);
314             }
315
316             if (lookup) {
317                 Result<List<PermDAO.Data>> rlpd = permDAO.read(trans, pr.value);
318                 if (rlpd.isOKhasData()) {
319                     for (PermDAO.Data pData : rlpd.value) {
320                         perms.add(pData);
321                     }
322                 }
323             } else {
324                 perms.add(pr.value);
325             }
326         }
327
328         return Result.ok(perms);
329     }
330
331     public Result<List<RoleDAO.Data>> getRolesByName(AuthzTrans trans,
332             String role) {
333         Result<NsSplit> nss = deriveNsSplit(trans, role);
334         if (nss.notOK()) {
335             return Result.err(nss);
336         }
337         String r = nss.value.name;
338         if (r.endsWith(".*")) { // do children Search
339             return roleDAO.readChildren(trans, nss.value.ns,
340                     r.substring(0, r.length() - 2));
341         } else if (ASTERIX.equals(r)) {
342             return roleDAO.readChildren(trans, nss.value.ns, ASTERIX);
343         } else {
344             return roleDAO.read(trans, nss.value.ns, r);
345         }
346     }
347
348     /**
349      * Derive NS
350      * 
351      * Given a Child Namespace, figure out what the best Namespace parent is.
352      * 
353      * For instance, if in the NS table, the parent "org.osaaf" exists, but not
354      * "org.osaaf.child" or "org.osaaf.a.b.c", then passing in either
355      * "org.osaaf.child" or "org.osaaf.a.b.c" will return "org.osaaf"
356      * 
357      * Uses recursive search on Cached DAO data
358      * 
359      * @param trans
360      * @param child
361      * @return
362      */
363     public Result<NsDAO.Data> deriveNs(AuthzTrans trans, String child) {
364         Result<List<NsDAO.Data>> r = nsDAO.read(trans, child);
365         
366         if (r.isOKhasData()) {
367             return Result.ok(r.value.get(0));
368         } else {
369             int dot;
370             if (child==null) {
371                 return Result.err(Status.ERR_NsNotFound, "No Namespace");
372             } else {
373                 dot = child.lastIndexOf('.');
374             }
375             if (dot < 0) {
376                 return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", child);
377             } else {
378                 return deriveNs(trans, child.substring(0, dot));
379             }
380         }
381     }
382
383     public Result<NsDAO.Data> deriveFirstNsForType(AuthzTrans trans, String str, NsType type) {
384         NsDAO.Data nsd;
385
386         for (String lookup = str;!".".equals(lookup) && lookup!=null;) {
387             Result<List<NsDAO.Data>> rld = nsDAO.read(trans, lookup);
388             if (rld.isOKhasData()) {
389                 nsd=rld.value.get(0);
390                 lookup = nsd.parent;
391                 if (type.type == nsd.type) {
392                     return Result.ok(nsd);
393                 } else {
394                     int dot = str.lastIndexOf('.');
395                     
396                     if (dot < 0) {
397                         return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", str);
398                     } else {
399                         return deriveFirstNsForType(trans, str.substring(0, dot),type);
400                     }
401                 }
402             } else {
403                 int dot = str.lastIndexOf('.');
404                 
405                 if (dot < 0) {
406                     return Result.err(Status.ERR_NsNotFound,"There is no valid Company Namespace for %s",str);
407                 } else {
408                     return deriveFirstNsForType(trans, str.substring(0, dot),type);
409                 }
410             }
411         }
412         return Result.err(Status.ERR_NotFound, str + " does not contain type " + type.name());
413     }
414
415     public Result<NsSplit> deriveNsSplit(AuthzTrans trans, String child) {
416         Result<NsDAO.Data> ndd = deriveNs(trans, child);
417         if (ndd.isOK()) {
418             NsSplit nss = new NsSplit(ndd.value, child);
419             if (nss.isOK()) {
420                 return Result.ok(nss);
421             } else {
422                 return Result.err(Status.ERR_NsNotFound,
423                         "Cannot split [%s] into valid namespace elements",
424                         child);
425             }
426         }
427         return Result.err(ndd);
428     }
429
430     /**
431      * Translate an ID into it's domain
432      * 
433      * i.e. myid1234@aaf.att.com results in domain of com.att.aaf
434      * 
435      * @param id
436      * @return
437      */
438     public static String domain2ns(String id) {
439         int at = id.indexOf('@');
440         if (at >= 0) {
441             String[] domain = id.substring(at + 1).split("\\.");
442             StringBuilder ns = new StringBuilder(id.length());
443             boolean first = true;
444             for (int i = domain.length - 1; i >= 0; --i) {
445                 if (first) {
446                     first = false;
447                 } else {
448                     ns.append('.');
449                 }
450                 ns.append(domain[i]);
451             }
452             return ns.toString();
453         } else {
454             return "";
455         }
456
457     }
458
459     /**
460      * Validate Namespace of ID@Domain
461      * 
462      * Namespace is reverse order of Domain.
463      * 
464      * @param trans
465      * @param id
466      * @return
467      */
468     public Result<NsDAO.Data> validNSOfDomain(AuthzTrans trans, String id) {
469         // Take domain, reverse order, and check on NS
470         String ns;
471         if (id.indexOf('@')<0) { // it's already an ns, not an ID
472             ns = id;
473         } else {
474             ns = domain2ns(id);
475         }
476         if (ns.length() > 0) {
477             if (!trans.org().getDomain().equals(ns)) { 
478                 Result<List<NsDAO.Data>> rlnsd = nsDAO.read(trans, ns);
479                 if (rlnsd.isOKhasData()) {
480                     return Result.ok(rlnsd.value.get(0));
481                 }
482             }
483         }
484         return Result.err(Status.ERR_NsNotFound,
485                 "A Namespace is not available for %s", id);
486     }
487
488     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,NsDAO.Data ndd, Access access) {
489         // <ns>.access|:role:<role name>|<read|write>
490         String ns = ndd.name;
491         int last;
492         do {
493             if (isGranted(trans, user, ns, ACCESS, ":ns", access.name())) {
494                 return Result.ok(ndd);
495             }
496             if ((last = ns.lastIndexOf('.')) >= 0) {
497                 ns = ns.substring(0, last);
498             }
499         } while (last >= 0);
500         // com.att.aaf.ns|:<client ns>:ns|<access>
501         // AAF-724 - Make consistent response for May User", and not take the
502         // last check... too confusing.
503         Result<NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd, ":"    + ndd.name + ":ns", access.name());
504         if (rv.isOK()) {
505             return rv;
506         } else if (rv.status==Result.ERR_Backend) {
507             return Result.err(rv);
508         } else {
509             return Result.err(Status.ERR_Denied, "[%s] may not %s in NS [%s]",
510                     user, access.name(), ndd.name);
511         }
512     }
513
514     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user, RoleDAO.Data rdd, Access access) {
515         Result<NsDAO.Data> rnsd = deriveNs(trans, rdd.ns);
516         if (rnsd.isOK()) {
517             return mayUser(trans, user, rnsd.value, rdd, access);
518         }
519         return rnsd;
520     }
521
522     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user, NsDAO.Data ndd, RoleDAO.Data rdd, Access access) {
523         // 1) Is User in the Role?
524         Result<List<UserRoleDAO.Data>> rurd = userRoleDAO.readUserInRole(trans, user, rdd.fullName());
525         if (rurd.isOKhasData()) {
526             return Result.ok(ndd);
527         }
528
529         String roleInst = ":role:" + rdd.name;
530         // <ns>.access|:role:<role name>|<read|write>
531         String ns = rdd.ns;
532         int last;
533         do {
534             if (isGranted(trans, user, ns,ACCESS, roleInst, access.name())) {
535                 return Result.ok(ndd);
536             }
537             if ((last = ns.lastIndexOf('.')) >= 0) {
538                 ns = ns.substring(0, last);
539             }
540         } while (last >= 0);
541
542         // Check if Access by Global Role perm
543         // com.att.aaf.ns|:<client ns>:role:name|<access>
544         Result<NsDAO.Data> rnsd = mayUserVirtueOfNS(trans, user, ndd, ":"
545                 + rdd.ns + roleInst, access.name());
546         if (rnsd.isOK()) {
547             return rnsd;
548         } else if (rnsd.status==Result.ERR_Backend) {
549             return Result.err(rnsd);
550         }
551
552         // Check if Access to Whole NS
553         // AAF-724 - Make consistent response for May User", and not take the
554         // last check... too confusing.
555         Result<org.onap.aaf.auth.dao.cass.NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd, 
556                 ":" + rdd.ns + ":ns", access.name());
557         if (rv.isOK()) {
558             return rv;
559         } else if (rnsd.status==Result.ERR_Backend) {
560             return Result.err(rnsd);
561         } else {
562             return Result.err(Status.ERR_Denied, "[%s] may not %s Role [%s]",
563                     user, access.name(), rdd.fullName());
564         }
565
566     }
567
568     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,PermDAO.Data pdd, Access access) {
569         Result<NsDAO.Data> rnsd = deriveNs(trans, pdd.ns);
570         if (rnsd.isOK()) {
571             return mayUser(trans, user, rnsd.value, pdd, access);
572         }
573         return rnsd;
574     }
575
576     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,NsDAO.Data ndd, PermDAO.Data pdd, Access access) {
577         if (isGranted(trans, user, pdd.ns, pdd.type, pdd.instance, pdd.action)) {
578             return Result.ok(ndd);
579         }
580         String permInst = ":perm:" + pdd.type + ':' + pdd.instance + ':' + pdd.action;
581         // <ns>.access|:role:<role name>|<read|write>
582         String ns = ndd.name;
583         int last;
584         do {
585             if (isGranted(trans, user, ns, ACCESS, permInst, access.name())) {
586                 return Result.ok(ndd);
587             }
588             if ((last = ns.lastIndexOf('.')) >= 0) {
589                 ns = ns.substring(0, last);
590             }
591         } while (last >= 0);
592
593         // Check if Access by NS perm
594         // com.att.aaf.ns|:<client ns>:role:name|<access>
595         Result<NsDAO.Data> rnsd = mayUserVirtueOfNS(trans, user, ndd, ":" + pdd.ns + permInst, access.name());
596         if (rnsd.isOK()) {
597             return rnsd;
598         } else if (rnsd.status==Result.ERR_Backend) {
599             return Result.err(rnsd);
600         }
601
602         // Check if Access to Whole NS
603         // AAF-724 - Make consistent response for May User", and not take the
604         // last check... too confusing.
605         Result<NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd, ":"    + pdd.ns + ":ns", access.name());
606         if (rv.isOK()) {
607             return rv;
608         } else {
609             return Result.err(Status.ERR_Denied,
610                     "[%s] may not %s Perm [%s|%s|%s]", user, access.name(),
611                     pdd.fullType(), pdd.instance, pdd.action);
612         }
613
614     }
615
616     public Result<Void> mayUser(AuthzTrans trans, DelegateDAO.Data dd, Access access) {
617         try {
618             Result<NsDAO.Data> rnsd = deriveNs(trans, domain2ns(trans.user()));
619             if (rnsd.isOKhasData() && mayUserVirtueOfNS(trans,trans.user(),rnsd.value, ":"    + rnsd.value.name + ":ns", access.name()).isOK()) {
620                 return Result.ok();
621             }
622             boolean isUser = trans.user().equals(dd.user);
623             boolean isDelegate = dd.delegate != null
624                     && (dd.user.equals(dd.delegate) || trans.user().equals(
625                             dd.delegate));
626             Organization org = trans.org();
627             switch (access) {
628             case create:
629                 if (org.getIdentity(trans, dd.user) == null) {
630                     return Result.err(Status.ERR_UserNotFound,
631                             "[%s] is not a user in the company database.",
632                             dd.user);
633                 }
634                 if (!dd.user.equals(dd.delegate) && org.getIdentity(trans, dd.delegate) == null) {
635                     return Result.err(Status.ERR_UserNotFound,
636                             "[%s] is not a user in the company database.",
637                             dd.delegate);
638                 }
639                 if (!trans.requested(REQD_TYPE.force) && dd.user != null && dd.user.equals(dd.delegate)) {
640                     return Result.err(Status.ERR_BadData,
641                             "[%s] cannot be a delegate for self", dd.user);
642                 }
643                 if (!isUser    && !isGranted(trans, trans.user(), ROOT_NS,DELG,
644                                 org.getDomain(), Question.CREATE)) {
645                     return Result.err(Status.ERR_Denied,
646                             "[%s] may not create a delegate for [%s]",
647                             trans.user(), dd.user);
648                 }
649                 break;
650             case read:
651             case write:
652                 if (!isUser    && !isDelegate && 
653                         !isGranted(trans, trans.user(), ROOT_NS,DELG,org.getDomain(), access.name())) {
654                     return Result.err(Status.ERR_Denied,
655                             "[%s] may not %s delegates for [%s]", trans.user(),
656                             access.name(), dd.user);
657                 }
658                 break;
659             default:
660                 return Result.err(Status.ERR_BadData,"Unknown Access type [%s]", access.name());
661             }
662         } catch (Exception e) {
663             return Result.err(e);
664         }
665         return Result.ok();
666     }
667
668     /*
669      * Check (recursively, if necessary), if able to do something based on NS
670      */
671     private Result<NsDAO.Data> mayUserVirtueOfNS(AuthzTrans trans, String user,    NsDAO.Data nsd, String ns_and_type, String access) {
672         String ns = nsd.name;
673
674         // If an ADMIN of the Namespace, then allow
675         
676         Result<List<UserRoleDAO.Data>> rurd;
677         if ((rurd = userRoleDAO.readUserInRole(trans, user, ns+DOT_ADMIN)).isOKhasData()) {
678             return Result.ok(nsd);
679         } else if (rurd.status==Result.ERR_Backend) {
680             return Result.err(rurd);
681         }
682         
683         // If Specially granted Global Permission
684         if (isGranted(trans, user, ROOT_NS,NS, ns_and_type, access)) {
685             return Result.ok(nsd);
686         }
687
688         // Check recur
689
690         int dot = ns.length();
691         if ((dot = ns.lastIndexOf('.', dot - 1)) >= 0) {
692             Result<NsDAO.Data> rnsd = deriveNs(trans, ns.substring(0, dot));
693             if (rnsd.isOK()) {
694                 rnsd = mayUserVirtueOfNS(trans, user, rnsd.value, ns_and_type,access);
695             } else if (rnsd.status==Result.ERR_Backend) {
696                 return Result.err(rnsd);
697             }
698             if (rnsd.isOK()) {
699                 return Result.ok(nsd);
700             } else if (rnsd.status==Result.ERR_Backend) {
701                 return Result.err(rnsd);
702             }
703         }
704         return Result.err(Status.ERR_Denied, "%s may not %s %s", user, access,
705                 ns_and_type);
706     }
707
708     
709     /**
710      * isGranted
711      * 
712      * Important function - Check internal Permission Schemes for Permission to
713      * do things
714      * 
715      * @param trans
716      * @param type
717      * @param instance
718      * @param action
719      * @return
720      */
721     public boolean isGranted(AuthzTrans trans, String user, String ns, String type,String instance, String action) {
722         Result<List<PermDAO.Data>> perms = getPermsByUser(trans, user, false);
723         if (perms.isOK()) {
724             for (PermDAO.Data pd : perms.value) {
725                 if (ns.equals(pd.ns)) {
726                     if (type.equals(pd.type)) {
727                         if (PermEval.evalInstance(pd.instance, instance)) {
728                             if (PermEval.evalAction(pd.action, action)) { // don't return action here, might miss other action 
729                                 return true;
730                             }
731                         }
732                     }
733                 }
734             }
735         }
736         return false;
737     }
738
739     public Result<Date> doesUserCredMatch(AuthzTrans trans, String user, byte[] cred) throws DAOException {
740         Result<List<CredDAO.Data>> result;
741         TimeTaken tt = trans.start("Read DB Cred", Env.REMOTE);
742         try {
743             result = credDAO.readID(trans, user);
744         } finally {
745             tt.done();
746         }
747
748         Result<Date> rv = null;
749         if (result.isOK()) {
750             if (result.isEmpty()) {
751                 rv = Result.err(Status.ERR_UserNotFound, user);
752                 if (willSpecialLog(trans,user)) {
753                     trans.audit().log("Special DEBUG:", user, " does not exist in DB");
754                 }
755             } else {
756                 Date now = new Date();
757                 // Bug noticed 6/22. Sorting on the result can cause Concurrency Issues.     
758                 List<CredDAO.Data> cddl;
759                 if (result.value.size() > 1) {
760                     cddl = new ArrayList<>(result.value.size());
761                     for (CredDAO.Data old : result.value) {
762                         if (old.type==CredDAO.BASIC_AUTH || old.type==CredDAO.BASIC_AUTH_SHA256) {
763                             cddl.add(old);
764                         }
765                     }
766                     if (cddl.size()>1) {
767                         Collections.sort(cddl, (a, b) -> b.expires.compareTo(a.expires));
768                     }
769                 } else {
770                     cddl = result.value;
771                 }
772     
773                 Date expired = null;
774                 StringBuilder debug = willSpecialLog(trans,user)?new StringBuilder():null;
775                 for (CredDAO.Data cdd : cddl) {
776                     if (!cdd.id.equals(user)) {
777                         trans.error().log("doesUserCredMatch DB call does not match for user: " + user);
778                     }
779                     if (cdd.expires.after(now)) {
780                         byte[] dbcred = cdd.cred.array();
781                         
782                         try {
783                             switch(cdd.type) {
784                                 case CredDAO.BASIC_AUTH:
785                                     byte[] md5=Hash.hashMD5(cred);
786                                     if (Hash.compareTo(md5,dbcred)==0) {
787                                         checkLessThanDays(trans,7,now,cdd);
788                                         return Result.ok(cdd.expires);
789                                     } else if (debug!=null) {
790                                         load(debug, cdd);
791                                     }
792                                     break;
793                                 case CredDAO.BASIC_AUTH_SHA256:
794                                     ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + cred.length);
795                                     bb.putInt(cdd.other);
796                                     bb.put(cred);
797                                     byte[] hash = Hash.hashSHA256(bb.array());
798     
799                                     if (Hash.compareTo(hash,dbcred)==0) {
800                                         checkLessThanDays(trans,7,now,cdd);
801                                         return Result.ok(cdd.expires);
802                                     } else if (debug!=null) {
803                                         load(debug, cdd);
804                                     }
805                                     break;
806                                 default:
807                                     trans.error().log("Unknown Credential Type %s for %s, %s",Integer.toString(cdd.type),cdd.id, Chrono.dateTime(cdd.expires));
808                             }
809                         } catch (NoSuchAlgorithmException e) {
810                             trans.error().log(e);
811                         }
812                     } else {
813                         if (expired==null || expired.before(cdd.expires)) {
814                             expired = cdd.expires;
815                         }
816                     }
817                 } // end for each
818                 if (debug==null) {
819                     trans.audit().printf("No cred matches ip=%s, user=%s\n",trans.ip(),user);
820                 } else {
821                     trans.audit().printf("No cred matches ip=%s, user=%s %s\n",trans.ip(),user,debug.toString());
822                 }
823                 if (expired!=null) {
824                     // Note: this is only returned if there are no good Credentials
825                     rv = Result.err(Status.ERR_Security,
826                             "Credentials %s from %s expired %s",trans.user(), trans.ip(), Chrono.dateTime(expired));
827                 }
828             }
829         } else {
830             return Result.err(result);
831         }
832         return rv == null ? Result.create((Date) null, Status.ERR_Security, "Wrong credential") : rv;
833     }
834
835
836     private void load(StringBuilder debug, Data cdd) {
837         debug.append("DB Entry: user=");
838         debug.append(cdd.id);
839         debug.append(",type=");
840         debug.append(cdd.type);
841         debug.append(",expires=");
842         debug.append(Chrono.dateTime(cdd.expires));
843         debug.append('\n');
844     }
845
846
847     private void checkLessThanDays(AuthzTrans trans, int days, Date now, Data cdd) {
848         long close = now.getTime() + (days * 86400000);
849         long cexp=cdd.expires.getTime();
850         if (cexp<close) {
851             int daysLeft = days-(int)((close-cexp)/86400000);
852             trans.audit().printf("user=%s,ip=%s,expires=%s,days=%d,msg=\"Password expires in less than %d day%s\"",
853                 cdd.id,trans.ip(),Chrono.dateOnlyStamp(cdd.expires),daysLeft, daysLeft,daysLeft==1?"":"s");
854         }
855     }
856
857
858     public Result<CredDAO.Data> userCredSetup(AuthzTrans trans, CredDAO.Data cred) {
859         if (cred.type==CredDAO.RAW) {
860             TimeTaken tt = trans.start("Hash Cred", Env.SUB);
861             try {
862                 cred.type = CredDAO.BASIC_AUTH_SHA256;
863                 cred.other = random.nextInt();
864                 ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + cred.cred.capacity());
865                 bb.putInt(cred.other);
866                 bb.put(cred.cred);
867                 byte[] hash = Hash.hashSHA256(bb.array());
868                 cred.cred = ByteBuffer.wrap(hash);
869                 return Result.ok(cred);
870             } catch (NoSuchAlgorithmException e) {
871                 return Result.err(Status.ERR_General,e.getLocalizedMessage());
872             } finally {
873                 tt.done();
874             }
875             
876         }
877         return Result.err(Status.ERR_Security,"invalid/unreadable credential");
878     }
879     
880     public Result<Boolean> userCredCheck(AuthzTrans trans, CredDAO.Data orig, final byte[] raw) {
881             TimeTaken tt = trans.start("CheckCred Cred", Env.SUB);
882             try {
883                 switch(orig.type) {
884                     case CredDAO.BASIC_AUTH_SHA256:
885                         ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + raw.length);
886                         bb.putInt(orig.other);
887                         bb.put(raw);
888                         return Result.ok(Hash.compareTo(orig.cred.array(),Hash.hashSHA256(bb.array()))==0);
889                     case CredDAO.BASIC_AUTH:
890                         return Result.ok( Hash.compareTo(orig.cred.array(), Hash.hashMD5(raw))==0);
891                     default:
892                         return Result.ok(false);
893                 }
894             } catch (NoSuchAlgorithmException e) {
895                 return Result.err(Status.ERR_General,e.getLocalizedMessage());
896             } finally {
897                 tt.done();
898             }
899     }
900
901     public static final String APPROVED = "APPROVE";
902     public static final String REJECT = "REJECT";
903     public static final String PENDING = "PENDING";
904
905     public Result<Void> canAddUser(AuthzTrans trans, UserRoleDAO.Data data,
906             List<ApprovalDAO.Data> approvals) {
907         // get the approval policy for the organization
908
909         // get the list of approvals with an accept status
910
911         // validate the approvals against the policy
912
913         // for now check if all approvals are received and return
914         // SUCCESS/FAILURE/SKIP
915         boolean bReject = false;
916         boolean bPending = false;
917
918         for (ApprovalDAO.Data approval : approvals) {
919             if (approval.status.equals(REJECT)) {
920                 bReject = true;
921             } else if (approval.status.equals(PENDING)) {
922                 bPending = true;
923             }
924         }
925         if (bReject) {
926             return Result.err(Status.ERR_Policy,
927                     "Approval Polocy not conformed");
928         }
929         if (bPending) {
930             return Result.err(Status.ERR_ActionNotCompleted,
931                     "Required Approvals not received");
932         }
933
934         return Result.ok();
935     }
936
937     private static final String NO_CACHE_NAME = "No Cache Data named %s";
938
939     public Result<Void> clearCache(AuthzTrans trans, String cname) {
940         boolean all = "all".equals(cname);
941         Result<Void> rv = null;
942
943         if (all || NsDAO.TABLE.equals(cname)) {
944             int[] seg = series(NsDAO.CACHE_SEG);
945             for (int i: seg) {cacheClear(trans, NsDAO.TABLE,i);}
946             rv = cacheInfoDAO.touch(trans, NsDAO.TABLE, seg);
947         }
948         if (all || PermDAO.TABLE.equals(cname)) {
949             int[] seg = series(PermDAO.CACHE_SEG);
950             for (int i: seg) {cacheClear(trans, PermDAO.TABLE,i);}
951             rv = cacheInfoDAO.touch(trans, PermDAO.TABLE,seg);
952         }
953         if (all || RoleDAO.TABLE.equals(cname)) {
954             int[] seg = series(RoleDAO.CACHE_SEG);
955             for (int i: seg) {cacheClear(trans, RoleDAO.TABLE,i);}
956             rv = cacheInfoDAO.touch(trans, RoleDAO.TABLE,seg);
957         }
958         if (all || UserRoleDAO.TABLE.equals(cname)) {
959             int[] seg = series(UserRoleDAO.CACHE_SEG);
960             for (int i: seg) {cacheClear(trans, UserRoleDAO.TABLE,i);}
961             rv = cacheInfoDAO.touch(trans, UserRoleDAO.TABLE,seg);
962         }
963         if (all || CredDAO.TABLE.equals(cname)) {
964             int[] seg = series(CredDAO.CACHE_SEG);
965             for (int i: seg) {cacheClear(trans, CredDAO.TABLE,i);}
966             rv = cacheInfoDAO.touch(trans, CredDAO.TABLE,seg);
967         }
968         if (all || CertDAO.TABLE.equals(cname)) {
969             int[] seg = series(CertDAO.CACHE_SEG);
970             for (int i: seg) {cacheClear(trans, CertDAO.TABLE,i);}
971             rv = cacheInfoDAO.touch(trans, CertDAO.TABLE,seg);
972         }
973
974         if (rv == null) {
975             rv = Result.err(Status.ERR_BadData, NO_CACHE_NAME, cname);
976         }
977         return rv;
978     }
979
980     public Result<Void> cacheClear(AuthzTrans trans, String cname,Integer segment) {
981         Result<Void> rv;
982         if (NsDAO.TABLE.equals(cname)) {
983             rv = nsDAO.invalidate(segment);
984         } else if (PermDAO.TABLE.equals(cname)) {
985             rv = permDAO.invalidate(segment);
986         } else if (RoleDAO.TABLE.equals(cname)) {
987             rv = roleDAO.invalidate(segment);
988         } else if (UserRoleDAO.TABLE.equals(cname)) {
989             rv = userRoleDAO.invalidate(segment);
990         } else if (CredDAO.TABLE.equals(cname)) {
991             rv = credDAO.invalidate(segment);
992         } else if (CertDAO.TABLE.equals(cname)) {
993             rv = certDAO.invalidate(segment);
994         } else {
995             rv = Result.err(Status.ERR_BadData, NO_CACHE_NAME, cname);
996         }
997         return rv;
998     }
999
1000     private int[] series(int max) {
1001         int[] series = new int[max];
1002         for (int i = 0; i < max; ++i)
1003             series[i] = i;
1004         return series;
1005     }
1006
1007     public boolean isDelegated(AuthzTrans trans, String user, String approver, Map<String,Result<List<DelegateDAO.Data>>> rldd ) {
1008         Result<List<DelegateDAO.Data>> userDelegatedFor = rldd.get(user);
1009         if (userDelegatedFor==null) {
1010             userDelegatedFor=delegateDAO.readByDelegate(trans, user);
1011             rldd.put(user, userDelegatedFor);
1012         }
1013         if (userDelegatedFor.isOKhasData()) {
1014             for (DelegateDAO.Data curr : userDelegatedFor.value) {
1015                 if (curr.user.equals(approver) && curr.delegate.equals(user)
1016                         && curr.expires.after(new Date())) {
1017                     return true;
1018                 }
1019             }
1020         }
1021         return false;
1022     }
1023
1024     public static boolean willSpecialLog(AuthzTrans trans, String user) {
1025         Boolean b = trans.get(specialLogSlot, null);
1026         if (b==null) { // we haven't evaluated in this trans for Special Log yet
1027             if (specialLog==null) {
1028                 return false;
1029             } else {
1030                 b = specialLog.contains(user);
1031                 trans.put(specialLogSlot, b);
1032             }
1033         }
1034         return b;
1035     }
1036     
1037     public static void logEncryptTrace(AuthzTrans trans, String data) {
1038         long ti;
1039         trans.put(transIDSlot, ti=nextTraceID());
1040         trans.trace().log("id="+Long.toHexString(ti)+",data=\""+trans.env().encryptor().encrypt(data)+'"');
1041     }
1042
1043     private synchronized static long nextTraceID() {
1044         return ++traceID;
1045     }
1046
1047     public static synchronized boolean specialLogOn(AuthzTrans trans, String id) {
1048         if (specialLog == null) {
1049             specialLog = new HashSet<>();
1050         }
1051         boolean rc = specialLog.add(id);
1052         if (rc) {
1053             trans.trace().printf("Trace on for %s requested by %s",id,trans.user());            
1054         }
1055         return rc;
1056     }
1057
1058     public static synchronized boolean specialLogOff(AuthzTrans trans, String id) {
1059         if (specialLog==null) {
1060             return false;
1061         }
1062         boolean rv = specialLog.remove(id);
1063         if (specialLog.isEmpty()) {
1064             specialLog = null;
1065         }
1066         if (rv) {
1067             trans.trace().printf("Trace off for %s requested by %s",id,trans.user());            
1068         }
1069         return rv;
1070     }
1071
1072     /** 
1073      * canMove
1074      * Which Types can be moved
1075      * @param nsType
1076      * @return
1077      */
1078     public boolean canMove(NsType nsType) {
1079         boolean rv;
1080         switch(nsType) {
1081             case DOT:
1082             case ROOT:
1083             case COMPANY:
1084             case UNKNOWN:
1085                 rv = false;
1086                 break;
1087             default:
1088                 rv = true;
1089         }
1090         return rv;
1091     }
1092
1093     public boolean isAdmin(AuthzTrans trans, String user, String ns) {
1094         Date now = new Date();
1095         Result<List<UserRoleDAO.Data>> rur = userRoleDAO.read(trans, user,ns+DOT_ADMIN);
1096         if (rur.isOKhasData()) {
1097                 for (UserRoleDAO.Data urdd : rur.value){
1098                     if (urdd.expires.after(now)) {
1099                         return true;
1100                     }
1101                 }
1102         };
1103         return false;
1104     }
1105     
1106     public boolean isOwner(AuthzTrans trans, String user, String ns) {
1107         Result<List<UserRoleDAO.Data>> rur = userRoleDAO.read(trans, user,ns+DOT_OWNER);
1108         Date now = new Date();
1109         if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
1110             if (urdd.expires.after(now)) {
1111                 return true;
1112             }
1113         }};
1114         return false;
1115     }
1116
1117     public int countOwner(AuthzTrans trans, String ns) {
1118         Result<List<UserRoleDAO.Data>> rur = userRoleDAO.readByRole(trans,ns+DOT_OWNER);
1119         Date now = new Date();
1120         int count = 0;
1121         if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
1122             if (urdd.expires.after(now)) {
1123                 ++count;
1124             }
1125         }};
1126         return count;
1127     }
1128     
1129     /**
1130      * Return a Unique String, (same string, if it is already unique), with only
1131      * lowercase letters, digits and the '.' character.
1132      * 
1133      * @param name
1134      * @return
1135      * @throws IOException 
1136      */
1137     public static String toUnique(String name) throws IOException {
1138         byte[] from = name.getBytes();
1139         StringBuilder sb = new StringBuilder();
1140         byte f;
1141         for (int i=0;i<from.length;++i) {
1142             f=(byte)(from[i]); // printables;
1143             sb.append((char)((f>>4)+0x61));
1144             sb.append((char)((f&0x0F)+0x61));
1145         }
1146         return sb.toString();
1147     }
1148     
1149     public static String fromUnique(String name) throws IOException {
1150         byte[] from = name.getBytes();
1151         StringBuilder sb = new StringBuilder();
1152         char c;
1153         for (int i=0;i<from.length;++i) {
1154             c = (char)((from[i]-0x61)<<4);
1155             c |= (from[++i]-0x61);
1156             sb.append(c);
1157         }
1158         return sb.toString();
1159     }
1160
1161 }