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