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();
109 // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway
110 Cred.load(trans, session);
114 // Create Intermediate Output
115 writerList = new HashMap<>();
117 expireRange = new ExpireRange(trans.env().access());
118 sdate = Chrono.dateOnlyStamp(now);
119 for( List<Range> lr : expireRange.ranges.values()) {
121 if(writerList.get(r.name())==null) {
122 File file = new File(logDir(),r.name() + sdate +CSV);
123 CSV csv = new CSV(env.access(),file);
124 CSV.Writer cw = csv.writer(false);
125 cw.row(INFO,r.name(),sdate,r.reportingLevel());
126 writerList.put(r.name(),cw);
127 if("Delete".equals(r.name())) {
128 deleteDate = r.getEnd();
131 trans.init().log("Creating File:",file.getAbsolutePath());
136 // Setup New Approvals file
137 futureRange = expireRange.newFutureRange();
138 File file = new File(logDir(),NEED_APPROVALS + sdate +CSV);
139 CSV approveCSV = new CSV(env.access(),file);
140 needApproveCW = approveCSV.writer();
141 needApproveCW.row(INFO,NEED_APPROVALS,sdate,1);
142 writerList.put(NEED_APPROVALS,needApproveCW);
144 // Setup Extend Approvals file
145 file = new File(logDir(),EXTEND + sdate +CSV);
146 CSV extendCSV = new CSV(env.access(),file);
147 extendCW = extendCSV.writer();
148 extendCW.row(INFO,EXTEND,sdate,1);
149 writerList.put(EXTEND,extendCW);
151 // Load full data of the following
152 Approval.load(trans, session, Approval.v2_0_17);
153 Role.load(trans, session);
154 ln = new LastNotified(session);
162 protected void run(AuthzTrans trans) {
163 AuthzTrans noAvg = trans.env().newTransNoAvg();
166 // Load all Notifieds, and either add to local Data, or mark for Deletion.
167 ln.loadAll(noAvg,expireRange.approveDelete,deleteCW);
170 final Map<UUID,Ticket> goodTickets = new TreeMap<>();
171 TimeTaken tt = trans.start("Analyze Expired Futures",Trans.SUB);
173 Future.load(noAvg, session, Future.withConstruct, fut -> {
174 List<Approval> appls = Approval.byTicket.get(fut.id());
175 if(!futureRange.inRange(fut.expires())) {
176 deleteCW.comment("Future %s expired", fut.id());
177 Future.row(deleteCW,fut);
179 for(Approval a : appls) {
180 Approval.row(deleteCW, a);
183 } else if(appls==null) { // Orphaned Future (no Approvals)
184 deleteCW.comment("Future is Orphaned");
185 Future.row(deleteCW,fut);
187 goodTickets.put(fut.fdd.id, new Ticket(fut));
194 Set<String> approvers = new TreeSet<>();
195 tt = trans.start("Connect Approvals with Futures",Trans.SUB);
197 for(Approval appr : Approval.list) {
199 UUID ticketID = appr.getTicket();
201 ticket = goodTickets.get(appr.getTicket());
203 if(ticket == null) { // Orphaned Approvals, no Futures
204 deleteCW.comment("Approval is Orphaned");
205 Approval.row(deleteCW, appr);
207 ticket.approvals.add(appr); // add to found Ticket
208 approvers.add(appr.getApprover());
215 /* Run through all Futures, and see if
216 * 1) they have been executed (no longer valid)
217 * 2) The current Approvals indicate they can proceed
219 Map<String,Pending> pendingApprs = new HashMap<>();
220 Map<String,Pending> pendingTemp = new HashMap<>();
222 // Convert Good Tickets to keyed User/Role for UserRole Step
223 Map<String,Ticket> mur = new TreeMap<>();
226 tt = trans.start("Analyze Good Tickets",Trans.SUB);
228 for(Ticket ticket : goodTickets.values()) {
231 switch(ticket.f.target()) {
233 int state[][] = new int[3][3];
236 for(Approval appr : ticket.approvals) {
237 switch(appr.getType()) {
247 ++state[type][total]; // count per type
248 switch(appr.getStatus()) {
250 ++state[type][pending];
251 approver = appr.getApprover();
252 Pending n = pendingTemp.get(approver);
254 Date lastNotified = ln.lastNotified(approver,"ur",ticket.f.fdd.target_key);
255 pendingTemp.put(approver,new Pending(lastNotified));
261 ++state[type][approved];
264 ++state[type][unknown];
269 // Always must have at least 1 owner
270 if((state[owner][total]>0 && state[owner][approved]>0) &&
271 // If there are no Supervisors, that's ok
272 (state[supervisor][total]==0 ||
273 // But if there is a Supervisor, they must have approved
274 (state[supervisor][approved]>0))) {
275 UserRoleDAO.Data urdd = new UserRoleDAO.Data();
277 urdd.reconstitute(ticket.f.fdd.construct);
278 if(urdd.expires.before(ticket.f.expires())) {
279 extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
281 } catch (IOException e) {
282 trans.error().log("Could not reconstitute UserRole");
284 } else { // Load all the Pending.
285 for(Entry<String, Pending> es : pendingTemp.entrySet()) {
286 Pending p = pendingApprs.get(es.getKey());
288 pendingApprs.put(es.getKey(), es.getValue());
290 p.inc(es.getValue());
297 if("user_role".equals(ticket.f.fdd.target)) {
298 String key = ticket.f.fdd.target_key;
300 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
338 Run through User Roles.
339 Owners are treated specially in next section.
340 Regular roles are checked against Date Ranges. If match Date Range, write out to appropriate file.
343 tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
344 Set<String> specialCommented = new HashSet<>();
345 Map<String, Set<UserRole>> owners = new TreeMap<>();
347 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
350 identity = trans.org().getIdentity(noAvg,ur.user());
352 // Candidate for Delete, but not Users if Special
353 String id = ur.user();
354 for(String s : specialDomains) {
356 if(!specialCommented.contains(id)) {
357 deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s);
358 specialCommented.add(id);
363 if(specialNames.contains(id)) {
364 if(!specialCommented.contains(id)) {
365 deleteCW.comment("ID %s is a special ID (UR Org Check)", id);
366 specialCommented.add(id);
370 ur.row(deleteCW, UserRole.UR,"Not in Organization");
372 } else if(Role.byName.get(ur.role())==null) {
373 ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
376 // Just let expired UserRoles sit until deleted
377 if(futureRange.inRange(ur.expires())) {
378 if(!mur.containsKey(ur.user() + '|' + ur.role())) {
379 // Cannot just delete owners, unless there is at least one left. Process later
380 if ("owner".equals(ur.rname())) {
381 Set<UserRole> urs = owners.get(ur.role());
383 urs = new HashSet<UserRole>();
384 owners.put(ur.role(), urs);
388 Range r = writeAnalysis(noAvg,ur);
390 Approval existing = findApproval(ur);
392 ur.row(needApproveCW,UserRole.APPROVE_UR);
398 } catch (OrganizationException e) {
399 noAvg.error().log(e);
407 Now Process Owners, one owner Role at a time, ensuring one is left,
408 preferably a good one. If so, process the others as normal.
410 Otherwise, write to ExpiredOwners Report
412 tt = trans.start("Analyze Owners Separately",Trans.SUB);
414 if (!owners.values().isEmpty()) {
415 File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV);
416 final CSV ownerCSV = new CSV(env.access(),file);
417 CSV.Writer expOwner = ownerCSV.writer();
418 expOwner.row(INFO,EXPIRED_OWNERS,sdate,2);
421 for (Set<UserRole> sur : owners.values()) {
423 for (UserRole ur : sur) {
424 if (ur.expires().after(now)) {
429 for (UserRole ur : sur) {
430 if (goodOwners >= minOwners) {
431 Range r = writeAnalysis(noAvg, ur);
433 Approval existing = findApproval(ur);
435 ur.row(needApproveCW,UserRole.APPROVE_UR);
439 expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
440 Approval existing = findApproval(ur);
442 ur.row(needApproveCW,UserRole.APPROVE_UR);
458 * Check for Expired Credentials
462 tt = trans.start("Analyze Expired Credentials",Trans.SUB);
464 for (Cred cred : Cred.data.values()) {
465 List<Instance> linst = cred.instances;
467 Instance lastBath = null;
468 for(Instance inst : linst) {
470 // writeAnalysis(trans, cred, inst);
471 // // Special Behavior: only eval the LAST Instance
473 // All Creds go through Life Cycle
474 if(deleteDate!=null && inst.expires.before(deleteDate)) {
475 writeAnalysis(noAvg, cred, inst); // will go to Delete
476 // Basic Auth has Pre-EOL notifications IF there is no Newer Credential
477 } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) {
478 if(lastBath==null || lastBath.expires.before(inst.expires)) {
484 writeAnalysis(noAvg, cred, lastBath);
493 tt = trans.start("Analyze Expired X509s",Trans.SUB);
495 X509.load(noAvg, session, x509 -> {
497 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
498 writeAnalysis(noAvg, x509, (X509Certificate)cert);
500 } catch (CertificateException | IOException e) {
501 noAvg.error().log(e, "Error Decrypting X509");
507 } catch (FileNotFoundException e) {
512 private Approval findApproval(UserRole ur) {
513 Approval existing = null;
514 List<Approval> apprs = Approval.byUser.get(ur.user());
516 for(Approval appr : apprs) {
517 if(ur.role().equals(appr.getRole()) &&
518 appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
526 private Range writeAnalysis(AuthzTrans trans, UserRole ur) {
527 Range r = expireRange.getRange("ur", ur.expires());
529 Date lnd = ln.lastNotified(LastNotified.newKey(ur));
530 // Note: lnd is NEVER null
533 i = org.getIdentity(trans, ur.user());
534 } catch (OrganizationException e) {
537 if(r.needsContact(lnd,i)) {
538 CSV.Writer cw = writerList.get(r.name());
540 ur.row(cw,UserRole.UR);
547 private void writeAnalysis(AuthzTrans trans, Cred cred, Instance inst) {
548 if(cred!=null && inst!=null) {
549 Range r = expireRange.getRange("cred", inst.expires);
551 Date lnd = ln.lastNotified(LastNotified.newKey(cred,inst));
552 // Note: lnd is NEVER null
555 i = org.getIdentity(trans, cred.id);
556 } catch (OrganizationException e) {
559 if(r.needsContact(lnd,i)) {
560 CSV.Writer cw = writerList.get(r.name());
569 private void writeAnalysis(AuthzTrans trans, X509 x509, X509Certificate x509Cert) throws IOException {
570 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
572 Date lnd = ln.lastNotified(LastNotified.newKey(x509,x509Cert));
573 // Note: lnd is NEVER null
576 i = org.getIdentity(trans, x509.id);
577 } catch (OrganizationException e) {
580 if(r.needsContact(lnd,i)) {
581 CSV.Writer cw = writerList.get(r.name());
583 x509.row(cw,x509Cert);
590 protected void _close(AuthzTrans trans) {
592 for(CSV.Writer cw : writerList.values()) {