2 * ============LICENSE_START====================================================
4 * ===========================================================================
5 * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
7 * Modifications Copyright (C) 2019 IBM.
8 * ===========================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END====================================================
24 package org.onap.aaf.auth.batch.reports;
27 import java.io.FileNotFoundException;
28 import java.io.IOException;
29 import java.security.cert.Certificate;
30 import java.security.cert.CertificateException;
31 import java.security.cert.X509Certificate;
32 import java.util.Date;
33 import java.util.GregorianCalendar;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
38 import java.util.Map.Entry;
40 import java.util.TreeMap;
41 import java.util.TreeSet;
42 import java.util.UUID;
44 import org.onap.aaf.auth.batch.Batch;
45 import org.onap.aaf.auth.batch.approvalsets.Pending;
46 import org.onap.aaf.auth.batch.approvalsets.Ticket;
47 import org.onap.aaf.auth.batch.helpers.Approval;
48 import org.onap.aaf.auth.batch.helpers.Cred;
49 import org.onap.aaf.auth.batch.helpers.Cred.Instance;
50 import org.onap.aaf.auth.batch.helpers.ExpireRange;
51 import org.onap.aaf.auth.batch.helpers.ExpireRange.Range;
52 import org.onap.aaf.auth.batch.helpers.Future;
53 import org.onap.aaf.auth.batch.helpers.LastNotified;
54 import org.onap.aaf.auth.batch.helpers.Role;
55 import org.onap.aaf.auth.batch.helpers.UserRole;
56 import org.onap.aaf.auth.batch.helpers.X509;
57 import org.onap.aaf.auth.dao.cass.CredDAO;
58 import org.onap.aaf.auth.dao.cass.UserRoleDAO;
59 import org.onap.aaf.auth.env.AuthzTrans;
60 import org.onap.aaf.auth.org.Organization.Identity;
61 import org.onap.aaf.auth.org.OrganizationException;
62 import org.onap.aaf.cadi.configure.Factory;
63 import org.onap.aaf.cadi.util.CSV;
64 import org.onap.aaf.misc.env.APIException;
65 import org.onap.aaf.misc.env.Env;
66 import org.onap.aaf.misc.env.TimeTaken;
67 import org.onap.aaf.misc.env.Trans;
68 import org.onap.aaf.misc.env.util.Chrono;
71 public class Analyze extends Batch {
72 private static final int unknown=0;
73 private static final int owner=1;
74 private static final int supervisor=2;
75 private static final int total=0;
76 private static final int pending=1;
77 private static final int approved=2;
80 public static final String NEED_APPROVALS = "NeedApprovals";
81 private static final String EXTEND = "Extend";
82 private static final String EXPIRED_OWNERS = "ExpiredOwners";
83 private static final String CSV = ".csv";
84 private static final String INFO = "info";
85 private int minOwners;
86 private Map<String, CSV.Writer> writerList;
87 private ExpireRange expireRange;
88 private Date deleteDate;
89 private CSV.Writer deleteCW;
90 private CSV.Writer needApproveCW;
91 private CSV.Writer extendCW;
92 private Range futureRange;
93 private final String sdate;
94 private LastNotified ln;
96 public Analyze(AuthzTrans trans) throws APIException, IOException, OrganizationException {
98 trans.info().log("Starting Connection Process");
100 TimeTaken tt0 = trans.start("Cassandra Initialization", Env.SUB);
102 TimeTaken tt = trans.start("Connect to Cluster", Env.REMOTE);
104 session = cluster.connect();
112 // Create Intermediate Output
113 writerList = new HashMap<>();
115 expireRange = new ExpireRange(trans.env().access());
116 sdate = Chrono.dateOnlyStamp(now);
117 for( List<Range> lr : expireRange.ranges.values()) {
119 if(writerList.get(r.name())==null) {
120 File file = new File(logDir(),r.name() + sdate +CSV);
121 CSV csv = new CSV(env.access(),file);
122 CSV.Writer cw = csv.writer(false);
123 cw.row(INFO,r.name(),sdate,r.reportingLevel());
124 writerList.put(r.name(),cw);
125 if("Delete".equals(r.name())) {
126 deleteDate = r.getEnd();
129 trans.init().log("Creating File:",file.getAbsolutePath());
134 // Setup New Approvals file
135 futureRange = expireRange.newFutureRange();
136 File file = new File(logDir(),NEED_APPROVALS + sdate +CSV);
137 CSV approveCSV = new CSV(env.access(),file);
138 needApproveCW = approveCSV.writer();
139 needApproveCW.row(INFO,NEED_APPROVALS,sdate,1);
140 writerList.put(NEED_APPROVALS,needApproveCW);
142 // Setup Extend Approvals file
143 file = new File(logDir(),EXTEND + sdate +CSV);
144 CSV extendCSV = new CSV(env.access(),file);
145 extendCW = extendCSV.writer();
146 extendCW.row(INFO,EXTEND,sdate,1);
147 writerList.put(EXTEND,extendCW);
149 // Load full data of the following
150 ln = new LastNotified(session);
158 protected void run(AuthzTrans trans) {
160 AuthzTrans noAvg = trans.env().newTransNoAvg();
163 // Load all Notifieds, and either add to local Data, or mark for Deletion.
164 ln.loadAll(noAvg,expireRange.approveDelete,deleteCW);
166 // Hold Good Tickets to keyed User/Role for UserRole Step
167 Map<String,Ticket> mur = new TreeMap<>();
170 Approval.load(trans, session, Approval.v2_0_17);
173 final Map<UUID,Ticket> goodTickets = new TreeMap<>();
174 tt = trans.start("Analyze Expired Futures",Trans.SUB);
176 Future.load(noAvg, session, Future.withConstruct, fut -> {
177 List<Approval> appls = Approval.byTicket.get(fut.id());
178 if(!futureRange.inRange(fut.expires())) {
179 deleteCW.comment("Future %s expired", fut.id());
180 Future.row(deleteCW,fut);
182 for(Approval a : appls) {
183 Approval.row(deleteCW, a);
186 } else if(appls==null) { // Orphaned Future (no Approvals)
187 deleteCW.comment("Future is Orphaned");
188 Future.row(deleteCW,fut);
190 goodTickets.put(fut.fdd.id, new Ticket(fut));
197 Set<String> approvers = new TreeSet<>();
198 tt = trans.start("Connect Approvals with Futures",Trans.SUB);
200 for(Approval appr : Approval.list) {
202 UUID ticketID = appr.getTicket();
204 ticket = goodTickets.get(appr.getTicket());
206 if(ticket == null) { // Orphaned Approvals, no Futures
207 deleteCW.comment("Approval is Orphaned");
208 Approval.row(deleteCW, appr);
210 ticket.approvals.add(appr); // add to found Ticket
211 approvers.add(appr.getApprover());
218 /* Run through all Futures, and see if
219 * 1) they have been executed (no longer valid)
220 * 2) The current Approvals indicate they can proceed
222 Map<String,Pending> pendingApprs = new HashMap<>();
223 Map<String,Pending> pendingTemp = new HashMap<>();
227 tt = trans.start("Analyze Good Tickets",Trans.SUB);
229 for(Ticket ticket : goodTickets.values()) {
232 switch(ticket.f.target()) {
234 int state[][] = new int[3][3];
237 for(Approval appr : ticket.approvals) {
238 switch(appr.getType()) {
248 ++state[type][total]; // count per type
249 switch(appr.getStatus()) {
251 ++state[type][pending];
252 approver = appr.getApprover();
253 Pending n = pendingTemp.get(approver);
255 Date lastNotified = ln.lastNotified(approver,"ur",ticket.f.fdd.target_key);
256 pendingTemp.put(approver,new Pending(lastNotified));
262 ++state[type][approved];
265 ++state[type][unknown];
270 // Always must have at least 1 owner
271 if((state[owner][total]>0 && state[owner][approved]>0) &&
272 // If there are no Supervisors, that's ok
273 (state[supervisor][total]==0 ||
274 // But if there is a Supervisor, they must have approved
275 (state[supervisor][approved]>0))) {
276 UserRoleDAO.Data urdd = new UserRoleDAO.Data();
278 urdd.reconstitute(ticket.f.fdd.construct);
279 if(urdd.expires.before(ticket.f.expires())) {
280 extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
282 } catch (IOException e) {
283 trans.error().log("Could not reconstitute UserRole");
285 } else { // Load all the Pending.
286 for(Entry<String, Pending> es : pendingTemp.entrySet()) {
287 Pending p = pendingApprs.get(es.getKey());
289 pendingApprs.put(es.getKey(), es.getValue());
291 p.inc(es.getValue());
298 if("user_role".equals(ticket.f.fdd.target)) {
299 String key = ticket.f.fdd.target_key;
301 mur.put(key, ticket);
309 // Good Tickets no longer needed
313 * Decide to Notify about Approvals, based on activity/last Notified
315 tt = trans.start("Analyze Approval Reminders", Trans.SUB);
317 GregorianCalendar gc = new GregorianCalendar();
318 gc.add(GregorianCalendar.DAY_OF_WEEK, 5);
319 Date remind = gc.getTime();
321 for(Entry<String, Pending> es : pendingApprs.entrySet()) {
322 Pending p = es.getValue();
324 || p.earliest() == null
325 || p.earliest().after(remind)) {
326 p.row(needApproveCW,es.getKey());
333 // clear out Approval Intermediates
341 Run through User Roles.
342 Owners are treated specially in next section.
343 Regular roles are checked against Date Ranges. If match Date Range, write out to appropriate file.
347 Role.load(trans, session);
350 tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
351 Set<String> specialCommented = new HashSet<>();
352 Map<String, Set<UserRole>> owners = new TreeMap<>();
354 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
357 identity = trans.org().getIdentity(noAvg,ur.user());
359 // Candidate for Delete, but not Users if Special
360 String id = ur.user();
361 for(String s : specialDomains) {
363 if(!specialCommented.contains(id)) {
364 deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s);
365 specialCommented.add(id);
370 if(specialNames.contains(id)) {
371 if(!specialCommented.contains(id)) {
372 deleteCW.comment("ID %s is a special ID (UR Org Check)", id);
373 specialCommented.add(id);
377 ur.row(deleteCW, UserRole.UR,"Not in Organization");
379 } else if(Role.byName.get(ur.role())==null) {
380 ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
383 // Just let expired UserRoles sit until deleted
384 if(futureRange.inRange(ur.expires())) {
385 if(!mur.containsKey(ur.user() + '|' + ur.role())) {
386 // Cannot just delete owners, unless there is at least one left. Process later
387 if ("owner".equals(ur.rname())) {
388 Set<UserRole> urs = owners.get(ur.role());
390 urs = new HashSet<UserRole>();
391 owners.put(ur.role(), urs);
395 Range r = writeAnalysis(noAvg,ur);
397 Approval existing = findApproval(ur);
399 ur.row(needApproveCW,UserRole.APPROVE_UR);
405 } catch (OrganizationException e) {
406 noAvg.error().log(e);
415 Now Process Owners, one owner Role at a time, ensuring one is left,
416 preferably a good one. If so, process the others as normal.
418 Otherwise, write to ExpiredOwners Report
420 tt = trans.start("Analyze Owners Separately",Trans.SUB);
422 if (!owners.values().isEmpty()) {
423 File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV);
424 final CSV ownerCSV = new CSV(env.access(),file);
425 CSV.Writer expOwner = ownerCSV.writer();
426 expOwner.row(INFO,EXPIRED_OWNERS,sdate,2);
429 for (Set<UserRole> sur : owners.values()) {
431 for (UserRole ur : sur) {
432 if (ur.expires().after(now)) {
437 for (UserRole ur : sur) {
438 if (goodOwners >= minOwners) {
439 Range r = writeAnalysis(noAvg, ur);
441 Approval existing = findApproval(ur);
443 ur.row(needApproveCW,UserRole.APPROVE_UR);
447 expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
448 Approval existing = findApproval(ur);
450 ur.row(needApproveCW,UserRole.APPROVE_UR);
470 * Check for Expired Credentials
473 // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway
474 Cred.load(trans, session);
476 tt = trans.start("Analyze Expired Credentials",Trans.SUB);
478 for (Cred cred : Cred.data.values()) {
479 List<Instance> linst = cred.instances;
481 Instance lastBath = null;
482 for(Instance inst : linst) {
484 // writeAnalysis(trans, cred, inst);
485 // // Special Behavior: only eval the LAST Instance
487 // All Creds go through Life Cycle
488 if(deleteDate!=null && inst.expires.before(deleteDate)) {
489 writeAnalysis(noAvg, cred, inst); // will go to Delete
490 // Basic Auth has Pre-EOL notifications IF there is no Newer Credential
491 } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) {
492 if(lastBath==null || lastBath.expires.before(inst.expires)) {
498 writeAnalysis(noAvg, cred, lastBath);
510 tt = trans.start("Analyze Expired X509s",Trans.SUB);
512 X509.load(noAvg, session, x509 -> {
514 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
515 writeAnalysis(noAvg, x509, (X509Certificate)cert);
517 } catch (CertificateException | IOException e) {
518 noAvg.error().log(e, "Error Decrypting X509");
524 } catch (FileNotFoundException e) {
529 private Approval findApproval(UserRole ur) {
530 Approval existing = null;
531 List<Approval> apprs = Approval.byUser.get(ur.user());
533 for(Approval appr : apprs) {
534 if(ur.role().equals(appr.getRole()) &&
535 appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
543 private Range writeAnalysis(AuthzTrans trans, UserRole ur) {
544 Range r = expireRange.getRange("ur", ur.expires());
546 Date lnd = ln.lastNotified(LastNotified.newKey(ur));
547 // Note: lnd is NEVER null
550 i = org.getIdentity(trans, ur.user());
551 } catch (OrganizationException e) {
554 if(r.needsContact(lnd,i)) {
555 CSV.Writer cw = writerList.get(r.name());
557 ur.row(cw,UserRole.UR);
564 private void writeAnalysis(AuthzTrans trans, Cred cred, Instance inst) {
565 if(cred!=null && inst!=null) {
566 Range r = expireRange.getRange("cred", inst.expires);
568 Date lnd = ln.lastNotified(LastNotified.newKey(cred,inst));
569 // Note: lnd is NEVER null
572 i = org.getIdentity(trans, cred.id);
573 } catch (OrganizationException e) {
576 if(r.needsContact(lnd,i)) {
577 CSV.Writer cw = writerList.get(r.name());
586 private void writeAnalysis(AuthzTrans trans, X509 x509, X509Certificate x509Cert) throws IOException {
587 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
589 Date lnd = ln.lastNotified(LastNotified.newKey(x509,x509Cert));
590 // Note: lnd is NEVER null
593 i = org.getIdentity(trans, x509.id);
594 } catch (OrganizationException e) {
597 if(r.needsContact(lnd,i)) {
598 CSV.Writer cw = writerList.get(r.name());
600 x509.row(cw,x509Cert);
607 protected void _close(AuthzTrans trans) {
609 for(CSV.Writer cw : writerList.values()) {