/*
* 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.ws.server.rest;
import com.novell.ldapchai.exception.ChaiUnavailableException;
import password.pwm.AppProperty;
import password.pwm.Permission;
import password.pwm.PwmApplication;
import password.pwm.PwmConstants;
import password.pwm.bean.pub.SessionStateInfoBean;
import password.pwm.config.ActionConfiguration;
import password.pwm.config.Configuration;
import password.pwm.config.FormConfiguration;
import password.pwm.config.PwmSetting;
import password.pwm.config.option.SelectableContextMode;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmOperationalException;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.http.IdleTimeoutCalculator;
import password.pwm.http.PwmRequest;
import password.pwm.http.PwmSession;
import password.pwm.http.PwmURL;
import password.pwm.http.servlet.PwmServletDefinition;
import password.pwm.i18n.Display;
import password.pwm.svc.event.AuditRecord;
import password.pwm.svc.event.HelpdeskAuditRecord;
import password.pwm.svc.event.SystemAuditRecord;
import password.pwm.svc.event.UserAuditRecord;
import password.pwm.svc.intruder.RecordType;
import password.pwm.svc.stats.Statistic;
import password.pwm.util.LocaleHelper;
import password.pwm.util.java.TimeDuration;
import password.pwm.util.logging.PwmLogger;
import password.pwm.util.macro.MacroMachine;
import password.pwm.util.secure.PwmHashAlgorithm;
import password.pwm.util.secure.SecureEngine;
import password.pwm.ws.server.RestRequestBean;
import password.pwm.ws.server.RestResultBean;
import password.pwm.ws.server.RestServerHelper;
import password.pwm.ws.server.ServicePermissions;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TreeMap;
import java.util.TreeSet;
@Path("/app-data")
public class RestAppDataServer extends AbstractRestServer {
private static final PwmLogger LOGGER = PwmLogger.forClass(RestAppDataServer.class);
public static class AppData implements Serializable {
public Map<String,Object> PWM_GLOBAL;
}
@GET
@Produces(MediaType.TEXT_HTML)
public javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException {
return RestServerHelper.doHtmlRedirect();
}
@GET
@Path("/audit")
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
public Response doGetAppAuditData(
@QueryParam("maximum") final int maximum
)
throws ChaiUnavailableException, PwmUnrecoverableException
{
final int max = maximum > 0
? maximum
: 10 * 1000;
final RestRequestBean restRequestBean;
try {
final ServicePermissions servicePermissions = new ServicePermissions();
servicePermissions.setAdminOnly(true);
servicePermissions.setAuthRequired(true);
servicePermissions.setBlockExternal(true);
restRequestBean = RestServerHelper.initializeRestRequest(request, response, servicePermissions, null);
} catch (PwmUnrecoverableException e) {
return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
}
final ArrayList<UserAuditRecord> userRecords = new ArrayList<>();
final ArrayList<HelpdeskAuditRecord> helpdeskRecords = new ArrayList<>();
final ArrayList<SystemAuditRecord> systemRecords = new ArrayList<>();
final Iterator<AuditRecord> iterator = restRequestBean.getPwmApplication().getAuditManager().readVault();
int counter = 0;
while (iterator.hasNext() && counter <= max) {
final AuditRecord loopRecord = iterator.next();
counter++;
if (loopRecord instanceof SystemAuditRecord) {
systemRecords.add((SystemAuditRecord)loopRecord);
} else if (loopRecord instanceof HelpdeskAuditRecord) {
helpdeskRecords.add((HelpdeskAuditRecord)loopRecord);
} else if (loopRecord instanceof UserAuditRecord) {
userRecords.add((UserAuditRecord)loopRecord);
}
}
final HashMap<String,List> outputMap = new HashMap<>();
outputMap.put("user",userRecords);
outputMap.put("helpdesk",helpdeskRecords);
outputMap.put("system",systemRecords);
final RestResultBean restResultBean = new RestResultBean();
restResultBean.setData(outputMap);
LOGGER.debug(restRequestBean.getPwmSession(),"output " + counter + " audit records.");
return restResultBean.asJsonResponse();
}
@GET
@Path("/session")
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
public Response doGetAppSessionData(
@QueryParam("maximum") final int maximum
)
throws ChaiUnavailableException, PwmUnrecoverableException
{
final int max = maximum > 0
? maximum
: 10 * 1000;
final RestRequestBean restRequestBean;
try {
final ServicePermissions servicePermissions = new ServicePermissions();
servicePermissions.setAdminOnly(true);
servicePermissions.setAuthRequired(true);
servicePermissions.setBlockExternal(true);
restRequestBean = RestServerHelper.initializeRestRequest(request, response, servicePermissions, null);
} catch (PwmUnrecoverableException e) {
return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
}
if (!restRequestBean.getPwmSession().getSessionManager().checkPermission(restRequestBean.getPwmApplication(), Permission.PWMADMIN)) {
final ErrorInformation errorInfo = PwmError.ERROR_UNAUTHORIZED.toInfo();
return RestResultBean.fromError(errorInfo, restRequestBean).asJsonResponse();
}
final ArrayList<SessionStateInfoBean> gridData = new ArrayList<>();
int counter = 0;
final Iterator<SessionStateInfoBean> infos = restRequestBean.getPwmApplication().getSessionTrackService().getSessionInfoIterator();
while (counter < max && infos.hasNext()) {
gridData.add(infos.next());
counter++;
}
final RestResultBean restResultBean = new RestResultBean();
restResultBean.setData(gridData);
return restResultBean.asJsonResponse();
}
@GET
@Path("/intruder")
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
public Response doGetAppIntruderData(
@QueryParam("maximum") final int maximum
)
throws ChaiUnavailableException, PwmUnrecoverableException
{
final int max = maximum > 0
? maximum
: 10 * 1000;
final RestRequestBean restRequestBean;
try {
final ServicePermissions servicePermissions = new ServicePermissions();
servicePermissions.setAdminOnly(true);
servicePermissions.setAuthRequired(true);
servicePermissions.setBlockExternal(true);
restRequestBean = RestServerHelper.initializeRestRequest(request, response, servicePermissions, null);
} catch (PwmUnrecoverableException e) {
return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
}
if (!restRequestBean.getPwmSession().getSessionManager().checkPermission(restRequestBean.getPwmApplication(), Permission.PWMADMIN)) {
final ErrorInformation errorInfo = PwmError.ERROR_UNAUTHORIZED.toInfo();
return RestResultBean.fromError(errorInfo, restRequestBean).asJsonResponse();
}
final TreeMap<String,Object> returnData = new TreeMap<>();
try {
for (final RecordType recordType : RecordType.values()) {
returnData.put(recordType.toString(),restRequestBean.getPwmApplication().getIntruderManager().getRecords(recordType, max));
}
} catch (PwmOperationalException e) {
final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_UNKNOWN,e.getMessage());
return RestResultBean.fromError(errorInfo, restRequestBean).asJsonResponse();
}
final RestResultBean restResultBean = new RestResultBean();
restResultBean.setData(returnData);
return restResultBean.asJsonResponse();
}
@GET
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
@Path("/client")
public Response doGetAppClientData(
@QueryParam("pageUrl") final String pageUrl,
@PathParam(value = "eTagUri") final String eTagUri,
@Context final HttpServletRequest request,
@Context final HttpServletResponse response
)
throws PwmUnrecoverableException, IOException, ChaiUnavailableException
{
final int maxCacheAgeSeconds = 60 * 5;
final RestRequestBean restRequestBean;
try {
restRequestBean = RestServerHelper.initializeRestRequest(request, response, ServicePermissions.PUBLIC, null);
} catch (PwmUnrecoverableException e) {
return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
}
final String eTagValue = makeClientEtag(restRequestBean.getPwmApplication(), restRequestBean.getPwmSession(), request);
// check the incoming header;
final String ifNoneMatchValue = request.getHeader("If-None-Match");
if (ifNoneMatchValue != null && ifNoneMatchValue.equals(eTagValue) && eTagValue.equals(eTagUri)) {
return Response.notModified().build();
}
response.setHeader("ETag", eTagValue);
response.setDateHeader("Expires", System.currentTimeMillis() + (maxCacheAgeSeconds * 1000));
response.setHeader("Cache-Control", "public, max-age=" + maxCacheAgeSeconds);
final AppData appData = makeAppData(restRequestBean.getPwmApplication(), restRequestBean.getPwmSession(), request, response, pageUrl);
final RestResultBean restResultBean = new RestResultBean();
restResultBean.setData(appData);
return restResultBean.asJsonResponse();
}
@GET
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
@Path("/strings/{bundle}")
public Response doGetStringData(
@PathParam(value = "bundle") final String bundleName
)
throws PwmUnrecoverableException, IOException, ChaiUnavailableException
{
final int maxCacheAgeSeconds = 60 * 5;
final RestRequestBean restRequestBean;
try {
restRequestBean = RestServerHelper.initializeRestRequest(request, response, ServicePermissions.PUBLIC, null);
} catch (PwmUnrecoverableException e) {
return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
}
final String eTagValue = makeClientEtag(restRequestBean.getPwmApplication(), restRequestBean.getPwmSession(), request);
response.setHeader("ETag",eTagValue);
response.setDateHeader("Expires", System.currentTimeMillis() + (maxCacheAgeSeconds * 1000));
response.setHeader("Cache-Control", "public, max-age=" + maxCacheAgeSeconds);
try {
final LinkedHashMap<String,String> displayData = new LinkedHashMap<>(makeDisplayData(restRequestBean.getPwmApplication(),
restRequestBean.getPwmSession(), bundleName));
final RestResultBean restResultBean = new RestResultBean();
restResultBean.setData(displayData);
return restResultBean.asJsonResponse();
} catch (Exception e) {
final String errorMSg = "error during rest /strings call for bundle " + bundleName + ", error: " + e.getMessage();
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,errorMSg);
return RestResultBean.fromError(errorInformation).asJsonResponse();
}
}
private AppData makeAppData(
final PwmApplication pwmApplication,
final PwmSession pwmSession,
final HttpServletRequest request,
final HttpServletResponse response,
final String pageUrl
)
throws ChaiUnavailableException, PwmUnrecoverableException
{
final AppData appData = new AppData();
appData.PWM_GLOBAL = makeClientData(pwmApplication, pwmSession, request, response, pageUrl);
return appData;
}
private Map<String,String> makeDisplayData(
final PwmApplication pwmApplication,
final PwmSession pwmSession,
final String bundleName
)
{
Class displayClass = LocaleHelper.classForShortName(bundleName);
displayClass = displayClass == null ? Display.class : displayClass;
final Locale userLocale = pwmSession.getSessionStateBean().getLocale();
final Configuration config = pwmApplication.getConfig();
final TreeMap<String,String> displayStrings = new TreeMap<>();
final ResourceBundle bundle = ResourceBundle.getBundle(displayClass.getName());
try {
final MacroMachine macroMachine = pwmSession.getSessionManager().getMacroMachine(pwmApplication);
for (final String key : new TreeSet<>(Collections.list(bundle.getKeys()))) {
String displayValue = LocaleHelper.getLocalizedMessage(userLocale, key, config, displayClass);
displayValue = macroMachine.expandMacros(displayValue);
displayStrings.put(key, displayValue);
}
} catch (Exception e) {
LOGGER.error(pwmSession,"error expanding macro display value: " + e.getMessage());
}
return displayStrings;
}
private static Map<String,Object> makeClientData(
final PwmApplication pwmApplication,
final PwmSession pwmSession,
final HttpServletRequest request,
final HttpServletResponse response,
final String pageUrl
)
throws ChaiUnavailableException, PwmUnrecoverableException
{
final Configuration config = pwmApplication.getConfig();
final TreeMap<String,Object> settingMap = new TreeMap<>();
settingMap.put("client.ajaxTypingTimeout", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_AJAX_TYPING_TIMEOUT)));
settingMap.put("client.ajaxTypingWait", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_AJAX_TYPING_WAIT)));
settingMap.put("client.activityMaxEpsRate", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_ACTIVITY_MAX_EPS_RATE)));
settingMap.put("client.js.enableHtml5Dialog", Boolean.parseBoolean(config.readAppProperty(AppProperty.CLIENT_JS_ENABLE_HTML5DIALOG)));
settingMap.put("client.pwShowRevertTimeout", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_PW_SHOW_REVERT_TIMEOUT)));
settingMap.put("enableIdleTimeout", config.readSettingAsBoolean(PwmSetting.DISPLAY_IDLE_TIMEOUT));
settingMap.put("pageLeaveNotice", config.readSettingAsLong(PwmSetting.SECURITY_PAGE_LEAVE_NOTICE_TIMEOUT));
settingMap.put("setting-showHidePasswordFields",pwmApplication.getConfig().readSettingAsBoolean(password.pwm.config.PwmSetting.DISPLAY_SHOW_HIDE_PASSWORD_FIELDS));
settingMap.put("setting-displayEula",PwmConstants.ENABLE_EULA_DISPLAY);
settingMap.put("setting-showStrengthMeter",config.readSettingAsBoolean(PwmSetting.PASSWORD_SHOW_STRENGTH_METER));
{
long idleSeconds = config.readSettingAsLong(PwmSetting.IDLE_TIMEOUT_SECONDS);
if (pageUrl == null || pageUrl.isEmpty()) {
LOGGER.warn(pwmSession, "request to /client data did not incliude pageUrl");
} else {
try {
final PwmURL pwmURL = new PwmURL(new URI(pageUrl), request.getContextPath());
final TimeDuration maxIdleTime = IdleTimeoutCalculator.idleTimeoutForRequest(pwmURL, pwmApplication, pwmSession);
idleSeconds = maxIdleTime.getTotalSeconds();
} catch (Exception e) {
LOGGER.error(pwmSession, "error determining idle timeout time for request: " + e.getMessage());
}
}
settingMap.put("MaxInactiveInterval", idleSeconds);
}
settingMap.put("paramName.locale", config.readAppProperty(AppProperty.HTTP_PARAM_NAME_LOCALE));
settingMap.put("startupTime",pwmApplication.getStartupTime());
settingMap.put("applicationMode",pwmApplication.getApplicationMode());
final String contextPath = request.getContextPath();
settingMap.put("url-context", contextPath);
settingMap.put("url-logout", contextPath + PwmServletDefinition.Logout.servletUrl());
settingMap.put("url-command", contextPath + PwmServletDefinition.PublicCommand.servletUrl());
settingMap.put("url-resources", contextPath + "/public/resources" + pwmApplication.getResourceServletService().getResourceNonce());
settingMap.put("url-restservice", contextPath + "/public/rest");
{
String passwordGuideText = pwmApplication.getConfig().readSettingAsLocalizedString(PwmSetting.DISPLAY_PASSWORD_GUIDE_TEXT,pwmSession.getSessionStateBean().getLocale());
final MacroMachine macroMachine = pwmSession.getSessionManager().getMacroMachine(pwmApplication);
passwordGuideText = macroMachine.expandMacros(passwordGuideText);
settingMap.put("passwordGuideText",passwordGuideText);
}
{
final List<String> formTypeOptions = new ArrayList<>();
for (final FormConfiguration.Type type : FormConfiguration.Type.values()) {
formTypeOptions.add(type.toString());
}
settingMap.put("formTypeOptions",formTypeOptions);
}
{
final List<String> actionTypeOptions = new ArrayList<>();
for (final ActionConfiguration.Type type : ActionConfiguration.Type.values()) {
actionTypeOptions.add(type.toString());
}
settingMap.put("actionTypeOptions",actionTypeOptions);
}
{
final List<String> epsTypes = new ArrayList<>();
for (final Statistic.EpsType loopEpsType : Statistic.EpsType.values()) {
epsTypes.add(loopEpsType.toString());
}
settingMap.put("epsTypes",epsTypes);
}
{
final List<String> epsDurations = new ArrayList<>();
for (final Statistic.EpsDuration loopEpsDuration : Statistic.EpsDuration.values()) {
epsDurations.add(loopEpsDuration.toString());
}
settingMap.put("epsDurations",epsDurations);
}
{
final Map<String,String> localeInfo = new TreeMap<>();
final Map<String,String> localeDisplayNames = new TreeMap<>();
final Map<String,String> localeFlags = new TreeMap<>();
for (final Locale locale : pwmApplication.getConfig().getKnownLocales()) {
final String flagCode = pwmApplication.getConfig().getKnownLocaleFlagMap().get(locale);
localeFlags.put(locale.toString(),flagCode);
localeInfo.put(locale.toString(),locale.getDisplayName() + " - " + locale.getDisplayLanguage(locale));
localeDisplayNames.put(locale.toString(),locale.getDisplayLanguage());
}
settingMap.put("localeInfo",localeInfo);
settingMap.put("localeDisplayNames",localeDisplayNames);
settingMap.put("localeFlags",localeFlags);
settingMap.put("defaultLocale",PwmConstants.DEFAULT_LOCALE.toString());
}
if (pwmApplication.getConfig().readSettingAsEnum(PwmSetting.LDAP_SELECTABLE_CONTEXT_MODE, SelectableContextMode.class) != SelectableContextMode.NONE) {
final Map<String,Map<String,String>> ldapProfiles = new LinkedHashMap<>();
for (final String ldapProfile : pwmApplication.getConfig().getLdapProfiles().keySet()) {
final Map<String,String> contexts = pwmApplication.getConfig().getLdapProfiles().get(ldapProfile).getSelectableContexts(pwmApplication);
ldapProfiles.put(ldapProfile,contexts);
}
settingMap.put("ldapProfiles",ldapProfiles);
}
return settingMap;
}
public static String makeClientEtag(final PwmRequest pwmRequest)
throws PwmUnrecoverableException
{
return makeClientEtag(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest());
}
public static String makeClientEtag(
final PwmApplication pwmApplication,
final PwmSession pwmSession,
final HttpServletRequest httpServletRequest
)
throws PwmUnrecoverableException
{
final StringBuilder inputString = new StringBuilder();
inputString.append(PwmConstants.BUILD_NUMBER);
inputString.append(pwmApplication.getStartupTime().toEpochMilli());
inputString.append(httpServletRequest.getSession().getMaxInactiveInterval());
inputString.append(pwmApplication.getInstanceNonce());
if (pwmSession.getSessionStateBean().getLocale() != null) {
inputString.append(pwmSession.getSessionStateBean().getLocale());
}
inputString.append(pwmSession.getSessionStateBean().getSessionID());
if (pwmSession.isAuthenticated()) {
inputString.append(pwmSession.getUserInfoBean().getUserGuid());
inputString.append(pwmSession.getLoginInfoBean().getAuthTime());
}
return SecureEngine.hash(inputString.toString(), PwmHashAlgorithm.SHA1).toLowerCase();
}
}