ClientApiServlet.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. package password.pwm.http.servlet;
  2. import com.novell.ldapchai.exception.ChaiUnavailableException;
  3. import lombok.Data;
  4. import password.pwm.AppProperty;
  5. import password.pwm.Permission;
  6. import password.pwm.PwmApplication;
  7. import password.pwm.PwmApplicationMode;
  8. import password.pwm.PwmConstants;
  9. import password.pwm.config.Configuration;
  10. import password.pwm.config.PwmSetting;
  11. import password.pwm.config.option.SelectableContextMode;
  12. import password.pwm.config.value.data.ActionConfiguration;
  13. import password.pwm.config.value.data.FormConfiguration;
  14. import password.pwm.error.ErrorInformation;
  15. import password.pwm.error.PwmError;
  16. import password.pwm.error.PwmException;
  17. import password.pwm.error.PwmUnrecoverableException;
  18. import password.pwm.http.HttpHeader;
  19. import password.pwm.http.HttpMethod;
  20. import password.pwm.http.IdleTimeoutCalculator;
  21. import password.pwm.http.ProcessStatus;
  22. import password.pwm.http.PwmHttpRequestWrapper;
  23. import password.pwm.http.PwmRequest;
  24. import password.pwm.http.PwmSession;
  25. import password.pwm.http.PwmURL;
  26. import password.pwm.i18n.Display;
  27. import password.pwm.svc.stats.EpsStatistic;
  28. import password.pwm.svc.stats.Statistic;
  29. import password.pwm.util.LocaleHelper;
  30. import password.pwm.util.java.TimeDuration;
  31. import password.pwm.util.logging.PwmLogger;
  32. import password.pwm.util.macro.MacroMachine;
  33. import password.pwm.util.secure.PwmHashAlgorithm;
  34. import password.pwm.util.secure.SecureEngine;
  35. import password.pwm.ws.server.RestResultBean;
  36. import password.pwm.ws.server.rest.RestHealthServer;
  37. import password.pwm.ws.server.rest.bean.HealthData;
  38. import javax.servlet.ServletException;
  39. import javax.servlet.annotation.WebServlet;
  40. import javax.servlet.http.HttpServletRequest;
  41. import javax.servlet.http.HttpServletResponse;
  42. import java.io.IOException;
  43. import java.io.Serializable;
  44. import java.net.URI;
  45. import java.time.Instant;
  46. import java.util.ArrayList;
  47. import java.util.Collection;
  48. import java.util.Collections;
  49. import java.util.LinkedHashMap;
  50. import java.util.List;
  51. import java.util.Locale;
  52. import java.util.Map;
  53. import java.util.ResourceBundle;
  54. import java.util.TreeMap;
  55. import java.util.TreeSet;
  56. @WebServlet(
  57. name="ClientApiServlet",
  58. urlPatterns = {
  59. PwmConstants.URL_PREFIX_PUBLIC + "/api",
  60. }
  61. )
  62. public class ClientApiServlet extends ControlledPwmServlet {
  63. private static final PwmLogger LOGGER = PwmLogger.forClass(ClientApiServlet.class);
  64. @Data
  65. public static class AppData implements Serializable {
  66. public Map<String,Object> PWM_GLOBAL;
  67. }
  68. @Data
  69. public static class PingResponse implements Serializable {
  70. private Instant time;
  71. private String runtimeNonce;
  72. }
  73. public enum ClientApiAction implements AbstractPwmServlet.ProcessAction {
  74. clientData(HttpMethod.GET),
  75. strings(HttpMethod.GET),
  76. health(HttpMethod.GET),
  77. ping(HttpMethod.GET),
  78. ;
  79. private final HttpMethod method;
  80. ClientApiAction(final HttpMethod method)
  81. {
  82. this.method = method;
  83. }
  84. public Collection<HttpMethod> permittedMethods()
  85. {
  86. return Collections.singletonList(method);
  87. }
  88. }
  89. @Override
  90. public Class<? extends ProcessAction> getProcessActionsClass() {
  91. return ClientApiServlet.ClientApiAction.class;
  92. }
  93. @Override
  94. protected void nextStep(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException {
  95. // no mvc pattern in this servlet
  96. }
  97. @Override
  98. public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
  99. return ProcessStatus.Continue;
  100. }
  101. @ActionHandler(action = "clientData")
  102. public ProcessStatus processRestClientData(final PwmRequest pwmRequest)
  103. throws PwmUnrecoverableException, IOException, ChaiUnavailableException
  104. {
  105. final String pageUrl = pwmRequest.readParameterAsString("pageUrl", PwmHttpRequestWrapper.Flag.BypassValidation);
  106. final String etagParam = pwmRequest.readParameterAsString("etag", PwmHttpRequestWrapper.Flag.BypassValidation);
  107. final int maxCacheAgeSeconds = 60 * 5;
  108. final String eTagValue = makeClientEtag(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest());
  109. // check the incoming header;
  110. final String ifNoneMatchValue = pwmRequest.readHeaderValueAsString("If-None-Match");
  111. if (ifNoneMatchValue != null && ifNoneMatchValue.equals(eTagValue) && eTagValue.equals(etagParam)) {
  112. pwmRequest.getPwmResponse().setStatus(304);
  113. return ProcessStatus.Halt;
  114. }
  115. pwmRequest.getPwmResponse().setHeader(HttpHeader.ETag, eTagValue);
  116. pwmRequest.getPwmResponse().setHeader(HttpHeader.Expires, String.valueOf(System.currentTimeMillis() + (maxCacheAgeSeconds * 1000)));
  117. pwmRequest.getPwmResponse().setHeader(HttpHeader.Cache_Control, "public, max-age=" + maxCacheAgeSeconds);
  118. final AppData appData = makeAppData(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest(), pwmRequest.getPwmResponse().getHttpServletResponse(), pageUrl);
  119. final RestResultBean restResultBean = new RestResultBean();
  120. restResultBean.setData(appData);
  121. pwmRequest.outputJsonResult(restResultBean);
  122. return ProcessStatus.Halt;
  123. }
  124. @ActionHandler(action = "strings")
  125. public ProcessStatus doGetStringsData(final PwmRequest pwmRequest
  126. )
  127. throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException {
  128. final String bundleName = pwmRequest.readParameterAsString("bundle");
  129. final int maxCacheAgeSeconds = 60 * 5;
  130. final String eTagValue = makeClientEtag(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest());
  131. pwmRequest.getPwmResponse().setHeader(HttpHeader.ETag, eTagValue);
  132. pwmRequest.getPwmResponse().setHeader(HttpHeader.Expires, String.valueOf(System.currentTimeMillis() + (maxCacheAgeSeconds * 1000)));
  133. pwmRequest.getPwmResponse().setHeader(HttpHeader.Cache_Control, "public, max-age=" + maxCacheAgeSeconds);
  134. try {
  135. final LinkedHashMap<String,String> displayData = new LinkedHashMap<>(makeDisplayData(pwmRequest.getPwmApplication(),
  136. pwmRequest.getPwmSession(), bundleName));
  137. final RestResultBean restResultBean = new RestResultBean();
  138. restResultBean.setData(displayData);
  139. pwmRequest.outputJsonResult(restResultBean);
  140. } catch (Exception e) {
  141. final String errorMSg = "error during rest /strings call for bundle " + bundleName + ", error: " + e.getMessage();
  142. final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,errorMSg);
  143. LOGGER.debug(pwmRequest, errorInformation);
  144. pwmRequest.respondWithError(errorInformation);
  145. }
  146. return ProcessStatus.Halt;
  147. }
  148. @ActionHandler(action = "health")
  149. public ProcessStatus restHealthProcessor(final PwmRequest pwmRequest)
  150. throws IOException, ServletException, PwmUnrecoverableException
  151. {
  152. if (pwmRequest.getPwmApplication().getApplicationMode() == PwmApplicationMode.RUNNING) {
  153. if (!pwmRequest.isAuthenticated()) {
  154. final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_AUTHENTICATION_REQUIRED);
  155. LOGGER.debug(pwmRequest, errorInformation);
  156. pwmRequest.respondWithError(errorInformation);
  157. return ProcessStatus.Halt;
  158. }
  159. if (!pwmRequest.getPwmSession().getSessionManager().checkPermission(pwmRequest.getPwmApplication(), Permission.PWMADMIN)) {
  160. final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, "admin privileges required");
  161. LOGGER.debug(pwmRequest, errorInformation);
  162. pwmRequest.respondWithError(errorInformation);
  163. return ProcessStatus.Halt;
  164. }
  165. }
  166. try {
  167. final HealthData jsonOutput = RestHealthServer.processGetHealthCheckData(
  168. pwmRequest.getPwmApplication(),
  169. pwmRequest.getLocale(),
  170. false);
  171. final RestResultBean restResultBean = new RestResultBean();
  172. restResultBean.setData(jsonOutput);
  173. pwmRequest.outputJsonResult(restResultBean);
  174. } catch (PwmException e) {
  175. final ErrorInformation errorInformation = e.getErrorInformation();
  176. LOGGER.debug(pwmRequest, errorInformation);
  177. pwmRequest.respondWithError(errorInformation);
  178. } catch (Exception e) {
  179. final String errorMessage = "unexpected error executing web service: " + e.getMessage();
  180. final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMessage);
  181. LOGGER.debug(pwmRequest, errorInformation);
  182. pwmRequest.respondWithError(errorInformation);
  183. }
  184. return ProcessStatus.Halt;
  185. }
  186. @ActionHandler(action = "ping")
  187. public ProcessStatus processPingRequest(final PwmRequest pwmRequest)
  188. throws IOException
  189. {
  190. final PingResponse pingResponse = new PingResponse();
  191. pingResponse.setTime(Instant.now());
  192. pingResponse.setRuntimeNonce(pwmRequest.getPwmApplication().getRuntimeNonce());
  193. pwmRequest.outputJsonResult(new RestResultBean(pingResponse));
  194. return ProcessStatus.Halt;
  195. }
  196. public static String makeClientEtag(final PwmRequest pwmRequest)
  197. throws PwmUnrecoverableException
  198. {
  199. return makeClientEtag(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest());
  200. }
  201. public static String makeClientEtag(
  202. final PwmApplication pwmApplication,
  203. final PwmSession pwmSession,
  204. final HttpServletRequest httpServletRequest
  205. )
  206. throws PwmUnrecoverableException
  207. {
  208. final StringBuilder inputString = new StringBuilder();
  209. inputString.append(PwmConstants.BUILD_NUMBER);
  210. inputString.append(pwmApplication.getStartupTime().toEpochMilli());
  211. inputString.append(httpServletRequest.getSession().getMaxInactiveInterval());
  212. inputString.append(pwmApplication.getRuntimeNonce());
  213. if (pwmSession.getSessionStateBean().getLocale() != null) {
  214. inputString.append(pwmSession.getSessionStateBean().getLocale());
  215. }
  216. inputString.append(pwmSession.getSessionStateBean().getSessionID());
  217. if (pwmSession.isAuthenticated()) {
  218. inputString.append(pwmSession.getUserInfo().getUserGuid());
  219. inputString.append(pwmSession.getLoginInfoBean().getAuthTime());
  220. }
  221. return SecureEngine.hash(inputString.toString(), PwmHashAlgorithm.SHA1).toLowerCase();
  222. }
  223. private AppData makeAppData(
  224. final PwmApplication pwmApplication,
  225. final PwmSession pwmSession,
  226. final HttpServletRequest request,
  227. final HttpServletResponse response,
  228. final String pageUrl
  229. )
  230. throws ChaiUnavailableException, PwmUnrecoverableException
  231. {
  232. final AppData appData = new AppData();
  233. appData.PWM_GLOBAL = makeClientData(pwmApplication, pwmSession, request, response, pageUrl);
  234. return appData;
  235. }
  236. private static Map<String,Object> makeClientData(
  237. final PwmApplication pwmApplication,
  238. final PwmSession pwmSession,
  239. final HttpServletRequest request,
  240. final HttpServletResponse response,
  241. final String pageUrl
  242. )
  243. throws ChaiUnavailableException, PwmUnrecoverableException
  244. {
  245. final Configuration config = pwmApplication.getConfig();
  246. final TreeMap<String,Object> settingMap = new TreeMap<>();
  247. settingMap.put("client.ajaxTypingTimeout", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_AJAX_TYPING_TIMEOUT)));
  248. settingMap.put("client.ajaxTypingWait", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_AJAX_TYPING_WAIT)));
  249. settingMap.put("client.activityMaxEpsRate", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_ACTIVITY_MAX_EPS_RATE)));
  250. settingMap.put("client.js.enableHtml5Dialog", Boolean.parseBoolean(config.readAppProperty(AppProperty.CLIENT_JS_ENABLE_HTML5DIALOG)));
  251. settingMap.put("client.pwShowRevertTimeout", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_PW_SHOW_REVERT_TIMEOUT)));
  252. settingMap.put("enableIdleTimeout", config.readSettingAsBoolean(PwmSetting.DISPLAY_IDLE_TIMEOUT));
  253. settingMap.put("pageLeaveNotice", config.readSettingAsLong(PwmSetting.SECURITY_PAGE_LEAVE_NOTICE_TIMEOUT));
  254. settingMap.put("setting-showHidePasswordFields",pwmApplication.getConfig().readSettingAsBoolean(password.pwm.config.PwmSetting.DISPLAY_SHOW_HIDE_PASSWORD_FIELDS));
  255. settingMap.put("setting-displayEula",PwmConstants.ENABLE_EULA_DISPLAY);
  256. settingMap.put("setting-showStrengthMeter",config.readSettingAsBoolean(PwmSetting.PASSWORD_SHOW_STRENGTH_METER));
  257. {
  258. long idleSeconds = config.readSettingAsLong(PwmSetting.IDLE_TIMEOUT_SECONDS);
  259. if (pageUrl == null || pageUrl.isEmpty()) {
  260. LOGGER.warn(pwmSession, "request to /client data did not incliude pageUrl");
  261. } else {
  262. try {
  263. final PwmURL pwmURL = new PwmURL(new URI(pageUrl), request.getContextPath());
  264. final TimeDuration maxIdleTime = IdleTimeoutCalculator.idleTimeoutForRequest(pwmURL, pwmApplication, pwmSession);
  265. idleSeconds = maxIdleTime.getTotalSeconds();
  266. } catch (Exception e) {
  267. LOGGER.error(pwmSession, "error determining idle timeout time for request: " + e.getMessage());
  268. }
  269. }
  270. settingMap.put("MaxInactiveInterval", idleSeconds);
  271. }
  272. settingMap.put("paramName.locale", config.readAppProperty(AppProperty.HTTP_PARAM_NAME_LOCALE));
  273. settingMap.put("runtimeNonce",pwmApplication.getRuntimeNonce());
  274. settingMap.put("applicationMode",pwmApplication.getApplicationMode());
  275. final String contextPath = request.getContextPath();
  276. settingMap.put("url-context", contextPath);
  277. settingMap.put("url-logout", contextPath + PwmServletDefinition.Logout.servletUrl());
  278. settingMap.put("url-command", contextPath + PwmServletDefinition.PublicCommand.servletUrl());
  279. settingMap.put("url-resources", contextPath + "/public/resources" + pwmApplication.getResourceServletService().getResourceNonce());
  280. settingMap.put("url-restservice", contextPath + "/public/rest");
  281. {
  282. String passwordGuideText = pwmApplication.getConfig().readSettingAsLocalizedString(PwmSetting.DISPLAY_PASSWORD_GUIDE_TEXT,pwmSession.getSessionStateBean().getLocale());
  283. final MacroMachine macroMachine = pwmSession.getSessionManager().getMacroMachine(pwmApplication);
  284. passwordGuideText = macroMachine.expandMacros(passwordGuideText);
  285. settingMap.put("passwordGuideText",passwordGuideText);
  286. }
  287. {
  288. final List<String> formTypeOptions = new ArrayList<>();
  289. for (final FormConfiguration.Type type : FormConfiguration.Type.values()) {
  290. formTypeOptions.add(type.toString());
  291. }
  292. settingMap.put("formTypeOptions",formTypeOptions);
  293. }
  294. {
  295. final List<String> actionTypeOptions = new ArrayList<>();
  296. for (final ActionConfiguration.Type type : ActionConfiguration.Type.values()) {
  297. actionTypeOptions.add(type.toString());
  298. }
  299. settingMap.put("actionTypeOptions",actionTypeOptions);
  300. }
  301. {
  302. final List<String> epsTypes = new ArrayList<>();
  303. for (final EpsStatistic loopEpsType : EpsStatistic.values()) {
  304. epsTypes.add(loopEpsType.toString());
  305. }
  306. settingMap.put("epsTypes",epsTypes);
  307. }
  308. {
  309. final List<String> epsDurations = new ArrayList<>();
  310. for (final Statistic.EpsDuration loopEpsDuration : Statistic.EpsDuration.values()) {
  311. epsDurations.add(loopEpsDuration.toString());
  312. }
  313. settingMap.put("epsDurations",epsDurations);
  314. }
  315. {
  316. final Map<String,String> localeInfo = new TreeMap<>();
  317. final Map<String,String> localeDisplayNames = new TreeMap<>();
  318. final Map<String,String> localeFlags = new TreeMap<>();
  319. for (final Locale locale : pwmApplication.getConfig().getKnownLocales()) {
  320. final String flagCode = pwmApplication.getConfig().getKnownLocaleFlagMap().get(locale);
  321. localeFlags.put(locale.toString(),flagCode);
  322. localeInfo.put(locale.toString(),locale.getDisplayName() + " - " + locale.getDisplayLanguage(locale));
  323. localeDisplayNames.put(locale.toString(),locale.getDisplayLanguage());
  324. }
  325. settingMap.put("localeInfo",localeInfo);
  326. settingMap.put("localeDisplayNames",localeDisplayNames);
  327. settingMap.put("localeFlags",localeFlags);
  328. settingMap.put("defaultLocale",PwmConstants.DEFAULT_LOCALE.toString());
  329. }
  330. if (pwmApplication.getConfig().readSettingAsEnum(PwmSetting.LDAP_SELECTABLE_CONTEXT_MODE, SelectableContextMode.class) != SelectableContextMode.NONE) {
  331. final Map<String,Map<String,String>> ldapProfiles = new LinkedHashMap<>();
  332. for (final String ldapProfile : pwmApplication.getConfig().getLdapProfiles().keySet()) {
  333. final Map<String,String> contexts = pwmApplication.getConfig().getLdapProfiles().get(ldapProfile).getSelectableContexts(pwmApplication);
  334. ldapProfiles.put(ldapProfile,contexts);
  335. }
  336. settingMap.put("ldapProfiles",ldapProfiles);
  337. }
  338. return settingMap;
  339. }
  340. private Map<String,String> makeDisplayData(
  341. final PwmApplication pwmApplication,
  342. final PwmSession pwmSession,
  343. final String bundleName
  344. )
  345. {
  346. Class displayClass = LocaleHelper.classForShortName(bundleName);
  347. displayClass = displayClass == null ? Display.class : displayClass;
  348. final Locale userLocale = pwmSession.getSessionStateBean().getLocale();
  349. final Configuration config = pwmApplication.getConfig();
  350. final TreeMap<String,String> displayStrings = new TreeMap<>();
  351. final ResourceBundle bundle = ResourceBundle.getBundle(displayClass.getName());
  352. try {
  353. final MacroMachine macroMachine = pwmSession.getSessionManager().getMacroMachine(pwmApplication);
  354. for (final String key : new TreeSet<>(Collections.list(bundle.getKeys()))) {
  355. String displayValue = LocaleHelper.getLocalizedMessage(userLocale, key, config, displayClass);
  356. displayValue = macroMachine.expandMacros(displayValue);
  357. displayStrings.put(key, displayValue);
  358. }
  359. } catch (Exception e) {
  360. LOGGER.error(pwmSession,"error expanding macro display value: " + e.getMessage());
  361. }
  362. return displayStrings;
  363. }
  364. }