package com.unionbankph.plugins.licman.process.activities; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.sql.Date; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.log4j.Logger; import com.appiancorp.suiteapi.cfg.ConfigurationLoader; import com.appiancorp.suiteapi.common.Name; import com.appiancorp.suiteapi.common.exceptions.InvalidGroupException; import com.appiancorp.suiteapi.common.exceptions.InvalidVersionException; import com.appiancorp.suiteapi.common.exceptions.PrivilegeException; import com.appiancorp.suiteapi.content.ContentService; import com.appiancorp.suiteapi.content.exceptions.InvalidContentException; import com.appiancorp.suiteapi.personalization.GroupDataType; import com.appiancorp.suiteapi.personalization.GroupService; import com.appiancorp.suiteapi.process.exceptions.SmartServiceException; import com.appiancorp.suiteapi.process.framework.Input; import com.appiancorp.suiteapi.process.framework.MessageContainer; import com.appiancorp.suiteapi.process.framework.Required; import com.appiancorp.suiteapi.process.palette.PaletteCategoryConstants; import com.appiancorp.suiteapi.process.palette.PaletteInfo; import com.unionbankph.plugins.licman.LicManUtils; import com.unionbankph.plugins.licman.LoginRecord; @PaletteInfo(paletteCategory = PaletteCategoryConstants.APPIAN_SMART_SERVICES, palette = "Analytics") public class CollectAuditedLogins extends AbstractCollect { private static final Logger LOG = Logger.getLogger(CollectAuditedLogins.class); private static final String AUDIT_LOG_FILENAME = "login-audit"; private static final String[] AUDIT_LOG_FILENAME_BAD_MATCH = { "tar", "gz", "zip", "login-audit-uuid" }; private static final String SHARED_LOGS_FOLDERNAME = "shared-logs"; private static final String AUDIT_LOGIN_SUCCESS = "Succeeded"; private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // Additional Inputs private Date startDate = null; private Date endDate = null; private Long[] excludeGroupIds; public CollectAuditedLogins(ContentService cs, GroupService gs) { super(cs, gs); } @Override public void runSmartService(File tempFile) throws InvalidContentException, IOException, InvalidVersionException, PrivilegeException, SmartServiceException { try { List allTargetLogFiles = new ArrayList<>(); allTargetLogFiles = getTargetLogFiles(); for (File file : allTargetLogFiles) { processLogins(file.getAbsolutePath(), tempFile); } LOG.debug(String.format("All succesful logins since %s retrieved and written to temp file.", dateFormat.format(startDate))); } catch (Exception e) { LOG.debug("Error retrieving or processing login audit logs.", e); throw new SmartServiceException.Builder(getClass(), e).build(); } } // Additional validations public void validate(MessageContainer messages) { super.validate(messages); if (startDate == null) { messages.addError("StartDate", "startdate.missing"); } if (endDate == null) { messages.addError("EndDate", "enddate.missing"); } } private List getTargetLogFiles() { String aeLogsPath = ConfigurationLoader.getConfiguration().getAeLogs(); String sharedLogsPath = getSharedLogsPath(aeLogsPath); if (sharedLogsPath != null) { // We have shared logs LOG.debug(String.format("shared-logs directory found at %s", sharedLogsPath)); List allSharedLogDirs = getSubDirs(sharedLogsPath); List allSharedLoginAuditFiles = new ArrayList<>(); for (File logDir : allSharedLogDirs) { LOG.debug(String.format("Extracting logs from node path at %s", logDir.getAbsolutePath())); List matchingFiles = getMatchingFilesFromDirectory(logDir.getAbsolutePath()); for (File match : matchingFiles) { allSharedLoginAuditFiles.add(match); } } return allSharedLoginAuditFiles; } else { LOG.debug(String.format("No shared-logs path exists. Defaulting to aeLogsPath at %s", aeLogsPath)); return getMatchingFilesFromDirectory(aeLogsPath); } } private static String getSharedLogsPath(String aeLogsPath) { /* * gettAeLogs - this is either: * -AE_HOME/shared-logs/ -> so look 1 up for shared-logs * -AE_HOME/logs -> so look at sibling for shared-logs * -> also check getAeLogs for shared-logs just in case */ String logParentPath = new File(aeLogsPath).getParent(); String testName = new File(logParentPath).getName(); String testPath = logParentPath + File.separator + SHARED_LOGS_FOLDERNAME; // Check aeLogs parent (testName is parent dir name) if (testName.equals(SHARED_LOGS_FOLDERNAME)) { return logParentPath; } // Check aeLogs sibling (testPath is would be sibling path) else if (new File(testPath).exists()) { return testPath; } // Check if this is shared logs path... else if (new File(aeLogsPath).getName().equals(SHARED_LOGS_FOLDERNAME)) { return aeLogsPath; } // Else no shared-logs return null; } private List getMatchingFilesFromDirectory(String directoryName) { File[] matchingFiles = new File(directoryName).listFiles(new FileFilter() { @Override public boolean accept(File file) { return file.isFile() && isValidFileName(file.getAbsolutePath()); } }); return Arrays.asList(matchingFiles); } private static List getSubDirs(String directoryName) { File[] subDirs = new File(directoryName).listFiles(new FileFilter() { @Override public boolean accept(File file) { return file.isDirectory(); } }); return Arrays.asList(subDirs); } private boolean isValidFileName(String filename) { // Match on file name if (filename.contains(AUDIT_LOG_FILENAME)) { // Reject bad matches for (String s : AUDIT_LOG_FILENAME_BAD_MATCH) { if (filename.contains(s)) { return false; } } // Filter by date range Date dateFromFilename = getDateFromFilename(filename); if (dateFromFilename == null) { return false; } if (!dateFromFilename.before(startDate) && !dateFromFilename.after(endDate)) { return true; } } return false; } private static Date getDateFromFilename(String filename) { String suffix = filename.substring(filename.lastIndexOf(".") + 1); if (suffix.length() == 10) { // Valid length for a date try { Date extractedDate = Date.valueOf(suffix); return extractedDate; } catch (Exception e) { LOG.debug(String.format("Failed to parse filename extracted date of %s to a date. Skipping file. Error was %s", suffix, e)); return null; } } else { return null; } }; private void processLogins(String filename, File tempFile) throws Exception { // Extract logins from a single file and push to temp file CSV List loginRecordList = new ArrayList(); List excludeUsernames = getExcludeUserList(); try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename)))) { String strLine = null; while ((strLine = br.readLine()) != null) { try { LoginRecord lr = new LoginRecord(); lr.parseLoginAuditLine(strLine); // Successful audit (target date range has already been validated at file level) // Username not included in excluded groups if (lr.getResult().equals(AUDIT_LOGIN_SUCCESS) && (excludeUsernames.isEmpty() || !excludeUsernames.contains(lr.getUsername()))) { loginRecordList.add(lr); } } catch (Exception e) { LOG.debug("Failed to read audit log line. Skipping line. Line was: " + strLine, e); } } } catch (Exception e) { LOG.debug("Failed to read from entries from audit log.", e); throw e; } if (loginRecordList != null) { appendRecordsToCSV(tempFile, loginRecordList); } } private List getExcludeUserList () throws InvalidGroupException { List usernames = new ArrayList(); if (this.excludeGroupIds == null) return usernames; for (Long groupId: this.excludeGroupIds) { usernames.addAll(Arrays.asList(this.gs.getMemberUsernames(groupId))); } return usernames; } private void appendRecordsToCSV(File file, List lrList) throws Exception { // TODO - Post-MVP - replace the code in with an implementation using bean to CSV writing to write full List to CSV in one go List records = new ArrayList<>(); for (LoginRecord lr : lrList) { records.add(lr.getStringArray()); } LicManUtils.appendStringArrayToCSV(file, records); } @Input(required = Required.ALWAYS) @Name("StartDate") public void setStartDate(Date startDate) { this.startDate = startDate; } @Input(required = Required.ALWAYS) @Name("EndDate") public void setEndDate(Date endDate) { this.endDate = endDate; } @Input(required = Required.OPTIONAL) @Name("ExcludeGroups") @GroupDataType public void setExcludeGroups(Long[] val) { this.excludeGroupIds = val; } }