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