Przeglądaj źródła

Merge pull request #85 from pwm-project/export-to-csv

Updated report so data from "Download as CSV" matches columns selected on the web page.
James Albright 8 lat temu
rodzic
commit
07a79000d2

+ 16 - 9
src/main/java/password/pwm/http/servlet/AdminServlet.java

@@ -22,7 +22,17 @@
 
 package password.pwm.http.servlet;
 
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+
 import com.novell.ldapchai.exception.ChaiUnavailableException;
+
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
@@ -32,19 +42,13 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmURL;
+import password.pwm.svc.report.ReportColumnFilter;
 import password.pwm.svc.report.ReportService;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.reports.ReportUtils;
 import password.pwm.ws.server.RestResultBean;
 
-import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
 @WebServlet(
         name = "AdminServlet",
         urlPatterns = {
@@ -176,8 +180,11 @@ public class AdminServlet extends AbstractPwmServlet {
 
         final OutputStream outputStream = pwmRequest.getPwmResponse().getOutputStream();
         try {
+            final String selectedColumns = pwmRequest.readParameterAsString("selectedColumns", "");
+
             final ReportService userReport = pwmApplication.getReportService();
-            userReport.outputToCsv(outputStream, true, pwmRequest.getLocale());
+            final ReportColumnFilter columnFilter = ReportUtils.toReportColumnFilter(selectedColumns);
+            userReport.outputToCsv(outputStream, true, pwmRequest.getLocale(), columnFilter);
         } catch (Exception e) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,e.getMessage());
             pwmRequest.respondWithError(errorInformation);

+ 202 - 0
src/main/java/password/pwm/svc/report/ReportColumnFilter.java

@@ -0,0 +1,202 @@
+package password.pwm.svc.report;
+
+public class ReportColumnFilter {
+    boolean userDnVisible;
+    boolean ldapProfileVisible;
+    boolean usernameVisible;
+    boolean emailVisible;
+    boolean userGuidVisible;
+    boolean accountExpirationTimeVisible;
+    boolean lastLoginTimeVisible;
+    boolean passwordExpirationTimeVisible;
+    boolean passwordChangeTimeVisible;
+    boolean responseSetTimeVisible;
+    boolean hasResponsesVisible;
+    boolean hasHelpdeskResponsesVisible;
+    boolean responseStorageMethodVisible;
+    boolean responseFormatTypeVisible;
+    boolean passwordStatusExpiredVisible;
+    boolean passwordStatusPreExpiredVisible;
+    boolean passwordStatusViolatesPolicyVisible;
+    boolean passwordStatusWarnPeriodVisible;
+    boolean requiresPasswordUpdateVisible;
+    boolean requiresResponseUpdateVisible;
+    boolean requiresProfileUpdateVisible;
+    boolean cacheTimestampVisible;
+
+    public boolean isUserDnVisible() {
+        return userDnVisible;
+    }
+
+    public void setUserDnVisible(boolean userDnVisible) {
+        this.userDnVisible = userDnVisible;
+    }
+
+    public boolean isLdapProfileVisible() {
+        return ldapProfileVisible;
+    }
+
+    public void setLdapProfileVisible(boolean ldapProfileVisible) {
+        this.ldapProfileVisible = ldapProfileVisible;
+    }
+
+    public boolean isUsernameVisible() {
+        return usernameVisible;
+    }
+
+    public void setUsernameVisible(boolean usernameVisible) {
+        this.usernameVisible = usernameVisible;
+    }
+
+    public boolean isEmailVisible() {
+        return emailVisible;
+    }
+
+    public void setEmailVisible(boolean emailVisible) {
+        this.emailVisible = emailVisible;
+    }
+
+    public boolean isUserGuidVisible() {
+        return userGuidVisible;
+    }
+
+    public void setUserGuidVisible(boolean userGuidVisible) {
+        this.userGuidVisible = userGuidVisible;
+    }
+
+    public boolean isAccountExpirationTimeVisible() {
+        return accountExpirationTimeVisible;
+    }
+
+    public void setAccountExpirationTimeVisible(boolean accountExpirationTimeVisible) {
+        this.accountExpirationTimeVisible = accountExpirationTimeVisible;
+    }
+
+    public boolean isLastLoginTimeVisible() {
+        return lastLoginTimeVisible;
+    }
+
+    public void setLastLoginTimeVisible(boolean lastLoginTimeVisible) {
+        this.lastLoginTimeVisible = lastLoginTimeVisible;
+    }
+
+    public boolean isPasswordExpirationTimeVisible() {
+        return passwordExpirationTimeVisible;
+    }
+
+    public void setPasswordExpirationTimeVisible(boolean passwordExpirationTimeVisible) {
+        this.passwordExpirationTimeVisible = passwordExpirationTimeVisible;
+    }
+
+    public boolean isPasswordChangeTimeVisible() {
+        return passwordChangeTimeVisible;
+    }
+
+    public void setPasswordChangeTimeVisible(boolean passwordChangeTimeVisible) {
+        this.passwordChangeTimeVisible = passwordChangeTimeVisible;
+    }
+
+    public boolean isResponseSetTimeVisible() {
+        return responseSetTimeVisible;
+    }
+
+    public void setResponseSetTimeVisible(boolean responseSetTimeVisible) {
+        this.responseSetTimeVisible = responseSetTimeVisible;
+    }
+
+    public boolean isHasResponsesVisible() {
+        return hasResponsesVisible;
+    }
+
+    public void setHasResponsesVisible(boolean hasResponsesVisible) {
+        this.hasResponsesVisible = hasResponsesVisible;
+    }
+
+    public boolean isHasHelpdeskResponsesVisible() {
+        return hasHelpdeskResponsesVisible;
+    }
+
+    public void setHasHelpdeskResponsesVisible(boolean hasHelpdeskResponsesVisible) {
+        this.hasHelpdeskResponsesVisible = hasHelpdeskResponsesVisible;
+    }
+
+    public boolean isResponseStorageMethodVisible() {
+        return responseStorageMethodVisible;
+    }
+
+    public void setResponseStorageMethodVisible(boolean responseStorageMethodVisible) {
+        this.responseStorageMethodVisible = responseStorageMethodVisible;
+    }
+
+    public boolean isResponseFormatTypeVisible() {
+        return responseFormatTypeVisible;
+    }
+
+    public void setResponseFormatTypeVisible(boolean responseFormatTypeVisible) {
+        this.responseFormatTypeVisible = responseFormatTypeVisible;
+    }
+
+    public boolean isPasswordStatusExpiredVisible() {
+        return passwordStatusExpiredVisible;
+    }
+
+    public void setPasswordStatusExpiredVisible(boolean passwordStatusExpiredVisible) {
+        this.passwordStatusExpiredVisible = passwordStatusExpiredVisible;
+    }
+
+    public boolean isPasswordStatusPreExpiredVisible() {
+        return passwordStatusPreExpiredVisible;
+    }
+
+    public void setPasswordStatusPreExpiredVisible(boolean passwordStatusPreExpiredVisible) {
+        this.passwordStatusPreExpiredVisible = passwordStatusPreExpiredVisible;
+    }
+
+    public boolean isPasswordStatusViolatesPolicyVisible() {
+        return passwordStatusViolatesPolicyVisible;
+    }
+
+    public void setPasswordStatusViolatesPolicyVisible(boolean passwordStatusViolatesPolicyVisible) {
+        this.passwordStatusViolatesPolicyVisible = passwordStatusViolatesPolicyVisible;
+    }
+
+    public boolean isPasswordStatusWarnPeriodVisible() {
+        return passwordStatusWarnPeriodVisible;
+    }
+
+    public void setPasswordStatusWarnPeriodVisible(boolean passwordStatusWarnPeriodVisible) {
+        this.passwordStatusWarnPeriodVisible = passwordStatusWarnPeriodVisible;
+    }
+
+    public boolean isRequiresPasswordUpdateVisible() {
+        return requiresPasswordUpdateVisible;
+    }
+
+    public void setRequiresPasswordUpdateVisible(boolean requiresPasswordUpdateVisible) {
+        this.requiresPasswordUpdateVisible = requiresPasswordUpdateVisible;
+    }
+
+    public boolean isRequiresResponseUpdateVisible() {
+        return requiresResponseUpdateVisible;
+    }
+
+    public void setRequiresResponseUpdateVisible(boolean requiresResponseUpdateVisible) {
+        this.requiresResponseUpdateVisible = requiresResponseUpdateVisible;
+    }
+
+    public boolean isRequiresProfileUpdateVisible() {
+        return requiresProfileUpdateVisible;
+    }
+
+    public void setRequiresProfileUpdateVisible(boolean requiresProfileUpdateVisible) {
+        this.requiresProfileUpdateVisible = requiresProfileUpdateVisible;
+    }
+
+    public boolean isCacheTimestampVisible() {
+        return cacheTimestampVisible;
+    }
+
+    public void setCacheTimestampVisible(boolean cacheTimestampVisible) {
+        this.cacheTimestampVisible = cacheTimestampVisible;
+    }
+}

+ 122 - 89
src/main/java/password/pwm/svc/report/ReportService.java

@@ -420,56 +420,56 @@ public class ReportService implements PwmService {
         return returnList;
     }
 
-    public RecordIterator<UserCacheRecord> iterator() {
-        return new RecordIterator<>(userCacheService.<UserCacheService.StorageKey>iterator());
+    public interface RecordIterator<K> extends ClosableIterator<UserCacheRecord> {
     }
 
-    public class RecordIterator<K> implements ClosableIterator<UserCacheRecord> {
-
-        private UserCacheService.UserStatusCacheBeanIterator<UserCacheService.StorageKey> storageKeyIterator;
-
-        public RecordIterator(UserCacheService.UserStatusCacheBeanIterator<UserCacheService.StorageKey> storageKeyIterator) {
-            this.storageKeyIterator = storageKeyIterator;
-        }
+    public RecordIterator<UserCacheRecord> iterator() {
+        return new RecordIterator<UserCacheRecord>() {
+            private UserCacheService.UserStatusCacheBeanIterator<UserCacheService.StorageKey> storageKeyIterator = userCacheService.iterator();
 
-        public boolean hasNext() {
-            return this.storageKeyIterator.hasNext();
-        }
+            @Override
+            public boolean hasNext() {
+                return this.storageKeyIterator.hasNext();
+            }
 
-        public UserCacheRecord next()
-        {
-            try {
-                UserCacheRecord returnBean = null;
-                while (returnBean == null && this.storageKeyIterator.hasNext()) {
-                    UserCacheService.StorageKey key = this.storageKeyIterator.next();
-                    returnBean = userCacheService.readStorageKey(key);
-                    if (returnBean != null) {
-                        if (returnBean.getCacheTimestamp() == null) {
-                            LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"purging record due to missing cache timestamp: " + JsonUtil.serialize(returnBean));
-                            userCacheService.removeStorageKey(key);
-                        } else if (TimeDuration.fromCurrent(returnBean.getCacheTimestamp()).isLongerThan(settings.getMaxCacheAge())) {
-                            LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"purging record due to old age timestamp: " + JsonUtil.serialize(returnBean));
-                            userCacheService.removeStorageKey(key);
-                        } else {
-                            return returnBean;
+            @Override
+            public UserCacheRecord next()
+            {
+                try {
+                    UserCacheRecord returnBean = null;
+                    while (returnBean == null && this.storageKeyIterator.hasNext()) {
+                        UserCacheService.StorageKey key = this.storageKeyIterator.next();
+                        returnBean = userCacheService.readStorageKey(key);
+                        if (returnBean != null) {
+                            if (returnBean.getCacheTimestamp() == null) {
+                                LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"purging record due to missing cache timestamp: " + JsonUtil.serialize(returnBean));
+                                userCacheService.removeStorageKey(key);
+                            } else if (TimeDuration.fromCurrent(returnBean.getCacheTimestamp()).isLongerThan(settings.getMaxCacheAge())) {
+                                LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"purging record due to old age timestamp: " + JsonUtil.serialize(returnBean));
+                                userCacheService.removeStorageKey(key);
+                            } else {
+                                return returnBean;
+                            }
                         }
-                    }
 
+                    }
+                } catch (LocalDBException e) {
+                    throw new IllegalStateException("unexpected iterator traversal error while reading LocalDB: " + e.getMessage());
                 }
-            } catch (LocalDBException e) {
-                throw new IllegalStateException("unexpected iterator traversal error while reading LocalDB: " + e.getMessage());
+                return null;
             }
-            return null;
-        }
 
-        public void remove()
-        {
+            @Override
+            public void remove()
+            {
 
-        }
+            }
 
-        public void close() {
-            storageKeyIterator.close();
-        }
+            @Override
+            public void close() {
+                storageKeyIterator.close();
+            }
+        };
     }
 
     public void outputSummaryToCsv(final OutputStream outputStream, final Locale locale)
@@ -490,34 +490,53 @@ public class ReportService implements PwmService {
     }
 
     public void outputToCsv(final OutputStream outputStream, final boolean includeHeader, final Locale locale)
+    throws IOException, ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException
+    {
+        final Configuration config = pwmApplication.getConfig();
+        final ReportColumnFilter columnFilter = new ReportColumnFilter();
+
+        outputToCsv(outputStream, includeHeader, locale, config, columnFilter);
+    }
+
+    public void outputToCsv(final OutputStream outputStream, final boolean includeHeader, final Locale locale, final ReportColumnFilter columnFilter)
+    throws IOException, ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException
+    {
+        final Configuration config = pwmApplication.getConfig();
+        outputToCsv(outputStream, includeHeader, locale, config, columnFilter);
+    }
+
+    public void outputToCsv(final OutputStream outputStream, final boolean includeHeader, final Locale locale, final Configuration config, final ReportColumnFilter columnFilter)
             throws IOException, ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException
     {
         final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream);
-        final Configuration config = pwmApplication.getConfig();
         final Class localeClass = password.pwm.i18n.Admin.class;
         if (includeHeader) {
             final List<String> headerRow = new ArrayList<>();
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_UserDN", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_LDAP_Profile", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_Username", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_Email", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_UserGuid", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_LastLogin", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdExpireTime", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdChangeTime", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_ResponseSaveTime", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_HasResponses", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_HasHelpdeskResponses", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_ResponseStorageMethod", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_ResponseFormatType", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdExpired", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdPreExpired", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdViolatesPolicy", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdWarnPeriod", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_RequiresPasswordUpdate", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_RequiresResponseUpdate", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_RequiresProfileUpdate", config, localeClass));
-            headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_RecordCacheTime", config, localeClass));
+
+            if (columnFilter.isUsernameVisible())                     headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_Username", config, localeClass));
+            if (columnFilter.isUserDnVisible())                       headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_UserDN", config, localeClass));
+            if (columnFilter.isLdapProfileVisible())                  headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_LDAP_Profile", config, localeClass));
+            if (columnFilter.isEmailVisible())                        headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_Email", config, localeClass));
+            if (columnFilter.isUserGuidVisible())                     headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_UserGuid", config, localeClass));
+            if (columnFilter.isAccountExpirationTimeVisible())        headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_AccountExpireTime", config, localeClass));
+            if (columnFilter.isPasswordExpirationTimeVisible())       headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdExpireTime", config, localeClass));
+            if (columnFilter.isPasswordChangeTimeVisible())           headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdChangeTime", config, localeClass));
+            if (columnFilter.isResponseSetTimeVisible())              headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_ResponseSaveTime", config, localeClass));
+            if (columnFilter.isLastLoginTimeVisible())                headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_LastLogin", config, localeClass));
+            if (columnFilter.isHasResponsesVisible())                 headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_HasResponses", config, localeClass));
+            if (columnFilter.isHasHelpdeskResponsesVisible())         headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_HasHelpdeskResponses", config, localeClass));
+            if (columnFilter.isResponseStorageMethodVisible())        headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_ResponseStorageMethod", config, localeClass));
+            if (columnFilter.isResponseFormatTypeVisible())           headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_ResponseFormatType", config, localeClass));
+            if (columnFilter.isPasswordStatusExpiredVisible())        headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdExpired", config, localeClass));
+            if (columnFilter.isPasswordStatusPreExpiredVisible())     headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdPreExpired", config, localeClass));
+            if (columnFilter.isPasswordStatusViolatesPolicyVisible()) headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdViolatesPolicy", config, localeClass));
+            if (columnFilter.isPasswordStatusWarnPeriodVisible())     headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdWarnPeriod", config, localeClass));
+            if (columnFilter.isRequiresPasswordUpdateVisible())       headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_RequiresPasswordUpdate", config, localeClass));
+            if (columnFilter.isRequiresResponseUpdateVisible())       headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_RequiresResponseUpdate", config, localeClass));
+            if (columnFilter.isRequiresProfileUpdateVisible())        headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_RequiresProfileUpdate", config, localeClass));
+            if (columnFilter.isCacheTimestampVisible())               headerRow.add(LocaleHelper.getLocalizedMessage(locale, "Field_Report_RecordCacheTime", config, localeClass));
+
+
             csvPrinter.printRecord(headerRow);
         }
 
@@ -526,7 +545,7 @@ public class ReportService implements PwmService {
             cacheBeanIterator = this.iterator();
             while (cacheBeanIterator.hasNext()) {
                 final UserCacheRecord userCacheRecord = cacheBeanIterator.next();
-                outputRecordRow(config, locale, userCacheRecord, csvPrinter);
+                outputRecordRow(config, locale, userCacheRecord, csvPrinter, columnFilter);
             }
         } finally {
             if (cacheBeanIterator != null) {
@@ -541,7 +560,8 @@ public class ReportService implements PwmService {
             final Configuration config,
             final Locale locale,
             final UserCacheRecord userCacheRecord,
-            final CSVPrinter csvPrinter
+            final CSVPrinter csvPrinter,
+            final ReportColumnFilter columnFilter
     )
             throws IOException
     {
@@ -549,32 +569,45 @@ public class ReportService implements PwmService {
         final String falseField = Display.getLocalizedMessage(locale, Display.Value_False, config);
         final String naField = Display.getLocalizedMessage(locale, Display.Value_NotApplicable, config);
         final List<String> csvRow = new ArrayList<>();
-        csvRow.add(userCacheRecord.getUserDN());
-        csvRow.add(userCacheRecord.getLdapProfile());
-        csvRow.add(userCacheRecord.getUsername());
-        csvRow.add(userCacheRecord.getEmail());
-        csvRow.add(userCacheRecord.getUserGUID());
-        csvRow.add(userCacheRecord.getLastLoginTime() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format(
-                userCacheRecord.getLastLoginTime()));
-        csvRow.add(userCacheRecord.getPasswordExpirationTime() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format(
-                userCacheRecord.getPasswordExpirationTime()));
-        csvRow.add(userCacheRecord.getPasswordChangeTime() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format(
-                userCacheRecord.getPasswordChangeTime()));
-        csvRow.add(userCacheRecord.getResponseSetTime() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format(
-                userCacheRecord.getResponseSetTime()));
-        csvRow.add(userCacheRecord.isHasResponses() ? trueField : falseField);
-        csvRow.add(userCacheRecord.isHasHelpdeskResponses() ? trueField : falseField);
-        csvRow.add(userCacheRecord.getResponseStorageMethod() == null ? naField : userCacheRecord.getResponseStorageMethod().toString());
-        csvRow.add(userCacheRecord.getResponseFormatType() == null ? naField : userCacheRecord.getResponseFormatType().toString());
-        csvRow.add(userCacheRecord.getPasswordStatus().isExpired() ? trueField : falseField);
-        csvRow.add(userCacheRecord.getPasswordStatus().isPreExpired() ? trueField : falseField);
-        csvRow.add(userCacheRecord.getPasswordStatus().isViolatesPolicy() ? trueField : falseField);
-        csvRow.add(userCacheRecord.getPasswordStatus().isWarnPeriod() ? trueField : falseField);
-        csvRow.add(userCacheRecord.isRequiresPasswordUpdate() ? trueField : falseField);
-        csvRow.add(userCacheRecord.isRequiresResponseUpdate() ? trueField : falseField);
-        csvRow.add(userCacheRecord.isRequiresProfileUpdate() ? trueField : falseField);
-        csvRow.add(userCacheRecord.getCacheTimestamp() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format(
-                userCacheRecord.getCacheTimestamp()));
+        if (columnFilter.isUsernameVisible())                     csvRow.add(userCacheRecord.getUsername());
+        if (columnFilter.isUserDnVisible())                       csvRow.add(userCacheRecord.getUserDN());
+        if (columnFilter.isLdapProfileVisible())                  csvRow.add(userCacheRecord.getLdapProfile());
+        if (columnFilter.isEmailVisible())                        csvRow.add(userCacheRecord.getEmail());
+        if (columnFilter.isUserGuidVisible())                     csvRow.add(userCacheRecord.getUserGUID());
+        if (columnFilter.isAccountExpirationTimeVisible())        csvRow.add(userCacheRecord.getAccountExpirationTime() == null ? naField : 
+            PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getAccountExpirationTime()));
+
+        if (columnFilter.isPasswordExpirationTimeVisible())       csvRow.add(userCacheRecord.getPasswordExpirationTime() == null ? naField : 
+            PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getPasswordExpirationTime()));
+
+        if (columnFilter.isPasswordChangeTimeVisible())           csvRow.add(userCacheRecord.getPasswordChangeTime() == null ? naField : 
+            PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getPasswordChangeTime()));
+
+        if (columnFilter.isResponseSetTimeVisible())              csvRow.add(userCacheRecord.getResponseSetTime() == null ? naField : 
+            PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getResponseSetTime()));
+
+        if (columnFilter.isLastLoginTimeVisible())                csvRow.add(userCacheRecord.getLastLoginTime() == null ? naField : 
+            PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getLastLoginTime()));
+
+        if (columnFilter.isHasResponsesVisible())                 csvRow.add(userCacheRecord.isHasResponses() ? trueField : falseField);
+        if (columnFilter.isHasHelpdeskResponsesVisible())         csvRow.add(userCacheRecord.isHasHelpdeskResponses() ? trueField : falseField);
+
+        if (columnFilter.isResponseStorageMethodVisible())        csvRow.add(userCacheRecord.getResponseStorageMethod() == null ? naField : 
+            userCacheRecord.getResponseStorageMethod().toString());
+
+        if (columnFilter.isResponseFormatTypeVisible())           csvRow.add(userCacheRecord.getResponseFormatType() == null ? naField : 
+            userCacheRecord.getResponseFormatType().toString());
+
+        if (columnFilter.isPasswordStatusExpiredVisible())        csvRow.add(userCacheRecord.getPasswordStatus().isExpired() ? trueField : falseField);
+        if (columnFilter.isPasswordStatusPreExpiredVisible())     csvRow.add(userCacheRecord.getPasswordStatus().isPreExpired() ? trueField : falseField);
+        if (columnFilter.isPasswordStatusViolatesPolicyVisible()) csvRow.add(userCacheRecord.getPasswordStatus().isViolatesPolicy() ? trueField : falseField);
+        if (columnFilter.isPasswordStatusWarnPeriodVisible())     csvRow.add(userCacheRecord.getPasswordStatus().isWarnPeriod() ? trueField : falseField);
+        if (columnFilter.isRequiresPasswordUpdateVisible())       csvRow.add(userCacheRecord.isRequiresPasswordUpdate() ? trueField : falseField);
+        if (columnFilter.isRequiresResponseUpdateVisible())       csvRow.add(userCacheRecord.isRequiresResponseUpdate() ? trueField : falseField);
+        if (columnFilter.isRequiresProfileUpdateVisible())        csvRow.add(userCacheRecord.isRequiresProfileUpdate() ? trueField : falseField);
+
+        if (columnFilter.isCacheTimestampVisible())               csvRow.add(userCacheRecord.getCacheTimestamp() == null ? naField : 
+            PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getCacheTimestamp()));
 
         csvPrinter.printRecord(csvRow);
     }

+ 45 - 0
src/main/java/password/pwm/util/reports/ReportUtils.java

@@ -0,0 +1,45 @@
+package password.pwm.util.reports;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.h2.util.StringUtils;
+
+import password.pwm.svc.report.ReportColumnFilter;
+
+public class ReportUtils {
+    public static ReportColumnFilter toReportColumnFilter(String desiredColumnsStr) {
+        ReportColumnFilter reportColumnFilter = new ReportColumnFilter();
+
+        if (desiredColumnsStr != null) {
+            String[] selectedColumnsArray = StringUtils.arraySplit(desiredColumnsStr, ',', true);
+            Set<String> desiredColumns = new HashSet<String>(Arrays.asList(selectedColumnsArray));
+
+            reportColumnFilter.setUserDnVisible(desiredColumns.contains("userDN"));
+            reportColumnFilter.setLdapProfileVisible(desiredColumns.contains("ldapProfile"));
+            reportColumnFilter.setUsernameVisible(desiredColumns.contains("username"));
+            reportColumnFilter.setEmailVisible(desiredColumns.contains("email"));
+            reportColumnFilter.setUserGuidVisible(desiredColumns.contains("userGUID"));
+            reportColumnFilter.setAccountExpirationTimeVisible(desiredColumns.contains("accountExpirationTime"));
+            reportColumnFilter.setLastLoginTimeVisible(desiredColumns.contains("lastLoginTime"));
+            reportColumnFilter.setPasswordExpirationTimeVisible(desiredColumns.contains("passwordExpirationTime"));
+            reportColumnFilter.setPasswordChangeTimeVisible(desiredColumns.contains("passwordChangeTime"));
+            reportColumnFilter.setResponseSetTimeVisible(desiredColumns.contains("responseSetTime"));
+            reportColumnFilter.setHasResponsesVisible(desiredColumns.contains("hasResponses"));
+            reportColumnFilter.setHasHelpdeskResponsesVisible(desiredColumns.contains("hasHelpdeskResponses"));
+            reportColumnFilter.setResponseStorageMethodVisible(desiredColumns.contains("responseStorageMethod"));
+            reportColumnFilter.setResponseFormatTypeVisible(desiredColumns.contains("responseFormatType"));
+            reportColumnFilter.setPasswordStatusExpiredVisible(desiredColumns.contains("passwordStatusExpired"));
+            reportColumnFilter.setPasswordStatusPreExpiredVisible(desiredColumns.contains("passwordStatusPreExpired"));
+            reportColumnFilter.setPasswordStatusViolatesPolicyVisible(desiredColumns.contains("passwordStatusViolatesPolicy"));
+            reportColumnFilter.setPasswordStatusWarnPeriodVisible(desiredColumns.contains("passwordStatusWarnPeriod"));
+            reportColumnFilter.setRequiresPasswordUpdateVisible(desiredColumns.contains("requiresPasswordUpdate"));
+            reportColumnFilter.setRequiresResponseUpdateVisible(desiredColumns.contains("requiresResponseUpdate"));
+            reportColumnFilter.setRequiresProfileUpdateVisible(desiredColumns.contains("requiresProfileUpdate"));
+            reportColumnFilter.setCacheTimestampVisible(desiredColumns.contains("cacheTimestamp"));
+        }
+
+        return reportColumnFilter;
+    }
+}

+ 4 - 1
src/main/webapp/WEB-INF/jsp/admin-analysis.jsp

@@ -122,7 +122,7 @@
                             <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-refresh">&nbsp;</span></pwm:if>
                             <pwm:display key="Button_Refresh" bundle="Admin"/>
                         </button>
-                        <form action="<pwm:current-url/>" method="post">
+                        <form id="downloadUserReportCsvForm" action="<pwm:current-url/>" method="post">
                             <button type="submit" class="btn" id="button-downloadUserReportCsv">
                                 <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-download">&nbsp;</span></pwm:if>
                                 <pwm:display key="Button_DownloadCSV" bundle="Admin"/>
@@ -135,11 +135,14 @@
                                             text: '<pwm:display key="Tooltip_DownloadReportRecords" bundle="Admin"/>',
                                             width: 350
                                         });
+
+                                        PWM_ADMIN.initDownloadUserReportCsvForm();
                                     });
                                 </script>
                             </pwm:script>
                             <input type="hidden" name="processAction" value="downloadUserReportCsv"/>
                             <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
+                            <input type="hidden" name="selectedColumns" value="" />
                         </form>
                     </div>
                     <pwm:script>

+ 40 - 1
src/main/webapp/public/resources/js/admin.js

@@ -112,6 +112,7 @@ PWM_ADMIN.reportDataHeaders = function() {
         "username":PWM_ADMIN.showString("Field_Report_Username"),
         "userDN":PWM_ADMIN.showString("Field_Report_UserDN"),
         "ldapProfile":PWM_ADMIN.showString("Field_Report_LDAP_Profile"),
+        "email":PWM_ADMIN.showString("Field_Report_Email"),
         "userGUID":PWM_ADMIN.showString("Field_Report_UserGuid"),
         "accountExpirationTime":PWM_ADMIN.showString("Field_Report_AccountExpireTime"),
         "passwordExpirationTime":PWM_ADMIN.showString("Field_Report_PwdExpireTime"),
@@ -122,6 +123,10 @@ PWM_ADMIN.reportDataHeaders = function() {
         "hasHelpdeskResponses":PWM_ADMIN.showString("Field_Report_HasHelpdeskResponses"),
         "responseStorageMethod":PWM_ADMIN.showString("Field_Report_ResponseStorageMethod"),
         "responseFormatType":PWM_ADMIN.showString("Field_Report_ResponseFormatType"),
+        "passwordStatusExpired":PWM_ADMIN.showString("Field_Report_PwdExpired"),
+        "passwordStatusPreExpired":PWM_ADMIN.showString("Field_Report_PwdPreExpired"),
+        "passwordStatusViolatesPolicy":PWM_ADMIN.showString("Field_Report_PwdViolatesPolicy"),
+        "passwordStatusWarnPeriod":PWM_ADMIN.showString("Field_Report_PwdWarnPeriod"),
         "requiresPasswordUpdate":PWM_ADMIN.showString("Field_Report_RequiresPasswordUpdate"),
         "requiresResponseUpdate":PWM_ADMIN.showString("Field_Report_RequiresResponseUpdate"),
         "requiresProfileUpdate":PWM_ADMIN.showString("Field_Report_RequiresProfileUpdate"),
@@ -145,11 +150,16 @@ PWM_ADMIN.initReportDataGrid=function() {
             // unclick superfluous fields
             PWM_MAIN.getObject('grid-hider-menu-check-cacheTimestamp').click();
             PWM_MAIN.getObject('grid-hider-menu-check-ldapProfile').click();
+            PWM_MAIN.getObject('grid-hider-menu-check-email').click();
             PWM_MAIN.getObject('grid-hider-menu-check-userGUID').click();
             PWM_MAIN.getObject('grid-hider-menu-check-responseStorageMethod').click();
             PWM_MAIN.getObject('grid-hider-menu-check-responseFormatType').click();
             PWM_MAIN.getObject('grid-hider-menu-check-userDN').click();
             PWM_MAIN.getObject('grid-hider-menu-check-hasHelpdeskResponses').click();
+            PWM_MAIN.getObject('grid-hider-menu-check-passwordStatusExpired').click();
+            PWM_MAIN.getObject('grid-hider-menu-check-passwordStatusPreExpired').click();
+            PWM_MAIN.getObject('grid-hider-menu-check-passwordStatusViolatesPolicy').click();
+            PWM_MAIN.getObject('grid-hider-menu-check-passwordStatusWarnPeriod').click();
 
             PWM_VAR['reportGrid'].on(".dgrid-row:click", function(evt){
                 PWM_ADMIN.detailView(evt, PWM_ADMIN.reportDataHeaders(), PWM_VAR['reportGrid']);
@@ -157,6 +167,21 @@ PWM_ADMIN.initReportDataGrid=function() {
         });
 };
 
+PWM_ADMIN.initDownloadUserReportCsvForm = function() {
+    require(["dojo/on", "dojo/query"], function(on, query) {
+        query("#downloadUserReportCsvForm").on("click", function(e) {
+            var selectedColumns = [];
+
+            query("#grid-hider-menu input:checked").forEach(function(node, index, nodeList) {
+                selectedColumns.push(node.id.replace('grid-hider-menu-check-', ''));
+            });
+
+            console.log("Selected columns: " + selectedColumns);
+            downloadUserReportCsvForm.selectedColumns.value = selectedColumns;
+        });
+    });
+}
+
 PWM_ADMIN.refreshReportDataGrid=function() {
     if (PWM_MAIN.getObject('button-refreshReportDataGrid')) {
         PWM_MAIN.getObject('button-refreshReportDataGrid').disabled = true;
@@ -172,7 +197,21 @@ PWM_ADMIN.refreshReportDataGrid=function() {
             PWM_MAIN.showErrorDialog(data);
             return;
         }
-        PWM_VAR['reportGrid'].renderArray(data['data']['users']);
+
+        var users = data['data']['users'];
+
+        // "Flatten out" the nested properties, so they can be displayed in the grid
+        for (var i = 0, len = users.length; i < len; i++) {
+            var user = users[i];
+            if (user.hasOwnProperty("passwordStatus")) {
+                user["passwordStatusExpired"] = user["passwordStatus"]["expired"];
+                user["passwordStatusPreExpired"] = user["passwordStatus"]["preExpired"];
+                user["passwordStatusViolatesPolicy"] = user["passwordStatus"]["violatesPolicy"];
+                user["passwordStatusWarnPeriod"] = user["passwordStatus"]["warnPeriod"];
+            }
+        }
+
+        PWM_VAR['reportGrid'].renderArray(users);
     };
     PWM_MAIN.ajaxRequest(url,loadFunction,{method:'GET'});
 };

+ 5 - 1
src/main/webapp/public/resources/style.css

@@ -1043,4 +1043,8 @@ dialog .closeIcon { float: right; cursor: pointer; margin-right: 3px; }
 
 .something {
     display: none;
-}
+}
+
+#grid-hider-menu {
+    width: 200px;
+}

+ 130 - 0
src/test/java/password/pwm/svc/report/ReportServiceTest.java

@@ -0,0 +1,130 @@
+package password.pwm.svc.report;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mortbay.io.WriterOutputStream;
+
+import com.google.gson.reflect.TypeToken;
+
+import password.pwm.config.Configuration;
+import password.pwm.svc.report.ReportService.RecordIterator;
+import password.pwm.util.JsonUtil;
+
+public class ReportServiceTest {
+    private static final Type USER_CACHE_RECORD_LIST_TYPE = new TypeToken<ArrayList<UserCacheRecord>>(){}.getType();
+
+    private Configuration configuration;
+    private ReportService reportService;
+
+    @Before
+    public void setUp() throws Exception {
+        configuration = mock(Configuration.class);
+
+        // Call the real ReportService.outputToCsv() method, but mock the ReportService.iterator() method so it loads the user records from a json file.
+        reportService = mock(ReportService.class);
+        doCallRealMethod().when(reportService).outputToCsv(any(OutputStream.class), anyBoolean(), any(Locale.class), any(Configuration.class), any(ReportColumnFilter.class));
+        doAnswer(new Answer<RecordIterator<UserCacheRecord>>() {
+            @Override
+            public RecordIterator<UserCacheRecord> answer(InvocationOnMock invocation) throws Throwable {
+                final String userRecordsJson = IOUtils.toString(getClass().getResourceAsStream("allUserRecords.json"));
+                final List<UserCacheRecord> userCacheRecords = JsonUtil.deserialize(userRecordsJson, USER_CACHE_RECORD_LIST_TYPE);
+                final Iterator<UserCacheRecord> userCacheRecordIterator = userCacheRecords.iterator();
+
+                RecordIterator<UserCacheRecord> recordIterator = new RecordIterator<UserCacheRecord>() {
+                    @Override
+                    public void close() {}
+
+                    @Override
+                    public void remove() {
+                        userCacheRecordIterator.remove();
+                    }
+
+                    @Override
+                    public boolean hasNext() {
+                        return userCacheRecordIterator.hasNext();
+                    }
+
+                    @Override
+                    public UserCacheRecord next() {
+                        return userCacheRecordIterator.next();
+                    }
+                };
+
+                return recordIterator;
+            }
+        }).when(reportService).iterator();
+    }
+
+    @Test
+    public void testOutputToCsv_normal() throws Exception {
+        // Set the desired filters
+        ReportColumnFilter columnFilter = new ReportColumnFilter();
+        setAllTrue(columnFilter);
+
+        // Test the reportService.outputToCsv method()
+        StringWriter outputWriter = new StringWriter();
+        reportService.outputToCsv(new WriterOutputStream(outputWriter), true, Locale.ENGLISH, configuration, columnFilter);
+
+        // Verify the results
+        String actual = outputWriter.toString();
+        String expected = IOUtils.toString(getClass().getResourceAsStream("allUserRecordsReport.csv"));
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testOutputToCsv_noUserDnColumn() throws Exception {
+        // Set the desired filters
+        ReportColumnFilter columnFilter = new ReportColumnFilter();
+        setAllTrue(columnFilter);
+        columnFilter.setUserDnVisible(false);
+
+        // Test the reportService.outputToCsv method()
+        StringWriter outputWriter = new StringWriter();
+        reportService.outputToCsv(new WriterOutputStream(outputWriter), true, Locale.ENGLISH, configuration, columnFilter);
+
+        // Verify the results
+        String actual = outputWriter.toString();
+        String expected = IOUtils.toString(getClass().getResourceAsStream("allUserRecordsReport-noUserDnColumn.csv"));
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testOutputToCsv_onlyUserDnColumn() throws Exception {
+        // Set the desired filters
+        ReportColumnFilter columnFilter = new ReportColumnFilter();
+        columnFilter.setUserDnVisible(true);
+
+        // Test the reportService.outputToCsv method()
+        StringWriter outputWriter = new StringWriter();
+        reportService.outputToCsv(new WriterOutputStream(outputWriter), true, Locale.ENGLISH, configuration, columnFilter);
+
+        // Verify the results
+        String actual = outputWriter.toString();
+        String expected = IOUtils.toString(getClass().getResourceAsStream("allUserRecordsReport-onlyUserDnColumn.csv"));
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    private void setAllTrue(ReportColumnFilter columnFilter) throws Exception {
+        for (Method method : columnFilter.getClass().getDeclaredMethods()) {
+            if (method.getName().startsWith("set")) {
+                method.invoke(columnFilter, true);
+            }
+        }
+    }
+}

+ 73 - 0
src/test/java/password/pwm/util/reports/ReportUtilsTest.java

@@ -0,0 +1,73 @@
+package password.pwm.util.reports;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.Test;
+
+import password.pwm.svc.report.ReportColumnFilter;
+
+public class ReportUtilsTest {
+
+    @Test
+    public void testToReportColumnFilter() {
+        String desiredColumns = "username,userDN,ldapProfile,userGUID,accountExpirationTime,passwordExpirationTime,passwordChangeTime,responseSetTime,lastLoginTime,hasResponses,hasHelpdeskResponses,responseStorageMethod,responseFormatType,requiresPasswordUpdate,requiresResponseUpdate,requiresProfileUpdate,cacheTimestamp";
+        ReportColumnFilter reportColumnFilter = ReportUtils.toReportColumnFilter(desiredColumns);
+
+        // Verify the column name string gets translated into a ReportColumnFilter correctly:
+        assertThat(reportColumnFilter.isUserDnVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isLdapProfileVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isUsernameVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isUserGuidVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isAccountExpirationTimeVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isLastLoginTimeVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isPasswordExpirationTimeVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isPasswordChangeTimeVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isResponseSetTimeVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isHasResponsesVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isHasHelpdeskResponsesVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isResponseStorageMethodVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isResponseFormatTypeVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isRequiresPasswordUpdateVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isRequiresResponseUpdateVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isRequiresProfileUpdateVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isCacheTimestampVisible()).isEqualTo(true);
+
+        assertThat(reportColumnFilter.isEmailVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isPasswordStatusExpiredVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isPasswordStatusPreExpiredVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isPasswordStatusViolatesPolicyVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isPasswordStatusWarnPeriodVisible()).isEqualTo(false);
+    }
+
+    @Test
+    public void testToReportColumnFilter2() {
+        String desiredColumns = "username,email,passwordStatusExpired,passwordStatusPreExpired,passwordStatusViolatesPolicy,passwordStatusWarnPeriod";
+        ReportColumnFilter reportColumnFilter = ReportUtils.toReportColumnFilter(desiredColumns);
+
+        // Verify the column name string gets translated into a ReportColumnFilter correctly:
+        assertThat(reportColumnFilter.isUsernameVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isEmailVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isPasswordStatusExpiredVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isPasswordStatusPreExpiredVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isPasswordStatusViolatesPolicyVisible()).isEqualTo(true);
+        assertThat(reportColumnFilter.isPasswordStatusWarnPeriodVisible()).isEqualTo(true);
+
+        assertThat(reportColumnFilter.isUserDnVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isLdapProfileVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isUserGuidVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isAccountExpirationTimeVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isLastLoginTimeVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isPasswordExpirationTimeVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isPasswordChangeTimeVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isResponseSetTimeVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isHasResponsesVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isHasHelpdeskResponsesVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isResponseStorageMethodVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isResponseFormatTypeVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isRequiresPasswordUpdateVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isRequiresResponseUpdateVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isRequiresProfileUpdateVisible()).isEqualTo(false);
+        assertThat(reportColumnFilter.isCacheTimestampVisible()).isEqualTo(false);
+    }
+
+}

+ 49 - 0
src/test/resources/password/pwm/svc/report/allUserRecords.json

@@ -0,0 +1,49 @@
+[{
+    "userDN": "cn=fflintstone,ou=users,o=novell",
+    "ldapProfile": "Fred's LDAP profile",
+    "username": "fflintstone",
+    "cacheTimestamp": "2016-08-30T17:29:46Z",
+    "email": "fflintstone@flintstones.tv",
+    "userGUID": "FRED1234",
+    "lastLoginTime": "2016-08-30T17:01:46Z",
+    "passwordExpirationTime": "2016-08-30T17:02:46Z",
+    "passwordChangeTime": "2016-08-30T17:03:46Z",
+    "responseSetTime": "2016-08-30T17:04:46Z",
+    "hasResponses": true,
+    "hasHelpdeskResponses": false,
+    "responseStorageMethod": "AUTO",
+    "responseFormatType": "TEXT",
+    "passwordStatus": {
+        "expired": true,
+        "preExpired": false,
+        "violatesPolicy": true,
+        "warnPeriod": false
+    },
+    "requiresPasswordUpdate": true,
+    "requiresResponseUpdate": false,
+    "requiresProfileUpdate": true
+}, {
+    "userDN": "cn=brubble,ou=users,o=novell",
+    "ldapProfile": "Barney's LDAP profile",
+    "username": "brubble",
+    "cacheTimestamp": "2016-08-30T18:29:46Z",
+    "email": "fflintstone@flintstones.tv",
+    "userGUID": "BARNEY1234",
+    "lastLoginTime": "2016-08-30T18:01:46Z",
+    "passwordExpirationTime": "2016-08-30T18:02:46Z",
+    "passwordChangeTime": "2016-08-30T18:03:46Z",
+    "responseSetTime": "2016-08-30T18:04:46Z",
+    "hasResponses": false,
+    "hasHelpdeskResponses": true,
+    "responseStorageMethod": "LDAP",
+    "responseFormatType": "HELPDESK",
+    "passwordStatus": {
+        "expired": false,
+        "preExpired": true,
+        "violatesPolicy": false,
+        "warnPeriod": true
+    },
+    "requiresPasswordUpdate": false,
+    "requiresResponseUpdate": true,
+    "requiresProfileUpdate": false
+}]

+ 3 - 0
src/test/resources/password/pwm/svc/report/allUserRecordsReport-noUserDnColumn.csv

@@ -0,0 +1,3 @@
+Username,LDAP Profile,Email,UserGuid,Account Expiration Time,Password Expiration Time,Password Change Time,Response Save Time,Last Login Time,Has Valid Responses,Has Helpdesk Responses,Response Storage Method,Response Format Type,Password Expired,Password Pre-Expired,Password Violates Policy,Password In Warn Period,Requires Password Update,Requires Response Update,Requires Profile Update,Record Cache Time
+fflintstone,Fred's LDAP profile,fflintstone@flintstones.tv,FRED1234,n/a,2016-08-30T17:02:46Z,2016-08-30T17:03:46Z,2016-08-30T17:04:46Z,2016-08-30T17:01:46Z,True,False,AUTO,TEXT,True,False,True,False,True,False,True,2016-08-30T17:29:46Z
+brubble,Barney's LDAP profile,fflintstone@flintstones.tv,BARNEY1234,n/a,2016-08-30T18:02:46Z,2016-08-30T18:03:46Z,2016-08-30T18:04:46Z,2016-08-30T18:01:46Z,False,True,LDAP,HELPDESK,False,True,False,True,False,True,False,2016-08-30T18:29:46Z

+ 3 - 0
src/test/resources/password/pwm/svc/report/allUserRecordsReport-onlyUserDnColumn.csv

@@ -0,0 +1,3 @@
+UserDN
+"cn=fflintstone,ou=users,o=novell"
+"cn=brubble,ou=users,o=novell"

+ 3 - 0
src/test/resources/password/pwm/svc/report/allUserRecordsReport.csv

@@ -0,0 +1,3 @@
+Username,UserDN,LDAP Profile,Email,UserGuid,Account Expiration Time,Password Expiration Time,Password Change Time,Response Save Time,Last Login Time,Has Valid Responses,Has Helpdesk Responses,Response Storage Method,Response Format Type,Password Expired,Password Pre-Expired,Password Violates Policy,Password In Warn Period,Requires Password Update,Requires Response Update,Requires Profile Update,Record Cache Time
+fflintstone,"cn=fflintstone,ou=users,o=novell",Fred's LDAP profile,fflintstone@flintstones.tv,FRED1234,n/a,2016-08-30T17:02:46Z,2016-08-30T17:03:46Z,2016-08-30T17:04:46Z,2016-08-30T17:01:46Z,True,False,AUTO,TEXT,True,False,True,False,True,False,True,2016-08-30T17:29:46Z
+brubble,"cn=brubble,ou=users,o=novell",Barney's LDAP profile,fflintstone@flintstones.tv,BARNEY1234,n/a,2016-08-30T18:02:46Z,2016-08-30T18:03:46Z,2016-08-30T18:04:46Z,2016-08-30T18:01:46Z,False,True,LDAP,HELPDESK,False,True,False,True,False,True,False,2016-08-30T18:29:46Z