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