Change API Version to 2.1.15
[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, String instance, String action) {
250         if(type.indexOf('@') >= 0) {
251                 int colon = type.indexOf(':');
252                 if(colon>=0) {
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;
257                         pdd.action = action;
258                 
259                         return Result.ok(pdd);
260                 } else {
261                         return Result.err(Result.ERR_BadData,"Could not extract ns and type from " + type);
262                 }
263         } else {
264                 Result<NsDAO.Data> rnd = deriveNs(trans, type);
265                 if (rnd.isOK()) {
266                     return Result.ok(new PermDAO.Data(new NsSplit(rnd.value, type),
267                             instance, action));
268                 } else {
269                     return Result.err(rnd);
270                 }
271         }
272     }
273
274     /**
275      * getPermsByUser
276      * 
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
279      * 
280      * @param trans
281      * @param user
282      * @return
283      */
284     public Result<List<PermDAO.Data>> getPermsByUser(AuthzTrans trans, String user, boolean lookup) {
285         return PermLookup.get(trans, this, user).getPerms(lookup);
286     }
287     
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);
293         }
294         
295         Set<String> nss;
296         if (forUser.equals(user)) {
297             nss = null;
298         } else {
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);
305             }
306             
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]);
311                 } else {
312                     trans.error().log(pn,", derived from a Role, is invalid:",decoded.errorString());
313                 }
314             }
315         }
316
317         List<PermDAO.Data> rlpUser = new ArrayList<>();
318         Result<PermDAO.Data> rpdd;
319         PermDAO.Data pdd;
320         for (String pn : plPermNames.value) {
321             rpdd = PermDAO.Data.decode(trans, this, pn);
322             if (rpdd.isOKhasData()) {
323                 pdd=rpdd.value;
324                 if (nss==null || nss.contains(pdd.ns)) {
325                     rlpUser.add(pdd);
326                 }
327             } else {
328                 trans.error().log(pn,", derived from a Role, is invalid.  Run Data Cleanup:",rpdd.errorString());
329             }
330         }
331         return Result.ok(rlpUser); 
332     }
333
334     public Result<List<PermDAO.Data>> getPermsByType(AuthzTrans trans, String type) {
335         if(type.indexOf('@') >= 0) {
336                 int colon = type.indexOf(':');
337                 if(colon>=0) {
338                         return permDAO.readByType(trans, type.substring(0, colon),type.substring(colon+1));
339                 } else {
340                         return Result.err(Result.ERR_BadData, "%s is malformed",type);
341                 }
342         } else {
343                 Result<NsSplit> nss = deriveNsSplit(trans, type);
344                 if (nss.notOK()) {
345                     return Result.err(nss);
346                 }
347                 return permDAO.readByType(trans, nss.value.ns, nss.value.name);
348         }
349     }
350
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(':');
354                 if(colon>=0) {
355                         return permDAO.read(trans, type.substring(0, colon),type.substring(colon+1), instance,action);
356                 } else {
357                         return Result.err(Result.ERR_BadData, "%s is malformed",type);
358                 }
359         } else {
360                 Result<NsSplit> nss = deriveNsSplit(trans, type);
361                 if (nss.notOK()) {
362                     return Result.err(nss);
363                 }
364                 
365                 return permDAO.read(trans, nss.value.ns, nss.value.name, instance,action);
366         }
367     }
368
369     public Result<List<PermDAO.Data>> getPermsByRole(AuthzTrans trans, String role, boolean lookup) {
370         Result<NsSplit> nss = deriveNsSplit(trans, role);
371         if (nss.notOK()) {
372             return Result.err(nss);
373         }
374
375         Result<List<RoleDAO.Data>> rlrd = roleDAO.read(trans, nss.value.ns,
376                 nss.value.name);
377         if (rlrd.notOKorIsEmpty()) {
378             return Result.err(rlrd);
379         }
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));
385             }
386         }
387
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);
393             if (pr.notOK()) {
394                 return Result.err(pr);
395             }
396
397             if (lookup) {
398                 Result<List<PermDAO.Data>> rlpd = permDAO.read(trans, pr.value);
399                 if (rlpd.isOKhasData()) {
400                     for (PermDAO.Data pData : rlpd.value) {
401                         perms.add(pData);
402                     }
403                 }
404             } else {
405                 perms.add(pr.value);
406             }
407         }
408
409         return Result.ok(perms);
410     }
411
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");
416                 } else {
417                         return Result.err(Result.ERR_BadData,"%s is a badly formatted role",role);
418                 }
419         }
420         Result<NsSplit> nss = deriveNsSplit(trans, role);
421         if (nss.notOK()) {
422             return Result.err(nss);
423         }
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);
430         } else {
431             return roleDAO.read(trans, nss.value.ns, r);
432         }
433     }
434
435     /**
436      * Derive NS
437      * 
438      * Given a Child Namespace, figure out what the best Namespace parent is.
439      * 
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"
443      * 
444      * Uses recursive search on Cached DAO data
445      * 
446      * @param trans
447      * @param child
448      * @return
449      */
450     public Result<NsDAO.Data> deriveNs(AuthzTrans trans, String child) {
451         Result<List<NsDAO.Data>> r = nsDAO.read(trans, child);
452         
453         if (r.isOKhasData()) {
454             return Result.ok(r.value.get(0));
455         } else {
456             int dot = child.lastIndexOf('.');
457             if (dot < 0) {
458                 return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", child);
459             } else {
460                 return deriveNs(trans, child.substring(0, dot));
461             }
462         }
463     }
464
465     public Result<NsDAO.Data> deriveFirstNsForType(AuthzTrans trans, String str, NsType type) {
466         NsDAO.Data nsd;
467
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);
472                 lookup = nsd.parent;
473                 if (type.type == nsd.type) {
474                     return Result.ok(nsd);
475                 } else {
476                     int dot = str.lastIndexOf('.');
477                     
478                     if (dot < 0) {
479                         return Result.err(Status.ERR_NsNotFound, "No Namespace for [%s]", str);
480                     } else {
481                         return deriveFirstNsForType(trans, str.substring(0, dot),type);
482                     }
483                 }
484             } else {
485                 int dot = str.lastIndexOf('.');
486                 
487                 if (dot < 0) {
488                     return Result.err(Status.ERR_NsNotFound,"There is no valid Company Namespace for %s",str);
489                 } else {
490                     return deriveFirstNsForType(trans, str.substring(0, dot),type);
491                 }
492             }
493         }
494         return Result.err(Status.ERR_NotFound, str + " does not contain type " + type.name());
495     }
496
497     public Result<NsSplit> deriveNsSplit(AuthzTrans trans, String child) {
498         Result<NsDAO.Data> ndd = deriveNs(trans, child);
499         if (ndd.isOK()) {
500             NsSplit nss = new NsSplit(ndd.value, child);
501             if (nss.isOK()) {
502                 return Result.ok(nss);
503             } else {
504                 return Result.err(Status.ERR_NsNotFound,
505                         "Cannot split [%s] into valid namespace elements",
506                         child);
507             }
508         }
509         return Result.err(ndd);
510     }
511
512     /**
513      * Translate an ID into it's domain
514      * 
515      * i.e. myid1234@aaf.att.com results in domain of com.att.aaf
516      * 
517      * @param id
518      * @return
519      */
520     public static String domain2ns(String id) {
521         int at = id.indexOf('@');
522         if (at >= 0) {
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) {
527                 if (first) {
528                     first = false;
529                 } else {
530                     ns.append('.');
531                 }
532                 ns.append(domain[i]);
533             }
534             return ns.toString();
535         } else {
536             return "";
537         }
538
539     }
540
541     /**
542      * Validate Namespace of ID@Domain
543      * 
544      * Namespace is reverse order of Domain.
545      * 
546      * @param trans
547      * @param id
548      * @return
549      */
550     public Result<NsDAO.Data> validNSOfDomain(AuthzTrans trans, String id) {
551         // Take domain, reverse order, and check on NS
552         String ns;
553         if (id.indexOf('@')<0) { // it's already an ns, not an ID
554             ns = id;
555         } else {
556             ns = domain2ns(id);
557         }
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));
563                 }
564             }
565         }
566         return Result.err(Status.ERR_NsNotFound,
567                 "A Namespace is not available for %s", id);
568     }
569
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;
573         int last;
574         do {
575             if (isGranted(trans, user, ns, ACCESS, ":ns", access.name())) {
576                 return Result.ok(ndd);
577             }
578             if ((last = ns.lastIndexOf('.')) >= 0) {
579                 ns = ns.substring(0, last);
580             }
581         } while (last >= 0);
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());
586         if (rv.isOK()) {
587             return rv;
588         } else if (rv.status==Result.ERR_Backend) {
589             return Result.err(rv);
590         } else {
591             return Result.err(Status.ERR_Denied, "[%s] may not %s in NS [%s]",
592                     user, access.name(), ndd.name);
593         }
594     }
595
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);
599         }
600         Result<NsDAO.Data> rnsd = deriveNs(trans, rdd.ns);
601         if (rnsd.isOK()) {
602             return mayUser(trans, user, rnsd.value, rdd, access);
603         }
604         return rnsd;
605     }
606
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);
612         }
613
614         String roleInst = ":role:" + rdd.name;
615         // <ns>.access|:role:<role name>|<read|write>
616         String ns = rdd.ns;
617         int last;
618         do {
619             if (isGranted(trans, user, ns,ACCESS, roleInst, access.name())) {
620                 return Result.ok(ndd);
621             }
622             if ((last = ns.lastIndexOf('.')) >= 0) {
623                 ns = ns.substring(0, last);
624             }
625         } while (last >= 0);
626
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());
631         if (rnsd.isOK()) {
632             return rnsd;
633         } else if (rnsd.status==Result.ERR_Backend) {
634             return Result.err(rnsd);
635         }
636
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());
642         if (rv.isOK()) {
643             return rv;
644         } else if (rnsd.status==Result.ERR_Backend) {
645             return Result.err(rnsd);
646         } else {
647             return Result.err(Status.ERR_Denied, "[%s] may not %s Role [%s]",
648                     user, access.name(), rdd.fullName());
649         }
650
651     }
652
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();
657                         ndd.name = user;
658                         ndd.type = NsDAO.USER;
659                         ndd.parent = "";
660                         return Result.ok(ndd);
661                 } else {
662                         return Result.err(Result.ERR_Security,"Only a User may modify User");
663                 }
664         }
665         Result<NsDAO.Data> rnsd = deriveNs(trans, pdd.ns);
666         if (rnsd.isOK()) {
667             return mayUser(trans, user, rnsd.value, pdd, access);
668         }
669         return rnsd;
670     }
671
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);
675         }
676         String permInst = ":perm:" + pdd.type + ':' + pdd.instance + ':' + pdd.action;
677         // <ns>.access|:role:<role name>|<read|write>
678         String ns = ndd.name;
679         int last;
680         do {
681             if (isGranted(trans, user, ns, ACCESS, permInst, access.name())) {
682                 return Result.ok(ndd);
683             }
684             if ((last = ns.lastIndexOf('.')) >= 0) {
685                 ns = ns.substring(0, last);
686             }
687         } while (last >= 0);
688
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());
692         if (rnsd.isOK()) {
693             return rnsd;
694         } else if (rnsd.status==Result.ERR_Backend) {
695             return Result.err(rnsd);
696         }
697
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());
702         if (rv.isOK()) {
703             return rv;
704         } else {
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);
708         }
709
710     }
711
712     public Result<Void> mayUser(AuthzTrans trans, DelegateDAO.Data dd, Access access) {
713         try {
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()) {
716                 return Result.ok();
717             }
718             boolean isUser = trans.user().equals(dd.user);
719             boolean isDelegate = dd.delegate != null
720                     && (dd.user.equals(dd.delegate) || trans.user().equals(
721                             dd.delegate));
722             Organization org = trans.org();
723             switch (access) {
724             case create:
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.",
728                             dd.user);
729                 }
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.",
733                             dd.delegate);
734                 }
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);
738                 }
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);
744                 }
745                 break;
746             case read:
747             case write:
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);
753                 }
754                 break;
755             default:
756                 return Result.err(Status.ERR_BadData,"Unknown Access type [%s]", access.name());
757             }
758         } catch (Exception e) {
759             return Result.err(e);
760         }
761         return Result.ok();
762     }
763
764     /*
765      * Check (recursively, if necessary), if able to do something based on NS
766      */
767     private Result<NsDAO.Data> mayUserVirtueOfNS(AuthzTrans trans, String user,    NsDAO.Data nsd, String ns_and_type, String access) {
768         String ns = nsd.name;
769
770         // If an ADMIN of the Namespace, then allow
771         
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);
777         }
778         
779         // If Specially granted Global Permission
780         if (isGranted(trans, user, ROOT_NS,NS, ns_and_type, access)) {
781             return Result.ok(nsd);
782         }
783
784         // Check recur
785
786         int dot = ns.length();
787         if ((dot = ns.lastIndexOf('.', dot - 1)) >= 0) {
788             Result<NsDAO.Data> rnsd = deriveNs(trans, ns.substring(0, dot));
789             if (rnsd.isOK()) {
790                 rnsd = mayUserVirtueOfNS(trans, user, rnsd.value, ns_and_type,access);
791             } else if (rnsd.status==Result.ERR_Backend) {
792                 return Result.err(rnsd);
793             }
794             if (rnsd.isOK()) {
795                 return Result.ok(nsd);
796             } else if (rnsd.status==Result.ERR_Backend) {
797                 return Result.err(rnsd);
798             }
799         }
800         return Result.err(Status.ERR_Denied, "%s may not %s %s", user, access,
801                 ns_and_type);
802     }
803
804     
805     /**
806      * isGranted
807      * 
808      * Important function - Check internal Permission Schemes for Permission to
809      * do things
810      * 
811      * @param trans
812      * @param type
813      * @param instance
814      * @param action
815      * @return
816      */
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);
819         if (perms.isOK()) {
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 
825                                 return true;
826                             }
827                         }
828                     }
829                 }
830             }
831         }
832         return false;
833     }
834
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);
838         try {
839             result = credDAO.readID(trans, user);
840         } finally {
841             tt.done();
842         }
843
844         Result<Date> rv = null;
845         if (result.isOK()) {
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");
850                 }
851             } else {
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) {
859                             cddl.add(old);
860                         }
861                     }
862                     if (cddl.size()>1) {
863                         Collections.sort(cddl, (a, b) -> b.expires.compareTo(a.expires));
864                     }
865                 } else {
866                     cddl = result.value;
867                 }
868     
869                 Date expired = null;
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);
874                     }
875                     if (cdd.expires.after(now)) {
876                         byte[] dbcred = cdd.cred.array();
877                         
878                         try {
879                             switch(cdd.type) {
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) {
887                                         load(debug, cdd);
888                                     }
889                                     break;
890                                 case CredDAO.BASIC_AUTH_SHA256:
891                                     ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + cred.length);
892                                     bb.putInt(cdd.other);
893                                     bb.put(cred);
894                                     byte[] hash = Hash.hashSHA256(bb.array());
895     
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) {
901                                         load(debug, cdd);
902                                     }
903                                     break;
904                                 default:
905                                     trans.error().log("Unknown Credential Type %s for %s, %s",Integer.toString(cdd.type),cdd.id, Chrono.dateTime(cdd.expires));
906                             }
907                         } catch (NoSuchAlgorithmException e) {
908                             trans.error().log(e);
909                         }
910                     } else {
911                         if (expired==null || expired.before(cdd.expires)) {
912                             expired = cdd.expires;
913                             trans.setTag(cdd.tag);
914                         }
915                     }
916                 } // end for each
917                 
918                 if (expired!=null) {
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));
922                 } else {
923                         if (debug==null && alwaysSpecial) {
924                                 debug = new StringBuilder();
925                         }
926                         if (debug!=null) {
927                                 debug.append(trans.env().encryptor().encrypt(new String(cred)));
928                                 rv = Result.err(Status.ERR_Security,String.format("invalid password - %s",debug.toString()));
929                         }
930                 }
931             }
932         } else {
933             return Result.err(result);
934         }
935         return rv == null ? Result.err(Status.ERR_Security, "Wrong credential") : rv;
936     }
937
938
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);
948         debug.append('\n');
949     }
950
951
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();
955         if (cexp<close) {
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");
960         }
961     }
962
963
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);
967             try {
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);
972                 bb.put(cred.cred);
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());
978             } finally {
979                 tt.done();
980             }
981             
982         } else if (cred.type==CredDAO.FQI) {
983                 cred.cred = null;
984                 return Result.ok(cred);
985         }
986         return Result.err(Status.ERR_Security,"invalid/unreadable credential");
987     }
988     
989     public Result<Boolean> userCredCheck(AuthzTrans trans, CredDAO.Data orig, final byte[] raw) {
990             TimeTaken tt = trans.start("CheckCred Cred", Env.SUB);
991             try {
992                 switch(orig.type) {
993                     case CredDAO.BASIC_AUTH_SHA256:
994                         ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + raw.length);
995                         bb.putInt(orig.other);
996                         bb.put(raw);
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);
1000                     case CredDAO.FQI:
1001                     default:
1002                         return Result.ok(false);
1003                 }
1004             } catch (NoSuchAlgorithmException e) {
1005                 return Result.err(Status.ERR_General,e.getLocalizedMessage());
1006             } finally {
1007                 tt.done();
1008             }
1009     }
1010
1011     public static final String APPROVED = "APPROVE";
1012     public static final String REJECT = "REJECT";
1013     public static final String PENDING = "PENDING";
1014
1015     public Result<Void> canAddUser(AuthzTrans trans, UserRoleDAO.Data data,
1016             List<ApprovalDAO.Data> approvals) {
1017         // get the approval policy for the organization
1018
1019         // get the list of approvals with an accept status
1020
1021         // validate the approvals against the policy
1022
1023         // for now check if all approvals are received and return
1024         // SUCCESS/FAILURE/SKIP
1025         boolean bReject = false;
1026         boolean bPending = false;
1027
1028         for (ApprovalDAO.Data approval : approvals) {
1029             if (approval.status.equals(REJECT)) {
1030                 bReject = true;
1031             } else if (approval.status.equals(PENDING)) {
1032                 bPending = true;
1033             }
1034         }
1035         if (bReject) {
1036             return Result.err(Status.ERR_Policy,
1037                     "Approval Polocy not conformed");
1038         }
1039         if (bPending) {
1040             return Result.err(Status.ERR_ActionNotCompleted,
1041                     "Required Approvals not received");
1042         }
1043
1044         return Result.ok();
1045     }
1046
1047     private static final String NO_CACHE_NAME = "No Cache Data named %s";
1048
1049     public Result<Void> clearCache(AuthzTrans trans, String cname) {
1050         boolean all = "all".equals(cname);
1051         Result<Void> rv = null;
1052
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);
1057         }
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);
1062         }
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);
1067         }
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);
1072         }
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);
1077         }
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);
1082         }
1083
1084         if (rv == null) {
1085             rv = Result.err(Status.ERR_BadData, NO_CACHE_NAME, cname);
1086         }
1087         return rv;
1088     }
1089
1090     public Result<Void> cacheClear(AuthzTrans trans, String cname,Integer segment) {
1091         Result<Void> rv;
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);
1104         } else {
1105             rv = Result.err(Status.ERR_BadData, NO_CACHE_NAME, cname);
1106         }
1107         return rv;
1108     }
1109
1110     private int[] series(int max) {
1111         int[] series = new int[max];
1112         for (int i = 0; i < max; ++i)
1113             series[i] = i;
1114         return series;
1115     }
1116
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);
1122         }
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())) {
1127                     return true;
1128                 }
1129             }
1130         }
1131         return false;
1132     }
1133
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) {
1138                 return false;
1139             } else {
1140                 b = specialLog.contains(user);
1141                 trans.put(specialLogSlot, b);
1142             }
1143         }
1144         return b;
1145     }
1146     
1147     public static void logEncryptTrace(AuthzTrans trans, String data) {
1148         long ti;
1149         trans.put(transIDSlot, ti=nextTraceID());
1150         trans.trace().log("id="+Long.toHexString(ti)+",data=\""+trans.env().encryptor().encrypt(data)+'"');
1151     }
1152
1153     private synchronized static long nextTraceID() {
1154         return ++traceID;
1155     }
1156
1157     public static synchronized boolean specialLogOn(AuthzTrans trans, String id) {
1158         if (specialLog == null) {
1159             specialLog = new HashSet<>();
1160         }
1161         boolean rc = specialLog.add(id);
1162         if (rc) {
1163             trans.trace().printf("Trace on for %s requested by %s",id,trans.user());            
1164         }
1165         return rc;
1166     }
1167
1168     public static synchronized boolean specialLogOff(AuthzTrans trans, String id) {
1169         if (specialLog==null) {
1170             return false;
1171         }
1172         boolean rv = specialLog.remove(id);
1173         if (specialLog.isEmpty()) {
1174             specialLog = null;
1175         }
1176         if (rv) {
1177             trans.trace().printf("Trace off for %s requested by %s",id,trans.user());            
1178         }
1179         return rv;
1180     }
1181
1182     /** 
1183      * canMove
1184      * Which Types can be moved
1185      * @param nsType
1186      * @return
1187      */
1188     public boolean canMove(NsType nsType) {
1189         boolean rv;
1190         switch(nsType) {
1191             case DOT:
1192             case ROOT:
1193             case COMPANY:
1194             case UNKNOWN:
1195                 rv = false;
1196                 break;
1197             default:
1198                 rv = true;
1199         }
1200         return rv;
1201     }
1202
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)) {
1209                         return true;
1210                     }
1211                 }
1212         };
1213         return false;
1214     }
1215     
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)) {
1221                 return true;
1222             }
1223         }};
1224         return false;
1225     }
1226
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();
1230         int count = 0;
1231         if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
1232             if (urdd.expires.after(now)) {
1233                 ++count;
1234             }
1235         }};
1236         return count;
1237     }
1238     
1239     /**
1240      * Return a Unique String, (same string, if it is already unique), with only
1241      * lowercase letters, digits and the '.' character.
1242      * 
1243      * @param name
1244      * @return
1245      * @throws IOException 
1246      */
1247     public static String toUnique(String name) throws IOException {
1248         byte[] from = name.getBytes();
1249         StringBuilder sb = new StringBuilder();
1250         byte f;
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));
1255         }
1256         return sb.toString();
1257     }
1258     
1259     public static String fromUnique(String name) throws IOException {
1260         byte[] from = name.getBytes();
1261         StringBuilder sb = new StringBuilder();
1262         char c;
1263         for (int i=0;i<from.length;++i) {
1264             c = (char)((from[i]-0x61)<<4);
1265             c |= (from[++i]-0x61);
1266             sb.append(c);
1267         }
1268         return sb.toString();
1269     }
1270
1271 }