PageRenderTime 55ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://github.com/danielreuther/liferay-portal
Java | 468 lines | 354 code | 97 blank | 17 comment | 35 complexity | ab465b16751f30b36289cb65a66500aa 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.timebased.otp.web.internal.checker;
  15. import com.liferay.multi.factor.authentication.spi.checker.browser.BrowserMFAChecker;
  16. import com.liferay.multi.factor.authentication.spi.checker.setup.SetupMFAChecker;
  17. import com.liferay.multi.factor.authentication.timebased.otp.model.MFATimeBasedOTPEntry;
  18. import com.liferay.multi.factor.authentication.timebased.otp.service.MFATimeBasedOTPEntryLocalService;
  19. import com.liferay.multi.factor.authentication.timebased.otp.web.internal.audit.MFATimeBasedOTPAuditMessageBuilder;
  20. import com.liferay.multi.factor.authentication.timebased.otp.web.internal.configuration.MFATimeBasedOTPConfiguration;
  21. import com.liferay.multi.factor.authentication.timebased.otp.web.internal.constants.MFATimeBasedOTPWebKeys;
  22. import com.liferay.multi.factor.authentication.timebased.otp.web.internal.util.MFATimeBasedOTPUtil;
  23. import com.liferay.petra.string.StringBundler;
  24. import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
  25. import com.liferay.portal.kernel.audit.AuditMessage;
  26. import com.liferay.portal.kernel.exception.PortalException;
  27. import com.liferay.portal.kernel.log.Log;
  28. import com.liferay.portal.kernel.log.LogFactoryUtil;
  29. import com.liferay.portal.kernel.model.Company;
  30. import com.liferay.portal.kernel.model.User;
  31. import com.liferay.portal.kernel.security.auth.CompanyThreadLocal;
  32. import com.liferay.portal.kernel.service.UserLocalService;
  33. import com.liferay.portal.kernel.util.HashMapDictionary;
  34. import com.liferay.portal.kernel.util.ParamUtil;
  35. import com.liferay.portal.kernel.util.Portal;
  36. import com.liferay.portal.kernel.util.Validator;
  37. import com.liferay.portal.util.PropsValues;
  38. import java.io.IOException;
  39. import java.util.ArrayList;
  40. import java.util.Arrays;
  41. import java.util.List;
  42. import java.util.Map;
  43. import java.util.Objects;
  44. import javax.servlet.RequestDispatcher;
  45. import javax.servlet.ServletContext;
  46. import javax.servlet.ServletException;
  47. import javax.servlet.http.HttpServletRequest;
  48. import javax.servlet.http.HttpServletResponse;
  49. import javax.servlet.http.HttpSession;
  50. import org.osgi.framework.BundleContext;
  51. import org.osgi.framework.ServiceRegistration;
  52. import org.osgi.service.component.annotations.Activate;
  53. import org.osgi.service.component.annotations.Component;
  54. import org.osgi.service.component.annotations.ConfigurationPolicy;
  55. import org.osgi.service.component.annotations.Deactivate;
  56. import org.osgi.service.component.annotations.Reference;
  57. import org.osgi.service.component.annotations.ReferenceCardinality;
  58. /**
  59. * @author Tomas Polesovsky
  60. * @author Marta Medio
  61. */
  62. @Component(
  63. configurationPid = "com.liferay.multi.factor.authentication.timebased.otp.web.internal.configuration.MFATimeBasedOTPConfiguration.scoped",
  64. configurationPolicy = ConfigurationPolicy.REQUIRE, immediate = true,
  65. service = {}
  66. )
  67. public class TimeBasedOTPBrowserSetupMFAChecker
  68. implements BrowserMFAChecker, SetupMFAChecker {
  69. @Override
  70. public void includeBrowserVerification(
  71. HttpServletRequest httpServletRequest,
  72. HttpServletResponse httpServletResponse, long userId)
  73. throws IOException, ServletException {
  74. RequestDispatcher requestDispatcher =
  75. _servletContext.getRequestDispatcher(
  76. "/mfa_timebased_otp_checker/verify_browser.jsp");
  77. requestDispatcher.include(httpServletRequest, httpServletResponse);
  78. }
  79. @Override
  80. public void includeSetup(
  81. HttpServletRequest httpServletRequest,
  82. HttpServletResponse httpServletResponse, long userId)
  83. throws Exception {
  84. MFATimeBasedOTPEntry mfaTimeBasedOTPEntry =
  85. _mfaTimeBasedOTPEntryLocalService.fetchMFATimeBasedOTPEntryByUserId(
  86. userId);
  87. if (mfaTimeBasedOTPEntry != null) {
  88. RequestDispatcher requestDispatcher =
  89. _servletContext.getRequestDispatcher(
  90. "/mfa_timebased_otp_checker/setup_completed.jsp");
  91. requestDispatcher.include(httpServletRequest, httpServletResponse);
  92. }
  93. else {
  94. Company company = _portal.getCompany(httpServletRequest);
  95. String mfaTimeBasedOTPSharedSecret =
  96. MFATimeBasedOTPUtil.generateSharedSecret(
  97. _mfaTimeBasedOTPConfiguration.algorithmKeySize());
  98. httpServletRequest.setAttribute(
  99. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_ALGORITHM, "SHA1");
  100. httpServletRequest.setAttribute(
  101. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_COMPANY_NAME,
  102. company.getName());
  103. httpServletRequest.setAttribute(
  104. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_DIGITS,
  105. MFATimeBasedOTPUtil.MFA_TIMEBASED_OTP_DIGITS);
  106. httpServletRequest.setAttribute(
  107. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_SHARED_SECRET,
  108. mfaTimeBasedOTPSharedSecret);
  109. httpServletRequest.setAttribute(
  110. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_TIME_COUNTER,
  111. MFATimeBasedOTPUtil.MFA_TIMEBASED_OTP_COUNTER);
  112. RequestDispatcher requestDispatcher =
  113. _servletContext.getRequestDispatcher(
  114. "/mfa_timebased_otp_checker/setup.jsp");
  115. requestDispatcher.include(httpServletRequest, httpServletResponse);
  116. HttpServletRequest originalHttpServletRequest =
  117. _portal.getOriginalServletRequest(httpServletRequest);
  118. HttpSession httpSession = originalHttpServletRequest.getSession();
  119. httpSession.setAttribute(
  120. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_SHARED_SECRET,
  121. mfaTimeBasedOTPSharedSecret);
  122. }
  123. }
  124. @Override
  125. public boolean isAvailable(long userId) {
  126. MFATimeBasedOTPEntry mfaTimeBasedOTPEntry =
  127. _mfaTimeBasedOTPEntryLocalService.fetchMFATimeBasedOTPEntryByUserId(
  128. userId);
  129. if (mfaTimeBasedOTPEntry != null) {
  130. return true;
  131. }
  132. return false;
  133. }
  134. @Override
  135. public boolean isBrowserVerified(
  136. HttpServletRequest httpServletRequest, long userId) {
  137. HttpServletRequest originalHttpServletRequest =
  138. _portal.getOriginalServletRequest(httpServletRequest);
  139. HttpSession httpSession = originalHttpServletRequest.getSession(false);
  140. if (_isVerified(httpSession, userId)) {
  141. return true;
  142. }
  143. return false;
  144. }
  145. @Override
  146. public void removeExistingSetup(long userId) {
  147. MFATimeBasedOTPEntry mfaTimeBasedOTPEntry =
  148. _mfaTimeBasedOTPEntryLocalService.fetchMFATimeBasedOTPEntryByUserId(
  149. userId);
  150. if (mfaTimeBasedOTPEntry != null) {
  151. _mfaTimeBasedOTPEntryLocalService.deleteMFATimeBasedOTPEntry(
  152. mfaTimeBasedOTPEntry);
  153. }
  154. }
  155. @Override
  156. public boolean setUp(HttpServletRequest httpServletRequest, long userId) {
  157. HttpServletRequest originalHttpServletRequest =
  158. _portal.getOriginalServletRequest(httpServletRequest);
  159. HttpSession httpSession = originalHttpServletRequest.getSession();
  160. String mfaTimeBasedOTPSharedSecret = (String)httpSession.getAttribute(
  161. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_SHARED_SECRET);
  162. String mfaTimeBasedOTP = ParamUtil.getString(
  163. httpServletRequest, "mfaTimeBasedOTP");
  164. try {
  165. if (MFATimeBasedOTPUtil.verifyTimeBasedOTP(
  166. _mfaTimeBasedOTPConfiguration.clockSkew(),
  167. mfaTimeBasedOTPSharedSecret, mfaTimeBasedOTP)) {
  168. MFATimeBasedOTPEntry timeBasedOTPEntry =
  169. _mfaTimeBasedOTPEntryLocalService.addTimeBasedOTPEntry(
  170. userId, mfaTimeBasedOTPSharedSecret);
  171. if (timeBasedOTPEntry != null) {
  172. return true;
  173. }
  174. }
  175. }
  176. catch (PortalException portalException) {
  177. _log.error(
  178. StringBundler.concat(
  179. "Unable to generate time-based one-time password for user ",
  180. userId, ": ", portalException.getMessage()),
  181. portalException);
  182. }
  183. return false;
  184. }
  185. @Override
  186. public boolean verifyBrowserRequest(
  187. HttpServletRequest httpServletRequest,
  188. HttpServletResponse httpServletResponse, long userId)
  189. throws Exception {
  190. User user = _userLocalService.fetchUser(userId);
  191. if (user == null) {
  192. if (_log.isWarnEnabled()) {
  193. _log.warn(
  194. "Requested one-time password time-based verification for " +
  195. "nonexistent user " + userId);
  196. }
  197. _routeAuditMessage(
  198. _mfaTimeBasedOTPAuditMessageBuilder.
  199. buildNonexistentUserVerificationFailureAuditMessage(
  200. CompanyThreadLocal.getCompanyId(), userId,
  201. _getClassName()));
  202. return false;
  203. }
  204. if (!isAvailable(user.getUserId())) {
  205. if (_log.isWarnEnabled()) {
  206. _log.warn(
  207. "Requested time-based one time password for user" + userId +
  208. " with incomplete configuration");
  209. }
  210. _routeAuditMessage(
  211. _mfaTimeBasedOTPAuditMessageBuilder.
  212. buildUnconfiguredUserVerificationFailureAuditMessage(
  213. CompanyThreadLocal.getCompanyId(), user,
  214. _getClassName()));
  215. return false;
  216. }
  217. String mfaTimeBasedOTP = ParamUtil.getString(
  218. httpServletRequest, "mfaTimeBasedOTP");
  219. if (Validator.isBlank(mfaTimeBasedOTP)) {
  220. return false;
  221. }
  222. HttpServletRequest originalHttpServletRequest =
  223. _portal.getOriginalServletRequest(httpServletRequest);
  224. String remoteAddress = originalHttpServletRequest.getRemoteAddr();
  225. if (_verify(mfaTimeBasedOTP, user.getUserId())) {
  226. HttpSession httpSession = originalHttpServletRequest.getSession();
  227. httpSession.setAttribute(
  228. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_VALIDATED_AT_TIME,
  229. System.currentTimeMillis());
  230. httpSession.setAttribute(
  231. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_VALIDATED_USER_ID,
  232. userId);
  233. _mfaTimeBasedOTPEntryLocalService.updateAttempts(
  234. userId, remoteAddress, true);
  235. _routeAuditMessage(
  236. _mfaTimeBasedOTPAuditMessageBuilder.
  237. buildVerificationSuccessAuditMessage(
  238. user, _getClassName()));
  239. return true;
  240. }
  241. _mfaTimeBasedOTPEntryLocalService.updateAttempts(
  242. user.getUserId(), remoteAddress, false);
  243. _routeAuditMessage(
  244. _mfaTimeBasedOTPAuditMessageBuilder.
  245. buildVerificationFailureAuditMessage(
  246. user, _getClassName(),
  247. "Incorrect time-based one-time password"));
  248. return false;
  249. }
  250. @Activate
  251. protected void activate(
  252. BundleContext bundleContext, Map<String, Object> properties) {
  253. _mfaTimeBasedOTPConfiguration = ConfigurableUtil.createConfigurable(
  254. MFATimeBasedOTPConfiguration.class, properties);
  255. if (!_mfaTimeBasedOTPConfiguration.enabled()) {
  256. return;
  257. }
  258. if (PropsValues.SESSION_ENABLE_PHISHING_PROTECTION) {
  259. List<String> sessionPhishingProtectedAttributes = new ArrayList<>(
  260. Arrays.asList(
  261. PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES));
  262. sessionPhishingProtectedAttributes.add(
  263. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_VALIDATED_AT_TIME);
  264. sessionPhishingProtectedAttributes.add(
  265. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_VALIDATED_USER_ID);
  266. PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES =
  267. sessionPhishingProtectedAttributes.toArray(new String[0]);
  268. }
  269. _serviceRegistration = bundleContext.registerService(
  270. new String[] {
  271. BrowserMFAChecker.class.getName(),
  272. SetupMFAChecker.class.getName()
  273. },
  274. this, new HashMapDictionary<>(properties));
  275. }
  276. @Deactivate
  277. protected void deactivate() {
  278. if (_serviceRegistration == null) {
  279. return;
  280. }
  281. _serviceRegistration.unregister();
  282. if (PropsValues.SESSION_ENABLE_PHISHING_PROTECTION) {
  283. List<String> sessionPhishingProtectedAttributes = new ArrayList<>(
  284. Arrays.asList(
  285. PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES));
  286. sessionPhishingProtectedAttributes.remove(
  287. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_VALIDATED_AT_TIME);
  288. sessionPhishingProtectedAttributes.remove(
  289. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_VALIDATED_USER_ID);
  290. PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES =
  291. sessionPhishingProtectedAttributes.toArray(new String[0]);
  292. }
  293. }
  294. private String _getClassName() {
  295. Class<?> clazz = getClass();
  296. return clazz.getName();
  297. }
  298. private boolean _isVerified(HttpSession httpSession, long userId) {
  299. User user = _userLocalService.fetchUser(userId);
  300. if (user == null) {
  301. if (_log.isWarnEnabled()) {
  302. _log.warn(
  303. "Requested one-time password email verification for " +
  304. "nonexistent user " + userId);
  305. }
  306. _routeAuditMessage(
  307. _mfaTimeBasedOTPAuditMessageBuilder.
  308. buildNonexistentUserVerificationFailureAuditMessage(
  309. CompanyThreadLocal.getCompanyId(), userId,
  310. _getClassName()));
  311. return false;
  312. }
  313. if (httpSession == null) {
  314. _routeAuditMessage(
  315. _mfaTimeBasedOTPAuditMessageBuilder.
  316. buildNotVerifiedAuditMessage(
  317. user, _getClassName(), "Empty session"));
  318. return false;
  319. }
  320. Object mfaTimeBasedOTPValidatedUserId = httpSession.getAttribute(
  321. MFATimeBasedOTPWebKeys.MFA_TIME_BASED_OTP_VALIDATED_USER_ID);
  322. if (mfaTimeBasedOTPValidatedUserId == null) {
  323. _routeAuditMessage(
  324. _mfaTimeBasedOTPAuditMessageBuilder.
  325. buildNotVerifiedAuditMessage(
  326. user, _getClassName(), "Not verified yet"));
  327. return false;
  328. }
  329. if (!Objects.equals(mfaTimeBasedOTPValidatedUserId, userId)) {
  330. _routeAuditMessage(
  331. _mfaTimeBasedOTPAuditMessageBuilder.
  332. buildNotVerifiedAuditMessage(
  333. user, _getClassName(), "Not the same user"));
  334. return false;
  335. }
  336. return true;
  337. }
  338. private void _routeAuditMessage(AuditMessage auditMessage) {
  339. if (_mfaTimeBasedOTPAuditMessageBuilder != null) {
  340. _mfaTimeBasedOTPAuditMessageBuilder.routeAuditMessage(auditMessage);
  341. }
  342. }
  343. private boolean _verify(String timeBasedOtpValue, long userId) {
  344. MFATimeBasedOTPEntry mfaTimeBasedOTPEntry =
  345. _mfaTimeBasedOTPEntryLocalService.fetchMFATimeBasedOTPEntryByUserId(
  346. userId);
  347. if (mfaTimeBasedOTPEntry != null) {
  348. return MFATimeBasedOTPUtil.verifyTimeBasedOTP(
  349. _mfaTimeBasedOTPConfiguration.clockSkew(),
  350. mfaTimeBasedOTPEntry.getSharedSecret(), timeBasedOtpValue);
  351. }
  352. return false;
  353. }
  354. private static final Log _log = LogFactoryUtil.getLog(
  355. TimeBasedOTPBrowserSetupMFAChecker.class);
  356. @Reference(cardinality = ReferenceCardinality.OPTIONAL)
  357. private MFATimeBasedOTPAuditMessageBuilder
  358. _mfaTimeBasedOTPAuditMessageBuilder;
  359. private MFATimeBasedOTPConfiguration _mfaTimeBasedOTPConfiguration;
  360. @Reference
  361. private MFATimeBasedOTPEntryLocalService _mfaTimeBasedOTPEntryLocalService;
  362. @Reference
  363. private Portal _portal;
  364. private ServiceRegistration<?> _serviceRegistration;
  365. @Reference(
  366. target = "(osgi.web.symbolicname=com.liferay.multi.factor.authentication.timebased.otp.web)"
  367. )
  368. private ServletContext _servletContext;
  369. @Reference
  370. private UserLocalService _userLocalService;
  371. }