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 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.lang.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 = (PluggableAuthenticationService) Framework.getRuntime().getComponent( 081 PluggableAuthenticationService.NAME); 082 AuthenticationPluginDescriptor tokenAuthPluginDesc = authenticationService.getDescriptor(TOKEN_AUTH_PLUGIN_NAME); 083 if (tokenAuthPluginDesc == null 084 || !(Boolean.valueOf(tokenAuthPluginDesc.getParameters().get(TokenAuthenticator.ALLOW_ANONYMOUS_KEY)))) { 085 log.debug("Anonymous user is not allowed to acquire an authentication token."); 086 resp.sendError(HttpStatus.SC_UNAUTHORIZED); 087 return; 088 } 089 090 } 091 092 // Get request parameters 093 String applicationName = req.getParameter(APPLICATION_NAME_PARAM); 094 String deviceId = req.getParameter(DEVICE_ID_PARAM); 095 String deviceDescription = req.getParameter(DEVICE_DESCRIPTION_PARAM); 096 String permission = req.getParameter(PERMISSION_PARAM); 097 String revokeParam = req.getParameter(REVOKE_PARAM); 098 boolean revoke = Boolean.valueOf(revokeParam); 099 100 // If one of the required parameters is null or empty, send an 101 // error with the 400 status 102 if (!revoke 103 && (StringUtils.isEmpty(applicationName) || StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(permission))) { 104 log.error("The following request parameters are mandatory to acquire an authentication token: applicationName, deviceId, permission."); 105 resp.sendError(HttpStatus.SC_BAD_REQUEST); 106 return; 107 } 108 if (revoke && (StringUtils.isEmpty(applicationName) || StringUtils.isEmpty(deviceId))) { 109 log.error("The following request parameters are mandatory to revoke an authentication token: applicationName, deviceId."); 110 resp.sendError(HttpStatus.SC_BAD_REQUEST); 111 return; 112 } 113 114 // Get user name from request Principal 115 if (principal == null) { 116 resp.sendError(HttpStatus.SC_UNAUTHORIZED); 117 return; 118 } 119 String userName = principal.getName(); 120 121 // Write response 122 String response = null; 123 int statusCode; 124 TokenAuthenticationService tokenAuthService = Framework.getService(TokenAuthenticationService.class); 125 try { 126 // Token acquisition: acquire token and write it to the response 127 // body 128 if (!revoke) { 129 response = tokenAuthService.acquireToken(userName, applicationName, deviceId, deviceDescription, 130 permission); 131 statusCode = 201; 132 } 133 // Token revocation 134 else { 135 String token = tokenAuthService.getToken(userName, applicationName, deviceId); 136 if (token == null) { 137 response = String.format( 138 "No token found for userName %s, applicationName %s and deviceId %s; nothing to do.", 139 userName, applicationName, deviceId); 140 statusCode = 400; 141 } else { 142 tokenAuthService.revokeToken(token); 143 response = String.format("Token revoked for userName %s, applicationName %s and deviceId %s.", 144 userName, applicationName, deviceId); 145 statusCode = 202; 146 } 147 } 148 sendTextResponse(resp, response, statusCode); 149 } catch (TokenAuthenticationException e) { 150 // Should never happen as parameters have already been checked 151 resp.sendError(HttpStatus.SC_NOT_FOUND); 152 } 153 } 154 155 protected void sendTextResponse(HttpServletResponse resp, String textResponse, int statusCode) throws IOException { 156 157 resp.setContentType("text/plain"); 158 resp.setCharacterEncoding(UTF_8.name()); 159 resp.setStatus(statusCode); 160 resp.setContentLength(textResponse.getBytes().length); 161 OutputStream out = resp.getOutputStream(); 162 out.write(textResponse.getBytes(UTF_8)); 163 out.close(); 164 } 165 166}