Merge "System dependent separators"
[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                                                                 potentialSanNames.add(cn);
169                                                         } else {
170                                                                 try {
171                                                                         InetAddress[] ias = InetAddress.getAllByName(cn);
172                                                                         Set<String> potentialSanNames = new HashSet<>();
173                                                                         for (InetAddress ia1 : ias) {
174                                                                                 InetAddress ia2 = InetAddress.getByAddress(ia1.getAddress());
175                                                                                 if (primary == null && ias.length == 1 && trans.ip().equals(ia1.getHostAddress())) {
176                                                                                         primary = ia1;
177                                                                                 } else if (!cn.equals(ia1.getHostName())
178                                                                                                 && !ia2.getHostName().equals(ia2.getHostAddress())) {
179                                                                                         potentialSanNames.add(ia1.getHostName());
180                                                                                 }
181                                                                         }
182                                                                 } catch (UnknownHostException 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                                         host = primary.getHostAddress();
197                                 }
198
199                                 ArtiDAO.Data add = null;
200                                 Result<List<ArtiDAO.Data>> ra = artiDAO.read(trans, req.value.mechid, host);
201                                 if (ra.isOKhasData()) {
202                                         if (add == null) {
203                                                 add = ra.value.get(0); // single key
204                                         }
205                                 } else {
206                                         ra = artiDAO.read(trans, req.value.mechid, key);
207                                         if (ra.isOKhasData()) { // is the Template available?
208                                                 add = ra.value.get(0);
209                                                 add.machine = host;
210                                                 for (String s : fqdns) {
211                                                         if (!s.equals(add.machine)) {
212                                                                 add.sans(true).add(s);
213                                                         }
214                                                 }
215                                                 Result<ArtiDAO.Data> rc = artiDAO.create(trans, add); // Create new Artifact from Template
216                                                 if (rc.notOK()) {
217                                                         return Result.err(rc);
218                                                 }
219                                         } else {
220                                                 add = ra.value.get(0);
221                                         }
222                                 }
223
224                                 // Add Artifact listed FQDNs
225                                 if (add.sans != null) {
226                                         for (String s : add.sans) {
227                                                 if (!fqdns.contains(s)) {
228                                                         fqdns.add(s);
229                                                 }
230                                         }
231                                 }
232
233                                 // Policy 2: If Config marked as Expired, do not create or renew
234                                 Date now = new Date();
235                                 if (add.expires != null && now.after(add.expires)) {
236                                         return Result.err(Result.ERR_Policy, "Configuration for %s %s is expired %s", add.mechid,
237                                                         add.machine, Chrono.dateFmt.format(add.expires));
238                                 }
239
240                                 // Policy 3: MechID must be current
241                                 Identity muser = org.getIdentity(trans, add.mechid);
242                                 if (muser == null) {
243                                         return Result.err(Result.ERR_Policy, "MechID must exist in %s", org.getName());
244                                 }
245
246                                 // Policy 4: Sponsor must be current
247                                 Identity ouser = muser.responsibleTo();
248                                 if (ouser == null) {
249                                         return Result.err(Result.ERR_Policy, "%s does not have a current sponsor at %s", add.mechid,
250                                                         org.getName());
251                                 } else if (!ouser.isFound() || ouser.mayOwn() != null) {
252                                         return Result.err(Result.ERR_Policy, "%s reports that %s cannot be responsible for %s",
253                                                         org.getName(), trans.user());
254                                 }
255
256                                 // Set Email from most current Sponsor
257                                 email = ouser.email();
258
259                                 // Policy 5: keep Artifact data current
260                                 if (!ouser.fullID().equals(add.sponsor)) {
261                                         add.sponsor = ouser.fullID();
262                                         artiDAO.update(trans, add);
263                                 }
264
265                                 // Policy 7: Caller must be the MechID or have specifically delegated
266                                 // permissions
267                                 if (!(trans.user().equals(req.value.mechid)
268                                                 || trans.fish(new AAFPermission(mechNS,CERTMAN, ca.getName(), REQUEST)))) {
269                                         return Result.err(Status.ERR_Denied, "%s must have access to modify x509 certs in NS %s",
270                                                         trans.user(), mechNS);
271                                 }
272
273                                 // Make sure Primary is the first in fqdns
274                                 if (fqdns.size() > 1) {
275                                         for (int i = 0; i < fqdns.size(); ++i) {
276                                                 if(primary==null) {
277                                                         trans.error().log("CMService var primary is null");
278                                                 } else {
279                                                         String fg = fqdns.get(i);
280                                                         if (fg!=null && fg.equals(primary.getHostName())) {
281                                                                 if (i != 0) {
282                                                                         String tmp = fqdns.get(0);
283                                                                         fqdns.set(0, primary.getHostName());
284                                                                         fqdns.set(i, tmp);
285                                                                 }
286                                                         }
287                                                 }
288                                         }
289                                 }
290                         } catch (Exception e) {
291                                 e.printStackTrace();
292                                 trans.error().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                                 X509andChain x509ac = ca.sign(trans, csrMeta);
301                                 if (x509ac == null) {
302                                         return Result.err(Result.ERR_ActionNotCompleted, "x509 Certificate not signed by CA");
303                                 }
304                                 trans.info().printf("X509 Subject: %s", x509ac.getX509().getSubjectDN());
305
306                                 X509Certificate x509 = x509ac.getX509();
307                                 CertDAO.Data cdd = new CertDAO.Data();
308                                 cdd.ca = ca.getName();
309                                 cdd.serial = x509.getSerialNumber();
310                                 cdd.id = req.value.mechid;
311                                 cdd.x500 = x509.getSubjectDN().getName();
312                                 cdd.x509 = Factory.toString(trans, x509);
313                                 certDAO.create(trans, cdd);
314
315                                 CredDAO.Data crdd = new CredDAO.Data();
316                                 crdd.other = Question.random.nextInt();
317                                 crdd.cred = getChallenge256SaltedHash(csrMeta.challenge(), crdd.other);
318                                 crdd.expires = x509.getNotAfter();
319                                 crdd.id = req.value.mechid;
320                                 crdd.ns = Question.domain2ns(crdd.id);
321                                 crdd.type = CredDAO.CERT_SHA256_RSA;
322                                 credDAO.create(trans, crdd);
323
324                                 CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(notes));
325                                 return Result.ok(cr);
326                         } catch (Exception e) {
327                                 trans.error().log(e);
328                                 return Result.err(Result.ERR_ActionNotCompleted, e.getMessage());
329                         }
330                 } else {
331                         return Result.err(req);
332                 }
333         }
334
335         public Result<CertResp> renewCert(AuthzTrans trans, Result<CertRenew> renew) {
336                 if (renew.isOK()) {
337                         return Result.err(Result.ERR_NotImplemented, "Not implemented yet");
338                 } else {
339                         return Result.err(renew);
340                 }
341         }
342
343         public Result<Void> dropCert(AuthzTrans trans, Result<CertDrop> drop) {
344                 if (drop.isOK()) {
345                         return Result.err(Result.ERR_NotImplemented, "Not implemented yet");
346                 } else {
347                         return Result.err(drop);
348                 }
349         }
350
351         public Result<List<Data>> readCertsByMechID(AuthzTrans trans, String mechID) {
352                 // Policy 1: To Read, must have NS Read or is Sponsor
353                 String ns = Question.domain2ns(mechID);
354                 try {
355                         if (trans.user().equals(mechID) || trans.fish(new AAFPermission(ns,ACCESS, "*", "read"))
356                                         || (trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, mechID)) == null) {
357                                 return certDAO.readID(trans, mechID);
358                         } else {
359                                 return Result.err(Result.ERR_Denied, "%s is not the ID, Sponsor or NS Owner/Admin for %s at %s",
360                                                 trans.user(), mechID, trans.org().getName());
361                         }
362                 } catch (OrganizationException e) {
363                         return Result.err(e);
364                 }
365         }
366
367         public Result<CertResp> requestPersonalCert(AuthzTrans trans, CA ca) {
368                 if (ca.inPersonalDomains(trans.getUserPrincipal())) {
369                         Organization org = trans.org();
370
371                         // Policy 1: MechID must be current
372                         Identity ouser;
373                         try {
374                                 ouser = org.getIdentity(trans, trans.user());
375                         } catch (OrganizationException e1) {
376                                 trans.error().log(e1);
377                                 ouser = null;
378                         }
379                         if (ouser == null) {
380                                 return Result.err(Result.ERR_Policy, "Requesting User must exist in %s", org.getName());
381                         }
382
383                         // Set Email from most current Sponsor
384
385                         CSRMeta csrMeta;
386                         try {
387                                 csrMeta = BCFactory.createPersonalCSRMeta(ca, trans.user(), ouser.email());
388                                 X509andChain x509ac = ca.sign(trans, csrMeta);
389                                 if (x509ac == null) {
390                                         return Result.err(Result.ERR_ActionNotCompleted, "x509 Certificate not signed by CA");
391                                 }
392                                 X509Certificate x509 = x509ac.getX509();
393                                 CertDAO.Data cdd = new CertDAO.Data();
394                                 cdd.ca = ca.getName();
395                                 cdd.serial = x509.getSerialNumber();
396                                 cdd.id = trans.user();
397                                 cdd.x500 = x509.getSubjectDN().getName();
398                                 cdd.x509 = Factory.toString(trans, x509);
399                                 certDAO.create(trans, cdd);
400
401                                 CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(null));
402                                 return Result.ok(cr);
403                         } catch (Exception e) {
404                                 trans.error().log(e);
405                                 return Result.err(Result.ERR_ActionNotCompleted, e.getMessage());
406                         }
407                 } else {
408                         return Result.err(Result.ERR_Denied, trans.user(), " not supported for CA", ca.getName());
409                 }
410         }
411
412         ///////////////
413         // Artifact
414         //////////////
415         public Result<Void> createArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) {
416                 CertmanValidator v = new CertmanValidator().artisRequired(list, 1);
417                 if (v.err()) {
418                         return Result.err(Result.ERR_BadData, v.errs());
419                 }
420                 for (ArtiDAO.Data add : list) {
421                         try {
422                                 // Policy 1: MechID must exist in Org
423                                 Identity muser = trans.org().getIdentity(trans, add.mechid);
424                                 if (muser == null) {
425                                         return Result.err(Result.ERR_Denied, "%s is not valid for %s", add.mechid, trans.org().getName());
426                                 }
427
428                                 // Policy 2: MechID must have valid Organization Owner
429                                 Identity emailUser;
430                                 if (muser.isPerson()) {
431                                         emailUser = muser;
432                                 } else {
433                                         Identity ouser = muser.responsibleTo();
434                                         if (ouser == null) {
435                                                 return Result.err(Result.ERR_Denied, "%s is not a valid Sponsor for %s at %s", trans.user(),
436                                                                 add.mechid, trans.org().getName());
437                                         }
438
439                                         // Policy 3: Calling ID must be MechID Owner
440                                         if (!trans.user().startsWith(ouser.id())) {
441                                                 return Result.err(Result.ERR_Denied, "%s is not the Sponsor for %s at %s", trans.user(),
442                                                                 add.mechid, trans.org().getName());
443                                         }
444                                         emailUser = ouser;
445                                 }
446
447                                 // Policy 4: Renewal Days are between 10 and 60 (constants, may be
448                                 // parameterized)
449                                 if (add.renewDays < MIN_RENEWAL) {
450                                         add.renewDays = STD_RENEWAL;
451                                 } else if (add.renewDays > MAX_RENEWAL) {
452                                         add.renewDays = MAX_RENEWAL;
453                                 }
454
455                                 // Policy 5: If Notify is blank, set to Owner's Email
456                                 if (add.notify == null || add.notify.length() == 0) {
457                                         add.notify = "mailto:" + emailUser.email();
458                                 }
459
460                                 // Policy 6: Only do Domain by Exception
461                                 if (add.machine.startsWith("*")) { // Domain set
462                                         CA ca = certman.getCA(add.ca);
463
464                                         if (!trans.fish(new AAFPermission(ca.getPermNS(),ca.getPermType(), add.ca, DOMAIN))) {
465                                                 return Result.err(Result.ERR_Denied, "Domain Artifacts (%s) requires specific Permission",
466                                                                 add.machine);
467                                         }
468                                 }
469
470                                 // Set Sponsor from Golden Source
471                                 add.sponsor = emailUser.fullID();
472
473                         } catch (OrganizationException e) {
474                                 return Result.err(e);
475                         }
476                         // Add to DB
477                         Result<ArtiDAO.Data> rv = artiDAO.create(trans, add);
478                         // TODO come up with Partial Reporting Scheme, or allow only one at a time.
479                         if (rv.notOK()) {
480                                 return Result.err(rv);
481                         }
482                 }
483                 return Result.ok();
484         }
485
486         public Result<List<ArtiDAO.Data>> readArtifacts(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException {
487                 CertmanValidator v = new CertmanValidator().keys(add);
488                 if (v.err()) {
489                         return Result.err(Result.ERR_BadData, v.errs());
490                 }
491                 Result<List<ArtiDAO.Data>> data = artiDAO.read(trans, add);
492                 if (data.notOKorIsEmpty()) {
493                         return data;
494                 }
495                 add = data.value.get(0);
496                 if (trans.user().equals(add.mechid) 
497                                 || trans.fish(root_read_permission,
498                                                           new AAFPermission(add.ns,ACCESS, "*", "read"),
499                                                       new AAFPermission(add.ns,CERTMAN, add.ca, "read"),
500                                                       new AAFPermission(add.ns,CERTMAN, add.ca, "request"))
501                                 || (trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, add.mechid)) == null) {
502                         return data;
503                 } else {
504                         return Result.err(Result.ERR_Denied,
505                                         "%s is not %s, is not the sponsor, and doesn't have delegated permission.", trans.user(),
506                                         add.mechid, add.ns + ".certman|" + add.ca + "|read or ...|request"); // note: reason is set by 2nd
507                                                                                                                                                                                         // case, if 1st case misses
508                 }
509
510         }
511
512         public Result<List<ArtiDAO.Data>> readArtifactsByMechID(AuthzTrans trans, String mechid)
513                         throws OrganizationException {
514                 CertmanValidator v = new CertmanValidator();
515                 v.nullOrBlank("mechid", mechid);
516                 if (v.err()) {
517                         return Result.err(Result.ERR_BadData, v.errs());
518                 }
519                 String ns = FQI.reverseDomain(mechid);
520
521                 String reason;
522                 if (trans.fish(new AAFPermission(ns, ACCESS, "*", "read"))
523                                 || (reason = trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, mechid)) == null) {
524                         return artiDAO.readByMechID(trans, mechid);
525                 } else {
526                         return Result.err(Result.ERR_Denied, reason); // note: reason is set by 2nd case, if 1st case misses
527                 }
528
529         }
530
531         public Result<List<ArtiDAO.Data>> readArtifactsByMachine(AuthzTrans trans, String machine) {
532                 CertmanValidator v = new CertmanValidator();
533                 v.nullOrBlank("machine", machine);
534                 if (v.err()) {
535                         return Result.err(Result.ERR_BadData, v.errs());
536                 }
537
538                 // TODO do some checks?
539
540                 Result<List<ArtiDAO.Data>> rv = artiDAO.readByMachine(trans, machine);
541                 return rv;
542         }
543
544         public Result<List<ArtiDAO.Data>> readArtifactsByNs(AuthzTrans trans, String ns) {
545                 CertmanValidator v = new CertmanValidator();
546                 v.nullOrBlank("ns", ns);
547                 if (v.err()) {
548                         return Result.err(Result.ERR_BadData, v.errs());
549                 }
550
551                 // TODO do some checks?
552
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                 // TODO - 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 = certman.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 }