Remove Tabs, per Jococo
[aaf/authz.git] / auth / auth-service / src / main / java / org / onap / aaf / auth / service / AuthzCassServiceImpl.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.service;
23
24 import static org.onap.aaf.auth.env.AuthzTrans.REQD_TYPE.force;
25 import static org.onap.aaf.auth.env.AuthzTrans.REQD_TYPE.future;
26 import static org.onap.aaf.auth.layer.Result.OK;
27 import static org.onap.aaf.auth.rserv.HttpMethods.DELETE;
28 import static org.onap.aaf.auth.rserv.HttpMethods.GET;
29 import static org.onap.aaf.auth.rserv.HttpMethods.POST;
30 import static org.onap.aaf.auth.rserv.HttpMethods.PUT;
31
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.Date;
37 import java.util.GregorianCalendar;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.TreeMap;
44 import java.util.UUID;
45 import java.util.concurrent.TimeUnit;
46
47 import javax.servlet.http.HttpServletRequest;
48
49 import org.onap.aaf.auth.common.Define;
50 import org.onap.aaf.auth.dao.DAOException;
51 import org.onap.aaf.auth.dao.cached.CachedPermDAO;
52 import org.onap.aaf.auth.dao.cached.CachedRoleDAO;
53 import org.onap.aaf.auth.dao.cached.CachedUserRoleDAO;
54 import org.onap.aaf.auth.dao.cass.ApprovalDAO;
55 import org.onap.aaf.auth.dao.cass.CertDAO;
56 import org.onap.aaf.auth.dao.cass.CredDAO;
57 import org.onap.aaf.auth.dao.cass.DelegateDAO;
58 import org.onap.aaf.auth.dao.cass.FutureDAO;
59 import org.onap.aaf.auth.dao.cass.HistoryDAO;
60 import org.onap.aaf.auth.dao.cass.Namespace;
61 import org.onap.aaf.auth.dao.cass.NsDAO;
62 import org.onap.aaf.auth.dao.cass.NsDAO.Data;
63 import org.onap.aaf.auth.dao.cass.NsSplit;
64 import org.onap.aaf.auth.dao.cass.NsType;
65 import org.onap.aaf.auth.dao.cass.PermDAO;
66 import org.onap.aaf.auth.dao.cass.RoleDAO;
67 import org.onap.aaf.auth.dao.cass.Status;
68 import org.onap.aaf.auth.dao.cass.UserRoleDAO;
69 import org.onap.aaf.auth.dao.hl.CassExecutor;
70 import org.onap.aaf.auth.dao.hl.Function;
71 import org.onap.aaf.auth.dao.hl.Function.FUTURE_OP;
72 import org.onap.aaf.auth.dao.hl.Function.Lookup;
73 import org.onap.aaf.auth.dao.hl.Function.OP_STATUS;
74 import org.onap.aaf.auth.dao.hl.PermLookup;
75 import org.onap.aaf.auth.dao.hl.Question;
76 import org.onap.aaf.auth.dao.hl.Question.Access;
77 import org.onap.aaf.auth.env.AuthzTrans;
78 import org.onap.aaf.auth.env.AuthzTrans.REQD_TYPE;
79 import org.onap.aaf.auth.layer.Result;
80 import org.onap.aaf.auth.org.Executor;
81 import org.onap.aaf.auth.org.Organization;
82 import org.onap.aaf.auth.org.Organization.Expiration;
83 import org.onap.aaf.auth.org.Organization.Identity;
84 import org.onap.aaf.auth.org.Organization.Policy;
85 import org.onap.aaf.auth.org.OrganizationException;
86 import org.onap.aaf.auth.rserv.doc.ApiDoc;
87 import org.onap.aaf.auth.service.mapper.Mapper;
88 import org.onap.aaf.auth.service.mapper.Mapper.API;
89 import org.onap.aaf.auth.service.validation.ServiceValidator;
90 import org.onap.aaf.auth.validation.Validator;
91 import org.onap.aaf.cadi.aaf.Defaults;
92 import org.onap.aaf.cadi.principal.BasicPrincipal;
93 import org.onap.aaf.cadi.util.FQI;
94 import org.onap.aaf.misc.env.Env;
95 import org.onap.aaf.misc.env.TimeTaken;
96 import org.onap.aaf.misc.env.util.Chrono;
97 import org.onap.aaf.misc.env.util.Split;
98
99 import aaf.v2_0.CredRequest;
100
101 /**
102  * AuthzCassServiceImpl implements AuthzCassService for 
103  * 
104  * @author Jonathan
105  *
106  * @param <NSS>
107  * @param <PERMS>
108  * @param <PERMKEY>
109  * @param <ROLES>
110  * @param <USERS>
111  * @param <DELGS>
112  * @param <REQUEST>
113  * @param <HISTORY>
114  * @param <ERR>
115  * @param <APPROVALS>
116  */
117 public class AuthzCassServiceImpl    <NSS,PERMS,PERMKEY,ROLES,USERS,USERROLES,DELGS,CERTS,KEYS,REQUEST,HISTORY,ERR,APPROVALS>
118     implements AuthzService            <NSS,PERMS,PERMKEY,ROLES,USERS,USERROLES,DELGS,CERTS,KEYS,REQUEST,HISTORY,ERR,APPROVALS> {
119     
120     private static final String TWO_SPACE = "  ";
121     private Mapper                    <NSS,PERMS,PERMKEY,ROLES,USERS,USERROLES,DELGS,CERTS,KEYS,REQUEST,HISTORY,ERR,APPROVALS> mapper;
122     @Override
123     public Mapper                    <NSS,PERMS,PERMKEY,ROLES,USERS,USERROLES,DELGS,CERTS,KEYS,REQUEST,HISTORY,ERR,APPROVALS> mapper() {return mapper;}
124     
125     private static final String ASTERIX = "*";
126     private static final String CACHE = "cache";
127     private static final String ROOT_NS = Define.ROOT_NS();
128     private static final String ROOT_COMPANY = Define.ROOT_COMPANY();
129
130     private final Question ques;
131     private final Function func;
132     
133     public AuthzCassServiceImpl(AuthzTrans trans, Mapper<NSS,PERMS,PERMKEY,ROLES,USERS,USERROLES,DELGS,CERTS,KEYS,REQUEST,HISTORY,ERR,APPROVALS> mapper,Question question) {
134         this.ques = question;
135         func = new Function(trans, question);
136         this.mapper = mapper;
137         
138     }
139
140 /***********************************
141  * NAMESPACE 
142  ***********************************/
143     /**
144      * createNS
145      * @throws DAOException 
146      * @see org.onap.aaf.auth.service.AuthzService#createNS(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String, java.lang.String)
147      */
148     @ApiDoc( 
149             method = POST,  
150             path = "/authz/ns",
151             params = {},
152             expectedCode = 201,
153             errorCodes = { 403,404,406,409 }, 
154             text = { "Namespace consists of: ",
155                     "<ul><li>name - What you want to call this Namespace</li>",
156                     "<li>responsible(s) - Person(s) who receive Notifications and approves Requests ",
157                     "regarding this Namespace. Companies have Policies as to who may take on ",
158                     "this Responsibility. Separate multiple identities with commas</li>",
159                     "<li>admin(s) - Person(s) who are allowed to make changes on the namespace, ",
160                     "including creating Roles, Permissions and Credentials. Separate multiple ",
161                     "identities with commas</li></ul>",
162                     "Note: Namespaces are dot-delimited (i.e. com.myCompany.myApp) and must be ",
163                     "created with parent credentials (i.e. To create com.myCompany.myApp, you must ",
164                     "be an admin of com.myCompany or com"
165                     }
166             )
167     @Override
168     public Result<Void> createNS(final AuthzTrans trans, REQUEST from, NsType type) {
169         final Result<Namespace> rnamespace = mapper.ns(trans, from);
170         final ServiceValidator v = new ServiceValidator();
171         if (v.ns(rnamespace).err()) { 
172             return Result.err(Status.ERR_BadData,v.errs());
173         }
174         final Namespace namespace = rnamespace.value;
175         final Result<NsDAO.Data> parentNs = ques.deriveNs(trans,namespace.name);
176         if (parentNs.notOK()) {
177             return Result.err(parentNs);
178         }
179         
180         // Note: Data validate occurs in func.createNS
181         if (namespace.name.lastIndexOf('.')<0) { // Root Namespace... Function will check if allowed
182             return func.createNS(trans, namespace, false);
183         }
184         
185         Result<FutureDAO.Data> fd = mapper.future(trans, NsDAO.TABLE,from,namespace,true, 
186                 new Mapper.Memo() {
187                     @Override
188                     public String get() {
189                         return "Create Namespace [" + namespace.name + ']';
190                     }
191                 },
192                 new MayChange() {
193                     private Result<NsDAO.Data> rnd;
194                     @Override
195                     public Result<?> mayChange() {
196                         if (rnd==null) {
197                             rnd = ques.mayUser(trans, trans.user(), parentNs.value,Access.write);
198                         }
199                         return rnd;
200                     }
201                 });
202             switch(fd.status) {
203                 case OK:
204                     Result<String> rfc = func.createFuture(trans, fd.value, namespace.name, trans.user(),parentNs.value, FUTURE_OP.C);
205                     if (rfc.isOK()) {
206                         return Result.err(Status.ACC_Future, "NS [%s] is saved for future processing",namespace.name);
207                     } else { 
208                         return Result.err(rfc);
209                     }
210                 case Status.ACC_Now:
211                     return func.createNS(trans, namespace, false);
212                 default:
213                     return Result.err(fd);
214             }
215     }
216     
217     @ApiDoc(
218             method = POST,  
219             path = "/authz/ns/:ns/admin/:id",
220             params = {     "ns|string|true",
221                         "id|string|true" 
222                     },
223             expectedCode = 201,
224             errorCodes = { 403,404,406,409 }, 
225             text = {     "Add an Identity :id to the list of Admins for the Namespace :ns", 
226                         "Note: :id must be fully qualified (i.e. ab1234@people.osaaf.org)" }
227             )
228     @Override
229     public Result<Void> addAdminNS(AuthzTrans trans, String ns, String id) {
230         return func.addUserRole(trans, id, ns,Question.ADMIN);
231     }
232
233     @ApiDoc(
234             method = DELETE,  
235             path = "/authz/ns/:ns/admin/:id",
236             params = {     "ns|string|true",
237                         "id|string|true" 
238                     },
239             expectedCode = 200,
240             errorCodes = { 403,404 }, 
241             text = {     "Remove an Identity :id from the list of Admins for the Namespace :ns",
242                         "Note: :id must be fully qualified (i.e. ab1234@people.osaaf.org)" }
243             )
244     @Override
245     public Result<Void> delAdminNS(AuthzTrans trans, String ns, String id) {
246         return func.delAdmin(trans,ns,id);
247     }
248
249     @ApiDoc(
250             method = POST,  
251             path = "/authz/ns/:ns/responsible/:id",
252             params = {     "ns|string|true",
253                         "id|string|true" 
254                     },
255             expectedCode = 201,
256             errorCodes = { 403,404,406,409 }, 
257             text = {     "Add an Identity :id to the list of Responsibles for the Namespace :ns",
258                         "Note: :id must be fully qualified (i.e. ab1234@people.osaaf.org)" }
259             )
260     @Override
261     public Result<Void> addResponsibleNS(AuthzTrans trans, String ns, String id) {
262         return func.addUserRole(trans,id,ns,Question.OWNER);
263     }
264
265     @ApiDoc(
266             method = DELETE,  
267             path = "/authz/ns/:ns/responsible/:id",
268             params = {     "ns|string|true",
269                         "id|string|true" 
270                     },
271             expectedCode = 200,
272             errorCodes = { 403,404 }, 
273             text = {     "Remove an Identity :id to the list of Responsibles for the Namespace :ns",
274                         "Note: :id must be fully qualified (i.e. ab1234@people.osaaf.org)",
275                         "Note: A namespace must have at least 1 responsible party"
276                     }
277             )
278     @Override
279     public Result<Void> delResponsibleNS(AuthzTrans trans, String ns, String id) {
280         return func.delOwner(trans,ns,id);
281     }
282
283     /* (non-Javadoc)
284      * @see org.onap.aaf.auth.service.AuthzService#applyModel(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.Object)
285      */
286     @ApiDoc(
287             method = POST,  
288             path = "/authz/ns/:ns/attrib/:key/:value",
289             params = {     "ns|string|true",
290                         "key|string|true",
291                         "value|string|true"},
292             expectedCode = 201,
293             errorCodes = { 403,404,406,409 },  
294             text = {     
295                 "Create an attribute in the Namespace",
296                 "You must be given direct permission for key by AAF"
297                 }
298             )
299     @Override
300     public Result<Void> createNsAttrib(AuthzTrans trans, String ns, String key, String value) {
301         TimeTaken tt = trans.start("Create NsAttrib " + ns + ':' + key + ':' + value, Env.SUB);
302         try {
303             // Check inputs
304             final Validator v = new ServiceValidator();
305             if (v.ns(ns).err() ||
306                v.key(key).err() ||
307                v.value(value).err()) {
308                 return Result.err(Status.ERR_BadData,v.errs());
309             }
310
311             // Check if exists already
312             Result<List<Data>> rlnsd = ques.nsDAO().read(trans, ns);
313             if (rlnsd.notOKorIsEmpty()) {
314                 return Result.err(rlnsd);
315             }
316             NsDAO.Data nsd = rlnsd.value.get(0);
317
318             // Check for Existence
319             if (nsd.attrib.get(key)!=null) {
320                 return Result.err(Status.ERR_ConflictAlreadyExists, "NS Property %s:%s exists", ns, key);
321             }
322             
323             // Check if User may put
324             if (!ques.isGranted(trans, trans.user(), ROOT_NS, Question.ATTRIB, 
325                     ":"+trans.org().getDomain()+".*:"+key, Access.write.name())) {
326                 return Result.err(Status.ERR_Denied, "%s may not create NS Attrib [%s:%s]", trans.user(),ns, key);
327             }
328
329             // Add Attrib
330             nsd.attrib.put(key, value);
331             ques.nsDAO().dao().attribAdd(trans,ns,key,value);
332             ques.nsDAO().invalidate(trans, nsd);
333             return Result.ok();
334         } finally {
335             tt.done();
336         }
337     }
338     
339     @ApiDoc(
340             method = GET,  
341             path = "/authz/ns/attrib/:key",
342             params = {     "key|string|true" },
343             expectedCode = 200,
344             errorCodes = { 403,404 },  
345             text = {     
346                 "Read Attributes for Namespace"
347                 }
348             )
349     @Override
350     public Result<KEYS> readNsByAttrib(AuthzTrans trans, String key) {
351         // Check inputs
352         final Validator v = new ServiceValidator();
353         if (v.nullOrBlank("Key",key).err()) {
354               return Result.err(Status.ERR_BadData,v.errs());
355         }
356
357         // May Read
358         if (!ques.isGranted(trans, trans.user(), ROOT_NS, Question.ATTRIB, 
359                     ":"+trans.org().getDomain()+".*:"+key, Question.READ)) {
360             return Result.err(Status.ERR_Denied,"%s may not read NS by Attrib '%s'",trans.user(),key);
361         }
362
363         Result<Set<String>> rsd = ques.nsDAO().dao().readNsByAttrib(trans, key);
364         if (rsd.notOK()) {
365             return Result.err(rsd);
366         }
367         return mapper().keys(rsd.value);
368     }
369
370
371     @ApiDoc(
372             method = PUT,  
373             path = "/authz/ns/:ns/attrib/:key/:value",
374             params = {     "ns|string|true",
375                         "key|string|true"},
376             expectedCode = 200,
377             errorCodes = { 403,404 },  
378             text = {     
379                 "Update Value on an existing attribute in the Namespace",
380                 "You must be given direct permission for key by AAF"
381                 }
382             )
383     @Override
384     public Result<?> updateNsAttrib(AuthzTrans trans, String ns, String key, String value) {
385         TimeTaken tt = trans.start("Update NsAttrib " + ns + ':' + key + ':' + value, Env.SUB);
386         try {
387             // Check inputs
388             final Validator v = new ServiceValidator();
389             if (v.ns(ns).err() ||
390                v.key(key).err() ||
391                v.value(value).err()) {
392                 return Result.err(Status.ERR_BadData,v.errs());
393             }
394
395             // Check if exists already (NS must exist)
396             Result<List<Data>> rlnsd = ques.nsDAO().read(trans, ns);
397             if (rlnsd.notOKorIsEmpty()) {
398                 return Result.err(rlnsd);
399             }
400             NsDAO.Data nsd = rlnsd.value.get(0);
401
402             // Check for Existence
403             if (nsd.attrib.get(key)==null) {
404                 return Result.err(Status.ERR_NotFound, "NS Property %s:%s exists", ns, key);
405             }
406             
407             // Check if User may put
408             if (!ques.isGranted(trans, trans.user(), ROOT_NS, Question.ATTRIB, 
409                     ":"+trans.org().getDomain()+".*:"+key, Access.write.name())) {
410                 return Result.err(Status.ERR_Denied, "%s may not create NS Attrib [%s:%s]", trans.user(),ns, key);
411             }
412
413             // Add Attrib
414             nsd.attrib.put(key, value);
415             ques.nsDAO().invalidate(trans, nsd);
416             return ques.nsDAO().update(trans,nsd);
417  
418         } finally {
419             tt.done();
420         }
421     }
422
423     @ApiDoc(
424             method = DELETE,  
425             path = "/authz/ns/:ns/attrib/:key",
426             params = {     "ns|string|true",
427                         "key|string|true"},
428             expectedCode = 200,
429             errorCodes = { 403,404 },  
430             text = {     
431                 "Delete an attribute in the Namespace",
432                 "You must be given direct permission for key by AAF"
433                 }
434             )
435     @Override
436     public Result<Void> deleteNsAttrib(AuthzTrans trans, String ns, String key) {
437         TimeTaken tt = trans.start("Delete NsAttrib " + ns + ':' + key, Env.SUB);
438         try {
439             // Check inputs
440             final Validator v = new ServiceValidator();
441             if (v.nullOrBlank("NS",ns).err() ||
442                v.nullOrBlank("Key",key).err()) {
443                 return Result.err(Status.ERR_BadData,v.errs());
444             }
445
446             // Check if exists already
447             Result<List<Data>> rlnsd = ques.nsDAO().read(trans, ns);
448             if (rlnsd.notOKorIsEmpty()) {
449                 return Result.err(rlnsd);
450             }
451             NsDAO.Data nsd = rlnsd.value.get(0);
452
453             // Check for Existence
454             if (nsd.attrib.get(key)==null) {
455                 return Result.err(Status.ERR_NotFound, "NS Property [%s:%s] does not exist", ns, key);
456             }
457             
458             // Check if User may del
459             if (!ques.isGranted(trans, trans.user(), ROOT_NS, "attrib", ":" + ROOT_COMPANY + ".*:"+key, Access.write.name())) {
460                 return Result.err(Status.ERR_Denied, "%s may not delete NS Attrib [%s:%s]", trans.user(),ns, key);
461             }
462
463             // Add Attrib
464             nsd.attrib.remove(key);
465             ques.nsDAO().dao().attribRemove(trans,ns,key);
466             ques.nsDAO().invalidate(trans, nsd);
467             return Result.ok();
468         } finally {
469             tt.done();
470         }
471     }
472
473     @ApiDoc(
474             method = GET,  
475             path = "/authz/nss/:id",
476             params = {     "id|string|true" },
477             expectedCode = 200,
478             errorCodes = { 404,406 }, 
479             text = {     
480                 "Lists the Owner(s), Admin(s), Description, and Attributes of Namespace :id",
481             }
482             )
483     @Override
484     public Result<NSS> getNSbyName(AuthzTrans trans, String ns, boolean includeExpired) {
485         final Validator v = new ServiceValidator();
486         if (v.nullOrBlank("NS", ns).err()) {
487             return Result.err(Status.ERR_BadData,v.errs());
488         }
489         
490         Result<List<NsDAO.Data>> rlnd = ques.nsDAO().read(trans, ns);
491         if (rlnd.isOK()) {
492             if (rlnd.isEmpty()) {
493                 return Result.err(Status.ERR_NotFound, "No data found for %s",ns);
494             }
495             Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), rlnd.value.get(0), Access.read);
496             if (rnd.notOK()) {
497                 return Result.err(rnd); 
498             }
499             
500             
501             Namespace namespace = new Namespace(rnd.value);
502             Result<List<String>> rd = func.getOwners(trans, namespace.name, includeExpired);
503             if (rd.isOK()) {
504                 namespace.owner = rd.value;
505             }
506             rd = func.getAdmins(trans, namespace.name, includeExpired);
507             if (rd.isOK()) {
508                 namespace.admin = rd.value;
509             }
510             
511             NSS nss = mapper.newInstance(API.NSS);
512             return mapper.nss(trans, namespace, nss);
513         } else {
514             return Result.err(rlnd);
515         }
516     }
517
518     @ApiDoc(
519             method = GET,  
520             path = "/authz/nss/admin/:id",
521             params = {     "id|string|true" },
522             expectedCode = 200,
523             errorCodes = { 403,404 }, 
524             text = {     "Lists all Namespaces where Identity :id is an Admin", 
525                         "Note: :id must be fully qualified (i.e. ab1234@people.osaaf.org)" 
526                     }
527             )
528     @Override
529     public Result<NSS> getNSbyAdmin(AuthzTrans trans, String user, boolean full) {
530         final Validator v = new ServiceValidator();
531         if (v.nullOrBlank("User", user).err()) {
532             return Result.err(Status.ERR_BadData, v.errs());
533         }
534         
535         Result<Collection<Namespace>> rn = loadNamepace(trans, user, ".admin", full);
536         if (rn.notOK()) {
537             return Result.err(rn);
538         }
539         if (rn.isEmpty()) {
540             return Result.err(Status.ERR_NotFound, "[%s] is not an admin for any namespaces",user);        
541         }
542         NSS nss = mapper.newInstance(API.NSS);
543         // Note: "loadNamespace" already validates view of Namespace
544         return mapper.nss(trans, rn.value, nss);
545     }
546
547     @ApiDoc(
548             method = GET,  
549             path = "/authz/nss/either/:id",
550             params = {     "id|string|true" },
551             expectedCode = 200,
552             errorCodes = { 403,404 }, 
553             text = {     "Lists all Namespaces where Identity :id is either an Admin or an Owner", 
554                         "Note: :id must be fully qualified (i.e. ab1234@people.osaaf.org)" 
555                     }
556             )
557     @Override
558     public Result<NSS> getNSbyEither(AuthzTrans trans, String user, boolean full) {
559         final Validator v = new ServiceValidator();
560         if (v.nullOrBlank("User", user).err()) {
561             return Result.err(Status.ERR_BadData, v.errs());
562         }
563         
564         Result<Collection<Namespace>> rn = loadNamepace(trans, user, null, full);
565         if (rn.notOK()) {
566             return Result.err(rn);
567         }
568         if (rn.isEmpty()) {
569             return Result.err(Status.ERR_NotFound, "[%s] is not an admin or owner for any namespaces",user);        
570         }
571         NSS nss = mapper.newInstance(API.NSS);
572         // Note: "loadNamespace" already validates view of Namespace
573         return mapper.nss(trans, rn.value, nss);
574     }
575
576     private Result<Collection<Namespace>> loadNamepace(AuthzTrans trans, String user, String endsWith, boolean full) {
577         Result<List<UserRoleDAO.Data>> urd = ques.userRoleDAO().readByUser(trans, user);
578         if (urd.notOKorIsEmpty()) {
579             return Result.err(urd);
580         }
581         Map<String, Namespace> lm = new HashMap<>();
582         Map<String, Namespace> other = full || endsWith==null?null:new TreeMap<>();
583         for (UserRoleDAO.Data urdd : urd.value) {
584             if (full) {
585                 if (endsWith==null || urdd.role.endsWith(endsWith)) {
586                     RoleDAO.Data rd = RoleDAO.Data.decode(urdd);
587                     Result<NsDAO.Data> nsd = ques.mayUser(trans, user, rd, Access.read);
588                     if (nsd.isOK()) {
589                         Namespace namespace = lm.get(nsd.value.name);
590                         if (namespace==null) {
591                             namespace = new Namespace(nsd.value);
592                             lm.put(namespace.name,namespace);
593                         }
594                         Result<List<String>> rls = func.getAdmins(trans, namespace.name, false);
595                         if (rls.isOK()) {
596                             namespace.admin=rls.value;
597                         }
598                         
599                         rls = func.getOwners(trans, namespace.name, false);
600                         if (rls.isOK()) {
601                             namespace.owner=rls.value;
602                         }
603                     }
604                 }
605             } else { // Shortened version.  Only Namespace Info available from Role.
606                 if (Question.ADMIN.equals(urdd.rname) || Question.OWNER.equals(urdd.rname)) {
607                     RoleDAO.Data rd = RoleDAO.Data.decode(urdd);
608                     Result<NsDAO.Data> nsd = ques.mayUser(trans, user, rd, Access.read);
609                     if (nsd.isOK()) {
610                         Namespace namespace = lm.get(nsd.value.name);
611                         if (namespace==null) {
612                             if (other!=null) {
613                                 namespace = other.remove(nsd.value.name);
614                             }
615                             if (namespace==null) {
616                                 namespace = new Namespace(nsd.value);
617                                 namespace.admin=new ArrayList<>();
618                                 namespace.owner=new ArrayList<>();
619                             }
620                             if (endsWith==null || urdd.role.endsWith(endsWith)) {
621                                 lm.put(namespace.name,namespace);
622                             } else { 
623                                 other.put(namespace.name,namespace);
624                             }
625                         }
626                         if (Question.OWNER.equals(urdd.rname)) {
627                             namespace.owner.add(urdd.user);
628                         } else {
629                             namespace.admin.add(urdd.user);
630                         }
631                     }
632                 }
633             }
634         }
635         return Result.ok(lm.values());
636     }
637
638     @ApiDoc(
639             method = GET,  
640             path = "/authz/nss/responsible/:id",
641             params = {     "id|string|true" },
642             expectedCode = 200,
643             errorCodes = { 403,404 }, 
644             text = {     "Lists all Namespaces where Identity :id is a Responsible Party", 
645                         "Note: :id must be fully qualified (i.e. ab1234@people.osaaf.org)"
646                     }
647             )
648     @Override
649     public Result<NSS> getNSbyResponsible(AuthzTrans trans, String user, boolean full) {
650         final Validator v = new ServiceValidator();
651         if (v.nullOrBlank("User", user).err()) {
652             return Result.err(Status.ERR_BadData, v.errs());
653         }
654         Result<Collection<Namespace>> rn = loadNamepace(trans, user, ".owner",full);
655         if (rn.notOK()) {
656             return Result.err(rn);
657         }
658         if (rn.isEmpty()) {
659             return Result.err(Status.ERR_NotFound, "[%s] is not an owner for any namespaces",user);        
660         }
661         NSS nss = mapper.newInstance(API.NSS);
662         // Note: "loadNamespace" prevalidates
663         return mapper.nss(trans, rn.value, nss);
664     }
665     
666     @ApiDoc(
667             method = GET,  
668             path = "/authz/nss/children/:id",
669             params = {     "id|string|true" },
670             expectedCode = 200,
671             errorCodes = { 403,404 }, 
672             text = {     "Lists all Child Namespaces of Namespace :id", 
673                         "Note: This is not a cached read"
674                     }
675             )
676     @Override
677     public Result<NSS> getNSsChildren(AuthzTrans trans, String parent) {
678         final Validator v = new ServiceValidator();
679         if (v.nullOrBlank("NS", parent).err())  {
680             return Result.err(Status.ERR_BadData,v.errs());
681         }
682         
683         Result<NsDAO.Data> rnd = ques.deriveNs(trans, parent);
684         if (rnd.notOK()) {
685             return Result.err(rnd);
686         }
687         rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
688         if (rnd.notOK()) {
689             return Result.err(rnd); 
690         }
691
692         Set<Namespace> lm = new HashSet<>();
693         Result<List<NsDAO.Data>> rlnd = ques.nsDAO().dao().getChildren(trans, parent);
694         if (rlnd.isOK()) {
695             if (rlnd.isEmpty()) {
696                 return Result.err(Status.ERR_NotFound, "No data found for %s",parent);
697             }
698             for (NsDAO.Data ndd : rlnd.value) {
699                 Namespace namespace = new Namespace(ndd);
700                 Result<List<String>> rls = func.getAdmins(trans, namespace.name, false);
701                 if (rls.isOK()) {
702                     namespace.admin=rls.value;
703                 }
704                 
705                 rls = func.getOwners(trans, namespace.name, false);
706                 if (rls.isOK()) {
707                     namespace.owner=rls.value;
708                 }
709
710                 lm.add(namespace);
711             }
712             NSS nss = mapper.newInstance(API.NSS);
713             return mapper.nss(trans,lm, nss);
714         } else {
715             return Result.err(rlnd);
716         }
717     }
718
719
720     @ApiDoc(
721             method = PUT,  
722             path = "/authz/ns",
723             params = {},
724             expectedCode = 200,
725             errorCodes = { 403,404,406 }, 
726             text = { "Replace the Current Description of a Namespace with a new one"
727                     }
728             )
729     @Override
730     public Result<Void> updateNsDescription(AuthzTrans trans, REQUEST from) {
731         final Result<Namespace> nsd = mapper.ns(trans, from);
732         final ServiceValidator v = new ServiceValidator();
733         if (v.ns(nsd).err()) {
734             return Result.err(Status.ERR_BadData,v.errs());
735         }
736         if (v.nullOrBlank("description", nsd.value.description).err()) {
737             return Result.err(Status.ERR_BadData,v.errs());
738         }
739
740         Namespace namespace = nsd.value;
741         Result<List<NsDAO.Data>> rlnd = ques.nsDAO().read(trans, namespace.name);
742         
743         if (rlnd.notOKorIsEmpty()) {
744             return Result.err(Status.ERR_NotFound, "Namespace [%s] does not exist",namespace.name);
745         }
746         
747         if (ques.mayUser(trans, trans.user(), rlnd.value.get(0), Access.write).notOK()) {
748             return Result.err(Status.ERR_Denied, "You do not have approval to change %s",namespace.name);
749         }
750
751         Result<Void> rdr = ques.nsDAO().dao().addDescription(trans, namespace.name, namespace.description);
752         if (rdr.isOK()) {
753             return Result.ok();
754         } else {
755             return Result.err(rdr);
756         }
757     }
758     
759     /**
760      * deleteNS
761      * @throws DAOException 
762      * @see org.onap.aaf.auth.service.AuthzService#deleteNS(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String, java.lang.String)
763      */
764     @ApiDoc(
765             method = DELETE,  
766             path = "/authz/ns/:ns",
767             params = {     "ns|string|true" },
768             expectedCode = 200,
769             errorCodes = { 403,404,424 }, 
770             text = {     "Delete the Namespace :ns. Namespaces cannot normally be deleted when there ",
771                         "are still credentials associated with them, but they can be deleted by setting ",
772                         "the \"force\" property. To do this: Add 'force=true' as a query parameter",
773                         "<p>WARNING: Using force will delete all credentials attached to this namespace. Use with care.</p>"
774                         + "if the \"force\" property is set to 'force=move', then Permissions and Roles are not deleted,"
775                         + "but are retained, and assigned to the Parent Namespace.  'force=move' is not permitted "
776                         + "at or below Application Scope"
777                         }
778             )
779     @Override
780     public Result<Void> deleteNS(AuthzTrans trans, String ns) {
781         return func.deleteNS(trans, ns);
782     }
783
784
785 /***********************************
786  * PERM 
787  ***********************************/
788
789     /*
790      * (non-Javadoc)
791      * @see org.onap.aaf.auth.service.AuthzService#createOrUpdatePerm(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.Object, boolean, java.lang.String, java.lang.String, java.lang.String, java.util.List, java.util.List)
792      */
793     @ApiDoc( 
794             method = POST,  
795             path = "/authz/perm",
796             params = {},
797             expectedCode = 201,
798             errorCodes = {403,404,406,409}, 
799             text = { "Permission consists of:",
800                      "<ul><li>type - a Namespace qualified identifier specifying what kind of resource "
801                      + "is being protected</li>",
802                      "<li>instance - a key, possibly multi-dimensional, that identifies a specific "
803                      + " instance of the type</li>",
804                      "<li>action - what kind of action is allowed</li></ul>",
805                      "Note: instance and action can be an *"
806                      }
807             )
808     @Override
809     public Result<Void> createPerm(final AuthzTrans trans,REQUEST rreq) {        
810         final Result<PermDAO.Data> newPd = mapper.perm(trans, rreq);
811
812         final ServiceValidator v = new ServiceValidator();
813         if (v.perm(newPd).err()) {
814             return Result.err(Status.ERR_BadData,v.errs());
815         }
816
817         // User Permission mechanism
818         if(newPd.value.ns.indexOf('@')>0) {
819             PermDAO.Data pdd = newPd.value;
820             if(trans.user().equals(newPd.value.ns)) {
821                 CachedPermDAO permDAO = ques.permDAO();
822                 Result<List<PermDAO.Data>> rlpdd = permDAO.read(trans, pdd);
823                 if(rlpdd.notOK()) {
824                     return Result.err(rlpdd);
825                 }
826                 if(!rlpdd.isEmpty()) {
827                     return Result.err(Result.ERR_ConflictAlreadyExists,"Permission already exists"); 
828                 }
829
830                 RoleDAO.Data rdd = new RoleDAO.Data();
831                 rdd.ns = pdd.ns;
832                 rdd.name = "user";
833
834                 pdd.roles(true).add(rdd.fullName());
835                 Result<PermDAO.Data> rpdd = permDAO.create(trans, pdd);
836                 if(rpdd.notOK()) {
837                     return Result.err(rpdd);
838                 }
839                 
840                 CachedRoleDAO roleDAO = ques.roleDAO();
841                 Result<List<RoleDAO.Data>> rlrdd = roleDAO.read(trans, rdd);
842                 if(rlrdd.notOK()) {
843                     return Result.err(rlrdd);
844                 } else {
845                     if(!rlrdd.isEmpty()) {
846                         rdd = rlrdd.value.get(0);
847                     }
848                 }
849                 
850                 String eperm = pdd.encode();
851                 rdd.perms(true).add(eperm);
852                 Result<Void> rv = roleDAO.update(trans, rdd);
853                 if(rv.notOK()) {
854                     return rv;
855                 }
856                  
857                 CachedUserRoleDAO urDAO = ques.userRoleDAO();
858                 UserRoleDAO.Data urdd = new UserRoleDAO.Data();
859                 urdd.user = trans.user();
860                 urdd.ns = rdd.ns;
861                 urdd.rname = rdd.name;
862                 urdd.role = rdd.fullName();
863                 Result<List<UserRoleDAO.Data>> rlurdd = urDAO.read(trans, urdd);
864                 if(rlurdd.notOK()) {
865                     return Result.err(rlrdd);
866                 } else if(rlurdd.isEmpty()) {
867                     GregorianCalendar gc = trans.org().expiration(null, Expiration.UserInRole);
868                     if(gc==null) {
869                         return Result.err(Result.ERR_Policy,"Organzation does not grant Expiration for UserRole");
870                     } else {
871                         urdd.expires = gc.getTime();
872                     }
873                     Result<UserRoleDAO.Data> rurdd = urDAO.create(trans, urdd);
874                     return Result.err(rurdd);
875                 }
876                 return rv;
877             } else {
878                 return Result.err(Result.ERR_Security,"Only the User can create User Permissions");
879             }
880         } else {
881             // Does Perm Type exist as a Namespace?
882             if(newPd.value.type.isEmpty() || ques.nsDAO().read(trans, newPd.value.fullType()).isOKhasData()) {
883                 return Result.err(Status.ERR_ConflictAlreadyExists,
884                         "Permission Type exists as a Namespace");
885             }
886             
887             Result<FutureDAO.Data> fd = mapper.future(trans, PermDAO.TABLE, rreq, newPd.value,false,
888                 new Mapper.Memo() {
889                     @Override
890                     public String get() {
891                         return "Create Permission [" + 
892                             newPd.value.fullType() + '|' + 
893                             newPd.value.instance + '|' + 
894                             newPd.value.action + ']';
895                     }
896                 },
897                 new MayChange() {
898                     private Result<NsDAO.Data> nsd;
899                     @Override
900                     public Result<?> mayChange() {
901                         if (nsd==null) {
902                             nsd = ques.mayUser(trans, trans.user(), newPd.value, Access.write);
903                         }
904                         return nsd;
905                     }
906                 });
907             
908             Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, newPd.value.ns);
909             if (nsr.notOKorIsEmpty()) {
910                 return Result.err(nsr);
911             }
912             switch(fd.status) {
913                 case OK:
914                     Result<String> rfc = func.createFuture(trans,fd.value, 
915                             newPd.value.fullType() + '|' + newPd.value.instance + '|' + newPd.value.action,
916                             trans.user(),
917                             nsr.value.get(0),
918                             FUTURE_OP.C);
919                     if (rfc.isOK()) {
920                         return Result.err(Status.ACC_Future, "Perm [%s.%s|%s|%s] is saved for future processing",
921                                 newPd.value.ns,
922                                 newPd.value.type,
923                                 newPd.value.instance,
924                                 newPd.value.action);
925                     } else {
926                         return Result.err(rfc);
927                     }
928                 case Status.ACC_Now:
929                     return func.createPerm(trans, newPd.value, true);
930                 default:
931                     return Result.err(fd);
932             }
933         }
934     }
935
936     @ApiDoc( 
937             method = GET,  
938             path = "/authz/perms/:type",
939             params = {"type|string|true"},
940             expectedCode = 200,
941             errorCodes = { 404,406 }, 
942             text = { "List All Permissions that match the :type element of the key" }
943             )
944     @Override
945     public Result<PERMS> getPermsByType(AuthzTrans trans, final String permType) {
946         final Validator v = new ServiceValidator();
947         if (v.nullOrBlank("PermType", permType).err()) {
948             return Result.err(Status.ERR_BadData,v.errs());
949         }
950
951         Result<List<PermDAO.Data>> rlpd = ques.getPermsByType(trans, permType);
952         if (rlpd.notOK()) {
953             return Result.err(rlpd);
954         }
955
956 //        We don't have instance & action for mayUserView... do we want to loop through all returned here as well as in mapper?
957 //        Result<NsDAO.Data> r;
958 //        if ((r = ques.mayUserViewPerm(trans, trans.user(), permType)).notOK())return Result.err(r);
959         
960         PERMS perms = mapper.newInstance(API.PERMS);
961         if (!rlpd.isEmpty()) {
962             // Note: Mapper will restrict what can be viewed
963             return mapper.perms(trans, rlpd.value, perms, true);
964         }
965         return Result.ok(perms);
966     }
967     
968     @ApiDoc( 
969             method = GET,  
970             path = "/authz/perms/:type/:instance/:action",
971             params = {"type|string|true",
972                       "instance|string|true",
973                       "action|string|true"},
974             expectedCode = 200,
975             errorCodes = { 404,406 }, 
976             text = { "List Permissions that match key; :type, :instance and :action" }
977             )
978     @Override
979     public Result<PERMS> getPermsByName(AuthzTrans trans, String type, String instance, String action) {
980         final Validator v = new ServiceValidator();
981         if (v.nullOrBlank("PermType", type).err()
982                 || v.nullOrBlank("PermInstance", instance).err()
983                 || v.nullOrBlank("PermAction", action).err()) {
984             return Result.err(Status.ERR_BadData,v.errs());
985         }
986         
987         Result<List<PermDAO.Data>> rlpd = ques.getPermsByName(trans, type, instance, action);
988         if (rlpd.notOK()) {
989             return Result.err(rlpd);
990         }
991
992         PERMS perms = mapper.newInstance(API.PERMS);
993         if (!rlpd.isEmpty()) {
994             // Note: Mapper will restrict what can be viewed
995             return mapper.perms(trans, rlpd.value, perms, true);
996         }
997         return Result.ok(perms);
998     }
999
1000     @ApiDoc( 
1001             method = GET,  
1002             path = "/authz/perms/user/:user",
1003             params = {"user|string|true"},
1004             expectedCode = 200,
1005             errorCodes = { 404,406 }, 
1006             text = { "List All Permissions that match user :user",
1007                      "<p>'user' must be expressed as full identity (ex: id@full.domain.com)</p>"}
1008             )
1009     @Override
1010     public Result<PERMS> getPermsByUser(AuthzTrans trans, String user) {
1011         final Validator v = new ServiceValidator();
1012         if (v.nullOrBlank("User", user).err()) {
1013             return Result.err(Status.ERR_BadData,v.errs());
1014         }
1015
1016         PermLookup pl = PermLookup.get(trans,ques,user);
1017         Result<List<PermDAO.Data>> rlpd = pl.getPerms(trans.requested(force));
1018         if (rlpd.notOK()) {
1019             return Result.err(rlpd);
1020         }
1021         
1022         PERMS perms = mapper.newInstance(API.PERMS);
1023         
1024         if (rlpd.isEmpty()) {
1025             return Result.ok(perms);
1026         }
1027         // Note: Mapper will restrict what can be viewed
1028         //   if user is the same as that which is looked up, no filtering is required
1029         return mapper.perms(trans, rlpd.value, 
1030                 perms, 
1031                 !user.equals(trans.user()));
1032     }
1033
1034     @ApiDoc( 
1035             method = GET,  
1036             path = "/authz/perms/user/:user/scope/:scope",
1037             params = {"user|string|true","scope|string|true"},
1038             expectedCode = 200,
1039             errorCodes = { 404,406 }, 
1040             text = { "List All Permissions that match user :user, filtered by NS (Scope)",
1041                      "<p>'user' must be expressed as full identity (ex: id@full.domain.com)</p>",
1042                      "<p>'scope' must be expressed as NSs separated by ':'</p>"
1043                     }
1044             )
1045     @Override
1046     public Result<PERMS> getPermsByUserScope(AuthzTrans trans, String user, String[] scopes) {
1047         final Validator v = new ServiceValidator();
1048         if (v.nullOrBlank("User", user).err()) {
1049             return Result.err(Status.ERR_BadData,v.errs());
1050         }
1051
1052         Result<List<PermDAO.Data>> rlpd = ques.getPermsByUser(trans, user, trans.requested(force));
1053         if (rlpd.notOK()) {
1054             return Result.err(rlpd);
1055         }
1056         
1057         PERMS perms = mapper.newInstance(API.PERMS);
1058         
1059         if (rlpd.isEmpty()) {
1060             return Result.ok(perms);
1061         }
1062         // Note: Mapper will restrict what can be viewed
1063         //   if user is the same as that which is looked up, no filtering is required
1064         return mapper.perms(trans, rlpd.value, 
1065                 perms, 
1066                 scopes,
1067                 !user.equals(trans.user()));
1068     }
1069
1070     @ApiDoc( 
1071             method = POST,  
1072             path = "/authz/perms/user/:user",
1073             params = {"user|string|true"},
1074             expectedCode = 200,
1075             errorCodes = { 404,406 }, 
1076             text = { "List All Permissions that match user :user",
1077                      "<p>'user' must be expressed as full identity (ex: id@full.domain.com)</p>",
1078                      "",
1079                      "Present Queries as one or more Permissions (see ContentType Links below for format).",
1080                      "",
1081                      "If the Caller is Granted this specific Permission, and the Permission is valid",
1082                      "  for the User, it will be included in response Permissions, along with",
1083                      "  all the normal permissions on the 'GET' version of this call.  If it is not",
1084                      "  valid, or Caller does not have permission to see, it will be removed from the list",
1085                      "",
1086                      "  *Note: This design allows you to make one call for all expected permissions",
1087                      " The permission to be included MUST be:",
1088                      "     <user namespace>.access|:<ns|role|perm>[:key]|<create|read|write>",
1089                      "   examples:",
1090                      "     com.att.myns.access|:ns|write",
1091                      "     com.att.myns.access|:role:myrole|create",
1092                      "     com.att.myns.access|:perm:mytype:myinstance:myaction|read",
1093                      ""
1094                      }
1095             )
1096     @Override
1097     public Result<PERMS> getPermsByUser(AuthzTrans trans, PERMS _perms, String user) {
1098             PERMS perms = _perms;
1099         final Validator v = new ServiceValidator();
1100         if (v.nullOrBlank("User", user).err()) {
1101             return Result.err(Status.ERR_BadData,v.errs());
1102         }
1103         
1104         //////////////
1105         PermLookup pl = PermLookup.get(trans,ques,user);
1106         Result<List<PermDAO.Data>> rlpd = pl.getPerms(trans.requested(force));
1107         if (rlpd.notOK()) {
1108             return Result.err(rlpd);
1109         }
1110         
1111         /*//TODO 
1112           1) See if allowed to query
1113           2) See if User is allowed
1114           */
1115         Result<List<PermDAO.Data>> in = mapper.perms(trans, perms);
1116         if (in.isOKhasData()) {
1117             List<PermDAO.Data> out = rlpd.value;
1118             boolean ok;
1119             for (PermDAO.Data pdd : in.value) {
1120                 ok = false;
1121                 if ("access".equals(pdd.type)) {
1122                     Access access = Access.valueOf(pdd.action);
1123                     String[] mdkey = Split.splitTrim(':',pdd.instance);
1124                     if (mdkey.length>1) {
1125                         String type = mdkey[1];
1126                         if ("role".equals(type)) {
1127                             if (mdkey.length>2) {
1128                                 RoleDAO.Data rdd = new RoleDAO.Data();
1129                                 rdd.ns=pdd.ns;
1130                                 rdd.name=mdkey[2];
1131                                 ok = ques.mayUser(trans, trans.user(), rdd, Access.read).isOK() && ques.mayUser(trans, user, rdd , access).isOK();
1132                             }
1133                         } else if ("perm".equals(type)) {
1134                             if (mdkey.length>4) { // also need instance/action
1135                                 PermDAO.Data p = new PermDAO.Data();
1136                                 p.ns=pdd.ns;
1137                                 p.type=mdkey[2];
1138                                 p.instance=mdkey[3];
1139                                 p.action=mdkey[4];
1140                                 ok = ques.mayUser(trans, trans.user(), p, Access.read).isOK() && ques.mayUser(trans, user, p , access).isOK();
1141                             }
1142                         } else if ("ns".equals(type)) {
1143                             NsDAO.Data ndd = new NsDAO.Data();
1144                             ndd.name=pdd.ns;
1145                             ok = ques.mayUser(trans, trans.user(), ndd, Access.read).isOK() && ques.mayUser(trans, user, ndd , access).isOK();
1146                         }
1147                     }
1148                 }
1149                 if (ok) {
1150                     out.add(pdd);
1151                 }
1152             }
1153         }        
1154         
1155         perms = mapper.newInstance(API.PERMS);
1156         if (rlpd.isEmpty()) {
1157             return Result.ok(perms);
1158         }
1159         // Note: Mapper will restrict what can be viewed
1160         //   if user is the same as that which is looked up, no filtering is required
1161         return mapper.perms(trans, rlpd.value, 
1162                 perms, 
1163                 !user.equals(trans.user()));
1164     }
1165     
1166     @ApiDoc( 
1167             method = GET,  
1168             path = "/authz/perms/role/:role",
1169             params = {"role|string|true"},
1170             expectedCode = 200,
1171             errorCodes = { 404,406 }, 
1172             text = { "List All Permissions that are granted to :role" }
1173             )
1174     @Override
1175     public Result<PERMS> getPermsByRole(AuthzTrans trans,String role) {
1176         final Validator v = new ServiceValidator();
1177         if (v.nullOrBlank("Role", role).err()) {
1178             return Result.err(Status.ERR_BadData,v.errs());
1179         }
1180
1181         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans, ques,role);
1182         if (rrdd.notOK()) {
1183             return Result.err(rrdd);
1184         }
1185
1186         Result<NsDAO.Data> r = ques.mayUser(trans, trans.user(), rrdd.value, Access.read);
1187         if (r.notOK()) {
1188             return Result.err(r);
1189         }
1190
1191         PERMS perms = mapper.newInstance(API.PERMS);
1192
1193         Result<List<PermDAO.Data>> rlpd = ques.getPermsByRole(trans, role, trans.requested(force));
1194         if (rlpd.isOKhasData()) {
1195             // Note: Mapper will restrict what can be viewed
1196             return mapper.perms(trans, rlpd.value, perms, true);
1197         }
1198         return Result.ok(perms);
1199     }
1200
1201     @ApiDoc( 
1202             method = GET,  
1203             path = "/authz/perms/ns/:ns",
1204             params = {"ns|string|true"},
1205             expectedCode = 200,
1206             errorCodes = { 404,406 }, 
1207             text = { "List All Permissions that are in Namespace :ns" }
1208             )
1209     @Override
1210     public Result<PERMS> getPermsByNS(AuthzTrans trans,String ns) {
1211         final Validator v = new ServiceValidator();
1212         if (v.nullOrBlank("NS", ns).err()) {
1213             return Result.err(Status.ERR_BadData,v.errs());
1214         }
1215
1216         Result<NsDAO.Data> rnd = ques.deriveNs(trans, ns);
1217         if (rnd.notOK()) {
1218             return Result.err(rnd);
1219         }
1220
1221         rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
1222         if (rnd.notOK()) {
1223             return Result.err(rnd);     
1224         }
1225         
1226         Result<List<PermDAO.Data>> rlpd = ques.permDAO().readNS(trans, ns);
1227         if (rlpd.notOK()) {
1228             return Result.err(rlpd);
1229         }
1230
1231         PERMS perms = mapper.newInstance(API.PERMS);
1232         if (!rlpd.isEmpty()) {
1233             // Note: Mapper will restrict what can be viewed
1234             return mapper.perms(trans, rlpd.value,perms, true);
1235         }
1236         return Result.ok(perms);
1237     }
1238     
1239     @ApiDoc( 
1240             method = PUT,  
1241             path =     "/authz/perm/:type/:instance/:action",
1242             params = {"type|string|true",
1243                       "instance|string|true",
1244                         "action|string|true"},
1245             expectedCode = 200,
1246             errorCodes = { 404,406, 409 }, 
1247             text = { "Rename the Permission referenced by :type :instance :action, and "
1248                     + "rename (copy/delete) to the Permission described in PermRequest" }
1249             )
1250     @Override
1251     public Result<Void> renamePerm(final AuthzTrans trans,REQUEST rreq, String origType, String origInstance, String origAction) {
1252         final Result<PermDAO.Data> newPd = mapper.perm(trans, rreq);
1253         final ServiceValidator v = new ServiceValidator();
1254         if (v.perm(newPd).err()) {
1255             return Result.err(Status.ERR_BadData,v.errs());
1256         }
1257
1258         if (ques.mayUser(trans, trans.user(), newPd.value,Access.write).notOK()) {
1259             return Result.err(Status.ERR_Denied, "You do not have approval to change Permission [%s.%s|%s|%s]",
1260                     newPd.value.ns,newPd.value.type,newPd.value.instance,newPd.value.action);
1261         }
1262         
1263         Result<NsSplit> nss = ques.deriveNsSplit(trans, origType);
1264         Result<List<PermDAO.Data>> origRlpd = ques.permDAO().read(trans, nss.value.ns, nss.value.name, origInstance, origAction); 
1265         
1266         if (origRlpd.notOKorIsEmpty()) {
1267             return Result.err(Status.ERR_PermissionNotFound, 
1268                     "Permission [%s|%s|%s] does not exist",
1269                     origType,origInstance,origAction);
1270         }
1271         
1272         PermDAO.Data origPd = origRlpd.value.get(0);
1273
1274         if (!origPd.ns.equals(newPd.value.ns)) {
1275             return Result.err(Status.ERR_Denied, "Cannot change namespace with rename command. " +
1276                     "<new type> must start with [" + origPd.ns + "]");
1277         }
1278         
1279         if ( origPd.type.equals(newPd.value.type) && 
1280                 origPd.action.equals(newPd.value.action) && 
1281                 origPd.instance.equals(newPd.value.instance) ) {
1282             return Result.err(Status.ERR_ConflictAlreadyExists, "New Permission must be different than original permission");
1283         }
1284         
1285         Set<String> origRoles = origPd.roles(false);
1286         if (!origRoles.isEmpty()) {
1287             Set<String> roles = newPd.value.roles(true);
1288             for (String role : origPd.roles) {
1289                 roles.add(role); 
1290             }
1291         }    
1292         
1293         newPd.value.description = origPd.description;
1294         
1295         Result<Void> rv = null;
1296         
1297         rv = func.createPerm(trans, newPd.value, false);
1298         if (rv.isOK()) {
1299             rv = func.deletePerm(trans, origPd, true, false);
1300         }
1301         return rv;
1302     }
1303     
1304     @ApiDoc( 
1305             method = PUT,  
1306             path = "/authz/perm",
1307             params = {},
1308             expectedCode = 200,
1309             errorCodes = { 404,406 }, 
1310             text = { "Add Description Data to Perm" }
1311             )
1312     @Override
1313     public Result<Void> updatePermDescription(AuthzTrans trans, REQUEST from) {
1314         final Result<PermDAO.Data> pd = mapper.perm(trans, from);
1315         final ServiceValidator v = new ServiceValidator();
1316         if (v.perm(pd).err()) {
1317             return Result.err(Status.ERR_BadData,v.errs());
1318         }
1319         if (v.nullOrBlank("description", pd.value.description).err()) {
1320             return Result.err(Status.ERR_BadData,v.errs());
1321         }
1322         final PermDAO.Data perm = pd.value;
1323         if (ques.permDAO().read(trans, perm.ns, perm.type, perm.instance,perm.action).notOKorIsEmpty()) {
1324             return Result.err(Status.ERR_NotFound, "Permission [%s.%s|%s|%s] does not exist",
1325                 perm.ns,perm.type,perm.instance,perm.action);
1326         }
1327
1328         if (ques.mayUser(trans, trans.user(), perm, Access.write).notOK()) {
1329             return Result.err(Status.ERR_Denied, "You do not have approval to change Permission [%s.%s|%s|%s]",
1330                     perm.ns,perm.type,perm.instance,perm.action);
1331         }
1332
1333         Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, pd.value.ns);
1334         if (nsr.notOKorIsEmpty()) {
1335             return Result.err(nsr);
1336         }
1337
1338         Result<Void> rdr = ques.permDAO().addDescription(trans, perm.ns, perm.type, perm.instance,
1339                 perm.action, perm.description);
1340         if (rdr.isOK()) {
1341             return Result.ok();
1342         } else {
1343             return Result.err(rdr);
1344         }
1345
1346     }
1347     
1348     @ApiDoc(
1349             method = PUT,
1350             path = "/authz/role/perm",
1351             params = {},
1352             expectedCode = 201,
1353             errorCodes = {403,404,406,409},
1354             text = { "Set a permission's roles to roles given" }
1355            )
1356
1357     @Override
1358     public Result<Void> resetPermRoles(final AuthzTrans trans, REQUEST rreq) {
1359         final Result<PermDAO.Data> updt = mapper.permFromRPRequest(trans, rreq);
1360         if (updt.notOKorIsEmpty()) {
1361             return Result.err(updt);
1362         }
1363
1364         final ServiceValidator v = new ServiceValidator();
1365         if (v.perm(updt).err()) {
1366             return Result.err(Status.ERR_BadData,v.errs());
1367         }
1368
1369         Result<NsDAO.Data> nsd = ques.mayUser(trans, trans.user(), updt.value, Access.write);
1370         if (nsd.notOK()) {
1371             return Result.err(nsd);
1372         }
1373
1374         // Read full set to get CURRENT values
1375         Result<List<PermDAO.Data>> rcurr = ques.permDAO().read(trans, 
1376                 updt.value.ns, 
1377                 updt.value.type, 
1378                 updt.value.instance, 
1379                 updt.value.action);
1380         
1381         if (rcurr.notOKorIsEmpty()) {
1382             return Result.err(Status.ERR_PermissionNotFound, 
1383                     "Permission [%s.%s|%s|%s] does not exist",
1384                      updt.value.ns,updt.value.type,updt.value.instance,updt.value.action);
1385         }
1386         
1387         // Create a set of Update Roles, which are in Internal Format
1388         Set<String> updtRoles = new HashSet<>();
1389         Result<NsSplit> nss;
1390         for (String role : updt.value.roles(false)) {
1391             nss = ques.deriveNsSplit(trans, role);
1392             if (nss.isOK()) {
1393                 updtRoles.add(nss.value.ns + '|' + nss.value.name);
1394             } else {
1395                 trans.error().log(nss.errorString());
1396             }
1397         }
1398
1399         Result<Void> rv = null;
1400         
1401         for (PermDAO.Data curr : rcurr.value) {
1402             Set<String> currRoles = curr.roles(false);
1403             // must add roles to this perm, and add this perm to each role 
1404             // in the update, but not in the current            
1405             for (String role : updtRoles) {
1406                 if (!currRoles.contains(role)) {
1407                     Result<RoleDAO.Data> key = RoleDAO.Data.decode(trans, ques, role);
1408                     if (key.isOKhasData()) {
1409                         Result<List<RoleDAO.Data>> rrd = ques.roleDAO().read(trans, key.value);
1410                         if (rrd.isOKhasData()) {
1411                             for (RoleDAO.Data r : rrd.value) {
1412                                 rv = func.addPermToRole(trans, r, curr, false);
1413                                 if (rv.notOK() && rv.status!=Result.ERR_ConflictAlreadyExists) {
1414                                     return Result.err(rv);
1415                                 }
1416                             }
1417                         } else {
1418                             return Result.err(rrd);
1419                         }
1420                     }
1421                 }
1422             }
1423             // similarly, must delete roles from this perm, and delete this perm from each role
1424             // in the update, but not in the current
1425             for (String role : currRoles) {
1426                 if (!updtRoles.contains(role)) {
1427                     Result<RoleDAO.Data> key = RoleDAO.Data.decode(trans, ques, role);
1428                     if (key.isOKhasData()) {
1429                         Result<List<RoleDAO.Data>> rdd = ques.roleDAO().read(trans, key.value);
1430                         if (rdd.isOKhasData()) {
1431                             for (RoleDAO.Data r : rdd.value) {
1432                                 rv = func.delPermFromRole(trans, r, curr, true);
1433                                 if (rv.notOK() && rv.status!=Status.ERR_PermissionNotFound) {
1434                                     return Result.err(rv);
1435                                 }
1436                             }
1437                         }
1438                     }
1439                 }
1440             }                
1441         } 
1442         return rv==null?Result.ok():rv;        
1443     }
1444     
1445     @ApiDoc( 
1446             method = DELETE,
1447             path = "/authz/perm",
1448             params = {},
1449             expectedCode = 200,
1450             errorCodes = { 404,406 }, 
1451             text = { "Delete the Permission referenced by PermKey.",
1452                     "You cannot normally delete a permission which is still granted to roles,",
1453                     "however the \"force\" property allows you to do just that. To do this: Add",
1454                     "'force=true' as a query parameter.",
1455                     "<p>WARNING: Using force will ungrant this permission from all roles. Use with care.</p>" }
1456             )
1457     @Override
1458     public Result<Void> deletePerm(final AuthzTrans trans, REQUEST from) {
1459         Result<PermDAO.Data> pd = mapper.perm(trans, from);
1460         if (pd.notOK()) {
1461             return Result.err(pd);
1462         }
1463         final ServiceValidator v = new ServiceValidator();
1464         if (v.nullOrBlank(pd.value).err()) {
1465             return Result.err(Status.ERR_BadData,v.errs());
1466         }
1467         final PermDAO.Data perm = pd.value;
1468         if (ques.permDAO().read(trans, perm).notOKorIsEmpty()) {
1469             return Result.err(Status.ERR_PermissionNotFound, "Permission [%s.%s|%s|%s] does not exist",
1470                     perm.ns,perm.type,perm.instance,perm.action    );
1471         }
1472         
1473         Result<FutureDAO.Data> fd = mapper.future(trans,PermDAO.TABLE,from,perm,false,
1474                 new Mapper.Memo() {
1475                     @Override
1476                     public String get() {
1477                         return "Delete Permission [" + perm.fullPerm() + ']';
1478                     }
1479                 },
1480             new MayChange() {
1481                 private Result<NsDAO.Data> nsd;
1482                 @Override
1483                 public Result<?> mayChange() {
1484                     if (nsd==null) {
1485                         nsd = ques.mayUser(trans, trans.user(), perm, Access.write);
1486                     }
1487                     return nsd;
1488                 }
1489             });
1490         
1491         switch(fd.status) {
1492         case OK:
1493             Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, perm.ns);
1494             if (nsr.notOKorIsEmpty()) {
1495                 return Result.err(nsr);
1496             }
1497             
1498             Result<String> rfc = func.createFuture(trans, fd.value, 
1499                     perm.encode(), trans.user(),nsr.value.get(0),FUTURE_OP.D);
1500             if (rfc.isOK()) {
1501                 return Result.err(Status.ACC_Future, "Perm Deletion [%s] is saved for future processing",perm.encode());
1502             } else { 
1503                 return Result.err(rfc);
1504             }
1505         case Status.ACC_Now:
1506             return func.deletePerm(trans,perm,trans.requested(force), false);
1507         default:
1508             return Result.err(fd);
1509         }            
1510     }    
1511     
1512     @ApiDoc( 
1513             method = DELETE,
1514             path = "/authz/perm/:name/:type/:action",
1515             params = {"type|string|true",
1516                       "instance|string|true",
1517                           "action|string|true"},
1518             expectedCode = 200,
1519             errorCodes = { 404,406 }, 
1520             text = { "Delete the Permission referenced by :type :instance :action",
1521                     "You cannot normally delete a permission which is still granted to roles,",
1522                     "however the \"force\" property allows you to do just that. To do this: Add",
1523                     "'force=true' as a query parameter",
1524                     "<p>WARNING: Using force will ungrant this permission from all roles. Use with care.</p>"}
1525             )
1526     @Override
1527     public Result<Void> deletePerm(AuthzTrans trans, String type, String instance, String action) {
1528         final Validator v = new ServiceValidator();
1529         if (v.nullOrBlank("Type",type)
1530             .nullOrBlank("Instance",instance)
1531             .nullOrBlank("Action",action)
1532             .err()) {
1533             return Result.err(Status.ERR_BadData,v.errs());
1534         }
1535         
1536         Result<PermDAO.Data> pd = ques.permFrom(trans, type, instance, action);
1537         if (pd.isOK()) {
1538             return func.deletePerm(trans, pd.value, trans.requested(force), false);
1539         } else {
1540             return Result.err(pd);
1541         }
1542     }
1543
1544 /***********************************
1545  * ROLE 
1546  ***********************************/
1547     @ApiDoc(
1548             method = POST,
1549             path = "/authz/role",
1550             params = {},
1551             expectedCode = 201,
1552             errorCodes = {403,404,406,409},
1553             text = {
1554
1555                 "Roles are part of Namespaces",
1556                 "Examples:",
1557                 "<ul><li>org.onap.aaf - The team that created and maintains AAF</li>",
1558                 "Roles do not include implied permissions for an App.  Instead, they contain explicit Granted Permissions by any Namespace in AAF (See Permissions)",
1559                 "Restrictions on Role Names:",
1560                 "<ul><li>Must start with valid Namespace name, terminated by . (dot/period)</li>",
1561                 "<li>Allowed Characters are a-zA-Z0-9._-</li>",
1562                 "<li>role names are Case Sensitive</li></ul>",
1563                 "The right questions to ask for defining and populating a Role in AAF, therefore, are:",
1564                 "<ul><li>'What Job Function does this represent?'</li>",
1565                 "<li>'Does this person perform this Job Function?'</li></ul>" }
1566            )
1567
1568     @Override
1569     public Result<Void> createRole(final AuthzTrans trans, REQUEST from) {
1570         final Result<RoleDAO.Data> rd = mapper.role(trans, from);
1571         // Does Perm Type exist as a Namespace?
1572         if(rd.value.name.isEmpty() || ques.nsDAO().read(trans, rd.value.fullName()).isOKhasData()) {
1573             return Result.err(Status.ERR_ConflictAlreadyExists,
1574                     "Role exists as a Namespace");
1575         }
1576         final ServiceValidator v = new ServiceValidator();
1577         if (v.role(rd).err()) {
1578             return Result.err(Status.ERR_BadData,v.errs());
1579         }
1580         final RoleDAO.Data role = rd.value;
1581         if (ques.roleDAO().read(trans, role.ns, role.name).isOKhasData()) {
1582             return Result.err(Status.ERR_ConflictAlreadyExists, "Role [" + role.fullName() + "] already exists");
1583         }
1584
1585         Result<FutureDAO.Data> fd = mapper.future(trans,RoleDAO.TABLE,from,role,false,
1586             new Mapper.Memo() {
1587                 @Override
1588                 public String get() {
1589                     return "Create Role [" + 
1590                         rd.value.fullName() + 
1591                         ']';
1592                 }
1593             },
1594             new MayChange() {
1595                 private Result<NsDAO.Data> nsd;
1596                 @Override
1597                 public Result<?> mayChange() {
1598                     if (nsd==null) {
1599                         nsd = ques.mayUser(trans, trans.user(), role, Access.write);
1600                     }
1601                     return nsd;
1602                 }
1603             });
1604         
1605         Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, rd.value.ns);
1606         if (nsr.notOKorIsEmpty()) {
1607             return Result.err(nsr);
1608         }
1609
1610         switch(fd.status) {
1611             case OK:
1612                 Result<String> rfc = func.createFuture(trans, fd.value, 
1613                         role.encode(), trans.user(),nsr.value.get(0),FUTURE_OP.C);
1614                 if (rfc.isOK()) {
1615                     return Result.err(Status.ACC_Future, "Role [%s.%s] is saved for future processing",
1616                             rd.value.ns,
1617                             rd.value.name);
1618                 } else { 
1619                     return Result.err(rfc);
1620                 }
1621             case Status.ACC_Now:
1622                 Result<RoleDAO.Data> rdr = ques.roleDAO().create(trans, role);
1623                 if (rdr.isOK()) {
1624                     return Result.ok();
1625                 } else {
1626                     return Result.err(rdr);
1627                 }
1628             default:
1629                 return Result.err(fd);
1630         }
1631     }
1632
1633     /* (non-Javadoc)
1634      * @see org.onap.aaf.auth.service.AuthzService#getRolesByName(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String)
1635      */
1636     @ApiDoc(
1637             method = GET,
1638             path = "/authz/roles/:role",
1639             params = {"role|string|true"}, 
1640             expectedCode = 200,
1641             errorCodes = {404,406},
1642             text = { "List Roles that match :role",
1643                      "Note: You must have permission to see any given role"
1644                    }
1645            )
1646     @Override
1647     public Result<ROLES> getRolesByName(AuthzTrans trans, String role) {
1648         final Validator v = new ServiceValidator();
1649         if (v.nullOrBlank("Role", role).err()) {
1650             return Result.err(Status.ERR_BadData,v.errs());
1651         }
1652         
1653         // Determine if User can ask this question
1654         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans, ques, role);
1655         if (rrdd.isOKhasData()) {
1656             Result<NsDAO.Data> r;
1657             if ((r = ques.mayUser(trans, trans.user(), rrdd.value, Access.read)).notOK()) {
1658                 return Result.err(r);
1659             }
1660         } else {
1661             return Result.err(rrdd);
1662         }
1663         
1664         // Look up data
1665         int query = role.indexOf('?');
1666         Result<List<RoleDAO.Data>> rlrd = ques.getRolesByName(trans, query<0?role:role.substring(0, query));
1667         if (rlrd.isOK()) {
1668             // Note: Mapper will restrict what can be viewed
1669             ROLES roles = mapper.newInstance(API.ROLES);
1670             return mapper.roles(trans, rlrd.value, roles, true);
1671         } else {
1672             return Result.err(rlrd);
1673         }
1674     }
1675
1676     /* (non-Javadoc)
1677      * @see org.onap.aaf.auth.service.AuthzService#getRolesByUser(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String)
1678      */
1679     @ApiDoc(
1680             method = GET,
1681             path = "/authz/roles/user/:name",
1682             params = {"name|string|true"},
1683             expectedCode = 200,
1684             errorCodes = {404,406},
1685             text = { "List all Roles that match user :name",
1686                      "'user' must be expressed as full identity (ex: id@full.domain.com)",
1687                         "Note: You must have permission to see any given role"
1688             }
1689            )
1690
1691     @Override
1692     public Result<ROLES> getRolesByUser(AuthzTrans trans, String user) {
1693         final Validator v = new ServiceValidator();
1694         if (v.nullOrBlank("User", user).err()) {
1695             return Result.err(Status.ERR_BadData,v.errs());
1696         }
1697
1698         ROLES roles = mapper.newInstance(API.ROLES);
1699         // Get list of roles per user, then add to Roles as we go
1700         Result<List<RoleDAO.Data>> rlrd;
1701         Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByUser(trans, user);
1702         if (rlurd.isOKhasData()) {
1703             for (UserRoleDAO.Data urd : rlurd.value ) {
1704                 rlrd = ques.roleDAO().read(trans, urd.ns,urd.rname);
1705                 // Note: Mapper will restrict what can be viewed
1706                 //   if user is the same as that which is looked up, no filtering is required
1707                 if (rlrd.isOKhasData()) {
1708                     mapper.roles(trans, rlrd.value,roles, !user.equals(trans.user()));
1709                 }
1710             }
1711         }
1712         return Result.ok(roles);
1713     }
1714
1715     /*
1716      * (non-Javadoc)
1717      * @see org.onap.aaf.auth.service.AuthzService#getRolesByNS(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String)
1718      */
1719     @ApiDoc(
1720             method = GET,
1721             path = "/authz/roles/ns/:ns",
1722             params = {"ns|string|true"},
1723             expectedCode = 200,
1724             errorCodes = {404,406},
1725             text = { "List all Roles for the Namespace :ns", 
1726                          "Note: You must have permission to see any given role"
1727             }
1728            )
1729
1730     @Override
1731     public Result<ROLES> getRolesByNS(AuthzTrans trans, String ns) {
1732         final Validator v = new ServiceValidator();
1733         if (v.nullOrBlank("NS", ns).err()) {
1734             return Result.err(Status.ERR_BadData,v.errs());
1735         }
1736         
1737         // check if user is allowed to view NS
1738         Result<NsDAO.Data> rnsd = ques.deriveNs(trans, ns); 
1739         if (rnsd.notOK()) {
1740             return Result.err(rnsd);     
1741         }
1742         rnsd = ques.mayUser(trans, trans.user(), rnsd.value, Access.read);
1743         if (rnsd.notOK()) {
1744             return Result.err(rnsd);     
1745         }
1746
1747         TimeTaken tt = trans.start("MAP Roles by NS to Roles", Env.SUB);
1748         try {
1749             ROLES roles = mapper.newInstance(API.ROLES);
1750             // Get list of roles per user, then add to Roles as we go
1751             Result<List<RoleDAO.Data>> rlrd = ques.roleDAO().readNS(trans, ns);
1752             if (rlrd.isOK()) {
1753                 if (!rlrd.isEmpty()) {
1754                     // Note: Mapper doesn't need to restrict what can be viewed, because we did it already.
1755                     mapper.roles(trans,rlrd.value,roles,false);
1756                 }
1757                 return Result.ok(roles);
1758             } else {
1759                 return Result.err(rlrd);
1760             }
1761         } finally {
1762             tt.done();
1763         }
1764     }
1765
1766     /*
1767      * (non-Javadoc)
1768      * @see org.onap.aaf.auth.service.AuthzService#getRolesByNS(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String)
1769      */
1770     @ApiDoc(
1771             method = GET,
1772             path = "/authz/roles/name/:name",
1773             params = {"name|string|true"},
1774             expectedCode = 200,
1775             errorCodes = {404,406},
1776             text = { "List all Roles for only the Name of Role (without Namespace)", 
1777                          "Note: You must have permission to see any given role"
1778             }
1779            )
1780     @Override
1781     public Result<ROLES> getRolesByNameOnly(AuthzTrans trans, String name) {
1782         final Validator v = new ServiceValidator();
1783         if (v.nullOrBlank("Name", name).err()) {
1784             return Result.err(Status.ERR_BadData,v.errs());
1785         }
1786         
1787         // User Mapper to make sure user is allowed to view NS
1788
1789         TimeTaken tt = trans.start("MAP Roles by Name to Roles", Env.SUB);
1790         try {
1791             ROLES roles = mapper.newInstance(API.ROLES);
1792             // Get list of roles per user, then add to Roles as we go
1793             Result<List<RoleDAO.Data>> rlrd = ques.roleDAO().readName(trans, name);
1794             if (rlrd.isOK()) {
1795                 if (!rlrd.isEmpty()) {
1796                     // Note: Mapper will restrict what can be viewed
1797                     mapper.roles(trans,rlrd.value,roles,true);
1798                 }
1799                 return Result.ok(roles);
1800             } else {
1801                 return Result.err(rlrd);
1802             }
1803         } finally {
1804             tt.done();
1805         }
1806     }
1807
1808     @ApiDoc(
1809             method = GET,
1810             path = "/authz/roles/perm/:type/:instance/:action",
1811             params = {"type|string|true",
1812                       "instance|string|true",
1813                       "action|string|true"},
1814             expectedCode = 200,
1815             errorCodes = {404,406},
1816             text = { "Find all Roles containing the given Permission." +
1817                      "Permission consists of:",
1818                      "<ul><li>type - a Namespace qualified identifier specifying what kind of resource "
1819                      + "is being protected</li>",
1820                      "<li>instance - a key, possibly multi-dimensional, that identifies a specific "
1821                      + " instance of the type</li>",
1822                      "<li>action - what kind of action is allowed</li></ul>",
1823                      "Notes: instance and action can be an *",
1824                      "       You must have permission to see any given role"
1825                      }
1826            )
1827
1828     @Override
1829     public Result<ROLES> getRolesByPerm(AuthzTrans trans, String type, String instance, String action) {
1830         final Validator v = new ServiceValidator();
1831         if (v.permType(type)
1832             .permInstance(instance)
1833             .permAction(action)
1834             .err()) {
1835             return Result.err(Status.ERR_BadData,v.errs());
1836         }
1837         
1838         TimeTaken tt = trans.start("Map Perm Roles Roles", Env.SUB);
1839         try {
1840             ROLES roles = mapper.newInstance(API.ROLES);
1841             // Get list of roles per user, then add to Roles as we go
1842             Result<NsSplit> nsSplit = ques.deriveNsSplit(trans, type);
1843             if (nsSplit.isOK()) {
1844                 PermDAO.Data pdd = new PermDAO.Data(nsSplit.value, instance, action);
1845                 Result<?> res;
1846                 if ((res=ques.mayUser(trans, trans.user(), pdd, Question.Access.read)).notOK()) {
1847                     return Result.err(res);
1848                 }
1849                 
1850                 Result<List<PermDAO.Data>> pdlr = ques.permDAO().read(trans, pdd);
1851                 if (pdlr.isOK())for (PermDAO.Data pd : pdlr.value) {
1852                     Result<List<RoleDAO.Data>> rlrd;
1853                     for (String r : pd.roles) {
1854                         Result<String[]> rs = RoleDAO.Data.decodeToArray(trans, ques, r);
1855                         if (rs.isOK()) {
1856                             rlrd = ques.roleDAO().read(trans, rs.value[0],rs.value[1]);
1857                             // Note: Mapper will restrict what can be viewed
1858                             if (rlrd.isOKhasData()) {
1859                                 mapper.roles(trans,rlrd.value,roles,true);
1860                             }
1861                         }
1862                     }
1863                 }
1864             }
1865             return Result.ok(roles);
1866         } finally {
1867             tt.done();
1868         }
1869     }
1870
1871     @ApiDoc(
1872             method = PUT,
1873             path = "/authz/role",
1874             params = {},
1875             expectedCode = 200,
1876             errorCodes = {404,406},
1877             text = { "Add Description Data to a Role" }
1878            )
1879
1880     @Override
1881     public Result<Void> updateRoleDescription(AuthzTrans trans, REQUEST from) {
1882         final Result<RoleDAO.Data> rd = mapper.role(trans, from);
1883         final ServiceValidator v = new ServiceValidator();
1884         if (v.role(rd).err()) {
1885             return Result.err(Status.ERR_BadData,v.errs());
1886         } {
1887         if (v.nullOrBlank("description", rd.value.description).err()) {
1888             return Result.err(Status.ERR_BadData,v.errs());
1889         }
1890         }
1891         final RoleDAO.Data role = rd.value;
1892         if (ques.roleDAO().read(trans, role.ns, role.name).notOKorIsEmpty()) {
1893             return Result.err(Status.ERR_NotFound, "Role [" + role.fullName() + "] does not exist");
1894         }
1895
1896         if (ques.mayUser(trans, trans.user(), role, Access.write).notOK()) {
1897             return Result.err(Status.ERR_Denied, "You do not have approval to change " + role.fullName());
1898         }
1899
1900         Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, rd.value.ns);
1901         if (nsr.notOKorIsEmpty()) {
1902             return Result.err(nsr);
1903         }
1904
1905         Result<Void> rdr = ques.roleDAO().addDescription(trans, role.ns, role.name, role.description);
1906         if (rdr.isOK()) {
1907             return Result.ok();
1908         } else {
1909             return Result.err(rdr);
1910         }
1911
1912     }
1913     
1914     @ApiDoc(
1915             method = POST,
1916             path = "/authz/role/perm",
1917             params = {},
1918             expectedCode = 201,
1919             errorCodes = {403,404,406,409},
1920             text = { "Grant a Permission to a Role",
1921                      "Permission consists of:", 
1922                      "<ul><li>type - a Namespace qualified identifier specifying what kind of resource "
1923                      + "is being protected</li>",
1924                      "<li>instance - a key, possibly multi-dimensional, that identifies a specific "
1925                      + " instance of the type</li>",
1926                      "<li>action - what kind of action is allowed</li></ul>",
1927                      "Note: instance and action can be an *",
1928                      "Note: Using the \"force\" property will create the Permission, if it doesn't exist AND the requesting " +
1929                      " ID is allowed to create.  It will then grant",
1930                      "  the permission to the role in one step. To do this: add 'force=true' as a query parameter."
1931                     }
1932            )
1933
1934     @Override
1935     public Result<Void> addPermToRole(final AuthzTrans trans, REQUEST rreq) {
1936         // Translate Request into Perm and Role Objects
1937         final Result<PermDAO.Data> rpd = mapper.permFromRPRequest(trans, rreq);
1938         if (rpd.notOKorIsEmpty()) {
1939             return Result.err(rpd);
1940         }
1941         final Result<RoleDAO.Data> rrd = mapper.roleFromRPRequest(trans, rreq);
1942         if (rrd.notOKorIsEmpty()) {
1943             return Result.err(rrd);
1944         }
1945         
1946         // Validate Role and Perm values
1947         final ServiceValidator v = new ServiceValidator();
1948         if (v.perm(rpd.value)
1949             .role(rrd.value)
1950             .err()) {
1951             return Result.err(Status.ERR_BadData,v.errs());
1952         }
1953
1954         Result<List<RoleDAO.Data>> rlrd = ques.roleDAO().read(trans, rrd.value.ns, rrd.value.name);
1955         if (rlrd.notOKorIsEmpty()) {
1956             return Result.err(Status.ERR_RoleNotFound, "Role [%s] does not exist", rrd.value.fullName());
1957         }
1958         
1959         // Check Status of Data in DB (does it exist)
1960         Result<List<PermDAO.Data>> rlpd = ques.permDAO().read(trans, rpd.value.ns, 
1961                 rpd.value.type, rpd.value.instance, rpd.value.action);
1962         PermDAO.Data createPerm = null; // if not null, create first
1963         if (rlpd.notOKorIsEmpty()) { // Permission doesn't exist
1964             if (trans.requested(force)) {
1965                 // Remove roles from perm data object so we just create the perm here
1966                 createPerm = rpd.value;
1967                 createPerm.roles.clear();
1968             } else {
1969                 return Result.err(Status.ERR_PermissionNotFound,"Permission [%s.%s|%s|%s] does not exist", 
1970                         rpd.value.ns,rpd.value.type,rpd.value.instance,rpd.value.action);
1971             }
1972         } else {
1973             if (rlpd.value.get(0).roles(false).contains(rrd.value.encode())) {
1974                 return Result.err(Status.ERR_ConflictAlreadyExists,
1975                         "Permission [%s.%s|%s|%s] already granted to Role [%s.%s]",
1976                         rpd.value.ns,rpd.value.type,rpd.value.instance,rpd.value.action,
1977                         rrd.value.ns,rrd.value.name
1978                     );
1979             }
1980         }
1981
1982         
1983         Result<FutureDAO.Data> fd = mapper.future(trans, PermDAO.TABLE, rreq, rpd.value,true, // Allow grants to create Approvals
1984                 new Mapper.Memo() {
1985                     @Override
1986                     public String get() {
1987                         return "Grant Permission [" + rpd.value.fullPerm() + ']' +
1988                             " to Role [" + rrd.value.fullName() + "]";
1989                     }
1990                 },
1991                 new MayChange() {
1992                     private Result<NsDAO.Data> nsd;
1993                     @Override
1994                     public Result<?> mayChange() {
1995                         if (nsd==null) {
1996                             nsd = ques.mayUser(trans, trans.user(), rpd.value, Access.write);
1997                             if(nsd.notOK()) {
1998                                 trans.requested(REQD_TYPE.future,true);
1999                             }
2000                         }
2001                         return nsd;
2002                     }
2003                 });
2004         Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, rpd.value.ns);
2005         if (nsr.notOKorIsEmpty()) {
2006             return Result.err(nsr);
2007         }
2008         switch(fd.status) {
2009             case OK:
2010                 Result<String> rfc = func.createFuture(trans,fd.value, 
2011                         rpd.value.fullPerm(),
2012                         trans.user(),
2013                         nsr.value.get(0),
2014                         FUTURE_OP.G);
2015                 if (rfc.isOK()) {
2016                     return Result.err(Status.ACC_Future, "Perm [%s.%s|%s|%s] is saved for future processing",
2017                             rpd.value.ns,
2018                             rpd.value.type,
2019                             rpd.value.instance,
2020                             rpd.value.action);
2021                 } else { 
2022                     return Result.err(rfc);
2023                 }
2024             case Status.ACC_Now:
2025                 Result<Void> rv = null;
2026                 if (createPerm!=null) {// has been validated for creating
2027                     rv = func.createPerm(trans, createPerm, false);
2028                 }
2029                 if (rv==null || rv.isOK()) {
2030                     rv = func.addPermToRole(trans, rrd.value, rpd.value, false);
2031                 }
2032                 return rv;
2033             default:
2034                 return Result.err(fd);
2035         }
2036         
2037     }
2038
2039     /**
2040      * Delete Perms from Roles (UnGrant)
2041      * @param trans
2042      * @param roleFullName
2043      * @return
2044      */
2045     @ApiDoc(
2046             method = DELETE,
2047             path = "/authz/role/:role/perm",
2048             params = {"role|string|true"},
2049             expectedCode = 200,
2050             errorCodes = {404,406},
2051             text = { "Ungrant a permission from Role :role" }
2052            )
2053
2054     @Override
2055     public Result<Void> delPermFromRole(final AuthzTrans trans, REQUEST rreq) {
2056         final Result<PermDAO.Data> updt = mapper.permFromRPRequest(trans, rreq);
2057         if (updt.notOKorIsEmpty()) {
2058             return Result.err(updt);
2059         }
2060         final Result<RoleDAO.Data> rrd = mapper.roleFromRPRequest(trans, rreq);
2061         if (rrd.notOKorIsEmpty()) {
2062             return Result.err(rrd);
2063         }
2064
2065         final ServiceValidator v = new ServiceValidator();
2066         if (v.nullOrBlank(updt.value)
2067             .nullOrBlank(rrd.value)
2068             .err()) {
2069             return Result.err(Status.ERR_BadData,v.errs());
2070         }
2071
2072         return delPermFromRole(trans, updt.value,rrd.value, rreq);
2073     }
2074         
2075     private Result<Void> delPermFromRole(final AuthzTrans trans, PermDAO.Data pdd, RoleDAO.Data rdd, REQUEST rreq) {        
2076         Result<List<PermDAO.Data>> rlpd = ques.permDAO().read(trans, pdd.ns, pdd.type, 
2077                 pdd.instance, pdd.action);
2078         
2079         if (rlpd.notOKorIsEmpty()) {
2080             return Result.err(Status.ERR_PermissionNotFound, 
2081                 "Permission [%s.%s|%s|%s] does not exist",
2082                     pdd.ns,pdd.type,pdd.instance,pdd.action);
2083         }
2084         
2085         Result<FutureDAO.Data> fd = mapper.future(trans, PermDAO.TABLE, rreq, pdd,true, // allow ungrants requests
2086                 new Mapper.Memo() {
2087                     @Override
2088                     public String get() {
2089                         return "Ungrant Permission [" + pdd.fullPerm() + ']' +
2090                             " from Role [" + rdd.fullName() + "]";
2091                     }
2092                 },
2093                 new MayChange() {
2094                     private Result<NsDAO.Data> nsd;
2095                     @Override
2096                     public Result<?> mayChange() {
2097                         if (nsd==null) {
2098                             nsd = ques.mayUser(trans, trans.user(), pdd, Access.write);
2099                         }
2100                         return nsd;
2101                     }
2102                 });
2103         Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, pdd.ns);
2104         if (nsr.notOKorIsEmpty()) {
2105             return Result.err(nsr);
2106         }
2107         switch(fd.status) {
2108             case OK:
2109                 Result<String> rfc = func.createFuture(trans,fd.value, 
2110                         pdd.fullPerm(),
2111                         trans.user(),
2112                         nsr.value.get(0),
2113                         FUTURE_OP.UG
2114                         );
2115                 if (rfc.isOK()) {
2116                     return Result.err(Status.ACC_Future, "Perm [%s.%s|%s|%s] is saved for future processing",
2117                             pdd.ns,
2118                             pdd.type,
2119                             pdd.instance,
2120                             pdd.action);
2121                 } else {
2122                     return Result.err(rfc);
2123                 }
2124             case Status.ACC_Now:
2125                 return func.delPermFromRole(trans, rdd, pdd, false);
2126             default:
2127                 return Result.err(fd);
2128         }
2129     }
2130     
2131 /*
2132     @ApiDoc(
2133             method = DELETE,
2134             path = "/authz/role/:role/perm/:type/:instance/:action",
2135             params = {"role|string|true",
2136                          "perm type|string|true",
2137                          "perm instance|string|true",
2138                          "perm action|string|true"
2139                 },
2140             expectedCode = 200,
2141             errorCodes = {404,406},
2142             text = { "Ungrant a single permission from Role :role with direct key" }
2143            )
2144 */
2145     @Override
2146     public Result<Void> delPermFromRole(AuthzTrans trans, String role, String type, String instance, String action) {
2147         Result<Data> rpns = ques.deriveNs(trans, type);
2148         if (rpns.notOKorIsEmpty()) {
2149             return Result.err(rpns);
2150         }
2151         
2152             final Validator v = new ServiceValidator();
2153         if (v.role(role)
2154             .permType(rpns.value.name,rpns.value.parent)
2155             .permInstance(instance)
2156             .permAction(action)
2157             .err()) {
2158             return Result.err(Status.ERR_BadData,v.errs());
2159         }
2160         
2161             Result<Data> rrns = ques.deriveNs(trans, role);
2162             if (rrns.notOKorIsEmpty()) {
2163                 return Result.err(rrns);
2164             }
2165             
2166         final Result<List<RoleDAO.Data>> rrd = ques.roleDAO().read(trans, rrns.value.parent, rrns.value.name);
2167         if (rrd.notOKorIsEmpty()) {
2168             return Result.err(rrd);
2169         }
2170         
2171         final Result<List<PermDAO.Data>> rpd = ques.permDAO().read(trans, rpns.value.parent, rpns.value.name, instance, action);
2172         if (rpd.notOKorIsEmpty()) {
2173             return Result.err(rpd);
2174         }
2175
2176         
2177         return delPermFromRole(trans,rpd.value.get(0), rrd.value.get(0), mapper.ungrantRequest(trans, role, type, instance, action));
2178     }
2179     
2180     @ApiDoc(
2181             method = DELETE,
2182             path = "/authz/role/:role",
2183             params = {"role|string|true"},
2184             expectedCode = 200,
2185             errorCodes = {404,406},
2186             text = { "Delete the Role named :role"}
2187            )
2188
2189     @Override
2190     public Result<Void> deleteRole(AuthzTrans trans, String role)  {
2191         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans,ques,role);
2192         if (rrdd.isOKhasData()) {
2193             final ServiceValidator v = new ServiceValidator();
2194             if (v.nullOrBlank(rrdd.value).err()) { 
2195                 return Result.err(Status.ERR_BadData,v.errs());
2196             }
2197             return func.deleteRole(trans, rrdd.value, false, false);
2198         } else {
2199             return Result.err(rrdd);
2200         }
2201     }
2202
2203     @ApiDoc(
2204             method = DELETE,
2205             path = "/authz/role",
2206             params = {},
2207             expectedCode = 200,
2208             errorCodes = { 404,406 },
2209             text = { "Delete the Role referenced by RoleKey",
2210                     "You cannot normally delete a role which still has permissions granted or users assigned to it,",
2211                     "however the \"force\" property allows you to do just that. To do this: Add 'force=true'",
2212                     "as a query parameter.",
2213                     "<p>WARNING: Using force will remove all users and permission from this role. Use with care.</p>"}
2214            )
2215
2216     @Override
2217     public Result<Void> deleteRole(final AuthzTrans trans, REQUEST from) {
2218         final Result<RoleDAO.Data> rd = mapper.role(trans, from);
2219         final ServiceValidator v = new ServiceValidator();
2220         if (rd==null) {
2221             return Result.err(Status.ERR_BadData,"Request does not contain Role");
2222         }
2223         if (v.nullOrBlank(rd.value).err()) {
2224             return Result.err(Status.ERR_BadData,v.errs());
2225         }
2226         final RoleDAO.Data role = rd.value;
2227         if (ques.roleDAO().read(trans, role).notOKorIsEmpty() && !trans.requested(force)) {
2228             return Result.err(Status.ERR_RoleNotFound, "Role [" + role.fullName() + "] does not exist");
2229         }
2230
2231         Result<FutureDAO.Data> fd = mapper.future(trans,RoleDAO.TABLE,from,role,false,
2232             () -> "Delete Role [" + role.fullName() + ']'
2233                     + " and all attached user roles",
2234             new MayChange() {
2235                 private Result<NsDAO.Data> nsd;
2236                 @Override
2237                 public Result<?> mayChange() {
2238                     if (nsd==null) {
2239                         nsd = ques.mayUser(trans, trans.user(), role, Access.write);
2240                     }
2241                     return nsd;
2242                 }
2243             });
2244         
2245         switch(fd.status) {
2246         case OK:
2247             Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, rd.value.ns);
2248             if (nsr.notOKorIsEmpty()) {
2249                 return Result.err(nsr);
2250             }
2251             
2252             Result<String> rfc = func.createFuture(trans, fd.value, 
2253                     role.encode(), trans.user(),nsr.value.get(0),FUTURE_OP.D);
2254             if (rfc.isOK()) {
2255                 return Result.err(Status.ACC_Future, "Role Deletion [%s.%s] is saved for future processing",
2256                         rd.value.ns,
2257                         rd.value.name);
2258             } else { 
2259                 return Result.err(rfc);
2260             }
2261         case Status.ACC_Now:
2262             return func.deleteRole(trans,role,trans.requested(force), true /*preapproved*/);
2263         default:
2264             return Result.err(fd);
2265     }
2266
2267     }
2268
2269 /***********************************
2270  * CRED 
2271  ***********************************/
2272     private class MayCreateCred implements MayChange {
2273         private Result<NsDAO.Data> nsd;
2274         private AuthzTrans trans;
2275         private CredDAO.Data cred;
2276         private Executor exec;
2277         
2278         public MayCreateCred(AuthzTrans trans, CredDAO.Data cred, Executor exec) {
2279             this.trans = trans;
2280             this.cred = cred;
2281             this.exec = exec;
2282         }
2283
2284         @Override
2285         public Result<?> mayChange() {
2286             if (nsd==null) {
2287                 nsd = ques.validNSOfDomain(trans, cred.id);
2288             }
2289             // is Ns of CredID valid?
2290             if (nsd.isOK()) {
2291                 try {
2292                     // Check Org Policy
2293                     if (trans.org().validate(trans,Policy.CREATE_MECHID, exec, cred.id)==null) {
2294                         return Result.ok(); 
2295                     } else {
2296                        Result<?> rmc = ques.mayUser(trans, trans.user(), nsd.value, Access.write);
2297                        if (rmc.isOKhasData()) {
2298                            return rmc;
2299                        }
2300                     }
2301                 } catch (Exception e) {
2302                     trans.warn().log(e);
2303                 }
2304             } else {
2305                 trans.warn().log(nsd.errorString());
2306             }
2307             return Result.err(Status.ERR_Denied,"%s is not allowed to create %s in %s",trans.user(),cred.id,cred.ns);
2308         }
2309     }
2310
2311     private class MayChangeCred implements MayChange {
2312         private static final String EXTEND = "extend";
2313         private static final String RESET = "reset";
2314         private static final String DELETE = "delete";
2315         private Result<NsDAO.Data> nsd;
2316         private AuthzTrans trans;
2317         private CredDAO.Data cred;
2318         private String action;
2319         public MayChangeCred(AuthzTrans trans, CredDAO.Data cred, String action) {
2320             this.trans = trans;
2321             this.cred = cred;
2322             this.action = action;
2323         }
2324
2325         @Override
2326         public Result<?> mayChange() {
2327             // User can change himself (but not create)
2328             if (nsd==null) {
2329                 nsd = ques.validNSOfDomain(trans, cred.id);
2330             }
2331             // Get the Namespace
2332             if (nsd.isOK()) {
2333                 String ns = nsd.value.name;
2334                 String user = trans.user();
2335                 String company;
2336                 String temp[] = Split.split('.',ns);
2337                 switch(temp.length) {
2338                     case 0:
2339                         company = Defaults.AAF_NS;
2340                         break;
2341                     case 1:
2342                         company = temp[0];
2343                         break;
2344                     default:
2345                         company = temp[0] + '.' + temp[1];
2346                 }
2347                 switch(action) {
2348                     case DELETE:
2349                         if(ques.isOwner(trans, user,ns) ||
2350                                 ques.isAdmin(trans, user,ns) ||
2351                                 ques.isGranted(trans, user, ROOT_NS,"password",company,DELETE)) {
2352                                      return Result.ok();
2353                         }
2354                         break;
2355                     case RESET:
2356                     case EXTEND:
2357                         if (ques.isGranted(trans, trans.user(), ROOT_NS,"password",company,action)) {
2358                             return Result.ok();
2359                         }
2360                         break;
2361                 }
2362             }
2363             return Result.err(Status.ERR_Denied,"%s is not allowed to %s %s in %s",trans.user(),action,cred.id,cred.ns);
2364         }
2365     }
2366
2367     private final long DAY_IN_MILLIS = 24*3600*1000L;
2368     
2369     @ApiDoc( 
2370             method = POST,  
2371             path = "/authn/cred",
2372             params = {},
2373             expectedCode = 201,
2374             errorCodes = {403,404,406,409}, 
2375             text = { "A credential consists of:",
2376                      "<ul><li>id - the ID to create within AAF. The domain is in reverse",
2377                      "order of Namespace (i.e. Users of Namespace com.att.myapp would be",
2378                      "AB1234@myapp.att.com</li>",
2379                      "<li>password - Company Policy Compliant Password</li></ul>",
2380                      "Note: AAF does support multiple credentials with the same ID.",
2381                      "Check with your organization if you have this implemented."
2382                      }
2383             )
2384     @Override
2385     public Result<Void> createUserCred(final AuthzTrans trans, REQUEST from) {
2386         final String cmdDescription = ("Create User Credential");
2387         TimeTaken tt = trans.start(cmdDescription, Env.SUB);
2388         
2389         try {
2390             Result<CredDAO.Data> rcred = mapper.cred(trans, from, true);
2391             if (rcred.isOKhasData()) {
2392                 rcred = ques.userCredSetup(trans, rcred.value);
2393                 
2394                 final ServiceValidator v = new ServiceValidator();
2395                 
2396                 if (v.cred(trans, trans.org(),rcred,true).err()) { // Note: Creates have stricter Validations 
2397                     return Result.err(Status.ERR_BadData,v.errs());
2398                 }
2399                 
2400
2401                 // 2016-4 Jonathan, New Behavior - If MechID is not registered with Org, deny creation
2402                 Identity mechID =  null;
2403                 Organization org = trans.org();
2404                 try {
2405                     mechID = org.getIdentity(trans, rcred.value.id);
2406                 } catch (Exception e1) {
2407                     trans.error().log(e1,rcred.value.id,"cannot be validated at this time");
2408                 }
2409                 if (mechID==null || !mechID.isFound()) { 
2410                     return Result.err(Status.ERR_Policy,"MechIDs must be registered with %s before provisioning in AAF",org.getName());
2411                 }
2412
2413                 Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, rcred.value.ns);
2414                 if (nsr.notOKorIsEmpty()) {
2415                     return Result.err(Status.ERR_NsNotFound,"Cannot provision %s on non-existent Namespace %s",mechID.id(),rcred.value.ns);
2416                 }
2417                 
2418
2419                 boolean firstID = false;
2420                 MayChange mc;
2421                 
2422                 CassExecutor exec = new CassExecutor(trans, func);
2423                 Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, rcred.value.id);
2424                 if (rlcd.isOKhasData()) {
2425                     if (!org.canHaveMultipleCreds(rcred.value.id)) {
2426                         return Result.err(Status.ERR_ConflictAlreadyExists, "Credential exists");
2427                     }
2428                     Result<Boolean> rb;
2429                     for (CredDAO.Data curr : rlcd.value) {
2430                         // May not use the same password in the list
2431                         // Note: ASPR specifies character differences, but we don't actually store the
2432                         // password to validate char differences.
2433                         
2434 //                      byte[] rawCred = rcred.value.type==CredDAO.RAW?null:;                            return Result.err(Status.ERR_ConflictAlreadyExists, "Credential with same Expiration Date exists");
2435                         if(rcred.value.type==CredDAO.FQI ) {
2436                             if(curr.type==CredDAO.FQI) {
2437                                 return Result.err(Status.ERR_ConflictAlreadyExists, "Credential with same Expiration Date exists");
2438                             }
2439                         } else {
2440     
2441                             rb = ques.userCredCheck(trans, curr, rcred.value.cred!=null?rcred.value.cred.array():null);
2442                             if (rb.notOK()) {
2443                                 return Result.err(rb);
2444                             } else if (rb.value){
2445                                 return Result.err(Status.ERR_Policy, "Credential content cannot be reused.");
2446                             } else if(Chrono.dateOnlyStamp(curr.expires).equals(Chrono.dateOnlyStamp(rcred.value.expires)) 
2447                                     && curr.type==rcred.value.type 
2448                                     ) {
2449                                 // Allow if expiring differential is greater than 1 day (for TEMP)
2450                                 // Unless expiring in 1 day
2451                                 if(System.currentTimeMillis() - rcred.value.expires.getTime() > TimeUnit.DAYS.toMillis(1)) {
2452                                     return Result.err(Status.ERR_ConflictAlreadyExists, "Credential with same Expiration Date exists");
2453                                 }
2454                             }
2455                         }
2456                     }    
2457                 } else {
2458                     try {
2459                     // 2016-04-12 Jonathan If Caller is the Sponsor and is also an Owner of NS, allow without special Perm
2460                         String theMechID = rcred.value.id;
2461                         Boolean otherMechIDs = false;
2462                         // find out if this is the only mechID.  other MechIDs mean special handling (not automated)
2463                         for (CredDAO.Data cd : ques.credDAO().readNS(trans,nsr.value.get(0).name).value) {
2464                             if (!cd.id.equals(theMechID)) {
2465                                 otherMechIDs = true;
2466                                 break;
2467                             }
2468                         }
2469                         String reason;
2470                         // We can say "ID does not exist" here
2471                         if ((reason=org.validate(trans, Policy.CREATE_MECHID, exec, theMechID,trans.user(),otherMechIDs.toString()))!=null) {
2472                             return Result.err(Status.ERR_Denied, reason); 
2473                         }
2474                         firstID=true;
2475                     } catch (Exception e) {
2476                         return Result.err(e);
2477                     }
2478                 }
2479     
2480                 mc = new MayCreateCred(trans, rcred.value, exec);
2481                 
2482                 final CredDAO.Data cdd = rcred.value;
2483                 Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from, rcred.value,false, // may want to enable in future.
2484                     new Mapper.Memo() {
2485                         @Override
2486                         public String get() {
2487                             return cmdDescription + " [" + 
2488                                 cdd.id + '|' 
2489                                 + cdd.type + '|' 
2490                                 + cdd.expires + ']';
2491                         }
2492                     },
2493                     mc);
2494                 
2495                 switch(fd.status) {
2496                     case OK:
2497                         Result<String> rfc = func.createFuture(trans, fd.value, 
2498                                 rcred.value.id + '|' + rcred.value.type.toString() + '|' + rcred.value.expires,
2499                                 trans.user(), nsr.value.get(0), FUTURE_OP.C);
2500                         if (rfc.isOK()) {
2501                             return Result.err(Status.ACC_Future, "Credential Request [%s|%s|%s] is saved for future processing",
2502                                     rcred.value.id,
2503                                     Integer.toString(rcred.value.type),
2504                                     rcred.value.expires.toString());
2505                         } else { 
2506                             return Result.err(rfc);
2507                         }
2508                     case Status.ACC_Now:
2509                         try {
2510                             if (firstID) {
2511                                 // OK, it's a first ID, and not by NS Owner
2512                                 if(!ques.isOwner(trans,trans.user(),cdd.ns)) {
2513                                     // Admins are not allowed to set first Cred, but Org has already
2514                                     // said entity MAY create, typically by Permission
2515                                     // We can't know which reason they are allowed here, so we 
2516                                     // have to assume that any with Special Permission would not be 
2517                                     // an Admin.
2518                                     if(ques.isAdmin(trans, trans.user(), cdd.ns)) {
2519                                         return Result.err(Result.ERR_Denied, 
2520                                             "Only Owners may create first passwords in their Namespace. Admins may modify after one exists" );
2521                                     } else {
2522                                         // Allow IDs that AREN'T part of NS with Org Onboarding Permission  (see Org object) to create Temp Passwords.
2523                                         rcred.value.expires = org.expiration(null, Expiration.TempPassword).getTime();
2524                                     }
2525                                 }
2526                             }
2527                         } catch (Exception e) {
2528                             trans.error().log(e, "While setting expiration to TempPassword");
2529                         }
2530                         
2531                         Result<?>udr = ques.credDAO().create(trans, rcred.value);
2532                         if (udr.isOK()) {
2533                             return Result.ok();
2534                         }
2535                         return Result.err(udr);
2536                     default:
2537                         return Result.err(fd);
2538                 }
2539
2540             } else {
2541                 return Result.err(rcred);
2542             }
2543         } finally {
2544             tt.done();
2545         }
2546     }
2547
2548     @ApiDoc(   
2549             method = GET,  
2550             path = "/authn/creds/ns/:ns",
2551             params = {"ns|string|true"},
2552             expectedCode = 200,
2553             errorCodes = {403,404,406}, 
2554             text = { "Return all IDs in Namespace :ns"
2555                      }
2556             )
2557     @Override
2558     public Result<USERS> getCredsByNS(AuthzTrans trans, String ns) {
2559         final Validator v = new ServiceValidator();
2560         if (v.ns(ns).err()) {
2561             return Result.err(Status.ERR_BadData,v.errs());
2562         }
2563         
2564         // check if user is allowed to view NS
2565         Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
2566         if (rnd.notOK()) {
2567             return Result.err(rnd); 
2568         }
2569         rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
2570         if (rnd.notOK()) {
2571             return Result.err(rnd); 
2572         }
2573     
2574         TimeTaken tt = trans.start("MAP Creds by NS to Creds", Env.SUB);
2575         try {            
2576             USERS users = mapper.newInstance(API.USERS);
2577             Result<List<CredDAO.Data>> rlcd = ques.credDAO().readNS(trans, ns);
2578                     
2579             if (rlcd.isOK()) {
2580                 if (!rlcd.isEmpty()) {
2581                     return mapper.cred(rlcd.value, users);
2582                 }
2583                 return Result.ok(users);        
2584             } else {
2585                 return Result.err(rlcd);
2586             }
2587         } finally {
2588             tt.done();
2589         }
2590             
2591     }
2592
2593     @ApiDoc(   
2594             method = GET,  
2595             path = "/authn/creds/id/:ns",
2596             params = {"id|string|true"},
2597             expectedCode = 200,
2598             errorCodes = {403,404,406}, 
2599             text = { "Return all IDs in for ID"
2600                     ,"(because IDs are multiple, due to multiple Expiration Dates)"
2601                      }
2602             )
2603     @Override
2604     public Result<USERS> getCredsByID(AuthzTrans trans, String id) {
2605         final Validator v = new ServiceValidator();
2606         if (v.nullOrBlank("ID",id).err()) {
2607             return Result.err(Status.ERR_BadData,v.errs());
2608         }
2609         
2610         String ns = Question.domain2ns(id);
2611         // check if user is allowed to view NS
2612         Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
2613         if (rnd.notOK()) {
2614             return Result.err(rnd); 
2615         }
2616         rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
2617         if (rnd.notOK()) {
2618             return Result.err(rnd); 
2619         }
2620     
2621         TimeTaken tt = trans.start("MAP Creds by ID to Creds", Env.SUB);
2622         try {            
2623             USERS users = mapper.newInstance(API.USERS);
2624             Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, id);
2625                     
2626             if (rlcd.isOK()) {
2627                 if (!rlcd.isEmpty()) {
2628                     return mapper.cred(rlcd.value, users);
2629                 }
2630                 return Result.ok(users);        
2631             } else {
2632                 return Result.err(rlcd);
2633             }
2634         } finally {
2635             tt.done();
2636         }
2637             
2638     }
2639
2640     @ApiDoc(   
2641             method = GET,  
2642             path = "/authn/certs/id/:id",
2643             params = {"id|string|true"},
2644             expectedCode = 200,
2645             errorCodes = {403,404,406}, 
2646             text = { "Return Cert Info for ID"
2647                    }
2648             )
2649     @Override
2650     public Result<CERTS> getCertInfoByID(AuthzTrans trans, HttpServletRequest req, String id) {
2651         TimeTaken tt = trans.start("Get Cert Info by ID", Env.SUB);
2652         try {            
2653             CERTS certs = mapper.newInstance(API.CERTS);
2654             Result<List<CertDAO.Data>> rlcd = ques.certDAO().readID(trans, id);
2655                     
2656             if (rlcd.isOK()) {
2657                 if (!rlcd.isEmpty()) {
2658                     return mapper.cert(rlcd.value, certs);
2659                 }
2660                 return Result.ok(certs);        
2661             } else { 
2662                 return Result.err(rlcd);
2663             }
2664         } finally {
2665             tt.done();
2666         }
2667
2668     }
2669
2670     @ApiDoc( 
2671             method = PUT,  
2672             path = "/authn/cred",
2673             params = {},
2674             expectedCode = 200,
2675             errorCodes = {300,403,404,406}, 
2676             text = { "Reset a Credential Password. If multiple credentials exist for this",
2677                         "ID, you will need to specify which entry you are resetting in the",
2678                         "CredRequest object"
2679                      }
2680             )
2681     @Override
2682     public Result<Void> resetUserCred(final AuthzTrans trans, REQUEST from) {
2683         final String cmdDescription = "Update User Credential";
2684         TimeTaken tt = trans.start(cmdDescription, Env.SUB);
2685         try {
2686             Result<CredDAO.Data> rcred = mapper.cred(trans, from, true);
2687             if (rcred.isOKhasData()) {
2688                 rcred = ques.userCredSetup(trans, rcred.value);
2689     
2690                 final ServiceValidator v = new ServiceValidator();
2691                 
2692                 if (v.cred(trans, trans.org(),rcred,false).err()) {// Note: Creates have stricter Validations 
2693                     return Result.err(Status.ERR_BadData,v.errs());
2694                 }
2695                 Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, rcred.value.id);
2696                 if (rlcd.notOKorIsEmpty()) {
2697                     return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
2698                 } 
2699                 
2700                 MayChange mc = new MayChangeCred(trans, rcred.value,MayChangeCred.RESET);
2701                 Result<?> rmc = mc.mayChange(); 
2702                 if (rmc.notOK()) {
2703                     return Result.err(rmc);
2704                 }
2705                 
2706                 List<CredDAO.Data> lcdd = filterList(rlcd.value,CredDAO.BASIC_AUTH, CredDAO.BASIC_AUTH_SHA256);
2707                 
2708                 Result<Integer> ri = selectEntryIfMultiple((CredRequest)from, lcdd, MayChangeCred.RESET);
2709                 if (ri.notOK()) {
2710                     return Result.err(ri);
2711                 }
2712                 int entry = ri.value;
2713     
2714                 
2715                 final CredDAO.Data cred = rcred.value;
2716                 
2717                 Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from, rcred.value,false,
2718                 new Mapper.Memo() {
2719                     @Override
2720                     public String get() {
2721                         return cmdDescription + " [" + 
2722                             cred.id + '|' 
2723                             + cred.type + '|' 
2724                             + cred.expires + ']';
2725                     }
2726                 },
2727                 mc);
2728                 
2729                 Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, rcred.value.ns);
2730                 if (nsr.notOKorIsEmpty()) {
2731                     return Result.err(nsr);
2732                 }
2733     
2734                 switch(fd.status) {
2735                     case OK:
2736                         Result<String> rfc = func.createFuture(trans, fd.value, 
2737                                 rcred.value.id + '|' + rcred.value.type.toString() + '|' + rcred.value.expires,
2738                                 trans.user(), nsr.value.get(0), FUTURE_OP.U);
2739                         if (rfc.isOK()) {
2740                             return Result.err(Status.ACC_Future, "Credential Request [%s|%s|%s]",
2741                                     rcred.value.id,
2742                                     Integer.toString(rcred.value.type),
2743                                     rcred.value.expires.toString());
2744                         } else { 
2745                             return Result.err(rfc);
2746                         }
2747                     case Status.ACC_Now:
2748                         Result<?>udr = null;
2749                         // If we are Resetting Password on behalf of someone else (am not the Admin)
2750                         //  use TempPassword Expiration time.
2751                         Expiration exp;
2752                         if (ques.isAdmin(trans, trans.user(), nsr.value.get(0).name)) {
2753                             exp = Expiration.Password;
2754                         } else {
2755                             exp = Expiration.TempPassword;
2756                         }
2757                         
2758                         Organization org = trans.org();
2759                         CredDAO.Data current = rlcd.value.get(entry);
2760                         // If user resets password in same day, we will have a primary key conflict, so subtract 1 day
2761                         if (current.expires.equals(rcred.value.expires) 
2762                                     && rlcd.value.get(entry).type==rcred.value.type) {
2763                             GregorianCalendar gc = org.expiration(null, exp,rcred.value.id);
2764                             gc = Chrono.firstMomentOfDay(gc);
2765                             gc.set(GregorianCalendar.HOUR_OF_DAY, org.startOfDay());                        
2766                             rcred.value.expires = new Date(gc.getTimeInMillis() - DAY_IN_MILLIS);
2767                         } else {
2768                             rcred.value.expires = org.expiration(null,exp).getTime();
2769                         }
2770
2771                         udr = ques.credDAO().create(trans, rcred.value);
2772                         if (udr.isOK()) {
2773                             udr = ques.credDAO().delete(trans, rlcd.value.get(entry),false);
2774                         }
2775                         if (udr.isOK()) {
2776                             return Result.ok();
2777                         }
2778     
2779                         return Result.err(udr);
2780                     default:
2781                         return Result.err(fd);
2782                 }
2783             } else {
2784                 return Result.err(rcred);
2785             }
2786         } finally {
2787             tt.done();
2788         }
2789     }
2790
2791     @ApiDoc( 
2792             method = PUT,  
2793             path = "/authn/cred/:days",
2794             params = {"days|string|true"},
2795             expectedCode = 200,
2796             errorCodes = {300,403,404,406}, 
2797             text = { "Extend a Credential Expiration Date. The intention of this API is",
2798                         "to avoid an outage in PROD due to a Credential expiring before it",
2799                         "can be configured correctly. Measures are being put in place ",
2800                         "so that this is not abused."
2801                      }
2802             )
2803     @Override
2804     public Result<Void> extendUserCred(final AuthzTrans trans, REQUEST from, String days) {
2805         TimeTaken tt = trans.start("Extend User Credential", Env.SUB);
2806         try {
2807             Result<CredDAO.Data> cred = mapper.cred(trans, from, false);
2808             Organization org = trans.org();
2809             final ServiceValidator v = new ServiceValidator();
2810             if (v.notOK(cred).err() || 
2811                v.nullOrBlank(cred.value.id, "Invalid ID").err() ||
2812                v.user(org,cred.value.id).err())  {
2813                  return Result.err(Status.ERR_BadData,v.errs());
2814             }
2815             
2816             try {
2817                 String reason;
2818                 if ((reason=org.validate(trans, Policy.MAY_EXTEND_CRED_EXPIRES, new CassExecutor(trans,func)))!=null) {
2819                     return Result.err(Status.ERR_Policy,reason);
2820                 }
2821             } catch (Exception e) {
2822                 String msg;
2823                 trans.error().log(e, msg="Could not contact Organization for User Validation");
2824                 return Result.err(Status.ERR_Denied, msg);
2825             }
2826     
2827             // Get the list of Cred Entries
2828             Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, cred.value.id);
2829             if (rlcd.notOKorIsEmpty()) {
2830                 return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
2831             }
2832             
2833             // Only Passwords can be extended
2834             List<CredDAO.Data> lcdd = filterList(rlcd.value,CredDAO.BASIC_AUTH, CredDAO.BASIC_AUTH_SHA256);
2835
2836             //Need to do the "Pick Entry" mechanism
2837             // Note, this sorts
2838             Result<Integer> ri = selectEntryIfMultiple((CredRequest)from, lcdd, MayChangeCred.EXTEND);
2839             if (ri.notOK()) {
2840                 return Result.err(ri);
2841             }
2842
2843             CredDAO.Data found = lcdd.get(ri.value);
2844             CredDAO.Data cd = cred.value;
2845             // Copy over the cred
2846             cd.id = found.id;
2847             cd.cred = found.cred;
2848             cd.other = found.other;
2849             cd.type = found.type;
2850             cd.ns = found.ns;
2851             cd.notes = "Extended";
2852             cd.tag = found.tag;
2853             cd.expires = org.expiration(null, Expiration.ExtendPassword,days).getTime();
2854             if(cd.expires.before(found.expires)) {
2855                 return Result.err(Result.ERR_BadData,String.format("Credential's expiration date is more than %s days in the future",days));
2856             }
2857             
2858             cred = ques.credDAO().create(trans, cd);
2859             if (cred.isOK()) {
2860                 return Result.ok();
2861             }
2862             return Result.err(cred);
2863         } finally {
2864             tt.done();
2865         }
2866     }    
2867
2868     @ApiDoc( 
2869             method = DELETE,  
2870             path = "/authn/cred",
2871             params = {},
2872             expectedCode = 200,
2873             errorCodes = {300,403,404,406}, 
2874             text = { "Delete a Credential. If multiple credentials exist for this",
2875                     "ID, you will need to specify which entry you are deleting in the",
2876                     "CredRequest object."
2877                      }
2878             )
2879     @Override
2880     public Result<Void> deleteUserCred(AuthzTrans trans, REQUEST from)  {
2881         final Result<CredDAO.Data> cred = mapper.cred(trans, from, false);
2882         final Validator v = new ServiceValidator();
2883         if (v.nullOrBlank("cred", cred.value.id).err()) {
2884             return Result.err(Status.ERR_BadData,v.errs());
2885         }
2886
2887         MayChange mc = new MayChangeCred(trans,cred.value,MayChangeCred.DELETE);
2888         Result<?> rmc = mc.mayChange(); 
2889         if (rmc.notOK()) {
2890             return Result.err(rmc);
2891         }
2892         
2893         boolean doForce = trans.requested(force);
2894         Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, cred.value.id);
2895         if (rlcd.notOKorIsEmpty()) {
2896             // Empty Creds should not have user_roles.
2897             Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByUser(trans, cred.value.id);
2898             if (rlurd.isOKhasData()) {
2899                 for (UserRoleDAO.Data data : rlurd.value) {
2900                     ques.userRoleDAO().delete(trans, data, false);
2901                 }
2902             }
2903             return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
2904         }
2905         boolean isLastCred = rlcd.value.size()==1;
2906         
2907         int entry;
2908         CredRequest cr = (CredRequest)from;
2909         if(isLastCred) {
2910             if(cr.getEntry()==null || "1".equals(cr.getEntry())) {
2911                 entry = 0;
2912             } else {
2913                 return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
2914             }
2915         } else {
2916             entry = -1;
2917             int fentry = entry;
2918             if(cred.value.type==CredDAO.FQI) {
2919                 entry = -1;
2920                 for(CredDAO.Data cdd : rlcd.value) {
2921                     ++fentry;
2922                     if(cdd.type == CredDAO.FQI) {
2923                         entry = fentry;
2924                         break; 
2925                     }
2926                 }
2927             } else {
2928                 if (!doForce) {
2929                     if (rlcd.value.size() > 1) {
2930                         String inputOption = cr.getEntry();
2931                         if (inputOption == null) {
2932                             List<CredDAO.Data> list = filterList(rlcd.value,CredDAO.BASIC_AUTH,CredDAO.BASIC_AUTH_SHA256,CredDAO.CERT_SHA256_RSA);
2933                             String message = selectCredFromList(list, MayChangeCred.DELETE);
2934                             Object[] variables = buildVariables(list);
2935                             return Result.err(Status.ERR_ChoiceNeeded, message, variables);
2936                         } else {
2937                             try {
2938                                 if (inputOption.length()>5) { // should be a date
2939                                     Date d = Chrono.xmlDatatypeFactory.newXMLGregorianCalendar(inputOption).toGregorianCalendar().getTime();
2940                                     for (CredDAO.Data cd : rlcd.value) {
2941                                         ++fentry;
2942                                         if (cd.type.equals(cr.getType()) && cd.expires.equals(d)) {
2943                                             entry = fentry;
2944                                             break;
2945                                         }
2946                                     }
2947                                 } else {
2948                                     entry = Integer.parseInt(inputOption) - 1;
2949                                     int count = 0;
2950                                     for (CredDAO.Data cd : rlcd.value) {
2951                                         if(cd.type!=CredDAO.BASIC_AUTH && cd.type!=CredDAO.BASIC_AUTH_SHA256 && cd.type!=CredDAO.CERT_SHA256_RSA) {
2952                                             ++entry;
2953                                         }
2954                                         if(++count>entry) {
2955                                             break;
2956                                         }
2957                                     }
2958                                 }
2959                             } catch (NullPointerException e) {
2960                                 return Result.err(Status.ERR_BadData, "Invalid Date Format for Entry");
2961                             } catch (NumberFormatException e) {
2962                                 return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
2963                             }
2964                         }
2965                         isLastCred = (entry==-1);
2966                     } else {
2967                         isLastCred = true;
2968                     }
2969                     if (entry < -1 || entry >= rlcd.value.size()) {
2970                         return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
2971                     }
2972                 }
2973             }
2974         }
2975         
2976         Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from,cred.value,false,
2977             () -> "Delete Credential [" +
2978                 cred.value.id +
2979                 ']',
2980             mc);
2981     
2982         Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, cred.value.ns);
2983         if (nsr.notOKorIsEmpty()) {
2984             return Result.err(nsr);
2985         }
2986     
2987         switch(fd.status) {
2988             case OK:
2989                 Result<String> rfc = func.createFuture(trans, fd.value, cred.value.id,
2990                         trans.user(), nsr.value.get(0), FUTURE_OP.D);
2991     
2992                 if (rfc.isOK()) {
2993                     return Result.err(Status.ACC_Future, "Credential Delete [%s] is saved for future processing",cred.value.id);
2994                 } else { 
2995                     return Result.err(rfc);
2996                 }
2997             case Status.ACC_Now:
2998                 Result<?>udr = null;
2999                 if (!trans.requested(force)) {
3000                     if (entry<0 || entry >= rlcd.value.size()) {
3001                         if(cred.value.type==CredDAO.FQI) {
3002                             return Result.err(Status.ERR_BadData,"FQI does not exist");
3003                         } else {
3004                             return Result.err(Status.ERR_BadData,"Invalid Choice [" + entry + "] chosen for Delete [%s] is saved for future processing",cred.value.id);
3005                         }
3006                     }
3007                     udr = ques.credDAO().delete(trans, rlcd.value.get(entry),false);
3008                 } else {
3009                     for (CredDAO.Data curr : rlcd.value) {
3010                         udr = ques.credDAO().delete(trans, curr, false);
3011                         if (udr.notOK()) {
3012                             return Result.err(udr);
3013                         }
3014                     }
3015                 }
3016                 if (isLastCred) {
3017                     Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByUser(trans, cred.value.id);
3018                     if (rlurd.isOK()) {
3019                         for (UserRoleDAO.Data data : rlurd.value) {
3020                             ques.userRoleDAO().delete(trans, data, false);
3021                         }
3022                     }
3023                 }
3024                 if (udr==null) {
3025                     return Result.err(Result.ERR_NotFound,"No User Data found");
3026                 }
3027                 if (udr.isOK()) {
3028                     return Result.ok();
3029                 }
3030                 return Result.err(udr);
3031             default:
3032                 return Result.err(fd);
3033         }
3034     
3035     }
3036
3037     /*
3038      * Codify the way to get Either Choice Needed or actual Integer from Credit Request
3039      */
3040     private Result<Integer> selectEntryIfMultiple(final CredRequest cr, List<CredDAO.Data> lcd, String action) {
3041         int entry = 0;
3042         if (lcd.size() > 1) {
3043             String inputOption = cr.getEntry();
3044             if (inputOption == null) {
3045                 String message = selectCredFromList(lcd, action);
3046                 Object[] variables = buildVariables(lcd);
3047                 return Result.err(Status.ERR_ChoiceNeeded, message, variables);
3048             } else {
3049                 if(MayChangeCred.EXTEND.equals(action)) {
3050                     // might be Tag
3051                     if(inputOption.length()>4) { //Tag is at least 12
3052                         int e = 0;
3053                         CredDAO.Data last = null;
3054                         int lastIdx = -1;
3055                         for(CredDAO.Data cdd : lcd) {
3056                             if(inputOption.equals(cdd.tag)) {
3057                                 if(last==null) {
3058                                     last = cdd;
3059                                     lastIdx = e;
3060                                 } else {
3061                                     if(last.expires.before(cdd.expires)) {
3062                                         last = cdd;
3063                                         lastIdx = e;
3064                                     }
3065                                 }
3066                             }
3067                             ++e;
3068                         }
3069                         if(last!=null) {
3070                             return Result.ok(lastIdx);
3071                         }
3072                         return Result.err(Status.ERR_BadData, "User chose unknown Tag");
3073                     }
3074                 }
3075                 entry = Integer.parseInt(inputOption) - 1;
3076             }
3077             if (entry < 0 || entry >= lcd.size()) {
3078                 return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
3079             }
3080         }
3081         return Result.ok(entry);
3082     }
3083
3084     private List<CredDAO.Data> filterList(List<CredDAO.Data> orig, Integer ... types) {
3085         List<CredDAO.Data> rv = new ArrayList<>();
3086         for(CredDAO.Data cdd : orig) {
3087             if(cdd!=null) {
3088                 for(int t : types) {
3089                     if(t==cdd.type) {
3090                            rv.add(cdd);
3091                     }
3092                 }
3093             }
3094         }
3095         Collections.sort(rv, (o1,o2) -> {
3096             if(o1.type==o2.type) {
3097                 return o1.expires.compareTo(o2.expires);
3098             } else {
3099                 return o1.type.compareTo(o2.type);
3100             }
3101         });
3102         return rv;
3103     }
3104
3105     private String[] buildVariables(List<CredDAO.Data> value) {
3106         String [] vars = new String[value.size()];
3107         CredDAO.Data cdd;
3108         
3109         for (int i = 0; i < value.size(); i++) {
3110             cdd = value.get(i);
3111             vars[i] = cdd.id + TWO_SPACE + Define.getCredType(cdd.type) + TWO_SPACE + Chrono.niceUTCStamp(cdd.expires) + TWO_SPACE + cdd.tag;
3112         }
3113         return vars;
3114     }
3115     
3116     private String selectCredFromList(List<CredDAO.Data> value, String action) {
3117         StringBuilder errMessage = new StringBuilder();
3118         String userPrompt = MayChangeCred.DELETE.equals(action)?
3119                 "Select which cred to delete (set force=true to delete all):":
3120                 "Select which cred to " + action + ':';
3121         int numSpaces = value.get(0).id.length() - "Id".length();
3122         
3123         errMessage.append(userPrompt + '\n');
3124         errMessage.append("        ID");
3125         for (int i = 0; i < numSpaces; i++) {
3126             errMessage.append(' ');
3127         }
3128         errMessage.append("  Type  Expires               Tag " + '\n');
3129         for (int i=0;i<value.size();++i) {
3130             errMessage.append("    %s\n");
3131         }
3132         if(MayChangeCred.EXTEND.equals(action)) {
3133             errMessage.append("Run same command again with chosen entry or Tag as last parameter");
3134         } else {
3135             errMessage.append("Run same command again with chosen entry as last parameter");
3136         }
3137         return errMessage.toString();
3138         
3139     }
3140
3141     @Override
3142     public Result<Date> doesCredentialMatch(AuthzTrans trans, REQUEST credReq) {
3143         TimeTaken tt = trans.start("Does Credential Match", Env.SUB);
3144         try {
3145             // Note: Mapper assigns RAW type
3146             Result<CredDAO.Data> data = mapper.cred(trans, credReq,false);
3147             if (data.notOKorIsEmpty()) {
3148                 return Result.err(data);
3149             }
3150             CredDAO.Data cred = data.value;    // of the Mapped Cred
3151             if (cred.cred==null) {
3152                 return Result.err(Result.ERR_BadData,"No Password");
3153             } else {
3154                 return ques.doesUserCredMatch(trans, cred.id, cred.cred.array());
3155             }
3156
3157         } catch (DAOException e) {
3158             trans.error().log(e,"Error looking up cred");
3159             return Result.err(Status.ERR_Denied,"Credential does not match");
3160         } finally {
3161             tt.done();
3162         }
3163     }
3164
3165     @ApiDoc( 
3166             method = POST,  
3167             path = "/authn/validate",
3168             params = {},
3169             expectedCode = 200,
3170             errorCodes = { 403 }, 
3171             text = { "Validate a Credential given a Credential Structure.  This is a more comprehensive validation, can "
3172                     + "do more than BasicAuth as Credential types exp" }
3173             )
3174     @Override
3175     public Result<Date> validateBasicAuth(AuthzTrans trans, String basicAuth) {
3176         //TODO how to make sure people don't use this in browsers?  Do we care?
3177         TimeTaken tt = trans.start("Validate Basic Auth", Env.SUB);
3178         try {
3179             BasicPrincipal bp = new BasicPrincipal(basicAuth,trans.org().getRealm());
3180             Result<Date> rq = ques.doesUserCredMatch(trans, bp.getName(), bp.getCred());
3181             // Note: Only want to log problem, don't want to send back to end user
3182             if (rq.isOK()) {
3183                 return rq;
3184             } else {
3185                 trans.audit().log(rq.errorString());
3186             }
3187         } catch (Exception e) {
3188             trans.warn().log(e);
3189         } finally {
3190             tt.done();
3191         }
3192         return Result.err(Status.ERR_Denied,"Bad Basic Auth");
3193     }
3194
3195 @ApiDoc( 
3196             method = GET,  
3197             path = "/authn/basicAuth",
3198             params = {},
3199             expectedCode = 200,
3200             errorCodes = { 403 }, 
3201             text = { "!!!! DEPRECATED without X509 Authentication STOP USING THIS API BY DECEMBER 2017, or use Certificates !!!!\n" 
3202                     + "Use /authn/validate instead\n"
3203                     + "Note: Validate a Password using BasicAuth Base64 encoded Header. This HTTP/S call is intended as a fast"
3204                     + " User/Password lookup for Security Frameworks, and responds 200 if it passes BasicAuth "
3205                 + "security, and 403 if it does not." }
3206             )
3207     private void basicAuth() {
3208         // This is a place holder for Documentation.  The real BasicAuth API does not call Service.
3209     }
3210
3211 /***********************************
3212  * USER-ROLE 
3213  ***********************************/
3214     @ApiDoc( 
3215             method = POST,  
3216             path = "/authz/userRole",
3217             params = {},
3218             expectedCode = 201,
3219             errorCodes = {403,404,406,409}, 
3220             text = { "Create a UserRole relationship (add User to Role)",
3221                      "A UserRole is an object Representation of membership of a Role for limited time.",
3222                      "If a shorter amount of time for Role ownership is required, use the 'End' field.",
3223                      "** Note: Owners of Namespaces will be required to revalidate users in these roles ",
3224                      "before Expirations expire.  Namespace owners will be notified by email."
3225                    }
3226             )
3227     @Override
3228     public Result<Void> createUserRole(final AuthzTrans trans, REQUEST from) {
3229         TimeTaken tt = trans.start("Create UserRole", Env.SUB);
3230         try {
3231             Result<UserRoleDAO.Data> urr = mapper.userRole(trans, from);
3232             if (urr.notOKorIsEmpty()) {
3233                 return Result.err(urr);
3234             }
3235             final UserRoleDAO.Data userRole = urr.value;
3236             
3237             final ServiceValidator v = new ServiceValidator();
3238             if (v.user_role(trans.user(),userRole).err() ||
3239                v.user(trans.org(), userRole.user).err()) {
3240                 return Result.err(Status.ERR_BadData,v.errs());
3241             }
3242
3243
3244              
3245             // Check if user can change first
3246             Result<FutureDAO.Data> fd = mapper.future(trans,UserRoleDAO.TABLE,from,urr.value,true, // may request Approvals
3247                 () -> "Add User [" + userRole.user + "] to Role [" +
3248                         userRole.role +
3249                         ']',
3250                 new MayChange() {
3251                     private Result<NsDAO.Data> nsd;
3252                     @Override
3253                     public Result<?> mayChange() {
3254                         if(urr.value.role.startsWith(urr.value.user)) {
3255                             return Result.ok((NsDAO.Data)null);
3256                         }
3257                         if (nsd==null) {
3258                             RoleDAO.Data r = RoleDAO.Data.decode(userRole);
3259                             nsd = ques.mayUser(trans, trans.user(), r, Access.write);
3260                         }
3261                         return nsd;
3262                     }
3263                 });
3264             
3265             NsDAO.Data ndd;
3266             if(userRole.role.startsWith(userRole.user)) {
3267                 userRole.ns=userRole.user;
3268                 userRole.rname="user";
3269                 ndd = null;
3270             } else {
3271                 Result<NsDAO.Data> nsr = ques.deriveNs(trans, userRole.role);
3272                 if (nsr.notOK()) {
3273                     return Result.err(nsr);
3274                 }
3275                 ndd = nsr.value;
3276             }
3277
3278             switch(fd.status) {
3279                 case OK:
3280                     Result<String> rfc = func.createFuture(trans, fd.value, userRole.user+'|'+userRole.ns + '.' + userRole.rname, 
3281                             userRole.user, ndd, FUTURE_OP.C);
3282                     if (rfc.isOK()) {
3283                         return Result.err(Status.ACC_Future, "UserRole [%s - %s.%s] is saved for future processing",
3284                                 userRole.user,
3285                                 userRole.ns,
3286                                 userRole.rname);
3287                     } else { 
3288                         return Result.err(rfc);
3289                     }
3290                 case Status.ACC_Now:
3291                     return func.addUserRole(trans, userRole);
3292                 default:
3293                     return Result.err(fd);
3294             }
3295         } finally {
3296             tt.done();
3297         }
3298     }
3299     
3300         /**
3301          * getUserRolesByRole
3302          */
3303         @ApiDoc(
3304                 method = GET,
3305                 path = "/authz/userRoles/role/:role",
3306                 params = {"role|string|true"},
3307                 expectedCode = 200,
3308                 errorCodes = {404,406},
3309                 text = { "List all Users that are attached to Role specified in :role",
3310                         }
3311                )
3312         @Override
3313         public Result<USERROLES> getUserRolesByRole(AuthzTrans trans, String role) {
3314             final Validator v = new ServiceValidator();
3315             if (v.nullOrBlank("Role",role).err()) {
3316                 return Result.err(Status.ERR_BadData,v.errs());
3317             }
3318             
3319             Result<RoleDAO.Data> rrdd;
3320             rrdd = RoleDAO.Data.decode(trans,ques,role);
3321             if (rrdd.notOK()) {
3322                 return Result.err(rrdd);
3323             }
3324             // May Requester see result?
3325             Result<NsDAO.Data> ns = ques.mayUser(trans,trans.user(), rrdd.value,Access.read);
3326             if (ns.notOK()) {
3327                 return Result.err(ns);
3328             }
3329     
3330     //        boolean filter = true;        
3331     //        if (ns.value.isAdmin(trans.user()) || ns.value.isResponsible(trans.user()))
3332     //            filter = false;
3333             
3334             // Get list of roles per user, then add to Roles as we go
3335             HashSet<UserRoleDAO.Data> userSet = new HashSet<>();
3336             Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByRole(trans, role);
3337             if (rlurd.isOK()) {
3338                 for (UserRoleDAO.Data data : rlurd.value) {
3339                     userSet.add(data);
3340                 }
3341             }
3342             
3343             @SuppressWarnings("unchecked")
3344             USERROLES users = (USERROLES) mapper.newInstance(API.USER_ROLES);
3345             // Checked for permission
3346             mapper.userRoles(trans, userSet, users);
3347             return Result.ok(users);
3348         }
3349         /**
3350          * getUserRolesByRole
3351          */
3352         @ApiDoc(
3353                 method = GET,
3354                 path = "/authz/userRoles/user/:user",
3355                 params = {"role|string|true"},
3356                 expectedCode = 200,
3357                 errorCodes = {404,406},
3358                 text = { "List all UserRoles for :user",
3359                         }
3360                )
3361         @Override
3362         public Result<USERROLES> getUserRolesByUser(AuthzTrans trans, String user) {
3363             final Validator v = new ServiceValidator();
3364             if (v.nullOrBlank("User",user).err()) {
3365                 return Result.err(Status.ERR_BadData,v.errs());
3366             }
3367             
3368             // Get list of roles per user, then add to Roles as we go
3369             Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByUser(trans, user);
3370             if (rlurd.notOK()) { 
3371                 return Result.err(rlurd);
3372             }
3373             
3374             /* Check for
3375              *   1) is User 
3376              *   2) is User's Supervisor
3377              *   3) Has special global access =read permission
3378              *   
3379              *   If none of the 3, then filter results to NSs in which Calling User has Ns.access * read
3380              */
3381             boolean mustFilter;
3382             String callingUser = trans.getUserPrincipal().getName();
3383             NsDAO.Data ndd = new NsDAO.Data();
3384
3385             if (user.equals(callingUser)) {
3386                 mustFilter = false;
3387             } else {
3388                 Organization org = trans.org();
3389                 try {
3390                     Identity orgID = org.getIdentity(trans, user);
3391                     Identity manager = orgID==null?null:orgID.responsibleTo();
3392                     if (orgID!=null && (manager!=null && callingUser.equals(manager.fullID()))) {
3393                         mustFilter = false;
3394                     } else if (ques.isGranted(trans, callingUser, ROOT_NS, Question.ACCESS, "*", Access.read.name())) {
3395                         mustFilter=false;
3396                     } else {
3397                         mustFilter = true;
3398                     }
3399                 } catch (OrganizationException e) {
3400                     trans.env().log(e);
3401                     mustFilter = true;
3402                 }
3403             }
3404             
3405             List<UserRoleDAO.Data> content;
3406             if (mustFilter) {
3407                 content = new ArrayList<>(rlurd.value.size()); // avoid multi-memory redos
3408                 
3409                 for (UserRoleDAO.Data data : rlurd.value) {
3410                     ndd.name=data.ns;
3411                     Result<Data> mur = ques.mayUser(trans, callingUser, ndd, Access.read);
3412                     if (mur.isOK()){
3413                         content.add(data);
3414                     }
3415                 }
3416                 
3417             } else {
3418                 content = rlurd.value;
3419             }
3420
3421
3422             @SuppressWarnings("unchecked")
3423             USERROLES users = (USERROLES) mapper.newInstance(API.USER_ROLES);
3424             // Checked for permission
3425             mapper.userRoles(trans, content, users);
3426             return Result.ok(users);
3427         }
3428
3429         
3430  
3431     
3432      @ApiDoc(
3433             method = GET,
3434             path = "/authz/userRole/extend/:user/:role",
3435             params = {    "user|string|true",
3436                         "role|string|true"
3437                     },
3438             expectedCode = 200,
3439             errorCodes = {403,404,406},
3440             text = { "Extend the Expiration of this User Role by the amount set by Organization",
3441                      "Requestor must be allowed to modify the role"
3442                     }
3443            )
3444     @Override
3445     public Result<Void> extendUserRole(AuthzTrans trans, String user, String role) {
3446         Organization org = trans.org();
3447         final ServiceValidator v = new ServiceValidator();
3448         if (v.user(org, user)
3449             .role(role)
3450             .err()) {
3451             return Result.err(Status.ERR_BadData,v.errs());
3452         }
3453     
3454         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans,ques,role);
3455         if (rrdd.notOK()) {
3456             return Result.err(rrdd);
3457         }
3458         
3459         Result<NsDAO.Data> rcr = ques.mayUser(trans, trans.user(), rrdd.value, Access.write);
3460         boolean mayNotChange;
3461         if ((mayNotChange = rcr.notOK()) && !trans.requested(future)) {
3462             return Result.err(rcr);
3463         }
3464         
3465         Result<List<UserRoleDAO.Data>> rr = ques.userRoleDAO().read(trans, user,role);
3466         if (rr.notOK()) {
3467             return Result.err(rr);
3468         }
3469         for (UserRoleDAO.Data userRole : rr.value) {
3470             if (mayNotChange) { // Function exited earlier if !trans.futureRequested
3471                 FutureDAO.Data fto = new FutureDAO.Data();
3472                 fto.target=UserRoleDAO.TABLE;
3473                 fto.memo = "Extend User ["+userRole.user+"] in Role ["+userRole.role+"]";
3474                 GregorianCalendar now = new GregorianCalendar();
3475                 fto.start = now.getTime();
3476                 fto.expires = org.expiration(now, Expiration.Future).getTime();
3477                 try {
3478                     fto.construct = userRole.bytify();
3479                 } catch (IOException e) {
3480                     trans.error().log(e, "Error while bytifying UserRole for Future");
3481                     return Result.err(e);
3482                 }
3483
3484                 Result<String> rfc = func.createFuture(trans, fto, 
3485                         userRole.user+'|'+userRole.role, userRole.user, rcr.value, FUTURE_OP.U);
3486                 if (rfc.isOK()) {
3487                     return Result.err(Status.ACC_Future, "UserRole [%s - %s] is saved for future processing",
3488                             userRole.user,
3489                             userRole.role);
3490                 } else {
3491                     return Result.err(rfc);
3492                 }
3493             } else {
3494                 return func.extendUserRole(trans, userRole, false);
3495             }
3496         }
3497         return Result.err(Result.ERR_NotFound,"This user and role doesn't exist");
3498     }
3499
3500     @ApiDoc( 
3501             method = DELETE,  
3502             path = "/authz/userRole/:user/:role",
3503             params = {    "user|string|true",
3504                         "role|string|true"
3505                     },
3506             expectedCode = 200,
3507             errorCodes = {403,404,406}, 
3508             text = { "Remove Role :role from User :user."
3509                    }
3510             )
3511     @Override
3512     public Result<Void> deleteUserRole(AuthzTrans trans, String usr, String role) {
3513         Validator val = new ServiceValidator();
3514         if (val.nullOrBlank("User", usr)
3515               .nullOrBlank("Role", role).err()) {
3516             return Result.err(Status.ERR_BadData, val.errs());
3517         }
3518
3519         boolean mayNotChange;
3520         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans,ques,role);
3521         if (rrdd.notOK()) {
3522             return Result.err(rrdd);
3523         }
3524         
3525         RoleDAO.Data rdd = rrdd.value;
3526         Result<NsDAO.Data> rns = ques.mayUser(trans, trans.user(), rdd, Access.write);
3527
3528         // Make sure we don't delete the last owner of valid NS
3529         if (rns.isOKhasData() && Question.OWNER.equals(rdd.name) && ques.countOwner(trans,rdd.ns)<=1) {
3530             return Result.err(Status.ERR_Denied,"You may not delete the last Owner of " + rdd.ns );
3531         }
3532         
3533         if (mayNotChange=rns.notOK()) {
3534             if (!trans.requested(future)) {
3535                 return Result.err(rns);
3536             }
3537         }
3538
3539         Result<List<UserRoleDAO.Data>> rulr;
3540         if ((rulr=ques.userRoleDAO().read(trans, usr, role)).notOKorIsEmpty()) {
3541             return Result.err(Status.ERR_UserRoleNotFound, "User [ "+usr+" ] is not "
3542                     + "Assigned to the Role [ " + role + " ]");
3543         }
3544
3545         UserRoleDAO.Data userRole = rulr.value.get(0);
3546         if (mayNotChange) { // Function exited earlier if !trans.futureRequested
3547             FutureDAO.Data fto = new FutureDAO.Data();
3548             fto.target=UserRoleDAO.TABLE;
3549             fto.memo = "Remove User ["+userRole.user+"] from Role ["+userRole.role+"]";
3550             GregorianCalendar now = new GregorianCalendar();
3551             fto.start = now.getTime();
3552             fto.expires = trans.org().expiration(now, Expiration.Future).getTime();
3553
3554             Result<String> rfc = func.createFuture(trans, fto, 
3555                     userRole.user+'|'+userRole.role, userRole.user, rns.value, FUTURE_OP.D);
3556             if (rfc.isOK()) {
3557                 return Result.err(Status.ACC_Future, "UserRole [%s - %s] is saved for future processing", 
3558                         userRole.user,
3559                         userRole.role);
3560             } else { 
3561                 return Result.err(rfc);
3562             }
3563         } else {
3564             return ques.userRoleDAO().delete(trans, rulr.value.get(0), false);
3565         }
3566     }
3567
3568     @ApiDoc( 
3569             method = GET,  
3570             path = "/authz/userRole/:user/:role",
3571             params = {"user|string|true",
3572                       "role|string|true"},
3573             expectedCode = 200,
3574             errorCodes = {403,404,406}, 
3575             text = { "Returns the User (with Expiration date from listed User/Role) if it exists"
3576                    }
3577             )
3578     @Override
3579     public Result<USERS> getUserInRole(AuthzTrans trans, String user, String role) {
3580         final Validator v = new ServiceValidator();
3581         if (v.role(role).nullOrBlank("User", user).err()) {
3582             return Result.err(Status.ERR_BadData,v.errs());
3583         }
3584
3585 //        Result<NsDAO.Data> ns = ques.deriveNs(trans, role);
3586 //        if (ns.notOK()) return Result.err(ns);
3587 //        
3588 //        Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), ns.value, Access.write);
3589         // May calling user see by virtue of the Role
3590         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans, ques, role);
3591         if (rrdd.notOK()) {
3592             return Result.err(rrdd);
3593         }
3594         Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), rrdd.value,Access.read);
3595         if (rnd.notOK()) {
3596             return Result.err(rnd); 
3597         }
3598         
3599         HashSet<UserRoleDAO.Data> userSet = new HashSet<>();
3600         Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readUserInRole(trans, user, role);
3601         if (rlurd.isOK()) {
3602             for (UserRoleDAO.Data data : rlurd.value) {
3603                 userSet.add(data);
3604             }
3605         }
3606         
3607         @SuppressWarnings("unchecked")
3608         USERS users = (USERS) mapper.newInstance(API.USERS);
3609         mapper.users(trans, userSet, users);
3610         return Result.ok(users);
3611     }
3612
3613     @ApiDoc( 
3614             method = GET,  
3615             path = "/authz/users/role/:role",
3616             params = {"user|string|true",
3617                       "role|string|true"},
3618             expectedCode = 200,
3619             errorCodes = {403,404,406}, 
3620             text = { "Returns the User (with Expiration date from listed User/Role) if it exists"
3621                    }
3622             )
3623     @Override
3624     public Result<USERS> getUsersByRole(AuthzTrans trans, String role) {
3625         final Validator v = new ServiceValidator();
3626         if (v.nullOrBlank("Role",role).err()) {
3627             return Result.err(Status.ERR_BadData,v.errs());
3628         }
3629
3630 //        Result<NsDAO.Data> ns = ques.deriveNs(trans, role);
3631 //        if (ns.notOK()) return Result.err(ns);
3632 //        
3633 //        Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), ns.value, Access.write);
3634         // May calling user see by virtue of the Role
3635         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans, ques, role);
3636         if (rrdd.notOK()) {
3637             return Result.err(rrdd);
3638         }
3639         
3640         boolean contactOnly = false;
3641         // Allow the request of any valid user to find the contact of the NS (Owner)
3642         Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), rrdd.value,Access.read);
3643         if (rnd.notOK()) {
3644             if (Question.OWNER.equals(rrdd.value.name)) {
3645                 contactOnly = true;
3646             } else {
3647                 return Result.err(rnd);
3648             }
3649         }
3650         
3651         HashSet<UserRoleDAO.Data> userSet = new HashSet<>();
3652         Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByRole(trans, role);
3653         if (rlurd.isOK()) { 
3654             for (UserRoleDAO.Data data : rlurd.value) {
3655                 if (contactOnly) { //scrub data
3656                     // Can't change actual object, or will mess up the cache.
3657                     UserRoleDAO.Data scrub = new UserRoleDAO.Data();
3658                     scrub.ns = data.ns;
3659                     scrub.rname = data.rname;
3660                     scrub.role = data.role;
3661                     scrub.user = data.user;
3662                     userSet.add(scrub);
3663                 } else {
3664                     userSet.add(data);
3665                 }
3666             }
3667         }
3668         
3669         @SuppressWarnings("unchecked")
3670         USERS users = (USERS) mapper.newInstance(API.USERS);
3671         mapper.users(trans, userSet, users);
3672         return Result.ok(users);
3673     }
3674
3675     /**
3676      * getUsersByPermission
3677      */
3678     @ApiDoc(
3679             method = GET,
3680             path = "/authz/users/perm/:type/:instance/:action",
3681             params = {    "type|string|true",
3682                         "instance|string|true",
3683                         "action|string|true"
3684                     },
3685             expectedCode = 200,
3686             errorCodes = {404,406},
3687             text = { "List all Users that have Permission specified by :type :instance :action",
3688                     }
3689            )
3690     @Override
3691     public Result<USERS> getUsersByPermission(AuthzTrans trans, String type, String instance, String action) {
3692         final Validator v = new ServiceValidator();
3693         if (v.nullOrBlank("Type",type)
3694             .nullOrBlank("Instance",instance)
3695             .nullOrBlank("Action",action)            
3696             .err()) {
3697             return Result.err(Status.ERR_BadData,v.errs());
3698         }
3699
3700         Result<NsSplit> nss = ques.deriveNsSplit(trans, type);
3701         if (nss.notOK()) {
3702             return Result.err(nss);
3703         }
3704         
3705         Result<List<NsDAO.Data>> nsd = ques.nsDAO().read(trans, nss.value.ns);
3706         if (nsd.notOK()) {
3707             return Result.err(nsd);
3708         }
3709         
3710         boolean allInstance = ASTERIX.equals(instance);
3711         boolean allAction = ASTERIX.equals(action);
3712         // Get list of roles per Permission, 
3713         // Then loop through Roles to get Users
3714         // Note: Use Sets to avoid processing or responding with Duplicates
3715         Set<String> roleUsed = new HashSet<>();
3716         Set<UserRoleDAO.Data> userSet = new HashSet<>();
3717         
3718         if (!nss.isEmpty()) {
3719             Result<List<PermDAO.Data>> rlp = ques.permDAO().readByType(trans, nss.value.ns, nss.value.name);
3720             if (rlp.isOKhasData()) {
3721                 for (PermDAO.Data pd : rlp.value) {
3722                     if ((allInstance || pd.instance.equals(instance)) && 
3723                             (allAction || pd.action.equals(action))) {
3724                         if (ques.mayUser(trans, trans.user(),pd,Access.read).isOK()) {
3725                             for (String role : pd.roles) {
3726                                 if (!roleUsed.contains(role)) { // avoid evaluating Role many times
3727                                     roleUsed.add(role);
3728                                     Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByRole(trans, role.replace('|', '.'));
3729                                     if (rlurd.isOKhasData()) {
3730                                         for (UserRoleDAO.Data urd : rlurd.value) {
3731                                             userSet.add(urd);
3732                                         }
3733                                     }
3734                                 }
3735                             }
3736                         }
3737                     }
3738                 }
3739             }
3740         }
3741         @SuppressWarnings("unchecked")
3742         USERS users = (USERS) mapper.newInstance(API.USERS);
3743         mapper.users(trans, userSet, users);
3744         return Result.ok(users);
3745     }
3746
3747 /***********************************
3748  * HISTORY 
3749  ***********************************/    
3750     @Override
3751     public Result<HISTORY> getHistoryByUser(final AuthzTrans trans, String user, final int[] yyyymm, final int sort) {    
3752         final Validator v = new ServiceValidator();
3753         if (v.nullOrBlank("User",user).err()) {
3754             return Result.err(Status.ERR_BadData,v.errs());
3755         }
3756
3757         Result<NsDAO.Data> rnd;
3758         // Users may look at their own data
3759          if (trans.user().equals(user)) {
3760                 // Users may look at their own data
3761          } else {
3762             int at = user.indexOf('@');
3763             if (at>=0 && trans.org().getRealm().equals(user.substring(at+1))) {
3764                 NsDAO.Data nsd  = new NsDAO.Data();
3765                 nsd.name = Question.domain2ns(user);
3766                 rnd = ques.mayUser(trans, trans.user(), nsd, Access.read);
3767                 if (rnd.notOK()) {
3768                     return Result.err(rnd);
3769                 }
3770             } else {
3771                 rnd = ques.validNSOfDomain(trans, user);
3772                 if (rnd.notOK()) {
3773                     return Result.err(rnd);
3774                 }
3775
3776                 rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
3777                 if (rnd.notOK()) {
3778                     return Result.err(rnd);
3779                 }
3780             }
3781          }
3782         Result<List<HistoryDAO.Data>> resp = ques.historyDAO().readByUser(trans, user, yyyymm);
3783         if (resp.notOK()) {
3784             return Result.err(resp);
3785         }
3786         return mapper.history(trans, resp.value,sort);
3787     }
3788
3789     @Override
3790     public Result<HISTORY> getHistoryByRole(AuthzTrans trans, String role, int[] yyyymm, final int sort) {
3791         final Validator v = new ServiceValidator();
3792         if (v.nullOrBlank("Role",role).err()) {
3793             return Result.err(Status.ERR_BadData,v.errs());
3794         }
3795
3796         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans, ques, role);
3797         if (rrdd.notOK()) {
3798             return Result.err(rrdd);
3799         }
3800         
3801         Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), rrdd.value, Access.read);
3802         if (rnd.notOK()) {
3803             return Result.err(rnd);
3804         }
3805         Result<List<HistoryDAO.Data>> resp = ques.historyDAO().readBySubject(trans, role, "role", yyyymm); 
3806         if (resp.notOK()) {
3807             return Result.err(resp);
3808         }
3809         return mapper.history(trans, resp.value,sort);
3810     }
3811
3812     @Override
3813     public Result<HISTORY> getHistoryByPerm(AuthzTrans trans, String type, int[] yyyymm, final int sort) {
3814         final Validator v = new ServiceValidator();
3815         if (v.nullOrBlank("Type",type)
3816             .err()) {
3817             return Result.err(Status.ERR_BadData,v.errs());
3818         }
3819
3820         // May user see Namespace of Permission (since it's only one piece... we can't check for "is permission part of")
3821         Result<List<HistoryDAO.Data>> resp;
3822         if(type.startsWith(trans.user())) {
3823             resp = ques.historyDAO().readBySubject(trans, type, "perm", yyyymm);
3824         } else {
3825             Result<NsDAO.Data> rnd = ques.deriveNs(trans,type);
3826             if (rnd.notOK()) {
3827                 return Result.err(rnd);
3828             }
3829             rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
3830             if (rnd.notOK()) {
3831                 return Result.err(rnd);    
3832             }
3833             resp = ques.historyDAO().readBySubject(trans, type, "perm", yyyymm);
3834         }
3835         
3836         if (resp.notOK()) {
3837             return Result.err(resp);
3838         }
3839         return mapper.history(trans, resp.value,sort);
3840     }
3841
3842     @Override
3843     public Result<HISTORY> getHistoryByNS(AuthzTrans trans, String ns, int[] yyyymm, final int sort) {
3844         final Validator v = new ServiceValidator();
3845         if (v.nullOrBlank("NS",ns).err()) { 
3846             return Result.err(Status.ERR_BadData,v.errs());
3847         }
3848
3849         Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
3850         if (rnd.notOK()) {
3851             return Result.err(rnd);
3852         }
3853         rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
3854         if (rnd.notOK()) {
3855             return Result.err(rnd);    
3856         }
3857
3858         Result<List<HistoryDAO.Data>> resp = ques.historyDAO().readBySubject(trans, ns, "ns", yyyymm);
3859         if (resp.notOK()) {
3860             return Result.err(resp);
3861         }
3862         return mapper.history(trans, resp.value,sort);
3863     }
3864
3865     @Override
3866     public Result<HISTORY> getHistoryBySubject(AuthzTrans trans, String subject, String target, int[] yyyymm, final int sort) {
3867         NsDAO.Data ndd = new NsDAO.Data();
3868         ndd.name = FQI.reverseDomain(subject);
3869         Result<Data> rnd = ques.mayUser(trans, trans.user(), ndd, Access.read);
3870         if (rnd.notOK()) {
3871             return Result.err(rnd);    
3872         }
3873
3874         Result<List<HistoryDAO.Data>> resp = ques.historyDAO().readBySubject(trans, subject, target, yyyymm);
3875         if (resp.notOK()) {
3876             return Result.err(resp);
3877         }
3878         return mapper.history(trans, resp.value,sort);
3879     }
3880
3881 /***********************************
3882  * DELEGATE 
3883  ***********************************/
3884     @Override
3885     public Result<Void> createDelegate(final AuthzTrans trans, REQUEST base) {
3886         return createOrUpdateDelegate(trans, base, Question.Access.create);
3887     }
3888
3889     @Override
3890     public Result<Void> updateDelegate(AuthzTrans trans, REQUEST base) {
3891         return createOrUpdateDelegate(trans, base, Question.Access.write);
3892     }
3893
3894
3895     private Result<Void> createOrUpdateDelegate(final AuthzTrans trans, REQUEST base, final Access access) {
3896         final Result<DelegateDAO.Data> rd = mapper.delegate(trans, base);
3897         final ServiceValidator v = new ServiceValidator();
3898         if (v.delegate(trans.org(),rd).err()) { 
3899             return Result.err(Status.ERR_BadData,v.errs());
3900         }
3901
3902         final DelegateDAO.Data dd = rd.value;
3903         
3904         Result<List<DelegateDAO.Data>> ddr = ques.delegateDAO().read(trans, dd);
3905         if (access==Access.create && ddr.isOKhasData()) {
3906             return Result.err(Status.ERR_ConflictAlreadyExists, "[%s] already delegates to [%s]", dd.user, ddr.value.get(0).delegate);
3907         } else if (access!=Access.create && ddr.notOKorIsEmpty()) { 
3908             return Result.err(Status.ERR_NotFound, "[%s] does not have a Delegate Record to [%s].",dd.user,access.name());
3909         }
3910         Result<Void> rv = ques.mayUser(trans, dd, access);
3911         if (rv.notOK()) {
3912             return rv;
3913         }
3914         
3915         Result<FutureDAO.Data> fd = mapper.future(trans,DelegateDAO.TABLE,base, dd, false,
3916             () -> {
3917                 StringBuilder sb = new StringBuilder();
3918                 sb.append(access.name());
3919                 sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
3920                 sb.append("Delegate ");
3921                 sb.append(access==Access.create?"[":"to [");
3922                 sb.append(rd.value.delegate);
3923                 sb.append("] for [");
3924                 sb.append(rd.value.user);
3925                 sb.append(']');
3926                 return sb.toString();
3927             },
3928             () -> {
3929                 return Result.ok(); // Validate in code above
3930             });
3931         
3932         switch(fd.status) {
3933             case OK:
3934                 Result<String> rfc = func.createFuture(trans, fd.value, 
3935                         dd.user, trans.user(),null, access==Access.create?FUTURE_OP.C:FUTURE_OP.U);
3936                 if (rfc.isOK()) { 
3937                     return Result.err(Status.ACC_Future, "Delegate for [%s]",
3938                             dd.user);
3939                 } else { 
3940                     return Result.err(rfc);
3941                 }
3942             case Status.ACC_Now:
3943                 if (access==Access.create) {
3944                     Result<DelegateDAO.Data> rdr = ques.delegateDAO().create(trans, dd);
3945                     if (rdr.isOK()) {
3946                         return Result.ok();
3947                     } else {
3948                         return Result.err(rdr);
3949                     }
3950                 } else {
3951                     return ques.delegateDAO().update(trans, dd);
3952                 }
3953             default:
3954                 return Result.err(fd);
3955         }
3956     }
3957
3958     @Override
3959     public Result<Void> deleteDelegate(AuthzTrans trans, REQUEST base) {
3960         final Result<DelegateDAO.Data> rd = mapper.delegate(trans, base);
3961         final Validator v = new ServiceValidator();
3962         if (v.notOK(rd).nullOrBlank("User", rd.value.user).err()) {
3963             return Result.err(Status.ERR_BadData,v.errs());
3964         }
3965         
3966         Result<List<DelegateDAO.Data>> ddl;
3967         if ((ddl=ques.delegateDAO().read(trans, rd.value)).notOKorIsEmpty()) {
3968             return Result.err(Status.ERR_DelegateNotFound,"Cannot delete non-existent Delegate");
3969         }
3970         final DelegateDAO.Data dd = ddl.value.get(0);
3971         Result<Void> rv = ques.mayUser(trans, dd, Access.write);
3972         if (rv.notOK()) {
3973             return rv;
3974         }
3975         
3976         return ques.delegateDAO().delete(trans, dd, false);
3977     }
3978
3979     @Override
3980     public Result<Void> deleteDelegate(AuthzTrans trans, String userName) {
3981         DelegateDAO.Data dd = new DelegateDAO.Data();
3982         final Validator v = new ServiceValidator();
3983         if (v.nullOrBlank("User", userName).err()) {
3984             return Result.err(Status.ERR_BadData,v.errs());
3985         }
3986         dd.user = userName;
3987         Result<List<DelegateDAO.Data>> ddl;
3988         if ((ddl=ques.delegateDAO().read(trans, dd)).notOKorIsEmpty()) {
3989             return Result.err(Status.ERR_DelegateNotFound,"Cannot delete non-existent Delegate");
3990         }
3991         dd = ddl.value.get(0);
3992         Result<Void> rv = ques.mayUser(trans, dd, Access.write);
3993         if (rv.notOK()) {
3994             return rv;
3995         }
3996         
3997         return ques.delegateDAO().delete(trans, dd, false);
3998     }
3999     
4000     @Override
4001     public Result<DELGS> getDelegatesByUser(AuthzTrans trans, String user) {
4002         final Validator v = new ServiceValidator();
4003         if (v.nullOrBlank("User", user).err()) {
4004             return Result.err(Status.ERR_BadData,v.errs());
4005         }
4006
4007         DelegateDAO.Data ddd = new DelegateDAO.Data();
4008         ddd.user = user;
4009         ddd.delegate = null;
4010         Result<Void> rv = ques.mayUser(trans, ddd, Access.read);
4011         if (rv.notOK()) {
4012             return Result.err(rv);
4013         }
4014         
4015         TimeTaken tt = trans.start("Get delegates for a user", Env.SUB);
4016
4017         Result<List<DelegateDAO.Data>> dbDelgs = ques.delegateDAO().read(trans, user);
4018         try {
4019             if (dbDelgs.isOKhasData()) {
4020                 return mapper.delegate(dbDelgs.value);
4021             } else {
4022                 return Result.err(Status.ERR_DelegateNotFound,"No Delegate found for [%s]",user);
4023             }
4024         } finally {
4025             tt.done();
4026         }        
4027     }
4028
4029     @Override
4030     public Result<DELGS> getDelegatesByDelegate(AuthzTrans trans, String delegate) {
4031         final Validator v = new ServiceValidator();
4032         if (v.nullOrBlank("Delegate", delegate).err()) {
4033             return Result.err(Status.ERR_BadData,v.errs());
4034         }
4035
4036         DelegateDAO.Data ddd = new DelegateDAO.Data();
4037         ddd.user = delegate;
4038         Result<Void> rv = ques.mayUser(trans, ddd, Access.read);
4039         if (rv.notOK()) {
4040             return Result.err(rv);
4041         }
4042
4043         TimeTaken tt = trans.start("Get users for a delegate", Env.SUB);
4044
4045         Result<List<DelegateDAO.Data>> dbDelgs = ques.delegateDAO().readByDelegate(trans, delegate);
4046         try {
4047             if (dbDelgs.isOKhasData()) {
4048                 return mapper.delegate(dbDelgs.value);
4049             } else {
4050                 return Result.err(Status.ERR_DelegateNotFound,"Delegate [%s] is not delegating for anyone.",delegate);
4051             }
4052         } finally {
4053             tt.done();
4054         }        
4055     }
4056
4057 /***********************************
4058  * APPROVAL 
4059  ***********************************/
4060     private static final String APPR_FMT = "actor=%s, action=%s, operation=\"%s\", requestor=%s, delegator=%s";
4061     @Override
4062     public Result<Void> updateApproval(AuthzTrans trans, APPROVALS approvals) {
4063         Result<List<ApprovalDAO.Data>> rlad = mapper.approvals(approvals);
4064         if (rlad.notOK()) {
4065             return Result.err(rlad);
4066         }
4067         int numApprs = rlad.value.size();
4068         if (numApprs<1) {
4069             return Result.err(Status.ERR_NoApprovals,"No Approvals sent for Updating");
4070         }
4071         int numProcessed = 0;
4072         String user = trans.user();
4073         
4074         Result<List<ApprovalDAO.Data>> curr;
4075         Lookup<List<ApprovalDAO.Data>> apprByTicket=null;
4076         for (ApprovalDAO.Data updt : rlad.value) {
4077             if (updt.ticket!=null) {
4078                 curr = ques.approvalDAO().readByTicket(trans, updt.ticket);
4079                 if (curr.isOKhasData()) {
4080                     final List<ApprovalDAO.Data> add = curr.value;
4081                     // Store a Pre-Lookup
4082                     apprByTicket = (trans1, noop) -> add;
4083                 }
4084             } else if (updt.id!=null) {
4085                 curr = ques.approvalDAO().read(trans, updt);
4086             } else if (updt.approver!=null) {
4087                 curr = ques.approvalDAO().readByApprover(trans, updt.approver);
4088             } else {
4089                 return Result.err(Status.ERR_BadData,"Approvals need ID, Ticket or Approval data to update");
4090             }
4091
4092             if (curr.isOKhasData()) {
4093                 Map<String, Result<List<DelegateDAO.Data>>> delegateCache = new HashMap<>();
4094                 Map<UUID, FutureDAO.Data> futureCache = new HashMap<>();
4095                 FutureDAO.Data hasDeleted = new FutureDAO.Data();
4096                 
4097                 for (ApprovalDAO.Data cd : curr.value) {
4098                     if ("pending".equals(cd.status)) {
4099                         // Check for right record.  Need ID, or (Ticket&Trans.User==Appr)
4100                         // If Default ID
4101                         boolean delegatedAction = ques.isDelegated(trans, user, cd.approver, delegateCache);
4102                         String delegator = cd.approver;
4103                         if (updt.id!=null || 
4104                             (updt.ticket!=null && user.equals(cd.approver)) ||
4105                             (updt.ticket!=null && delegatedAction)) {
4106                             if (updt.ticket.equals(cd.ticket)) {
4107                                 Changed ch = new Changed();
4108                                 cd.id = ch.changed(cd.id,updt.id);
4109 //                                cd.ticket = changed(cd.ticket,updt.ticket);
4110                                 cd.user = ch.changed(cd.user,updt.user);
4111                                 cd.approver = ch.changed(cd.approver,updt.approver);
4112                                 cd.type = ch.changed(cd.type,updt.type);
4113                                 cd.status = ch.changed(cd.status,updt.status);
4114                                 cd.memo = ch.changed(cd.memo,updt.memo);
4115                                 cd.operation = ch.changed(cd.operation,updt.operation);
4116                                 cd.updated = ch.changed(cd.updated,updt.updated==null?new Date():updt.updated);
4117 //                                if (updt.status.equals("denied")) {
4118 //                                    cd.last_notified = null;
4119 //                                }
4120                                 if (cd.ticket!=null) {
4121                                     FutureDAO.Data fdd = futureCache.get(cd.ticket);
4122                                     if (fdd==null) { // haven't processed ticket yet
4123                                         Result<FutureDAO.Data> rfdd = ques.futureDAO().readPrimKey(trans, cd.ticket);
4124                                         if (rfdd.isOK()) {
4125                                             fdd = rfdd.value; // null is ok
4126                                         } else {
4127                                             fdd = hasDeleted;
4128                                         }
4129                                         futureCache.put(cd.ticket, fdd); // processed this Ticket... don't do others on this ticket
4130                                     }
4131                                     if (fdd==hasDeleted) { // YES, by Object
4132                                         cd.ticket = null;
4133                                         cd.status = "ticketDeleted";
4134                                         ch.hasChanged(true);
4135                                     } else {
4136                                         FUTURE_OP fop = FUTURE_OP.toFO(cd.operation);
4137                                         if (fop==null) {
4138                                             trans.info().printf("Approval Status %s is not actionable",cd.status);
4139                                         } else if (apprByTicket!=null) {
4140                                             Result<OP_STATUS> rv = func.performFutureOp(trans, fop, fdd, apprByTicket,func.urDBLookup);
4141                                             if (rv.isOK()) {
4142                                                 switch(rv.value) {
4143                                                     case E:
4144                                                         if (delegatedAction) {
4145                                                             trans.audit().printf(APPR_FMT,user,updt.status,cd.memo,cd.user,delegator);
4146                                                         }
4147                                                         futureCache.put(cd.ticket, hasDeleted);
4148                                                         break;
4149                                                     case D:
4150                                                     case L:
4151                                                         ch.hasChanged(true);
4152                                                         trans.audit().printf(APPR_FMT,user,rv.value.desc(),cd.memo,cd.user,delegator);
4153                                                         futureCache.put(cd.ticket, hasDeleted);
4154                                                         break;
4155                                                     default:
4156                                                 }
4157                                             } else {
4158                                                 trans.info().log(rv.toString());
4159                                             }
4160                                         }
4161
4162                                     }
4163                                     ++numProcessed;
4164                                 }
4165                                 if (ch.hasChanged()) {
4166                                     ques.approvalDAO().update(trans, cd, true);
4167                                 }
4168                             }
4169                         }
4170                     }
4171                 }
4172             }
4173         }
4174
4175         if (numApprs==numProcessed) {
4176             return Result.ok();
4177         }
4178         return Result.err(Status.ERR_ActionNotCompleted,numProcessed + " out of " + numApprs + " completed");
4179
4180     }
4181     
4182     private static class Changed {
4183         private boolean hasChanged = false;
4184
4185         public<T> T changed(T src, T proposed) {
4186             if (proposed==null || (src!=null && src.equals(proposed))) {
4187                 return src;
4188             }
4189             hasChanged=true;
4190             return proposed;
4191         }
4192
4193         public void hasChanged(boolean b) {
4194             hasChanged=b;
4195         }
4196
4197         public boolean hasChanged() {
4198             return hasChanged;
4199         }
4200     }
4201
4202     @Override
4203     public Result<APPROVALS> getApprovalsByUser(AuthzTrans trans, String user) {
4204         final Validator v = new ServiceValidator();
4205         if (v.nullOrBlank("User", user).err()) { 
4206             return Result.err(Status.ERR_BadData,v.errs());
4207         }
4208
4209         Result<List<ApprovalDAO.Data>> rapd = ques.approvalDAO().readByUser(trans, user);
4210         if (rapd.isOK()) {
4211             return mapper.approvals(rapd.value);
4212         } else {
4213             return Result.err(rapd);
4214         }
4215 }
4216
4217     @Override
4218     public Result<APPROVALS> getApprovalsByTicket(AuthzTrans trans, String ticket) {
4219         final Validator v = new ServiceValidator();
4220         if (v.nullOrBlank("Ticket", ticket).err()) { 
4221             return Result.err(Status.ERR_BadData,v.errs());
4222         }
4223         UUID uuid;
4224         try {
4225             uuid = UUID.fromString(ticket);
4226         } catch (IllegalArgumentException e) {
4227             return Result.err(Status.ERR_BadData,e.getMessage());
4228         }
4229     
4230         Result<List<ApprovalDAO.Data>> rapd = ques.approvalDAO().readByTicket(trans, uuid);
4231         if (rapd.isOK()) {
4232             return mapper.approvals(rapd.value);
4233         } else {
4234             return Result.err(rapd);
4235         }
4236     }
4237     
4238     @Override
4239     public Result<APPROVALS> getApprovalsByApprover(AuthzTrans trans, String approver) {
4240         final Validator v = new ServiceValidator();
4241         if (v.nullOrBlank("Approver", approver).err()) {
4242             return Result.err(Status.ERR_BadData,v.errs());
4243         }
4244         
4245         List<ApprovalDAO.Data> listRapds = new ArrayList<>();
4246         
4247         Result<List<ApprovalDAO.Data>> myRapd = ques.approvalDAO().readByApprover(trans, approver);
4248         if (myRapd.notOK()) {
4249             return Result.err(myRapd);
4250         }
4251         
4252         listRapds.addAll(myRapd.value);
4253         
4254         Result<List<DelegateDAO.Data>> delegatedFor = ques.delegateDAO().readByDelegate(trans, approver);
4255         if (delegatedFor.isOK()) {
4256             for (DelegateDAO.Data dd : delegatedFor.value) {
4257                 if (dd.expires.after(new Date())) {
4258                     String delegator = dd.user;
4259                     Result<List<ApprovalDAO.Data>> rapd = ques.approvalDAO().readByApprover(trans, delegator);
4260                     if (rapd.isOK()) {
4261                         for (ApprovalDAO.Data d : rapd.value) { 
4262                             if (!d.user.equals(trans.user())) {
4263                                 listRapds.add(d);
4264                             }
4265                         }
4266                     }
4267                 }
4268             }
4269         }
4270         
4271         return mapper.approvals(listRapds);
4272     }
4273     
4274     /* (non-Javadoc)
4275      * @see org.onap.aaf.auth.service.AuthzService#clearCache(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String)
4276      */
4277     @Override
4278     public Result<Void> cacheClear(AuthzTrans trans, String cname) {
4279         if (ques.isGranted(trans,trans.user(),ROOT_NS,CACHE,cname,"clear")) {
4280             return ques.clearCache(trans,cname);
4281         }
4282         return Result.err(Status.ERR_Denied, "%s does not have AAF Permission '%s.%s|%s|clear",
4283                 trans.user(),ROOT_NS,CACHE,cname);
4284     }
4285
4286     /* (non-Javadoc)
4287      * @see org.onap.aaf.auth.service.AuthzService#cacheClear(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String, java.lang.Integer)
4288      */
4289     @Override
4290     public Result<Void> cacheClear(AuthzTrans trans, String cname, int[] segment) {
4291         if (ques.isGranted(trans,trans.user(),ROOT_NS,CACHE,cname,"clear")) {
4292             Result<Void> v=null;
4293             for (int i: segment) {
4294                 v=ques.cacheClear(trans,cname,i);
4295             }
4296             if (v!=null) {
4297                 return v;
4298             }
4299         }
4300         return Result.err(Status.ERR_Denied, "%s does not have AAF Permission '%s.%s|%s|clear",
4301                 trans.user(),ROOT_NS,CACHE,cname);
4302     }
4303
4304     /* (non-Javadoc)
4305      * @see org.onap.aaf.auth.service.AuthzService#dbReset(org.onap.aaf.auth.env.test.AuthzTrans)
4306      */
4307     @Override
4308     public void dbReset(AuthzTrans trans) {
4309         ques.historyDAO().reportPerhapsReset(trans, null);
4310     }
4311
4312 }
4313