diff --git a/oauth-core/pom.xml b/oauth-core/pom.xml index 1676dc8..595d03d 100644 --- a/oauth-core/pom.xml +++ b/oauth-core/pom.xml @@ -47,10 +47,15 @@ - org.glassfish - javax.servlet + javax.servlet + javax.servlet-api provided + + javax.ws.rs + javax.ws.rs-api + + diff --git a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/OAuthConfiguration.java b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/OAuthConfiguration.java index fe5b311..7cdedb6 100644 --- a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/OAuthConfiguration.java +++ b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/OAuthConfiguration.java @@ -1,18 +1,14 @@ -/******************************************************************************* - * Copyright (c) 2012 IBM Corporation. +/* + * Copyright (c) 2012-2019 IBM Corporation and others * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v. 1.0 which accompanies this distribution. - * - * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * - * IBM Corporation - initial API and implementation - *******************************************************************************/ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ package org.eclipse.lyo.server.oauth.core; import javax.servlet.http.HttpServletResponse; @@ -24,6 +20,8 @@ import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStore; import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStoreException; +import org.eclipse.lyo.server.oauth.core.token.IJaxTokenStrategy; +import org.eclipse.lyo.server.oauth.core.token.JaxTokenStrategy; import org.eclipse.lyo.server.oauth.core.token.SimpleTokenStrategy; import org.eclipse.lyo.server.oauth.core.token.TokenStrategy; @@ -36,6 +34,7 @@ public class OAuthConfiguration { private OAuthValidator validator; private TokenStrategy tokenStrategy; + private IJaxTokenStrategy jaxTokenStrategy; private ConsumerStore consumerStore = null; private Application application = null; private boolean v1_0Allowed = true; @@ -49,6 +48,7 @@ public static OAuthConfiguration getInstance() { private OAuthConfiguration() { validator = new SimpleOAuthValidator(); tokenStrategy = new SimpleTokenStrategy(); + jaxTokenStrategy = new JaxTokenStrategy(128, 1024); } /** @@ -74,19 +74,37 @@ public void setValidator(OAuthValidator validator) { * * @return the token strategy */ + public IJaxTokenStrategy getJaxTokenStrategy() { + return jaxTokenStrategy; + } + + /** + * Sets the strategy used to generate and verify OAuth tokens. + * + * @param tokenStrategy the strategy + */ + public void setJaxTokenStrategy(IJaxTokenStrategy tokenStrategy) { + this.jaxTokenStrategy = tokenStrategy; + } + + /** + * See {@link #getJaxTokenStrategy()} + * @return + */ public TokenStrategy getTokenStrategy() { return tokenStrategy; } /** * Sets the strategy used to generate and verify OAuth tokens. - * + * * @param tokenStrategy the strategy */ public void setTokenStrategy(TokenStrategy tokenStrategy) { this.tokenStrategy = tokenStrategy; } + /** * Gets the store used for managing consumers. * diff --git a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/OAuthRequest.java b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/OAuthRequest.java index 5fae9ee..c6ee1ee 100644 --- a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/OAuthRequest.java +++ b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/OAuthRequest.java @@ -13,9 +13,15 @@ import java.io.IOException; import java.net.URISyntaxException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.ws.rs.core.MultivaluedMap; import net.oauth.OAuth; import net.oauth.OAuthAccessor; @@ -71,15 +77,62 @@ public OAuthRequest(HttpServletRequest request) String token = this.message.getToken(); if (token != null) { this.accessor.tokenSecret = OAuthConfiguration.getInstance() - .getTokenStrategy().getTokenSecret(this.httpRequest, token); + .getJaxTokenStrategy().getTokenSecret(token); } } - + + public static class OAuthServletRequestWrapper extends HttpServletRequestWrapper { + + private final Map formParams; + + /** + * Constructs a request object wrapping the given request. + * + * @param request + * @throws IllegalArgumentException if the request is null + */ + public OAuthServletRequestWrapper(HttpServletRequest request, + MultivaluedMap formParams) { + super(request); + this.formParams = aggregateMultimap(formParams); + } + + private Map aggregateMultimap(MultivaluedMap multimap) { + HashMap map = new HashMap<>(); + multimap.forEach((key, strings) -> map.put(key, strings.toArray(new String[0]))); + return map; + } + + @Override + public String getParameter(String name) { + String[] values = formParams.get(name); + if (values == null || values.length == 0) { + return null; + } + return values[0]; + } + + @Override + public Map getParameterMap() { + return formParams; + } + + @Override + public Enumeration getParameterNames() { + return Collections.enumeration(formParams.keySet()); + } + + @Override + public String[] getParameterValues(String name) { + return formParams.get(name); + } + } + public HttpServletRequest getHttpRequest() { return httpRequest; } - public void setHttpRequest(HttpServletRequest httpRequest) { + private void setHttpRequest(HttpServletRequest httpRequest) { this.httpRequest = httpRequest; } @@ -112,7 +165,7 @@ public void validate() throws OAuthException, IOException, ServletException { try { OAuthConfiguration config = OAuthConfiguration.getInstance(); config.getValidator().validateMessage(message, accessor); - config.getTokenStrategy().validateAccessToken(this); + config.getJaxTokenStrategy().validateAccessToken(this); } catch (URISyntaxException e) { throw new ServletException(e); } diff --git a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/IJaxTokenStrategy.java b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/IJaxTokenStrategy.java new file mode 100644 index 0000000..8cdba3a --- /dev/null +++ b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/IJaxTokenStrategy.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 KTH Royal Institute of Technology and others + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.lyo.server.oauth.core.token; + +import net.oauth.OAuthMessage; +import net.oauth.OAuthProblemException; +import org.eclipse.lyo.server.oauth.core.OAuthRequest; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +public interface IJaxTokenStrategy { + void generateRequestToken(OAuthRequest oAuthRequest) throws IOException; + void validateVerificationCode(OAuthRequest oAuthRequest) throws IOException, OAuthProblemException; + void generateAccessToken(OAuthRequest oAuthRequest) throws OAuthProblemException, IOException; + + String validateRequestToken(OAuthMessage message) throws IOException, OAuthProblemException; + String getCallback(String requestToken) throws OAuthProblemException; + void markRequestTokenAuthorized(HttpServletRequest httpRequest, String requestToken) throws OAuthProblemException; + String generateVerificationCode(String requestToken) throws OAuthProblemException; + String getTokenSecret(String secretToken) throws OAuthProblemException; + + void validateAccessToken(OAuthRequest oAuthRequest) throws IOException, OAuthProblemException; +} diff --git a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/JaxTokenStrategy.java b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/JaxTokenStrategy.java new file mode 100644 index 0000000..40e7044 --- /dev/null +++ b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/JaxTokenStrategy.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2019 KTH Royal Institute of Technology and others + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.lyo.server.oauth.core.token; + +import net.oauth.OAuth; +import net.oauth.OAuthAccessor; +import net.oauth.OAuthMessage; +import net.oauth.OAuthProblemException; +import org.eclipse.lyo.server.oauth.core.OAuthRequest; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Map; + +public class JaxTokenStrategy implements IJaxTokenStrategy { + // key is request token string, value is RequestTokenData + private final Map requestTokens; + + // key is access token, value is consumer key + private final Map accessTokens; + + // key is token, value is token secret + private final Map tokenSecrets; + + public JaxTokenStrategy(int requestTokenMaxCount, int accessTokenMaxCount) { + requestTokens = new LRUCache<>(requestTokenMaxCount); + accessTokens = new LRUCache<>(accessTokenMaxCount); + tokenSecrets = new LRUCache<>(requestTokenMaxCount + accessTokenMaxCount); + } + + @Override + public void generateRequestToken(OAuthRequest oAuthRequest) throws IOException { + OAuthAccessor accessor = oAuthRequest.getAccessor(); + accessor.requestToken = StrategyUtil.generateTokenString(); + accessor.tokenSecret = StrategyUtil.generateTokenString(); + String callback = oAuthRequest.getMessage() + .getParameter(OAuth.OAUTH_CALLBACK); + synchronized (requestTokens) { + requestTokens.put(accessor.requestToken, new RequestTokenData( + accessor.consumer.consumerKey, callback)); + } + synchronized (tokenSecrets) { + tokenSecrets.put(accessor.requestToken, accessor.tokenSecret); + } + } + + @Override + public void validateVerificationCode(OAuthRequest oAuthRequest) throws IOException, OAuthProblemException { + String verificationCode = oAuthRequest.getMessage().getParameter( + OAuth.OAUTH_VERIFIER); + if (verificationCode == null) { + throw new OAuthProblemException( + OAuth.Problems.OAUTH_PARAMETERS_ABSENT); + } + + RequestTokenData tokenData = getRequestTokenData(oAuthRequest); + if (!verificationCode.equals(tokenData.getVerificationCode())) { + throw new OAuthProblemException( + OAuth.Problems.OAUTH_PARAMETERS_REJECTED); + } + + } + + @Override + public void generateAccessToken(OAuthRequest oAuthRequest) throws OAuthProblemException, IOException { + // Remove the old request token. + OAuthAccessor accessor = oAuthRequest.getAccessor(); + String requestToken = oAuthRequest.getMessage().getToken(); + synchronized (requestTokens) { + if (!isRequestTokenAuthorized(requestToken)) { + throw new OAuthProblemException( + OAuth.Problems.ADDITIONAL_AUTHORIZATION_REQUIRED); + } + + requestTokens.remove(requestToken); + } + + // Generate a new access token. + accessor.accessToken = StrategyUtil.generateTokenString(); + synchronized (accessTokens) { + accessTokens.put(accessor.accessToken, + accessor.consumer.consumerKey); + } + + // Remove the old token secret and create a new one for this access + // token. + accessor.tokenSecret = StrategyUtil.generateTokenString(); + synchronized (tokenSecrets) { + tokenSecrets.remove(requestToken); + tokenSecrets.put(accessor.accessToken, accessor.tokenSecret); + } + + accessor.requestToken = null; + + } + + private boolean isRequestTokenAuthorized(String requestToken) throws OAuthProblemException { + return getRequestTokenData(requestToken).isAuthorized(); + } + + + @Override + public String validateRequestToken(OAuthMessage message) throws IOException, OAuthProblemException { + return getRequestTokenData(message.getToken()).getConsumerKey(); + } + + @Override + public String getCallback(String requestToken) throws OAuthProblemException { + return getRequestTokenData(requestToken).getCallback(); + } + + @Override + public void markRequestTokenAuthorized(HttpServletRequest httpRequest, String requestToken) + throws OAuthProblemException { + getRequestTokenData(requestToken).setAuthorized(true); + } + + @Override + public String generateVerificationCode(String requestToken) throws OAuthProblemException { + String verificationCode = StrategyUtil.generateTokenString(); + getRequestTokenData(requestToken).setVerificationCode(verificationCode); + + return verificationCode; + } + + @Override + public String getTokenSecret(String token) throws OAuthProblemException { + synchronized (tokenSecrets) { + String tokenSecret = tokenSecrets.get(token); + if (tokenSecret == null) { + // It's possible the token secret was purged from the LRU cache, + // or the token is just not recognized. Either way, we can + // consider the token rejected. + throw new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED); + } + return tokenSecret; + } + } + + @Override + public void validateAccessToken(OAuthRequest oAuthRequest) throws IOException, OAuthProblemException { + synchronized (accessTokens) { + String actualValue = accessTokens.get(oAuthRequest.getMessage().getToken()); + if (!oAuthRequest.getConsumer().consumerKey.equals(actualValue)) { + throw new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED); + } + } + } + + /** + * Gets the request token data from this OAuth request. + * + * @param oAuthRequest + * the OAuth request + * @return the request token data + * @throws OAuthProblemException + * if the request token is invalid + * @throws IOException + * on reading OAuth parameters + */ + protected RequestTokenData getRequestTokenData(OAuthRequest oAuthRequest) + throws OAuthProblemException, IOException { + return getRequestTokenData(oAuthRequest.getMessage().getToken()); + } + + /** + * Gets the request token data for this request token. + * + * @param requestToken + * the request token string + * @return the request token data + * @throws OAuthProblemException + * if the request token is invalid + */ + protected RequestTokenData getRequestTokenData(String requestToken) + throws OAuthProblemException { + synchronized (requestTokens) { + RequestTokenData tokenData = requestTokens.get(requestToken); + if (tokenData == null) { + throw new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED); + } + return tokenData; + } + } +} diff --git a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/RequestTokenData.java b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/RequestTokenData.java new file mode 100644 index 0000000..5168107 --- /dev/null +++ b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/RequestTokenData.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2012-2019 IBM Corporation and others + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.lyo.server.oauth.core.token; + +/** + * Holds information associated with a request token such as the callback + * URL and OAuth verification code. + * + * @author Samuel Padgett + */ +class RequestTokenData { + private String consumerKey; + private boolean authorized; + private String callback; + private String verificationCode; + + public RequestTokenData(String consumerKey) { + this.consumerKey = consumerKey; + this.authorized = false; + this.callback = null; + } + + public RequestTokenData(String consumerKey, String callback) { + this.consumerKey = consumerKey; + this.authorized = false; + this.callback = callback; + } + + public String getConsumerKey() { + return consumerKey; + } + + public void setConsumerKey(String consumerKey) { + this.consumerKey = consumerKey; + } + + public boolean isAuthorized() { + return authorized; + } + + public void setAuthorized(boolean authorized) { + this.authorized = authorized; + } + + public String getCallback() { + return callback; + } + + public void setCallback(String callback) { + this.callback = callback; + } + + public String getVerificationCode() { + return verificationCode; + } + + public void setVerificationCode(String verificationCode) { + this.verificationCode = verificationCode; + } +} diff --git a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/SimpleTokenStrategy.java b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/SimpleTokenStrategy.java index 451455c..6f49a98 100644 --- a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/SimpleTokenStrategy.java +++ b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/SimpleTokenStrategy.java @@ -1,23 +1,18 @@ -/******************************************************************************* - * Copyright (c) 2012 IBM Corporation. +/* + * Copyright (c) 2012-2019 IBM Corporation and others * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v. 1.0 which accompanies this distribution. - * - * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * - * IBM Corporation - initial API and implementation - *******************************************************************************/ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ package org.eclipse.lyo.server.oauth.core.token; import java.io.IOException; import java.util.Map; -import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -40,64 +35,7 @@ public class SimpleTokenStrategy implements TokenStrategy { private final static int REQUEST_TOKEN_MAX_ENTIRES = 500; private final static int ACCESS_TOKEN_MAX_ENTRIES = 5000; - - /** - * Holds information associated with a request token such as the callback - * URL and OAuth verification code. - * - * @author Samuel Padgett - */ - protected class RequestTokenData { - private String consumerKey; - private boolean authorized; - private String callback; - private String verificationCode; - - public RequestTokenData(String consumerKey) { - this.consumerKey = consumerKey; - this.authorized = false; - this.callback = null; - } - public RequestTokenData(String consumerKey, String callback) { - this.consumerKey = consumerKey; - this.authorized = false; - this.callback = callback; - } - - public String getConsumerKey() { - return consumerKey; - } - - public void setConsumerKey(String consumerKey) { - this.consumerKey = consumerKey; - } - - public boolean isAuthorized() { - return authorized; - } - - public void setAuthorized(boolean authorized) { - this.authorized = authorized; - } - - public String getCallback() { - return callback; - } - - public void setCallback(String callback) { - this.callback = callback; - } - - public String getVerificationCode() { - return verificationCode; - } - - public void setVerificationCode(String verificationCode) { - this.verificationCode = verificationCode; - } - } - // key is request token string, value is RequestTokenData private Map requestTokens; @@ -137,8 +75,8 @@ public SimpleTokenStrategy(int requestTokenMaxCount, int accessTokenMaxCount) { public void generateRequestToken(OAuthRequest oAuthRequest) throws IOException { OAuthAccessor accessor = oAuthRequest.getAccessor(); - accessor.requestToken = generateTokenString(); - accessor.tokenSecret = generateTokenString(); + accessor.requestToken = StrategyUtil.generateTokenString(); + accessor.tokenSecret = StrategyUtil.generateTokenString(); String callback = oAuthRequest.getMessage() .getParameter(OAuth.OAUTH_CALLBACK); synchronized (requestTokens) { @@ -177,7 +115,7 @@ public boolean isRequestTokenAuthorized(HttpServletRequest httpRequest, @Override public String generateVerificationCode(HttpServletRequest httpRequest, String requestToken) throws OAuthProblemException { - String verificationCode = generateTokenString(); + String verificationCode = StrategyUtil.generateTokenString(); getRequestTokenData(requestToken).setVerificationCode(verificationCode); return verificationCode; @@ -217,7 +155,7 @@ public void generateAccessToken(OAuthRequest oAuthRequest) throws OAuthProblemEx } // Generate a new access token. - accessor.accessToken = generateTokenString(); + accessor.accessToken = StrategyUtil.generateTokenString(); synchronized (accessTokens) { accessTokens.put(accessor.accessToken, accessor.consumer.consumerKey); @@ -225,7 +163,7 @@ public void generateAccessToken(OAuthRequest oAuthRequest) throws OAuthProblemEx // Remove the old token secret and create a new one for this access // token. - accessor.tokenSecret = generateTokenString(); + accessor.tokenSecret = StrategyUtil.generateTokenString(); synchronized (tokenSecrets) { tokenSecrets.remove(requestToken); tokenSecrets.put(accessor.accessToken, accessor.tokenSecret); @@ -260,15 +198,6 @@ public String getTokenSecret(HttpServletRequest httpRequest, String token) return tokenSecret; } } - - /** - * Creates a unique, random string to use for tokens. - * - * @return the random string - */ - protected String generateTokenString() { - return UUID.randomUUID().toString(); - } /** * Gets the request token data from this OAuth request. diff --git a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/StrategyUtil.java b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/StrategyUtil.java new file mode 100644 index 0000000..de39fad --- /dev/null +++ b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/StrategyUtil.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2019 KTH Royal Institute of Technology and others + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package org.eclipse.lyo.server.oauth.core.token; + +import java.util.UUID; + +public class StrategyUtil { + /** + * Creates a unique, random string to use for tokens. + * + * @return the random string + */ + public static String generateTokenString() { + return UUID.randomUUID().toString(); + } +} diff --git a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/TokenStrategy.java b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/TokenStrategy.java index 940b9ca..ebf89f3 100644 --- a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/TokenStrategy.java +++ b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/token/TokenStrategy.java @@ -25,9 +25,12 @@ * Manages and validates OAuth tokens and token secrets. * {@link SimpleTokenStrategy} is a basic implementation, but you can implement * this interface to generate and validate OAuth tokens your own way. + * + *

Deprecated due to not using JAX-RS inputs. Broken in Jersey.

* * @author Samuel Padgett */ +@Deprecated public interface TokenStrategy { /** * Generates a request token and token secret and sets it in the accessor in diff --git a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/utils/AbstractAdapterCredentialsFilter.java b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/utils/AbstractAdapterCredentialsFilter.java index a101859..7320b72 100644 --- a/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/utils/AbstractAdapterCredentialsFilter.java +++ b/oauth-core/src/main/java/org/eclipse/lyo/server/oauth/core/utils/AbstractAdapterCredentialsFilter.java @@ -1,5 +1,5 @@ -/******************************************************************************* - * Copyright (c) 2012, 2014 IBM Corporation. +/* + * Copyright (c) 2012-2019 IBM Corporation and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,13 +8,7 @@ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * - * Michael Fiedler - initial API and implementation for Bugzilla adapter - * Susumu Fukuda - extracted this class from Bugzilla adapter CredentialsFilter - * Samuel Padgett - fix NPEx when Exception.getCause() returns null in Application.login() - *******************************************************************************/ + */ package org.eclipse.lyo.server.oauth.core.utils; import java.io.IOException; @@ -47,8 +41,8 @@ import org.eclipse.lyo.server.oauth.core.OAuthRequest; import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStore; import org.eclipse.lyo.server.oauth.core.consumer.LyoOAuthConsumer; +import org.eclipse.lyo.server.oauth.core.token.JaxTokenStrategy; import org.eclipse.lyo.server.oauth.core.token.LRUCache; -import org.eclipse.lyo.server.oauth.core.token.SimpleTokenStrategy; /** *

Overview

@@ -448,7 +442,7 @@ public boolean isAuthenticated(HttpServletRequest request) { * Override some SimpleTokenStrategy methods so that we can keep the * Connector associated with the OAuth tokens. */ - config.setTokenStrategy(new SimpleTokenStrategy() { + config.setJaxTokenStrategy(new JaxTokenStrategy(128, 4096) { @SuppressWarnings("unchecked") @Override public void markRequestTokenAuthorized( diff --git a/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/OAuthService.java b/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/OAuthService.java index 1c4e635..d2cde03 100644 --- a/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/OAuthService.java +++ b/oauth-webapp/src/main/java/org/eclipse/lyo/server/oauth/webapp/services/OAuthService.java @@ -1,15 +1,14 @@ /* - Copyright (c) 2012-2019 IBM Corporation and others - - All rights reserved. This program and the accompanying materials - are made available under the terms of the Eclipse Public License v1.0 - and Eclipse Distribution License v. 1.0 which accompanies this distribution. - - The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html - and the Eclipse Distribution License is available at - http://www.eclipse.org/org/documents/edl-v10.php. + * Copyright (c) 2012-2019 IBM Corporation and others + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. */ - package org.eclipse.lyo.server.oauth.webapp.services; import java.io.IOException; @@ -21,18 +20,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import javax.ws.rs.*; +import javax.ws.rs.core.*; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.UriBuilder; import net.oauth.OAuth; import net.oauth.OAuth.Parameter; @@ -52,7 +42,9 @@ import org.eclipse.lyo.server.oauth.core.OAuthRequest; import org.eclipse.lyo.server.oauth.core.consumer.ConsumerStoreException; import org.eclipse.lyo.server.oauth.core.consumer.LyoOAuthConsumer; -import org.eclipse.lyo.server.oauth.core.token.TokenStrategy; +import org.eclipse.lyo.server.oauth.core.token.IJaxTokenStrategy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Issues OAuth request tokens, handles authentication, and then exchanges @@ -65,46 +57,55 @@ @Path("/oauth") public class OAuthService { - @Context - protected HttpServletRequest httpRequest; - - @Context - protected HttpServletResponse httpResponse; + private final Logger log = LoggerFactory.getLogger(OAuthService.class); @GET @Path("/requestToken") - public Response doGetRequestToken() throws IOException, ServletException { - return doPostRequestToken(); + public Response doGetRequestToken(@Context HttpServletRequest httpRequest, + @Context HttpServletResponse httpResponse, + @Context UriInfo uriInfo) { +// MultivaluedMap params = uriInfo.getQueryParameters(); + return doRequestTokenInternal(httpRequest, httpResponse); } /** * Responds with a request token and token secret. * * @return the response - * @throws IOException - * on I/O errors - * @throws ServletException - * on servlet errors */ @POST + @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) @Path("/requestToken") - public Response doPostRequestToken() throws IOException, ServletException { + public Response doPostRequestToken(@Context HttpServletRequest httpRequest, + @Context HttpServletResponse httpResponse, + Form requestTokenForm) { + MultivaluedMap params = requestTokenForm.asMap(); + OAuthRequest.OAuthServletRequestWrapper requestWrapper = new OAuthRequest.OAuthServletRequestWrapper(httpRequest, params); + return doRequestTokenInternal(requestWrapper, httpResponse); + } + + private Response doRequestTokenInternal(HttpServletRequest httpRequest, + HttpServletResponse httpResponse) { try { - OAuthRequest oAuthRequest = validateRequest(); + OAuthRequest oAuthRequest = validateRequest(httpRequest); // Generate the token. - OAuthConfiguration.getInstance().getTokenStrategy() + OAuthConfiguration.getInstance().getJaxTokenStrategy() .generateRequestToken(oAuthRequest); + log.debug("Request token generated"); // Check for OAuth 1.0a authentication. boolean callbackConfirmed = confirmCallback(oAuthRequest); - + // Respond to the consumer. OAuthAccessor accessor = oAuthRequest.getAccessor(); return respondWithToken(accessor.requestToken, accessor.tokenSecret, callbackConfirmed); } catch (OAuthException e) { - return respondWithOAuthProblem(e); + log.warn("Error generating a request token", e); + return respondWithOAuthProblem(e, httpRequest, httpResponse); + } catch (IOException e) { + throw new IllegalStateException(e); } } @@ -119,7 +120,9 @@ public Response doPostRequestToken() throws IOException, ServletException { */ @GET @Path("/authorize") - public Response authorize() throws ServletException, IOException { + public Response authorize(@Context HttpServletRequest httpRequest, + @Context HttpServletResponse httpResponse) + throws ServletException, IOException { try { /* * Check that the request token is valid and determine what consumer @@ -129,11 +132,10 @@ public Response authorize() throws ServletException, IOException { */ OAuthMessage message = OAuthServlet.getMessage(httpRequest, null); OAuthConfiguration config = OAuthConfiguration.getInstance(); - String consumerKey = config.getTokenStrategy() - .validateRequestToken(httpRequest, message); + String consumerKey = config.getJaxTokenStrategy().validateRequestToken(message); - LyoOAuthConsumer consumer = OAuthConfiguration.getInstance() - .getConsumerStore().getConsumer(consumerKey); + LyoOAuthConsumer consumer = OAuthConfiguration.getInstance().getConsumerStore() + .getConsumer(consumerKey); // Pass some data to the JSP. httpRequest.setAttribute("requestToken", message.getToken()); @@ -145,24 +147,22 @@ public Response authorize() throws ServletException, IOException { httpRequest.setAttribute("callbackConfirmed", new Boolean(callbackConfirmed)); // The application name is displayed on the OAuth login page. - httpRequest.setAttribute("applicationName", - config.getApplication().getName()); + httpRequest.setAttribute("applicationName", config.getApplication().getName()); - httpResponse.setHeader(OAuthServerConstants.HDR_CACHE_CONTROL, - OAuthServerConstants.NO_CACHE); + httpResponse.setHeader(OAuthServerConstants.HDR_CACHE_CONTROL, OAuthServerConstants.NO_CACHE); if (config.getApplication().isAuthenticated(httpRequest)) { // Show the grant access page. - httpRequest.getRequestDispatcher("/oauth/authorize.jsp").forward( - httpRequest, httpResponse); + httpRequest.getRequestDispatcher("/oauth/authorize.jsp").forward(httpRequest, + httpResponse); } else { // Show the login page. - httpRequest.getRequestDispatcher("/oauth/login.jsp").forward( - httpRequest, httpResponse); + httpRequest.getRequestDispatcher("/oauth/login.jsp").forward(httpRequest, + httpResponse); } return null; } catch (OAuthException e) { - return respondWithOAuthProblem(e); + return respondWithOAuthProblem(e, httpRequest, httpResponse); } } @@ -174,7 +174,8 @@ public Response authorize() throws ServletException, IOException { */ @POST @Path("/login") - public Response login(@FormParam("id") String id, + public Response login(@Context HttpServletRequest httpRequest, + @FormParam("id") String id, @FormParam("password") String password, @FormParam("requestToken") String requestToken) { CSRFPrevent.check(httpRequest); @@ -194,7 +195,7 @@ public Response login(@FormParam("id") String id, } try { - OAuthConfiguration.getInstance().getTokenStrategy() + OAuthConfiguration.getInstance().getJaxTokenStrategy() .markRequestTokenAuthorized(httpRequest, requestToken); } catch (OAuthException e) { return Response.status(Status.CONFLICT) @@ -207,7 +208,8 @@ public Response login(@FormParam("id") String id, @POST @Path("/internal/approveToken") - public Response authorize(@FormParam("requestToken") String requestToken) { + public Response authorize(@Context HttpServletRequest httpRequest, + @FormParam("requestToken") String requestToken) { CSRFPrevent.check(httpRequest); try { @@ -218,12 +220,12 @@ public Response authorize(@FormParam("requestToken") String requestToken) { return Response.status(Status.SERVICE_UNAVAILABLE).build(); } - return authorizeToken(requestToken); + return authorizeToken(requestToken, httpRequest); } - private Response authorizeToken(String requestToken) { + private Response authorizeToken(String requestToken, HttpServletRequest httpRequest) { try { - OAuthConfiguration.getInstance().getTokenStrategy() + OAuthConfiguration.getInstance().getJaxTokenStrategy() .markRequestTokenAuthorized(httpRequest, requestToken); } catch (OAuthException e) { return Response.status(Status.CONFLICT) @@ -236,8 +238,9 @@ private Response authorizeToken(String requestToken) { @GET @Path("/accessToken") - public Response doGetAccessToken() throws IOException, ServletException { - return doPostAccessToken(); + public Response doGetAccessToken(@Context HttpServletRequest httpRequest, + @Context HttpServletResponse httpResponse) { + return doAccessTokenInternal(httpRequest, httpResponse); } /** @@ -245,22 +248,27 @@ public Response doGetAccessToken() throws IOException, ServletException { * The request must be signed and the request token valid. * * @return the response - * @throws IOException - * on I/O errors - * @throws ServletException - * on servlet errors */ @POST + @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) @Path("/accessToken") - public Response doPostAccessToken() throws IOException, ServletException { + public Response doPostAccessToken(@Context HttpServletRequest httpRequest, + @Context HttpServletResponse httpResponse, + Form accessTokenForm) { + MultivaluedMap params = accessTokenForm.asMap(); + OAuthRequest.OAuthServletRequestWrapper wrappedRequest = new OAuthRequest.OAuthServletRequestWrapper(httpRequest, params); + return doAccessTokenInternal(wrappedRequest, httpResponse); + } + + private Response doAccessTokenInternal(HttpServletRequest httpRequest, + HttpServletResponse httpResponse) { try { // Validate the request is signed and check that the request token // is valid. - OAuthRequest oAuthRequest = validateRequest(); + OAuthRequest oAuthRequest = validateRequest(httpRequest); OAuthConfiguration config = OAuthConfiguration.getInstance(); - TokenStrategy strategy = config.getTokenStrategy(); - strategy.validateRequestToken(httpRequest, - oAuthRequest.getMessage()); + IJaxTokenStrategy strategy = config.getJaxTokenStrategy(); + strategy.validateRequestToken(oAuthRequest.getMessage()); // The verification code MUST be passed in the request if this is // OAuth 1.0a. @@ -271,12 +279,15 @@ public Response doPostAccessToken() throws IOException, ServletException { // Generate a new access token for this accessor. strategy.generateAccessToken(oAuthRequest); + log.debug("Access token generated"); // Send the new token and secret back to the consumer. OAuthAccessor accessor = oAuthRequest.getAccessor(); return respondWithToken(accessor.accessToken, accessor.tokenSecret); } catch (OAuthException e) { - return respondWithOAuthProblem(e); + return respondWithOAuthProblem(e, httpRequest, httpResponse); + } catch (IOException e) { + throw new IllegalStateException(e); } } @@ -294,12 +305,12 @@ public Response doPostAccessToken() throws IOException, ServletException { // Some consumers do not set an appropriate Content-Type header. //@Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public Response provisionalKey() + public Response provisionalKey(@Context HttpServletRequest httpRequest, + @Context HttpServletResponse httpResponse) throws NullPointerException, IOException { try { // Create the consumer from the request. - JSONObject request = (JSONObject) JSON.parse(httpRequest - .getInputStream()); + JSONObject request = (JSONObject) JSON.parse(httpRequest.getInputStream()); String name = null; if (request.has("name") && request.get("name") != null) { @@ -307,7 +318,7 @@ public Response provisionalKey() } if (name == null || name.trim().equals("")) { - name = getRemoteHost(); + name = getRemoteHost(httpRequest); } String secret = request.getString("secret"); @@ -354,15 +365,17 @@ public Response provisionalKey() * on errors showing the JSP * @throws IOException * on errors showing the JSP - * @see #showConsumerKeyManagementPage() + * @see #showConsumerKeyManagementPage(HttpServletRequest, HttpServletResponse) */ @GET @Path("/approveKey") @Produces({ MediaType.TEXT_HTML }) - public Response showApproveKeyPage(@QueryParam("key") String key) + public Response showApproveKeyPage(@Context HttpServletRequest httpRequest, + @Context HttpServletResponse httpResponse, + @QueryParam("key") String key) throws ServletException, IOException { if (key == null || "".equals(key)) { - return showConsumerKeyManagementPage(); + return showConsumerKeyManagementPage(httpRequest, httpResponse); } try { @@ -372,7 +385,7 @@ public Response showApproveKeyPage(@QueryParam("key") String key) httpRequest.setAttribute("applicationName", app.getName()); if (!app.isAdminSession(httpRequest)) { - return showAdminLogin(); + return showAdminLogin(httpRequest, httpResponse); } LyoOAuthConsumer provisionalConsumer = OAuthConfiguration @@ -401,7 +414,7 @@ public Response showApproveKeyPage(@QueryParam("key") String key) return Response.status(Status.CONFLICT).type(MediaType.TEXT_PLAIN) .entity(e.getMessage()).build(); } catch (OAuthProblemException e) { - return respondWithOAuthProblem(e); + return respondWithOAuthProblem(e, httpRequest, httpResponse); } } @@ -417,14 +430,15 @@ public Response showApproveKeyPage(@QueryParam("key") String key) */ @GET @Path("/admin") - public Response showConsumerKeyManagementPage() throws ServletException, - IOException { + public Response showConsumerKeyManagementPage(@Context HttpServletRequest httpRequest, + @Context HttpServletResponse httpResponse) + throws ServletException, IOException { try { Application app = OAuthConfiguration.getInstance().getApplication(); httpRequest.setAttribute("applicationName", app.getName()); if (!app.isAdminSession(httpRequest)) { - return showAdminLogin(); + return showAdminLogin(httpRequest, httpResponse); } } catch (OAuthException e) { return Response.status(Status.SERVICE_UNAVAILABLE).build(); @@ -445,7 +459,9 @@ public Response showConsumerKeyManagementPage() throws ServletException, */ @POST @Path("/adminLogin") - public Response login(@FormParam("id") String id, + public Response login(@Context HttpServletRequest httpRequest, + @Context HttpServletResponse httpResponse, + @FormParam("id") String id, @FormParam("password") String password) { CSRFPrevent.check(httpRequest); @@ -476,9 +492,8 @@ protected boolean confirmCallback(OAuthRequest oAuthRequest) throws OAuthException { boolean callbackConfirmed = OAuthConfiguration .getInstance() - .getTokenStrategy() - .getCallback(httpRequest, - oAuthRequest.getAccessor().requestToken) != null; + .getJaxTokenStrategy() + .getCallback(oAuthRequest.getAccessor().requestToken) != null; if (callbackConfirmed) { oAuthRequest.getConsumer().setOAuthVersion( LyoOAuthConsumer.OAuthVersion.OAUTH_1_0A); @@ -506,14 +521,16 @@ protected boolean confirmCallback(OAuthRequest oAuthRequest) * @throws IOException * on I/O errors */ - protected OAuthRequest validateRequest() throws OAuthException, IOException { + protected OAuthRequest validateRequest(HttpServletRequest httpRequest) throws OAuthException, IOException { OAuthRequest oAuthRequest = new OAuthRequest(httpRequest); try { OAuthValidator validator = OAuthConfiguration.getInstance() .getValidator(); validator.validateMessage(oAuthRequest.getMessage(), oAuthRequest.getAccessor()); + log.debug("Request validated for {}", oAuthRequest.getAccessor().consumer.consumerKey); } catch (URISyntaxException e) { + log.warn("Failed to validate request from {}", oAuthRequest.getAccessor().consumer.consumerKey); throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR); } @@ -535,21 +552,44 @@ protected Response respondWithToken(String token, String tokenSecret, } String responseBody = OAuth.formEncode(oAuthParameters); + log.debug("Sending token to the consumer"); + if (log.isTraceEnabled()) { + log.trace("Body: {}", prettyPrint(oAuthParameters)); + } return Response.ok(responseBody) .type(MediaType.APPLICATION_FORM_URLENCODED) .header(OAuthServerConstants.HDR_CACHE_CONTROL, OAuthServerConstants.NO_CACHE).build(); } - protected Response respondWithOAuthProblem(OAuthException e) - throws IOException, ServletException { + private String prettyPrint(List oAuthParameters) { + StringBuilder sb = new StringBuilder(); + for (Parameter parameter : oAuthParameters) { + sb.append(parameter.getKey()) + .append('=') + .append(parameter.getValue()) + .append(';'); + } + return sb.toString(); + } + + protected Response respondWithOAuthProblem(OAuthException e, HttpServletRequest httpRequest, + HttpServletResponse httpResponse) { + log.warn("Problem encountered while preparing response", e); try { OAuthServlet.handleException(httpResponse, e, OAuthConfiguration .getInstance().getApplication().getRealm(httpRequest)); } catch (OAuthProblemException serviceUnavailableException) { + log.debug("OAuthProblemException thrown", serviceUnavailableException); return Response.status(Status.SERVICE_UNAVAILABLE).build(); + } catch (ServletException ex) { + log.debug("ServletException thrown", ex); + throw new WebApplicationException(ex); + } catch (IOException ex) { + log.debug("IO exception", ex); + throw new IllegalStateException(ex); } - + return Response.status(Status.UNAUTHORIZED).build(); } @@ -570,8 +610,8 @@ private String getCallbackURL(OAuthMessage message, // If this is OAuth 1.0a, the callback was passed when the consumer // asked for a request token. String requestToken = message.getToken(); - callback = OAuthConfiguration.getInstance().getTokenStrategy() - .getCallback(httpRequest, requestToken); + callback = OAuthConfiguration.getInstance().getJaxTokenStrategy() + .getCallback(requestToken); } if (callback == null) { @@ -582,15 +622,15 @@ private String getCallbackURL(OAuthMessage message, .queryParam(OAuth.OAUTH_TOKEN, message.getToken()); if (consumer.getOAuthVersion() == LyoOAuthConsumer.OAuthVersion.OAUTH_1_0A) { String verificationCode = OAuthConfiguration.getInstance() - .getTokenStrategy() - .generateVerificationCode(httpRequest, message.getToken()); + .getJaxTokenStrategy() + .generateVerificationCode(message.getToken()); uriBuilder.queryParam(OAuth.OAUTH_VERIFIER, verificationCode); } return uriBuilder.build().toString(); } - private String getRemoteHost() { + private String getRemoteHost(HttpServletRequest httpRequest) { try { // Try to get the hostname of the consumer. return InetAddress.getByName(httpRequest.getRemoteHost()) @@ -605,7 +645,8 @@ private String getRemoteHost() { } } - private Response showAdminLogin() throws ServletException, IOException { + private Response showAdminLogin(HttpServletRequest httpRequest, + HttpServletResponse httpResponse) throws ServletException, IOException { httpResponse.setHeader(OAuthServerConstants.HDR_CACHE_CONTROL, OAuthServerConstants.NO_CACHE); StringBuffer callback = httpRequest.getRequestURL(); String query = httpRequest.getQueryString();