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