001/*
002 * (C) Copyright 2006-20012 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Antoine Taillefer
016 */
017package org.nuxeo.ecm.platform.ui.web.auth.token;
018
019import java.util.List;
020import java.util.Map;
021
022import javax.security.auth.spi.LoginModule;
023import javax.servlet.http.Cookie;
024import javax.servlet.http.HttpServletRequest;
025import javax.servlet.http.HttpServletResponse;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo;
030import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin;
031import org.nuxeo.ecm.platform.usermanager.UserManager;
032import org.nuxeo.ecm.tokenauth.service.TokenAuthenticationService;
033import org.nuxeo.runtime.api.Framework;
034
035/**
036 * Handles authentication with a token sent as a request header.
037 * <p>
038 * The user is retrieved with the {@link TokenAuthenticationService}.
039 * <p>
040 * This Authentication Plugin is configured to be used with the Trusting_LM {@link LoginModule} plugin => no password
041 * check will be done, a principal will be created from the userName if the user exists in the user directory.
042 *
043 * @author Antoine Taillefer (ataillefer@nuxeo.com)
044 * @since 5.7
045 */
046public class TokenAuthenticator implements NuxeoAuthenticationPlugin {
047
048    public static final String ALLOW_ANONYMOUS_KEY = "allowAnonymous";
049
050    private static final String HTTPS = "https";
051
052    private static final String LOCALHOST = "localhost";
053
054    private static final Log log = LogFactory.getLog(TokenAuthenticator.class);
055
056    protected static final String TOKEN_HEADER = "X-Authentication-Token";
057
058    protected boolean allowAnonymous = false;
059
060    @Override
061    public Boolean handleLoginPrompt(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String baseURL) {
062        return false;
063    }
064
065    @Override
066    public UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest httpRequest,
067            HttpServletResponse httpResponse) {
068
069        String token = getTokenFromRequest(httpRequest);
070
071        if (token == null) {
072            log.debug(String.format("Found no '%s' header in the request.", TOKEN_HEADER));
073            return null;
074        }
075
076        String userName = getUserByToken(token);
077        if (userName == null) {
078            log.debug(String.format("No user bound to the token '%s' (maybe it has been revoked), returning null.",
079                    token));
080            return null;
081        }
082        // Don't retrieve identity for anonymous user unless 'allowAnonymous' parameter is explicitly set to true in
083        // the authentication plugin configuration
084        UserManager userManager = Framework.getService(UserManager.class);
085        if (userManager != null && userName.equals(userManager.getAnonymousUserId()) && !allowAnonymous) {
086            log.debug("Anonymous user is not allowed to get authenticated by token, returning null.");
087            return null;
088        }
089        return new UserIdentificationInfo(userName, userName);
090    }
091
092    /**
093     * Gets the token from the request if present else null.
094     *
095     * @param httpRequest
096     * @return
097     * @since 5.9.2
098     */
099    private String getTokenFromRequest(HttpServletRequest httpRequest) {
100        String token = httpRequest.getHeader(TOKEN_HEADER);
101
102        // If we don't find the token in request header, let's check in cookies
103        if (token == null && httpRequest.getCookies() != null) {
104            Cookie cookie = getTokenCookie(httpRequest);
105            if (cookie != null && isAllowedToUseCookieToken(httpRequest)) {
106                return cookie.getValue();
107            }
108        }
109        return token;
110    }
111
112    /**
113     * Returns the token from the cookies if found else null.
114     *
115     * @param httpRequest
116     * @return
117     * @since 5.9.2
118     */
119    private Cookie getTokenCookie(HttpServletRequest httpRequest) {
120        Cookie[] cookies = httpRequest.getCookies();
121        if (cookies != null) {
122            for (Cookie cookie : cookies) {
123                if (cookie.getName().equals(TOKEN_HEADER) && isAllowedToUseCookieToken(httpRequest)) {
124                    return cookie;
125                }
126            }
127        }
128        return null;
129    }
130
131    /**
132     * Guard the use of cookie token to htpps only or localhost.
133     *
134     * @param httpRequest
135     * @return
136     * @since 5.9.2
137     */
138    private boolean isAllowedToUseCookieToken(HttpServletRequest req) {
139        if (LOCALHOST.equals(req.getServerName())) {
140            return true;
141        }
142        return HTTPS.equals(req.getScheme());
143    }
144
145    @Override
146    public Boolean needLoginPrompt(HttpServletRequest httpRequest) {
147        return false;
148    }
149
150    @Override
151    public void initPlugin(Map<String, String> parameters) {
152        if (parameters.containsKey(ALLOW_ANONYMOUS_KEY)) {
153            allowAnonymous = Boolean.valueOf(parameters.get(ALLOW_ANONYMOUS_KEY));
154        }
155    }
156
157    @Override
158    public List<String> getUnAuthenticatedURLPrefix() {
159        return null;
160    }
161
162    protected String getUserByToken(String token) {
163
164        TokenAuthenticationService tokenAuthService = Framework.getLocalService(TokenAuthenticationService.class);
165        return tokenAuthService.getUserName(token);
166    }
167
168}