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