diff --git a/api/src/main/java/io/jsonwebtoken/io/ParserBuilder.java b/api/src/main/java/io/jsonwebtoken/io/ParserBuilder.java index 595fa6c45..faf62f940 100644 --- a/api/src/main/java/io/jsonwebtoken/io/ParserBuilder.java +++ b/api/src/main/java/io/jsonwebtoken/io/ParserBuilder.java @@ -40,15 +40,15 @@ public interface ParserBuilder> extends Builder B provider(Provider provider); /** - * Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. The + * Uses the specified reader to convert JSON Strings (UTF-8 byte streams) into Java Map objects. The * resulting Maps are then used to construct respective JWT objects (JWTs, JWKs, etc). * - *

If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the + *

If this method is not called, JJWT will use whatever Reader it can find at runtime, checking for the * presence of well-known implementations such as Jackson, Gson, and org.json. If one of these is not found * in the runtime classpath, an exception will be thrown when the {@link #build()} method is called. * - * @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects. + * @param reader the Reader to use when converting JSON Strings (UTF-8 byte streams) into Map objects. * @return the builder for method chaining. */ - B deserializer(Deserializer> deserializer); + B jsonReader(Reader> reader); } diff --git a/api/src/main/java/io/jsonwebtoken/security/JwkSetParserBuilder.java b/api/src/main/java/io/jsonwebtoken/security/JwkSetParserBuilder.java index 4254879c5..6709fbb3a 100644 --- a/api/src/main/java/io/jsonwebtoken/security/JwkSetParserBuilder.java +++ b/api/src/main/java/io/jsonwebtoken/security/JwkSetParserBuilder.java @@ -23,9 +23,10 @@ * Example usage: *

  * JwkSet jwkSet = Jwks.setParser()
- *         .provider(aJcaProvider)     // optional
- *         .deserializer(deserializer) // optional
- *         .operationPolicy(policy)    // optional
+ *         .provider(aJcaProvider)      // optional
+ *         .deserializer(deserializer)  // optional
+ *         .operationPolicy(policy)     // optional
+ *         .ignoreUnsupported(aBoolean) // optional
  *         .build()
  *         .parse(jwkSetString);
* diff --git a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonWriter.java b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonWriter.java index a7e599b9a..038323f90 100644 --- a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonWriter.java +++ b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonWriter.java @@ -58,10 +58,6 @@ public JacksonWriter(ObjectMapper objectMapper) { @Override public void write(Writer out, T t) throws IOException { Assert.notNull(out, "Writer cannot be null."); - writeValue(t, out); - } - - protected void writeValue(T t, Writer writer) throws java.io.IOException { - this.objectMapper.writeValue(writer, t); + this.objectMapper.writeValue(out, t); } } diff --git a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSerializerTest.groovy b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSerializerTest.groovy index 5db57d09a..0f2594a06 100644 --- a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSerializerTest.groovy +++ b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSerializerTest.groovy @@ -32,7 +32,7 @@ class JacksonSerializerTest { @Before void setUp() { - serializer = new JacksonSerializer() + serializer = new JacksonSerializer() } @Test @@ -43,7 +43,7 @@ class JacksonSerializerTest { @Test void testDefaultConstructor() { - assertSame JacksonWriter.DEFAULT_OBJECT_MAPPER, serializer.objectMapper + assertSame JacksonWriter.DEFAULT_OBJECT_MAPPER, serializer.objectMapper } @Test @@ -67,11 +67,10 @@ class JacksonSerializerTest { @Test void testSerializeFailsWithJsonProcessingException() { - def ex = new IOException('foo') def serializer = new JacksonSerializer() { @Override - protected void writeValue(Object o, Writer writer) throws IOException { + void write(Writer out, Object o) throws IOException { throw ex } } diff --git a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonWriterTest.groovy b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonWriterTest.groovy index d852db730..049ea5a04 100644 --- a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonWriterTest.groovy +++ b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonWriterTest.groovy @@ -117,12 +117,10 @@ class JacksonWriterTest { @Test void testWriteFailsWithJsonProcessingException() { - def ex = new IOException('foo') - writer = new JacksonWriter() { @Override - protected void writeValue(Object o, java.io.Writer writer) throws java.io.IOException { + void write(java.io.Writer out, Object o) throws java.io.IOException { throw ex } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java index a0b261140..1d68c5d77 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java @@ -21,8 +21,8 @@ import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.impl.io.MapSerializer; import io.jsonwebtoken.impl.io.SerializingMapWriter; +import io.jsonwebtoken.impl.io.WritingSerializer; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Functions; @@ -153,7 +153,7 @@ public JwtBuilder jsonWriter(Writer> writer) { private byte[] serialize(Map map) { Nameable nameable = Assert.isInstanceOf(Nameable.class, map, "JWT internal maps implement Nameable."); Writer> jsonWriter = Assert.stateNotNull(this.jsonWriter, "JSON Writer cannot be null."); - MapSerializer serializer = new MapSerializer(jsonWriter, nameable.getName()); + WritingSerializer> serializer = new WritingSerializer<>(jsonWriter, nameable.getName()); ByteArrayOutputStream baos = new ByteArrayOutputStream(256); java.io.Writer writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8); try { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index bf548f8b6..015a707f6 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -39,7 +39,7 @@ import io.jsonwebtoken.ProtectedHeader; import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.UnsupportedJwtException; -import io.jsonwebtoken.impl.io.JwtDeserializer; +import io.jsonwebtoken.impl.io.JsonObjectDeserializer; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.RedactedSupplier; @@ -917,8 +917,8 @@ protected byte[] decode(CharSequence base64UrlEncoded, String name) { ByteArrayInputStream is = new ByteArrayInputStream(bytes); java.io.Reader r = new InputStreamReader(is); try { - JwtDeserializer> jwtd = new JwtDeserializer<>(jsonReader, name); - return jwtd.apply(r); + JsonObjectDeserializer deserializer = new JsonObjectDeserializer(jsonReader, name); + return deserializer.apply(r); } finally { Objects.nullSafeClose(r); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/AbstractParserBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/io/AbstractParserBuilder.java index 531022f87..cfb85f0a0 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/AbstractParserBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/AbstractParserBuilder.java @@ -16,9 +16,9 @@ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.lang.Services; -import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.io.ParserBuilder; +import io.jsonwebtoken.io.Reader; import java.security.Provider; import java.util.Map; @@ -27,7 +27,7 @@ public abstract class AbstractParserBuilder> im protected Provider provider; - protected Deserializer> deserializer; + protected Reader> jsonReader; @SuppressWarnings("unchecked") protected final B self() { @@ -41,17 +41,16 @@ public B provider(Provider provider) { } @Override - public B deserializer(Deserializer> deserializer) { - this.deserializer = deserializer; + public B jsonReader(Reader> reader) { + this.jsonReader = reader; return self(); } @Override public final Parser build() { - if (this.deserializer == null) { - // try to find one based on the services available: + if (this.jsonReader == null) { //noinspection unchecked - this.deserializer = Services.loadFirst(Deserializer.class); + this.jsonReader = Services.loadFirst(Reader.class); } return doBuild(); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java b/impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java index d11c18b32..1b390b647 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java @@ -17,65 +17,31 @@ import io.jsonwebtoken.impl.lang.Converter; import io.jsonwebtoken.impl.lang.Function; -import io.jsonwebtoken.io.DeserializationException; -import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.lang.Assert; -import java.nio.charset.StandardCharsets; +import java.io.Reader; +import java.io.StringReader; import java.util.Map; public class ConvertingParser implements Parser { - private final Deserializer deserializer; + private final Function> deserializer; private final Converter converter; - private final Function exceptionHandler; - public ConvertingParser(Deserializer> deserializer, Converter converter, - Function exceptionHandler) { - this.deserializer = Assert.notNull(deserializer, "Deserializer cannot be null."); - this.converter = Assert.notNull(converter, "Converter canot be null."); - this.exceptionHandler = Assert.notNull(exceptionHandler, "exceptionHandler function cannot be null."); - } - - private RuntimeException doThrow(Throwable t) { - DeserializationException e = t instanceof DeserializationException ? (DeserializationException) t : - new DeserializationException("Unable to deserialize JSON: " + t.getMessage(), t); - throw Assert.notNull(this.exceptionHandler.apply(e), "Exception handler cannot return null."); - } - - private Map deserialize(String json) { - Assert.hasText(json, "JSON string cannot be null or empty."); - byte[] data = json.getBytes(StandardCharsets.UTF_8); - try { - return deserialize(data); - } catch (Throwable t) { - throw doThrow(t); - } - } - - @SuppressWarnings("unchecked") - private Map deserialize(byte[] data) { - Object val = this.deserializer.deserialize(data); - if (val == null) { - String msg = "Deserialized data resulted in a null value; cannot create Map"; - throw new DeserializationException(msg); - } - if (!(val instanceof Map)) { - String msg = "Deserialized data is not a JSON Object; cannot create Map"; - throw new DeserializationException(msg); - } - // JSON Specification requires all JSON Objects to have string-only keys. So instead of - // checking that the val.keySet() has all Strings, we blindly cast to a Map - // since input would rarely, if ever have non-string keys. Even if it did, the resulting - // ClassCastException would be caught by the calling deserialize(String) method above. - return (Map) val; + public ConvertingParser(Function> deserializer, Converter converter) { + this.deserializer = Assert.notNull(deserializer, "Deserializer function cannot be null."); + this.converter = Assert.notNull(converter, "Converter cannot be null."); } @Override - public final T parse(String input) { - Map m = deserialize(input); + Assert.hasText(input, "Parse input String cannot be null or empty."); + return parse(new StringReader(input)); + } + + public final T parse(Reader reader) { + Map m = this.deserializer.apply(reader); return this.converter.applyFrom(m); } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java b/impl/src/main/java/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java new file mode 100644 index 000000000..05def5c83 --- /dev/null +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * 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 io.jsonwebtoken.impl.io; + +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.io.DeserializationException; +import io.jsonwebtoken.io.Reader; +import io.jsonwebtoken.lang.Assert; + +import java.util.Map; + +/** + * Function that wraps a {@link Reader} to add JWT-related error handling. + * + * @since 0.11.3 (renamed from JwtDeserializer) + */ +public class JsonObjectDeserializer implements Function> { + + private static final String MALFORMED_ERROR = "Malformed %s JSON: %s"; + private static final String MALFORMED_COMPLEX_ERROR = "Malformed or excessively complex %s JSON. " + + "If experienced in a production environment, this could reflect a potential malicious %s, please " + + "investigate the source further. Cause: %s"; + + private final Reader reader; + private final String name; + + public JsonObjectDeserializer(Reader reader, String name) { + this.reader = Assert.notNull(reader, "reader cannot be null."); + this.name = Assert.hasText(name, "name cannot be null or empty."); + } + + @Override + public Map apply(java.io.Reader reader) { + Assert.notNull(reader, "Reader argument cannot be null."); + Object value; + try { + value = this.reader.read(reader); + if (value == null) { + String msg = "Deserialized data resulted in a null value; cannot create Map"; + throw new DeserializationException(msg); + } + if (!(value instanceof Map)) { + String msg = "Deserialized data is not a JSON Object; cannot create Map"; + throw new DeserializationException(msg); + } + // JSON Specification requires all JSON Objects to have string-only keys. So instead of + // checking that the val.keySet() has all Strings, we blindly cast to a Map + // since input would rarely, if ever, have non-string keys. + //noinspection unchecked + return (Map) value; + } catch (StackOverflowError e) { + String msg = String.format(MALFORMED_COMPLEX_ERROR, this.name, this.name, e.getMessage()); + throw new DeserializationException(msg, e); + } catch (Throwable t) { + throw malformed(t); + } + } + + protected RuntimeException malformed(Throwable t) { + String msg = String.format(MALFORMED_ERROR, this.name, t.getMessage()); + throw new MalformedJwtException(msg, t); + } +} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/JwtDeserializer.java b/impl/src/main/java/io/jsonwebtoken/impl/io/JwtDeserializer.java deleted file mode 100644 index ada3e9c47..000000000 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/JwtDeserializer.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2021 jsonwebtoken.io - * - * 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 io.jsonwebtoken.impl.io; - -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.impl.lang.Function; -import io.jsonwebtoken.io.DeserializationException; -import io.jsonwebtoken.io.Reader; -import io.jsonwebtoken.lang.Assert; - -/** - * Function that wraps a {@link Reader} to add JWT-related error handling. - * - * @param type of object to deserialize. - * @since 0.11.3 - */ -public final class JwtDeserializer implements Function { - - static final String MALFORMED_ERROR = "Malformed %s JSON: %s"; - static final String MALFORMED_COMPLEX_ERROR = "Malformed or excessively complex %s JSON. This could reflect a " + - "potential malicious JWT, please investigate the JWT source further. Cause: %s"; - - private final Reader reader; - private final String name; - - public JwtDeserializer(Reader reader, String name) { - this.reader = Assert.notNull(reader, "reader cannot be null."); - this.name = Assert.hasText(name, "name cannot be null or empty."); - } - - @Override - public T apply(java.io.Reader reader) { - Assert.notNull(reader, "Reader argument cannot be null."); - try { - return this.reader.read(reader); - } catch (StackOverflowError e) { - String msg = String.format(MALFORMED_COMPLEX_ERROR, this.name, e.getMessage()); - throw new DeserializationException(msg, e); - } catch (Throwable t) { - String msg = String.format(MALFORMED_ERROR, this.name, t.getMessage()); - throw new MalformedJwtException(msg, t); - } - } -} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/MapSerializer.java b/impl/src/main/java/io/jsonwebtoken/impl/io/WritingSerializer.java similarity index 78% rename from impl/src/main/java/io/jsonwebtoken/impl/io/MapSerializer.java rename to impl/src/main/java/io/jsonwebtoken/impl/io/WritingSerializer.java index f0929ce13..34c03948f 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/MapSerializer.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/WritingSerializer.java @@ -20,22 +20,20 @@ import io.jsonwebtoken.io.Writer; import io.jsonwebtoken.lang.Assert; -import java.util.Map; +public class WritingSerializer implements BiConsumer { -public class MapSerializer implements BiConsumer> { - - private final Writer> DELEGATE; + private final Writer DELEGATE; private final String name; - public MapSerializer(Writer> jsonWriter, String name) { + public WritingSerializer(Writer jsonWriter, String name) { this.DELEGATE = Assert.notNull(jsonWriter, "JSON Writer cannot be null."); this.name = Assert.hasText(name, "Name cannot be null or empty."); } @Override - public void accept(java.io.Writer writer, Map map) { + public void accept(java.io.Writer writer, T object) { try { - this.DELEGATE.write(writer, map); + this.DELEGATE.write(writer, object); } catch (Throwable t) { String msg = String.format("Cannot serialize %s to JSON. Cause: %s", name, t.getMessage()); throw new SerializationException(msg, t); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java index e984c0409..25f8cdae3 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java @@ -16,24 +16,17 @@ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.ConvertingParser; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.JwkParserBuilder; -import io.jsonwebtoken.security.MalformedKeyException; public class DefaultJwkParserBuilder extends AbstractJwkParserBuilder, JwkParserBuilder> implements JwkParserBuilder { @Override public Parser> doBuild() { - return new ConvertingParser<>(this.deserializer, - new JwkConverter<>(new JwkBuilderSupplier(this.provider, this.operationPolicy)), - new Function() { - @Override - public RuntimeException apply(Throwable t) { - String msg = "Unable to deserialize JWK: " + t.getMessage(); - return new MalformedKeyException(msg, t); - } - }); + JwkDeserializer deserializer = new JwkDeserializer(this.jsonReader); + JwkBuilderSupplier supplier = new JwkBuilderSupplier(this.provider, this.operationPolicy); + JwkConverter> converter = new JwkConverter<>(supplier); + return new ConvertingParser<>(deserializer, converter); } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java index 6fc354941..4236691e1 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java @@ -16,11 +16,9 @@ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.io.ConvertingParser; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.security.JwkSet; import io.jsonwebtoken.security.JwkSetParserBuilder; -import io.jsonwebtoken.security.MalformedKeySetException; public class DefaultJwkSetParserBuilder extends AbstractJwkParserBuilder implements JwkSetParserBuilder { @@ -35,15 +33,9 @@ public JwkSetParserBuilder ignoreUnsupported(boolean ignore) { @Override public Parser doBuild() { + JwkSetDeserializer deserializer = new JwkSetDeserializer(this.jsonReader); JwkBuilderSupplier supplier = new JwkBuilderSupplier(this.provider, this.operationPolicy); JwkSetConverter converter = new JwkSetConverter(supplier, this.ignoreUnsupported); - return new ConvertingParser<>(this.deserializer, converter, - new Function() { - @Override - public RuntimeException apply(Throwable t) { - String msg = "Unable to deserialize JWK Set: " + t.getMessage(); - return new MalformedKeySetException(msg, t); - } - }); + return new ConvertingParser<>(deserializer, converter); } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkDeserializer.java b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkDeserializer.java new file mode 100644 index 000000000..b6c998964 --- /dev/null +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkDeserializer.java @@ -0,0 +1,33 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * 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 io.jsonwebtoken.impl.security; + +import io.jsonwebtoken.impl.io.JsonObjectDeserializer; +import io.jsonwebtoken.io.Reader; +import io.jsonwebtoken.security.MalformedKeyException; + +public class JwkDeserializer extends JsonObjectDeserializer { + + public JwkDeserializer(Reader reader) { + super(reader, "JWK"); + } + + @Override + protected RuntimeException malformed(Throwable t) { + String msg = "Malformed JWK JSON: " + t.getMessage(); + return new MalformedKeyException(msg); + } +} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkSetDeserializer.java b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkSetDeserializer.java new file mode 100644 index 000000000..5fa11f544 --- /dev/null +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkSetDeserializer.java @@ -0,0 +1,33 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * 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 io.jsonwebtoken.impl.security; + +import io.jsonwebtoken.impl.io.JsonObjectDeserializer; +import io.jsonwebtoken.io.Reader; +import io.jsonwebtoken.security.MalformedKeySetException; + +public class JwkSetDeserializer extends JsonObjectDeserializer { + + public JwkSetDeserializer(Reader reader) { + super(reader, "JWK Set"); + } + + @Override + protected RuntimeException malformed(Throwable t) { + String msg = "Malformed JWK Set JSON: " + t.getMessage(); + throw new MalformedKeySetException(msg, t); + } +} diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/ConvertingParserTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/ConvertingParserTest.groovy deleted file mode 100644 index a70307d0b..000000000 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/io/ConvertingParserTest.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * 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 io.jsonwebtoken.impl.io - -import io.jsonwebtoken.impl.lang.Function -import io.jsonwebtoken.impl.lang.Functions -import io.jsonwebtoken.impl.security.JwkConverter -import io.jsonwebtoken.io.DeserializationException -import io.jsonwebtoken.io.Deserializer -import org.junit.Test - -import static org.junit.Assert.assertEquals -import static org.junit.Assert.assertSame - -class ConvertingParserTest { - - /** - * Asserts that if a non-DeserializationException is thrown during deserialization, that it is wrapped in a - * DeserializationException - */ - @Test - void testDeserializeNonDeserializationException() { - def cause = new IllegalArgumentException("test") - def deserializer = new Deserializer>() { - @Override - Map deserialize(byte[] bytes) throws DeserializationException { - throw cause - } - } - def parser = new ConvertingParser(deserializer, JwkConverter.ANY, - Functions.identity() as Function) - - try { - parser.parse('foo') - } catch (DeserializationException expected) { - String msg = "Unable to deserialize JSON: test" - assertEquals msg, expected.getMessage() - assertSame cause, expected.cause - } - } -} diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/JwtDeserializerTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/JsonObjectDeserializerTest.groovy similarity index 86% rename from impl/src/test/groovy/io/jsonwebtoken/impl/io/JwtDeserializerTest.groovy rename to impl/src/test/groovy/io/jsonwebtoken/impl/io/JsonObjectDeserializerTest.groovy index 0b54a1803..defef0b6a 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/io/JwtDeserializerTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/io/JsonObjectDeserializerTest.groovy @@ -23,7 +23,7 @@ import org.junit.Test import static org.junit.Assert.* -class JwtDeserializerTest { +class JsonObjectDeserializerTest { /** * It's possible for JSON parsers to throw a StackOverflowError when body is deeply nested. Since it's possible @@ -41,10 +41,10 @@ class JwtDeserializerTest { try { // doesn't matter for this test, just has to be non-null: def r = new InputStreamReader(new ByteArrayInputStream(Bytes.EMPTY)) - new JwtDeserializer<>(reader, 'claims').apply(r) + new JsonObjectDeserializer(reader, 'claims').apply(r) fail() } catch (DeserializationException e) { - String msg = String.format(JwtDeserializer.MALFORMED_COMPLEX_ERROR, 'claims', 'foo') + String msg = String.format(JsonObjectDeserializer.MALFORMED_COMPLEX_ERROR, 'claims', 'claims', 'foo') assertEquals msg, e.message } } @@ -64,10 +64,10 @@ class JwtDeserializerTest { try { // doesn't matter for this test, just has to be non-null: def r = new InputStreamReader(new ByteArrayInputStream(Bytes.EMPTY)) - new JwtDeserializer<>(reader, 'claims').apply(r) + new JsonObjectDeserializer(reader, 'claims').apply(r) fail() } catch (MalformedJwtException e) { - String msg = String.format(JwtDeserializer.MALFORMED_ERROR, 'claims', 'foo') + String msg = String.format(JsonObjectDeserializer.MALFORMED_ERROR, 'claims', 'foo') assertEquals msg, e.message assertSame ex, e.cause } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkParserBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkParserBuilderTest.groovy index 2097c669f..336c85d32 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkParserBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkParserBuilderTest.groovy @@ -18,18 +18,19 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.ConvertingParser import io.jsonwebtoken.impl.lang.Services import io.jsonwebtoken.io.DeserializationException -import io.jsonwebtoken.io.Deserializer +import io.jsonwebtoken.io.Reader import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.MalformedKeyException +import io.jsonwebtoken.security.SecretJwk import org.junit.Test import java.nio.charset.StandardCharsets import java.security.Key import java.security.Provider -import static org.easymock.EasyMock.createMock +import static org.easymock.EasyMock.* import static org.junit.Assert.* class DefaultJwkParserBuilderTest { @@ -63,10 +64,16 @@ class DefaultJwkParserBuilderTest { } @Test - void testDeserializer() { - def deserializer = createMock(Deserializer) - def parser = Jwks.parser().deserializer(deserializer).build() as ConvertingParser - assertSame deserializer, parser.deserializer + void testJsonReader() { + def reader = createMock(Reader) as Reader + def m = RFC7516AppendixA3Test.KEK_VALUES // any test key will do + expect(reader.read(anyObject(java.io.Reader.class))).andReturn(m) + replay reader + def jwk = Jwks.parser().jsonReader(reader).build().parse('foo') + verify reader + assertTrue jwk instanceof SecretJwk + assertEquals m.kty, jwk.kty + assertEquals m.k, jwk.k.get() } @Test @@ -138,18 +145,18 @@ class DefaultJwkParserBuilderTest { @Test void testDeserializationFailure() { - def deserializer = new Deserializer() { + def reader = new Reader() { @Override - Object deserialize(byte[] bytes) throws DeserializationException { - throw new DeserializationException("test") + Object read(java.io.Reader reader) throws IOException { + throw new DeserializationException('test') } } - def parser = new DefaultJwkParserBuilder().deserializer(deserializer).build() + def parser = new DefaultJwkParserBuilder().jsonReader(reader).build() try { parser.parse('foo') fail() } catch (MalformedKeyException expected) { - String msg = "Unable to deserialize JWK: test" + String msg = "Malformed JWK JSON: test" assertEquals msg, expected.getMessage() } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilderTest.groovy index cdc72e582..3e88b680e 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilderTest.groovy @@ -16,8 +16,8 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.io.DeserializationException -import io.jsonwebtoken.io.Deserializer import io.jsonwebtoken.io.Parser +import io.jsonwebtoken.io.Reader import io.jsonwebtoken.security.JwkSet import io.jsonwebtoken.security.Jwks import io.jsonwebtoken.security.MalformedKeySetException @@ -66,18 +66,18 @@ class DefaultJwkSetParserBuilderTest { */ @Test void testDeserializeException() { - def deserializer = new Deserializer() { + def reader = new Reader() { @Override - Object deserialize(byte[] bytes) throws DeserializationException { - throw new DeserializationException("foo") + Object read(java.io.Reader reader) throws IOException { + throw new DeserializationException('foo') } } - parser = new DefaultJwkSetParserBuilder().deserializer(deserializer).build() + parser = new DefaultJwkSetParserBuilder().jsonReader(reader).build() try { parser.parse('foo') } catch (MalformedKeySetException expected) { - String msg = "Unable to deserialize JWK Set: foo" + String msg = "Malformed JWK Set JSON: foo" assertEquals msg, expected.message } }