001/* 002 * (C) Copyright 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 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.platform.ui.web.auth.oauth; 020 021import static java.nio.charset.StandardCharsets.UTF_8; 022import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 023import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; 024import static javax.servlet.http.HttpServletResponse.SC_OK; 025import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; 026import static net.oauth.OAuth.OAUTH_CALLBACK; 027import static net.oauth.OAuth.OAUTH_TOKEN; 028import static net.oauth.OAuth.OAUTH_TOKEN_SECRET; 029import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.REQUESTED_URL; 030 031import java.io.IOException; 032import java.io.UnsupportedEncodingException; 033import java.net.URISyntaxException; 034import java.net.URLEncoder; 035import java.util.LinkedHashMap; 036import java.util.Map; 037 038import javax.servlet.ServletException; 039import javax.servlet.http.HttpServlet; 040import javax.servlet.http.HttpServletRequest; 041import javax.servlet.http.HttpServletResponse; 042 043import org.apache.commons.logging.Log; 044import org.apache.commons.logging.LogFactory; 045import org.nuxeo.common.utils.URIUtils; 046import org.nuxeo.ecm.core.api.NuxeoException; 047import org.nuxeo.ecm.platform.oauth.consumers.NuxeoOAuthConsumer; 048import org.nuxeo.ecm.platform.oauth.consumers.OAuthConsumerRegistry; 049import org.nuxeo.ecm.platform.oauth.tokens.OAuthToken; 050import org.nuxeo.ecm.platform.oauth.tokens.OAuthTokenStore; 051import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 052import org.nuxeo.runtime.api.Framework; 053 054import net.oauth.OAuth; 055import net.oauth.OAuthAccessor; 056import net.oauth.OAuthException; 057import net.oauth.OAuthMessage; 058import net.oauth.OAuthValidator; 059import net.oauth.SimpleOAuthValidator; 060import net.oauth.server.OAuthServlet; 061 062/** 063 * Servlet for the /oauth endpoint. 064 * 065 * @since 10.3 066 */ 067public class NuxeoOAuth1Servlet extends HttpServlet { 068 069 private static final long serialVersionUID = 1L; 070 071 private static final Log log = LogFactory.getLog(NuxeoOAuth1Servlet.class); 072 073 public static final String ENDPOINT_REQUEST_TOKEN = "/request-token"; 074 075 public static final String ENDPOINT_AUTHORIZE = "/authorize"; 076 077 public static final String ENDPOINT_ACCESS_TOKEN = "/access-token"; 078 079 public static final String OAUTH_VERIFIER = "oauth_verifier"; 080 081 public static final String OAUTH_CALLBACK_CONFIRMED = "oauth_callback_confirmed"; 082 083 public static final String NUXEO_LOGIN_PARAM = "nuxeo_login"; 084 085 public static final String DURATION_PARAM = "duration"; 086 087 public static final String OAUTH_INFO_SESSION_KEY = "OAUTH-INFO"; 088 089 public static final String GRANT_PAGE = "oauthGrant.jsp"; 090 091 public static final String LOGIN_PAGE = "login.jsp"; 092 093 public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; 094 095 protected static String urlEncode(String string) { 096 try { 097 return URLEncoder.encode(string, UTF_8.name()); 098 } catch (UnsupportedEncodingException e) { 099 throw new NuxeoException(e); // cannot happen 100 } 101 } 102 103 @Override 104 protected void doGet(HttpServletRequest request, HttpServletResponse response) 105 throws ServletException, IOException { 106 String pathInfo = request.getPathInfo(); 107 if (pathInfo == null) { 108 response.sendError(SC_NOT_FOUND); 109 } else if (pathInfo.equals(ENDPOINT_REQUEST_TOKEN)) { 110 doGetRequestToken(request, response); 111 } else if (pathInfo.equals(ENDPOINT_AUTHORIZE)) { 112 doGetAuthorize(request, response); 113 } else if (pathInfo.equals(ENDPOINT_ACCESS_TOKEN)) { 114 doGetAccessToken(request, response); 115 } else { 116 response.sendError(SC_NOT_FOUND); 117 } 118 } 119 120 @Override 121 protected void doPost(HttpServletRequest request, HttpServletResponse response) 122 throws ServletException, IOException { 123 String pathInfo = request.getPathInfo(); 124 if (pathInfo == null) { 125 response.sendError(SC_NOT_FOUND); 126 } else if (pathInfo.equals(ENDPOINT_AUTHORIZE)) { 127 doPostAuthorize(request, response); 128 } else { 129 response.sendError(SC_NOT_FOUND); 130 } 131 } 132 133 /** 134 * Generates a request token, redirects to the Nuxeo login page, and provides a later redirect URL to the OAuth 135 * grant page. 136 */ 137 protected void doGetAuthorize(HttpServletRequest request, HttpServletResponse response) throws IOException { 138 String token = request.getParameter(OAUTH_TOKEN); 139 // create request token 140 OAuthTokenStore tokenStore = Framework.getService(OAuthTokenStore.class); 141 OAuthToken rToken = tokenStore.getRequestToken(token); 142 // store it in session 143 request.getSession(true).setAttribute(OAUTH_INFO_SESSION_KEY, rToken); 144 // redirect to login page with appropriate further redirect URL 145 String redirectUrl = GRANT_PAGE + "?" + OAUTH_TOKEN + "=" + urlEncode(token); 146 String url = VirtualHostHelper.getBaseURL(request) + LOGIN_PAGE + "?" + REQUESTED_URL + "=" 147 + urlEncode(redirectUrl); 148 response.sendRedirect(url); 149 } 150 151 /** 152 * Adds a verifier and username to the request token and redirects to the callback URL. 153 */ 154 protected void doPostAuthorize(HttpServletRequest request, HttpServletResponse response) throws IOException { 155 String token = request.getParameter(OAUTH_TOKEN); 156 String username = request.getParameter(NUXEO_LOGIN_PARAM); 157 String duration = request.getParameter(DURATION_PARAM); 158 // add verifier and username to request token 159 OAuthTokenStore tokenStore = Framework.getService(OAuthTokenStore.class); 160 OAuthToken rToken = tokenStore.addVerifierToRequestToken(token, Long.valueOf(duration)); 161 rToken.setNuxeoLogin(username); 162 // find callback url 163 String callbackUrl = rToken.getCallbackUrl(); 164 if (callbackUrl == null) { 165 // get the callback url from the consumer 166 OAuthConsumerRegistry consumerRegistry = Framework.getService(OAuthConsumerRegistry.class); 167 NuxeoOAuthConsumer consumer = consumerRegistry.getConsumer(rToken.getConsumerKey()); 168 if (consumer == null) { 169 int sc = OAuth.Problems.TO_HTTP_CODE.get(OAuth.Problems.CONSUMER_KEY_UNKNOWN).intValue(); 170 response.sendError(sc, "Unknown consumer key"); 171 return; 172 } 173 callbackUrl = consumer.getCallbackURL(); 174 if (callbackUrl == null) { 175 log.error("No callback URL configured for consumer: " + rToken.getConsumerKey()); 176 response.sendError(SC_INTERNAL_SERVER_ERROR); 177 return; 178 } 179 } 180 Map<String, String> params = new LinkedHashMap<>(); 181 params.put(OAUTH_TOKEN, rToken.getToken()); 182 params.put(OAUTH_VERIFIER, rToken.getVerifier()); 183 String targetUrl = URIUtils.addParametersToURIQuery(callbackUrl, params); 184 response.sendRedirect(targetUrl); 185 } 186 187 protected void doGetRequestToken(HttpServletRequest request, HttpServletResponse response) throws IOException { 188 // get consumer 189 OAuthMessage message = OAuthServlet.getMessage(request, null); 190 String consumerKey = message.getConsumerKey(); 191 OAuthConsumerRegistry consumerRegistry = Framework.getService(OAuthConsumerRegistry.class); 192 NuxeoOAuthConsumer consumer = consumerRegistry.getConsumer(consumerKey, message.getSignatureMethod()); 193 if (consumer == null) { 194 int sc = OAuth.Problems.TO_HTTP_CODE.get(OAuth.Problems.CONSUMER_KEY_UNKNOWN).intValue(); 195 response.sendError(sc, "Unknown consumer key"); 196 return; 197 } 198 OAuthAccessor accessor = new OAuthAccessor(consumer); 199 OAuthValidator validator = new SimpleOAuthValidator(); 200 try { 201 validator.validateMessage(message, accessor); 202 } catch (OAuthException | URISyntaxException | IOException e) { 203 int sc = OAuth.Problems.TO_HTTP_CODE.get(OAuth.Problems.SIGNATURE_INVALID).intValue(); 204 response.sendError(sc, "Cannot validate signature"); 205 return; 206 } 207 String callbackUrl = message.getParameter(OAUTH_CALLBACK); 208 OAuthTokenStore tokenStore = Framework.getService(OAuthTokenStore.class); 209 OAuthToken rToken = tokenStore.createRequestToken(consumerKey, callbackUrl); 210 211 Map<String, String> params = new LinkedHashMap<>(); 212 params.put(OAUTH_TOKEN, rToken.getToken()); 213 params.put(OAUTH_TOKEN_SECRET, rToken.getTokenSecret()); 214 params.put(OAUTH_CALLBACK_CONFIRMED, "true"); 215 String body = URIUtils.getURIQuery(params); 216 response.setStatus(SC_OK); 217 response.setContentType(APPLICATION_X_WWW_FORM_URLENCODED); 218 response.getWriter().write(body); 219 } 220 221 protected void doGetAccessToken(HttpServletRequest request, HttpServletResponse response) throws IOException { 222 OAuthMessage message = OAuthServlet.getMessage(request, null); 223 String consumerKey = message.getConsumerKey(); 224 String token = message.getToken(); 225 OAuthConsumerRegistry consumerRegistry = Framework.getService(OAuthConsumerRegistry.class); 226 NuxeoOAuthConsumer consumer = consumerRegistry.getConsumer(consumerKey, message.getSignatureMethod()); 227 if (consumer == null) { 228 int sc = OAuth.Problems.TO_HTTP_CODE.get(OAuth.Problems.CONSUMER_KEY_UNKNOWN).intValue(); 229 response.sendError(sc, "Unknown consumer key"); 230 return; 231 } 232 // get request token 233 OAuthAccessor accessor = new OAuthAccessor(consumer); 234 OAuthTokenStore tokenStore = Framework.getService(OAuthTokenStore.class); 235 OAuthToken rToken = tokenStore.getRequestToken(token); 236 accessor.requestToken = rToken.getToken(); 237 accessor.tokenSecret = rToken.getTokenSecret(); 238 // validate signature 239 OAuthValidator validator = new SimpleOAuthValidator(); 240 try { 241 validator.validateMessage(message, accessor); 242 } catch (OAuthException | URISyntaxException | IOException e) { 243 int errCode = OAuth.Problems.TO_HTTP_CODE.get(OAuth.Problems.SIGNATURE_INVALID).intValue(); 244 response.sendError(errCode, "Cannot validate signature"); 245 return; 246 } 247 248 String verifier = message.getParameter(OAUTH_VERIFIER); 249 boolean allowByPassVerifier = false; 250 if (verifier == null) { 251 // here we don't have the verifier in the request 252 // this is strictly prohibited in the spec 253 // => see http://tools.ietf.org/html/rfc5849 page 11 254 // Anyway since iGoogle does not seem to forward the verifier 255 // we allow it for designated consumers 256 allowByPassVerifier = consumer.allowBypassVerifier(); 257 } 258 if (!rToken.getVerifier().equals(verifier) && !allowByPassVerifier) { 259 response.sendError(SC_UNAUTHORIZED, "Verifier is not correct"); 260 return; 261 } 262 OAuthToken aToken = tokenStore.createAccessTokenFromRequestToken(rToken); 263 response.setStatus(SC_OK); 264 response.setContentType(APPLICATION_X_WWW_FORM_URLENCODED); 265 Map<String, String> params = new LinkedHashMap<>(); 266 params.put(OAUTH_TOKEN, aToken.getToken()); 267 params.put(OAUTH_TOKEN_SECRET, aToken.getTokenSecret()); 268 String body = URIUtils.getURIQuery(params); 269 response.getWriter().write(body); 270 } 271 272}