ClientApiServlet.java 20 KB

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