diff -Nru spring-social-1.0.3.RELEASE/spring-social-core/src/main/java/org/springframework/social/connect/support/OAuth2ConnectionFactory.java spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-core/src/main/java/org/springframework/social/connect/support/OAuth2ConnectionFactory.java --- spring-social-1.0.3.RELEASE/spring-social-core/src/main/java/org/springframework/social/connect/support/OAuth2ConnectionFactory.java 2013-06-05 23:50:44.000000000 +0200 +++ spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-core/src/main/java/org/springframework/social/connect/support/OAuth2ConnectionFactory.java 2016-02-08 12:18:37.022762908 +0100 @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.springframework.social.connect.support; +import java.util.UUID; + import org.springframework.social.connect.ApiAdapter; import org.springframework.social.connect.Connection; import org.springframework.social.connect.ConnectionData; @@ -31,6 +33,8 @@ */ public class OAuth2ConnectionFactory<S> extends ConnectionFactory<S> { + private String scope = null; + /** * Create a {@link OAuth2ConnectionFactory}. * @param providerId the provider id e.g. "facebook" @@ -40,6 +44,35 @@ public OAuth2ConnectionFactory(String providerId, OAuth2ServiceProvider<S> serviceProvider, ApiAdapter<S> apiAdapter) { super(providerId, serviceProvider, apiAdapter); } + + /** + * Sets the default value to send in the scope parameter during authorization. + * Null by default, meaning that no scope parameter will be sent and the default scope will be determined by the provider. + * @param scope The default value to send as scope during authorization. + */ + public void setScope(String scope) { + this.scope = scope; + } + + public String getScope() { + return scope; + } + + /** + * Generates a value for the state parameter. + * @return a random UUID by default. + */ + public String generateState() { + return UUID.randomUUID().toString(); + } + + /** + * Indicates that this provider supports the state parameter in callbacks to prevent against CSRF. + * Default implementation returns true. + */ + public boolean supportsStateParameter() { + return true; + } /** * Get the ServiceProvider's {@link OAuth2Operations} that allows the client application to conduct the OAuth2 flow with the provider. @@ -83,4 +116,4 @@ return (OAuth2ServiceProvider<S>) getServiceProvider(); } -} \ Manca newline alla fine del file +} diff -Nru spring-social-1.0.3.RELEASE/spring-social-core/src/main/java/org/springframework/social/oauth2/AbstractOAuth2ServiceProvider.java spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-core/src/main/java/org/springframework/social/oauth2/AbstractOAuth2ServiceProvider.java --- spring-social-1.0.3.RELEASE/spring-social-core/src/main/java/org/springframework/social/oauth2/AbstractOAuth2ServiceProvider.java 2013-06-05 23:50:44.000000000 +0200 +++ spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-core/src/main/java/org/springframework/social/oauth2/AbstractOAuth2ServiceProvider.java 2016-02-08 12:19:48.511653995 +0100 @@ -40,5 +40,5 @@ } public abstract S getApi(String accessToken); - + } \ Manca newline alla fine del file diff -Nru spring-social-1.0.3.RELEASE/spring-social-web/src/main/java/org/springframework/social/connect/web/ConnectSupport.java spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/main/java/org/springframework/social/connect/web/ConnectSupport.java --- spring-social-1.0.3.RELEASE/spring-social-web/src/main/java/org/springframework/social/connect/web/ConnectSupport.java 2013-06-05 23:50:44.000000000 +0200 +++ spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/main/java/org/springframework/social/connect/web/ConnectSupport.java 2016-02-08 12:17:09.990765180 +0100 @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,11 @@ */ package org.springframework.social.connect.web; +import static java.util.Arrays.*; + +import java.util.List; +import java.util.Map.Entry; + import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; @@ -32,6 +37,7 @@ import org.springframework.social.oauth2.GrantType; import org.springframework.social.oauth2.OAuth2Operations; import org.springframework.social.oauth2.OAuth2Parameters; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.context.request.NativeWebRequest; @@ -51,6 +57,8 @@ private String applicationUrl; + private String callbackUrl; + /** * Flag indicating if this instance will support OAuth-based authentication instead of the traditional user authorization. * Some providers expose a special "authenticateUrl" the user should be redirected to as part of an OAuth-based authentication attempt. @@ -75,6 +83,16 @@ public void setApplicationUrl(String applicationUrl) { this.applicationUrl = applicationUrl; } + + /** + * Configures a specific callback URL that is to be used instead of calculating one based on the application URL or current request URL. + * When set this URL will override the default behavior where the callback URL is derived from the current request and/or a specified application URL. + * When set along with applicationUrl, the applicationUrl will be ignored. + * @param callbackUrl the callback URL to send to providers during authorization. Default is null. + */ + public void setCallbackUrl(String callbackUrl) { + this.callbackUrl = callbackUrl; + } /** * Builds the provider URL to redirect the user to for connection authorization. @@ -125,6 +143,10 @@ * @return a new connection to the service provider */ public Connection<?> completeConnection(OAuth2ConnectionFactory<?> connectionFactory, NativeWebRequest request) { + if (connectionFactory.supportsStateParameter()) { + verifyStateParameter(request); + } + String code = request.getParameter("code"); try { AccessGrant accessGrant = connectionFactory.getOAuthOperations().exchangeForAccess(code, callbackUrl(request), null); @@ -136,29 +158,61 @@ } } + private void verifyStateParameter(NativeWebRequest request) { + String state = request.getParameter("state"); + String originalState = extractCachedOAuth2State(request); + if (state != null && !state.equals(originalState)) { + throw new IllegalStateException("The OAuth2 'state' parameter doesn't match."); + } + } + + protected String callbackUrl(NativeWebRequest request) { + if (callbackUrl != null) { + return callbackUrl; + } + HttpServletRequest nativeRequest = request.getNativeRequest(HttpServletRequest.class); + if (applicationUrl != null) { + return applicationUrl + connectPath(nativeRequest); + } else { + return nativeRequest.getRequestURL().toString(); + } + } + // internal helpers private String buildOAuth1Url(OAuth1ConnectionFactory<?> connectionFactory, NativeWebRequest request, MultiValueMap<String, String> additionalParameters) { OAuth1Operations oauthOperations = connectionFactory.getOAuthOperations(); - OAuth1Parameters parameters = new OAuth1Parameters(additionalParameters); + MultiValueMap<String, String> requestParameters = getRequestParameters(request); + OAuth1Parameters parameters = getOAuth1Parameters(request, additionalParameters); + parameters.putAll(requestParameters); if (oauthOperations.getVersion() == OAuth1Version.CORE_10) { parameters.setCallbackUrl(callbackUrl(request)); } - OAuthToken requestToken = fetchRequestToken(request, oauthOperations); + OAuthToken requestToken = fetchRequestToken(request, requestParameters, oauthOperations); request.setAttribute(OAUTH_TOKEN_ATTRIBUTE, requestToken, RequestAttributes.SCOPE_SESSION); return buildOAuth1Url(oauthOperations, requestToken.getValue(), parameters); } - private OAuthToken fetchRequestToken(NativeWebRequest request, OAuth1Operations oauthOperations) { + private OAuth1Parameters getOAuth1Parameters(NativeWebRequest request, MultiValueMap<String, String> additionalParameters) { + OAuth1Parameters parameters = new OAuth1Parameters(additionalParameters); + parameters.putAll(getRequestParameters(request)); + return parameters; + } + + private OAuthToken fetchRequestToken(NativeWebRequest request, MultiValueMap<String, String> requestParameters, OAuth1Operations oauthOperations) { if (oauthOperations.getVersion() == OAuth1Version.CORE_10_REVISION_A) { - return oauthOperations.fetchRequestToken(callbackUrl(request), null); + return oauthOperations.fetchRequestToken(callbackUrl(request), requestParameters); } - return oauthOperations.fetchRequestToken(null, null); + return oauthOperations.fetchRequestToken(null, requestParameters); } private String buildOAuth2Url(OAuth2ConnectionFactory<?> connectionFactory, NativeWebRequest request, MultiValueMap<String, String> additionalParameters) { OAuth2Operations oauthOperations = connectionFactory.getOAuthOperations(); - OAuth2Parameters parameters = getOAuth2Parameters(request, additionalParameters); + String defaultScope = connectionFactory.getScope(); + OAuth2Parameters parameters = getOAuth2Parameters(request, defaultScope, additionalParameters); + String state = connectionFactory.generateState(); + parameters.add("state", state); + request.setAttribute(OAUTH2_STATE_ATTRIBUTE, state, RequestAttributes.SCOPE_SESSION); if (useAuthenticateUrl) { return oauthOperations.buildAuthenticateUrl(GrantType.AUTHORIZATION_CODE, parameters); } else { @@ -166,25 +220,19 @@ } } - private OAuth2Parameters getOAuth2Parameters(NativeWebRequest request, MultiValueMap<String, String> additionalParameters) { + private OAuth2Parameters getOAuth2Parameters(NativeWebRequest request, String defaultScope, MultiValueMap<String, String> additionalParameters) { OAuth2Parameters parameters = new OAuth2Parameters(additionalParameters); + parameters.putAll(getRequestParameters(request, "scope")); parameters.setRedirectUri(callbackUrl(request)); String scope = request.getParameter("scope"); if (scope != null) { parameters.setScope(scope); + } else if (defaultScope != null) { + parameters.setScope(defaultScope); } return parameters; } - private String callbackUrl(NativeWebRequest request) { - HttpServletRequest nativeRequest = request.getNativeRequest(HttpServletRequest.class); - if (applicationUrl != null) { - return applicationUrl + connectPath(nativeRequest); - } else { - return nativeRequest.getRequestURL().toString(); - } - } - private String connectPath(HttpServletRequest request) { String pathInfo = request.getPathInfo(); return request.getServletPath() + (pathInfo != null ? pathInfo : ""); @@ -204,6 +252,25 @@ return requestToken; } + private String extractCachedOAuth2State(WebRequest request) { + String state = (String) request.getAttribute(OAUTH2_STATE_ATTRIBUTE, RequestAttributes.SCOPE_SESSION); + request.removeAttribute(OAUTH2_STATE_ATTRIBUTE, RequestAttributes.SCOPE_SESSION); + return state; + } + + private MultiValueMap<String, String> getRequestParameters(NativeWebRequest request, String... ignoredParameters) { + List<String> ignoredParameterList = asList(ignoredParameters); + MultiValueMap<String, String> convertedMap = new LinkedMultiValueMap<String, String>(); + for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) { + if (!ignoredParameterList.contains(entry.getKey())) { + convertedMap.put(entry.getKey(), asList(entry.getValue())); + } + } + return convertedMap; + } + private static final String OAUTH_TOKEN_ATTRIBUTE = "oauthToken"; + + private static final String OAUTH2_STATE_ATTRIBUTE = "oauth2State"; -} \ Manca newline alla fine del file +} diff -Nru spring-social-1.0.3.RELEASE/spring-social-web/src/test/java/org/springframework/social/connect/web/ConnectControllerTest.java spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/test/java/org/springframework/social/connect/web/ConnectControllerTest.java --- spring-social-1.0.3.RELEASE/spring-social-web/src/test/java/org/springframework/social/connect/web/ConnectControllerTest.java 2013-06-05 23:50:44.000000000 +0200 +++ spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/test/java/org/springframework/social/connect/web/ConnectControllerTest.java 2016-02-08 12:23:09.845087336 +0100 @@ -209,7 +209,7 @@ connectionFactoryLocator.addConnectionFactory(connectionFactory); MockMvc mockMvc = standaloneSetup(new ConnectController(connectionFactoryLocator, null)).build(); mockMvc.perform(post("/connect/oauth2Provider")) - .andExpect(redirectedUrl(OAUTH2_AUTHORIZE_URL)); + .andExpect(redirectedUrl(OAUTH2_AUTHORIZE_URL + "&state=STATE")); } @Test @@ -219,7 +219,7 @@ connectionFactoryLocator.addConnectionFactory(connectionFactory); MockMvc mockMvc = standaloneSetup(new ConnectController(connectionFactoryLocator, null)).build(); mockMvc.perform(post("/connect/oauth2Provider").param("scope", "read,write")) - .andExpect(redirectedUrl(OAUTH2_AUTHORIZE_URL + "&scope=read%2Cwrite")); + .andExpect(redirectedUrl(OAUTH2_AUTHORIZE_URL + "&scope=read%2Cwrite&state=STATE")); } @Test diff -Nru spring-social-1.0.3.RELEASE/spring-social-web/src/test/java/org/springframework/social/connect/web/ConnectSupportTest.java spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/test/java/org/springframework/social/connect/web/ConnectSupportTest.java --- spring-social-1.0.3.RELEASE/spring-social-web/src/test/java/org/springframework/social/connect/web/ConnectSupportTest.java 2013-06-05 23:50:44.000000000 +0200 +++ spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/test/java/org/springframework/social/connect/web/ConnectSupportTest.java 2016-02-08 12:29:25.107998446 +0100 @@ -200,7 +200,7 @@ mockRequest.setRequestURI("/connect/someprovider"); ServletWebRequest request = new ServletWebRequest(mockRequest); String url = support.buildOAuthUrl(new TestOAuth2ConnectionFactory(), request); - assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=http://somesite.com/connect/someprovider", url); + assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=http://somesite.com/connect/someprovider&state=STATE", url); } @Test @@ -213,7 +213,7 @@ mockRequest.setServletPath("/appname/connect/someprovider"); ServletWebRequest request = new ServletWebRequest(mockRequest); String url = support.buildOAuthUrl(new TestOAuth2ConnectionFactory(), request); - assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=https://someothersite.com:1234/appname/connect/someprovider", url); + assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=https://someothersite.com:1234/appname/connect/someprovider&state=STATE", url); } @Test @@ -226,7 +226,7 @@ mockRequest.setServletPath("/connect/someprovider"); ServletWebRequest request = new ServletWebRequest(mockRequest); String url = support.buildOAuthUrl(new TestOAuth2ConnectionFactory(), request); - assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=https://someothersite.com:1234/connect/someprovider", url); + assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=https://someothersite.com:1234/connect/someprovider&state=STATE", url); } @Test @@ -240,7 +240,7 @@ mockRequest.setPathInfo("/connect/someprovider"); ServletWebRequest request = new ServletWebRequest(mockRequest); String url = support.buildOAuthUrl(new TestOAuth2ConnectionFactory(), request); - assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=https://someothersite.com:1234/spring-social-showcase/foo/connect/someprovider", url); + assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=https://someothersite.com:1234/spring-social-showcase/foo/connect/someprovider&state=STATE", url); } @Test @@ -253,7 +253,7 @@ mockRequest.setServletPath("/connect/someprovider"); ServletWebRequest request = new ServletWebRequest(mockRequest); String url = support.buildOAuthUrl(new TestOAuth2ConnectionFactory(), request); - assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=http://ec2.instance.com:8080/spring-social/showcase/connect/someprovider", url); + assertEquals("https://serviceprovider.com/oauth/authorize?redirect_uri=http://ec2.instance.com:8080/spring-social/showcase/connect/someprovider&state=STATE", url); } @Test @@ -267,7 +267,7 @@ ServletWebRequest request = new ServletWebRequest(mockRequest); TestOAuth2ConnectionFactory connectionFactory = new TestOAuth2ConnectionFactory(); String url = support.buildOAuthUrl(connectionFactory, request); - assertEquals("https://serviceprovider.com/oauth/authenticate?redirect_uri=http://somesite.com/connect/someprovider", url); + assertEquals("https://serviceprovider.com/oauth/authenticate?redirect_uri=http://somesite.com/connect/someprovider&state=STATE", url); } @Test @@ -282,7 +282,7 @@ MultiValueMap<String, String> additionalParameters = new LinkedMultiValueMap<String, String>(); additionalParameters.set("display", "popup"); String url = support.buildOAuthUrl(connectionFactory, request, additionalParameters); - assertEquals("https://serviceprovider.com/oauth/authorize?display=popup&redirect_uri=http://somesite.com/connect/someprovider", url); + assertEquals("https://serviceprovider.com/oauth/authorize?display=popup&redirect_uri=http://somesite.com/connect/someprovider&state=STATE", url); } private static class PortAwareMockHttpServletRequest extends MockHttpServletRequest { @@ -408,6 +408,12 @@ return new OAuth2Connection<TestApi>("someprovider", "providerUserId", accessGrant.getAccessToken(), accessGrant.getRefreshToken(), accessGrant.getExpireTime(), SERVICE_PROVIDER, API_ADAPTER); } + + + @Override + public String generateState() { + return "STATE"; + } } private static class TestOAuth2ServiceProvider implements OAuth2ServiceProvider<TestApi> { diff -Nru spring-social-1.0.3.RELEASE/spring-social-web/src/test/java/org/springframework/social/connect/web/ProviderSignInControllerTest.java spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/test/java/org/springframework/social/connect/web/ProviderSignInControllerTest.java --- spring-social-1.0.3.RELEASE/spring-social-web/src/test/java/org/springframework/social/connect/web/ProviderSignInControllerTest.java 2013-06-05 23:50:44.000000000 +0200 +++ spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/test/java/org/springframework/social/connect/web/ProviderSignInControllerTest.java 2016-02-08 12:30:29.166398516 +0100 @@ -177,7 +177,7 @@ new ConnectionData("oauth2Provider", "provider2User1", null, null, null, null, null, null, null))); MockMvc mockMvc = standaloneSetup(new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, null)).build(); mockMvc.perform(post("/signin/oauth2Provider")) - .andExpect(redirectedUrl("https://someprovider.com/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A80%2Fsignin%2Foauth2Provider")); + .andExpect(redirectedUrl("https://someprovider.com/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A80%2Fsignin%2Foauth2Provider&state=STATE")); } @Test diff -Nru spring-social-1.0.3.RELEASE/spring-social-web/src/test/java/org/springframework/social/connect/web/test/StubOAuth2ConnectionFactory.java spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/test/java/org/springframework/social/connect/web/test/StubOAuth2ConnectionFactory.java --- spring-social-1.0.3.RELEASE/spring-social-web/src/test/java/org/springframework/social/connect/web/test/StubOAuth2ConnectionFactory.java 2013-06-05 23:50:44.000000000 +0200 +++ spring-social-1.0.3.RELEASE.CVE-2015-5258/spring-social-web/src/test/java/org/springframework/social/connect/web/test/StubOAuth2ConnectionFactory.java 2016-02-08 12:31:18.377632961 +0100 @@ -27,5 +27,9 @@ public StubOAuth2ConnectionFactory(String clientId, String clientSecret, StubOAuthTemplateBehavior behavior) { super("oauth2Provider", new StubOAuth2ServiceProvider(clientId, clientSecret, behavior), null); } - + + @Override + public String generateState() { + return "STATE"; + } }