From de572be8e9bb36d85c371919e2c1f1632b146177 Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Sun, 12 Jul 2020 01:30:11 +0200 Subject: [PATCH] Add OAuth2AuthenticationException to allowlist Add mixins for - OAuth2AuthenticationException - OAuth2Error Closes gh-8797 --- .../OAuth2AuthenticationExceptionMixin.java | 46 ++++++ .../jackson2/OAuth2ClientJackson2Module.java | 8 + .../client/jackson2/OAuth2ErrorMixin.java | 47 ++++++ ...uth2AuthenticationExceptionMixinTests.java | 140 ++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationExceptionMixin.java create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ErrorMixin.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationExceptionMixinTests.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationExceptionMixin.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationExceptionMixin.java new file mode 100644 index 0000000000..187eb16d8e --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationExceptionMixin.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.jackson2; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; + +/** + * This mixin class is used to serialize/deserialize + * {@link OAuth2AuthenticationException}. + * + * @author Dennis Neufeld + * @since 5.3.4 + * @see OAuth2AuthenticationException + * @see OAuth2ClientJackson2Module + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true, value = {"cause", "stackTrace", "suppressedExceptions"}) +abstract class OAuth2AuthenticationExceptionMixin { + + @JsonCreator + OAuth2AuthenticationExceptionMixin( + @JsonProperty("error") OAuth2Error error, + @JsonProperty("detailMessage") String message) { + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.java index fa8dbcb200..4aa70fc847 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.java @@ -22,6 +22,8 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.oidc.OidcIdToken; @@ -50,6 +52,8 @@ import java.util.Collections; *
  • {@link OidcUserAuthorityMixin}
  • *
  • {@link DefaultOidcUserMixin}
  • *
  • {@link OAuth2AuthenticationTokenMixin}
  • + *
  • {@link OAuth2AuthenticationExceptionMixin}
  • + *
  • {@link OAuth2ErrorMixin}
  • * * * If not already enabled, default typing will be automatically enabled @@ -78,6 +82,8 @@ import java.util.Collections; * @see OidcUserAuthorityMixin * @see DefaultOidcUserMixin * @see OAuth2AuthenticationTokenMixin + * @see OAuth2AuthenticationExceptionMixin + * @see OAuth2ErrorMixin */ public class OAuth2ClientJackson2Module extends SimpleModule { @@ -101,5 +107,7 @@ public class OAuth2ClientJackson2Module extends SimpleModule { context.setMixInAnnotations(OidcUserAuthority.class, OidcUserAuthorityMixin.class); context.setMixInAnnotations(DefaultOidcUser.class, DefaultOidcUserMixin.class); context.setMixInAnnotations(OAuth2AuthenticationToken.class, OAuth2AuthenticationTokenMixin.class); + context.setMixInAnnotations(OAuth2AuthenticationException.class, OAuth2AuthenticationExceptionMixin.class); + context.setMixInAnnotations(OAuth2Error.class, OAuth2ErrorMixin.class); } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ErrorMixin.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ErrorMixin.java new file mode 100644 index 0000000000..aba0c5b61f --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2ErrorMixin.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.jackson2; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.springframework.security.oauth2.core.OAuth2Error; + +/** + * This mixin class is used to serialize/deserialize {@link OAuth2Error} as part of + * {@link org.springframework.security.oauth2.core.OAuth2AuthenticationException}. + * + * @author Dennis Neufeld + * @since 5.3.4 + * @see OAuth2Error + * @see OAuth2AuthenticationExceptionMixin + * @see OAuth2ClientJackson2Module + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +abstract class OAuth2ErrorMixin { + + @JsonCreator + OAuth2ErrorMixin( + @JsonProperty("errorCode") String errorCode, + @JsonProperty("description") String description, + @JsonProperty("uri") String uri) { + } +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationExceptionMixinTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationExceptionMixinTests.java new file mode 100644 index 0000000000..537c90026d --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationExceptionMixinTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.jackson2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests for {@link OAuth2AuthenticationExceptionMixin}. + * + * @author Dennis Neufeld + * @since 5.3.4 + */ +public class OAuth2AuthenticationExceptionMixinTests { + + private ObjectMapper mapper; + + @Before + public void setup() { + ClassLoader loader = getClass().getClassLoader(); + this.mapper = new ObjectMapper(); + this.mapper.registerModules(SecurityJackson2Modules.getModules(loader)); + } + + @Test + public void serializeWhenMixinRegisteredThenSerializes() throws Exception { + OAuth2AuthenticationException exception = new OAuth2AuthenticationException(new OAuth2Error( + "[authorization_request_not_found]", + "Authorization Request Not Found", + "/foo/bar" + ), "Authorization Request Not Found"); + + String serializedJson = this.mapper.writeValueAsString(exception); + String expected = asJson(exception); + JSONAssert.assertEquals(expected, serializedJson, true); + } + + @Test + public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception { + OAuth2AuthenticationException exception = new OAuth2AuthenticationException( + new OAuth2Error("[authorization_request_not_found]") + ); + + String serializedJson = this.mapper.writeValueAsString(exception); + String expected = asJson(exception); + JSONAssert.assertEquals(expected, serializedJson, true); + } + + @Test + public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() { + String json = asJson(new OAuth2AuthenticationException( + new OAuth2Error("[authorization_request_not_found]") + )); + assertThatThrownBy(() -> new ObjectMapper().readValue(json, OAuth2AuthenticationException.class)) + .isInstanceOf(JsonProcessingException.class); + } + + @Test + public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception { + OAuth2AuthenticationException expected = new OAuth2AuthenticationException(new OAuth2Error( + "[authorization_request_not_found]", + "Authorization Request Not Found", + "/foo/bar" + ), "Authorization Request Not Found"); + + OAuth2AuthenticationException exception = this.mapper.readValue(asJson(expected), OAuth2AuthenticationException.class); + assertThat(exception).isNotNull(); + assertThat(exception.getCause()).isNull(); + assertThat(exception.getMessage()).isEqualTo(expected.getMessage()); + + OAuth2Error oauth2Error = exception.getError(); + assertThat(oauth2Error).isNotNull(); + assertThat(oauth2Error.getErrorCode()).isEqualTo(expected.getError().getErrorCode()); + assertThat(oauth2Error.getDescription()).isEqualTo(expected.getError().getDescription()); + assertThat(oauth2Error.getUri()).isEqualTo(expected.getError().getUri()); + } + + @Test + public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Exception { + OAuth2AuthenticationException expected = new OAuth2AuthenticationException( + new OAuth2Error("[authorization_request_not_found]") + ); + + OAuth2AuthenticationException exception = this.mapper.readValue(asJson(expected), OAuth2AuthenticationException.class); + assertThat(exception).isNotNull(); + assertThat(exception.getCause()).isNull(); + assertThat(exception.getMessage()).isNull(); + + OAuth2Error oauth2Error = exception.getError(); + assertThat(oauth2Error).isNotNull(); + assertThat(oauth2Error.getErrorCode()).isEqualTo(expected.getError().getErrorCode()); + assertThat(oauth2Error.getDescription()).isNull(); + assertThat(oauth2Error.getUri()).isNull(); + } + + private String asJson(OAuth2AuthenticationException exception) { + OAuth2Error error = exception.getError(); + // @formatter:off + return "\n{" + + "\n \"@class\": \"org.springframework.security.oauth2.core.OAuth2AuthenticationException\"," + + "\n \"error\":" + + "\n {" + + "\n \"@class\":\"org.springframework.security.oauth2.core.OAuth2Error\"," + + "\n \"errorCode\":\"" + error.getErrorCode() + "\"," + + "\n \"description\":" + jsonStringOrNull(error.getDescription()) + "," + + "\n \"uri\":" + jsonStringOrNull(error.getUri()) + + "\n }," + + "\n \"detailMessage\":" + jsonStringOrNull(exception.getMessage()) + + "\n}"; + // @formatter:on + } + + private String jsonStringOrNull(String input) { + return input != null + ? "\"" + input + "\"" + : "null"; + } +}