2 * ============LICENSE_START====================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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====================================================
22 package org.onap.aaf.auth.dao.hl;
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;
34 import java.util.Random;
36 import java.util.TreeSet;
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;
79 import com.datastax.driver.core.Cluster;
84 * A Data Access Combination Object which asks Security and other Questions
89 public class Question {
91 // DON'T CHANGE FROM lower Case!!!
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";
102 static final String ASTERIX = "*";
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();
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";
121 public static final int MAX_SCOPE = 10;
122 public static final int APP_SCOPE = 3;
123 public static final int COMPANY_SCOPE = 2;
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;
133 private final HistoryDAO historyDAO;
134 public HistoryDAO historyDAO() {
138 private final CachedNSDAO nsDAO;
139 public CachedNSDAO nsDAO() {
143 private final CachedRoleDAO roleDAO;
144 public CachedRoleDAO roleDAO() {
148 private final CachedPermDAO permDAO;
149 public CachedPermDAO permDAO() {
153 private final CachedUserRoleDAO userRoleDAO;
154 public CachedUserRoleDAO userRoleDAO() {
158 private final CachedCredDAO credDAO;
159 public CachedCredDAO credDAO() {
163 private final CachedCertDAO certDAO;
164 public CachedCertDAO certDAO() {
168 private final DelegateDAO delegateDAO;
169 public DelegateDAO delegateDAO() {
173 private final FutureDAO futureDAO;
174 public FutureDAO futureDAO() {
178 private final ApprovalDAO approvalDAO;
179 public ApprovalDAO approvalDAO() {
183 public final LocateDAO locateDAO;
184 public LocateDAO locateDAO() {
188 private final CacheInfoDAO cacheInfoDAO;
189 private final int cldays;
190 private final boolean alwaysSpecial;
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);
198 // Deal with Cached Entries
199 cacheInfoDAO = new CacheInfoDAO(trans, historyDAO);
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);
208 locateDAO = new LocateDAO(trans,historyDAO);
209 futureDAO = new FutureDAO(trans, historyDAO);
210 delegateDAO = new DelegateDAO(trans, historyDAO);
211 approvalDAO = new ApprovalDAO(trans, historyDAO);
213 if (specialLogSlot==null) {
214 specialLogSlot = trans.slot(AuthzTransFilter.SPECIAL_LOG_SLOT);
217 if (transIDSlot==null) {
218 transIDSlot = trans.slot(AuthzTransFilter.TRANS_ID_SLOT);
221 AbsCassDAO.primePSIs(trans);
223 cldays = Integer.parseInt(trans.getProperty(Config.AAF_CRED_WARN_DAYS, Config.AAF_CRED_WARN_DAYS_DFT));
225 alwaysSpecial = Boolean.parseBoolean(trans.getProperty("aaf_always_special", Boolean.FALSE.toString()));
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);
235 public void close(AuthzTrans trans) {
236 historyDAO.close(trans);
237 cacheInfoDAO.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);
249 public Result<PermDAO.Data> permFrom(AuthzTrans trans, String type, String instance, String action) {
250 if(type.indexOf('@') >= 0) {
251 int colon = type.indexOf(':');
253 PermDAO.Data pdd = new PermDAO.Data();
254 pdd.ns = type.substring(0, colon);
255 pdd.type = type.substring(colon+1);
256 pdd.instance = instance;
259 return Result.ok(pdd);
261 return Result.err(Result.ERR_BadData,"Could not extract ns and type from " + type);
264 Result<NsDAO.Data> rnd = deriveNs(trans, type);
266 return Result.ok(new PermDAO.Data(new NsSplit(rnd.value, type),
269 return Result.err(rnd);
277 * Because this call is frequently called internally, AND because we already
278 * look for it in the initial Call, we cache within the Transaction
284 public Result<List<PermDAO.Data>> getPermsByUser(AuthzTrans trans, String user, boolean lookup) {
285 return PermLookup.get(trans, this, user).getPerms(lookup);
288 public Result<List<PermDAO.Data>> getPermsByUserFromRolesFilter(AuthzTrans trans, String user, String forUser) {
289 PermLookup plUser = PermLookup.get(trans, this, user);
290 Result<Set<String>> plPermNames = plUser.getPermNames();
291 if (plPermNames.notOK()) {
292 return Result.err(plPermNames);
296 if (forUser.equals(user)) {
299 // Setup a TreeSet to check on Namespaces to
300 nss = new TreeSet<>();
301 PermLookup fUser = PermLookup.get(trans, this, forUser);
302 Result<Set<String>> forUpn = fUser.getPermNames();
303 if (forUpn.notOK()) {
304 return Result.err(forUpn);
307 for (String pn : forUpn.value) {
308 Result<String[]> decoded = PermDAO.Data.decodeToArray(trans, this, pn);
309 if (decoded.isOKhasData()) {
310 nss.add(decoded.value[0]);
312 trans.error().log(pn,", derived from a Role, is invalid:",decoded.errorString());
317 List<PermDAO.Data> rlpUser = new ArrayList<>();
318 Result<PermDAO.Data> rpdd;
320 for (String pn : plPermNames.value) {
321 rpdd = PermDAO.Data.decode(trans, this, pn);
322 if (rpdd.isOKhasData()) {
324 if (nss==null || nss.contains(pdd.ns)) {
328 trans.error().log(pn,", derived from a Role, is invalid. Run Data Cleanup:",rpdd.errorString());
331 return Result.ok(rlpUser);
334 public Result<List<PermDAO.Data>> getPermsByType(AuthzTrans trans, String type) {
335 if(type.indexOf('@') >= 0) {
336 int colon = type.indexOf(':');
338 return permDAO.readByType(trans, type.substring(0, colon),type.substring(colon+1));
340 return Result.err(Result.ERR_BadData, "%s is malformed",type);
343 Result<NsSplit> nss = deriveNsSplit(trans, type);
345 return Result.err(nss);
347 return permDAO.readByType(trans, nss.value.ns, nss.value.name);
351 public Result<List<PermDAO.Data>> getPermsByName(AuthzTrans trans, String type, String instance, String action) {
352 if(type.indexOf('@') >= 0) {
353 int colon = type.indexOf(':');
355 return permDAO.read(trans, type.substring(0, colon),type.substring(colon+1), instance,action);
357 return Result.err(Result.ERR_BadData, "%s is malformed",type);
360 Result<NsSplit> nss = deriveNsSplit(trans, type);
362 return Result.err(nss);
365 return permDAO.read(trans, nss.value.ns, nss.value.name, instance,action);
369 public Result<List<PermDAO.Data>> getPermsByRole(AuthzTrans trans, String role, boolean lookup) {
370 Result<NsSplit> nss = deriveNsSplit(trans, role);
372 return Result.err(nss);
375 Result<List<RoleDAO.Data>> rlrd = roleDAO.read(trans, nss.value.ns,
377 if (rlrd.notOKorIsEmpty()) {
378 return Result.err(rlrd);
380 // Using Set to avoid duplicates
381 Set<String> permNames = new HashSet<>();
382 if (rlrd.isOKhasData()) {
383 for (RoleDAO.Data drr : rlrd.value) {
384 permNames.addAll(drr.perms(false));
388 // Note: It should be ok for a Valid user to have no permissions -
389 // Jonathan 8/12/2013
390 List<PermDAO.Data> perms = new ArrayList<>();
391 for (String perm : permNames) {
392 Result<PermDAO.Data> pr = PermDAO.Data.decode(trans, this, perm);
394 return Result.err(pr);
398 Result<List<PermDAO.Data>> rlpd = permDAO.read(trans, pr.value);
399 if (rlpd.isOKhasData()) {
400 for (PermDAO.Data pData : rlpd.value) {
409 return Result.ok(perms);
412 public Result<List<RoleDAO.Data>> getRolesByName(AuthzTrans trans, String role) {
413 if(role.startsWith(trans.user()) ) {
414 if(role.endsWith(":user")) {
415 return roleDAO.read(trans,trans.user(), "user");
417 return Result.err(Result.ERR_BadData,"%s is a badly formatted role",role);
420 Result<NsSplit> nss = deriveNsSplit(trans, role);
422 return Result.err(nss);
424 String r = nss.value.name;
425 if (r.endsWith(".*")) { // do children Search
426 return roleDAO.readChildren(trans, nss.value.ns,
427 r.substring(0, r.length() - 2));
428 } else if (ASTERIX.equals(r)) {
429 return roleDAO.readChildren(trans, nss.value.ns, ASTERIX);
431 return roleDAO.read(trans, nss.value.ns, r);
438 * Given a Child Namespace, figure out what the best Namespace parent is.
440 * For instance, if in the NS table, the parent "org.osaaf" exists, but not
441 * "org.osaaf.child" or "org.osaaf.a.b.c", then passing in either
442 * "org.osaaf.child" or "org.osaaf.a.b.c" will return "org.osaaf"
444 * Uses recursive search on Cached DAO data
450 public Result<NsDAO.Data> deriveNs(AuthzTrans trans, String child) {
451 Result<List<NsDAO.Data>> r = nsDAO.read(trans, child);
453 if (r.isOKhasData()) {
454 return Result.ok(r.value.get(0));
456 int dot = child.lastIndexOf('.');
458 return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", child);
460 return deriveNs(trans, child.substring(0, dot));
465 public Result<NsDAO.Data> deriveFirstNsForType(AuthzTrans trans, String str, NsType type) {
468 for (String lookup = str;!".".equals(lookup) && lookup!=null;) {
469 Result<List<NsDAO.Data>> rld = nsDAO.read(trans, lookup);
470 if (rld.isOKhasData()) {
471 nsd=rld.value.get(0);
473 if (type.type == nsd.type) {
474 return Result.ok(nsd);
476 int dot = str.lastIndexOf('.');
479 return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", str);
481 return deriveFirstNsForType(trans, str.substring(0, dot),type);
485 int dot = str.lastIndexOf('.');
488 return Result.err(Status.ERR_NsNotFound,"There is no valid Company Namespace for %s",str);
490 return deriveFirstNsForType(trans, str.substring(0, dot),type);
494 return Result.err(Status.ERR_NotFound, str + " does not contain type " + type.name());
497 public Result<NsSplit> deriveNsSplit(AuthzTrans trans, String child) {
498 Result<NsDAO.Data> ndd = deriveNs(trans, child);
500 NsSplit nss = new NsSplit(ndd.value, child);
502 return Result.ok(nss);
504 return Result.err(Status.ERR_NsNotFound,
505 "Cannot split [%s] into valid namespace elements",
509 return Result.err(ndd);
513 * Translate an ID into it's domain
515 * i.e. myid1234@aaf.att.com results in domain of com.att.aaf
520 public static String domain2ns(String id) {
521 int at = id.indexOf('@');
523 String[] domain = id.substring(at + 1).split("\\.");
524 StringBuilder ns = new StringBuilder(id.length());
525 boolean first = true;
526 for (int i = domain.length - 1; i >= 0; --i) {
532 ns.append(domain[i]);
534 return ns.toString();
542 * Validate Namespace of ID@Domain
544 * Namespace is reverse order of Domain.
550 public Result<NsDAO.Data> validNSOfDomain(AuthzTrans trans, String id) {
551 // Take domain, reverse order, and check on NS
553 if (id.indexOf('@')<0) { // it's already an ns, not an ID
558 if (ns.length() > 0) {
559 if (!trans.org().getDomain().equals(ns)) {
560 Result<List<NsDAO.Data>> rlnsd = nsDAO.read(trans, ns);
561 if (rlnsd.isOKhasData()) {
562 return Result.ok(rlnsd.value.get(0));
566 return Result.err(Status.ERR_NsNotFound,
567 "A Namespace is not available for %s", id);
570 public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,NsDAO.Data ndd, Access access) {
571 // <ns>.access|:role:<role name>|<read|write>
572 String ns = ndd.name;
575 if (isGranted(trans, user, ns, ACCESS, ":ns", access.name())) {
576 return Result.ok(ndd);
578 if ((last = ns.lastIndexOf('.')) >= 0) {
579 ns = ns.substring(0, last);
582 // com.att.aaf.ns|:<client ns>:ns|<access>
583 // AAF-724 - Make consistent response for May User", and not take the
584 // last check... too confusing.
585 Result<NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd, ":" + ndd.name + ":ns", access.name());
588 } else if (rv.status==Result.ERR_Backend) {
589 return Result.err(rv);
591 return Result.err(Status.ERR_Denied, "[%s] may not %s in NS [%s]",
592 user, access.name(), ndd.name);
596 public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user, RoleDAO.Data rdd, Access access) {
597 if(trans.user().equals(rdd.ns)) {
598 return Result.ok((NsDAO.Data)null);
600 Result<NsDAO.Data> rnsd = deriveNs(trans, rdd.ns);
602 return mayUser(trans, user, rnsd.value, rdd, access);
607 public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user, NsDAO.Data ndd, RoleDAO.Data rdd, Access access) {
608 // 1) Is User in the Role?
609 Result<List<UserRoleDAO.Data>> rurd = userRoleDAO.readUserInRole(trans, user, rdd.fullName());
610 if (rurd.isOKhasData()) {
611 return Result.ok(ndd);
614 String roleInst = ":role:" + rdd.name;
615 // <ns>.access|:role:<role name>|<read|write>
619 if (isGranted(trans, user, ns,ACCESS, roleInst, access.name())) {
620 return Result.ok(ndd);
622 if ((last = ns.lastIndexOf('.')) >= 0) {
623 ns = ns.substring(0, last);
627 // Check if Access by Global Role perm
628 // com.att.aaf.ns|:<client ns>:role:name|<access>
629 Result<NsDAO.Data> rnsd = mayUserVirtueOfNS(trans, user, ndd, ":"
630 + rdd.ns + roleInst, access.name());
633 } else if (rnsd.status==Result.ERR_Backend) {
634 return Result.err(rnsd);
637 // Check if Access to Whole NS
638 // AAF-724 - Make consistent response for May User", and not take the
639 // last check... too confusing.
640 Result<org.onap.aaf.auth.dao.cass.NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd,
641 ":" + rdd.ns + ":ns", access.name());
644 } else if (rnsd.status==Result.ERR_Backend) {
645 return Result.err(rnsd);
647 return Result.err(Status.ERR_Denied, "[%s] may not %s Role [%s]",
648 user, access.name(), rdd.fullName());
653 public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,PermDAO.Data pdd, Access access) {
654 if(pdd.ns.indexOf('@')>-1) {
655 if(user.equals(pdd.ns) || isGranted(trans,user,Define.ROOT_NS(),"access",pdd.instance,READ)) {
656 NsDAO.Data ndd = new NsDAO.Data();
658 ndd.type = NsDAO.USER;
660 return Result.ok(ndd);
662 return Result.err(Result.ERR_Security,"Only a User may modify User");
665 Result<NsDAO.Data> rnsd = deriveNs(trans, pdd.ns);
667 return mayUser(trans, user, rnsd.value, pdd, access);
672 public Result<NsDAO.Data> mayUser(AuthzTrans trans, String user,NsDAO.Data ndd, PermDAO.Data pdd, Access access) {
673 if (isGranted(trans, user, pdd.ns, pdd.type, pdd.instance, pdd.action)) {
674 return Result.ok(ndd);
676 String permInst = ":perm:" + pdd.type + ':' + pdd.instance + ':' + pdd.action;
677 // <ns>.access|:role:<role name>|<read|write>
678 String ns = ndd.name;
681 if (isGranted(trans, user, ns, ACCESS, permInst, access.name())) {
682 return Result.ok(ndd);
684 if ((last = ns.lastIndexOf('.')) >= 0) {
685 ns = ns.substring(0, last);
689 // Check if Access by NS perm
690 // com.att.aaf.ns|:<client ns>:role:name|<access>
691 Result<NsDAO.Data> rnsd = mayUserVirtueOfNS(trans, user, ndd, ":" + pdd.ns + permInst, access.name());
694 } else if (rnsd.status==Result.ERR_Backend) {
695 return Result.err(rnsd);
698 // Check if Access to Whole NS
699 // AAF-724 - Make consistent response for May User", and not take the
700 // last check... too confusing.
701 Result<NsDAO.Data> rv = mayUserVirtueOfNS(trans, user, ndd, ":" + pdd.ns + ":ns", access.name());
705 return Result.err(Status.ERR_Denied,
706 "[%s] may not %s Perm [%s|%s|%s]", user, access.name(),
707 pdd.fullType(), pdd.instance, pdd.action);
712 public Result<Void> mayUser(AuthzTrans trans, DelegateDAO.Data dd, Access access) {
714 Result<NsDAO.Data> rnsd = deriveNs(trans, domain2ns(trans.user()));
715 if (rnsd.isOKhasData() && mayUserVirtueOfNS(trans,trans.user(),rnsd.value, ":" + rnsd.value.name + ":ns", access.name()).isOK()) {
718 boolean isUser = trans.user().equals(dd.user);
719 boolean isDelegate = dd.delegate != null
720 && (dd.user.equals(dd.delegate) || trans.user().equals(
722 Organization org = trans.org();
725 if (org.getIdentity(trans, dd.user) == null) {
726 return Result.err(Status.ERR_UserNotFound,
727 "[%s] is not a user in the company database.",
730 if (!dd.user.equals(dd.delegate) && org.getIdentity(trans, dd.delegate) == null) {
731 return Result.err(Status.ERR_UserNotFound,
732 "[%s] is not a user in the company database.",
735 if (!trans.requested(REQD_TYPE.force) && dd.user != null && dd.user.equals(dd.delegate)) {
736 return Result.err(Status.ERR_BadData,
737 "[%s] cannot be a delegate for self", dd.user);
739 if (!isUser && !isGranted(trans, trans.user(), ROOT_NS,DELG,
740 org.getDomain(), Question.CREATE)) {
741 return Result.err(Status.ERR_Denied,
742 "[%s] may not create a delegate for [%s]",
743 trans.user(), dd.user);
748 if (!isUser && !isDelegate &&
749 !isGranted(trans, trans.user(), ROOT_NS,DELG,org.getDomain(), access.name())) {
750 return Result.err(Status.ERR_Denied,
751 "[%s] may not %s delegates for [%s]", trans.user(),
752 access.name(), dd.user);
756 return Result.err(Status.ERR_BadData,"Unknown Access type [%s]", access.name());
758 } catch (Exception e) {
759 return Result.err(e);
765 * Check (recursively, if necessary), if able to do something based on NS
767 private Result<NsDAO.Data> mayUserVirtueOfNS(AuthzTrans trans, String user, NsDAO.Data nsd, String ns_and_type, String access) {
768 String ns = nsd.name;
770 // If an ADMIN of the Namespace, then allow
772 Result<List<UserRoleDAO.Data>> rurd;
773 if ((rurd = userRoleDAO.readUserInRole(trans, user, ns+DOT_ADMIN)).isOKhasData()) {
774 return Result.ok(nsd);
775 } else if (rurd.status==Result.ERR_Backend) {
776 return Result.err(rurd);
779 // If Specially granted Global Permission
780 if (isGranted(trans, user, ROOT_NS,NS, ns_and_type, access)) {
781 return Result.ok(nsd);
786 int dot = ns.length();
787 if ((dot = ns.lastIndexOf('.', dot - 1)) >= 0) {
788 Result<NsDAO.Data> rnsd = deriveNs(trans, ns.substring(0, dot));
790 rnsd = mayUserVirtueOfNS(trans, user, rnsd.value, ns_and_type,access);
791 } else if (rnsd.status==Result.ERR_Backend) {
792 return Result.err(rnsd);
795 return Result.ok(nsd);
796 } else if (rnsd.status==Result.ERR_Backend) {
797 return Result.err(rnsd);
800 return Result.err(Status.ERR_Denied, "%s may not %s %s", user, access,
808 * Important function - Check internal Permission Schemes for Permission to
817 public boolean isGranted(AuthzTrans trans, String user, String ns, String type,String instance, String action) {
818 Result<List<PermDAO.Data>> perms = getPermsByUser(trans, user, false);
820 for (PermDAO.Data pd : perms.value) {
821 if (ns.equals(pd.ns)) {
822 if (type.equals(pd.type)) {
823 if (PermEval.evalInstance(pd.instance, instance)) {
824 if (PermEval.evalAction(pd.action, action)) { // don't return action here, might miss other action
835 public Result<Date> doesUserCredMatch(AuthzTrans trans, String user, byte[] cred) throws DAOException {
836 Result<List<CredDAO.Data>> result;
837 TimeTaken tt = trans.start("Read DB Cred", Env.REMOTE);
839 result = credDAO.readID(trans, user);
844 Result<Date> rv = null;
846 if (result.isEmpty()) {
847 rv = Result.err(Status.ERR_UserNotFound, user);
848 if (willSpecialLog(trans,user)) {
849 trans.audit().log("Special DEBUG:", user, " does not exist in DB");
852 Date now = new Date();
853 // Bug noticed 6/22. Sorting on the result can cause Concurrency Issues.
854 List<CredDAO.Data> cddl;
855 if (result.value.size() > 1) {
856 cddl = new ArrayList<>(result.value.size());
857 for (CredDAO.Data old : result.value) {
858 if (old.type==CredDAO.BASIC_AUTH || old.type==CredDAO.BASIC_AUTH_SHA256) {
863 Collections.sort(cddl, (a, b) -> b.expires.compareTo(a.expires));
870 StringBuilder debug = willSpecialLog(trans,user)?new StringBuilder():null;
871 for (CredDAO.Data cdd : cddl) {
872 if (!cdd.id.equals(user)) {
873 trans.error().log("doesUserCredMatch DB call does not match for user: " + user);
875 if (cdd.expires.after(now)) {
876 byte[] dbcred = cdd.cred.array();
880 case CredDAO.BASIC_AUTH:
881 byte[] md5=Hash.hashMD5(cred);
882 if (Hash.compareTo(md5,dbcred)==0) {
883 checkLessThanDays(trans,cldays,now,cdd);
884 trans.setTag(cdd.tag);
885 return Result.ok(cdd.expires);
886 } else if (debug!=null) {
890 case CredDAO.BASIC_AUTH_SHA256:
891 ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + cred.length);
892 bb.putInt(cdd.other);
894 byte[] hash = Hash.hashSHA256(bb.array());
896 if (Hash.compareTo(hash,dbcred)==0) {
897 checkLessThanDays(trans,cldays,now,cdd);
898 trans.setTag(cdd.tag);
899 return Result.ok(cdd.expires);
900 } else if (debug!=null) {
905 trans.error().log("Unknown Credential Type %s for %s, %s",Integer.toString(cdd.type),cdd.id, Chrono.dateTime(cdd.expires));
907 } catch (NoSuchAlgorithmException e) {
908 trans.error().log(e);
911 if (expired==null || expired.before(cdd.expires)) {
912 expired = cdd.expires;
913 trans.setTag(cdd.tag);
919 // Note: this is only returned if there are no good Credentials
920 rv = Result.err(Status.ERR_Security,
921 "Credentials expired %s",Chrono.utcStamp(expired));
923 if (debug==null && alwaysSpecial) {
924 debug = new StringBuilder();
927 debug.append(trans.env().encryptor().encrypt(new String(cred)));
928 rv = Result.err(Status.ERR_Security,String.format("invalid password - %s",debug.toString()));
933 return Result.err(result);
935 return rv == null ? Result.err(Status.ERR_Security, "Wrong credential") : rv;
939 private void load(StringBuilder debug, Data cdd) {
940 debug.append("\nDB Entry: user=");
941 debug.append(cdd.id);
942 debug.append(",type=");
943 debug.append(cdd.type);
944 debug.append(",expires=");
945 debug.append(Chrono.dateTime(cdd.expires));
946 debug.append(",tag=");
947 debug.append(cdd.tag);
952 private void checkLessThanDays(AuthzTrans trans, int days, Date now, Data cdd) {
953 long close = now.getTime() + (days * 86400000);
954 long cexp=cdd.expires.getTime();
956 int daysLeft = days-(int)((close-cexp)/86400000);
957 trans.audit().printf("user=%s,ip=%s,expires=%s,days=%d,tag=%s,msg=\"Password expires in less than %d day%s\"",
958 cdd.id,trans.ip(),Chrono.dateOnlyStamp(cdd.expires),daysLeft, cdd.tag,
959 daysLeft,daysLeft==1?"":"s");
964 public Result<CredDAO.Data> userCredSetup(AuthzTrans trans, CredDAO.Data cred) {
965 if (cred.type==CredDAO.RAW) {
966 TimeTaken tt = trans.start("Hash Cred", Env.SUB);
968 cred.type = CredDAO.BASIC_AUTH_SHA256;
969 cred.other = random.nextInt();
970 ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + cred.cred.capacity());
971 bb.putInt(cred.other);
973 byte[] hash = Hash.hashSHA256(bb.array());
974 cred.cred = ByteBuffer.wrap(hash);
975 return Result.ok(cred);
976 } catch (NoSuchAlgorithmException e) {
977 return Result.err(Status.ERR_General,e.getLocalizedMessage());
982 } else if (cred.type==CredDAO.FQI) {
984 return Result.ok(cred);
986 return Result.err(Status.ERR_Security,"invalid/unreadable credential");
989 public Result<Boolean> userCredCheck(AuthzTrans trans, CredDAO.Data orig, final byte[] raw) {
990 TimeTaken tt = trans.start("CheckCred Cred", Env.SUB);
993 case CredDAO.BASIC_AUTH_SHA256:
994 ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + raw.length);
995 bb.putInt(orig.other);
997 return Result.ok(Hash.compareTo(orig.cred.array(),Hash.hashSHA256(bb.array()))==0);
998 case CredDAO.BASIC_AUTH:
999 return Result.ok( Hash.compareTo(orig.cred.array(), Hash.hashMD5(raw))==0);
1002 return Result.ok(false);
1004 } catch (NoSuchAlgorithmException e) {
1005 return Result.err(Status.ERR_General,e.getLocalizedMessage());
1011 public static final String APPROVED = "APPROVE";
1012 public static final String REJECT = "REJECT";
1013 public static final String PENDING = "PENDING";
1015 public Result<Void> canAddUser(AuthzTrans trans, UserRoleDAO.Data data,
1016 List<ApprovalDAO.Data> approvals) {
1017 // get the approval policy for the organization
1019 // get the list of approvals with an accept status
1021 // validate the approvals against the policy
1023 // for now check if all approvals are received and return
1024 // SUCCESS/FAILURE/SKIP
1025 boolean bReject = false;
1026 boolean bPending = false;
1028 for (ApprovalDAO.Data approval : approvals) {
1029 if (approval.status.equals(REJECT)) {
1031 } else if (approval.status.equals(PENDING)) {
1036 return Result.err(Status.ERR_Policy,
1037 "Approval Polocy not conformed");
1040 return Result.err(Status.ERR_ActionNotCompleted,
1041 "Required Approvals not received");
1047 private static final String NO_CACHE_NAME = "No Cache Data named %s";
1049 public Result<Void> clearCache(AuthzTrans trans, String cname) {
1050 boolean all = "all".equals(cname);
1051 Result<Void> rv = null;
1053 if (all || NsDAO.TABLE.equals(cname)) {
1054 int[] seg = series(NsDAO.CACHE_SEG);
1055 for (int i: seg) {cacheClear(trans, NsDAO.TABLE,i);}
1056 rv = cacheInfoDAO.touch(trans, NsDAO.TABLE, seg);
1058 if (all || PermDAO.TABLE.equals(cname)) {
1059 int[] seg = series(PermDAO.CACHE_SEG);
1060 for (int i: seg) {cacheClear(trans, PermDAO.TABLE,i);}
1061 rv = cacheInfoDAO.touch(trans, PermDAO.TABLE,seg);
1063 if (all || RoleDAO.TABLE.equals(cname)) {
1064 int[] seg = series(RoleDAO.CACHE_SEG);
1065 for (int i: seg) {cacheClear(trans, RoleDAO.TABLE,i);}
1066 rv = cacheInfoDAO.touch(trans, RoleDAO.TABLE,seg);
1068 if (all || UserRoleDAO.TABLE.equals(cname)) {
1069 int[] seg = series(UserRoleDAO.CACHE_SEG);
1070 for (int i: seg) {cacheClear(trans, UserRoleDAO.TABLE,i);}
1071 rv = cacheInfoDAO.touch(trans, UserRoleDAO.TABLE,seg);
1073 if (all || CredDAO.TABLE.equals(cname)) {
1074 int[] seg = series(CredDAO.CACHE_SEG);
1075 for (int i: seg) {cacheClear(trans, CredDAO.TABLE,i);}
1076 rv = cacheInfoDAO.touch(trans, CredDAO.TABLE,seg);
1078 if (all || CertDAO.TABLE.equals(cname)) {
1079 int[] seg = series(CertDAO.CACHE_SEG);
1080 for (int i: seg) {cacheClear(trans, CertDAO.TABLE,i);}
1081 rv = cacheInfoDAO.touch(trans, CertDAO.TABLE,seg);
1085 rv = Result.err(Status.ERR_BadData, NO_CACHE_NAME, cname);
1090 public Result<Void> cacheClear(AuthzTrans trans, String cname,Integer segment) {
1092 if (NsDAO.TABLE.equals(cname)) {
1093 rv = nsDAO.invalidate(segment);
1094 } else if (PermDAO.TABLE.equals(cname)) {
1095 rv = permDAO.invalidate(segment);
1096 } else if (RoleDAO.TABLE.equals(cname)) {
1097 rv = roleDAO.invalidate(segment);
1098 } else if (UserRoleDAO.TABLE.equals(cname)) {
1099 rv = userRoleDAO.invalidate(segment);
1100 } else if (CredDAO.TABLE.equals(cname)) {
1101 rv = credDAO.invalidate(segment);
1102 } else if (CertDAO.TABLE.equals(cname)) {
1103 rv = certDAO.invalidate(segment);
1105 rv = Result.err(Status.ERR_BadData, NO_CACHE_NAME, cname);
1110 private int[] series(int max) {
1111 int[] series = new int[max];
1112 for (int i = 0; i < max; ++i)
1117 public boolean isDelegated(AuthzTrans trans, String user, String approver, Map<String,Result<List<DelegateDAO.Data>>> rldd ) {
1118 Result<List<DelegateDAO.Data>> userDelegatedFor = rldd.get(user);
1119 if (userDelegatedFor==null) {
1120 userDelegatedFor=delegateDAO.readByDelegate(trans, user);
1121 rldd.put(user, userDelegatedFor);
1123 if (userDelegatedFor.isOKhasData()) {
1124 for (DelegateDAO.Data curr : userDelegatedFor.value) {
1125 if (curr.user.equals(approver) && curr.delegate.equals(user)
1126 && curr.expires.after(new Date())) {
1134 public static boolean willSpecialLog(AuthzTrans trans, String user) {
1135 Boolean b = trans.get(specialLogSlot, null);
1136 if (b==null) { // we haven't evaluated in this trans for Special Log yet
1137 if (specialLog==null) {
1140 b = specialLog.contains(user);
1141 trans.put(specialLogSlot, b);
1147 public static void logEncryptTrace(AuthzTrans trans, String data) {
1149 trans.put(transIDSlot, ti=nextTraceID());
1150 trans.trace().log("id="+Long.toHexString(ti)+",data=\""+trans.env().encryptor().encrypt(data)+'"');
1153 private synchronized static long nextTraceID() {
1157 public static synchronized boolean specialLogOn(AuthzTrans trans, String id) {
1158 if (specialLog == null) {
1159 specialLog = new HashSet<>();
1161 boolean rc = specialLog.add(id);
1163 trans.trace().printf("Trace on for %s requested by %s",id,trans.user());
1168 public static synchronized boolean specialLogOff(AuthzTrans trans, String id) {
1169 if (specialLog==null) {
1172 boolean rv = specialLog.remove(id);
1173 if (specialLog.isEmpty()) {
1177 trans.trace().printf("Trace off for %s requested by %s",id,trans.user());
1184 * Which Types can be moved
1188 public boolean canMove(NsType nsType) {
1203 public boolean isAdmin(AuthzTrans trans, String user, String ns) {
1204 Result<List<UserRoleDAO.Data>> rur = userRoleDAO.read(trans, user,ns+DOT_ADMIN);
1205 if (rur.isOKhasData()) {
1206 Date now = new Date();
1207 for (UserRoleDAO.Data urdd : rur.value){
1208 if (urdd.expires.after(now)) {
1216 public boolean isOwner(AuthzTrans trans, String user, String ns) {
1217 Result<List<UserRoleDAO.Data>> rur = userRoleDAO.read(trans, user,ns+DOT_OWNER);
1218 if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
1219 Date now = new Date();
1220 if (urdd.expires.after(now)) {
1227 public int countOwner(AuthzTrans trans, String ns) {
1228 Result<List<UserRoleDAO.Data>> rur = userRoleDAO.readByRole(trans,ns+DOT_OWNER);
1229 Date now = new Date();
1231 if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
1232 if (urdd.expires.after(now)) {
1240 * Return a Unique String, (same string, if it is already unique), with only
1241 * lowercase letters, digits and the '.' character.
1245 * @throws IOException
1247 public static String toUnique(String name) throws IOException {
1248 byte[] from = name.getBytes();
1249 StringBuilder sb = new StringBuilder();
1251 for (int i=0;i<from.length;++i) {
1252 f=(byte)(from[i]); // printables;
1253 sb.append((char)((f>>4)+0x61));
1254 sb.append((char)((f&0x0F)+0x61));
1256 return sb.toString();
1259 public static String fromUnique(String name) throws IOException {
1260 byte[] from = name.getBytes();
1261 StringBuilder sb = new StringBuilder();
1263 for (int i=0;i<from.length;++i) {
1264 c = (char)((from[i]-0x61)<<4);
1265 c |= (from[++i]-0x61);
1268 return sb.toString();