001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.security.auth.tunnel;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.model.User;
020    import com.liferay.portal.kernel.security.auth.AuthException;
021    import com.liferay.portal.kernel.security.auth.RemoteAuthException;
022    import com.liferay.portal.kernel.security.auth.http.HttpAuthManagerUtil;
023    import com.liferay.portal.kernel.security.auth.http.HttpAuthorizationHeader;
024    import com.liferay.portal.kernel.security.auth.tunnel.TunnelAuthenticationManager;
025    import com.liferay.portal.kernel.service.UserLocalServiceUtil;
026    import com.liferay.portal.kernel.servlet.HttpHeaders;
027    import com.liferay.portal.kernel.util.GetterUtil;
028    import com.liferay.portal.kernel.util.PropsKeys;
029    import com.liferay.portal.kernel.util.StringUtil;
030    import com.liferay.portal.kernel.util.Validator;
031    import com.liferay.portal.util.PortalInstances;
032    import com.liferay.portal.util.PropsValues;
033    import com.liferay.util.Encryptor;
034    import com.liferay.util.EncryptorException;
035    
036    import java.net.HttpURLConnection;
037    
038    import java.security.Key;
039    
040    import java.util.Objects;
041    
042    import javax.crypto.spec.SecretKeySpec;
043    
044    import javax.servlet.http.HttpServletRequest;
045    
046    import org.apache.commons.codec.DecoderException;
047    import org.apache.commons.codec.binary.Hex;
048    
049    /**
050     * @author Tomas Polesovsky
051     */
052    public class TunnelAuthenticationManagerImpl
053            implements TunnelAuthenticationManager {
054    
055            @Override
056            public long getUserId(HttpServletRequest httpServletRequest)
057                    throws AuthException {
058    
059                    HttpAuthorizationHeader httpAuthorizationHeader =
060                            HttpAuthManagerUtil.parse(httpServletRequest);
061    
062                    if (httpAuthorizationHeader == null) {
063                            return 0;
064                    }
065    
066                    String scheme = httpAuthorizationHeader.getScheme();
067    
068                    if (!StringUtil.equalsIgnoreCase(
069                                    scheme, HttpAuthorizationHeader.SCHEME_BASIC)) {
070    
071                            AuthException authException = new RemoteAuthException(
072                                    "Invalid scheme " + scheme);
073    
074                            authException.setType(AuthException.INTERNAL_SERVER_ERROR);
075    
076                            throw authException;
077                    }
078    
079                    String expectedPassword = null;
080    
081                    String login = httpAuthorizationHeader.getAuthParameter(
082                            HttpAuthorizationHeader.AUTH_PARAMETER_NAME_USERNAME);
083    
084                    try {
085                            expectedPassword = Encryptor.encrypt(getSharedSecretKey(), login);
086                    }
087                    catch (EncryptorException ee) {
088                            AuthException authException = new RemoteAuthException(ee);
089    
090                            authException.setType(AuthException.INTERNAL_SERVER_ERROR);
091    
092                            throw authException;
093                    }
094                    catch (AuthException ae) {
095                            AuthException authException = new RemoteAuthException(ae);
096    
097                            authException.setType(ae.getType());
098    
099                            throw authException;
100                    }
101    
102                    String password = httpAuthorizationHeader.getAuthParameter(
103                            HttpAuthorizationHeader.AUTH_PARAMETER_NAME_PASSWORD);
104    
105                    if (!Objects.equals(expectedPassword, password)) {
106                            AuthException authException = new RemoteAuthException();
107    
108                            authException.setType(RemoteAuthException.WRONG_SHARED_SECRET);
109    
110                            throw authException;
111                    }
112    
113                    User user = UserLocalServiceUtil.fetchUser(GetterUtil.getLong(login));
114    
115                    if (user == null) {
116                            long companyId = PortalInstances.getCompanyId(httpServletRequest);
117    
118                            user = UserLocalServiceUtil.fetchUserByEmailAddress(
119                                    companyId, login);
120    
121                            if (user == null) {
122                                    user = UserLocalServiceUtil.fetchUserByScreenName(
123                                            companyId, login);
124                            }
125                    }
126    
127                    if (user == null) {
128                            AuthException authException = new RemoteAuthException(
129                                    "Unable to find user " + login);
130    
131                            authException.setType(AuthException.INTERNAL_SERVER_ERROR);
132    
133                            throw authException;
134                    }
135    
136                    return user.getUserId();
137            }
138    
139            @Override
140            public void setCredentials(
141                            String login, HttpURLConnection httpURLConnection)
142                    throws Exception {
143    
144                    if (Validator.isBlank(login)) {
145                            throw new IllegalArgumentException("Login is null");
146                    }
147    
148                    HttpAuthorizationHeader httpAuthorizationHeader =
149                            new HttpAuthorizationHeader(HttpAuthorizationHeader.SCHEME_BASIC);
150    
151                    String password = Encryptor.encrypt(getSharedSecretKey(), login);
152    
153                    httpAuthorizationHeader.setAuthParameter(
154                            HttpAuthorizationHeader.AUTH_PARAMETER_NAME_PASSWORD, password);
155    
156                    httpAuthorizationHeader.setAuthParameter(
157                            HttpAuthorizationHeader.AUTH_PARAMETER_NAME_USERNAME, login);
158                    httpURLConnection.setRequestProperty(
159                            HttpHeaders.AUTHORIZATION, httpAuthorizationHeader.toString());
160            }
161    
162            protected Key getSharedSecretKey() throws AuthException {
163                    String sharedSecret = PropsValues.TUNNELING_SERVLET_SHARED_SECRET;
164                    boolean sharedSecretHex =
165                            PropsValues.TUNNELING_SERVLET_SHARED_SECRET_HEX;
166    
167                    if (Validator.isNull(sharedSecret)) {
168                            String message =
169                                    "Please configure " + PropsKeys.TUNNELING_SERVLET_SHARED_SECRET;
170    
171                            if (_log.isWarnEnabled()) {
172                                    _log.warn(message);
173                            }
174    
175                            AuthException authException = new AuthException(message);
176    
177                            authException.setType(AuthException.NO_SHARED_SECRET);
178    
179                            throw authException;
180                    }
181    
182                    byte[] key = null;
183    
184                    if (sharedSecretHex) {
185                            try {
186                                    key = Hex.decodeHex(sharedSecret.toCharArray());
187                            }
188                            catch (DecoderException de) {
189                                    if (_log.isWarnEnabled()) {
190                                            _log.warn(de, de);
191                                    }
192    
193                                    AuthException authException = new AuthException();
194    
195                                    authException.setType(AuthException.INVALID_SHARED_SECRET);
196    
197                                    throw authException;
198                            }
199                    }
200                    else {
201                            key = sharedSecret.getBytes();
202                    }
203    
204                    if (key.length < 8) {
205                            String message =
206                                    PropsKeys.TUNNELING_SERVLET_SHARED_SECRET + " is too short";
207    
208                            if (_log.isWarnEnabled()) {
209                                    _log.warn(message);
210                            }
211    
212                            AuthException authException = new AuthException(message);
213    
214                            authException.setType(AuthException.INVALID_SHARED_SECRET);
215    
216                            throw authException;
217                    }
218    
219                    if (StringUtil.equalsIgnoreCase(
220                                    PropsValues.TUNNELING_SERVLET_ENCRYPTION_ALGORITHM, "AES") &&
221                            (key.length != 16) && (key.length != 32)) {
222    
223                            String message =
224                                    PropsKeys.TUNNELING_SERVLET_SHARED_SECRET +
225                                            " must have 16 or 32 bytes when used with AES";
226    
227                            if (_log.isWarnEnabled()) {
228                                    _log.warn(message);
229                            }
230    
231                            AuthException authException = new AuthException(message);
232    
233                            authException.setType(AuthException.INVALID_SHARED_SECRET);
234    
235                            throw authException;
236                    }
237    
238                    return new SecretKeySpec(
239                            key, PropsValues.TUNNELING_SERVLET_ENCRYPTION_ALGORITHM);
240            }
241    
242            private static final Log _log = LogFactoryUtil.getLog(
243                    TunnelAuthenticationManagerImpl.class);
244    
245    }