From adb8c60173ddd6524e9ecd30b31f423a0979a3a4 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Fri, 25 May 2018 16:07:59 -0500 Subject: [PATCH] Extract OidcUserRequestUtils This logic is shared by both reactive and non-reactive clients. Issue: gh-5330 --- .../oidc/userinfo/OidcUserRequestUtils.java | 70 +++++++++++++++ .../userinfo/OidcUserRequestUtilsTests.java | 89 +++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtilsTests.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java new file mode 100644 index 0000000000..797528ceb4 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2018 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.oidc.userinfo; + +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * Utilities for working with the {@link OidcUserRequest} + * + * @author Rob Winch + * @since 5.1 + */ +final class OidcUserRequestUtils { + + /** + * Determines if an {@link OidcUserRequest} should attempt to retrieve the user info endpoint. Will return true if + * all of the following are true: + * + * + * @param userRequest + * @return + */ + static boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) { + // Auto-disabled if UserInfo Endpoint URI is not provided + ClientRegistration clientRegistration = userRequest.getClientRegistration(); + if (StringUtils.isEmpty(clientRegistration.getProviderDetails() + .getUserInfoEndpoint().getUri())) { + + return false; + } + + // The Claims requested by the profile, email, address, and phone scope values + // are returned from the UserInfo Endpoint (as described in Section 5.3.2), + // when a response_type value is used that results in an Access Token being issued. + // However, when no Access Token is issued, which is the case for the response_type=id_token, + // the resulting Claims are returned in the ID Token. + // The Authorization Code Grant Flow, which is response_type=code, results in an Access Token being issued. + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { + + // Return true if there is at least one match between the authorized scope(s) and UserInfo scope(s) + return CollectionUtils + .containsAny(userRequest.getAccessToken().getScopes(), userRequest.getClientRegistration().getScopes()); + } + + return false; + } + + private OidcUserRequestUtils() {} +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtilsTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtilsTests.java new file mode 100644 index 0000000000..9851c7fdd2 --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtilsTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2018 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.client.oidc.userinfo; + +import org.junit.Test; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.*; + +/** + * @author Rob Winch + * @since 5.1 + */ +public class OidcUserRequestUtilsTests { + private ClientRegistration.Builder registration = ClientRegistration.withRegistrationId("id") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationUri("https://example.com/oauth2/authorize") + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .userInfoUri("https://example.com/users/me") + .clientId("client-id") + .clientName("client-name") + .clientSecret("client-secret") + .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}") + .scope("user") + .tokenUri("https://example.com/oauth/access_token"); + + OidcIdToken idToken = new OidcIdToken("token123", Instant.now(), + Instant.now().plusSeconds(3600), Collections + .singletonMap(IdTokenClaimNames.SUB, "sub123")); + + OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, + "token", + Instant.now(), + Instant.now().plus(Duration.ofDays(1)), + Collections.singleton("user")); + + @Test + public void shouldRetrieveUserInfoWhenEndpointDefinedAndScopesOverlapThenTrue() { + assertThat(OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest())).isTrue(); + } + + @Test + public void shouldRetrieveUserInfoWhenNoUserInfoUriThenFalse() { + this.registration.userInfoUri(null); + + assertThat(OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest())).isFalse(); + } + + @Test + public void shouldRetrieveUserInfoWhenDifferentScopesThenFalse() { + this.registration.scope("notintoken"); + + assertThat(OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest())).isFalse(); + } + + @Test + public void shouldRetrieveUserInfoWhenNotAuthorizationCodeThenFalse() { + this.registration.authorizationGrantType(AuthorizationGrantType.IMPLICIT); + + assertThat(OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest())).isFalse(); + } + + private OidcUserRequest userRequest() { + return new OidcUserRequest(this.registration.build(), this.accessToken, this.idToken); + } +}