Browse Source

Updated report so data from "Download as CSV" matches columns selected on the web page.

This involved updating the javascript on the client side so it sends the list of selected columns to the server side when the "Download as CSV" button is clicked.  It also involved introducing the concept of column filters to the report service.
James Albright 9 years ago
parent
commit
fdc0e43c8e

+ 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;
+}

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

@@ -0,0 +1,125 @@
+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 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