Fixes from Regression Tests
[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                         String why;
2350                         if(ques.isOwner(trans, user,ns) ||
2351                                         ques.isAdmin(trans, user,ns) ||
2352                                         ques.isGranted(trans, user, ROOT_NS,"password",company,DELETE)) {
2353                                 return Result.ok();
2354                         }
2355                         break;
2356                     case RESET:
2357                     case EXTEND:
2358                         if (ques.isGranted(trans, trans.user(), ROOT_NS,"password",company,action)) {
2359                             return Result.ok();
2360                         }
2361                         break;
2362                 }
2363             }
2364             return Result.err(Status.ERR_Denied,"%s is not allowed to %s %s in %s",trans.user(),action,cred.id,cred.ns);
2365         }
2366     }
2367
2368     private final long DAY_IN_MILLIS = 24*3600*1000L;
2369
2370     @ApiDoc(
2371             method = POST,
2372             path = "/authn/cred",
2373             params = {},
2374             expectedCode = 201,
2375             errorCodes = {403,404,406,409},
2376             text = { "A credential consists of:",
2377                      "<ul><li>id - the ID to create within AAF. The domain is in reverse",
2378                      "order of Namespace (i.e. Users of Namespace com.att.myapp would be",
2379                      "AB1234@myapp.att.com</li>",
2380                      "<li>password - Company Policy Compliant Password</li></ul>",
2381                      "Note: AAF does support multiple credentials with the same ID.",
2382                      "Check with your organization if you have this implemented."
2383                      }
2384             )
2385     @Override
2386     public Result<Void> createUserCred(final AuthzTrans trans, REQUEST from) {
2387         final String cmdDescription = ("Create User Credential");
2388         TimeTaken tt = trans.start(cmdDescription, Env.SUB);
2389
2390         try {
2391             Result<CredDAO.Data> rcred = mapper.cred(trans, from, true);
2392             if (rcred.isOKhasData()) {
2393                 rcred = ques.userCredSetup(trans, rcred.value);
2394
2395                 final ServiceValidator v = new ServiceValidator();
2396
2397                 if (v.cred(trans, trans.org(),rcred,true).err()) { // Note: Creates have stricter Validations
2398                     return Result.err(Status.ERR_BadData,v.errs());
2399                 }
2400
2401
2402                 // 2016-4 Jonathan, New Behavior - If MechID is not registered with Org, deny creation
2403                 Identity mechID =  null;
2404                 Organization org = trans.org();
2405                 try {
2406                     mechID = org.getIdentity(trans, rcred.value.id);
2407                 } catch (Exception e1) {
2408                     trans.error().log(e1,rcred.value.id,"cannot be validated at this time");
2409                 }
2410                 if (mechID==null || !mechID.isFound()) {
2411                     return Result.err(Status.ERR_Policy,"MechIDs must be registered with %s before provisioning in AAF",org.getName());
2412                 }
2413
2414                 Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, rcred.value.ns);
2415                 if (nsr.notOKorIsEmpty()) {
2416                     return Result.err(Status.ERR_NsNotFound,"Cannot provision %s on non-existent Namespace %s",mechID.id(),rcred.value.ns);
2417                 }
2418
2419
2420                 boolean firstID = false;
2421                 MayChange mc;
2422
2423                 CassExecutor exec = new CassExecutor(trans, func);
2424                 Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, rcred.value.id);
2425                 if (rlcd.isOKhasData()) {
2426                     if (!org.canHaveMultipleCreds(rcred.value.id)) {
2427                         return Result.err(Status.ERR_ConflictAlreadyExists, "Credential exists");
2428                     }
2429                     Result<Boolean> rb;
2430                     for (CredDAO.Data curr : rlcd.value) {
2431                         // May not use the same password in the list
2432                         // Note: ASPR specifies character differences, but we don't actually store the
2433                         // password to validate char differences.
2434
2435 //                      byte[] rawCred = rcred.value.type==CredDAO.RAW?null:;                            return Result.err(Status.ERR_ConflictAlreadyExists, "Credential with same Expiration Date exists");
2436                         if(rcred.value.type==CredDAO.FQI ) {
2437                             if(curr.type==CredDAO.FQI) {
2438                                 return Result.err(Status.ERR_ConflictAlreadyExists, "Credential with same Expiration Date exists");
2439                             }
2440                         } else {
2441
2442                             rb = ques.userCredCheck(trans, curr, rcred.value.cred!=null?rcred.value.cred.array():null);
2443                             if (rb.notOK()) {
2444                                 return Result.err(rb);
2445                             } else if (rb.value){
2446                                 return Result.err(Status.ERR_Policy, "Credential content cannot be reused.");
2447                             } else if(Chrono.dateOnlyStamp(curr.expires).equals(Chrono.dateOnlyStamp(rcred.value.expires))
2448                                     && curr.type==rcred.value.type
2449                                     ) {
2450                                 // Allow if expiring differential is greater than 1 day (for TEMP)
2451                                 // Unless expiring in 1 day
2452                                 if(System.currentTimeMillis() - rcred.value.expires.getTime() > TimeUnit.DAYS.toMillis(1)) {
2453                                     return Result.err(Status.ERR_ConflictAlreadyExists, "Credential with same Expiration Date exists");
2454                                 }
2455                             }
2456                         }
2457                     }
2458                 } else {
2459                     try {
2460                     // 2016-04-12 Jonathan If Caller is the Sponsor and is also an Owner of NS, allow without special Perm
2461                         String theMechID = rcred.value.id;
2462                         Boolean otherMechIDs = false;
2463                         // find out if this is the only mechID.  other MechIDs mean special handling (not automated)
2464                         for (CredDAO.Data cd : ques.credDAO().readNS(trans,nsr.value.get(0).name).value) {
2465                             if (!cd.id.equals(theMechID)) {
2466                                 otherMechIDs = true;
2467                                 break;
2468                             }
2469                         }
2470                         String reason;
2471                         // We can say "ID does not exist" here
2472                         if ((reason=org.validate(trans, Policy.CREATE_MECHID, exec, theMechID,trans.user(),otherMechIDs.toString()))!=null) {
2473                             return Result.err(Status.ERR_Denied, reason);
2474                         }
2475                         firstID=true;
2476                     } catch (Exception e) {
2477                         return Result.err(e);
2478                     }
2479                 }
2480
2481                 mc = new MayCreateCred(trans, rcred.value, exec);
2482
2483                 final CredDAO.Data cdd = rcred.value;
2484                 Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from, rcred.value,false, // may want to enable in future.
2485                     new Mapper.Memo() {
2486                         @Override
2487                         public String get() {
2488                             return cmdDescription + " [" +
2489                                 cdd.id + '|'
2490                                 + cdd.type + '|'
2491                                 + cdd.expires + ']';
2492                         }
2493                     },
2494                     mc);
2495
2496                 switch(fd.status) {
2497                     case OK:
2498                         Result<String> rfc = func.createFuture(trans, fd.value,
2499                                 rcred.value.id + '|' + rcred.value.type.toString() + '|' + rcred.value.expires,
2500                                 trans.user(), nsr.value.get(0), FUTURE_OP.C);
2501                         if (rfc.isOK()) {
2502                             return Result.err(Status.ACC_Future, "Credential Request [%s|%s|%s] is saved for future processing",
2503                                     rcred.value.id,
2504                                     Integer.toString(rcred.value.type),
2505                                     rcred.value.expires.toString());
2506                         } else {
2507                             return Result.err(rfc);
2508                         }
2509                     case Status.ACC_Now:
2510                         try {
2511                             if (firstID) {
2512                                 // OK, it's a first ID, and not by NS Owner
2513                                 String user = trans.user();
2514                                 if(!ques.isOwner(trans,user,cdd.ns)) {
2515                                     // Admins are not allowed to set first Cred, but Org has already
2516                                     // said entity MAY create, typically by Permission
2517                                     // We can't know which reason they are allowed here, so we
2518                                     // have to assume that any with Special Permission would not be
2519                                     // an Admin.
2520                                         String domain = org.supportedDomain(user);
2521                                     if((domain!=null && !ques.isGranted(trans, user, ROOT_NS, "mechid", domain, Question.CREATE)) &&
2522                                                 ques.isAdmin(trans, user, cdd.ns)) {
2523                                         return Result.err(Result.ERR_Denied,
2524                                             "Only Owners may create first passwords in their Namespace. Admins may modify after one exists" );
2525                                     } else {
2526                                         // Allow IDs that AREN'T part of NS with Org Onboarding Permission  (see Org object) to create Temp Passwords.
2527                                         rcred.value.expires = org.expiration(null, Expiration.TempPassword).getTime();
2528                                     }
2529                                 }
2530                             }
2531                         } catch (Exception e) {
2532                             trans.error().log(e, "While setting expiration to TempPassword");
2533                         }
2534
2535                         Result<?>udr = ques.credDAO().create(trans, rcred.value);
2536                         if (udr.isOK()) {
2537                             return Result.ok();
2538                         }
2539                         return Result.err(udr);
2540                     default:
2541                         return Result.err(fd);
2542                 }
2543
2544             } else {
2545                 return Result.err(rcred);
2546             }
2547         } finally {
2548             tt.done();
2549         }
2550     }
2551
2552     @ApiDoc(
2553             method = GET,
2554             path = "/authn/creds/ns/:ns",
2555             params = {"ns|string|true"},
2556             expectedCode = 200,
2557             errorCodes = {403,404,406},
2558             text = { "Return all IDs in Namespace :ns"
2559                      }
2560             )
2561     @Override
2562     public Result<USERS> getCredsByNS(AuthzTrans trans, String ns) {
2563         final Validator v = new ServiceValidator();
2564         if (v.ns(ns).err()) {
2565             return Result.err(Status.ERR_BadData,v.errs());
2566         }
2567
2568         // check if user is allowed to view NS
2569         Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
2570         if (rnd.notOK()) {
2571             return Result.err(rnd);
2572         }
2573         rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
2574         if (rnd.notOK()) {
2575             return Result.err(rnd);
2576         }
2577
2578         TimeTaken tt = trans.start("MAP Creds by NS to Creds", Env.SUB);
2579         try {
2580             USERS users = mapper.newInstance(API.USERS);
2581             Result<List<CredDAO.Data>> rlcd = ques.credDAO().readNS(trans, ns);
2582
2583             if (rlcd.isOK()) {
2584                 if (!rlcd.isEmpty()) {
2585                     return mapper.cred(rlcd.value, users);
2586                 }
2587                 return Result.ok(users);
2588             } else {
2589                 return Result.err(rlcd);
2590             }
2591         } finally {
2592             tt.done();
2593         }
2594
2595     }
2596
2597     @ApiDoc(
2598             method = GET,
2599             path = "/authn/creds/id/:ns",
2600             params = {"id|string|true"},
2601             expectedCode = 200,
2602             errorCodes = {403,404,406},
2603             text = { "Return all IDs in for ID"
2604                     ,"(because IDs are multiple, due to multiple Expiration Dates)"
2605                      }
2606             )
2607     @Override
2608     public Result<USERS> getCredsByID(AuthzTrans trans, String id) {
2609         final Validator v = new ServiceValidator();
2610         if (v.nullOrBlank("ID",id).err()) {
2611             return Result.err(Status.ERR_BadData,v.errs());
2612         }
2613
2614         String ns = Question.domain2ns(id);
2615         // check if user is allowed to view NS
2616         Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
2617         if (rnd.notOK()) {
2618             return Result.err(rnd);
2619         }
2620         rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
2621         if (rnd.notOK()) {
2622             return Result.err(rnd);
2623         }
2624
2625         TimeTaken tt = trans.start("MAP Creds by ID to Creds", Env.SUB);
2626         try {
2627             USERS users = mapper.newInstance(API.USERS);
2628             Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, id);
2629
2630             if (rlcd.isOK()) {
2631                 if (!rlcd.isEmpty()) {
2632                     return mapper.cred(rlcd.value, users);
2633                 }
2634                 return Result.ok(users);
2635             } else {
2636                 return Result.err(rlcd);
2637             }
2638         } finally {
2639             tt.done();
2640         }
2641
2642     }
2643
2644     @ApiDoc(
2645             method = GET,
2646             path = "/authn/certs/id/:id",
2647             params = {"id|string|true"},
2648             expectedCode = 200,
2649             errorCodes = {403,404,406},
2650             text = { "Return Cert Info for ID"
2651                    }
2652             )
2653     @Override
2654     public Result<CERTS> getCertInfoByID(AuthzTrans trans, HttpServletRequest req, String id) {
2655         TimeTaken tt = trans.start("Get Cert Info by ID", Env.SUB);
2656         try {
2657             CERTS certs = mapper.newInstance(API.CERTS);
2658             Result<List<CertDAO.Data>> rlcd = ques.certDAO().readID(trans, id);
2659
2660             if (rlcd.isOK()) {
2661                 if (!rlcd.isEmpty()) {
2662                     return mapper.cert(rlcd.value, certs);
2663                 }
2664                 return Result.ok(certs);
2665             } else {
2666                 return Result.err(rlcd);
2667             }
2668         } finally {
2669             tt.done();
2670         }
2671
2672     }
2673
2674     @ApiDoc(
2675             method = PUT,
2676             path = "/authn/cred",
2677             params = {},
2678             expectedCode = 200,
2679             errorCodes = {300,403,404,406},
2680             text = { "Reset a Credential Password. If multiple credentials exist for this",
2681                         "ID, you will need to specify which entry you are resetting in the",
2682                         "CredRequest object"
2683                      }
2684             )
2685     @Override
2686     public Result<Void> resetUserCred(final AuthzTrans trans, REQUEST from) {
2687         final String cmdDescription = "Update User Credential";
2688         TimeTaken tt = trans.start(cmdDescription, Env.SUB);
2689         try {
2690             Result<CredDAO.Data> rcred = mapper.cred(trans, from, true);
2691             if (rcred.isOKhasData()) {
2692                 rcred = ques.userCredSetup(trans, rcred.value);
2693
2694                 final ServiceValidator v = new ServiceValidator();
2695
2696                 if (v.cred(trans, trans.org(),rcred,false).err()) {// Note: Creates have stricter Validations
2697                     return Result.err(Status.ERR_BadData,v.errs());
2698                 }
2699                 Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, rcred.value.id);
2700                 if (rlcd.notOKorIsEmpty()) {
2701                     return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
2702                 }
2703
2704                 MayChange mc = new MayChangeCred(trans, rcred.value,MayChangeCred.RESET);
2705                 Result<?> rmc = mc.mayChange();
2706                 if (rmc.notOK()) {
2707                     return Result.err(rmc);
2708                 }
2709
2710                 List<CredDAO.Data> lcdd = filterList(rlcd.value,CredDAO.BASIC_AUTH, CredDAO.BASIC_AUTH_SHA256);
2711
2712                 Result<Integer> ri = selectEntryIfMultiple((CredRequest)from, lcdd, MayChangeCred.RESET);
2713                 if (ri.notOK()) {
2714                     return Result.err(ri);
2715                 }
2716                 int entry = ri.value;
2717
2718
2719                 final CredDAO.Data cred = rcred.value;
2720
2721                 Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from, rcred.value,false,
2722                 new Mapper.Memo() {
2723                     @Override
2724                     public String get() {
2725                         return cmdDescription + " [" +
2726                             cred.id + '|'
2727                             + cred.type + '|'
2728                             + cred.expires + ']';
2729                     }
2730                 },
2731                 mc);
2732
2733                 Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, rcred.value.ns);
2734                 if (nsr.notOKorIsEmpty()) {
2735                     return Result.err(nsr);
2736                 }
2737
2738                 switch(fd.status) {
2739                     case OK:
2740                         Result<String> rfc = func.createFuture(trans, fd.value,
2741                                 rcred.value.id + '|' + rcred.value.type.toString() + '|' + rcred.value.expires,
2742                                 trans.user(), nsr.value.get(0), FUTURE_OP.U);
2743                         if (rfc.isOK()) {
2744                             return Result.err(Status.ACC_Future, "Credential Request [%s|%s|%s]",
2745                                     rcred.value.id,
2746                                     Integer.toString(rcred.value.type),
2747                                     rcred.value.expires.toString());
2748                         } else {
2749                             return Result.err(rfc);
2750                         }
2751                     case Status.ACC_Now:
2752                         Result<?>udr = null;
2753                         // If we are Resetting Password on behalf of someone else (am not the Admin)
2754                         //  use TempPassword Expiration time.
2755                         Expiration exp;
2756                         if (ques.isAdmin(trans, trans.user(), nsr.value.get(0).name)) {
2757                             exp = Expiration.Password;
2758                         } else {
2759                             exp = Expiration.TempPassword;
2760                         }
2761
2762                         Organization org = trans.org();
2763                         CredDAO.Data current = rlcd.value.get(entry);
2764                         // If user resets password in same day, we will have a primary key conflict, so subtract 1 day
2765                         if (current.expires.equals(rcred.value.expires)
2766                                     && rlcd.value.get(entry).type==rcred.value.type) {
2767                             GregorianCalendar gc = org.expiration(null, exp,rcred.value.id);
2768                             gc = Chrono.firstMomentOfDay(gc);
2769                             gc.set(GregorianCalendar.HOUR_OF_DAY, org.startOfDay());
2770                             rcred.value.expires = new Date(gc.getTimeInMillis() - DAY_IN_MILLIS);
2771                         } else {
2772                             rcred.value.expires = org.expiration(null,exp).getTime();
2773                         }
2774
2775                         udr = ques.credDAO().create(trans, rcred.value);
2776                         if (udr.isOK()) {
2777                             udr = ques.credDAO().delete(trans, rlcd.value.get(entry),false);
2778                         }
2779                         if (udr.isOK()) {
2780                             return Result.ok();
2781                         }
2782
2783                         return Result.err(udr);
2784                     default:
2785                         return Result.err(fd);
2786                 }
2787             } else {
2788                 return Result.err(rcred);
2789             }
2790         } finally {
2791             tt.done();
2792         }
2793     }
2794
2795     @ApiDoc(
2796             method = PUT,
2797             path = "/authn/cred/:days",
2798             params = {"days|string|true"},
2799             expectedCode = 200,
2800             errorCodes = {300,403,404,406},
2801             text = { "Extend a Credential Expiration Date. The intention of this API is",
2802                         "to avoid an outage in PROD due to a Credential expiring before it",
2803                         "can be configured correctly. Measures are being put in place ",
2804                         "so that this is not abused."
2805                      }
2806             )
2807     @Override
2808     public Result<Void> extendUserCred(final AuthzTrans trans, REQUEST from, String days) {
2809         TimeTaken tt = trans.start("Extend User Credential", Env.SUB);
2810         try {
2811             Result<CredDAO.Data> cred = mapper.cred(trans, from, false);
2812             Organization org = trans.org();
2813             final ServiceValidator v = new ServiceValidator();
2814             if (v.notOK(cred).err() ||
2815                v.nullOrBlank(cred.value.id, "Invalid ID").err() ||
2816                v.user(org,cred.value.id).err())  {
2817                  return Result.err(Status.ERR_BadData,v.errs());
2818             }
2819
2820             try {
2821                 String reason;
2822                 if ((reason=org.validate(trans, Policy.MAY_EXTEND_CRED_EXPIRES, new CassExecutor(trans,func)))!=null) {
2823                     return Result.err(Status.ERR_Policy,reason);
2824                 }
2825             } catch (Exception e) {
2826                 String msg;
2827                 trans.error().log(e, msg="Could not contact Organization for User Validation");
2828                 return Result.err(Status.ERR_Denied, msg);
2829             }
2830
2831             // Get the list of Cred Entries
2832             Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, cred.value.id);
2833             if (rlcd.notOKorIsEmpty()) {
2834                 return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
2835             }
2836
2837             // Only Passwords can be extended
2838             List<CredDAO.Data> lcdd = filterList(rlcd.value,CredDAO.BASIC_AUTH, CredDAO.BASIC_AUTH_SHA256);
2839
2840             //Need to do the "Pick Entry" mechanism
2841             // Note, this sorts
2842             Result<Integer> ri = selectEntryIfMultiple((CredRequest)from, lcdd, MayChangeCred.EXTEND);
2843             if (ri.notOK()) {
2844                 return Result.err(ri);
2845             }
2846
2847             CredDAO.Data found = lcdd.get(ri.value);
2848             CredDAO.Data cd = cred.value;
2849             // Copy over the cred
2850             cd.id = found.id;
2851             cd.cred = found.cred;
2852             cd.other = found.other;
2853             cd.type = found.type;
2854             cd.ns = found.ns;
2855             cd.notes = "Extended";
2856             cd.tag = found.tag;
2857             cd.expires = org.expiration(null, Expiration.ExtendPassword,days).getTime();
2858             if(cd.expires.before(found.expires)) {
2859                 return Result.err(Result.ERR_BadData,String.format("Credential's expiration date is more than %s days in the future",days));
2860             }
2861
2862             cred = ques.credDAO().create(trans, cd);
2863             if (cred.isOK()) {
2864                 return Result.ok();
2865             }
2866             return Result.err(cred);
2867         } finally {
2868             tt.done();
2869         }
2870     }
2871
2872     @ApiDoc(
2873             method = DELETE,
2874             path = "/authn/cred",
2875             params = {},
2876             expectedCode = 200,
2877             errorCodes = {300,403,404,406},
2878             text = { "Delete a Credential. If multiple credentials exist for this",
2879                     "ID, you will need to specify which entry you are deleting in the",
2880                     "CredRequest object."
2881                      }
2882             )
2883     @Override
2884     public Result<Void> deleteUserCred(AuthzTrans trans, REQUEST from)  {
2885         final Result<CredDAO.Data> cred = mapper.cred(trans, from, false);
2886         final Validator v = new ServiceValidator();
2887         if (v.nullOrBlank("cred", cred.value.id).err()) {
2888             return Result.err(Status.ERR_BadData,v.errs());
2889         }
2890
2891         MayChange mc = new MayChangeCred(trans,cred.value,MayChangeCred.DELETE);
2892         Result<?> rmc = mc.mayChange();
2893         if (rmc.notOK()) {
2894             return Result.err(rmc);
2895         }
2896
2897         boolean doForce = trans.requested(force);
2898         Result<List<CredDAO.Data>> rlcd = ques.credDAO().readID(trans, cred.value.id);
2899         if (rlcd.notOKorIsEmpty()) {
2900             // Empty Creds should not have user_roles.
2901             Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByUser(trans, cred.value.id);
2902             if (rlurd.isOKhasData()) {
2903                 for (UserRoleDAO.Data data : rlurd.value) {
2904                     ques.userRoleDAO().delete(trans, data, false);
2905                 }
2906             }
2907             return Result.err(Status.ERR_UserNotFound, "Credential does not exist");
2908         }
2909         boolean isLastCred = rlcd.value.size()==1;
2910
2911         int entry;
2912         CredRequest cr = (CredRequest)from;
2913         if(isLastCred) {
2914             if(cr.getEntry()==null || "1".equals(cr.getEntry())) {
2915                 entry = 0;
2916             } else {
2917                 return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
2918             }
2919         } else {
2920             entry = -1;
2921             int fentry = entry;
2922             if(cred.value.type==CredDAO.FQI) {
2923                 entry = -1;
2924                 for(CredDAO.Data cdd : rlcd.value) {
2925                     ++fentry;
2926                     if(cdd.type == CredDAO.FQI) {
2927                         entry = fentry;
2928                         break;
2929                     }
2930                 }
2931             } else {
2932                 if (!doForce) {
2933                     if (rlcd.value.size() > 1) {
2934                         String inputOption = cr.getEntry();
2935                         if (inputOption == null) {
2936                             List<CredDAO.Data> list = filterList(rlcd.value,CredDAO.BASIC_AUTH,CredDAO.BASIC_AUTH_SHA256,CredDAO.CERT_SHA256_RSA);
2937                             String message = selectCredFromList(list, MayChangeCred.DELETE);
2938                             Object[] variables = buildVariables(list);
2939                             return Result.err(Status.ERR_ChoiceNeeded, message, variables);
2940                         } else {
2941                             try {
2942                                 if (inputOption.length()>5) { // should be a date
2943                                     Date d = Chrono.xmlDatatypeFactory.newXMLGregorianCalendar(inputOption).toGregorianCalendar().getTime();
2944                                     for (CredDAO.Data cd : rlcd.value) {
2945                                         ++fentry;
2946                                         if (cd.type.equals(cr.getType()) && cd.expires.equals(d)) {
2947                                             entry = fentry;
2948                                             break;
2949                                         }
2950                                     }
2951                                 } else {
2952                                     entry = Integer.parseInt(inputOption) - 1;
2953                                     int count = 0;
2954                                     for (CredDAO.Data cd : rlcd.value) {
2955                                         if(cd.type!=CredDAO.BASIC_AUTH && cd.type!=CredDAO.BASIC_AUTH_SHA256 && cd.type!=CredDAO.CERT_SHA256_RSA) {
2956                                             ++entry;
2957                                         }
2958                                         if(++count>entry) {
2959                                             break;
2960                                         }
2961                                     }
2962                                 }
2963                             } catch (NullPointerException e) {
2964                                 return Result.err(Status.ERR_BadData, "Invalid Date Format for Entry");
2965                             } catch (NumberFormatException e) {
2966                                 return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
2967                             }
2968                         }
2969                         isLastCred = (entry==-1);
2970                     } else {
2971                         isLastCred = true;
2972                     }
2973                     if (entry < -1 || entry >= rlcd.value.size()) {
2974                         return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
2975                     }
2976                 }
2977             }
2978         }
2979
2980         Result<FutureDAO.Data> fd = mapper.future(trans,CredDAO.TABLE,from,cred.value,false,
2981             () -> "Delete Credential [" +
2982                 cred.value.id +
2983                 ']',
2984             mc);
2985
2986         Result<List<NsDAO.Data>> nsr = ques.nsDAO().read(trans, cred.value.ns);
2987         if (nsr.notOKorIsEmpty()) {
2988             return Result.err(nsr);
2989         }
2990
2991         switch(fd.status) {
2992             case OK:
2993                 Result<String> rfc = func.createFuture(trans, fd.value, cred.value.id,
2994                         trans.user(), nsr.value.get(0), FUTURE_OP.D);
2995
2996                 if (rfc.isOK()) {
2997                     return Result.err(Status.ACC_Future, "Credential Delete [%s] is saved for future processing",cred.value.id);
2998                 } else {
2999                     return Result.err(rfc);
3000                 }
3001             case Status.ACC_Now:
3002                 Result<?>udr = null;
3003                 if (!trans.requested(force)) {
3004                     if (entry<0 || entry >= rlcd.value.size()) {
3005                         if(cred.value.type==CredDAO.FQI) {
3006                             return Result.err(Status.ERR_BadData,"FQI does not exist");
3007                         } else {
3008                             return Result.err(Status.ERR_BadData,"Invalid Choice [" + entry + "] chosen for Delete [%s] is saved for future processing",cred.value.id);
3009                         }
3010                     }
3011                     udr = ques.credDAO().delete(trans, rlcd.value.get(entry),false);
3012                 } else {
3013                     for (CredDAO.Data curr : rlcd.value) {
3014                         udr = ques.credDAO().delete(trans, curr, false);
3015                         if (udr.notOK()) {
3016                             return Result.err(udr);
3017                         }
3018                     }
3019                 }
3020                 if (isLastCred) {
3021                     Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByUser(trans, cred.value.id);
3022                     if (rlurd.isOK()) {
3023                         for (UserRoleDAO.Data data : rlurd.value) {
3024                             ques.userRoleDAO().delete(trans, data, false);
3025                         }
3026                     }
3027                 }
3028                 if (udr==null) {
3029                     return Result.err(Result.ERR_NotFound,"No User Data found");
3030                 }
3031                 if (udr.isOK()) {
3032                     return Result.ok();
3033                 }
3034                 return Result.err(udr);
3035             default:
3036                 return Result.err(fd);
3037         }
3038
3039     }
3040
3041     /*
3042      * Codify the way to get Either Choice Needed or actual Integer from Credit Request
3043      */
3044     private Result<Integer> selectEntryIfMultiple(final CredRequest cr, List<CredDAO.Data> lcd, String action) {
3045         int entry = 0;
3046         if (lcd.size() > 1) {
3047             String inputOption = cr.getEntry();
3048             if (inputOption == null) {
3049                 String message = selectCredFromList(lcd, action);
3050                 Object[] variables = buildVariables(lcd);
3051                 return Result.err(Status.ERR_ChoiceNeeded, message, variables);
3052             } else {
3053                 if(MayChangeCred.EXTEND.equals(action)) {
3054                     // might be Tag
3055                     if(inputOption.length()>4) { //Tag is at least 12
3056                         int e = 0;
3057                         CredDAO.Data last = null;
3058                         int lastIdx = -1;
3059                         for(CredDAO.Data cdd : lcd) {
3060                             if(inputOption.equals(cdd.tag)) {
3061                                 if(last==null) {
3062                                     last = cdd;
3063                                     lastIdx = e;
3064                                 } else {
3065                                     if(last.expires.before(cdd.expires)) {
3066                                         last = cdd;
3067                                         lastIdx = e;
3068                                     }
3069                                 }
3070                             }
3071                             ++e;
3072                         }
3073                         if(last!=null) {
3074                             return Result.ok(lastIdx);
3075                         }
3076                         return Result.err(Status.ERR_BadData, "User chose unknown Tag");
3077                     }
3078                 }
3079                 entry = Integer.parseInt(inputOption) - 1;
3080             }
3081             if (entry < 0 || entry >= lcd.size()) {
3082                 return Result.err(Status.ERR_BadData, "User chose invalid credential selection");
3083             }
3084         }
3085         return Result.ok(entry);
3086     }
3087
3088     private List<CredDAO.Data> filterList(List<CredDAO.Data> orig, Integer ... types) {
3089         List<CredDAO.Data> rv = new ArrayList<>();
3090         for(CredDAO.Data cdd : orig) {
3091             if(cdd!=null) {
3092                 for(int t : types) {
3093                     if(t==cdd.type) {
3094                            rv.add(cdd);
3095                     }
3096                 }
3097             }
3098         }
3099         Collections.sort(rv, (o1,o2) -> {
3100             if(o1.type==o2.type) {
3101                 return o1.expires.compareTo(o2.expires);
3102             } else {
3103                 return o1.type.compareTo(o2.type);
3104             }
3105         });
3106         return rv;
3107     }
3108
3109     private String[] buildVariables(List<CredDAO.Data> value) {
3110         String [] vars = new String[value.size()];
3111         CredDAO.Data cdd;
3112
3113         for (int i = 0; i < value.size(); i++) {
3114             cdd = value.get(i);
3115             vars[i] = cdd.id + TWO_SPACE + Define.getCredType(cdd.type) + TWO_SPACE + Chrono.niceUTCStamp(cdd.expires) + TWO_SPACE + cdd.tag;
3116         }
3117         return vars;
3118     }
3119
3120     private String selectCredFromList(List<CredDAO.Data> value, String action) {
3121         StringBuilder errMessage = new StringBuilder();
3122         String userPrompt = MayChangeCred.DELETE.equals(action)?
3123                 "Select which cred to delete (set force=true to delete all):":
3124                 "Select which cred to " + action + ':';
3125         int numSpaces = value.get(0).id.length() - "Id".length();
3126
3127         errMessage.append(userPrompt + '\n');
3128         errMessage.append("        ID");
3129         for (int i = 0; i < numSpaces; i++) {
3130             errMessage.append(' ');
3131         }
3132         errMessage.append("  Type  Expires               Tag " + '\n');
3133         for (int i=0;i<value.size();++i) {
3134             errMessage.append("    %s\n");
3135         }
3136         if(MayChangeCred.EXTEND.equals(action)) {
3137             errMessage.append("Run same command again with chosen entry or Tag as last parameter");
3138         } else {
3139             errMessage.append("Run same command again with chosen entry as last parameter");
3140         }
3141         return errMessage.toString();
3142
3143     }
3144
3145     @Override
3146     public Result<Date> doesCredentialMatch(AuthzTrans trans, REQUEST credReq) {
3147         TimeTaken tt = trans.start("Does Credential Match", Env.SUB);
3148         try {
3149             // Note: Mapper assigns RAW type
3150             Result<CredDAO.Data> data = mapper.cred(trans, credReq,false);
3151             if (data.notOKorIsEmpty()) {
3152                 return Result.err(data);
3153             }
3154             CredDAO.Data cred = data.value;    // of the Mapped Cred
3155             if (cred.cred==null) {
3156                 return Result.err(Result.ERR_BadData,"No Password");
3157             } else {
3158                 return ques.doesUserCredMatch(trans, cred.id, cred.cred.array());
3159             }
3160
3161         } catch (DAOException e) {
3162             trans.error().log(e,"Error looking up cred");
3163             return Result.err(Status.ERR_Denied,"Credential does not match");
3164         } finally {
3165             tt.done();
3166         }
3167     }
3168
3169     @ApiDoc(
3170             method = POST,
3171             path = "/authn/validate",
3172             params = {},
3173             expectedCode = 200,
3174             errorCodes = { 403 },
3175             text = { "Validate a Credential given a Credential Structure.  This is a more comprehensive validation, can "
3176                     + "do more than BasicAuth as Credential types exp" }
3177             )
3178     @Override
3179     public Result<Date> validateBasicAuth(AuthzTrans trans, String basicAuth) {
3180         //TODO how to make sure people don't use this in browsers?  Do we care?
3181         TimeTaken tt = trans.start("Validate Basic Auth", Env.SUB);
3182         try {
3183             BasicPrincipal bp = new BasicPrincipal(basicAuth,trans.org().getRealm());
3184             Result<Date> rq = ques.doesUserCredMatch(trans, bp.getName(), bp.getCred());
3185             // Note: Only want to log problem, don't want to send back to end user
3186             if (rq.isOK()) {
3187                 return rq;
3188             } else {
3189                 trans.audit().log(rq.errorString());
3190             }
3191         } catch (Exception e) {
3192             trans.warn().log(e);
3193         } finally {
3194             tt.done();
3195         }
3196         return Result.err(Status.ERR_Denied,"Bad Basic Auth");
3197     }
3198
3199 @ApiDoc(
3200             method = GET,
3201             path = "/authn/basicAuth",
3202             params = {},
3203             expectedCode = 200,
3204             errorCodes = { 403 },
3205             text = { "!!!! DEPRECATED without X509 Authentication STOP USING THIS API BY DECEMBER 2017, or use Certificates !!!!\n"
3206                     + "Use /authn/validate instead\n"
3207                     + "Note: Validate a Password using BasicAuth Base64 encoded Header. This HTTP/S call is intended as a fast"
3208                     + " User/Password lookup for Security Frameworks, and responds 200 if it passes BasicAuth "
3209                 + "security, and 403 if it does not." }
3210             )
3211     private void basicAuth() {
3212         // This is a place holder for Documentation.  The real BasicAuth API does not call Service.
3213     }
3214
3215 /***********************************
3216  * USER-ROLE
3217  ***********************************/
3218     @ApiDoc(
3219             method = POST,
3220             path = "/authz/userRole",
3221             params = {},
3222             expectedCode = 201,
3223             errorCodes = {403,404,406,409},
3224             text = { "Create a UserRole relationship (add User to Role)",
3225                      "A UserRole is an object Representation of membership of a Role for limited time.",
3226                      "If a shorter amount of time for Role ownership is required, use the 'End' field.",
3227                      "** Note: Owners of Namespaces will be required to revalidate users in these roles ",
3228                      "before Expirations expire.  Namespace owners will be notified by email."
3229                    }
3230             )
3231     @Override
3232     public Result<Void> createUserRole(final AuthzTrans trans, REQUEST from) {
3233         TimeTaken tt = trans.start("Create UserRole", Env.SUB);
3234         try {
3235             Result<UserRoleDAO.Data> urr = mapper.userRole(trans, from);
3236             if (urr.notOKorIsEmpty()) {
3237                 return Result.err(urr);
3238             }
3239             final UserRoleDAO.Data userRole = urr.value;
3240
3241             final ServiceValidator v = new ServiceValidator();
3242             if (v.user_role(trans.user(),userRole).err() ||
3243                v.user(trans.org(), userRole.user).err()) {
3244                 return Result.err(Status.ERR_BadData,v.errs());
3245             }
3246
3247
3248
3249             // Check if user can change first
3250             Result<FutureDAO.Data> fd = mapper.future(trans,UserRoleDAO.TABLE,from,urr.value,true, // may request Approvals
3251                 () -> "Add User [" + userRole.user + "] to Role [" +
3252                         userRole.role +
3253                         ']',
3254                 new MayChange() {
3255                     private Result<NsDAO.Data> nsd;
3256                     @Override
3257                     public Result<?> mayChange() {
3258                         if(urr.value.role.startsWith(urr.value.user)) {
3259                             return Result.ok((NsDAO.Data)null);
3260                         }
3261                         if (nsd==null) {
3262                             RoleDAO.Data r = RoleDAO.Data.decode(userRole);
3263                             nsd = ques.mayUser(trans, trans.user(), r, Access.write);
3264                         }
3265                         return nsd;
3266                     }
3267                 });
3268
3269             NsDAO.Data ndd;
3270             if(userRole.role.startsWith(userRole.user)) {
3271                 userRole.ns=userRole.user;
3272                 userRole.rname="user";
3273                 ndd = null;
3274             } else {
3275                 Result<NsDAO.Data> nsr = ques.deriveNs(trans, userRole.role);
3276                 if (nsr.notOK()) {
3277                     return Result.err(nsr);
3278                 }
3279                 ndd = nsr.value;
3280             }
3281
3282             switch(fd.status) {
3283                 case OK:
3284                     Result<String> rfc = func.createFuture(trans, fd.value, userRole.user+'|'+userRole.ns + '.' + userRole.rname,
3285                             userRole.user, ndd, FUTURE_OP.C);
3286                     if (rfc.isOK()) {
3287                         return Result.err(Status.ACC_Future, "UserRole [%s - %s.%s] is saved for future processing",
3288                                 userRole.user,
3289                                 userRole.ns,
3290                                 userRole.rname);
3291                     } else {
3292                         return Result.err(rfc);
3293                     }
3294                 case Status.ACC_Now:
3295                     return func.addUserRole(trans, userRole);
3296                 default:
3297                     return Result.err(fd);
3298             }
3299         } finally {
3300             tt.done();
3301         }
3302     }
3303
3304         /**
3305          * getUserRolesByRole
3306          */
3307         @ApiDoc(
3308                 method = GET,
3309                 path = "/authz/userRoles/role/:role",
3310                 params = {"role|string|true"},
3311                 expectedCode = 200,
3312                 errorCodes = {404,406},
3313                 text = { "List all Users that are attached to Role specified in :role",
3314                         }
3315                )
3316         @Override
3317         public Result<USERROLES> getUserRolesByRole(AuthzTrans trans, String role) {
3318             final Validator v = new ServiceValidator();
3319             if (v.nullOrBlank("Role",role).err()) {
3320                 return Result.err(Status.ERR_BadData,v.errs());
3321             }
3322
3323             Result<RoleDAO.Data> rrdd;
3324             rrdd = RoleDAO.Data.decode(trans,ques,role);
3325             if (rrdd.notOK()) {
3326                 return Result.err(rrdd);
3327             }
3328             // May Requester see result?
3329             Result<NsDAO.Data> ns = ques.mayUser(trans,trans.user(), rrdd.value,Access.read);
3330             if (ns.notOK()) {
3331                 return Result.err(ns);
3332             }
3333
3334     //        boolean filter = true;
3335     //        if (ns.value.isAdmin(trans.user()) || ns.value.isResponsible(trans.user()))
3336     //            filter = false;
3337
3338             // Get list of roles per user, then add to Roles as we go
3339             HashSet<UserRoleDAO.Data> userSet = new HashSet<>();
3340             Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByRole(trans, role);
3341             if (rlurd.isOK()) {
3342                 for (UserRoleDAO.Data data : rlurd.value) {
3343                     userSet.add(data);
3344                 }
3345             }
3346
3347             @SuppressWarnings("unchecked")
3348             USERROLES users = (USERROLES) mapper.newInstance(API.USER_ROLES);
3349             // Checked for permission
3350             mapper.userRoles(trans, userSet, users);
3351             return Result.ok(users);
3352         }
3353         /**
3354          * getUserRolesByRole
3355          */
3356         @ApiDoc(
3357                 method = GET,
3358                 path = "/authz/userRoles/user/:user",
3359                 params = {"role|string|true"},
3360                 expectedCode = 200,
3361                 errorCodes = {404,406},
3362                 text = { "List all UserRoles for :user",
3363                         }
3364                )
3365         @Override
3366         public Result<USERROLES> getUserRolesByUser(AuthzTrans trans, String user) {
3367             final Validator v = new ServiceValidator();
3368             if (v.nullOrBlank("User",user).err()) {
3369                 return Result.err(Status.ERR_BadData,v.errs());
3370             }
3371
3372             // Get list of roles per user, then add to Roles as we go
3373             Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByUser(trans, user);
3374             if (rlurd.notOK()) {
3375                 return Result.err(rlurd);
3376             }
3377
3378             /* Check for
3379              *   1) is User
3380              *   2) is User's Supervisor
3381              *   3) Has special global access =read permission
3382              *
3383              *   If none of the 3, then filter results to NSs in which Calling User has Ns.access * read
3384              */
3385             boolean mustFilter;
3386             String callingUser = trans.getUserPrincipal().getName();
3387             NsDAO.Data ndd = new NsDAO.Data();
3388
3389             if (user.equals(callingUser)) {
3390                 mustFilter = false;
3391             } else {
3392                 Organization org = trans.org();
3393                 try {
3394                     Identity orgID = org.getIdentity(trans, user);
3395                     Identity manager = orgID==null?null:orgID.responsibleTo();
3396                     if (orgID!=null && (manager!=null && callingUser.equals(manager.fullID()))) {
3397                         mustFilter = false;
3398                     } else if (ques.isGranted(trans, callingUser, ROOT_NS, Question.ACCESS, "*", Access.read.name())) {
3399                         mustFilter=false;
3400                     } else {
3401                         mustFilter = true;
3402                     }
3403                 } catch (OrganizationException e) {
3404                     trans.env().log(e);
3405                     mustFilter = true;
3406                 }
3407             }
3408
3409             List<UserRoleDAO.Data> content;
3410             if (mustFilter) {
3411                 content = new ArrayList<>(rlurd.value.size()); // avoid multi-memory redos
3412
3413                 for (UserRoleDAO.Data data : rlurd.value) {
3414                     ndd.name=data.ns;
3415                     Result<Data> mur = ques.mayUser(trans, callingUser, ndd, Access.read);
3416                     if (mur.isOK()){
3417                         content.add(data);
3418                     }
3419                 }
3420
3421             } else {
3422                 content = rlurd.value;
3423             }
3424
3425
3426             @SuppressWarnings("unchecked")
3427             USERROLES users = (USERROLES) mapper.newInstance(API.USER_ROLES);
3428             // Checked for permission
3429             mapper.userRoles(trans, content, users);
3430             return Result.ok(users);
3431         }
3432
3433
3434
3435
3436      @ApiDoc(
3437             method = GET,
3438             path = "/authz/userRole/extend/:user/:role",
3439             params = {    "user|string|true",
3440                         "role|string|true"
3441                     },
3442             expectedCode = 200,
3443             errorCodes = {403,404,406},
3444             text = { "Extend the Expiration of this User Role by the amount set by Organization",
3445                      "Requestor must be allowed to modify the role"
3446                     }
3447            )
3448     @Override
3449     public Result<Void> extendUserRole(AuthzTrans trans, String user, String role) {
3450         Organization org = trans.org();
3451         final ServiceValidator v = new ServiceValidator();
3452         if (v.user(org, user)
3453             .role(role)
3454             .err()) {
3455             return Result.err(Status.ERR_BadData,v.errs());
3456         }
3457
3458         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans,ques,role);
3459         if (rrdd.notOK()) {
3460             return Result.err(rrdd);
3461         }
3462
3463         Result<NsDAO.Data> rcr = ques.mayUser(trans, trans.user(), rrdd.value, Access.write);
3464         boolean mayNotChange;
3465         if ((mayNotChange = rcr.notOK()) && !trans.requested(future)) {
3466             return Result.err(rcr);
3467         }
3468
3469         Result<List<UserRoleDAO.Data>> rr = ques.userRoleDAO().read(trans, user,role);
3470         if (rr.notOK()) {
3471             return Result.err(rr);
3472         }
3473         for (UserRoleDAO.Data userRole : rr.value) {
3474             if (mayNotChange) { // Function exited earlier if !trans.futureRequested
3475                 FutureDAO.Data fto = new FutureDAO.Data();
3476                 fto.target=UserRoleDAO.TABLE;
3477                 fto.memo = "Extend User ["+userRole.user+"] in Role ["+userRole.role+"]";
3478                 GregorianCalendar now = new GregorianCalendar();
3479                 fto.start = now.getTime();
3480                 fto.expires = org.expiration(now, Expiration.Future).getTime();
3481                 try {
3482                     fto.construct = userRole.bytify();
3483                 } catch (IOException e) {
3484                     trans.error().log(e, "Error while bytifying UserRole for Future");
3485                     return Result.err(e);
3486                 }
3487
3488                 Result<String> rfc = func.createFuture(trans, fto,
3489                         userRole.user+'|'+userRole.role, userRole.user, rcr.value, FUTURE_OP.U);
3490                 if (rfc.isOK()) {
3491                     return Result.err(Status.ACC_Future, "UserRole [%s - %s] is saved for future processing",
3492                             userRole.user,
3493                             userRole.role);
3494                 } else {
3495                     return Result.err(rfc);
3496                 }
3497             } else {
3498                 return func.extendUserRole(trans, userRole, false);
3499             }
3500         }
3501         return Result.err(Result.ERR_NotFound,"This user and role doesn't exist");
3502     }
3503
3504     @ApiDoc(
3505             method = DELETE,
3506             path = "/authz/userRole/:user/:role",
3507             params = {    "user|string|true",
3508                         "role|string|true"
3509                     },
3510             expectedCode = 200,
3511             errorCodes = {403,404,406},
3512             text = { "Remove Role :role from User :user."
3513                    }
3514             )
3515     @Override
3516     public Result<Void> deleteUserRole(AuthzTrans trans, String usr, String role) {
3517         Validator val = new ServiceValidator();
3518         if (val.nullOrBlank("User", usr)
3519               .nullOrBlank("Role", role).err()) {
3520             return Result.err(Status.ERR_BadData, val.errs());
3521         }
3522
3523         boolean mayNotChange;
3524         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans,ques,role);
3525         if (rrdd.notOK()) {
3526             return Result.err(rrdd);
3527         }
3528
3529         RoleDAO.Data rdd = rrdd.value;
3530         Result<NsDAO.Data> rns = ques.mayUser(trans, trans.user(), rdd, Access.write);
3531
3532         // Make sure we don't delete the last owner of valid NS
3533         if (rns.isOKhasData() && Question.OWNER.equals(rdd.name) && ques.countOwner(trans,rdd.ns)<=1) {
3534             return Result.err(Status.ERR_Denied,"You may not delete the last Owner of " + rdd.ns );
3535         }
3536
3537         if (mayNotChange=rns.notOK()) {
3538             if (!trans.requested(future)) {
3539                 return Result.err(rns);
3540             }
3541         }
3542
3543         Result<List<UserRoleDAO.Data>> rulr;
3544         if ((rulr=ques.userRoleDAO().read(trans, usr, role)).notOKorIsEmpty()) {
3545             return Result.err(Status.ERR_UserRoleNotFound, "User [ "+usr+" ] is not "
3546                     + "Assigned to the Role [ " + role + " ]");
3547         }
3548
3549         UserRoleDAO.Data userRole = rulr.value.get(0);
3550         if (mayNotChange) { // Function exited earlier if !trans.futureRequested
3551             FutureDAO.Data fto = new FutureDAO.Data();
3552             fto.target=UserRoleDAO.TABLE;
3553             fto.memo = "Remove User ["+userRole.user+"] from Role ["+userRole.role+"]";
3554             GregorianCalendar now = new GregorianCalendar();
3555             fto.start = now.getTime();
3556             fto.expires = trans.org().expiration(now, Expiration.Future).getTime();
3557
3558             Result<String> rfc = func.createFuture(trans, fto,
3559                     userRole.user+'|'+userRole.role, userRole.user, rns.value, FUTURE_OP.D);
3560             if (rfc.isOK()) {
3561                 return Result.err(Status.ACC_Future, "UserRole [%s - %s] is saved for future processing",
3562                         userRole.user,
3563                         userRole.role);
3564             } else {
3565                 return Result.err(rfc);
3566             }
3567         } else {
3568             return ques.userRoleDAO().delete(trans, rulr.value.get(0), false);
3569         }
3570     }
3571
3572     @ApiDoc(
3573             method = GET,
3574             path = "/authz/userRole/:user/:role",
3575             params = {"user|string|true",
3576                       "role|string|true"},
3577             expectedCode = 200,
3578             errorCodes = {403,404,406},
3579             text = { "Returns the User (with Expiration date from listed User/Role) if it exists"
3580                    }
3581             )
3582     @Override
3583     public Result<USERS> getUserInRole(AuthzTrans trans, String user, String role) {
3584         final Validator v = new ServiceValidator();
3585         if (v.role(role).nullOrBlank("User", user).err()) {
3586             return Result.err(Status.ERR_BadData,v.errs());
3587         }
3588
3589 //        Result<NsDAO.Data> ns = ques.deriveNs(trans, role);
3590 //        if (ns.notOK()) return Result.err(ns);
3591 //
3592 //        Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), ns.value, Access.write);
3593         // May calling user see by virtue of the Role
3594         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans, ques, role);
3595         if (rrdd.notOK()) {
3596             return Result.err(rrdd);
3597         }
3598         Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), rrdd.value,Access.read);
3599         if (rnd.notOK()) {
3600             return Result.err(rnd);
3601         }
3602
3603         HashSet<UserRoleDAO.Data> userSet = new HashSet<>();
3604         Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readUserInRole(trans, user, role);
3605         if (rlurd.isOK()) {
3606             for (UserRoleDAO.Data data : rlurd.value) {
3607                 userSet.add(data);
3608             }
3609         }
3610
3611         @SuppressWarnings("unchecked")
3612         USERS users = (USERS) mapper.newInstance(API.USERS);
3613         mapper.users(trans, userSet, users);
3614         return Result.ok(users);
3615     }
3616
3617     @ApiDoc(
3618             method = GET,
3619             path = "/authz/users/role/:role",
3620             params = {"user|string|true",
3621                       "role|string|true"},
3622             expectedCode = 200,
3623             errorCodes = {403,404,406},
3624             text = { "Returns the User (with Expiration date from listed User/Role) if it exists"
3625                    }
3626             )
3627     @Override
3628     public Result<USERS> getUsersByRole(AuthzTrans trans, String role) {
3629         final Validator v = new ServiceValidator();
3630         if (v.nullOrBlank("Role",role).err()) {
3631             return Result.err(Status.ERR_BadData,v.errs());
3632         }
3633
3634 //        Result<NsDAO.Data> ns = ques.deriveNs(trans, role);
3635 //        if (ns.notOK()) return Result.err(ns);
3636 //
3637 //        Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), ns.value, Access.write);
3638         // May calling user see by virtue of the Role
3639         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans, ques, role);
3640         if (rrdd.notOK()) {
3641             return Result.err(rrdd);
3642         }
3643
3644         boolean contactOnly = false;
3645         // Allow the request of any valid user to find the contact of the NS (Owner)
3646         Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), rrdd.value,Access.read);
3647         if (rnd.notOK()) {
3648             if (Question.OWNER.equals(rrdd.value.name)) {
3649                 contactOnly = true;
3650             } else {
3651                 return Result.err(rnd);
3652             }
3653         }
3654
3655         HashSet<UserRoleDAO.Data> userSet = new HashSet<>();
3656         Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByRole(trans, role);
3657         if (rlurd.isOK()) {
3658             for (UserRoleDAO.Data data : rlurd.value) {
3659                 if (contactOnly) { //scrub data
3660                     // Can't change actual object, or will mess up the cache.
3661                     UserRoleDAO.Data scrub = new UserRoleDAO.Data();
3662                     scrub.ns = data.ns;
3663                     scrub.rname = data.rname;
3664                     scrub.role = data.role;
3665                     scrub.user = data.user;
3666                     userSet.add(scrub);
3667                 } else {
3668                     userSet.add(data);
3669                 }
3670             }
3671         }
3672
3673         @SuppressWarnings("unchecked")
3674         USERS users = (USERS) mapper.newInstance(API.USERS);
3675         mapper.users(trans, userSet, users);
3676         return Result.ok(users);
3677     }
3678
3679     /**
3680      * getUsersByPermission
3681      */
3682     @ApiDoc(
3683             method = GET,
3684             path = "/authz/users/perm/:type/:instance/:action",
3685             params = {    "type|string|true",
3686                         "instance|string|true",
3687                         "action|string|true"
3688                     },
3689             expectedCode = 200,
3690             errorCodes = {404,406},
3691             text = { "List all Users that have Permission specified by :type :instance :action",
3692                     }
3693            )
3694     @Override
3695     public Result<USERS> getUsersByPermission(AuthzTrans trans, String type, String instance, String action) {
3696         final Validator v = new ServiceValidator();
3697         if (v.nullOrBlank("Type",type)
3698             .nullOrBlank("Instance",instance)
3699             .nullOrBlank("Action",action)
3700             .err()) {
3701             return Result.err(Status.ERR_BadData,v.errs());
3702         }
3703
3704         Result<NsSplit> nss = ques.deriveNsSplit(trans, type);
3705         if (nss.notOK()) {
3706             return Result.err(nss);
3707         }
3708
3709         Result<List<NsDAO.Data>> nsd = ques.nsDAO().read(trans, nss.value.ns);
3710         if (nsd.notOK()) {
3711             return Result.err(nsd);
3712         }
3713
3714         boolean allInstance = ASTERIX.equals(instance);
3715         boolean allAction = ASTERIX.equals(action);
3716         // Get list of roles per Permission,
3717         // Then loop through Roles to get Users
3718         // Note: Use Sets to avoid processing or responding with Duplicates
3719         Set<String> roleUsed = new HashSet<>();
3720         Set<UserRoleDAO.Data> userSet = new HashSet<>();
3721
3722         if (!nss.isEmpty()) {
3723             Result<List<PermDAO.Data>> rlp = ques.permDAO().readByType(trans, nss.value.ns, nss.value.name);
3724             if (rlp.isOKhasData()) {
3725                 for (PermDAO.Data pd : rlp.value) {
3726                     if ((allInstance || pd.instance.equals(instance)) &&
3727                             (allAction || pd.action.equals(action))) {
3728                         if (ques.mayUser(trans, trans.user(),pd,Access.read).isOK()) {
3729                             for (String role : pd.roles) {
3730                                 if (!roleUsed.contains(role)) { // avoid evaluating Role many times
3731                                     roleUsed.add(role);
3732                                     Result<List<UserRoleDAO.Data>> rlurd = ques.userRoleDAO().readByRole(trans, role.replace('|', '.'));
3733                                     if (rlurd.isOKhasData()) {
3734                                         for (UserRoleDAO.Data urd : rlurd.value) {
3735                                             userSet.add(urd);
3736                                         }
3737                                     }
3738                                 }
3739                             }
3740                         }
3741                     }
3742                 }
3743             }
3744         }
3745         @SuppressWarnings("unchecked")
3746         USERS users = (USERS) mapper.newInstance(API.USERS);
3747         mapper.users(trans, userSet, users);
3748         return Result.ok(users);
3749     }
3750
3751 /***********************************
3752  * HISTORY
3753  ***********************************/
3754     @Override
3755     public Result<HISTORY> getHistoryByUser(final AuthzTrans trans, String user, final int[] yyyymm, final int sort) {
3756         final Validator v = new ServiceValidator();
3757         if (v.nullOrBlank("User",user).err()) {
3758             return Result.err(Status.ERR_BadData,v.errs());
3759         }
3760
3761         Result<NsDAO.Data> rnd;
3762         // Users may look at their own data
3763          if (trans.user().equals(user)) {
3764                 // Users may look at their own data
3765          } else {
3766             int at = user.indexOf('@');
3767             if (at>=0 && trans.org().getRealm().equals(user.substring(at+1))) {
3768                 NsDAO.Data nsd  = new NsDAO.Data();
3769                 nsd.name = Question.domain2ns(user);
3770                 rnd = ques.mayUser(trans, trans.user(), nsd, Access.read);
3771                 if (rnd.notOK()) {
3772                     return Result.err(rnd);
3773                 }
3774             } else {
3775                 rnd = ques.validNSOfDomain(trans, user);
3776                 if (rnd.notOK()) {
3777                     return Result.err(rnd);
3778                 }
3779
3780                 rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
3781                 if (rnd.notOK()) {
3782                     return Result.err(rnd);
3783                 }
3784             }
3785          }
3786         Result<List<HistoryDAO.Data>> resp = ques.historyDAO().readByUser(trans, user, yyyymm);
3787         if (resp.notOK()) {
3788             return Result.err(resp);
3789         }
3790         return mapper.history(trans, resp.value,sort);
3791     }
3792
3793     @Override
3794     public Result<HISTORY> getHistoryByRole(AuthzTrans trans, String role, int[] yyyymm, final int sort) {
3795         final Validator v = new ServiceValidator();
3796         if (v.nullOrBlank("Role",role).err()) {
3797             return Result.err(Status.ERR_BadData,v.errs());
3798         }
3799
3800         Result<RoleDAO.Data> rrdd = RoleDAO.Data.decode(trans, ques, role);
3801         if (rrdd.notOK()) {
3802             return Result.err(rrdd);
3803         }
3804
3805         Result<NsDAO.Data> rnd = ques.mayUser(trans, trans.user(), rrdd.value, Access.read);
3806         if (rnd.notOK()) {
3807             return Result.err(rnd);
3808         }
3809         Result<List<HistoryDAO.Data>> resp = ques.historyDAO().readBySubject(trans, role, "role", yyyymm);
3810         if (resp.notOK()) {
3811             return Result.err(resp);
3812         }
3813         return mapper.history(trans, resp.value,sort);
3814     }
3815
3816     @Override
3817     public Result<HISTORY> getHistoryByPerm(AuthzTrans trans, String type, int[] yyyymm, final int sort) {
3818         final Validator v = new ServiceValidator();
3819         if (v.nullOrBlank("Type",type)
3820             .err()) {
3821             return Result.err(Status.ERR_BadData,v.errs());
3822         }
3823
3824         // May user see Namespace of Permission (since it's only one piece... we can't check for "is permission part of")
3825         Result<List<HistoryDAO.Data>> resp;
3826         if(type.startsWith(trans.user())) {
3827             resp = ques.historyDAO().readBySubject(trans, type, "perm", yyyymm);
3828         } else {
3829             Result<NsDAO.Data> rnd = ques.deriveNs(trans,type);
3830             if (rnd.notOK()) {
3831                 return Result.err(rnd);
3832             }
3833             rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
3834             if (rnd.notOK()) {
3835                 return Result.err(rnd);
3836             }
3837             resp = ques.historyDAO().readBySubject(trans, type, "perm", yyyymm);
3838         }
3839
3840         if (resp.notOK()) {
3841             return Result.err(resp);
3842         }
3843         return mapper.history(trans, resp.value,sort);
3844     }
3845
3846     @Override
3847     public Result<HISTORY> getHistoryByNS(AuthzTrans trans, String ns, int[] yyyymm, final int sort) {
3848         final Validator v = new ServiceValidator();
3849         if (v.nullOrBlank("NS",ns).err()) {
3850             return Result.err(Status.ERR_BadData,v.errs());
3851         }
3852
3853         Result<NsDAO.Data> rnd = ques.deriveNs(trans,ns);
3854         if (rnd.notOK()) {
3855             return Result.err(rnd);
3856         }
3857         rnd = ques.mayUser(trans, trans.user(), rnd.value, Access.read);
3858         if (rnd.notOK()) {
3859             return Result.err(rnd);
3860         }
3861
3862         Result<List<HistoryDAO.Data>> resp = ques.historyDAO().readBySubject(trans, ns, "ns", yyyymm);
3863         if (resp.notOK()) {
3864             return Result.err(resp);
3865         }
3866         return mapper.history(trans, resp.value,sort);
3867     }
3868
3869     @Override
3870     public Result<HISTORY> getHistoryBySubject(AuthzTrans trans, String subject, String target, int[] yyyymm, final int sort) {
3871         NsDAO.Data ndd = new NsDAO.Data();
3872         ndd.name = FQI.reverseDomain(subject);
3873         Result<Data> rnd = ques.mayUser(trans, trans.user(), ndd, Access.read);
3874         if (rnd.notOK()) {
3875             return Result.err(rnd);
3876         }
3877
3878         Result<List<HistoryDAO.Data>> resp = ques.historyDAO().readBySubject(trans, subject, target, yyyymm);
3879         if (resp.notOK()) {
3880             return Result.err(resp);
3881         }
3882         return mapper.history(trans, resp.value,sort);
3883     }
3884
3885 /***********************************
3886  * DELEGATE
3887  ***********************************/
3888     @Override
3889     public Result<Void> createDelegate(final AuthzTrans trans, REQUEST base) {
3890         return createOrUpdateDelegate(trans, base, Question.Access.create);
3891     }
3892
3893     @Override
3894     public Result<Void> updateDelegate(AuthzTrans trans, REQUEST base) {
3895         return createOrUpdateDelegate(trans, base, Question.Access.write);
3896     }
3897
3898
3899     private Result<Void> createOrUpdateDelegate(final AuthzTrans trans, REQUEST base, final Access access) {
3900         final Result<DelegateDAO.Data> rd = mapper.delegate(trans, base);
3901         final ServiceValidator v = new ServiceValidator();
3902         if (v.delegate(trans.org(),rd).err()) {
3903             return Result.err(Status.ERR_BadData,v.errs());
3904         }
3905
3906         final DelegateDAO.Data dd = rd.value;
3907         
3908         if(dd.user.contentEquals(dd.delegate) && !trans.requested(force)) {
3909                 return Result.err(Status.ERR_InvalidDelegate,dd.user + " cannot delegate to self");
3910         }
3911
3912         Result<List<DelegateDAO.Data>> ddr = ques.delegateDAO().read(trans, dd);
3913         if (access==Access.create && ddr.isOKhasData()) {
3914             return Result.err(Status.ERR_ConflictAlreadyExists, "[%s] already delegates to [%s]", dd.user, ddr.value.get(0).delegate);
3915         } else if (access!=Access.create && ddr.notOKorIsEmpty()) {
3916             return Result.err(Status.ERR_NotFound, "[%s] does not have a Delegate Record to [%s].",dd.user,access.name());
3917         }
3918         Result<Void> rv = ques.mayUser(trans, dd, access);
3919         if (rv.notOK()) {
3920             return rv;
3921         }
3922
3923         Result<FutureDAO.Data> fd = mapper.future(trans,DelegateDAO.TABLE,base, dd, false,
3924             () -> {
3925                 StringBuilder sb = new StringBuilder();
3926                 sb.append(access.name());
3927                 sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
3928                 sb.append("Delegate ");
3929                 sb.append(access==Access.create?"[":"to [");
3930                 sb.append(rd.value.delegate);
3931                 sb.append("] for [");
3932                 sb.append(rd.value.user);
3933                 sb.append(']');
3934                 return sb.toString();
3935             },
3936             () -> {
3937                 return Result.ok(); // Validate in code above
3938             });
3939
3940         switch(fd.status) {
3941             case OK:
3942                 Result<String> rfc = func.createFuture(trans, fd.value,
3943                         dd.user, trans.user(),null, access==Access.create?FUTURE_OP.C:FUTURE_OP.U);
3944                 if (rfc.isOK()) {
3945                     return Result.err(Status.ACC_Future, "Delegate for [%s]",
3946                             dd.user);
3947                 } else {
3948                     return Result.err(rfc);
3949                 }
3950             case Status.ACC_Now:
3951                 if (access==Access.create) {
3952                     Result<DelegateDAO.Data> rdr = ques.delegateDAO().create(trans, dd);
3953                     if (rdr.isOK()) {
3954                         return Result.ok();
3955                     } else {
3956                         return Result.err(rdr);
3957                     }
3958                 } else {
3959                     return ques.delegateDAO().update(trans, dd);
3960                 }
3961             default:
3962                 return Result.err(fd);
3963         }
3964     }
3965
3966     @Override
3967     public Result<Void> deleteDelegate(AuthzTrans trans, REQUEST base) {
3968         final Result<DelegateDAO.Data> rd = mapper.delegate(trans, base);
3969         final Validator v = new ServiceValidator();
3970         if (v.notOK(rd).nullOrBlank("User", rd.value.user).err()) {
3971             return Result.err(Status.ERR_BadData,v.errs());
3972         }
3973
3974         Result<List<DelegateDAO.Data>> ddl;
3975         if ((ddl=ques.delegateDAO().read(trans, rd.value)).notOKorIsEmpty()) {
3976             return Result.err(Status.ERR_DelegateNotFound,"Cannot delete non-existent Delegate");
3977         }
3978         final DelegateDAO.Data dd = ddl.value.get(0);
3979         Result<Void> rv = ques.mayUser(trans, dd, Access.write);
3980         if (rv.notOK()) {
3981             return rv;
3982         }
3983
3984         return ques.delegateDAO().delete(trans, dd, false);
3985     }
3986
3987     @Override
3988     public Result<Void> deleteDelegate(AuthzTrans trans, String userName) {
3989         DelegateDAO.Data dd = new DelegateDAO.Data();
3990         final Validator v = new ServiceValidator();
3991         if (v.nullOrBlank("User", userName).err()) {
3992             return Result.err(Status.ERR_BadData,v.errs());
3993         }
3994         dd.user = userName;
3995         Result<List<DelegateDAO.Data>> ddl;
3996         if ((ddl=ques.delegateDAO().read(trans, dd)).notOKorIsEmpty()) {
3997             return Result.err(Status.ERR_DelegateNotFound,"Cannot delete non-existent Delegate");
3998         }
3999         dd = ddl.value.get(0);
4000         Result<Void> rv = ques.mayUser(trans, dd, Access.write);
4001         if (rv.notOK()) {
4002             return rv;
4003         }
4004
4005         return ques.delegateDAO().delete(trans, dd, false);
4006     }
4007
4008     @Override
4009     public Result<DELGS> getDelegatesByUser(AuthzTrans trans, String user) {
4010         final Validator v = new ServiceValidator();
4011         if (v.nullOrBlank("User", user).err()) {
4012             return Result.err(Status.ERR_BadData,v.errs());
4013         }
4014
4015         DelegateDAO.Data ddd = new DelegateDAO.Data();
4016         ddd.user = user;
4017         ddd.delegate = null;
4018         Result<Void> rv = ques.mayUser(trans, ddd, Access.read);
4019         if (rv.notOK()) {
4020             return Result.err(rv);
4021         }
4022
4023         TimeTaken tt = trans.start("Get delegates for a user", Env.SUB);
4024
4025         Result<List<DelegateDAO.Data>> dbDelgs = ques.delegateDAO().read(trans, user);
4026         try {
4027             if (dbDelgs.isOKhasData()) {
4028                 return mapper.delegate(dbDelgs.value);
4029             } else {
4030                 return Result.err(Status.ERR_DelegateNotFound,"No Delegate found for [%s]",user);
4031             }
4032         } finally {
4033             tt.done();
4034         }
4035     }
4036
4037     @Override
4038     public Result<DELGS> getDelegatesByDelegate(AuthzTrans trans, String delegate) {
4039         final Validator v = new ServiceValidator();
4040         if (v.nullOrBlank("Delegate", delegate).err()) {
4041             return Result.err(Status.ERR_BadData,v.errs());
4042         }
4043
4044         DelegateDAO.Data ddd = new DelegateDAO.Data();
4045         ddd.user = delegate;
4046         Result<Void> rv = ques.mayUser(trans, ddd, Access.read);
4047         if (rv.notOK()) {
4048             return Result.err(rv);
4049         }
4050
4051         TimeTaken tt = trans.start("Get users for a delegate", Env.SUB);
4052
4053         Result<List<DelegateDAO.Data>> dbDelgs = ques.delegateDAO().readByDelegate(trans, delegate);
4054         try {
4055             if (dbDelgs.isOKhasData()) {
4056                 return mapper.delegate(dbDelgs.value);
4057             } else {
4058                 return Result.err(Status.ERR_DelegateNotFound,"Delegate [%s] is not delegating for anyone.",delegate);
4059             }
4060         } finally {
4061             tt.done();
4062         }
4063     }
4064
4065 /***********************************
4066  * APPROVAL
4067  ***********************************/
4068     private static final String APPR_FMT = "actor=%s, action=%s, operation=\"%s\", requestor=%s, delegator=%s";
4069     @Override
4070     public Result<Void> updateApproval(AuthzTrans trans, APPROVALS approvals) {
4071         Result<List<ApprovalDAO.Data>> rlad = mapper.approvals(approvals);
4072         if (rlad.notOK()) {
4073             return Result.err(rlad);
4074         }
4075         int numApprs = rlad.value.size();
4076         if (numApprs<1) {
4077             return Result.err(Status.ERR_NoApprovals,"No Approvals sent for Updating");
4078         }
4079         int numProcessed = 0;
4080         String user = trans.user();
4081
4082         Result<List<ApprovalDAO.Data>> curr;
4083         Lookup<List<ApprovalDAO.Data>> apprByTicket=null;
4084         for (ApprovalDAO.Data updt : rlad.value) {
4085             if (updt.ticket!=null) {
4086                 curr = ques.approvalDAO().readByTicket(trans, updt.ticket);
4087                 if (curr.isOKhasData()) {
4088                     final List<ApprovalDAO.Data> add = curr.value;
4089                     // Store a Pre-Lookup
4090                     apprByTicket = (trans1, noop) -> add;
4091                 }
4092             } else if (updt.id!=null) {
4093                 curr = ques.approvalDAO().read(trans, updt);
4094             } else if (updt.approver!=null) {
4095                 curr = ques.approvalDAO().readByApprover(trans, updt.approver);
4096             } else {
4097                 return Result.err(Status.ERR_BadData,"Approvals need ID, Ticket or Approval data to update");
4098             }
4099
4100             if (curr.isOKhasData()) {
4101                 Map<String, Result<List<DelegateDAO.Data>>> delegateCache = new HashMap<>();
4102                 Map<UUID, FutureDAO.Data> futureCache = new HashMap<>();
4103                 FutureDAO.Data hasDeleted = new FutureDAO.Data();
4104
4105                 for (ApprovalDAO.Data cd : curr.value) {
4106                     if ("pending".equals(cd.status)) {
4107                         // Check for right record.  Need ID, or (Ticket&Trans.User==Appr)
4108                         // If Default ID
4109                         boolean delegatedAction = ques.isDelegated(trans, user, cd.approver, delegateCache);
4110                         String delegator = cd.approver;
4111                         if (updt.id!=null ||
4112                             (updt.ticket!=null && user.equals(cd.approver)) ||
4113                             (updt.ticket!=null && delegatedAction)) {
4114                             if (updt.ticket.equals(cd.ticket)) {
4115                                 Changed ch = new Changed();
4116                                 cd.id = ch.changed(cd.id,updt.id);
4117 //                                cd.ticket = changed(cd.ticket,updt.ticket);
4118                                 cd.user = ch.changed(cd.user,updt.user);
4119                                 cd.approver = ch.changed(cd.approver,updt.approver);
4120                                 cd.type = ch.changed(cd.type,updt.type);
4121                                 cd.status = ch.changed(cd.status,updt.status);
4122                                 cd.memo = ch.changed(cd.memo,updt.memo);
4123                                 cd.operation = ch.changed(cd.operation,updt.operation);
4124                                 cd.updated = ch.changed(cd.updated,updt.updated==null?new Date():updt.updated);
4125 //                                if (updt.status.equals("denied")) {
4126 //                                    cd.last_notified = null;
4127 //                                }
4128                                 if (cd.ticket!=null) {
4129                                     FutureDAO.Data fdd = futureCache.get(cd.ticket);
4130                                     if (fdd==null) { // haven't processed ticket yet
4131                                         Result<FutureDAO.Data> rfdd = ques.futureDAO().readPrimKey(trans, cd.ticket);
4132                                         if (rfdd.isOK()) {
4133                                             fdd = rfdd.value; // null is ok
4134                                         } else {
4135                                             fdd = hasDeleted;
4136                                         }
4137                                         futureCache.put(cd.ticket, fdd); // processed this Ticket... don't do others on this ticket
4138                                     }
4139                                     if (fdd==hasDeleted) { // YES, by Object
4140                                         cd.ticket = null;
4141                                         cd.status = "ticketDeleted";
4142                                         ch.hasChanged(true);
4143                                     } else {
4144                                         FUTURE_OP fop = FUTURE_OP.toFO(cd.operation);
4145                                         if (fop==null) {
4146                                             trans.info().printf("Approval Status %s is not actionable",cd.status);
4147                                         } else if (apprByTicket!=null) {
4148                                             Result<OP_STATUS> rv = func.performFutureOp(trans, fop, fdd, apprByTicket,func.urDBLookup);
4149                                             if (rv.isOK()) {
4150                                                 switch(rv.value) {
4151                                                     case E:
4152                                                         if (delegatedAction) {
4153                                                             trans.audit().printf(APPR_FMT,user,updt.status,cd.memo,cd.user,delegator);
4154                                                         }
4155                                                         futureCache.put(cd.ticket, hasDeleted);
4156                                                         break;
4157                                                     case D:
4158                                                     case L:
4159                                                         ch.hasChanged(true);
4160                                                         trans.audit().printf(APPR_FMT,user,rv.value.desc(),cd.memo,cd.user,delegator);
4161                                                         futureCache.put(cd.ticket, hasDeleted);
4162                                                         break;
4163                                                     default:
4164                                                 }
4165                                             } else {
4166                                                 trans.info().log(rv.toString());
4167                                             }
4168                                         }
4169
4170                                     }
4171                                     ++numProcessed;
4172                                 }
4173                                 if (ch.hasChanged()) {
4174                                     ques.approvalDAO().update(trans, cd, true);
4175                                 }
4176                             }
4177                         }
4178                     }
4179                 }
4180             }
4181         }
4182
4183         if (numApprs==numProcessed) {
4184             return Result.ok();
4185         }
4186         return Result.err(Status.ERR_ActionNotCompleted,numProcessed + " out of " + numApprs + " completed");
4187
4188     }
4189
4190     private static class Changed {
4191         private boolean hasChanged = false;
4192
4193         public<T> T changed(T src, T proposed) {
4194             if (proposed==null || (src!=null && src.equals(proposed))) {
4195                 return src;
4196             }
4197             hasChanged=true;
4198             return proposed;
4199         }
4200
4201         public void hasChanged(boolean b) {
4202             hasChanged=b;
4203         }
4204
4205         public boolean hasChanged() {
4206             return hasChanged;
4207         }
4208     }
4209
4210     @Override
4211     public Result<APPROVALS> getApprovalsByUser(AuthzTrans trans, String user) {
4212         final Validator v = new ServiceValidator();
4213         if (v.nullOrBlank("User", user).err()) {
4214             return Result.err(Status.ERR_BadData,v.errs());
4215         }
4216
4217         Result<List<ApprovalDAO.Data>> rapd = ques.approvalDAO().readByUser(trans, user);
4218         if (rapd.isOK()) {
4219             return mapper.approvals(rapd.value);
4220         } else {
4221             return Result.err(rapd);
4222         }
4223 }
4224
4225     @Override
4226     public Result<APPROVALS> getApprovalsByTicket(AuthzTrans trans, String ticket) {
4227         final Validator v = new ServiceValidator();
4228         if (v.nullOrBlank("Ticket", ticket).err()) {
4229             return Result.err(Status.ERR_BadData,v.errs());
4230         }
4231         UUID uuid;
4232         try {
4233             uuid = UUID.fromString(ticket);
4234         } catch (IllegalArgumentException e) {
4235             return Result.err(Status.ERR_BadData,e.getMessage());
4236         }
4237
4238         Result<List<ApprovalDAO.Data>> rapd = ques.approvalDAO().readByTicket(trans, uuid);
4239         if (rapd.isOK()) {
4240             return mapper.approvals(rapd.value);
4241         } else {
4242             return Result.err(rapd);
4243         }
4244     }
4245
4246     @Override
4247     public Result<APPROVALS> getApprovalsByApprover(AuthzTrans trans, String approver) {
4248         final Validator v = new ServiceValidator();
4249         if (v.nullOrBlank("Approver", approver).err()) {
4250             return Result.err(Status.ERR_BadData,v.errs());
4251         }
4252
4253         List<ApprovalDAO.Data> listRapds = new ArrayList<>();
4254
4255         Result<List<ApprovalDAO.Data>> myRapd = ques.approvalDAO().readByApprover(trans, approver);
4256         if (myRapd.notOK()) {
4257             return Result.err(myRapd);
4258         }
4259
4260         listRapds.addAll(myRapd.value);
4261
4262         Result<List<DelegateDAO.Data>> delegatedFor = ques.delegateDAO().readByDelegate(trans, approver);
4263         if (delegatedFor.isOK()) {
4264             for (DelegateDAO.Data dd : delegatedFor.value) {
4265                 if (dd.expires.after(new Date())) {
4266                     String delegator = dd.user;
4267                     Result<List<ApprovalDAO.Data>> rapd = ques.approvalDAO().readByApprover(trans, delegator);
4268                     if (rapd.isOK()) {
4269                         for (ApprovalDAO.Data d : rapd.value) {
4270                             if (!d.user.equals(trans.user())) {
4271                                 listRapds.add(d);
4272                             }
4273                         }
4274                     }
4275                 }
4276             }
4277         }
4278
4279         return mapper.approvals(listRapds);
4280     }
4281
4282     /* (non-Javadoc)
4283      * @see org.onap.aaf.auth.service.AuthzService#clearCache(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String)
4284      */
4285     @Override
4286     public Result<Void> cacheClear(AuthzTrans trans, String cname) {
4287         if (ques.isGranted(trans,trans.user(),ROOT_NS,CACHE,cname,"clear")) {
4288             return ques.clearCache(trans,cname);
4289         }
4290         return Result.err(Status.ERR_Denied, "%s does not have AAF Permission '%s.%s|%s|clear",
4291                 trans.user(),ROOT_NS,CACHE,cname);
4292     }
4293
4294     /* (non-Javadoc)
4295      * @see org.onap.aaf.auth.service.AuthzService#cacheClear(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String, java.lang.Integer)
4296      */
4297     @Override
4298     public Result<Void> cacheClear(AuthzTrans trans, String cname, int[] segment) {
4299         if (ques.isGranted(trans,trans.user(),ROOT_NS,CACHE,cname,"clear")) {
4300             Result<Void> v=null;
4301             for (int i: segment) {
4302                 v=ques.cacheClear(trans,cname,i);
4303             }
4304             if (v!=null) {
4305                 return v;
4306             }
4307         }
4308         return Result.err(Status.ERR_Denied, "%s does not have AAF Permission '%s.%s|%s|clear",
4309                 trans.user(),ROOT_NS,CACHE,cname);
4310     }
4311
4312     /* (non-Javadoc)
4313      * @see org.onap.aaf.auth.service.AuthzService#dbReset(org.onap.aaf.auth.env.test.AuthzTrans)
4314      */
4315     @Override
4316     public void dbReset(AuthzTrans trans) {
4317         ques.historyDAO().reportPerhapsReset(trans, null);
4318     }
4319
4320 }
4321