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