/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.security.sso.ntlm.internal.servlet.filter;
import com.liferay.portal.instances.service.PortalInstancesLocalService;
import com.liferay.portal.kernel.cache.PortalCache;
import com.liferay.portal.kernel.cache.SingleVMPool;
import com.liferay.portal.kernel.io.BigEndianCodec;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.module.configuration.ConfigurationProvider;
import com.liferay.portal.kernel.security.SecureRandomUtil;
import com.liferay.portal.kernel.servlet.BaseFilter;
import com.liferay.portal.kernel.servlet.BrowserSniffer;
import com.liferay.portal.kernel.servlet.HttpHeaders;
import com.liferay.portal.kernel.settings.CompanyServiceSettingsLocator;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.security.sso.ntlm.configuration.NtlmConfiguration;
import com.liferay.portal.security.sso.ntlm.constants.NtlmConstants;
import com.liferay.portal.security.sso.ntlm.constants.NtlmWebKeys;
import com.liferay.portal.security.sso.ntlm.internal.NetlogonConnectionManager;
import com.liferay.portal.security.sso.ntlm.internal.NtlmManager;
import com.liferay.portal.security.sso.ntlm.internal.NtlmUserAccount;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jcifs.Config;
import jcifs.util.Base64;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
/**
* Participates in every login that triggers an HTTP request to Liferay Portal.
* It carries out one of the following tasks:
*
* <ol>
* <li>
* The filter looks for an Authorization request header with a value
* starting with the string <code>NTLM</code>. If found, and the 9th byte (
* <code>NTLM message type</code>) of the header value is <code>1</code> then
* this is a type 1 message from the client. The filter then prepares and
* returns a type 2 message which contains a nonce (a.k.a. challenge), and the
* configured Domain for which clients should provide credentials. The type 2
* message is placed into the WWW-Authenticate response header and HTTP response
* code 401 is issued. The nonce is associated with the HTTP session.
* </li>
* <li>
* If no nonce is found associated with the HTTP session, a simple value of
* <code>NTLM</code> is placed into the WWW-Authenticate response header and
* HTTP response code 401 is issued. This simulates case 1 when the client
* responds.
* </li>
* <li>
* At this stage, the Authorization request header has been received, but
* the 8th byte is not equal to <code>1</code>, and a nonce was previously
* associated with the HTTP session (see case 1). This means that the client has
* now sent its username, domain, workstation name, and the result of encrypting
* the hashed password with the NONCE. This is sent in the Authorization header.
* This is known as a type 3 message. This is now authenticated against the
* configured Domain Controller by passing it the type 3 message together with
* the NONCE. To achieve this, the Domain Controller retrieves the user's actual
* hashed password from the users directory and then repeats the same steps that
* the client took previously. If the same encryption result is achieved, then
* authentication is successful and the request attribute
* <code>NTLM_REMOTE_USER</code> is set equal to the username (known as screen
* name in Liferay terminology), in preparation for the
* {@link
* com.liferay.portal.security.sso.ntlm.internal.auto.login.NTLMAutoLogin} class
* to log the user in (see above).
* </li>
* </ol>
*
* @author Bruno Farache
* @author Marcus Schmidke
* @author Brian Wing Shun Chan
* @author Wesley Gong
* @author Marcellus Tavares
* @author Michael C. Han
*/
@Component(
configurationPid = "com.liferay.portal.security.sso.ntlm.configuration.NtlmConfiguration",
immediate = true,
property = {
"before-filter=Auto Login Filter", "dispatcher=FORWARD",
"dispatcher=REQUEST", "servlet-context-name=",
"servlet-filter-name=SSO Ntlm Filter", "url-pattern=/c/portal/login"
},
service = {Filter.class, NtlmFilter.class}
)
public class NtlmFilter extends BaseFilter {
@Override
public boolean isFilterEnabled(
HttpServletRequest request, HttpServletResponse response) {
if (!_browserSniffer.isIe(request)) {
return false;
}
long companyId = _portalInstancesLocalService.getCompanyId(request);
try {
NtlmConfiguration ntlmConfiguration =
_configurationProvider.getConfiguration(
NtlmConfiguration.class,
new CompanyServiceSettingsLocator(
companyId, NtlmConstants.SERVICE_NAME));
return ntlmConfiguration.enabled();
}
catch (Exception e) {
_log.error(e, e);
}
return false;
}
@Reference(unbind = "-")
public void setNetlogonConnectionManager(
NetlogonConnectionManager netlogonConnectionManager) {
_netlogonConnectionManager = netlogonConnectionManager;
}
@Reference(unbind = "-")
public void setSingleVMPool(SingleVMPool singleVMPool) {
_portalCache = (PortalCache<String, byte[]>)singleVMPool.getPortalCache(
NtlmFilter.class.getName());
}
@Activate
@Modified
protected void activate(Map<String, Object> properties) {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
String key = entry.getKey();
if (key.contains("jcifs.")) {
String value = (String)entry.getValue();
Config.setProperty(key, value);
}
}
_ntlmManagers.clear();
}
@Override
protected Log getLog() {
return _log;
}
protected NtlmManager getNtlmManager(long companyId) throws Exception {
NtlmConfiguration ntlmConfiguration =
_configurationProvider.getConfiguration(
NtlmConfiguration.class,
new CompanyServiceSettingsLocator(
companyId, NtlmConstants.SERVICE_NAME));
String domain = ntlmConfiguration.domain();
String domainController = ntlmConfiguration.domainController();
String domainControllerName = ntlmConfiguration.domainControllerName();
String serviceAccount = ntlmConfiguration.serviceAccount();
String servicePassword = ntlmConfiguration.servicePassword();
NtlmManager ntlmManager = _ntlmManagers.get(companyId);
if (ntlmManager == null) {
ntlmManager = new NtlmManager(
_netlogonConnectionManager, domain, domainController,
domainControllerName, serviceAccount, servicePassword);
_ntlmManagers.put(companyId, ntlmManager);
}
else {
if (!Objects.equals(ntlmManager.getDomain(), domain) ||
!Objects.equals(
ntlmManager.getDomainController(), domainController) ||
!Objects.equals(
ntlmManager.getDomainControllerName(),
domainControllerName) ||
!Objects.equals(
ntlmManager.getServiceAccount(), serviceAccount) ||
!Objects.equals(
ntlmManager.getServicePassword(), servicePassword)) {
ntlmManager.setConfiguration(
domain, domainController, domainControllerName,
serviceAccount, servicePassword);
}
}
return ntlmManager;
}
protected String getPortalCacheKey(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return request.getRemoteAddr();
}
return session.getId();
}
@Override
protected void processFilter(
HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain)
throws Exception {
// Type 1 NTLM requests from browser can (and should) always immediately
// be replied to with an Type 2 NTLM response, no matter whether we're
// yet logging in or whether it is much later in the session.
HttpSession session = request.getSession(false);
long companyId = _portalInstancesLocalService.getCompanyId(request);
String authorization = GetterUtil.getString(
request.getHeader(HttpHeaders.AUTHORIZATION));
if (authorization.startsWith("NTLM")) {
NtlmManager ntlmManager = getNtlmManager(companyId);
String portalCacheKey = getPortalCacheKey(request);
byte[] src = Base64.decode(authorization.substring(5));
if (src[8] == 1) {
byte[] serverChallenge = new byte[8];
BigEndianCodec.putLong(
serverChallenge, 0, SecureRandomUtil.nextLong());
byte[] challengeMessage = ntlmManager.negotiate(
src, serverChallenge);
authorization = Base64.encode(challengeMessage);
response.setContentLength(0);
response.setHeader(
HttpHeaders.WWW_AUTHENTICATE, "NTLM " + authorization);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.flushBuffer();
_portalCache.put(portalCacheKey, serverChallenge);
// Interrupt filter chain, send response. Browser will
// immediately post a new request.
return;
}
byte[] serverChallenge = _portalCache.get(portalCacheKey);
if (serverChallenge == null) {
response.setContentLength(0);
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "NTLM");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.flushBuffer();
return;
}
NtlmUserAccount ntlmUserAccount = null;
try {
ntlmUserAccount = ntlmManager.authenticate(
src, serverChallenge);
}
catch (Exception e) {
_log.error("Unable to perform NTLM authentication", e);
}
finally {
_portalCache.remove(portalCacheKey);
}
if (ntlmUserAccount == null) {
response.setContentLength(0);
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "NTLM");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.flushBuffer();
return;
}
if (_log.isDebugEnabled()) {
_log.debug("NTLM remote user " + ntlmUserAccount.getUserName());
}
request.setAttribute(
NtlmWebKeys.NTLM_REMOTE_USER, ntlmUserAccount.getUserName());
if (session != null) {
session.setAttribute(
NtlmWebKeys.NTLM_USER_ACCOUNT, ntlmUserAccount);
}
}
String path = request.getPathInfo();
if ((path != null) && path.endsWith("/login")) {
NtlmUserAccount ntlmUserAccount = null;
if (session != null) {
ntlmUserAccount = (NtlmUserAccount)session.getAttribute(
NtlmWebKeys.NTLM_USER_ACCOUNT);
}
if (ntlmUserAccount == null) {
response.setContentLength(0);
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "NTLM");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.flushBuffer();
return;
}
else {
request.setAttribute(
NtlmWebKeys.NTLM_REMOTE_USER,
ntlmUserAccount.getUserName());
}
}
processFilter(
NtlmPostFilter.class.getName(), request, response, filterChain);
}
@Reference(unbind = "-")
protected void setConfigurationProvider(
ConfigurationProvider configurationProvider) {
_configurationProvider = configurationProvider;
}
private static final Log _log = LogFactoryUtil.getLog(NtlmFilter.class);
@Reference
private BrowserSniffer _browserSniffer;
private ConfigurationProvider _configurationProvider;
private NetlogonConnectionManager _netlogonConnectionManager;
private final Map<Long, NtlmManager> _ntlmManagers =
new ConcurrentHashMap<>();
private PortalCache<String, byte[]> _portalCache;
@Reference
private PortalInstancesLocalService _portalInstancesLocalService;
}