PageRenderTime 44ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/dxp/apps/multi-factor-authentication/multi-factor-authentication-email-otp-web/src/main/java/com/liferay/multi/factor/authentication/email/otp/web/internal/checker/EmailOTPBrowserMFAChecker.java

https://github.com/danielreuther/liferay-portal
Java | 410 lines | 301 code | 92 blank | 17 comment | 33 complexity | 1706173adf50ee567d411ed35cd3b92d MD5 | raw file
  1. /**
  2. * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
  3. *
  4. * The contents of this file are subject to the terms of the Liferay Enterprise
  5. * Subscription License ("License"). You may not use this file except in
  6. * compliance with the License. You can obtain a copy of the License by
  7. * contacting Liferay, Inc. See the License for the specific language governing
  8. * permissions and limitations under the License, including but not limited to
  9. * distribution rights of the Software.
  10. *
  11. *
  12. *
  13. */
  14. package com.liferay.multi.factor.authentication.email.otp.web.internal.checker;
  15. import com.liferay.multi.factor.authentication.email.otp.configuration.MFAEmailOTPConfiguration;
  16. import com.liferay.multi.factor.authentication.email.otp.model.MFAEmailOTPEntry;
  17. import com.liferay.multi.factor.authentication.email.otp.service.MFAEmailOTPEntryLocalService;
  18. import com.liferay.multi.factor.authentication.email.otp.web.internal.audit.MFAEmailOTPAuditMessageBuilder;
  19. import com.liferay.multi.factor.authentication.email.otp.web.internal.constants.MFAEmailOTPWebKeys;
  20. import com.liferay.multi.factor.authentication.spi.checker.browser.BrowserMFAChecker;
  21. import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
  22. import com.liferay.portal.kernel.audit.AuditMessage;
  23. import com.liferay.portal.kernel.exception.PortalException;
  24. import com.liferay.portal.kernel.log.Log;
  25. import com.liferay.portal.kernel.log.LogFactoryUtil;
  26. import com.liferay.portal.kernel.model.User;
  27. import com.liferay.portal.kernel.security.auth.CompanyThreadLocal;
  28. import com.liferay.portal.kernel.service.UserLocalService;
  29. import com.liferay.portal.kernel.util.GetterUtil;
  30. import com.liferay.portal.kernel.util.ParamUtil;
  31. import com.liferay.portal.kernel.util.Portal;
  32. import com.liferay.portal.kernel.util.Time;
  33. import com.liferay.portal.util.PropsValues;
  34. import java.io.IOException;
  35. import java.util.ArrayList;
  36. import java.util.Arrays;
  37. import java.util.Date;
  38. import java.util.List;
  39. import java.util.Map;
  40. import java.util.Objects;
  41. import javax.servlet.RequestDispatcher;
  42. import javax.servlet.ServletContext;
  43. import javax.servlet.ServletException;
  44. import javax.servlet.http.HttpServletRequest;
  45. import javax.servlet.http.HttpServletResponse;
  46. import javax.servlet.http.HttpSession;
  47. import org.osgi.service.component.annotations.Activate;
  48. import org.osgi.service.component.annotations.Component;
  49. import org.osgi.service.component.annotations.ConfigurationPolicy;
  50. import org.osgi.service.component.annotations.Deactivate;
  51. import org.osgi.service.component.annotations.Reference;
  52. import org.osgi.service.component.annotations.ReferenceCardinality;
  53. /**
  54. * @author Arthur Chan
  55. * @author Marta Medio
  56. */
  57. @Component(
  58. configurationPid = "com.liferay.multi.factor.authentication.email.otp.configuration.MFAEmailOTPConfiguration.scoped",
  59. configurationPolicy = ConfigurationPolicy.OPTIONAL,
  60. service = BrowserMFAChecker.class
  61. )
  62. public class EmailOTPBrowserMFAChecker implements BrowserMFAChecker {
  63. @Override
  64. public void includeBrowserVerification(
  65. HttpServletRequest httpServletRequest,
  66. HttpServletResponse httpServletResponse, long userId)
  67. throws IOException, ServletException {
  68. User user = _userLocalService.fetchUser(userId);
  69. if (user == null) {
  70. if (_log.isWarnEnabled()) {
  71. _log.warn(
  72. "Requested one-time password email verification for " +
  73. "nonexistent user " + userId);
  74. }
  75. return;
  76. }
  77. if (_isMaximumAllowedAttemptsReached(user.getUserId())) {
  78. httpServletRequest.setAttribute(
  79. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_FAILED_ATTEMPTS_RETRY_TIMEOUT,
  80. _mfaEmailOTPConfiguration.retryTimeout());
  81. }
  82. HttpServletRequest originalHttpServletRequest =
  83. _portal.getOriginalServletRequest(httpServletRequest);
  84. HttpSession httpSession = originalHttpServletRequest.getSession();
  85. httpServletRequest.setAttribute(
  86. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_SEND_TO_ADDRESS_OBFUSCATED,
  87. obfuscateEmailAddress(user.getEmailAddress()));
  88. httpServletRequest.setAttribute(
  89. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_SET_AT_TIME,
  90. GetterUtil.getLong(
  91. httpSession.getAttribute(
  92. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_SET_AT_TIME),
  93. Long.MIN_VALUE));
  94. RequestDispatcher requestDispatcher =
  95. _servletContext.getRequestDispatcher(
  96. "/mfa_email_otp_checker/verify_browser.jsp");
  97. requestDispatcher.include(httpServletRequest, httpServletResponse);
  98. httpSession.setAttribute(
  99. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_PHASE, "verify");
  100. httpSession.setAttribute(
  101. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_USER_ID, userId);
  102. }
  103. @Override
  104. public boolean isBrowserVerified(
  105. HttpServletRequest httpServletRequest, long userId) {
  106. HttpServletRequest originalHttpServletRequest =
  107. _portal.getOriginalServletRequest(httpServletRequest);
  108. HttpSession httpSession = originalHttpServletRequest.getSession(false);
  109. if (_isVerified(httpSession, userId)) {
  110. return true;
  111. }
  112. return false;
  113. }
  114. @Override
  115. public boolean verifyBrowserRequest(
  116. HttpServletRequest httpServletRequest,
  117. HttpServletResponse httpServletResponse, long userId)
  118. throws Exception {
  119. User user = _userLocalService.fetchUser(userId);
  120. if (user == null) {
  121. if (_log.isWarnEnabled()) {
  122. _log.warn(
  123. "Requested one-time password email verification for " +
  124. "nonexistent user " + userId);
  125. }
  126. _routeAuditMessage(
  127. _mfaEmailOTPAuditMessageBuilder.
  128. buildNonexistentUserVerificationFailureAuditMessage(
  129. CompanyThreadLocal.getCompanyId(), userId,
  130. _getClassName()));
  131. return false;
  132. }
  133. MFAEmailOTPEntry mfaEmailOTPEntry =
  134. _mfaEmailOTPEntryLocalService.fetchMFAEmailOTPEntryByUserId(userId);
  135. if (mfaEmailOTPEntry == null) {
  136. _mfaEmailOTPEntryLocalService.addMFAEmailOTPEntry(userId);
  137. }
  138. if (_isMaximumAllowedAttemptsReached(userId)) {
  139. _routeAuditMessage(
  140. _mfaEmailOTPAuditMessageBuilder.
  141. buildVerificationFailureAuditMessage(
  142. user, _getClassName(),
  143. "Reached maximum allowed attempts"));
  144. return false;
  145. }
  146. HttpServletRequest originalHttpServletRequest =
  147. _portal.getOriginalServletRequest(httpServletRequest);
  148. HttpSession httpSession = originalHttpServletRequest.getSession();
  149. String otp = ParamUtil.getString(httpServletRequest, "otp");
  150. if (_verify(httpSession, otp)) {
  151. httpSession.setAttribute(
  152. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_AT_TIME,
  153. System.currentTimeMillis());
  154. httpSession.setAttribute(
  155. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_USER_ID, userId);
  156. _mfaEmailOTPEntryLocalService.updateAttempts(
  157. userId, originalHttpServletRequest.getRemoteAddr(), true);
  158. _routeAuditMessage(
  159. _mfaEmailOTPAuditMessageBuilder.
  160. buildVerificationSuccessAuditMessage(
  161. user, _getClassName()));
  162. return true;
  163. }
  164. _routeAuditMessage(
  165. _mfaEmailOTPAuditMessageBuilder.
  166. buildVerificationFailureAuditMessage(
  167. user, _getClassName(),
  168. "Incorrect email one-time password"));
  169. _mfaEmailOTPEntryLocalService.updateAttempts(
  170. userId, originalHttpServletRequest.getRemoteAddr(), false);
  171. return false;
  172. }
  173. protected static String obfuscateEmailAddress(String emailAddress) {
  174. String alias = emailAddress.substring(0, emailAddress.indexOf('@'));
  175. int maskLength = Math.max(
  176. (int)Math.ceil(alias.length() / 2.0), Math.min(3, alias.length()));
  177. int startIndex = (int)Math.ceil((alias.length() - maskLength) / 2.0);
  178. int endIndex = startIndex + maskLength;
  179. char[] chars = emailAddress.toCharArray();
  180. for (int i = startIndex; i < endIndex; i++) {
  181. chars[i] = '*';
  182. }
  183. return new String(chars);
  184. }
  185. @Activate
  186. protected void activate(Map<String, Object> properties) {
  187. _mfaEmailOTPConfiguration = ConfigurableUtil.createConfigurable(
  188. MFAEmailOTPConfiguration.class, properties);
  189. if (!PropsValues.SESSION_ENABLE_PHISHING_PROTECTION) {
  190. return;
  191. }
  192. List<String> sessionPhishingProtectedAttributes = new ArrayList<>(
  193. Arrays.asList(PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES));
  194. sessionPhishingProtectedAttributes.add(
  195. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_AT_TIME);
  196. sessionPhishingProtectedAttributes.add(
  197. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_USER_ID);
  198. PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES =
  199. sessionPhishingProtectedAttributes.toArray(new String[0]);
  200. }
  201. @Deactivate
  202. protected void deactivate() {
  203. if (!PropsValues.SESSION_ENABLE_PHISHING_PROTECTION) {
  204. return;
  205. }
  206. List<String> sessionPhishingProtectedAttributes = new ArrayList<>(
  207. Arrays.asList(PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES));
  208. sessionPhishingProtectedAttributes.remove(
  209. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_AT_TIME);
  210. sessionPhishingProtectedAttributes.remove(
  211. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_USER_ID);
  212. PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES =
  213. sessionPhishingProtectedAttributes.toArray(new String[0]);
  214. }
  215. private String _getClassName() {
  216. Class<?> clazz = getClass();
  217. return clazz.getName();
  218. }
  219. private boolean _isMaximumAllowedAttemptsReached(long userId) {
  220. try {
  221. MFAEmailOTPEntry mfaEmailOTPEntry =
  222. _mfaEmailOTPEntryLocalService.fetchMFAEmailOTPEntryByUserId(
  223. userId);
  224. if (mfaEmailOTPEntry == null) {
  225. return false;
  226. }
  227. if ((_mfaEmailOTPConfiguration.failedAttemptsAllowed() >= 0) &&
  228. (_mfaEmailOTPConfiguration.failedAttemptsAllowed() <=
  229. mfaEmailOTPEntry.getFailedAttempts()) &&
  230. (_mfaEmailOTPConfiguration.retryTimeout() >= 0)) {
  231. Date lastFailDate = mfaEmailOTPEntry.getLastFailDate();
  232. long time =
  233. (_mfaEmailOTPConfiguration.retryTimeout() * Time.SECOND) +
  234. lastFailDate.getTime();
  235. if (time <= System.currentTimeMillis()) {
  236. _mfaEmailOTPEntryLocalService.resetFailedAttempts(userId);
  237. }
  238. else {
  239. return true;
  240. }
  241. }
  242. }
  243. catch (PortalException portalException) {
  244. _log.error(portalException);
  245. }
  246. return false;
  247. }
  248. private boolean _isVerified(HttpSession httpSession, long userId) {
  249. User user = _userLocalService.fetchUser(userId);
  250. if (user == null) {
  251. if (_log.isWarnEnabled()) {
  252. _log.warn(
  253. "Requested one-time password email verification for " +
  254. "nonexistent user " + userId);
  255. }
  256. _routeAuditMessage(
  257. _mfaEmailOTPAuditMessageBuilder.
  258. buildNonexistentUserVerificationFailureAuditMessage(
  259. CompanyThreadLocal.getCompanyId(), userId,
  260. _getClassName()));
  261. return false;
  262. }
  263. if (httpSession == null) {
  264. _routeAuditMessage(
  265. _mfaEmailOTPAuditMessageBuilder.buildNotVerifiedAuditMessage(
  266. user, _getClassName(), "Empty session"));
  267. return false;
  268. }
  269. Object mfaEmailOTPValidatedUserId = httpSession.getAttribute(
  270. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_USER_ID);
  271. if (mfaEmailOTPValidatedUserId == null) {
  272. _routeAuditMessage(
  273. _mfaEmailOTPAuditMessageBuilder.buildNotVerifiedAuditMessage(
  274. user, _getClassName(), "Not verified yet"));
  275. return false;
  276. }
  277. if (!Objects.equals(mfaEmailOTPValidatedUserId, userId)) {
  278. _routeAuditMessage(
  279. _mfaEmailOTPAuditMessageBuilder.buildNotVerifiedAuditMessage(
  280. user, _getClassName(), "Not the same user"));
  281. return false;
  282. }
  283. return true;
  284. }
  285. private void _routeAuditMessage(AuditMessage auditMessage) {
  286. if (_mfaEmailOTPAuditMessageBuilder != null) {
  287. _mfaEmailOTPAuditMessageBuilder.routeAuditMessage(auditMessage);
  288. }
  289. }
  290. private boolean _verify(HttpSession httpSession, String otp) {
  291. String expectedMFAEmailOTP = (String)httpSession.getAttribute(
  292. MFAEmailOTPWebKeys.MFA_EMAIL_OTP);
  293. if ((expectedMFAEmailOTP == null) || !expectedMFAEmailOTP.equals(otp)) {
  294. return false;
  295. }
  296. httpSession.removeAttribute(MFAEmailOTPWebKeys.MFA_EMAIL_OTP);
  297. httpSession.removeAttribute(MFAEmailOTPWebKeys.MFA_EMAIL_OTP_PHASE);
  298. httpSession.removeAttribute(
  299. MFAEmailOTPWebKeys.MFA_EMAIL_OTP_SET_AT_TIME);
  300. httpSession.removeAttribute(MFAEmailOTPWebKeys.MFA_EMAIL_OTP_USER_ID);
  301. return true;
  302. }
  303. private static final Log _log = LogFactoryUtil.getLog(
  304. EmailOTPBrowserMFAChecker.class);
  305. @Reference(cardinality = ReferenceCardinality.OPTIONAL)
  306. private MFAEmailOTPAuditMessageBuilder _mfaEmailOTPAuditMessageBuilder;
  307. private MFAEmailOTPConfiguration _mfaEmailOTPConfiguration;
  308. @Reference
  309. private MFAEmailOTPEntryLocalService _mfaEmailOTPEntryLocalService;
  310. @Reference
  311. private Portal _portal;
  312. @Reference(
  313. target = "(osgi.web.symbolicname=com.liferay.multi.factor.authentication.email.otp.web)"
  314. )
  315. private ServletContext _servletContext;
  316. @Reference
  317. private UserLocalService _userLocalService;
  318. }