Collection syntax change because of Sonar
[aaf/authz.git] / auth / auth-certman / src / main / java / org / onap / aaf / auth / cm / service / CMService.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.cm.service;
23
24 import java.io.IOException;
25 import java.net.InetAddress;
26 import java.net.UnknownHostException;
27 import java.nio.ByteBuffer;
28 import java.security.NoSuchAlgorithmException;
29 import java.security.cert.X509Certificate;
30 import java.util.ArrayList;
31 import java.util.Date;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Set;
35
36 import org.onap.aaf.auth.cm.AAF_CM;
37 import org.onap.aaf.auth.cm.ca.CA;
38 import org.onap.aaf.auth.cm.ca.X509andChain;
39 import org.onap.aaf.auth.cm.cert.BCFactory;
40 import org.onap.aaf.auth.cm.cert.CSRMeta;
41 import org.onap.aaf.auth.cm.data.CertDrop;
42 import org.onap.aaf.auth.cm.data.CertRenew;
43 import org.onap.aaf.auth.cm.data.CertReq;
44 import org.onap.aaf.auth.cm.data.CertResp;
45 import org.onap.aaf.auth.cm.validation.CertmanValidator;
46 import org.onap.aaf.auth.dao.CassAccess;
47 import org.onap.aaf.auth.dao.cass.ArtiDAO;
48 import org.onap.aaf.auth.dao.cass.CacheInfoDAO;
49 import org.onap.aaf.auth.dao.cass.CertDAO;
50 import org.onap.aaf.auth.dao.cass.CertDAO.Data;
51 import org.onap.aaf.auth.dao.cass.CredDAO;
52 import org.onap.aaf.auth.dao.cass.HistoryDAO;
53 import org.onap.aaf.auth.dao.cass.Status;
54 import org.onap.aaf.auth.dao.hl.Question;
55 import org.onap.aaf.auth.env.AuthzTrans;
56 import org.onap.aaf.auth.layer.Result;
57 import org.onap.aaf.auth.org.Organization;
58 import org.onap.aaf.auth.org.Organization.Identity;
59 import org.onap.aaf.auth.org.OrganizationException;
60 import org.onap.aaf.cadi.Hash;
61 import org.onap.aaf.cadi.aaf.AAFPermission;
62 import org.onap.aaf.cadi.configure.Factory;
63 import org.onap.aaf.cadi.util.FQI;
64 import org.onap.aaf.misc.env.APIException;
65 import org.onap.aaf.misc.env.util.Chrono;
66
67
68 public class CMService {
69         // If we add more CAs, may want to parameterize
70         private static final int STD_RENEWAL = 30;
71         private static final int MAX_RENEWAL = 60;
72         private static final int MIN_RENEWAL = 10;
73         
74         public static final String REQUEST = "request";
75         public static final String RENEW = "renew";
76         public static final String DROP = "drop";
77 //      public static final String SANS = "san";
78         public static final String IPS = "ips";
79         public static final String DOMAIN = "domain";
80         
81         private static final String[] NO_NOTES = new String[0];
82         private final CertDAO certDAO;
83         private final CredDAO credDAO;
84         private final ArtiDAO artiDAO;
85 //      private DAO<AuthzTrans, ?>[] daos;
86         private AAF_CM certman;
87
88 //      @SuppressWarnings("unchecked")
89         public CMService(final AuthzTrans trans, AAF_CM certman) throws APIException, IOException {
90                 // Jonathan 4/2015 SessionFilter unneeded... DataStax already deals with Multithreading well
91                 
92                 HistoryDAO hd = new HistoryDAO(trans,  certman.cluster, CassAccess.KEYSPACE);
93                 CacheInfoDAO cid = new CacheInfoDAO(trans, hd);
94                 certDAO = new CertDAO(trans, hd, cid);
95                 credDAO = new CredDAO(trans, hd, cid);
96                 artiDAO = new ArtiDAO(trans, hd, cid);
97                 
98 //              daos =(DAO<AuthzTrans, ?>[]) new DAO<?,?>[] {
99 //                              hd,cid,certDAO,credDAO,artiDAO
100 //              };
101 //
102                 this.certman = certman;
103         }
104         
105         public Result<CertResp> requestCert(final AuthzTrans trans,final Result<CertReq> req, final CA ca) {
106                 if(req.isOK()) {
107
108                         if(req.value.fqdns.isEmpty()) {
109                                 return Result.err(Result.ERR_BadData,"No Machines passed in Request");
110                         }
111                         
112                         String key = req.value.fqdns.get(0);
113                         
114                         // Policy 6: Requester must be granted Change permission in Namespace requested
115                         String mechNS = FQI.reverseDomain(req.value.mechid);
116                         if(mechNS==null) {
117                                 return Result.err(Status.ERR_Denied, "%s does not reflect a valid AAF Namespace",req.value.mechid);
118                         }
119                         
120
121                         // Disallow non-AAF CA without special permission
122                         if(!ca.getName().equals("aaf") && !trans.fish( new AAFPermission(mechNS+".certman", ca.getName(), REQUEST))) {
123                                 return Result.err(Status.ERR_Denied, "'%s' does not have permission to request Certificates from Certificate Authority '%s'", 
124                                                 trans.user(),ca.getName());
125                         }
126
127                         List<String> notes = null;
128                         List<String> fqdns = new ArrayList<>(req.value.fqdns);
129                         
130                         
131                         String email = null;
132
133                         try {
134                                 Organization org = trans.org();
135                                 
136                                 InetAddress primary = null;
137                                 // Organize incoming information to get to appropriate Artifact
138                                 if(fqdns.size()>=1) {
139                                         // Accept domain wild cards, but turn into real machines
140                                         // Need *domain.com:real.machine.domain.com:san.machine.domain.com:...
141                                         if(fqdns.get(0).startsWith("*")) { // Domain set
142                                                 if(!trans.fish(new AAFPermission(ca.getPermType(), ca.getName(), DOMAIN))) {
143                                                         return Result.err(Result.ERR_Denied, "Domain based Authorizations (" + fqdns.get(0) + ") requires Exception");
144                                                 }
145                                                 
146                                                 //TODO check for Permission in Add Artifact?
147                                                 String domain = fqdns.get(0).substring(1);
148                                                 fqdns.remove(0);
149                                                 if(fqdns.size()>=1) {
150                                                         InetAddress ia = InetAddress.getByName(fqdns.get(0));
151                                                         if(ia==null) {
152                                                                 return Result.err(Result.ERR_Denied, "Request not made from matching IP matching domain");
153                                                         } else if(ia.getHostName().endsWith(domain)) {
154                                                                 primary = ia;
155                                                         }
156                                                 } else {
157                                                         return Result.err(Result.ERR_Denied, "Requests using domain require machine declaration");
158                                                 }
159                                         
160                                         } else {
161                                                 for(String cn : req.value.fqdns) {
162                                                         try {
163                                                                 InetAddress[] ias = InetAddress.getAllByName(cn);
164                                                                 Set<String> potentialSanNames = new HashSet<>();
165                                                                 for(InetAddress ia1 : ias) {
166                                                                         InetAddress ia2 = InetAddress.getByAddress(ia1.getAddress());
167                                                                         if(primary==null && ias.length==1 && trans.ip().equals(ia1.getHostAddress())) {
168                                                                                 primary = ia1;
169                                                                         } else if(!cn.equals(ia1.getHostName()) && !ia2.getHostName().equals(ia2.getHostAddress())) {
170                                                                                 potentialSanNames.add(ia1.getHostName());
171                                                                         }
172                                                                 }
173                                                         } catch (UnknownHostException e1) {
174                                                                 return Result.err(Result.ERR_BadData,"There is no DNS lookup for %s",cn);
175                                                         }
176                                                 
177                                                 }
178                                         }
179                                 }
180                                 
181                                 if(primary==null) {
182                                         return Result.err(Result.ERR_Denied, "Request not made from matching IP (%s)",trans.ip());
183 //                                      return Result.err(Result.ERR_BadData,"Calling Machine does not match DNS lookup for %s",req.value.fqdns.get(0));
184                                 }
185                                 
186                                 ArtiDAO.Data add = null;
187                                 Result<List<ArtiDAO.Data>> ra = artiDAO.read(trans, req.value.mechid,primary.getHostAddress());
188                                 if(ra.isOKhasData()) {
189                                         if(add==null) {
190                                                 add = ra.value.get(0); // single key
191                                         }
192                                 } else {
193                                          ra = artiDAO.read(trans, req.value.mechid,key);
194                                          if(ra.isOKhasData()) { // is the Template available?
195                                                  add = ra.value.get(0);
196                                                  add.machine=primary.getHostName();
197                                                  for(String s : fqdns) {
198                                                           if(!s.equals(add.machine)) {
199                                                                   add.sans(true).add(s);
200                                                           }
201                                                  }
202                                                  Result<ArtiDAO.Data> rc = artiDAO.create(trans, add); // Create new Artifact from Template
203                                                  if(rc.notOK()) {
204                                                          return Result.err(rc);
205                                                  }
206                                          } else {
207                                                  add = ra.value.get(0);
208                                          }
209                                 }
210                                 
211                                 // Add Artifact listed FQDNs
212                                 if(add.sans!=null) {
213                                         for(String s : add.sans) {
214                                                 if(!fqdns.contains(s)) {
215                                                         fqdns.add(s);
216                                                 }
217                                         }
218                                 }
219
220                                 // Policy 2: If Config marked as Expired, do not create or renew
221                                 Date now = new Date();
222                                 if(add.expires!=null && now.after(add.expires)) {
223                                         return Result.err(Result.ERR_Policy,"Configuration for %s %s is expired %s",add.mechid,add.machine,Chrono.dateFmt.format(add.expires));
224                                 }
225                                 
226                                 // Policy 3: MechID must be current
227                                 Identity muser = org.getIdentity(trans, add.mechid);
228                                 if(muser == null) {
229                                         return Result.err(Result.ERR_Policy,"MechID must exist in %s",org.getName());
230                                 }
231                                 
232                                 // Policy 4: Sponsor must be current
233                                 Identity ouser = muser.responsibleTo();
234                                 if(ouser==null) {
235                                         return Result.err(Result.ERR_Policy,"%s does not have a current sponsor at %s",add.mechid,org.getName());
236                                 } else if(!ouser.isFound() || ouser.mayOwn()!=null) {
237                                         return Result.err(Result.ERR_Policy,"%s reports that %s cannot be responsible for %s",org.getName(),trans.user());
238                                 }
239                                 
240                                 // Set Email from most current Sponsor
241                                 email = ouser.email();
242                                 
243                                 // Policy 5: keep Artifact data current
244                                 if(!ouser.fullID().equals(add.sponsor)) {
245                                         add.sponsor = ouser.fullID();
246                                         artiDAO.update(trans, add);
247                                 }
248                 
249                                 // Policy 7: Caller must be the MechID or have specifically delegated permissions
250                                 if(!(trans.user().equals(req.value.mechid) || trans.fish(new AAFPermission(mechNS + ".certman", ca.getName() , "request")))) {
251                                         return Result.err(Status.ERR_Denied, "%s must have access to modify x509 certs in NS %s",trans.user(),mechNS);
252                                 }
253                                 
254                                 // Policy 8: SANs only allowed by Exception... need permission
255                                 // 7/25/2017 - SAN Permission no longer required. CSO
256 //                              if(fqdns.size()>1 && !certman.aafLurPerm.fish(
257 //                                              new Principal() {
258 //                                                      @Override
259 //                                                      public String getName() {
260 //                                                              return req.value.mechid;
261 //                                                      }
262 //                                              },
263 //                                              new AAFPermission(ca.getPermType(), ca.getName(), SANS))) {
264 //                                      if(notes==null) {notes = new ArrayList<>();}
265 //                                      notes.add("Warning: Subject Alternative Names only allowed by Permission: Get CSO Exception.");
266 //                                      return Result.err(Status.ERR_Denied, "%s must have a CSO Exception to work with SAN",trans.user());
267 //                              }
268                                 
269                                 // Make sure Primary is the first in fqdns
270                                 if(fqdns.size()>1) {
271                                         for(int i=0;i<fqdns.size();++i) {
272                                                 if(fqdns.get(i).equals(primary.getHostName())) {
273                                                         if(i!=0) {
274                                                                 String tmp = fqdns.get(0);
275                                                                 fqdns.set(0, primary.getHostName());
276                                                                 fqdns.set(i, tmp);
277                                                         }
278                                                 }
279                                         }
280                                 }
281                         } catch (Exception e) {
282                                 trans.error().log(e);
283                                 return Result.err(Status.ERR_Denied,"MechID Sponsorship cannot be determined at this time.  Try later");
284                         }
285                         
286                         CSRMeta csrMeta;
287                         try {
288                                 csrMeta = BCFactory.createCSRMeta(
289                                                 ca, 
290                                                 req.value.mechid, 
291                                                 email, 
292                                                 fqdns);
293                                 X509andChain x509ac = ca.sign(trans, csrMeta);
294                                 if(x509ac==null) {
295                                         return Result.err(Result.ERR_ActionNotCompleted,"x509 Certificate not signed by CA");
296                                 }
297                                 trans.info().printf("X509 Subject: %s", x509ac.getX509().getSubjectDN());
298 //                              for(String s: x509ac.getTrustChain()) {
299 //                                      trans.warn().printf("Trust Cert: \n%s", s);
300 //                              }
301                                 
302                                 X509Certificate x509 = x509ac.getX509();
303                                 CertDAO.Data cdd = new CertDAO.Data();
304                                 cdd.ca=ca.getName();
305                                 cdd.serial=x509.getSerialNumber();
306                                 cdd.id=req.value.mechid;
307                                 cdd.x500=x509.getSubjectDN().getName();
308                                 cdd.x509=Factory.toString(trans, x509);
309                                 certDAO.create(trans, cdd);
310                                 
311                                 CredDAO.Data crdd = new CredDAO.Data();
312                                 crdd.other = Question.random.nextInt();
313                                 crdd.cred=getChallenge256SaltedHash(csrMeta.challenge(),crdd.other);
314                                 crdd.expires = x509.getNotAfter();
315                                 crdd.id = req.value.mechid;
316                                 crdd.ns = Question.domain2ns(crdd.id);
317                                 crdd.type = CredDAO.CERT_SHA256_RSA;
318                                 credDAO.create(trans, crdd);
319                                 
320                                 CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(),compileNotes(notes));
321                                 return Result.ok(cr);
322                         } catch (Exception e) {
323                                 trans.error().log(e);
324                                 return Result.err(Result.ERR_ActionNotCompleted,e.getMessage());
325                         }
326                 } else {
327                         return Result.err(req);
328                 }
329         }
330
331     public Result<CertResp> renewCert(AuthzTrans trans, Result<CertRenew> renew) {
332                 if(renew.isOK()) {
333                         return Result.err(Result.ERR_NotImplemented,"Not implemented yet");
334                 } else {
335                         return Result.err(renew);
336                 }       
337         }
338
339         public Result<Void> dropCert(AuthzTrans trans, Result<CertDrop> drop) {
340                 if(drop.isOK()) {
341                         return Result.err(Result.ERR_NotImplemented,"Not implemented yet");
342                 } else {
343                         return Result.err(drop);
344                 }       
345         }
346
347         public Result<List<Data>> readCertsByMechID(AuthzTrans trans, String mechID) {
348                 // Policy 1: To Read, must have NS Read or is Sponsor
349                 String ns = Question.domain2ns(mechID);
350                 try {
351                         if( trans.user().equals(mechID)
352                                         || trans.fish(new AAFPermission(ns + ".access", "*", "read"))
353                                         || (trans.org().validate(trans,Organization.Policy.OWNS_MECHID,null,mechID))==null) {
354                                 return certDAO.readID(trans, mechID);
355                         } else {
356                                 return Result.err(Result.ERR_Denied,"%s is not the ID, Sponsor or NS Owner/Admin for %s at %s",
357                                                 trans.user(),mechID,trans.org().getName());
358                         }
359                 } catch(OrganizationException e) {
360                         return Result.err(e);
361                 }
362         }
363
364         public Result<CertResp> requestPersonalCert(AuthzTrans trans, CA ca) {
365                 if(ca.inPersonalDomains(trans.getUserPrincipal())) {
366                         Organization org = trans.org();
367                                 
368                         // Policy 1: MechID must be current
369                         Identity ouser;
370                         try {
371                                 ouser = org.getIdentity(trans, trans.user());
372                         } catch (OrganizationException e1) {
373                                 trans.error().log(e1);
374                                 ouser = null;
375                         }
376                         if(ouser == null) {
377                                 return Result.err(Result.ERR_Policy,"Requesting User must exist in %s",org.getName());
378                         }
379                                 
380                         // Set Email from most current Sponsor
381                                 
382                         CSRMeta csrMeta;
383                         try {
384                                 csrMeta = BCFactory.createPersonalCSRMeta(
385                                                 ca, 
386                                                 trans.user(), 
387                                                 ouser.email());
388                                 X509andChain x509ac = ca.sign(trans, csrMeta);
389                                 if(x509ac==null) {
390                                         return Result.err(Result.ERR_ActionNotCompleted,"x509 Certificate not signed by CA");
391                                 }
392                                 X509Certificate x509 = x509ac.getX509();
393                                 CertDAO.Data cdd = new CertDAO.Data();
394                                 cdd.ca=ca.getName();
395                                 cdd.serial=x509.getSerialNumber();
396                                 cdd.id=trans.user();
397                                 cdd.x500=x509.getSubjectDN().getName();
398                                 cdd.x509=Factory.toString(trans, x509);
399                                 certDAO.create(trans, cdd);
400                                 
401                                 CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(null));
402                                 return Result.ok(cr);
403                         } catch (Exception e) {
404                                 trans.error().log(e);
405                                 return Result.err(Result.ERR_ActionNotCompleted,e.getMessage());
406                         }
407                 } else {
408                         return Result.err(Result.ERR_Denied,trans.user()," not supported for CA",ca.getName());
409                 }
410         }
411
412         ///////////////
413         // Artifact
414         //////////////
415         public Result<Void> createArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) {
416                 CertmanValidator v = new CertmanValidator().artisRequired(list, 1);
417                 if(v.err()) {
418                         return Result.err(Result.ERR_BadData,v.errs());
419                 }
420                 for(ArtiDAO.Data add : list) {
421                         try {
422                                 // Policy 1: MechID must exist in Org
423                                 Identity muser = trans.org().getIdentity(trans, add.mechid);
424                                 if(muser == null) {
425                                         return Result.err(Result.ERR_Denied,"%s is not valid for %s", add.mechid,trans.org().getName());
426                                 }
427                                 
428                                 // Policy 2: MechID must have valid Organization Owner
429                                 Identity emailUser;
430                                 if(muser.isPerson()) {
431                                         emailUser = muser;
432                                 } else {
433                                         Identity ouser = muser.responsibleTo();
434                                         if(ouser == null) {
435                                                 return Result.err(Result.ERR_Denied,"%s is not a valid Sponsor for %s at %s",
436                                                                 trans.user(),add.mechid,trans.org().getName());
437                                         }
438
439                                         // Policy 3: Calling ID must be MechID Owner
440                                         if(!trans.user().equals(ouser.fullID())) {
441                                                 return Result.err(Result.ERR_Denied,"%s is not the Sponsor for %s at %s",
442                                                                 trans.user(),add.mechid,trans.org().getName());
443                                         }
444                                         emailUser = ouser;
445                                 }
446                                 
447
448                                 // Policy 4: Renewal Days are between 10 and 60 (constants, may be parameterized)
449                                 if(add.renewDays<MIN_RENEWAL) {
450                                         add.renewDays = STD_RENEWAL;
451                                 } else if(add.renewDays>MAX_RENEWAL) {
452                                         add.renewDays = MAX_RENEWAL;
453                                 }
454                                 
455                                 // Policy 5: If Notify is blank, set to Owner's Email
456                                 if(add.notify==null || add.notify.length()==0) {
457                                         add.notify = "mailto:"+emailUser.email();
458                                 }
459                                 
460                                 // Policy 6: Only do Domain by Exception
461                                 if(add.machine.startsWith("*")) { // Domain set
462                                         CA ca = certman.getCA(add.ca);
463
464
465                                         if(!trans.fish(new AAFPermission(ca.getPermType(), add.ca, DOMAIN))) {
466                                                 return Result.err(Result.ERR_Denied,"Domain Artifacts (%s) requires specific Permission",
467                                                         add.machine);
468                                         }
469                                 }
470
471                                 // Set Sponsor from Golden Source
472                                 add.sponsor = emailUser.fullID();
473                                 
474                                 
475                         } catch (OrganizationException e) {
476                                 return Result.err(e);
477                         }
478                         // Add to DB
479                         Result<ArtiDAO.Data> rv = artiDAO.create(trans, add);
480                         // TODO come up with Partial Reporting Scheme, or allow only one at a time.
481                         if(rv.notOK()) {
482                                 return Result.err(rv);
483                         }
484                 }
485                 return Result.ok();
486         }
487
488         public Result<List<ArtiDAO.Data>> readArtifacts(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException {
489                 CertmanValidator v = new CertmanValidator().keys(add);
490                 if(v.err()) {
491                         return Result.err(Result.ERR_BadData,v.errs());
492                 }
493                 Result<List<ArtiDAO.Data>> data = artiDAO.read(trans, add);
494                 if(data.notOKorIsEmpty()) {
495                         return data;
496                 }
497                 add = data.value.get(0);
498                 if( trans.user().equals(add.mechid)
499                         || trans.fish(new AAFPermission(add.ns + ".access", "*", "read"))
500                         || trans.fish(new AAFPermission(add.ns+".certman",add.ca,"read"))
501                         || trans.fish(new AAFPermission(add.ns+".certman",add.ca,"request"))
502                         || (trans.org().validate(trans,Organization.Policy.OWNS_MECHID,null,add.mechid))==null) {
503                         return data;
504                 } else {
505                         return Result.err(Result.ERR_Denied,"%s is not %s, is not the sponsor, and doesn't have delegated permission.",trans.user(),add.mechid,add.ns+".certman|"+add.ca+"|read or ...|request"); // note: reason is set by 2nd case, if 1st case misses
506                 }
507
508         }
509
510         public Result<List<ArtiDAO.Data>> readArtifactsByMechID(AuthzTrans trans, String mechid) throws OrganizationException {
511                 CertmanValidator v = new CertmanValidator();
512                 v.nullOrBlank("mechid", mechid);
513                 if(v.err()) {
514                         return Result.err(Result.ERR_BadData,v.errs());
515                 }
516                 String ns = FQI.reverseDomain(mechid);
517                 
518                 String reason;
519                 if(trans.fish(new AAFPermission(ns + ".access", "*", "read"))
520                         || (reason=trans.org().validate(trans,Organization.Policy.OWNS_MECHID,null,mechid))==null) {
521                         return artiDAO.readByMechID(trans, mechid);
522                 } else {
523                         return Result.err(Result.ERR_Denied,reason); // note: reason is set by 2nd case, if 1st case misses
524                 }
525
526         }
527
528         public Result<List<ArtiDAO.Data>> readArtifactsByMachine(AuthzTrans trans, String machine) {
529                 CertmanValidator v = new CertmanValidator();
530                 v.nullOrBlank("machine", machine);
531                 if(v.err()) {
532                         return Result.err(Result.ERR_BadData,v.errs());
533                 }
534                 
535                 // TODO do some checks?
536
537                 Result<List<ArtiDAO.Data>> rv = artiDAO.readByMachine(trans, machine);
538                 return rv;
539         }
540
541         public Result<List<ArtiDAO.Data>> readArtifactsByNs(AuthzTrans trans, String ns) {
542                 CertmanValidator v = new CertmanValidator();
543                 v.nullOrBlank("ns", ns);
544                 if(v.err()) {
545                         return Result.err(Result.ERR_BadData,v.errs());
546                 }
547                 
548                 // TODO do some checks?
549
550                 Result<List<ArtiDAO.Data>> rv = artiDAO.readByNs(trans, ns);
551                 return rv;
552         }
553
554
555         public Result<Void> updateArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) throws OrganizationException {
556                 CertmanValidator v = new CertmanValidator();
557                 v.artisRequired(list, 1);
558                 if(v.err()) {
559                         return Result.err(Result.ERR_BadData,v.errs());
560                 }
561                 
562                 // Check if requesting User is Sponsor
563                 //TODO - Shall we do one, or multiples?
564                 for(ArtiDAO.Data add : list) {
565                         // Policy 1: MechID must exist in Org
566                         Identity muser = trans.org().getIdentity(trans, add.mechid);
567                         if(muser == null) {
568                                 return Result.err(Result.ERR_Denied,"%s is not valid for %s", add.mechid,trans.org().getName());
569                         }
570                         
571                         // Policy 2: MechID must have valid Organization Owner
572                         Identity ouser = muser.responsibleTo();
573                         if(ouser == null) {
574                                 return Result.err(Result.ERR_Denied,"%s is not a valid Sponsor for %s at %s",
575                                                 trans.user(),add.mechid,trans.org().getName());
576                         }
577
578                         // Policy 3: Renewal Days are between 10 and 60 (constants, may be parameterized)
579                         if(add.renewDays<MIN_RENEWAL) {
580                                 add.renewDays = STD_RENEWAL;
581                         } else if(add.renewDays>MAX_RENEWAL) {
582                                 add.renewDays = MAX_RENEWAL;
583                         }
584
585                         // Policy 4: Data is always updated with the latest Sponsor
586                         // Add to Sponsor, to make sure we are always up to date.
587                         add.sponsor = ouser.fullID();
588
589                         // Policy 5: If Notify is blank, set to Owner's Email
590                         if(add.notify==null || add.notify.length()==0) {
591                                 add.notify = "mailto:"+ouser.email();
592                         }
593                         // Policy 6: Only do Domain by Exception
594                         if(add.machine.startsWith("*")) { // Domain set
595                                 CA ca = certman.getCA(add.ca);
596                                 if(ca==null) {
597                                         return Result.err(Result.ERR_BadData, "CA is required in Artifact");
598                                 }
599                                 if(!trans.fish(new AAFPermission(ca.getPermType(), add.ca, DOMAIN))) {
600                                         return Result.err(Result.ERR_Denied,"Domain Artifacts (%s) requires specific Permission",
601                                                 add.machine);
602                                 }
603                         }
604
605                         // Policy 7: only Owner may update info
606                         if(trans.user().equals(add.sponsor)) {
607                                 return artiDAO.update(trans, add);
608                         } else {
609                                 return Result.err(Result.ERR_Denied,"%s may not update info for %s",trans.user(),muser.fullID());
610                         }
611                 }
612                 return Result.err(Result.ERR_BadData,"No Artifacts to update");
613         }
614         
615         public Result<Void> deleteArtifact(AuthzTrans trans, String mechid, String machine) throws OrganizationException {
616                 CertmanValidator v = new CertmanValidator();
617                 v.nullOrBlank("mechid", mechid)
618                  .nullOrBlank("machine", machine);
619                 if(v.err()) {
620                         return Result.err(Result.ERR_BadData,v.errs());
621                 }
622
623                 Result<List<ArtiDAO.Data>> rlad = artiDAO.read(trans, mechid, machine);
624                 if(rlad.notOKorIsEmpty()) {
625                         return Result.err(Result.ERR_NotFound,"Artifact for %s %s does not exist.",mechid,machine);
626                 }
627                 
628                 return deleteArtifact(trans,rlad.value.get(0));
629         }
630                 
631         private Result<Void> deleteArtifact(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException {
632                 // Policy 1: Record should be delete able only by Existing Sponsor.  
633                 String sponsor=null;
634                 Identity muser = trans.org().getIdentity(trans, add.mechid);
635                 if(muser != null) {
636                         Identity ouser = muser.responsibleTo();
637                         if(ouser!=null) {
638                                 sponsor = ouser.fullID();
639                         }
640                 }
641                 // Policy 1.a: If Sponsorship is deleted in system of Record, then 
642                 // accept deletion by sponsor in Artifact Table
643                 if(sponsor==null) {
644                         sponsor = add.sponsor;
645                 }
646                 
647                 String ns = FQI.reverseDomain(add.mechid);
648
649                 if(trans.fish(new AAFPermission(ns + ".access", "*", "write"))
650                                 || trans.user().equals(sponsor)) {
651                         return artiDAO.delete(trans, add, false);
652                 }
653                 return Result.err(Result.ERR_Denied, "%1 is not allowed to delete this item",trans.user());
654         }
655
656         public Result<Void> deleteArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) {
657                 CertmanValidator v = new CertmanValidator().artisRequired(list, 1);
658                 if(v.err()) {
659                         return Result.err(Result.ERR_BadData,v.errs());
660                 }
661
662                 try {
663                         boolean partial = false;
664                         Result<Void> result=null;
665                         for(ArtiDAO.Data add : list) {
666                                 result = deleteArtifact(trans, add);
667                                 if(result.notOK()) {
668                                         partial = true;
669                                 }
670                         }
671                         if(result == null) {
672                                 result = Result.err(Result.ERR_BadData,"No Artifacts to delete"); 
673                         } else if(partial) {
674                                 result.partialContent(true);
675                         }
676                         return result;
677                 } catch(Exception e) {
678                         return Result.err(e);
679                 }
680         }
681
682         private String[] compileNotes(List<String> notes) {
683                 String[] rv;
684                 if(notes==null) {
685                         rv = NO_NOTES;
686                 } else {
687                         rv = new String[notes.size()];
688                         notes.toArray(rv);
689                 }
690                 return rv;
691         }
692
693         private ByteBuffer getChallenge256SaltedHash(String challenge, int salt) throws NoSuchAlgorithmException {
694                 ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + challenge.length());
695                 bb.putInt(salt);
696                 bb.put(challenge.getBytes());
697                 byte[] hash = Hash.hashSHA256(bb.array());
698                 return ByteBuffer.wrap(hash);
699         }
700 }