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