/*
* 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.http.servlet.configmanager;
import org.apache.commons.csv.CSVPrinter;
import password.pwm.AppProperty;
import password.pwm.PwmAboutProperty;
import password.pwm.PwmApplication;
import password.pwm.PwmConstants;
import password.pwm.bean.UserIdentity;
import password.pwm.bean.pub.SessionStateInfoBean;
import password.pwm.config.Configuration;
import password.pwm.config.stored.StoredConfigurationImpl;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.health.HealthMonitor;
import password.pwm.health.HealthRecord;
import password.pwm.http.PwmRequest;
import password.pwm.http.servlet.admin.UserDebugDataBean;
import password.pwm.http.servlet.admin.UserDebugDataReader;
import password.pwm.ldap.LdapDebugDataGenerator;
import password.pwm.svc.PwmService;
import password.pwm.util.LDAPPermissionCalculator;
import password.pwm.util.java.FileSystemUtility;
import password.pwm.util.java.JavaHelper;
import password.pwm.util.java.JsonUtil;
import password.pwm.util.java.StringUtil;
import password.pwm.util.java.TimeDuration;
import password.pwm.util.localdb.LocalDB;
import password.pwm.util.logging.LocalDBSearchQuery;
import password.pwm.util.logging.LocalDBSearchResults;
import password.pwm.util.logging.PwmLogEvent;
import password.pwm.util.logging.PwmLogLevel;
import password.pwm.util.logging.PwmLogger;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class DebugItemGenerator {
private static final PwmLogger LOGGER = PwmLogger.forClass(DebugItemGenerator.class);
private static final List<Class<? extends Generator>> DEBUG_ZIP_ITEM_GENERATORS = Collections.unmodifiableList(Arrays.asList(
ConfigurationFileItemGenerator.class,
ConfigurationDebugJsonItemGenerator.class,
ConfigurationDebugTextItemGenerator.class,
AboutItemGenerator.class,
SystemEnvironmentItemGenerator.class,
AppPropertiesItemGenerator.class,
InfoDebugItemGenerator.class,
HealthDebugItemGenerator.class,
ThreadDumpDebugItemGenerator.class,
FileInfoDebugItemGenerator.class,
LogDebugItemGenerator.class,
LdapDebugItemGenerator.class,
LDAPPermissionItemGenerator.class,
LocalDBDebugGenerator.class,
SessionDataGenerator.class,
LdapRecentUserDebugGenerator.class
));
static void outputZipDebugFile(
final PwmRequest pwmRequest,
final ZipOutputStream zipOutput,
final String pathPrefix
)
throws IOException, PwmUnrecoverableException
{
final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
final String DEBUG_FILENAME = "zipDebugGeneration.csv";
final ByteArrayOutputStream debugGeneratorLogBaos = new ByteArrayOutputStream();
final CSVPrinter debugGeneratorLogFile = JavaHelper.makeCsvPrinter(debugGeneratorLogBaos);
for (final Class<? extends DebugItemGenerator.Generator> serviceClass : DEBUG_ZIP_ITEM_GENERATORS) {
try {
final Instant startTime = Instant.now();
LOGGER.trace(pwmRequest, "beginning output of item " + serviceClass.getSimpleName());
final Object newInstance = serviceClass.newInstance();
final DebugItemGenerator.Generator newGeneratorItem = (DebugItemGenerator.Generator)newInstance;
zipOutput.putNextEntry(new ZipEntry(pathPrefix + newGeneratorItem.getFilename()));
newGeneratorItem.outputItem(pwmApplication, pwmRequest, zipOutput);
zipOutput.closeEntry();
zipOutput.flush();
final String finishMsg = "completed output of " + newGeneratorItem.getFilename() + " in " + TimeDuration.fromCurrent(startTime).asCompactString();
LOGGER.trace(pwmRequest, finishMsg);
debugGeneratorLogFile.printRecord(JavaHelper.toIsoDate(new Date()),finishMsg);
} catch (Exception e) {
final String errorMsg = "unexpected error executing debug item output class '" + serviceClass.getName() + "', error: " + e.toString();
LOGGER.error(pwmRequest, errorMsg);
debugGeneratorLogFile.printRecord(JavaHelper.toIsoDate(new Date()),errorMsg);
final Writer stackTraceOutput = new StringWriter();
e.printStackTrace(new PrintWriter(stackTraceOutput));
debugGeneratorLogFile.printRecord(stackTraceOutput);
}
}
try {
zipOutput.putNextEntry(new ZipEntry(pathPrefix + DEBUG_FILENAME));
debugGeneratorLogFile.flush();
zipOutput.write(debugGeneratorLogBaos.toByteArray());
zipOutput.closeEntry();
} catch (Exception e) {
LOGGER.error("error generating " + DEBUG_FILENAME + ": " + e.getMessage());
}
zipOutput.flush();
}
static class ConfigurationDebugJsonItemGenerator implements Generator {
@Override
public String getFilename() {
return "configuration-debug.json";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception
{
final StoredConfigurationImpl storedConfiguration = ConfigManagerServlet.readCurrentConfiguration(pwmRequest);
storedConfiguration.resetAllPasswordValues("value removed from " + PwmConstants.PWM_APP_NAME + "-Support configuration export");
final String jsonOutput = JsonUtil.serialize(storedConfiguration.toJsonDebugObject(), JsonUtil.Flag.PrettyPrint);
outputStream.write(jsonOutput.getBytes(PwmConstants.DEFAULT_CHARSET));
}
}
static class ConfigurationDebugTextItemGenerator implements Generator {
@Override
public String getFilename() {
return "configuration-debug.txt";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception
{
final StoredConfigurationImpl storedConfiguration = ConfigManagerServlet.readCurrentConfiguration(pwmRequest);
storedConfiguration.resetAllPasswordValues("value removed from " + PwmConstants.PWM_APP_NAME + "-Support configuration export");
final StringWriter writer = new StringWriter();
writer.write("Configuration Debug Output for "
+ PwmConstants.PWM_APP_NAME + " "
+ PwmConstants.SERVLET_VERSION + "\n");
writer.write("Timestamp: " + JavaHelper.toIsoDate(storedConfiguration.modifyTime()) + "\n");
writer.write("This file is encoded using " + PwmConstants.DEFAULT_CHARSET.displayName() + "\n");
writer.write("\n");
final Map<String,String> modifiedSettings = storedConfiguration.getModifiedSettingDebugValues(PwmConstants.DEFAULT_LOCALE, true);
for (final String key : modifiedSettings.keySet()) {
final String value = modifiedSettings.get(key);
writer.write(">> Setting > " + key);
writer.write("\n");
writer.write(value);
writer.write("\n");
writer.write("\n");
}
outputStream.write(writer.toString().getBytes(PwmConstants.DEFAULT_CHARSET));
}
}
static class ConfigurationFileItemGenerator implements Generator {
@Override
public String getFilename() {
return PwmConstants.DEFAULT_CONFIG_FILE_FILENAME;
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception
{
final StoredConfigurationImpl storedConfiguration = ConfigManagerServlet.readCurrentConfiguration(pwmRequest);
storedConfiguration.resetAllPasswordValues("value removed from " + PwmConstants.PWM_APP_NAME + "-Support configuration export");
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
storedConfiguration.toXml(baos);
outputStream.write(baos.toByteArray());
}
}
static class AboutItemGenerator implements Generator {
@Override
public String getFilename() {
return "about.properties";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception
{
final Properties outputProps = new Properties() {
public synchronized Enumeration<Object> keys() {
return Collections.enumeration(new TreeSet<>(super.keySet()));
}
};
final Map<PwmAboutProperty,String> infoBean = PwmAboutProperty.makeInfoBean(pwmApplication);
for (final PwmAboutProperty aboutProperty : infoBean.keySet()) {
outputProps.put(aboutProperty.toString().replace("_","."), infoBean.get(aboutProperty));
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
outputProps.store(baos, JavaHelper.toIsoDate(new Date()));
outputStream.write(baos.toByteArray());
}
}
static class SystemEnvironmentItemGenerator implements Generator {
@Override
public String getFilename() {
return "system-environment.properties";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception
{
final Properties outputProps = JavaHelper.newSortedProperties();
// java threads
final Map<String,String> envProps = System.getenv();
for (final String key : envProps.keySet()) {
outputProps.put(key, envProps.get(key));
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
outputProps.store(baos,JavaHelper.toIsoDate(new Date()));
outputStream.write(baos.toByteArray());
}
}
static class AppPropertiesItemGenerator implements Generator {
@Override
public String getFilename() {
return "appProperties.properties";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception
{
final Configuration config = pwmRequest.getConfig();
final Properties outputProps = JavaHelper.newSortedProperties();
for (final AppProperty appProperty : AppProperty.values()) {
outputProps.setProperty(appProperty.getKey(), config.readAppProperty(appProperty));
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
outputProps.store(baos,JavaHelper.toIsoDate(new Date()));
outputStream.write(baos.toByteArray());
}
}
static class InfoDebugItemGenerator implements Generator {
@Override
public String getFilename() {
return "info.json";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception
{
final LinkedHashMap<String,Object> outputMap = new LinkedHashMap<>();
{ // services info
final LinkedHashMap<String,Object> servicesMap = new LinkedHashMap<>();
for (final PwmService service : pwmApplication.getPwmServices()) {
final LinkedHashMap<String,Object> serviceOutput = new LinkedHashMap<>();
serviceOutput.put("name", service.getClass().getSimpleName());
serviceOutput.put("status",service.status());
serviceOutput.put("health",service.healthCheck());
serviceOutput.put("serviceInfo",service.serviceInfo());
servicesMap.put(service.getClass().getSimpleName(), serviceOutput);
}
outputMap.put("services",servicesMap);
}
final String recordJson = JsonUtil.serializeMap(outputMap, JsonUtil.Flag.PrettyPrint);
outputStream.write(recordJson.getBytes(PwmConstants.DEFAULT_CHARSET));
}
}
static class HealthDebugItemGenerator implements Generator {
@Override
public String getFilename() {
return "health.json";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception {
final Set<HealthRecord> records = pwmApplication.getHealthMonitor().getHealthRecords(HealthMonitor.CheckTimeliness.CurrentButNotAncient);
final String recordJson = JsonUtil.serializeCollection(records, JsonUtil.Flag.PrettyPrint);
outputStream.write(recordJson.getBytes(PwmConstants.DEFAULT_CHARSET));
}
}
static class ThreadDumpDebugItemGenerator implements Generator {
@Override
public String getFilename() {
return "threads.txt";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintWriter writer = new PrintWriter( new OutputStreamWriter(baos, PwmConstants.DEFAULT_CHARSET) );
final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true,true);
for (final ThreadInfo threadInfo : threads) {
writer.write(JavaHelper.threadInfoToString(threadInfo));
}
writer.flush();
outputStream.write(baos.toByteArray());
}
}
static class LdapDebugItemGenerator implements Generator {
@Override
public String getFilename() {
return "ldap-servers.json";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception {
final List<LdapDebugDataGenerator.LdapDebugInfo> ldapDebugInfos = LdapDebugDataGenerator.makeLdapDebugInfos(
pwmRequest.getSessionLabel(),
pwmApplication.getConfig(),
pwmRequest.getLocale()
);
final Writer writer = new OutputStreamWriter(outputStream, PwmConstants.DEFAULT_CHARSET);
writer.write(JsonUtil.serializeCollection(ldapDebugInfos, JsonUtil.Flag.PrettyPrint));
writer.flush();
}
}
static class FileInfoDebugItemGenerator implements Generator {
@Override
public String getFilename() {
return "fileinformation.csv";
}
@Override
public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception {
final List<FileSystemUtility.FileSummaryInformation> fileSummaryInformations = new ArrayList<>();
final File applicationPath = pwmApplication.getPwmEnvironment().getApplicationPath();
if (pwmApplication.getPwmEnvironment().getContextManager() != null) {
try {
final File webInfPath = pwmApplication.getPwmEnvironment().getContextManager().locateWebInfFilePath();
if (webInfPath != null && webInfPath.exists()) {
final File servletRootPath = webInfPath.getParentFile();
if (servletRootPath != null) {
fileSummaryInformations.addAll(FileSystemUtility.readFileInformation(webInfPath));
}
}
} catch (Exception e) {
LOGGER.error(pwmRequest, "unable to generate webInfPath fileMd5sums during zip debug building: " + e.getMessage());
}
}
if (applicationPath != null ) {
try {
fileSummaryInformations.addAll(FileSystemUtility.readFileInformation(applicationPath));
} catch (Exception e) {
LOGGER.error(pwmRequest, "unable to generate appPath fileMd5sums during zip debug building: " + e.getMessage());
}
}
{
final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
{
final List<String> headerRow = new ArrayList<>();
headerRow.add("Filepath");
headerRow.add("Filename");
headerRow.add("Last Modified");
headerRow.add("Size");
headerRow.add("sha1sum");
csvPrinter.printComment(StringUtil.join(headerRow,","));
}
for (final FileSystemUtility.FileSummaryInformation fileSummaryInformation : fileSummaryInformations) {
try {
final List<String> dataRow = new ArrayList<>();
dataRow.add(fileSummaryInformation.getFilepath());
dataRow.add(fileSummaryInformation.getFilename());
dataRow.add(JavaHelper.toIsoDate(fileSummaryInformation.getModified()));
dataRow.add(String.valueOf(fileSummaryInformation.getSize()));
dataRow.add(fileSummaryInformation.getSha1sum());
csvPrinter.printRecord(dataRow);
} catch (Exception e) {
LOGGER.trace("error generating file summary info: " + e.getMessage());
}
}
csvPrinter.flush();
}
}
}
static class LogDebugItemGenerator implements Generator {
@Override
public String getFilename() {
return "debug.log";
}
@Override
public void outputItem(
final PwmApplication pwmApplication,
final PwmRequest pwmRequest,
final OutputStream outputStream
) throws Exception {
final int maxCount = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MANAGER_ZIPDEBUG_MAXLOGLINES));
final int maxSeconds = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MANAGER_ZIPDEBUG_MAXLOGSECONDS));
final LocalDBSearchQuery searchParameters = new LocalDBSearchQuery(
PwmLogLevel.TRACE,
maxCount,
null,
null,
(maxSeconds * 1000),
null
);
final LocalDBSearchResults searchResults = pwmApplication.getLocalDBLogger().readStoredEvents(
searchParameters);
int counter = 0;
while (searchResults.hasNext()) {
final PwmLogEvent event = searchResults.next();
outputStream.write(event.toLogString().getBytes(PwmConstants.DEFAULT_CHARSET));
outputStream.write("\n".getBytes(PwmConstants.DEFAULT_CHARSET));
counter++;
if (counter % 1000 == 0) {
outputStream.flush();
}
}
LOGGER.trace("output " + counter + " lines to " + this.getFilename());
}
}
static class LDAPPermissionItemGenerator implements Generator {
@Override
public String getFilename() {
return "ldapPermissionSuggestions.csv";
}
@Override
public void outputItem(
final PwmApplication pwmApplication,
final PwmRequest pwmRequest,
final OutputStream outputStream
) throws Exception {
final StoredConfigurationImpl storedConfiguration = ConfigManagerServlet.readCurrentConfiguration(pwmRequest);
final LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator(storedConfiguration);
final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
{
final List<String> headerRow = new ArrayList<>();
headerRow.add("Attribute");
headerRow.add("Actor");
headerRow.add("Access");
headerRow.add("Setting");
headerRow.add("Profile");
csvPrinter.printComment(StringUtil.join(headerRow,","));
}
for (final LDAPPermissionCalculator.PermissionRecord record : ldapPermissionCalculator.getPermissionRecords()) {
final List<String> dataRow = new ArrayList<>();
dataRow.add(record.getAttribute());
dataRow.add(record.getActor() == null ? "" : record.getActor().toString());
dataRow.add(record.getAccess() == null ? "" : record.getAccess().toString());
dataRow.add(record.getPwmSetting() == null ? "" : record.getPwmSetting().getKey());
dataRow.add(record.getProfile() == null ? "" : record.getProfile());
csvPrinter.printRecord(dataRow);
}
csvPrinter.flush();
}
}
static class LocalDBDebugGenerator implements Generator {
@Override
public String getFilename() {
return "localDBDebug.json";
}
@Override
public void outputItem(
final PwmApplication pwmApplication,
final PwmRequest pwmRequest,
final OutputStream outputStream
) throws Exception {
final LocalDB localDB = pwmApplication.getLocalDB();
final Map<String,Serializable> serializableMap = localDB.debugInfo();
outputStream.write(JsonUtil.serializeMap(serializableMap, JsonUtil.Flag.PrettyPrint).getBytes(PwmConstants.DEFAULT_CHARSET));
}
}
static class SessionDataGenerator implements Generator {
@Override
public String getFilename() {
return "sessions.csv";
}
@Override
public void outputItem(
final PwmApplication pwmApplication,
final PwmRequest pwmRequest,
final OutputStream outputStream
) throws Exception {
final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
{
final List<String> headerRow = new ArrayList<>();
headerRow.add("Label");
headerRow.add("Create Time");
headerRow.add("Last Time");
headerRow.add("Idle");
headerRow.add("Source Address");
headerRow.add("Source Host");
headerRow.add("LDAP Profile");
headerRow.add("UserID");
headerRow.add("UserDN");
headerRow.add("Locale");
headerRow.add("Last URL");
csvPrinter.printComment(StringUtil.join(headerRow,","));
}
final Iterator<SessionStateInfoBean> debugInfos = pwmApplication.getSessionTrackService().getSessionInfoIterator();
while (debugInfos.hasNext()) {
final SessionStateInfoBean info = debugInfos.next();
final List<String> dataRow = new ArrayList<>();
dataRow.add(info.getLabel());
dataRow.add(JavaHelper.toIsoDate(info.getCreateTime()));
dataRow.add(JavaHelper.toIsoDate(info.getLastTime()));
dataRow.add(info.getIdle());
dataRow.add(info.getSrcAddress());
dataRow.add(info.getSrcHost());
dataRow.add(info.getLdapProfile());
dataRow.add(info.getUserID());
dataRow.add(info.getUserDN());
dataRow.add(info.getLocale() != null ? info.getLocale().toLanguageTag() : "");
dataRow.add(info.getLastUrl());
csvPrinter.printRecord(dataRow);
}
csvPrinter.flush();
}
}
static class LdapRecentUserDebugGenerator implements Generator {
@Override
public String getFilename() {
return "recentUserDebugData.json";
}
@Override
public void outputItem(
final PwmApplication pwmApplication,
final PwmRequest pwmRequest,
final OutputStream outputStream
)
throws Exception
{
final List<UserIdentity> recentUsers = pwmApplication.getSessionTrackService().getRecentLogins();
final List<UserDebugDataBean> recentDebugBeans = new ArrayList<>();
for (final UserIdentity userIdentity : recentUsers) {
final UserDebugDataBean dataBean = UserDebugDataReader.readUserDebugData(
pwmApplication,
pwmRequest.getLocale(),
pwmRequest.getSessionLabel(),
userIdentity
);
recentDebugBeans.add(dataBean);
}
outputStream.write(JsonUtil.serializeCollection(recentDebugBeans, JsonUtil.Flag.PrettyPrint).getBytes(PwmConstants.DEFAULT_CHARSET));
}
}
interface Generator {
String getFilename();
void outputItem(
PwmApplication pwmApplication,
PwmRequest pwmRequest,
OutputStream outputStream
) throws Exception;
}
}