3abad1a551eb7b75253435362202732c1e57c90a
[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, String type, String instance, String action) {
329         if(type.indexOf('@') >= 0) {
330                 int colon = type.indexOf(':');
331                 if(colon>=0) {
332                         return permDAO.read(trans, type.substring(0, colon),type.substring(colon+1), instance,action);
333                 } else {
334                         return Result.err(Result.ERR_BadData, "%s is malformed",type);
335                 }
336         } else {
337                 Result<NsSplit> nss = deriveNsSplit(trans, type);
338                 if (nss.notOK()) {
339                     return Result.err(nss);
340                 }
341                 
342                 return permDAO.read(trans, nss.value.ns, nss.value.name, instance,action);
343         }
344     }
345
346     public Result<List<PermDAO.Data>> getPermsByRole(AuthzTrans trans, String role, boolean lookup) {
347         Result<NsSplit> nss = deriveNsSplit(trans, role);
348         if (nss.notOK()) {
349             return Result.err(nss);
350         }
351
352         Result<List<RoleDAO.Data>> rlrd = roleDAO.read(trans, nss.value.ns,
353                 nss.value.name);
354         if (rlrd.notOKorIsEmpty()) {
355             return Result.err(rlrd);
356         }
357         // Using Set to avoid duplicates
358         Set<String> permNames = new HashSet<>();
359         if (rlrd.isOKhasData()) {
360             for (RoleDAO.Data drr : rlrd.value) {
361                 permNames.addAll(drr.perms(false));
362             }
363         }
364
365         // Note: It should be ok for a Valid user to have no permissions -
366         // Jonathan 8/12/2013
367         List<PermDAO.Data> perms = new ArrayList<>();
368         for (String perm : permNames) {
369             Result<PermDAO.Data> pr = PermDAO.Data.decode(trans, this, perm);
370             if (pr.notOK()) {
371                 return Result.err(pr);
372             }
373
374             if (lookup) {
375                 Result<List<PermDAO.Data>> rlpd = permDAO.read(trans, pr.value);
376                 if (rlpd.isOKhasData()) {
377                     for (PermDAO.Data pData : rlpd.value) {
378                         perms.add(pData);
379                     }
380                 }
381             } else {
382                 perms.add(pr.value);
383             }
384         }
385
386         return Result.ok(perms);
387     }
388
389     public Result<List<RoleDAO.Data>> getRolesByName(AuthzTrans trans, String role) {
390         if(role.startsWith(trans.user()) ) {
391                 if(role.endsWith(":user")) {
392                         return roleDAO.read(trans,trans.user(), "user");
393                 } else {
394                         return Result.err(Result.ERR_BadData,"%s is a badly formatted role",role);
395                 }
396         }
397         Result<NsSplit> nss = deriveNsSplit(trans, role);
398         if (nss.notOK()) {
399             return Result.err(nss);
400         }
401         String r = nss.value.name;
402         if (r.endsWith(".*")) { // do children Search
403             return roleDAO.readChildren(trans, nss.value.ns,
404                     r.substring(0, r.length() - 2));
405         } else if (ASTERIX.equals(r)) {
406             return roleDAO.readChildren(trans, nss.value.ns, ASTERIX);
407         } else {
408             return roleDAO.read(trans, nss.value.ns, r);
409         }
410     }
411
412     /**
413      * Derive NS
414      * 
415      * Given a Child Namespace, figure out what the best Namespace parent is.
416      * 
417      * For instance, if in the NS table, the parent "org.osaaf" exists, but not
418      * "org.osaaf.child" or "org.osaaf.a.b.c", then passing in either
419      * "org.osaaf.child" or "org.osaaf.a.b.c" will return "org.osaaf"
420      * 
421      * Uses recursive search on Cached DAO data
422      * 
423      * @param trans
424      * @param child
425      * @return
426      */
427     public Result<NsDAO.Data> deriveNs(AuthzTrans trans, String child) {
428         Result<List<NsDAO.Data>> r = nsDAO.read(trans, child);
429         
430         if (r.isOKhasData()) {
431             return Result.ok(r.value.get(0));
432         } else {
433             int dot = child.lastIndexOf('.');
434             if (dot < 0) {
435                 return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", child);
436             } else {
437                 return deriveNs(trans, child.substring(0, dot));
438             }
439         }
440     }
441
442     public Result<NsDAO.Data> deriveFirstNsForType(AuthzTrans trans, String str, NsType type) {
443         NsDAO.Data nsd;
444
445         for (String lookup = str;!".".equals(lookup) && lookup!=null;) {
446             Result<List<NsDAO.Data>> rld = nsDAO.read(trans, lookup);
447             if (rld.isOKhasData()) {
448                 nsd=rld.value.get(0);
449                 lookup = nsd.parent;
450                 if (type.type == nsd.type) {
451                     return Result.ok(nsd);
452                 } else {
453                     int dot = str.lastIndexOf('.');
454                     
455                     if (dot < 0) {
456                         return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", str);
457                     } else {
458                         return deriveFirstNsForType(trans, str.substring(0, dot),type);
459                     }
460                 }
461             } else {
462                 int dot = str.lastIndexOf('.');
463                 
464                 if (dot < 0) {
465                     return Result.err(Status.ERR_NsNotFound,"There is no valid Company Namespace for %s",str);
466                 } else {
467                     return deriveFirstNsForType(trans, str.substring(0, dot),type);
468                 }
469             }
470         }
471         return Result.err(Status.ERR_NotFound, str + " does not contain type " + type.name());
472     }
473
474     public Result<NsSplit> deriveNsSplit(AuthzTrans trans, String child) {
475         Result<NsDAO.Data> ndd = deriveNs(trans, child);
476         if (ndd.isOK()) {
477             NsSplit nss = new NsSplit(ndd.value, child);
478             if (nss.isOK()) {
479                 return Result.ok(nss);
480             } else {
481                 return Result.err(Status.ERR_NsNotFound,
482                         "Cannot split [%s] into valid namespace elements",
483                         child);
484             }
485         }
486         return Result.err(ndd);
487     }
488
489     /**
490      * Translate an ID into it's domain
491      * 
492      * i.e. myid1234@aaf.att.com results in domain of com.att.aaf
493      * 
494      * @param id
495      * @return
496      */
497     public static String domain2ns(String id) {
498         int at = id.indexOf('@');
499         if (at >= 0) {
500             String[] domain = id.substring(at + 1).split("\\.");
501             StringBuilder ns = new StringBuilder(id.length());
502             boolean first = true;
503             for (int i = domain.length - 1; i >= 0; --i) {
504                 if (first) {
505                     first = false;
506                 } else {
507                     ns.append('.');
508                 }
509                 ns.append(domain[i]);
510             }
511             return ns.toString();
512         } else {
513             return "";
514         }
515
516     }
517
518     /**
519      * Validate Namespace of ID@Domain
520      * 
521      * Namespace is reverse order of Domain.
522      * 
523      * @param trans
524      * @param id
525      * @return
526      */
527     public Result<NsDAO.Data> validNSOfDomain(AuthzTrans trans, String id) {
528         // Take domain, reverse order, and check on NS
529         String ns;
530         if (id.indexOf('@')<0) { // it's already an ns, not an ID
531             ns = id;
532         } else {
533             ns = domain2ns(id);
534         }
535         if (ns.length() > 0) {
536             if (!trans.org().getDomain().equals(ns)) { 
537                 Result<List<NsDAO.Data>> rlnsd = nsDAO.read(trans, ns);
538                 if (rlnsd.isOKhasData()) {
539                     return Result.ok(rlnsd.value.get(0));
540                 }
541             }
542         }
543         return Result.err(Status.ERR_NsNotFound,
544                 "A Namespace is not available for %s", id);
545     }
546
547     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,NsDAO.Data ndd, Access access) {
548         // <ns>.access|:role:<role name>|<read|write>
549         String ns = ndd.name;
550         int last;
551         do {
552             if (isGranted(trans, user, ns, ACCESS, ":ns", access.name())) {
553                 return Result.ok(ndd);
554             }
555             if ((last = ns.lastIndexOf('.')) >= 0) {
556                 ns = ns.substring(0, last);
557             }
558         } while (last >= 0);
559         // com.att.aaf.ns|:<client ns>:ns|<access>
560         // AAF-724 - Make consistent response for May User", and not take the
561         // last check... too confusing.
562         Result<NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd, ":"    + ndd.name + ":ns", access.name());
563         if (rv.isOK()) {
564             return rv;
565         } else if (rv.status==Result.ERR_Backend) {
566             return Result.err(rv);
567         } else {
568             return Result.err(Status.ERR_Denied, "[%s] may not %s in NS [%s]",
569                     user, access.name(), ndd.name);
570         }
571     }
572
573     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user, RoleDAO.Data rdd, Access access) {
574         if(trans.user().equals(rdd.ns)) {
575                 return Result.ok((NsDAO.Data)null);
576         }
577         Result<NsDAO.Data> rnsd = deriveNs(trans, rdd.ns);
578         if (rnsd.isOK()) {
579             return mayUser(trans, user, rnsd.value, rdd, access);
580         }
581         return rnsd;
582     }
583
584     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user, NsDAO.Data ndd, RoleDAO.Data rdd, Access access) {
585         // 1) Is User in the Role?
586         Result<List<UserRoleDAO.Data>> rurd = userRoleDAO.readUserInRole(trans, user, rdd.fullName());
587         if (rurd.isOKhasData()) {
588             return Result.ok(ndd);
589         }
590
591         String roleInst = ":role:" + rdd.name;
592         // <ns>.access|:role:<role name>|<read|write>
593         String ns = rdd.ns;
594         int last;
595         do {
596             if (isGranted(trans, user, ns,ACCESS, roleInst, access.name())) {
597                 return Result.ok(ndd);
598             }
599             if ((last = ns.lastIndexOf('.')) >= 0) {
600                 ns = ns.substring(0, last);
601             }
602         } while (last >= 0);
603
604         // Check if Access by Global Role perm
605         // com.att.aaf.ns|:<client ns>:role:name|<access>
606         Result<NsDAO.Data> rnsd = mayUserVirtueOfNS(trans, user, ndd, ":"
607                 + rdd.ns + roleInst, access.name());
608         if (rnsd.isOK()) {
609             return rnsd;
610         } else if (rnsd.status==Result.ERR_Backend) {
611             return Result.err(rnsd);
612         }
613
614         // Check if Access to Whole NS
615         // AAF-724 - Make consistent response for May User", and not take the
616         // last check... too confusing.
617         Result<org.onap.aaf.auth.dao.cass.NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd, 
618                 ":" + rdd.ns + ":ns", access.name());
619         if (rv.isOK()) {
620             return rv;
621         } else if (rnsd.status==Result.ERR_Backend) {
622             return Result.err(rnsd);
623         } else {
624             return Result.err(Status.ERR_Denied, "[%s] may not %s Role [%s]",
625                     user, access.name(), rdd.fullName());
626         }
627
628     }
629
630     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,PermDAO.Data pdd, Access access) {
631         if(pdd.ns.indexOf('@')>-1) {
632                 if(user.equals(pdd.ns) || isGranted(trans,user,Define.ROOT_NS(),"access",pdd.instance,READ)) {
633                         NsDAO.Data ndd = new NsDAO.Data();
634                         ndd.name = user;
635                         ndd.type = NsDAO.USER;
636                         ndd.parent = "";
637                         return Result.ok(ndd);
638                 } else {
639                         return Result.err(Result.ERR_Security,"Only a User may modify User");
640                 }
641         }
642         Result<NsDAO.Data> rnsd = deriveNs(trans, pdd.ns);
643         if (rnsd.isOK()) {
644             return mayUser(trans, user, rnsd.value, pdd, access);
645         }
646         return rnsd;
647     }
648
649     public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,NsDAO.Data ndd, PermDAO.Data pdd, Access access) {
650         if (isGranted(trans, user, pdd.ns, pdd.type, pdd.instance, pdd.action)) {
651             return Result.ok(ndd);
652         }
653         String permInst = ":perm:" + pdd.type + ':' + pdd.instance + ':' + pdd.action;
654         // <ns>.access|:role:<role name>|<read|write>
655         String ns = ndd.name;
656         int last;
657         do {
658             if (isGranted(trans, user, ns, ACCESS, permInst, access.name())) {
659                 return Result.ok(ndd);
660             }
661             if ((last = ns.lastIndexOf('.')) >= 0) {
662                 ns = ns.substring(0, last);
663             }
664         } while (last >= 0);
665
666         // Check if Access by NS perm
667         // com.att.aaf.ns|:<client ns>:role:name|<access>
668         Result<NsDAO.Data> rnsd = mayUserVirtueOfNS(trans, user, ndd, ":" + pdd.ns + permInst, access.name());
669         if (rnsd.isOK()) {
670             return rnsd;
671         } else if (rnsd.status==Result.ERR_Backend) {
672             return Result.err(rnsd);
673         }
674
675         // Check if Access to Whole NS
676         // AAF-724 - Make consistent response for May User", and not take the
677         // last check... too confusing.
678         Result<NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd, ":"    + pdd.ns + ":ns", access.name());
679         if (rv.isOK()) {
680             return rv;
681         } else {
682             return Result.err(Status.ERR_Denied,
683                     "[%s] may not %s Perm [%s|%s|%s]", user, access.name(),
684                     pdd.fullType(), pdd.instance, pdd.action);
685         }
686
687     }
688
689     public Result<Void> mayUser(AuthzTrans trans, DelegateDAO.Data dd, Access access) {
690         try {
691             Result<NsDAO.Data> rnsd = deriveNs(trans, domain2ns(trans.user()));
692             if (rnsd.isOKhasData() && mayUserVirtueOfNS(trans,trans.user(),rnsd.value, ":"    + rnsd.value.name + ":ns", access.name()).isOK()) {
693                 return Result.ok();
694             }
695             boolean isUser = trans.user().equals(dd.user);
696             boolean isDelegate = dd.delegate != null
697                     && (dd.user.equals(dd.delegate) || trans.user().equals(
698                             dd.delegate));
699             Organization org = trans.org();
700             switch (access) {
701             case create:
702                 if (org.getIdentity(trans, dd.user) == null) {
703                     return Result.err(Status.ERR_UserNotFound,
704                             "[%s] is not a user in the company database.",
705                             dd.user);
706                 }
707                 if (!dd.user.equals(dd.delegate) && org.getIdentity(trans, dd.delegate) == null) {
708                     return Result.err(Status.ERR_UserNotFound,
709                             "[%s] is not a user in the company database.",
710                             dd.delegate);
711                 }
712                 if (!trans.requested(REQD_TYPE.force) && dd.user != null && dd.user.equals(dd.delegate)) {
713                     return Result.err(Status.ERR_BadData,
714                             "[%s] cannot be a delegate for self", dd.user);
715                 }
716                 if (!isUser    && !isGranted(trans, trans.user(), ROOT_NS,DELG,
717                                 org.getDomain(), Question.CREATE)) {
718                     return Result.err(Status.ERR_Denied,
719                             "[%s] may not create a delegate for [%s]",
720                             trans.user(), dd.user);
721                 }
722                 break;
723             case read:
724             case write:
725                 if (!isUser    && !isDelegate && 
726                         !isGranted(trans, trans.user(), ROOT_NS,DELG,org.getDomain(), access.name())) {
727                     return Result.err(Status.ERR_Denied,
728                             "[%s] may not %s delegates for [%s]", trans.user(),
729                             access.name(), dd.user);
730                 }
731                 break;
732             default:
733                 return Result.err(Status.ERR_BadData,"Unknown Access type [%s]", access.name());
734             }
735         } catch (Exception e) {
736             return Result.err(e);
737         }
738         return Result.ok();
739     }
740
741     /*
742      * Check (recursively, if necessary), if able to do something based on NS
743      */
744     private Result<NsDAO.Data> mayUserVirtueOfNS(AuthzTrans trans, String user,    NsDAO.Data nsd, String ns_and_type, String access) {
745         String ns = nsd.name;
746
747         // If an ADMIN of the Namespace, then allow
748         
749         Result<List<UserRoleDAO.Data>> rurd;
750         if ((rurd = userRoleDAO.readUserInRole(trans, user, ns+DOT_ADMIN)).isOKhasData()) {
751             return Result.ok(nsd);
752         } else if (rurd.status==Result.ERR_Backend) {
753             return Result.err(rurd);
754         }
755         
756         // If Specially granted Global Permission
757         if (isGranted(trans, user, ROOT_NS,NS, ns_and_type, access)) {
758             return Result.ok(nsd);
759         }
760
761         // Check recur
762
763         int dot = ns.length();
764         if ((dot = ns.lastIndexOf('.', dot - 1)) >= 0) {
765             Result<NsDAO.Data> rnsd = deriveNs(trans, ns.substring(0, dot));
766             if (rnsd.isOK()) {
767                 rnsd = mayUserVirtueOfNS(trans, user, rnsd.value, ns_and_type,access);
768             } else if (rnsd.status==Result.ERR_Backend) {
769                 return Result.err(rnsd);
770             }
771             if (rnsd.isOK()) {
772                 return Result.ok(nsd);
773             } else if (rnsd.status==Result.ERR_Backend) {
774                 return Result.err(rnsd);
775             }
776         }
777         return Result.err(Status.ERR_Denied, "%s may not %s %s", user, access,
778                 ns_and_type);
779     }
780
781     
782     /**
783      * isGranted
784      * 
785      * Important function - Check internal Permission Schemes for Permission to
786      * do things
787      * 
788      * @param trans
789      * @param type
790      * @param instance
791      * @param action
792      * @return
793      */
794     public boolean isGranted(AuthzTrans trans, String user, String ns, String type,String instance, String action) {
795         Result<List<PermDAO.Data>> perms = getPermsByUser(trans, user, false);
796         if (perms.isOK()) {
797             for (PermDAO.Data pd : perms.value) {
798                 if (ns.equals(pd.ns)) {
799                     if (type.equals(pd.type)) {
800                         if (PermEval.evalInstance(pd.instance, instance)) {
801                             if (PermEval.evalAction(pd.action, action)) { // don't return action here, might miss other action 
802                                 return true;
803                             }
804                         }
805                     }
806                 }
807             }
808         }
809         return false;
810     }
811
812     public Result<Date> doesUserCredMatch(AuthzTrans trans, String user, byte[] cred) throws DAOException {
813         Result<List<CredDAO.Data>> result;
814         TimeTaken tt = trans.start("Read DB Cred", Env.REMOTE);
815         try {
816             result = credDAO.readID(trans, user);
817         } finally {
818             tt.done();
819         }
820
821         Result<Date> rv = null;
822         if (result.isOK()) {
823             if (result.isEmpty()) {
824                 rv = Result.err(Status.ERR_UserNotFound, user);
825                 if (willSpecialLog(trans,user)) {
826                     trans.audit().log("Special DEBUG:", user, " does not exist in DB");
827                 }
828             } else {
829                 Date now = new Date();
830                 // Bug noticed 6/22. Sorting on the result can cause Concurrency Issues.     
831                 List<CredDAO.Data> cddl;
832                 if (result.value.size() > 1) {
833                     cddl = new ArrayList<>(result.value.size());
834                     for (CredDAO.Data old : result.value) {
835                         if (old.type==CredDAO.BASIC_AUTH || old.type==CredDAO.BASIC_AUTH_SHA256) {
836                             cddl.add(old);
837                         }
838                     }
839                     if (cddl.size()>1) {
840                         Collections.sort(cddl, (a, b) -> b.expires.compareTo(a.expires));
841                     }
842                 } else {
843                     cddl = result.value;
844                 }
845     
846                 Date expired = null;
847                 StringBuilder debug = willSpecialLog(trans,user)?new StringBuilder():null;
848                 for (CredDAO.Data cdd : cddl) {
849                     if (!cdd.id.equals(user)) {
850                         trans.error().log("doesUserCredMatch DB call does not match for user: " + user);
851                     }
852                     if (cdd.expires.after(now)) {
853                         byte[] dbcred = cdd.cred.array();
854                         
855                         try {
856                             switch(cdd.type) {
857                                 case CredDAO.BASIC_AUTH:
858                                     byte[] md5=Hash.hashMD5(cred);
859                                     if (Hash.compareTo(md5,dbcred)==0) {
860                                         checkLessThanDays(trans,cldays,now,cdd);
861                                         trans.setTag(cdd.tag);
862                                         return Result.ok(cdd.expires);
863                                     } else if (debug!=null) {
864                                         load(debug, cdd);
865                                     }
866                                     break;
867                                 case CredDAO.BASIC_AUTH_SHA256:
868                                     ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + cred.length);
869                                     bb.putInt(cdd.other);
870                                     bb.put(cred);
871                                     byte[] hash = Hash.hashSHA256(bb.array());
872     
873                                     if (Hash.compareTo(hash,dbcred)==0) {
874                                         checkLessThanDays(trans,cldays,now,cdd);
875                                         trans.setTag(cdd.tag);
876                                         return Result.ok(cdd.expires);
877                                     } else if (debug!=null) {
878                                         load(debug, cdd);
879                                     }
880                                     break;
881                                 default:
882                                     trans.error().log("Unknown Credential Type %s for %s, %s",Integer.toString(cdd.type),cdd.id, Chrono.dateTime(cdd.expires));
883                             }
884                         } catch (NoSuchAlgorithmException e) {
885                             trans.error().log(e);
886                         }
887                     } else {
888                         if (expired==null || expired.before(cdd.expires)) {
889                             expired = cdd.expires;
890                             trans.setTag(cdd.tag);
891                         }
892                     }
893                 } // end for each
894                 
895                 if (expired!=null) {
896                     // Note: this is only returned if there are no good Credentials
897                     rv = Result.err(Status.ERR_Security,
898                             "Credentials expired %s",Chrono.utcStamp(expired));
899                 } else {
900                         if (debug==null && alwaysSpecial) {
901                                 debug = new StringBuilder();
902                         }
903                         if (debug!=null) {
904                                 debug.append(trans.env().encryptor().encrypt(new String(cred)));
905                                 rv = Result.err(Status.ERR_Security,String.format("invalid password - %s",debug.toString()));
906                         }
907                 }
908             }
909         } else {
910             return Result.err(result);
911         }
912         return rv == null ? Result.err(Status.ERR_Security, "Wrong credential") : rv;
913     }
914
915
916     private void load(StringBuilder debug, Data cdd) {
917         debug.append("\nDB Entry: user=");
918         debug.append(cdd.id);
919         debug.append(",type=");
920         debug.append(cdd.type);
921         debug.append(",expires=");
922         debug.append(Chrono.dateTime(cdd.expires));
923         debug.append(",tag=");
924         debug.append(cdd.tag);
925         debug.append('\n');
926     }
927
928
929     private void checkLessThanDays(AuthzTrans trans, int days, Date now, Data cdd) {
930         long close = now.getTime() + (days * 86400000);
931         long cexp=cdd.expires.getTime();
932         if (cexp<close) {
933             int daysLeft = days-(int)((close-cexp)/86400000);
934             trans.audit().printf("user=%s,ip=%s,expires=%s,days=%d,tag=%s,msg=\"Password expires in less than %d day%s\"",
935                 cdd.id,trans.ip(),Chrono.dateOnlyStamp(cdd.expires),daysLeft, cdd.tag, 
936                 daysLeft,daysLeft==1?"":"s");
937         }
938     }
939
940
941     public Result<CredDAO.Data> userCredSetup(AuthzTrans trans, CredDAO.Data cred) {
942         if (cred.type==CredDAO.RAW) {
943             TimeTaken tt = trans.start("Hash Cred", Env.SUB);
944             try {
945                 cred.type = CredDAO.BASIC_AUTH_SHA256;
946                 cred.other = random.nextInt();
947                 ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + cred.cred.capacity());
948                 bb.putInt(cred.other);
949                 bb.put(cred.cred);
950                 byte[] hash = Hash.hashSHA256(bb.array());
951                 cred.cred = ByteBuffer.wrap(hash);
952                 return Result.ok(cred);
953             } catch (NoSuchAlgorithmException e) {
954                 return Result.err(Status.ERR_General,e.getLocalizedMessage());
955             } finally {
956                 tt.done();
957             }
958             
959         } else if (cred.type==CredDAO.FQI) {
960                 cred.cred = null;
961                 return Result.ok(cred);
962         }
963         return Result.err(Status.ERR_Security,"invalid/unreadable credential");
964     }
965     
966     public Result<Boolean> userCredCheck(AuthzTrans trans, CredDAO.Data orig, final byte[] raw) {
967             TimeTaken tt = trans.start("CheckCred Cred", Env.SUB);
968             try {
969                 switch(orig.type) {
970                     case CredDAO.BASIC_AUTH_SHA256:
971                         ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + raw.length);
972                         bb.putInt(orig.other);
973                         bb.put(raw);
974                         return Result.ok(Hash.compareTo(orig.cred.array(),Hash.hashSHA256(bb.array()))==0);
975                     case CredDAO.BASIC_AUTH:
976                         return Result.ok( Hash.compareTo(orig.cred.array(), Hash.hashMD5(raw))==0);
977                     case CredDAO.FQI:
978                     default:
979                         return Result.ok(false);
980                 }
981             } catch (NoSuchAlgorithmException e) {
982                 return Result.err(Status.ERR_General,e.getLocalizedMessage());
983             } finally {
984                 tt.done();
985             }
986     }
987
988     public static final String APPROVED = "APPROVE";
989     public static final String REJECT = "REJECT";
990     public static final String PENDING = "PENDING";
991
992     public Result<Void> canAddUser(AuthzTrans trans, UserRoleDAO.Data data,
993             List<ApprovalDAO.Data> approvals) {
994         // get the approval policy for the organization
995
996         // get the list of approvals with an accept status
997
998         // validate the approvals against the policy
999
1000         // for now check if all approvals are received and return
1001         // SUCCESS/FAILURE/SKIP
1002         boolean bReject = false;
1003         boolean bPending = false;
1004
1005         for (ApprovalDAO.Data approval : approvals) {
1006             if (approval.status.equals(REJECT)) {
1007                 bReject = true;
1008             } else if (approval.status.equals(PENDING)) {
1009                 bPending = true;
1010             }
1011         }
1012         if (bReject) {
1013             return Result.err(Status.ERR_Policy,
1014                     "Approval Polocy not conformed");
1015         }
1016         if (bPending) {
1017             return Result.err(Status.ERR_ActionNotCompleted,
1018                     "Required Approvals not received");
1019         }
1020
1021         return Result.ok();
1022     }
1023
1024     private static final String NO_CACHE_NAME = "No Cache Data named %s";
1025
1026     public Result<Void> clearCache(AuthzTrans trans, String cname) {
1027         boolean all = "all".equals(cname);
1028         Result<Void> rv = null;
1029
1030         if (all || NsDAO.TABLE.equals(cname)) {
1031             int[] seg = series(NsDAO.CACHE_SEG);
1032             for (int i: seg) {cacheClear(trans, NsDAO.TABLE,i);}
1033             rv = cacheInfoDAO.touch(trans, NsDAO.TABLE, seg);
1034         }
1035         if (all || PermDAO.TABLE.equals(cname)) {
1036             int[] seg = series(PermDAO.CACHE_SEG);
1037             for (int i: seg) {cacheClear(trans, PermDAO.TABLE,i);}
1038             rv = cacheInfoDAO.touch(trans, PermDAO.TABLE,seg);
1039         }
1040         if (all || RoleDAO.TABLE.equals(cname)) {
1041             int[] seg = series(RoleDAO.CACHE_SEG);
1042             for (int i: seg) {cacheClear(trans, RoleDAO.TABLE,i);}
1043             rv = cacheInfoDAO.touch(trans, RoleDAO.TABLE,seg);
1044         }
1045         if (all || UserRoleDAO.TABLE.equals(cname)) {
1046             int[] seg = series(UserRoleDAO.CACHE_SEG);
1047             for (int i: seg) {cacheClear(trans, UserRoleDAO.TABLE,i);}
1048             rv = cacheInfoDAO.touch(trans, UserRoleDAO.TABLE,seg);
1049         }
1050         if (all || CredDAO.TABLE.equals(cname)) {
1051             int[] seg = series(CredDAO.CACHE_SEG);
1052             for (int i: seg) {cacheClear(trans, CredDAO.TABLE,i);}
1053             rv = cacheInfoDAO.touch(trans, CredDAO.TABLE,seg);
1054         }
1055         if (all || CertDAO.TABLE.equals(cname)) {
1056             int[] seg = series(CertDAO.CACHE_SEG);
1057             for (int i: seg) {cacheClear(trans, CertDAO.TABLE,i);}
1058             rv = cacheInfoDAO.touch(trans, CertDAO.TABLE,seg);
1059         }
1060
1061         if (rv == null) {
1062             rv = Result.err(Status.ERR_BadData, NO_CACHE_NAME, cname);
1063         }
1064         return rv;
1065     }
1066
1067     public Result<Void> cacheClear(AuthzTrans trans, String cname,Integer segment) {
1068         Result<Void> rv;
1069         if (NsDAO.TABLE.equals(cname)) {
1070             rv = nsDAO.invalidate(segment);
1071         } else if (PermDAO.TABLE.equals(cname)) {
1072             rv = permDAO.invalidate(segment);
1073         } else if (RoleDAO.TABLE.equals(cname)) {
1074             rv = roleDAO.invalidate(segment);
1075         } else if (UserRoleDAO.TABLE.equals(cname)) {
1076             rv = userRoleDAO.invalidate(segment);
1077         } else if (CredDAO.TABLE.equals(cname)) {
1078             rv = credDAO.invalidate(segment);
1079         } else if (CertDAO.TABLE.equals(cname)) {
1080             rv = certDAO.invalidate(segment);
1081         } else {
1082             rv = Result.err(Status.ERR_BadData, NO_CACHE_NAME, cname);
1083         }
1084         return rv;
1085     }
1086
1087     private int[] series(int max) {
1088         int[] series = new int[max];
1089         for (int i = 0; i < max; ++i)
1090             series[i] = i;
1091         return series;
1092     }
1093
1094     public boolean isDelegated(AuthzTrans trans, String user, String approver, Map<String,Result<List<DelegateDAO.Data>>> rldd ) {
1095         Result<List<DelegateDAO.Data>> userDelegatedFor = rldd.get(user);
1096         if (userDelegatedFor==null) {
1097             userDelegatedFor=delegateDAO.readByDelegate(trans, user);
1098             rldd.put(user, userDelegatedFor);
1099         }
1100         if (userDelegatedFor.isOKhasData()) {
1101             for (DelegateDAO.Data curr : userDelegatedFor.value) {
1102                 if (curr.user.equals(approver) && curr.delegate.equals(user)
1103                         && curr.expires.after(new Date())) {
1104                     return true;
1105                 }
1106             }
1107         }
1108         return false;
1109     }
1110
1111     public static boolean willSpecialLog(AuthzTrans trans, String user) {
1112         Boolean b = trans.get(specialLogSlot, null);
1113         if (b==null) { // we haven't evaluated in this trans for Special Log yet
1114             if (specialLog==null) {
1115                 return false;
1116             } else {
1117                 b = specialLog.contains(user);
1118                 trans.put(specialLogSlot, b);
1119             }
1120         }
1121         return b;
1122     }
1123     
1124     public static void logEncryptTrace(AuthzTrans trans, String data) {
1125         long ti;
1126         trans.put(transIDSlot, ti=nextTraceID());
1127         trans.trace().log("id="+Long.toHexString(ti)+",data=\""+trans.env().encryptor().encrypt(data)+'"');
1128     }
1129
1130     private synchronized static long nextTraceID() {
1131         return ++traceID;
1132     }
1133
1134     public static synchronized boolean specialLogOn(AuthzTrans trans, String id) {
1135         if (specialLog == null) {
1136             specialLog = new HashSet<>();
1137         }
1138         boolean rc = specialLog.add(id);
1139         if (rc) {
1140             trans.trace().printf("Trace on for %s requested by %s",id,trans.user());            
1141         }
1142         return rc;
1143     }
1144
1145     public static synchronized boolean specialLogOff(AuthzTrans trans, String id) {
1146         if (specialLog==null) {
1147             return false;
1148         }
1149         boolean rv = specialLog.remove(id);
1150         if (specialLog.isEmpty()) {
1151             specialLog = null;
1152         }
1153         if (rv) {
1154             trans.trace().printf("Trace off for %s requested by %s",id,trans.user());            
1155         }
1156         return rv;
1157     }
1158
1159     /** 
1160      * canMove
1161      * Which Types can be moved
1162      * @param nsType
1163      * @return
1164      */
1165     public boolean canMove(NsType nsType) {
1166         boolean rv;
1167         switch(nsType) {
1168             case DOT:
1169             case ROOT:
1170             case COMPANY:
1171             case UNKNOWN:
1172                 rv = false;
1173                 break;
1174             default:
1175                 rv = true;
1176         }
1177         return rv;
1178     }
1179
1180     public boolean isAdmin(AuthzTrans trans, String user, String ns) {
1181         Date now = new Date();
1182         Result<List<UserRoleDAO.Data>> rur = userRoleDAO.read(trans, user,ns+DOT_ADMIN);
1183         if (rur.isOKhasData()) {
1184                 for (UserRoleDAO.Data urdd : rur.value){
1185                     if (urdd.expires.after(now)) {
1186                         return true;
1187                     }
1188                 }
1189         };
1190         return false;
1191     }
1192     
1193     public boolean isOwner(AuthzTrans trans, String user, String ns) {
1194         Result<List<UserRoleDAO.Data>> rur = userRoleDAO.read(trans, user,ns+DOT_OWNER);
1195         Date now = new Date();
1196         if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
1197             if (urdd.expires.after(now)) {
1198                 return true;
1199             }
1200         }};
1201         return false;
1202     }
1203
1204     public int countOwner(AuthzTrans trans, String ns) {
1205         Result<List<UserRoleDAO.Data>> rur = userRoleDAO.readByRole(trans,ns+DOT_OWNER);
1206         Date now = new Date();
1207         int count = 0;
1208         if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
1209             if (urdd.expires.after(now)) {
1210                 ++count;
1211             }
1212         }};
1213         return count;
1214     }
1215     
1216     /**
1217      * Return a Unique String, (same string, if it is already unique), with only
1218      * lowercase letters, digits and the '.' character.
1219      * 
1220      * @param name
1221      * @return
1222      * @throws IOException 
1223      */
1224     public static String toUnique(String name) throws IOException {
1225         byte[] from = name.getBytes();
1226         StringBuilder sb = new StringBuilder();
1227         byte f;
1228         for (int i=0;i<from.length;++i) {
1229             f=(byte)(from[i]); // printables;
1230             sb.append((char)((f>>4)+0x61));
1231             sb.append((char)((f&0x0F)+0x61));
1232         }
1233         return sb.toString();
1234     }
1235     
1236     public static String fromUnique(String name) throws IOException {
1237         byte[] from = name.getBytes();
1238         StringBuilder sb = new StringBuilder();
1239         char c;
1240         for (int i=0;i<from.length;++i) {
1241             c = (char)((from[i]-0x61)<<4);
1242             c |= (from[++i]-0x61);
1243             sb.append(c);
1244         }
1245         return sb.toString();
1246     }
1247
1248 }