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}