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