/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2017 The PWM Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.util;
import org.apache.commons.lang3.text.WordUtils;
import password.pwm.AppProperty;
import password.pwm.PwmApplication;
import password.pwm.PwmConstants;
import password.pwm.bean.EmailItemBean;
import password.pwm.config.PwmSetting;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.health.HealthMonitor;
import password.pwm.health.HealthRecord;
import password.pwm.i18n.Display;
import password.pwm.svc.report.ReportSummaryData;
import password.pwm.util.java.JavaHelper;
import password.pwm.util.java.TimeDuration;
import password.pwm.util.logging.PwmLogger;
import password.pwm.util.macro.MacroMachine;
import java.time.Instant;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
public abstract class AlertHandler {
private static final PwmLogger LOGGER = PwmLogger.forClass(AlertHandler.class);
public static void alertDailyStats(
final PwmApplication pwmApplication,
final Map<String, String> dailyStatistics
) throws PwmUnrecoverableException {
if (!checkIfEnabled(pwmApplication, PwmSetting.EVENTS_ALERT_DAILY_SUMMARY)) {
return;
}
final Locale locale = PwmConstants.DEFAULT_LOCALE;
for (final String toAddress : pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.AUDIT_EMAIL_SYSTEM_TO)) {
final String fromAddress = pwmApplication.getConfig().readAppProperty(AppProperty.AUDIT_EVENTS_EMAILFROM);
final String subject = Display.getLocalizedMessage(locale,Display.Title_Application,pwmApplication.getConfig()) + " - Daily Summary";
final StringBuilder textBody = new StringBuilder();
final StringBuilder htmlBody = new StringBuilder();
makeEmailBody(pwmApplication, dailyStatistics, locale, textBody, htmlBody);
final EmailItemBean emailItem = new EmailItemBean(toAddress, fromAddress, subject, textBody.toString(), htmlBody.toString());
LOGGER.debug("sending daily summary email to " + toAddress);
pwmApplication.getEmailQueue().submitEmail(emailItem, null, MacroMachine.forNonUserSpecific(pwmApplication,null));
}
}
private static void makeEmailBody(
final PwmApplication pwmApplication,
final Map<String, String> dailyStatistics,
final Locale locale,
final StringBuilder textBody,
final StringBuilder htmlBody)
{
{ // email html header
htmlBody.append("<html><head>");
htmlBody.append("<style type='text/css'");
htmlBody.append(
"\n" +
"html, body { font-family:Arial, Helvetica, sans-serif; color:#333333; font-size:12px; height:100%; margin:0 }\n" +
"\n" +
"a { color:#2D2D2D; text-decoration:underline; font-weight:bold }\n" +
"p { max-width: 600px; color:#2D2D2D; position:relative; margin-left: auto; margin-right: auto}\n" +
"hr { float: none; width:100px; position:relative; margin-left:5px; margin-top: 30px; margin-bottom: 30px; }\n" +
"\n" +
"h1 { font-size:16px; }\n" +
"h2 { font-size:14px; }\n" +
"h3 { font-size:12px; }\n" +
"\n" +
"select { font-family:Trebuchet MS, sans-serif; width: 500px }\n" +
"\n" +
"table { border-collapse:collapse; border: 2px solid #D4D4D4; width:100%; margin-left: auto; margin-right: auto }\n" +
"table td { border: 1px solid #D4D4D4; padding-left: 5px;}\n" +
"table td.title { text-align:center; font-weight: bold; font-size: 150%; padding-right: 10px; background-color:#DDDDDD }\n" +
"table td.key { text-align:right; font-weight:bold; padding-right: 10px; width: 200px;}\n" +
"\n" +
".inputfield { width:400px; margin: 5px; height:18px }\n" +
"\n" +
"/* main body wrapper, all elements (except footer) should be within wrapper */\n" +
"#wrapper { width:100%; min-height: 100%; height: auto !important; height: 100%; margin: 0; }\n" +
"\n" +
"\n" +
"/* main content section, all content should be inside a centerbody div */\n" +
"#centerbody { width:600px; min-width:600px; padding:0; position:relative; ; margin-left:auto; margin-right:auto; margin-top: 10px; clear:both; padding-bottom:40px;}\n" +
"\n" +
"/* all forms use a buttonbar div containing the action buttons */\n" +
"#buttonbar { margin-top: 30px; width:600px; margin-bottom: 15px; text-align: center}\n" +
"#buttonbar .btn { font-family:Trebuchet MS, sans-serif; margin-left: 5px; margin-right: 5px; padding: 0 .25em; width: auto; overflow: visible}\n" +
"\n" +
"/* used for password complexity meter */\n" +
"div.progress-container { border: 1px solid #ccc; width: 90px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; }\n" +
"div.progress-container > div { background-color: #ffffff; height: 10px; }\n" +
"\n" +
"/* header stuff */\n" +
"#header { width:100%; height: 70px; margin: 0; background-image:url('header-gradient.gif') }\n" +
"#header-page { width:600px; padding-top:9px; margin-left: auto; margin-right: auto; font-family:Trebuchet MS, sans-serif; font-size:22px; color:#FFFFFF; }\n" +
"#header-title { width:600px; margin: auto; font-family:Trebuchet MS, sans-serif; font-size:14px; color:#FFFFFF; }\n" +
"#header-warning { width:100%; background-color:#FFDC8B; text-align:center; padding-top:4px; padding-bottom:4px }\n" +
"\n" +
".clear { clear:both; }\n" +
"\n" +
".msg-info { display:block; padding:6px; background-color:#DDDDDD; width: 560px; border-radius:3px; -moz-border-radius:3px}\n" +
".msg-error { display:block; padding:6px; background-color:#FFCD59; width: 560px; border-radius:3px; -moz-border-radius:3px}\n" +
".msg-success { display:block; padding:6px; background-color:#EFEFEF; width: 560px; border-radius:3px; -moz-border-radius:3px}\n" +
"\n" +
"#footer { position:relative; ;text-align: center; bottom:0; width:100%; color: #BBBBBB; font-size: 11px; height: 30px; margin: 0; margin-top: -30px}\n" +
"#footer .idle_status { color: #333333; }\n" +
"\n" +
"#capslockwarning { font-family: Trebuchet MS, sans-serif; color: #ffffff; font-weight:bold; font-variant:small-caps; margin-bottom: 5px; background-color:#d20734; border-radius:3px}\n" +
"");
htmlBody.append("</style></head><body>");
}
{ // server info
final Map<String,String> metadata = new LinkedHashMap<>();
metadata.put("Instance ID", pwmApplication.getInstanceID());
metadata.put("Site URL", pwmApplication.getConfig().readSettingAsString(PwmSetting.PWM_SITE_URL));
metadata.put("Timestamp", JavaHelper.toIsoDate(Instant.now()));
metadata.put("Up Time", TimeDuration.fromCurrent(pwmApplication.getStartupTime()).asLongString());
for (final String key : metadata.keySet()) {
final String value = metadata.get(key);
htmlBody.append(key).append(": ").append(value).append("<br/>");
textBody.append(key).append(": ").append(value).append("\n");
}
}
textBody.append("\n");
htmlBody.append("<br/>");
{ // health check data
final Collection<HealthRecord> healthRecords = pwmApplication.getHealthMonitor().getHealthRecords(HealthMonitor.CheckTimeliness.Immediate);
textBody.append("-- Health Check Results --\n");
htmlBody.append("<h2>Health Check Results</h2>");
htmlBody.append("<table border='1'>");
for (final HealthRecord record : healthRecords) {
htmlBody.append("<tr><td class='key'>").append(record.getTopic(PwmConstants.DEFAULT_LOCALE,pwmApplication.getConfig())).append("</td>");
{
final String color;
switch (record.getStatus()) {
case GOOD:
color = "#8ced3f";
break;
case CAUTION:
color = "#FFCD59";
break;
case WARN:
color = "#d20734";
break;
default:
color = "white";
}
htmlBody.append("<td bgcolor='").append(color).append("'>").append(record.getStatus()).append("</td>");
}
htmlBody.append("<td>").append(record.getDetail(PwmConstants.DEFAULT_LOCALE,pwmApplication.getConfig())).append("</td></tr>");
}
htmlBody.append("</table>");
final int wrapLineLength = 120;
for (final HealthRecord record : healthRecords) { {
final String wrappedLine = wrapText(record.getStatus().getDescription(locale,
pwmApplication.getConfig()) + ": " + record.getTopic(PwmConstants.DEFAULT_LOCALE,
pwmApplication.getConfig()) + " - " + stripHtmlTags(
record.getDetail(PwmConstants.DEFAULT_LOCALE, pwmApplication.getConfig())),wrapLineLength);
textBody.append(wrappedLine).append("\n");
}
}
}
textBody.append("\n");
htmlBody.append("<br/>");
if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.REPORTING_ENABLE)) {
final List<ReportSummaryData.PresentationRow> summaryData = pwmApplication.getReportService()
.getSummaryData().asPresentableCollection(pwmApplication.getConfig(),locale);
textBody.append("-- Directory Report Summary --\n");
for (final ReportSummaryData.PresentationRow record : summaryData) {
textBody.append(record.getLabel()).append(": ").append(record.getCount());
if (record.getPct() != null && !record.getPct().isEmpty()) {
textBody.append(" (").append(record.getPct()).append(")");
}
textBody.append("\n");
}
htmlBody.append("<h2>Directory Report Summary</h2>");
htmlBody.append("<table border='1'>");
for (final ReportSummaryData.PresentationRow record : summaryData) {
htmlBody.append("<tr>");
htmlBody.append("<td class='key'>").append(record.getLabel()).append("</td>");
htmlBody.append("<td>").append(record.getCount()).append("</td>");
htmlBody.append("<td>").append(record.getPct() == null ? "" : record.getPct()).append("</td>");
htmlBody.append("</tr>");
}
htmlBody.append("</table>");
}
textBody.append("\n");
htmlBody.append("<br/>");
if (dailyStatistics != null && !dailyStatistics.isEmpty()) { // statistics
htmlBody.append("<h2>Daily Statistics</h2>");
textBody.append("--Daily Statistics--\n");
final Map<String, String> sortedStats = new TreeMap<>();
sortedStats.putAll(dailyStatistics);
htmlBody.append("<table border='1'>");
for (final String key : sortedStats.keySet()) {
final String value = dailyStatistics.get(key);
textBody.append(key).append(": ").append(value).append("\n");
htmlBody.append("<tr><td class='key'>").append(key).append("</td><td>").append(value).append("</td></tr>");
}
htmlBody.append("</table>");
}
htmlBody.append("</body></html>");
}
private static boolean checkIfEnabled(final PwmApplication pwmApplication, final PwmSetting pwmSetting) {
if (pwmApplication == null) {
return false;
}
if (pwmApplication.getConfig() == null) {
return false;
}
final List<String> toAddress = pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.AUDIT_EMAIL_SYSTEM_TO);
final String fromAddress = pwmApplication.getConfig().readAppProperty(AppProperty.AUDIT_EVENTS_EMAILFROM);
if (toAddress == null || toAddress.isEmpty() || toAddress.get(0) == null || toAddress.get(0).length() < 1) {
return false;
}
if (fromAddress == null || fromAddress.length() < 1) {
return false;
}
if (pwmSetting != null) {
return pwmApplication.getConfig().readSettingAsBoolean(pwmSetting);
}
return true;
}
private static String stripHtmlTags(final String input) {
return input == null ? "" : input.replaceAll("\\<.*?>","");
}
private static String wrapText(final String input, final int length) {
String output = WordUtils.wrap(input, length);
output = output.replace("\n","\n ");
return output;
}
}