RestChallengesServer.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /*
  2. * Password Management Servlets (PWM)
  3. * http://www.pwm-project.org
  4. *
  5. * Copyright (c) 2006-2009 Novell, Inc.
  6. * Copyright (c) 2009-2017 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.ws.server.rest;
  23. import com.novell.ldapchai.ChaiUser;
  24. import com.novell.ldapchai.cr.ChaiChallenge;
  25. import com.novell.ldapchai.cr.Challenge;
  26. import com.novell.ldapchai.cr.ChallengeSet;
  27. import com.novell.ldapchai.cr.ResponseSet;
  28. import com.novell.ldapchai.cr.bean.ChallengeBean;
  29. import password.pwm.Permission;
  30. import password.pwm.bean.ResponseInfoBean;
  31. import password.pwm.bean.UserIdentity;
  32. import password.pwm.config.PwmSetting;
  33. import password.pwm.config.profile.ChallengeProfile;
  34. import password.pwm.config.profile.HelpdeskProfile;
  35. import password.pwm.config.profile.PwmPasswordPolicy;
  36. import password.pwm.error.ErrorInformation;
  37. import password.pwm.error.PwmError;
  38. import password.pwm.error.PwmException;
  39. import password.pwm.error.PwmOperationalException;
  40. import password.pwm.error.PwmUnrecoverableException;
  41. import password.pwm.i18n.Message;
  42. import password.pwm.ldap.LdapOperationsHelper;
  43. import password.pwm.svc.event.AuditEvent;
  44. import password.pwm.svc.event.AuditRecordFactory;
  45. import password.pwm.svc.event.HelpdeskAuditRecord;
  46. import password.pwm.svc.event.UserAuditRecord;
  47. import password.pwm.svc.stats.Statistic;
  48. import password.pwm.svc.stats.StatisticsManager;
  49. import password.pwm.util.operations.CrService;
  50. import password.pwm.util.operations.PasswordUtility;
  51. import password.pwm.ws.server.RestRequestBean;
  52. import password.pwm.ws.server.RestResultBean;
  53. import password.pwm.ws.server.RestServerHelper;
  54. import password.pwm.ws.server.ServicePermissions;
  55. import javax.servlet.http.HttpServletRequest;
  56. import javax.ws.rs.Consumes;
  57. import javax.ws.rs.DELETE;
  58. import javax.ws.rs.GET;
  59. import javax.ws.rs.POST;
  60. import javax.ws.rs.Path;
  61. import javax.ws.rs.Produces;
  62. import javax.ws.rs.QueryParam;
  63. import javax.ws.rs.core.Context;
  64. import javax.ws.rs.core.MediaType;
  65. import javax.ws.rs.core.Response;
  66. import java.io.Serializable;
  67. import java.net.URISyntaxException;
  68. import java.time.Instant;
  69. import java.util.ArrayList;
  70. import java.util.LinkedHashMap;
  71. import java.util.List;
  72. import java.util.Locale;
  73. import java.util.Map;
  74. @Path("/challenges")
  75. public class RestChallengesServer extends AbstractRestServer {
  76. private static final ServicePermissions SERVICE_PERMISSIONS = ServicePermissions.builder()
  77. .adminOnly(false)
  78. .authRequired(true)
  79. .blockExternal(true)
  80. .build();
  81. public static class Policy {
  82. public List<ChallengeBean> challenges;
  83. public List<ChallengeBean> helpdeskChallenges;
  84. public int minimumRandoms;
  85. }
  86. public static class JsonChallengesData implements Serializable {
  87. public String username;
  88. public List<ChallengeBean> challenges;
  89. public List<ChallengeBean> helpdeskChallenges;
  90. public int minimumRandoms;
  91. public Policy policy;
  92. public ResponseInfoBean toResponseInfoBean(final Locale locale, final String csIdentifier)
  93. throws PwmOperationalException
  94. {
  95. final Map<Challenge,String> crMap = new LinkedHashMap<>();
  96. if (challenges != null) {
  97. for (final ChallengeBean challengeBean : challenges) {
  98. final Challenge challenge = ChaiChallenge.fromChallengeBean(challengeBean);
  99. final String answerText = challengeBean.getAnswer().getAnswerText();
  100. if (answerText == null || answerText.length() < 1) {
  101. throw new IllegalArgumentException("missing answerText for challenge '" + challenge.getChallengeText() + "'");
  102. }
  103. crMap.put(challenge,answerText);
  104. }
  105. }
  106. final Map<Challenge,String> helpdeskCrMap = new LinkedHashMap<>();
  107. if (helpdeskChallenges != null) {
  108. for (final ChallengeBean challengeBean : helpdeskChallenges) {
  109. final Challenge challenge = ChaiChallenge.fromChallengeBean(challengeBean);
  110. final String answerText = challengeBean.getAnswer().getAnswerText();
  111. if (answerText == null || answerText.length() < 1) {
  112. throw new IllegalArgumentException("missing answerText for helpdesk challenge '" + challenge.getChallengeText() + "'");
  113. }
  114. helpdeskCrMap.put(challenge,answerText);
  115. }
  116. }
  117. final ResponseInfoBean responseInfoBean = new ResponseInfoBean(
  118. crMap,
  119. helpdeskCrMap,
  120. locale,
  121. minimumRandoms,
  122. csIdentifier,
  123. null,
  124. null
  125. );
  126. responseInfoBean.setTimestamp(Instant.now());
  127. return responseInfoBean;
  128. }
  129. }
  130. @Context
  131. HttpServletRequest request;
  132. @GET
  133. @Produces(MediaType.TEXT_HTML)
  134. public javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException {
  135. return RestServerHelper.handleHtmlRequest();
  136. }
  137. @GET
  138. @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
  139. public Response doFormGetChallengeData(
  140. @QueryParam("answers") final boolean answers,
  141. @QueryParam("helpdesk") final boolean helpdesk,
  142. @QueryParam("username") final String username
  143. )
  144. throws PwmUnrecoverableException
  145. {
  146. final RestRequestBean restRequestBean;
  147. try {
  148. restRequestBean = RestServerHelper.initializeRestRequest(request, response, SERVICE_PERMISSIONS, username);
  149. } catch (PwmUnrecoverableException e) {
  150. return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
  151. }
  152. try {
  153. if (answers && !restRequestBean.getPwmApplication().getConfig().readSettingAsBoolean(PwmSetting.ENABLE_WEBSERVICES_READANSWERS)) {
  154. throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE,"retrieval of answers is not permitted"));
  155. }
  156. // gather data
  157. final ResponseSet responseSet;
  158. final ChallengeSet challengeSet;
  159. final ChallengeSet helpdeskChallengeSet;
  160. final String outputUsername;
  161. if (restRequestBean.getUserIdentity() == null) {
  162. final ChaiUser chaiUser = restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication());
  163. final CrService crService = restRequestBean.getPwmApplication().getCrService();
  164. responseSet = crService.readUserResponseSet(restRequestBean.getPwmSession().getLabel(), restRequestBean.getPwmSession().getUserInfo().getUserIdentity(), chaiUser);
  165. challengeSet = restRequestBean.getPwmSession().getUserInfo().getChallengeProfile().getChallengeSet();
  166. helpdeskChallengeSet = restRequestBean.getPwmSession().getUserInfo().getChallengeProfile().getHelpdeskChallengeSet();
  167. outputUsername = restRequestBean.getPwmSession().getUserInfo().getUserIdentity().getLdapProfileID();
  168. } else {
  169. final ChaiUser chaiUser = restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication(),restRequestBean.getUserIdentity());
  170. final Locale userLocale = restRequestBean.getPwmSession().getSessionStateBean().getLocale();
  171. final CrService crService = restRequestBean.getPwmApplication().getCrService();
  172. responseSet = crService.readUserResponseSet(restRequestBean.getPwmSession().getLabel(),restRequestBean.getUserIdentity(), chaiUser);
  173. final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser(
  174. restRequestBean.getPwmApplication(),
  175. restRequestBean.getPwmSession().getLabel(),
  176. restRequestBean.getUserIdentity(),
  177. chaiUser,userLocale
  178. );
  179. final ChallengeProfile challengeProfile = crService.readUserChallengeProfile(
  180. restRequestBean.getPwmSession().getLabel(),
  181. restRequestBean.getUserIdentity(),
  182. chaiUser,
  183. passwordPolicy,
  184. userLocale
  185. );
  186. challengeSet = challengeProfile.getChallengeSet();
  187. helpdeskChallengeSet = challengeProfile.getHelpdeskChallengeSet();
  188. outputUsername = restRequestBean.getUserIdentity().toDelimitedKey();
  189. }
  190. // build output
  191. final JsonChallengesData jsonData = new JsonChallengesData();
  192. {
  193. jsonData.username = outputUsername;
  194. if (responseSet != null) {
  195. jsonData.challenges = responseSet.asChallengeBeans(answers);
  196. if (helpdesk) {
  197. jsonData.helpdeskChallenges = responseSet.asHelpdeskChallengeBeans(answers);
  198. }
  199. jsonData.minimumRandoms = responseSet.getChallengeSet().getMinRandomRequired();
  200. }
  201. final Policy policy = new Policy();
  202. if (challengeSet != null) {
  203. policy.challenges = challengesToBeans(challengeSet.getChallenges());
  204. policy.minimumRandoms = challengeSet.getMinRandomRequired();
  205. }
  206. if (helpdeskChallengeSet != null && helpdesk) {
  207. policy.helpdeskChallenges = challengesToBeans(helpdeskChallengeSet.getChallenges());
  208. }
  209. if (policy.challenges != null || policy.helpdeskChallenges != null) {
  210. jsonData.policy = policy;
  211. }
  212. }
  213. // update statistics
  214. if (!restRequestBean.isExternal()) {
  215. StatisticsManager.incrementStat(restRequestBean.getPwmApplication(), Statistic.REST_CHALLENGES);
  216. }
  217. final RestResultBean resultBean = new RestResultBean();
  218. resultBean.setData(jsonData);
  219. return resultBean.asJsonResponse();
  220. } catch (PwmException e) {
  221. return RestResultBean.fromError(e.getErrorInformation(), restRequestBean).asJsonResponse();
  222. } catch (Exception e) {
  223. final String errorMsg = "unexpected error building json response: " + e.getMessage();
  224. final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
  225. return RestResultBean.fromError(errorInformation, restRequestBean).asJsonResponse();
  226. }
  227. }
  228. @POST
  229. @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
  230. @Consumes(MediaType.APPLICATION_JSON)
  231. public Response doSetChallengeDataJson(
  232. final JsonChallengesData jsonInput
  233. )
  234. {
  235. final RestRequestBean restRequestBean;
  236. try {
  237. restRequestBean = RestServerHelper.initializeRestRequest(request, response, SERVICE_PERMISSIONS, jsonInput.username);
  238. } catch (PwmUnrecoverableException e) {
  239. return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
  240. }
  241. try {
  242. if (!restRequestBean.getPwmSession().getSessionManager().checkPermission(restRequestBean.getPwmApplication(), Permission.SETUP_RESPONSE)) {
  243. throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,"actor does not have required permission"));
  244. }
  245. final ChaiUser chaiUser;
  246. final String userGUID;
  247. final String csIdentifer;
  248. final UserIdentity userIdentity;
  249. final CrService crService = restRequestBean.getPwmApplication().getCrService();
  250. if (restRequestBean.getUserIdentity() == null) {
  251. chaiUser = restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication());
  252. userIdentity = restRequestBean.getPwmSession().getUserInfo().getUserIdentity();
  253. userGUID = restRequestBean.getPwmSession().getUserInfo().getUserGuid();
  254. csIdentifer = restRequestBean.getPwmSession().getUserInfo().getChallengeProfile().getChallengeSet().getIdentifier();
  255. } else {
  256. userIdentity = restRequestBean.getUserIdentity();
  257. chaiUser = restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication(),userIdentity);
  258. userGUID = LdapOperationsHelper.readLdapGuidValue(
  259. restRequestBean.getPwmApplication(),
  260. restRequestBean.getPwmSession().getLabel(),
  261. userIdentity,
  262. false);
  263. final ChallengeProfile challengeProfile = crService.readUserChallengeProfile(
  264. restRequestBean.getPwmSession().getLabel(),
  265. userIdentity,
  266. chaiUser,
  267. PwmPasswordPolicy.defaultPolicy(),
  268. request.getLocale());
  269. csIdentifer = challengeProfile.getChallengeSet().getIdentifier();
  270. }
  271. final ResponseInfoBean responseInfoBean = jsonInput.toResponseInfoBean(request.getLocale(), csIdentifer);
  272. crService.writeResponses(userIdentity, chaiUser,userGUID,responseInfoBean);
  273. // update statistics
  274. if (!restRequestBean.isExternal()) {
  275. StatisticsManager.incrementStat(restRequestBean.getPwmApplication(), Statistic.REST_CHALLENGES);
  276. }
  277. final String successMsg = Message.Success_SetupResponse.getLocalizedMessage(request.getLocale(),restRequestBean.getPwmApplication().getConfig());
  278. final RestResultBean resultBean = new RestResultBean();
  279. resultBean.setError(false);
  280. resultBean.setSuccessMessage(successMsg);
  281. return resultBean.asJsonResponse();
  282. } catch (PwmException e) {
  283. return RestResultBean.fromError(e.getErrorInformation(), restRequestBean).asJsonResponse();
  284. } catch (Exception e) {
  285. final String errorMsg = "unexpected error reading json input: " + e.getMessage();
  286. final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
  287. return RestResultBean.fromError(errorInformation, restRequestBean).asJsonResponse();
  288. }
  289. }
  290. @DELETE
  291. @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
  292. public Response doDeleteChallengeData(
  293. @QueryParam("username") final String username
  294. )
  295. {
  296. final RestRequestBean restRequestBean;
  297. try {
  298. final ServicePermissions servicePermissions = SERVICE_PERMISSIONS.toBuilder()
  299. .helpdeskPermitted(true)
  300. .build();
  301. restRequestBean = RestServerHelper.initializeRestRequest(request, response, servicePermissions, username);
  302. } catch (PwmUnrecoverableException e) {
  303. return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
  304. }
  305. try {
  306. final HelpdeskProfile helpdeskProfile = restRequestBean.getPwmSession().getSessionManager().getHelpdeskProfile(restRequestBean.getPwmApplication());
  307. if (restRequestBean.getUserIdentity() == null) {
  308. if (!restRequestBean.getPwmSession().getSessionManager().checkPermission(restRequestBean.getPwmApplication(), Permission.SETUP_RESPONSE)) {
  309. throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,"actor does not have required permission"));
  310. }
  311. } else {
  312. if (helpdeskProfile == null) {
  313. throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,"actor does not have required permission"));
  314. }
  315. }
  316. final ChaiUser chaiUser;
  317. final String userGUID;
  318. if (restRequestBean.getUserIdentity() == null) {
  319. /* clear self */
  320. chaiUser = restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication());
  321. userGUID = restRequestBean.getPwmSession().getUserInfo().getUserGuid();
  322. // mark the event log
  323. final UserAuditRecord auditRecord = new AuditRecordFactory(restRequestBean.getPwmApplication(), restRequestBean.getPwmSession()).createUserAuditRecord(
  324. AuditEvent.CLEAR_RESPONSES,
  325. restRequestBean.getPwmSession().getUserInfo(),
  326. restRequestBean.getPwmSession()
  327. );
  328. restRequestBean.getPwmApplication().getAuditManager().submit(auditRecord);
  329. } else {
  330. /* clear 3rd party (helpdesk) */
  331. final boolean useProxy = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_USE_PROXY);
  332. chaiUser = useProxy
  333. ? restRequestBean.getPwmApplication().getProxiedChaiUser(restRequestBean.getUserIdentity())
  334. : restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication(),restRequestBean.getUserIdentity());
  335. userGUID = LdapOperationsHelper.readLdapGuidValue(
  336. restRequestBean.getPwmApplication(),
  337. restRequestBean.getPwmSession().getLabel(),
  338. restRequestBean.getUserIdentity(),
  339. false);
  340. // mark the event log
  341. final HelpdeskAuditRecord auditRecord = new AuditRecordFactory(restRequestBean.getPwmApplication(), restRequestBean.getPwmSession()).createHelpdeskAuditRecord(
  342. AuditEvent.HELPDESK_CLEAR_RESPONSES,
  343. restRequestBean.getPwmSession().getUserInfo().getUserIdentity(),
  344. null,
  345. restRequestBean.getUserIdentity(),
  346. restRequestBean.getPwmSession().getSessionStateBean().getSrcAddress(),
  347. restRequestBean.getPwmSession().getSessionStateBean().getSrcHostname()
  348. );
  349. restRequestBean.getPwmApplication().getAuditManager().submit(auditRecord);
  350. }
  351. final CrService crService = restRequestBean.getPwmApplication().getCrService();
  352. crService.clearResponses(
  353. restRequestBean.getPwmSession().getLabel(),
  354. restRequestBean.getUserIdentity(),
  355. chaiUser,
  356. userGUID
  357. );
  358. // update statistics
  359. if (!restRequestBean.isExternal()) {
  360. StatisticsManager.incrementStat(restRequestBean.getPwmApplication(), Statistic.REST_CHALLENGES);
  361. }
  362. final String successMsg = Message.Success_Unknown.getLocalizedMessage(request.getLocale(),restRequestBean.getPwmApplication().getConfig());
  363. final RestResultBean resultBean = new RestResultBean();
  364. resultBean.setError(false);
  365. resultBean.setSuccessMessage(successMsg);
  366. return resultBean.asJsonResponse();
  367. } catch (PwmException e) {
  368. return RestResultBean.fromError(e.getErrorInformation(), restRequestBean).asJsonResponse();
  369. } catch (Exception e) {
  370. final String errorMsg = "unexpected error delete responses: " + e.getMessage();
  371. final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
  372. return RestResultBean.fromError(errorInformation, restRequestBean).asJsonResponse();
  373. }
  374. }
  375. private static List<ChallengeBean> challengesToBeans(final List<Challenge> challenges) {
  376. final List<ChallengeBean> returnList = new ArrayList<>();
  377. for (final Challenge challenge : challenges) {
  378. returnList.add(challenge.asChallengeBean());
  379. }
  380. return returnList;
  381. }
  382. }