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.tokenauth.servlet;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.security.Principal;
022
023import javax.servlet.ServletException;
024import javax.servlet.http.HttpServlet;
025import javax.servlet.http.HttpServletRequest;
026import javax.servlet.http.HttpServletResponse;
027
028import org.apache.commons.httpclient.HttpStatus;
029import org.apache.commons.httpclient.util.URIUtil;
030import org.apache.commons.lang.StringUtils;
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.ecm.core.api.NuxeoPrincipal;
034import org.nuxeo.ecm.platform.ui.web.auth.service.AuthenticationPluginDescriptor;
035import org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService;
036import org.nuxeo.ecm.platform.ui.web.auth.token.TokenAuthenticator;
037import org.nuxeo.ecm.tokenauth.TokenAuthenticationException;
038import org.nuxeo.ecm.tokenauth.service.TokenAuthenticationService;
039import org.nuxeo.runtime.api.Framework;
040
041/**
042 * Servlet that allows to get a unique authentication token given the request Principal and some device information
043 * passed as request parameters: application name, device id, device description, permission. An error response will be
044 * sent with a 400 status code if one of the required parameters is null or empty. All parameters are required except
045 * for the device description.
046 * <p>
047 * The token is provided by the {@link TokenAuthenticationService}.
048 *
049 * @author Antoine Taillefer (ataillefer@nuxeo.com)
050 * @since 5.7
051 */
052public class TokenAuthenticationServlet extends HttpServlet {
053
054    private static final long serialVersionUID = 7792388601558509103L;
055
056    private static final Log log = LogFactory.getLog(TokenAuthenticationServlet.class);
057
058    protected static final String TOKEN_AUTH_PLUGIN_NAME = "TOKEN_AUTH";
059
060    protected static final String APPLICATION_NAME_PARAM = "applicationName";
061
062    protected static final String DEVICE_ID_PARAM = "deviceId";
063
064    protected static final String DEVICE_DESCRIPTION_PARAM = "deviceDescription";
065
066    protected static final String PERMISSION_PARAM = "permission";
067
068    protected static final String REVOKE_PARAM = "revoke";
069
070    @Override
071    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
072
073        // Don't provide token for anonymous user unless 'allowAnonymous' parameter is explicitly set to true in
074        // the authentication plugin configuration
075        Principal principal = req.getUserPrincipal();
076        if (principal instanceof NuxeoPrincipal && ((NuxeoPrincipal) principal).isAnonymous()) {
077            PluggableAuthenticationService authenticationService = (PluggableAuthenticationService) Framework.getRuntime().getComponent(
078                    PluggableAuthenticationService.NAME);
079            AuthenticationPluginDescriptor tokenAuthPluginDesc = authenticationService.getDescriptor(TOKEN_AUTH_PLUGIN_NAME);
080            if (tokenAuthPluginDesc == null
081                    || !(Boolean.valueOf(tokenAuthPluginDesc.getParameters().get(TokenAuthenticator.ALLOW_ANONYMOUS_KEY)))) {
082                log.debug("Anonymous user is not allowed to acquire an authentication token.");
083                resp.sendError(HttpStatus.SC_UNAUTHORIZED);
084                return;
085            }
086
087        }
088
089        // Get request parameters
090        String applicationName = req.getParameter(APPLICATION_NAME_PARAM);
091        String deviceId = req.getParameter(DEVICE_ID_PARAM);
092        String deviceDescription = req.getParameter(DEVICE_DESCRIPTION_PARAM);
093        String permission = req.getParameter(PERMISSION_PARAM);
094        String revokeParam = req.getParameter(REVOKE_PARAM);
095        boolean revoke = Boolean.valueOf(revokeParam);
096
097        // If one of the required parameters is null or empty, send an
098        // error with the 400 status
099        if (!revoke
100                && (StringUtils.isEmpty(applicationName) || StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(permission))) {
101            log.error("The following request parameters are mandatory to acquire an authentication token: applicationName, deviceId, permission.");
102            resp.sendError(HttpStatus.SC_BAD_REQUEST);
103            return;
104        }
105        if (revoke && (StringUtils.isEmpty(applicationName) || StringUtils.isEmpty(deviceId))) {
106            log.error("The following request parameters are mandatory to revoke an authentication token: applicationName, deviceId.");
107            resp.sendError(HttpStatus.SC_BAD_REQUEST);
108            return;
109        }
110
111        // Decode parameters
112        applicationName = URIUtil.decode(applicationName);
113        deviceId = URIUtil.decode(deviceId);
114        if (!StringUtils.isEmpty(deviceDescription)) {
115            deviceDescription = URIUtil.decode(deviceDescription);
116        }
117        if (!StringUtils.isEmpty(permission)) {
118            permission = URIUtil.decode(permission);
119        }
120
121        // Get user name from request Principal
122        if (principal == null) {
123            resp.sendError(HttpStatus.SC_UNAUTHORIZED);
124            return;
125        }
126        String userName = principal.getName();
127
128        // Write response
129        String response = null;
130        int statusCode;
131        TokenAuthenticationService tokenAuthService = Framework.getLocalService(TokenAuthenticationService.class);
132        try {
133            // Token acquisition: acquire token and write it to the response
134            // body
135            if (!revoke) {
136                response = tokenAuthService.acquireToken(userName, applicationName, deviceId, deviceDescription,
137                        permission);
138                statusCode = 201;
139            }
140            // Token revocation
141            else {
142                String token = tokenAuthService.getToken(userName, applicationName, deviceId);
143                if (token == null) {
144                    response = String.format(
145                            "No token found for userName %s, applicationName %s and deviceId %s; nothing to do.",
146                            userName, applicationName, deviceId);
147                    statusCode = 400;
148                } else {
149                    tokenAuthService.revokeToken(token);
150                    response = String.format("Token revoked for userName %s, applicationName %s and deviceId %s.",
151                            userName, applicationName, deviceId);
152                    statusCode = 202;
153                }
154            }
155            sendTextResponse(resp, response, statusCode);
156        } catch (TokenAuthenticationException e) {
157            // Should never happen as parameters have already been checked
158            resp.sendError(HttpStatus.SC_NOT_FOUND);
159        }
160    }
161
162    protected void sendTextResponse(HttpServletResponse resp, String textResponse, int statusCode) throws IOException {
163
164        resp.setContentType("text/plain");
165        resp.setStatus(statusCode);
166        resp.setContentLength(textResponse.getBytes().length);
167        OutputStream out = resp.getOutputStream();
168        out.write(textResponse.getBytes());
169        out.close();
170    }
171
172}