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