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 // for users and approvers still valid
211 String user = appr.getUser();
213 if(org.isRevoked(noAvg, appr.getApprover())) {
214 deleteCW.comment("Approver ID is revoked");
215 Approval.row(deleteCW, appr);
216 } else if(user!=null && !user.isEmpty() && org.isRevoked(noAvg, user)) {
217 deleteCW.comment("USER ID is revoked");
218 Approval.row(deleteCW, appr);
220 ticket.approvals.add(appr); // add to found Ticket
221 approvers.add(appr.getApprover());
229 /* Run through all Futures, and see if
230 * 1) they have been executed (no longer valid)
231 * 2) The current Approvals indicate they can proceed
233 Map<String,Pending> pendingApprs = new HashMap<>();
234 Map<String,Pending> pendingTemp = new HashMap<>();
238 tt = trans.start("Analyze Good Tickets",Trans.SUB);
240 for(Ticket ticket : goodTickets.values()) {
243 switch(ticket.f.target()) {
245 int state[][] = new int[3][3];
248 for(Approval appr : ticket.approvals) {
249 switch(appr.getType()) {
259 ++state[type][total]; // count per type
260 switch(appr.getStatus()) {
262 ++state[type][pending];
263 approver = appr.getApprover();
264 Pending n = pendingTemp.get(approver);
266 Date lastNotified = ln.lastNotified(approver,"ur",ticket.f.fdd.target_key);
267 pendingTemp.put(approver,new Pending(lastNotified));
273 ++state[type][approved];
276 ++state[type][unknown];
281 // Always must have at least 1 owner
282 if((state[owner][total]>0 && state[owner][approved]>0) &&
283 // If there are no Supervisors, that's ok
284 (state[supervisor][total]==0 ||
285 // But if there is a Supervisor, they must have approved
286 (state[supervisor][approved]>0))) {
287 UserRoleDAO.Data urdd = new UserRoleDAO.Data();
289 urdd.reconstitute(ticket.f.fdd.construct);
290 if(urdd.expires.before(ticket.f.expires())) {
291 extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
293 } catch (IOException e) {
294 trans.error().log("Could not reconstitute UserRole");
296 } else { // Load all the Pending.
297 for(Entry<String, Pending> es : pendingTemp.entrySet()) {
298 Pending p = pendingApprs.get(es.getKey());
300 pendingApprs.put(es.getKey(), es.getValue());
302 p.inc(es.getValue());
309 if("user_role".equals(ticket.f.fdd.target)) {
310 String key = ticket.f.fdd.target_key;
312 mur.put(key, ticket);
320 // Good Tickets no longer needed
324 * Decide to Notify about Approvals, based on activity/last Notified
326 tt = trans.start("Analyze Approval Reminders", Trans.SUB);
328 GregorianCalendar gc = new GregorianCalendar();
329 gc.add(GregorianCalendar.DAY_OF_WEEK, 5);
330 Date remind = gc.getTime();
332 for(Entry<String, Pending> es : pendingApprs.entrySet()) {
333 Pending p = es.getValue();
335 || p.earliest() == null
336 || p.earliest().after(remind)) {
337 p.row(needApproveCW,es.getKey());
344 // clear out Approval Intermediates
352 Run through User Roles.
353 Owners are treated specially in next section.
354 Regular roles are checked against Date Ranges. If match Date Range, write out to appropriate file.
358 Role.load(trans, session);
361 tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
362 Set<String> specialCommented = new HashSet<>();
363 Map<String, Set<UserRole>> owners = new TreeMap<>();
365 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
368 identity = trans.org().getIdentity(noAvg,ur.user());
370 // Candidate for Delete, but not Users if Special
371 String id = ur.user();
372 for(String s : specialDomains) {
374 if(!specialCommented.contains(id)) {
375 deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s);
376 specialCommented.add(id);
381 if(specialNames.contains(id)) {
382 if(!specialCommented.contains(id)) {
383 deleteCW.comment("ID %s is a special ID (UR Org Check)", id);
384 specialCommented.add(id);
388 ur.row(deleteCW, UserRole.UR,"Not in Organization");
390 } else if(Role.byName.get(ur.role())==null) {
391 ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
394 // Just let expired UserRoles sit until deleted
395 if(futureRange.inRange(ur.expires())) {
396 if(!mur.containsKey(ur.user() + '|' + ur.role())) {
397 // Cannot just delete owners, unless there is at least one left. Process later
398 if ("owner".equals(ur.rname())) {
399 Set<UserRole> urs = owners.get(ur.role());
401 urs = new HashSet<UserRole>();
402 owners.put(ur.role(), urs);
406 Range r = writeAnalysis(noAvg,ur);
408 Approval existing = findApproval(ur);
410 ur.row(needApproveCW,UserRole.APPROVE_UR);
416 } catch (OrganizationException e) {
417 noAvg.error().log(e);
426 Now Process Owners, one owner Role at a time, ensuring one is left,
427 preferably a good one. If so, process the others as normal.
429 Otherwise, write to ExpiredOwners Report
431 tt = trans.start("Analyze Owners Separately",Trans.SUB);
433 if (!owners.values().isEmpty()) {
434 File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV);
435 final CSV ownerCSV = new CSV(env.access(),file);
436 CSV.Writer expOwner = ownerCSV.writer();
437 expOwner.row(INFO,EXPIRED_OWNERS,sdate,2);
440 for (Set<UserRole> sur : owners.values()) {
442 for (UserRole ur : sur) {
443 if (ur.expires().after(now)) {
448 for (UserRole ur : sur) {
449 if (goodOwners >= minOwners) {
450 Range r = writeAnalysis(noAvg, ur);
452 Approval existing = findApproval(ur);
454 ur.row(needApproveCW,UserRole.APPROVE_UR);
458 expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
459 Approval existing = findApproval(ur);
461 ur.row(needApproveCW,UserRole.APPROVE_UR);
481 * Check for Expired Credentials
484 // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway
485 Cred.load(trans, session);
487 tt = trans.start("Analyze Expired Credentials",Trans.SUB);
489 for (Cred cred : Cred.data.values()) {
490 List<Instance> linst = cred.instances;
492 Instance lastBath = null;
493 for(Instance inst : linst) {
494 // All Creds go through Life Cycle
495 if(deleteDate!=null && inst.expires.before(deleteDate)) {
496 writeAnalysis(noAvg, cred, inst); // will go to Delete
497 // Basic Auth has Pre-EOL notifications IF there is no Newer Credential
498 } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) {
499 if(lastBath==null || lastBath.expires.before(inst.expires)) {
505 writeAnalysis(noAvg, cred, lastBath);
517 tt = trans.start("Analyze Expired X509s",Trans.SUB);
519 X509.load(noAvg, session, x509 -> {
521 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
522 writeAnalysis(noAvg, x509, (X509Certificate)cert);
524 } catch (CertificateException | IOException e) {
525 noAvg.error().log(e, "Error Decrypting X509");
531 } catch (FileNotFoundException e) {
536 private Approval findApproval(UserRole ur) {
537 Approval existing = null;
538 List<Approval> apprs = Approval.byUser.get(ur.user());
540 for(Approval appr : apprs) {
541 if(ur.role().equals(appr.getRole()) &&
542 appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
550 private Range writeAnalysis(AuthzTrans noAvg, UserRole ur) {
551 Range r = expireRange.getRange("ur", ur.expires());
553 Date lnd = ln.lastNotified(LastNotified.newKey(ur));
554 // Note: lnd is NEVER null
557 i = org.getIdentity(noAvg, ur.user());
558 } catch (OrganizationException e) {
561 if(r.needsContact(lnd,i)) {
562 CSV.Writer cw = writerList.get(r.name());
564 ur.row(cw,UserRole.UR);
571 private void writeAnalysis(AuthzTrans noAvg, Cred cred, Instance inst) {
572 if(cred!=null && inst!=null) {
573 Range r = expireRange.getRange("cred", inst.expires);
575 Date lnd = ln.lastNotified(LastNotified.newKey(cred,inst));
576 // Note: lnd is NEVER null
579 i = org.getIdentity(noAvg, cred.id);
580 } catch (OrganizationException e) {
583 if(r.needsContact(lnd,i)) {
584 CSV.Writer cw = writerList.get(r.name());
593 private void writeAnalysis(AuthzTrans noAvg, X509 x509, X509Certificate x509Cert) throws IOException {
594 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
596 Date lnd = ln.lastNotified(LastNotified.newKey(x509,x509Cert));
597 // Note: lnd is NEVER null
600 i = org.getIdentity(noAvg, x509.id);
601 } catch (OrganizationException e) {
604 if(r.needsContact(lnd,i)) {
605 CSV.Writer cw = writerList.get(r.name());
607 x509.row(cw,x509Cert);
614 protected void _close(AuthzTrans trans) {
616 for(CSV.Writer cw : writerList.values()) {