Browse Source

added user debug module in admin servlet

Jason Rivard 8 năm trước cách đây
mục cha
commit
15a85d919d

+ 26 - 0
src/main/java/password/pwm/http/JspUtility.java

@@ -26,8 +26,10 @@ import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.PwmSessionBean;
+import password.pwm.i18n.Display;
 import password.pwm.i18n.PwmDisplayBundle;
 import password.pwm.util.LocaleHelper;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.ServletRequest;
@@ -35,6 +37,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.jsp.PageContext;
 import java.io.Serializable;
+import java.time.Instant;
 import java.util.Locale;
 
 public abstract class JspUtility {
@@ -132,6 +135,29 @@ public abstract class JspUtility {
         return forRequest(pageContext.getRequest());
     }
 
+    public static String freindlyWrite(final PageContext pageContext, final boolean value) {
+        final PwmRequest pwmRequest = forRequest(pageContext.getRequest());
+        return value
+                ? LocaleHelper.getLocalizedMessage(Display.Value_True, pwmRequest)
+                : LocaleHelper.getLocalizedMessage(Display.Value_False, pwmRequest);
+    }
+
+    public static String freindlyWrite(final PageContext pageContext, final String input) {
+        final PwmRequest pwmRequest = forRequest(pageContext.getRequest());
+        if (StringUtil.isEmpty(input)) {
+            return LocaleHelper.getLocalizedMessage(Display.Value_NotApplicable, pwmRequest);
+        }
+        return StringUtil.escapeHtml(input);
+    }
+
+    public static String freindlyWrite(final PageContext pageContext, final Instant instant) {
+        final PwmRequest pwmRequest = forRequest(pageContext.getRequest());
+        if (instant == null) {
+            return LocaleHelper.getLocalizedMessage(Display.Value_NotApplicable, pwmRequest);
+        }
+        return "<span class=\"timestamp\">" + instant.toString() + "</span>";
+    }
+
     public static String localizedString(final PageContext pageContext, final String key, final Class<? extends PwmDisplayBundle> bundleClass, final String... values) {
         final PwmRequest pwmRequest = forRequest(pageContext.getRequest());
         return LocaleHelper.getLocalizedMessage(pwmRequest.getLocale(), key, pwmRequest.getConfig(), bundleClass, values);

+ 2 - 0
src/main/java/password/pwm/http/PwmRequest.java

@@ -323,6 +323,8 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
 
         ShortcutItems,
         NextUrl,
+
+        UserDebugData,
     }
 
     public static class FileUploadItem {

+ 45 - 0
src/main/java/password/pwm/http/bean/AdminBean.java

@@ -0,0 +1,45 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.bean;
+
+
+import password.pwm.config.option.SessionBeanMode;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class AdminBean extends PwmSessionBean {
+    @Override
+    public Type getType()
+    {
+        return Type.AUTHENTICATED;
+    }
+
+    @Override
+    public Set<SessionBeanMode> supportedModes()
+    {
+        return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(SessionBeanMode.LOCAL, SessionBeanMode.CRYPTCOOKIE, SessionBeanMode.CRYPTREQUEST)));
+    }
+}

+ 2 - 1
src/main/java/password/pwm/http/servlet/PwmServletDefinition.java

@@ -26,6 +26,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.ActivateUserBean;
+import password.pwm.http.bean.AdminBean;
 import password.pwm.http.bean.ChangePasswordBean;
 import password.pwm.http.bean.ConfigGuideBean;
 import password.pwm.http.bean.ConfigManagerBean;
@@ -75,7 +76,7 @@ public enum PwmServletDefinition {
     GuestRegistration(password.pwm.http.servlet.GuestRegistrationServlet.class, null),
     SelfDelete(DeleteAccountServlet.class, DeleteAccountBean.class),
 
-    Admin(AdminServlet.class, null),
+    Admin(AdminServlet.class, AdminBean.class),
     ConfigGuide(ConfigGuideServlet.class, ConfigGuideBean.class),
     ConfigEditor(ConfigEditorServlet.class, null),
     ConfigManager(ConfigManagerServlet.class, ConfigManagerBean.class),

+ 75 - 70
src/main/java/password/pwm/http/servlet/admin/AdminServlet.java

@@ -26,20 +26,26 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
+import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.JspUrl;
+import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmURL;
 import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
+import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.report.ReportColumnFilter;
 import password.pwm.svc.report.ReportCsvUtility;
 import password.pwm.svc.report.ReportService;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.reports.ReportUtils;
@@ -62,7 +68,7 @@ import java.util.LinkedHashMap;
                 PwmConstants.URL_PREFIX_PRIVATE + "/admin/Administration",
         }
 )
-public class AdminServlet extends AbstractPwmServlet {
+public class AdminServlet extends ControlledPwmServlet {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(AdminServlet.class);
 
@@ -92,87 +98,47 @@ public class AdminServlet extends AbstractPwmServlet {
         }
     }
 
-    protected AdminAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass()
     {
-        try {
-            return AdminAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+        return AdminAction.class;
+    }
+
+    @Override
+    protected void nextStep(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
+    {
+        forwardToJsp(pwmRequest);
     }
 
-    protected void processAction(final PwmRequest pwmRequest)
-            throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException
+    @Override
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException
     {
         if (!pwmRequest.isAuthenticated()) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_AUTHENTICATION_REQUIRED);
             pwmRequest.respondWithError(errorInformation);
-            return;
+            return ProcessStatus.Halt;
         }
 
         if (!pwmRequest.getPwmSession().getSessionManager().checkPermission(pwmRequest.getPwmApplication(), Permission.PWMADMIN))  {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED);
             pwmRequest.respondWithError(errorInformation);
-            return;
+            return ProcessStatus.Halt;
         }
-
-        final AdminAction action = readProcessAction(pwmRequest);
-        if (action != null) {
-            switch(action) {
-                case viewLogWindow:
-                    processViewLog(pwmRequest);
-                    return;
-
-                case downloadAuditLogCsv:
-                    downloadAuditLogCsv(pwmRequest);
-                    return;
-
-                case downloadUserReportCsv:
-                    downloadUserReportCsv(pwmRequest);
-                    return;
-
-                case downloadUserSummaryCsv:
-                    downloadUserSummaryCsv(pwmRequest);
-                    return;
-
-                case downloadStatisticsLogCsv:
-                    downloadStatisticsLogCsv(pwmRequest);
-                    return;
-
-                case clearIntruderTable:
-                    processClearIntruderTable(pwmRequest);
-                    return;
-
-                case reportCommand:
-                    processReportCommand(pwmRequest);
-                    return;
-
-                case reportSummary:
-                    processReportSummary(pwmRequest);
-                    return;
-
-                case reportStatus:
-                    processReportStatus(pwmRequest);
-                    return;
-
-                default:
-                    JavaHelper.unhandledSwitchStatement(action);
-            }
-        }
-
-        forwardToJsp(pwmRequest);
+        return ProcessStatus.Continue;
     }
 
-    private void processViewLog(
+    @ActionHandler(action =  "viewLogWindow")
+    private ProcessStatus processViewLog(
             final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException, IOException, ServletException
     {
         pwmRequest.forwardToJsp(JspUrl.ADMIN_LOGVIEW_WINDOW);
+        return ProcessStatus.Halt;
     }
 
-    private void downloadAuditLogCsv(
+    @ActionHandler(action = "downloadAuditLogCsv")
+    private ProcessStatus downloadAuditLogCsv(
             final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
@@ -190,9 +156,11 @@ public class AdminServlet extends AbstractPwmServlet {
         } finally {
             outputStream.close();
         }
+        return ProcessStatus.Halt;
     }
 
-    private void downloadUserReportCsv(
+    @ActionHandler(action = "downloadUserReportCsv")
+    private ProcessStatus downloadUserReportCsv(
             final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
@@ -214,9 +182,11 @@ public class AdminServlet extends AbstractPwmServlet {
         } finally {
             outputStream.close();
         }
+        return ProcessStatus.Halt;
     }
 
-    private void downloadUserSummaryCsv(
+    @ActionHandler(action = "downloadUserSummaryCsv")
+    private ProcessStatus downloadUserSummaryCsv(
             final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
@@ -235,9 +205,11 @@ public class AdminServlet extends AbstractPwmServlet {
         } finally {
             outputStream.close();
         }
+        return ProcessStatus.Halt;
     }
 
-    private void downloadStatisticsLogCsv(final PwmRequest pwmRequest)
+    @ActionHandler(action = "downloadStatisticsLogCsv")
+    private ProcessStatus downloadStatisticsLogCsv(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
@@ -254,25 +226,29 @@ public class AdminServlet extends AbstractPwmServlet {
         } finally {
             outputStream.close();
         }
+        return ProcessStatus.Halt;
     }
 
-    private void processClearIntruderTable(
+    @ActionHandler(action = "clearIntruderTable")
+    private ProcessStatus processClearIntruderTable(
             final PwmRequest pwmRequest
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
         if (!pwmRequest.getPwmSession().getSessionManager().checkPermission(pwmRequest.getPwmApplication(), Permission.PWMADMIN)) {
             LOGGER.info(pwmRequest, "unable to execute clear intruder records");
-            return;
+            return ProcessStatus.Halt;
         }
 
         //pwmApplication.getIntruderManager().clear();
 
         final RestResultBean restResultBean = new RestResultBean();
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-    private void processReportCommand(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException {
+    @ActionHandler(action = "reportCommand")
+    private ProcessStatus processReportCommand(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException {
         final ReportService.ReportCommand reportCommand = JavaHelper.readEnumFromString(
                 ReportService.ReportCommand.class,
                 null,
@@ -284,9 +260,11 @@ public class AdminServlet extends AbstractPwmServlet {
 
         final RestResultBean restResultBean = new RestResultBean();
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-    private void processReportStatus(final PwmRequest pwmRequest)
+    @ActionHandler(action = "reportStatus")
+    private ProcessStatus processReportStatus(final PwmRequest pwmRequest)
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException {
         try {
             final ReportStatusBean returnMap = ReportStatusBean.makeReportStatusData(
@@ -299,9 +277,11 @@ public class AdminServlet extends AbstractPwmServlet {
         } catch (LocalDBException e) {
             throw new PwmUnrecoverableException(e.getErrorInformation());
         }
+        return ProcessStatus.Halt;
     }
 
-    private void processReportSummary(final PwmRequest pwmRequest)
+    @ActionHandler(action = "reportSummary")
+    private ProcessStatus processReportSummary(final PwmRequest pwmRequest)
 
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
@@ -315,13 +295,39 @@ public class AdminServlet extends AbstractPwmServlet {
         final RestResultBean restResultBean = new RestResultBean();
         restResultBean.setData(returnMap);
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
+    private void processDebugUserSearch(final PwmRequest pwmRequest)
+            throws PwmUnrecoverableException
+    {
+        final String username = pwmRequest.readParameterAsString("username");
+        if (StringUtil.isEmpty(username)) {
+            return;
+        }
 
+        final UserSearchEngine userSearchEngine = pwmRequest.getPwmApplication().getUserSearchEngine();
+        final UserIdentity userIdentity;
+        try {
+            userIdentity = userSearchEngine.resolveUsername(username,null,null, pwmRequest.getSessionLabel());
+        } catch (ChaiUnavailableException e) {
+            setLastError(pwmRequest, PwmUnrecoverableException.fromChaiException(e).getErrorInformation());
+            return;
+        } catch (PwmOperationalException e) {
+            setLastError(pwmRequest, e.getErrorInformation());
+            return;
+        }
 
+        final UserDebugDataBean userDebugData = UserDebugDataReader.readUserDebugData(pwmRequest.getPwmApplication(), pwmRequest.getLocale(), pwmRequest.getSessionLabel(), userIdentity);
+        pwmRequest.setAttribute(PwmRequest.Attribute.UserDebugData, userDebugData);
+    }
 
-    public void forwardToJsp(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException {
+    private void forwardToJsp(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException {
         final Page currentPage = Page.forUrl(pwmRequest.getURL());
+        if (currentPage == Page.debugUser) {
+            processDebugUserSearch(pwmRequest);
+        }
+
         if (currentPage != null) {
             pwmRequest.forwardToJsp(currentPage.getJspURL());
             return;
@@ -329,7 +335,6 @@ public class AdminServlet extends AbstractPwmServlet {
         pwmRequest.sendRedirect(pwmRequest.getContextPath() + PwmServletDefinition.Admin.servletUrl() + Page.dashboard.getUrlSuffix());
     }
 
-
     public enum Page {
         dashboard(JspUrl.ADMIN_DASHBOARD,"/dashboard"),
         analysis(JspUrl.ADMIN_ANALYSIS,"/analysis"),

+ 43 - 0
src/main/java/password/pwm/http/servlet/admin/UserDebugDataBean.java

@@ -0,0 +1,43 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.servlet.admin;
+
+import lombok.Builder;
+import lombok.Getter;
+import password.pwm.Permission;
+import password.pwm.bean.UserInfoBean;
+import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.PwmPasswordPolicy;
+
+import java.io.Serializable;
+import java.util.Map;
+
+@Getter
+@Builder
+public class UserDebugDataBean implements Serializable {
+    private final UserInfoBean userInfoBean;
+    private final Map<Permission,String> permissions;
+    private final PwmPasswordPolicy ldapPasswordPolicy;
+    private final PwmPasswordPolicy configuredPasswordPolicy;
+    private final Map<ProfileType,String> profiles;
+}

+ 123 - 0
src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java

@@ -0,0 +1,123 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.servlet.admin;
+
+import password.pwm.Permission;
+import password.pwm.PwmApplication;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
+import password.pwm.bean.UserInfoBean;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.UserPermission;
+import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.ProfileUtility;
+import password.pwm.config.profile.PwmPasswordPolicy;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.LdapPermissionTester;
+import password.pwm.ldap.UserStatusReader;
+import password.pwm.util.operations.PasswordUtility;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+class UserDebugDataReader {
+    static UserDebugDataBean readUserDebugData(
+            final PwmApplication pwmApplication,
+            final Locale locale,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, sessionLabel);
+
+        final UserInfoBean userInfoBean = userStatusReader.populateUserInfoBean(locale, userIdentity);
+
+        final Map<Permission,String> permissions = UserDebugDataReader.permissionMap(pwmApplication, sessionLabel, userIdentity);
+
+        final Map<ProfileType,String> profiles = UserDebugDataReader.profileMap(pwmApplication, sessionLabel, userIdentity);
+
+        final PwmPasswordPolicy ldapPasswordPolicy = PasswordUtility.readLdapPasswordPolicy(pwmApplication, pwmApplication.getProxiedChaiUser(userIdentity));
+
+        final PwmPasswordPolicy configPasswordPolicy = pwmApplication.getConfig().getPasswordPolicy(userIdentity.getLdapProfileID(), locale);
+
+        final UserDebugDataBean userDebugData = UserDebugDataBean.builder()
+                .userInfoBean(userInfoBean)
+                .permissions(permissions)
+                .profiles(profiles)
+                .ldapPasswordPolicy(ldapPasswordPolicy)
+                .configuredPasswordPolicy(configPasswordPolicy)
+                .build();
+
+        return userDebugData;
+    }
+
+
+    private static Map<Permission, String> permissionMap(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity
+
+    )
+            throws PwmUnrecoverableException
+    {
+        final Map<Permission,String> results = new TreeMap<>();
+        for (final Permission permission : Permission.values()) {
+            final PwmSetting setting = permission.getPwmSetting();
+            if (!setting.isHidden() && !setting.getCategory().isHidden() && !setting.getCategory().hasProfiles()) {
+                final List<UserPermission> userPermission = pwmApplication.getConfig().readSettingAsUserPermission(permission.getPwmSetting());
+                final boolean result = LdapPermissionTester.testUserPermissions(
+                        pwmApplication,
+                        sessionLabel,
+                        userIdentity,
+                        userPermission
+                );
+                results.put(permission, result ? Permission.PermissionStatus.GRANTED.toString() : Permission.PermissionStatus.DENIED.toString());
+            }
+
+        }
+        return Collections.unmodifiableMap(results);
+    }
+
+    private static Map<ProfileType,String> profileMap(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity
+    ) throws PwmUnrecoverableException
+    {
+        final Map<ProfileType,String> results = new TreeMap<>();
+        for (final ProfileType profileType : ProfileType.values()) {
+            final String id = ProfileUtility.discoverProfileIDforUser(
+                    pwmApplication,
+                    sessionLabel,
+                    userIdentity,
+                    profileType
+            );
+            results.put(profileType, id);
+        }
+        return Collections.unmodifiableMap(results);
+    }
+}

+ 7 - 2
src/main/java/password/pwm/svc/event/AuditService.java

@@ -334,10 +334,15 @@ public class AuditService implements PwmService {
         // email alert
         sendAsEmail(auditRecord);
 
-        // add to user ldap record
+        // add to user history record
         if (auditRecord instanceof UserAuditRecord) {
             if (settings.getUserStoredEvents().contains(auditRecord.getEventCode())) {
-                userHistoryStore.updateUserHistory((UserAuditRecord) auditRecord);
+                final String perpetratorDN = ((UserAuditRecord) auditRecord).getPerpetratorDN();
+                if (!StringUtil.isEmpty(perpetratorDN)) {
+                    userHistoryStore.updateUserHistory((UserAuditRecord) auditRecord);
+                } else {
+                    LOGGER.trace("skipping update of user history, audit record does not have a perpetratorDN: " + JsonUtil.serialize(auditRecord));
+                }
             }
         }
 

+ 357 - 26
src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp

@@ -20,14 +20,17 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   --%>
 
+<%@ page import="com.novell.ldapchai.cr.Challenge" %>
 <%@ page import="password.pwm.Permission" %>
-<%@ page import="password.pwm.http.JspUtility" %>
-<%@ page import="password.pwm.http.PwmSession" %>
-<%@ page import="password.pwm.http.servlet.CommandServlet" %>
-<%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
-<%@ page import="password.pwm.util.java.JavaHelper" %>
+<%@ page import="password.pwm.bean.ResponseInfoBean" %>
+<%@ page import="password.pwm.bean.UserInfoBean" %>
+<%@ page import="password.pwm.config.profile.ChallengeProfile" %>
+<%@ page import="password.pwm.config.profile.ProfileType" %>
+<%@ page import="password.pwm.config.profile.PwmPasswordPolicy" %>
+<%@ page import="password.pwm.config.profile.PwmPasswordRule" %>
+<%@ page import="password.pwm.http.servlet.admin.UserDebugDataBean" %>
+<%@ page import="java.util.Map" %>
 <% final PwmRequest debug_pwmRequest = JspUtility.getPwmRequest(pageContext); %>
-<% final PwmSession debug_pwmSession = debug_pwmRequest.getPwmSession(); %>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
@@ -36,51 +39,379 @@
 <body class="nihilo">
 <div id="wrapper">
     <jsp:include page="/WEB-INF/jsp/fragment/header-body.jsp">
-        <jsp:param name="pwm.PageName" value="Debug"/>
+        <jsp:param name="pwm.PageName" value="User Debug"/>
     </jsp:include>
-    <div id="centerbody">
-        <div id="page-content-title">Debug</div>
+    <div id="centerbody" class="wide">
+        <div id="page-content-title">User Debug</div>
+        <%@ include file="fragment/admin-nav.jsp" %>
+
+        <% final UserDebugDataBean userDebugDataBean = (UserDebugDataBean)JspUtility.getAttribute(pageContext, PwmRequest.Attribute.UserDebugData); %>
+        <% if (userDebugDataBean == null) { %>
+        <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
+        <div id="panel-searchbar" class="searchbar">
+            <form method="post" class="pwm-form">
+                <input id="username" name="username" placeholder="<pwm:display key="Placeholder_Search"/>" title="<pwm:display key="Placeholder_Search"/>" class="helpdesk-input-username" <pwm:autofocus/> autocomplete="off"/>
+                <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
+                <button type="submit" class="btn"><pwm:display key="Button_Search"/></button>
+            </form>
+        </div>
+
+        <% } else { %>
+        <div class="buttonbar">
+            <form method="get" class="pwm-form">
+                <button type="submit" class="btn"><pwm:display key="Button_Continue"/></button>
+            </form>
+        </div>
+        <% final UserInfoBean userInfoBean = userDebugDataBean.getUserInfoBean(); %>
+        <% if (userInfoBean != null) { %>
         <table>
+            <tr>
+                <td colspan="10" class="title">Identity</td>
+            </tr>
             <tr>
                 <td class="key">UserDN</td>
-                <td><pwm:macro value="@LDAP:dn@"/></td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getUserIdentity().getUserDN())%></td>
             </tr>
             <tr>
                 <td class="key">Ldap Profile</td>
-                <td><%="".equals(debug_pwmSession.getUserInfoBean().getUserIdentity().getLdapProfileID()) ? "default" : debug_pwmSession.getUserInfoBean().getUserIdentity().getLdapProfileID()%></td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getUserIdentity().getLdapProfileID())%></td>
+            </tr>
+            <tr>
+                <td class="key">Username</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getUsername())%></td>
+            </tr>
+            <tr>
+                <td class="key"><%=PwmConstants.PWM_APP_NAME%> GUID</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getUserGuid())%></td>
+            </tr>
+        </table>
+        <br/>
+        <table>
+            <tr>
+                <td colspan="10" class="title">Status</td>
+            </tr>
+            <tr>
+                <td class="key">Last Login Time</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getLastLdapLoginTime())%></td>
+            </tr>
+            <tr>
+                <td class="key">Account Expiration Time</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getAccountExpirationTime())%></td>
+            </tr>
+            <tr>
+                <td class="key">Password Expiration</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getPasswordExpirationTime())%></td>
+            </tr>
+            <tr>
+                <td class="key">Password Last Modified</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getPasswordLastModifiedTime())%></td>
+            </tr>
+            <tr>
+                <td class="key">Email Address</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getUserEmailAddress())%></td>
+            </tr>
+            <tr>
+                <td class="key">Phone Number</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getUserSmsNumber())%></td>
+            </tr>
+            <tr>
+                <td class="key">Username</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userInfoBean.getUsername())%></td>
+            </tr>
+            <tr>
+                <td class="key">
+                    <pwm:display key="Field_PasswordExpired"/>
+                </td>
+                <td id="PasswordExpired">
+                    <%= JspUtility.freindlyWrite(pageContext, userInfoBean.getPasswordState().isExpired()) %>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">
+                    <pwm:display key="Field_PasswordPreExpired"/>
+                </td>
+                <td id="PasswordPreExpired">
+                    <%= JspUtility.freindlyWrite(pageContext, userInfoBean.getPasswordState().isPreExpired()) %>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">
+                    <pwm:display key="Field_PasswordWithinWarningPeriod"/>
+                </td>
+                <td id="PasswordWithinWarningPeriod">
+                    <%= JspUtility.freindlyWrite(pageContext, userInfoBean.getPasswordState().isWarnPeriod()) %>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">
+                    <pwm:display key="Field_PasswordViolatesPolicy"/>
+                </td>
+                <td id="PasswordViolatesPolicy">
+                    <%= JspUtility.freindlyWrite(pageContext, userInfoBean.getPasswordState().isViolatesPolicy()) %>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">
+                    Requires New Password
+                </td>
+                <td>
+                    <%= JspUtility.freindlyWrite(pageContext, userInfoBean.isRequiresNewPassword()) %>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">
+                    Requires Response Setup
+                </td>
+                <td>
+                    <%= JspUtility.freindlyWrite(pageContext, userInfoBean.isRequiresResponseConfig()) %>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">
+                    Requires OTP Setup
+                </td>
+                <td>
+                    <%= JspUtility.freindlyWrite(pageContext, userInfoBean.isRequiresOtpConfig()) %>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">
+                    Requires Profile Update
+                </td>
+                <td>
+                    <%= JspUtility.freindlyWrite(pageContext, userInfoBean.isRequiresUpdateProfile()) %>
+                </td>
+            </tr>
+        </table>
+        <br/>
+        <table>
+            <tr>
+                <td colspan="10" class="title">Applied Configuration</td>
+            </tr>
+            <tr>
+                <td class="key">Profiles</td>
+                <td>
+                    <table>
+                        <tr>
+                            <td class="key">Service</td>
+                            <td class="key">ProfileID</td>
+                        </tr>
+                        <% for (final ProfileType profileType : userDebugDataBean.getProfiles().keySet()) { %>
+                        <tr>
+                            <td><%=profileType%></td>
+                            <td><%=JspUtility.freindlyWrite(pageContext, userDebugDataBean.getProfiles().get(profileType))%></td>
+                        </tr>
+                        <% } %>
+                    </table>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">Permissions</td>
+                <td>
+                    <table>
+                        <tr>
+                            <td class="key">Permission</td>
+                            <td class="key">Status</td>
+                        </tr>
+                        <% for (final Permission permission : userDebugDataBean.getPermissions().keySet()) { %>
+                        <tr>
+                            <td><%=permission%></td>
+                            <td><%=JspUtility.freindlyWrite(pageContext, userDebugDataBean.getPermissions().get(permission))%></td>
+                        </tr>
+                        <% } %>
+                    </table>
+                </td>
+            </tr>
+        </table>
+        <br/>
+        <table>
+            <tr>
+                <td colspan="10" class="title">Password Policy</td>
+            </tr>
+            <% PwmPasswordPolicy userPolicy = userInfoBean.getPasswordPolicy(); %>
+            <% if (userPolicy != null) { %>
+            <% PwmPasswordPolicy configPolicy = userDebugDataBean.getConfiguredPasswordPolicy(); %>
+            <% PwmPasswordPolicy ldapPolicy = userDebugDataBean.getLdapPasswordPolicy(); %>
+            <tr>
+                <td>Policy Name</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userPolicy.getDisplayName(JspUtility.locale(request)))%></td>
+            </tr>
+            <tr>
+                <td>Policy ID</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, userPolicy.getIdentifier())%></td>
+            </tr>
+            <tr>
+                <td colspan="10">
+                    <table>
+                        <tr class="title">
+                            <td class="key">Rule</td>
+                            <td class="key">Rule Type</td>
+                            <td class="key">Configured Policy</td>
+                            <td class="key">LDAP Policy</td>
+                            <td class="key">Effective Policy</td>
+                        </tr>
+                        <% for (final PwmPasswordRule rule : PwmPasswordRule.values()) { %>
+                        <tr>
+                            <td><span title="<%=rule.getKey()%>"><%=rule.getLabel(JspUtility.locale(request), JspUtility.getPwmRequest(pageContext).getConfig())%></span></td>
+                            <td><%=rule.getRuleType()%></td>
+                            <td><%=JspUtility.freindlyWrite(pageContext, configPolicy.getValue(rule))%></td>
+                            <td><%=JspUtility.freindlyWrite(pageContext, ldapPolicy.getValue(rule))%></td>
+                            <td><%=JspUtility.freindlyWrite(pageContext, userPolicy.getValue(rule))%></td>
+                        </tr>
+                        <% } %>
+                    </table>
+                </td>
+            </tr>
+            <% } %>
+        </table>
+        <br/>
+        <table>
+            <tr>
+                <td colspan="10" class="title">Stored Responses</td>
+            </tr>
+            <% final ResponseInfoBean responseInfoBean = userInfoBean.getResponseInfoBean(); %>
+            <% if (responseInfoBean == null) { %>
+            <tr>
+                <td>Stored Responses</td>
+                <td><pwm:display key="Display_NotApplicable"/></td>
+            </tr>
+            <% } else { %>
+            <tr>
+                <td>Identifier</td>
+                <td><%=responseInfoBean.getCsIdentifier()%></td>
+            </tr>
+            <tr>
+                <td>Storage Type</td>
+                <td><%=responseInfoBean.getDataStorageMethod()%></td>
+            </tr>
+            <tr>
+                <td>Format</td>
+                <td><%=responseInfoBean.getFormatType()%></td>
             </tr>
             <tr>
-                <td class="key">AuthType</td>
-                <td><%=debug_pwmSession.getLoginInfoBean().getType()%></td>
+                <td>Locale</td>
+                <td><%=responseInfoBean.getLocale()%></td>
             </tr>
             <tr>
-                <td class="key">Session Creation Time</td>
-                <td><%=JavaHelper.toIsoDate(debug_pwmSession.getSessionStateBean().getSessionCreationTime())%></td>
+                <td>Storage Timestamp</td>
+                <td><%=JspUtility.freindlyWrite(pageContext, responseInfoBean.getTimestamp())%></td>
             </tr>
             <tr>
-                <td class="key">Session ForwardURL</td>
-                <td><%=debug_pwmSession.getSessionStateBean().getForwardURL()%></td>
+                <td>Challenges</td>
+                <% final Map<Challenge,String> crMap = responseInfoBean.getCrMap(); %>
+                <% if (crMap == null) { %>
+                <td>
+                    n/a
+                </td>
+                <% } else { %>
+                <td>
+                    <table>
+                        <tr>
+                            <td class="key">Type</td>
+                            <td class="key">Required</td>
+                            <td class="key">Text</td>
+                        </tr>
+                        <% for (final Challenge challenge : crMap.keySet()) { %>
+                        <tr>
+                            <td>
+                                <%= challenge.isAdminDefined() ? "Admin Defined" : "User Defined" %>
+                            </td>
+                            <td>
+                                <%= JspUtility.freindlyWrite(pageContext, challenge.isRequired())%>
+                            </td>
+                            <td>
+                                <%= JspUtility.freindlyWrite(pageContext, challenge.getChallengeText())%>
+                            </td>
+                        </tr>
+                        <% } %>
+                    </table>
+                </td>
+                <% } %>
             </tr>
             <tr>
-                <td class="key">Session LogoutURL</td>
-                <td><%=debug_pwmSession.getSessionStateBean().getLogoutURL()%></td>
+                <td>
+                    Minimum Randoms Required
+                </td>
+                <td>
+                    <%=responseInfoBean.getMinRandoms()%>
+                </td>
             </tr>
+            <tr>
+                <td>Helpdesk Challenges</td>
+                <% final Map<Challenge,String> helpdeskCrMap = responseInfoBean.getHelpdeskCrMap(); %>
+                <% if (helpdeskCrMap == null) { %>
+                <td>
+                <pwm:display key="Display_NotApplicable"/>
+                </td>
+                <% } else { %>
+                <td>
+                    <% for (final Challenge challenge : helpdeskCrMap.keySet()) { %>
+                    <%= JspUtility.freindlyWrite(pageContext, challenge.getChallengeText())%><br/>
+                    <% } %>
+                </td>
+                <% } %>
+            </tr>
+            <% } %>
         </table>
+        <br/>
         <table>
-            <% for (final Permission permission : Permission.values()) { %>
             <tr>
-                <td class="key"><%=permission.toString()%></td>
-                <td><%=debug_pwmSession.getSessionManager().checkPermission(debug_pwmRequest.getPwmApplication(), permission)%></td>
+                <td colspan="10" class="title">Challenge Profile</td>
+            </tr>
+            <% final ChallengeProfile challengeProfile = userInfoBean.getChallengeProfile(); %>
+            <% if (challengeProfile == null) { %>
+            <tr>
+                <td>Assigned Profile</td>
+                <td><pwm:display key="Display_NotApplicable"/></td>
+            </tr>
+            <% } else { %>
+            <tr>
+                <td>Display Name</td>
+                <td><%=challengeProfile.getDisplayName(JspUtility.locale(request))%></td>
+            </tr>
+            <tr>
+                <td>Identifier</td>
+                <td><%=challengeProfile.getIdentifier()%></td>
+            </tr>
+            <tr>
+                <td>Locale</td>
+                <td><%=challengeProfile.getLocale()%></td>
+            </tr>
+            <tr>
+                <td>Challenges</td>
+                <td>
+                    <table>
+                        <tr>
+                            <td class="key">Type</td>
+                            <td class="key">Required</td>
+                            <td class="key">Text</td>
+                        </tr>
+                        <% for (final Challenge challenge : challengeProfile.getChallengeSet().getChallenges()) { %>
+                        <tr>
+                            <td>
+                                <%= challenge.isAdminDefined() ? "Admin Defined" : "User Defined" %>
+                            </td>
+                            <td>
+                                <%= JspUtility.freindlyWrite(pageContext, challenge.isRequired())%>
+                            </td>
+                            <td>
+                                <%= JspUtility.freindlyWrite(pageContext, challenge.getChallengeText())%>
+                            </td>
+                        </tr>
+                        <% } %>
+                    </table>
+                </td>
             </tr>
             <% } %>
         </table>
+
+        <% } %>
         <div class="buttonbar">
-            <form action="<pwm:url url='<%=PwmServletDefinition.Command.servletUrl()%>' addContext="true"/>" method="post" enctype="application/x-www-form-urlencoded">
-                <input tabindex="2" type="submit" name="continue_btn" class="btn"
-                       value="    <pwm:display key="Button_Continue"/>    "/>
-                <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
+            <form method="get" class="pwm-form">
+                <button type="submit" class="btn"><pwm:display key="Button_Continue"/></button>
             </form>
         </div>
+        <% } %>
     </div>
     <div class="push"></div>
 </div>

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

@@ -44,7 +44,6 @@ PWM_ADMIN.initAdminNavMenu = function() {
                         PWM_MAIN.goto(PWM_GLOBAL['url-context'] + '/private/admin/tokens');
                     }
                 }));
-
                 pMenu.addChild(new MenuItem({
                     label: PWM_ADMIN.showString('Title_URLReference'),
                     id: 'urlReference_dropitem',
@@ -52,6 +51,13 @@ PWM_ADMIN.initAdminNavMenu = function() {
                         PWM_MAIN.goto(PWM_GLOBAL['url-context'] + '/private/admin/urls');
                     }
                 }));
+                pMenu.addChild(new MenuItem({
+                    label: 'User Debug',
+                    id: 'userDebug_dropitem',
+                    onClick: function() {
+                        PWM_MAIN.goto(PWM_GLOBAL['url-context'] + '/private/admin/debug');
+                    }
+                }));
                 pMenu.addChild(new MenuSeparator());
                 pMenu.addChild(new MenuItem({
                     label: 'Full Page Health Status',