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