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