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