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