PageRenderTime 52ms CodeModel.GetById 34ms app.highlight 16ms RepoModel.GetById 0ms 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
 15package com.liferay.multi.factor.authentication.email.otp.web.internal.checker;
 16
 17import com.liferay.multi.factor.authentication.email.otp.configuration.MFAEmailOTPConfiguration;
 18import com.liferay.multi.factor.authentication.email.otp.model.MFAEmailOTPEntry;
 19import com.liferay.multi.factor.authentication.email.otp.service.MFAEmailOTPEntryLocalService;
 20import com.liferay.multi.factor.authentication.email.otp.web.internal.audit.MFAEmailOTPAuditMessageBuilder;
 21import com.liferay.multi.factor.authentication.email.otp.web.internal.constants.MFAEmailOTPWebKeys;
 22import com.liferay.multi.factor.authentication.spi.checker.browser.BrowserMFAChecker;
 23import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil;
 24import com.liferay.portal.kernel.audit.AuditMessage;
 25import com.liferay.portal.kernel.exception.PortalException;
 26import com.liferay.portal.kernel.log.Log;
 27import com.liferay.portal.kernel.log.LogFactoryUtil;
 28import com.liferay.portal.kernel.model.User;
 29import com.liferay.portal.kernel.security.auth.CompanyThreadLocal;
 30import com.liferay.portal.kernel.service.UserLocalService;
 31import com.liferay.portal.kernel.util.GetterUtil;
 32import com.liferay.portal.kernel.util.ParamUtil;
 33import com.liferay.portal.kernel.util.Portal;
 34import com.liferay.portal.kernel.util.Time;
 35import com.liferay.portal.util.PropsValues;
 36
 37import java.io.IOException;
 38
 39import java.util.ArrayList;
 40import java.util.Arrays;
 41import java.util.Date;
 42import java.util.List;
 43import java.util.Map;
 44import java.util.Objects;
 45
 46import javax.servlet.RequestDispatcher;
 47import javax.servlet.ServletContext;
 48import javax.servlet.ServletException;
 49import javax.servlet.http.HttpServletRequest;
 50import javax.servlet.http.HttpServletResponse;
 51import javax.servlet.http.HttpSession;
 52
 53import org.osgi.service.component.annotations.Activate;
 54import org.osgi.service.component.annotations.Component;
 55import org.osgi.service.component.annotations.ConfigurationPolicy;
 56import org.osgi.service.component.annotations.Deactivate;
 57import org.osgi.service.component.annotations.Reference;
 58import org.osgi.service.component.annotations.ReferenceCardinality;
 59
 60/**
 61 * @author Arthur Chan
 62 * @author Marta Medio
 63 */
 64@Component(
 65	configurationPid = "com.liferay.multi.factor.authentication.email.otp.configuration.MFAEmailOTPConfiguration.scoped",
 66	configurationPolicy = ConfigurationPolicy.OPTIONAL,
 67	service = BrowserMFAChecker.class
 68)
 69public class EmailOTPBrowserMFAChecker implements BrowserMFAChecker {
 70
 71	@Override
 72	public void includeBrowserVerification(
 73			HttpServletRequest httpServletRequest,
 74			HttpServletResponse httpServletResponse, long userId)
 75		throws IOException, ServletException {
 76
 77		User user = _userLocalService.fetchUser(userId);
 78
 79		if (user == null) {
 80			if (_log.isWarnEnabled()) {
 81				_log.warn(
 82					"Requested one-time password email verification for " +
 83						"nonexistent user " + userId);
 84			}
 85
 86			return;
 87		}
 88
 89		if (_isMaximumAllowedAttemptsReached(user.getUserId())) {
 90			httpServletRequest.setAttribute(
 91				MFAEmailOTPWebKeys.MFA_EMAIL_OTP_FAILED_ATTEMPTS_RETRY_TIMEOUT,
 92				_mfaEmailOTPConfiguration.retryTimeout());
 93		}
 94
 95		HttpServletRequest originalHttpServletRequest =
 96			_portal.getOriginalServletRequest(httpServletRequest);
 97
 98		HttpSession httpSession = originalHttpServletRequest.getSession();
 99
100		httpServletRequest.setAttribute(
101			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_SEND_TO_ADDRESS_OBFUSCATED,
102			obfuscateEmailAddress(user.getEmailAddress()));
103		httpServletRequest.setAttribute(
104			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_SET_AT_TIME,
105			GetterUtil.getLong(
106				httpSession.getAttribute(
107					MFAEmailOTPWebKeys.MFA_EMAIL_OTP_SET_AT_TIME),
108				Long.MIN_VALUE));
109
110		RequestDispatcher requestDispatcher =
111			_servletContext.getRequestDispatcher(
112				"/mfa_email_otp_checker/verify_browser.jsp");
113
114		requestDispatcher.include(httpServletRequest, httpServletResponse);
115
116		httpSession.setAttribute(
117			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_PHASE, "verify");
118		httpSession.setAttribute(
119			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_USER_ID, userId);
120	}
121
122	@Override
123	public boolean isBrowserVerified(
124		HttpServletRequest httpServletRequest, long userId) {
125
126		HttpServletRequest originalHttpServletRequest =
127			_portal.getOriginalServletRequest(httpServletRequest);
128
129		HttpSession httpSession = originalHttpServletRequest.getSession(false);
130
131		if (_isVerified(httpSession, userId)) {
132			return true;
133		}
134
135		return false;
136	}
137
138	@Override
139	public boolean verifyBrowserRequest(
140			HttpServletRequest httpServletRequest,
141			HttpServletResponse httpServletResponse, long userId)
142		throws Exception {
143
144		User user = _userLocalService.fetchUser(userId);
145
146		if (user == null) {
147			if (_log.isWarnEnabled()) {
148				_log.warn(
149					"Requested one-time password email verification for " +
150						"nonexistent user " + userId);
151			}
152
153			_routeAuditMessage(
154				_mfaEmailOTPAuditMessageBuilder.
155					buildNonexistentUserVerificationFailureAuditMessage(
156						CompanyThreadLocal.getCompanyId(), userId,
157						_getClassName()));
158
159			return false;
160		}
161
162		MFAEmailOTPEntry mfaEmailOTPEntry =
163			_mfaEmailOTPEntryLocalService.fetchMFAEmailOTPEntryByUserId(userId);
164
165		if (mfaEmailOTPEntry == null) {
166			_mfaEmailOTPEntryLocalService.addMFAEmailOTPEntry(userId);
167		}
168
169		if (_isMaximumAllowedAttemptsReached(userId)) {
170			_routeAuditMessage(
171				_mfaEmailOTPAuditMessageBuilder.
172					buildVerificationFailureAuditMessage(
173						user, _getClassName(),
174						"Reached maximum allowed attempts"));
175
176			return false;
177		}
178
179		HttpServletRequest originalHttpServletRequest =
180			_portal.getOriginalServletRequest(httpServletRequest);
181
182		HttpSession httpSession = originalHttpServletRequest.getSession();
183
184		String otp = ParamUtil.getString(httpServletRequest, "otp");
185
186		if (_verify(httpSession, otp)) {
187			httpSession.setAttribute(
188				MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_AT_TIME,
189				System.currentTimeMillis());
190			httpSession.setAttribute(
191				MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_USER_ID, userId);
192
193			_mfaEmailOTPEntryLocalService.updateAttempts(
194				userId, originalHttpServletRequest.getRemoteAddr(), true);
195
196			_routeAuditMessage(
197				_mfaEmailOTPAuditMessageBuilder.
198					buildVerificationSuccessAuditMessage(
199						user, _getClassName()));
200
201			return true;
202		}
203
204		_routeAuditMessage(
205			_mfaEmailOTPAuditMessageBuilder.
206				buildVerificationFailureAuditMessage(
207					user, _getClassName(),
208					"Incorrect email one-time password"));
209
210		_mfaEmailOTPEntryLocalService.updateAttempts(
211			userId, originalHttpServletRequest.getRemoteAddr(), false);
212
213		return false;
214	}
215
216	protected static String obfuscateEmailAddress(String emailAddress) {
217		String alias = emailAddress.substring(0, emailAddress.indexOf('@'));
218
219		int maskLength = Math.max(
220			(int)Math.ceil(alias.length() / 2.0), Math.min(3, alias.length()));
221
222		int startIndex = (int)Math.ceil((alias.length() - maskLength) / 2.0);
223
224		int endIndex = startIndex + maskLength;
225
226		char[] chars = emailAddress.toCharArray();
227
228		for (int i = startIndex; i < endIndex; i++) {
229			chars[i] = '*';
230		}
231
232		return new String(chars);
233	}
234
235	@Activate
236	protected void activate(Map<String, Object> properties) {
237		_mfaEmailOTPConfiguration = ConfigurableUtil.createConfigurable(
238			MFAEmailOTPConfiguration.class, properties);
239
240		if (!PropsValues.SESSION_ENABLE_PHISHING_PROTECTION) {
241			return;
242		}
243
244		List<String> sessionPhishingProtectedAttributes = new ArrayList<>(
245			Arrays.asList(PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES));
246
247		sessionPhishingProtectedAttributes.add(
248			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_AT_TIME);
249		sessionPhishingProtectedAttributes.add(
250			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_USER_ID);
251
252		PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES =
253			sessionPhishingProtectedAttributes.toArray(new String[0]);
254	}
255
256	@Deactivate
257	protected void deactivate() {
258		if (!PropsValues.SESSION_ENABLE_PHISHING_PROTECTION) {
259			return;
260		}
261
262		List<String> sessionPhishingProtectedAttributes = new ArrayList<>(
263			Arrays.asList(PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES));
264
265		sessionPhishingProtectedAttributes.remove(
266			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_AT_TIME);
267		sessionPhishingProtectedAttributes.remove(
268			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_USER_ID);
269
270		PropsValues.SESSION_PHISHING_PROTECTED_ATTRIBUTES =
271			sessionPhishingProtectedAttributes.toArray(new String[0]);
272	}
273
274	private String _getClassName() {
275		Class<?> clazz = getClass();
276
277		return clazz.getName();
278	}
279
280	private boolean _isMaximumAllowedAttemptsReached(long userId) {
281		try {
282			MFAEmailOTPEntry mfaEmailOTPEntry =
283				_mfaEmailOTPEntryLocalService.fetchMFAEmailOTPEntryByUserId(
284					userId);
285
286			if (mfaEmailOTPEntry == null) {
287				return false;
288			}
289
290			if ((_mfaEmailOTPConfiguration.failedAttemptsAllowed() >= 0) &&
291				(_mfaEmailOTPConfiguration.failedAttemptsAllowed() <=
292					mfaEmailOTPEntry.getFailedAttempts()) &&
293				(_mfaEmailOTPConfiguration.retryTimeout() >= 0)) {
294
295				Date lastFailDate = mfaEmailOTPEntry.getLastFailDate();
296
297				long time =
298					(_mfaEmailOTPConfiguration.retryTimeout() * Time.SECOND) +
299						lastFailDate.getTime();
300
301				if (time <= System.currentTimeMillis()) {
302					_mfaEmailOTPEntryLocalService.resetFailedAttempts(userId);
303				}
304				else {
305					return true;
306				}
307			}
308		}
309		catch (PortalException portalException) {
310			_log.error(portalException);
311		}
312
313		return false;
314	}
315
316	private boolean _isVerified(HttpSession httpSession, long userId) {
317		User user = _userLocalService.fetchUser(userId);
318
319		if (user == null) {
320			if (_log.isWarnEnabled()) {
321				_log.warn(
322					"Requested one-time password email verification for " +
323						"nonexistent user " + userId);
324			}
325
326			_routeAuditMessage(
327				_mfaEmailOTPAuditMessageBuilder.
328					buildNonexistentUserVerificationFailureAuditMessage(
329						CompanyThreadLocal.getCompanyId(), userId,
330						_getClassName()));
331
332			return false;
333		}
334
335		if (httpSession == null) {
336			_routeAuditMessage(
337				_mfaEmailOTPAuditMessageBuilder.buildNotVerifiedAuditMessage(
338					user, _getClassName(), "Empty session"));
339
340			return false;
341		}
342
343		Object mfaEmailOTPValidatedUserId = httpSession.getAttribute(
344			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_VALIDATED_USER_ID);
345
346		if (mfaEmailOTPValidatedUserId == null) {
347			_routeAuditMessage(
348				_mfaEmailOTPAuditMessageBuilder.buildNotVerifiedAuditMessage(
349					user, _getClassName(), "Not verified yet"));
350
351			return false;
352		}
353
354		if (!Objects.equals(mfaEmailOTPValidatedUserId, userId)) {
355			_routeAuditMessage(
356				_mfaEmailOTPAuditMessageBuilder.buildNotVerifiedAuditMessage(
357					user, _getClassName(), "Not the same user"));
358
359			return false;
360		}
361
362		return true;
363	}
364
365	private void _routeAuditMessage(AuditMessage auditMessage) {
366		if (_mfaEmailOTPAuditMessageBuilder != null) {
367			_mfaEmailOTPAuditMessageBuilder.routeAuditMessage(auditMessage);
368		}
369	}
370
371	private boolean _verify(HttpSession httpSession, String otp) {
372		String expectedMFAEmailOTP = (String)httpSession.getAttribute(
373			MFAEmailOTPWebKeys.MFA_EMAIL_OTP);
374
375		if ((expectedMFAEmailOTP == null) || !expectedMFAEmailOTP.equals(otp)) {
376			return false;
377		}
378
379		httpSession.removeAttribute(MFAEmailOTPWebKeys.MFA_EMAIL_OTP);
380		httpSession.removeAttribute(MFAEmailOTPWebKeys.MFA_EMAIL_OTP_PHASE);
381		httpSession.removeAttribute(
382			MFAEmailOTPWebKeys.MFA_EMAIL_OTP_SET_AT_TIME);
383		httpSession.removeAttribute(MFAEmailOTPWebKeys.MFA_EMAIL_OTP_USER_ID);
384
385		return true;
386	}
387
388	private static final Log _log = LogFactoryUtil.getLog(
389		EmailOTPBrowserMFAChecker.class);
390
391	@Reference(cardinality = ReferenceCardinality.OPTIONAL)
392	private MFAEmailOTPAuditMessageBuilder _mfaEmailOTPAuditMessageBuilder;
393
394	private MFAEmailOTPConfiguration _mfaEmailOTPConfiguration;
395
396	@Reference
397	private MFAEmailOTPEntryLocalService _mfaEmailOTPEntryLocalService;
398
399	@Reference
400	private Portal _portal;
401
402	@Reference(
403		target = "(osgi.web.symbolicname=com.liferay.multi.factor.authentication.email.otp.web)"
404	)
405	private ServletContext _servletContext;
406
407	@Reference
408	private UserLocalService _userLocalService;
409
410}