Skip to content

Commit

Permalink
- Renamed JwtDeserializer to JsonObjectDeserializer that defaults to …
Browse files Browse the repository at this point in the history
…throwing MalformedJwtException. Added two subclasses, JwkDeserializer and JwkSetDeserializer that throws JWK and JWK Set-specific exceptions.

- Changed ParserBuilder#deserializer method name to ParserBuilder#jsonReader
  • Loading branch information
lhazlewood committed Sep 20, 2023
1 parent edb397c commit e45c27f
Show file tree
Hide file tree
Showing 20 changed files with 219 additions and 238 deletions.
8 changes: 4 additions & 4 deletions api/src/main/java/io/jsonwebtoken/io/ParserBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ public interface ParserBuilder<T, B extends ParserBuilder<T, B>> 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).
*
* <p>If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the
* <p>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<Map<String, ?>> deserializer);
B jsonReader(Reader<Map<String, ?>> reader);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
* Example usage:
* <blockquote><pre>
* 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);</pre></blockquote>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class JacksonSerializerTest {

@Before
void setUp() {
serializer = new JacksonSerializer()
serializer = new JacksonSerializer()
}

@Test
Expand All @@ -43,7 +43,7 @@ class JacksonSerializerTest {

@Test
void testDefaultConstructor() {
assertSame JacksonWriter.DEFAULT_OBJECT_MAPPER, serializer.objectMapper
assertSame JacksonWriter.DEFAULT_OBJECT_MAPPER, serializer.objectMapper
}

@Test
Expand All @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -153,7 +153,7 @@ public JwtBuilder jsonWriter(Writer<Map<String, ?>> writer) {
private byte[] serialize(Map<String, ?> map) {
Nameable nameable = Assert.isInstanceOf(Nameable.class, map, "JWT internal maps implement Nameable.");
Writer<Map<String, ?>> jsonWriter = Assert.stateNotNull(this.jsonWriter, "JSON Writer cannot be null.");
MapSerializer serializer = new MapSerializer(jsonWriter, nameable.getName());
WritingSerializer<Map<String, ?>> serializer = new WritingSerializer<>(jsonWriter, nameable.getName());
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
java.io.Writer writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8);
try {
Expand Down
6 changes: 3 additions & 3 deletions impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Map<String, ?>> jwtd = new JwtDeserializer<>(jsonReader, name);
return jwtd.apply(r);
JsonObjectDeserializer deserializer = new JsonObjectDeserializer(jsonReader, name);
return deserializer.apply(r);
} finally {
Objects.nullSafeClose(r);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,7 +27,7 @@ public abstract class AbstractParserBuilder<T, B extends ParserBuilder<T, B>> im

protected Provider provider;

protected Deserializer<Map<String, ?>> deserializer;
protected Reader<Map<String, ?>> jsonReader;

@SuppressWarnings("unchecked")
protected final B self() {
Expand All @@ -41,17 +41,16 @@ public B provider(Provider provider) {
}

@Override
public B deserializer(Deserializer<Map<String, ?>> deserializer) {
this.deserializer = deserializer;
public B jsonReader(Reader<Map<String, ?>> reader) {
this.jsonReader = reader;
return self();
}

@Override
public final Parser<T> 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();
}
Expand Down
58 changes: 12 additions & 46 deletions impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> implements Parser<T> {

private final Deserializer<?> deserializer;
private final Function<java.io.Reader, Map<String, ?>> deserializer;
private final Converter<T, Object> converter;
private final Function<Throwable, RuntimeException> exceptionHandler;

public ConvertingParser(Deserializer<Map<String, ?>> deserializer, Converter<T, Object> converter,
Function<Throwable, RuntimeException> 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<String, ?> 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<String, ?> deserialize(byte[] data) {
Object val = this.deserializer.deserialize(data);
if (val == null) {
String msg = "Deserialized data resulted in a null value; cannot create Map<String,?>";
throw new DeserializationException(msg);
}
if (!(val instanceof Map)) {
String msg = "Deserialized data is not a JSON Object; cannot create Map<String,?>";
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<String,?>
// 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<String, ?>) val;
public ConvertingParser(Function<java.io.Reader, Map<String, ?>> deserializer, Converter<T, Object> 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<String, ?> 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<String, ?> m = this.deserializer.apply(reader);
return this.converter.applyFrom(m);
}
}
Original file line number Diff line number Diff line change
@@ -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<java.io.Reader, Map<String, ?>> {

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<String, ?> 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<String,?>";
throw new DeserializationException(msg);
}
if (!(value instanceof Map)) {
String msg = "Deserialized data is not a JSON Object; cannot create Map<String,?>";
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<String,?>
// since input would rarely, if ever, have non-string keys.
//noinspection unchecked
return (Map<String, ?>) 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);
}
}
57 changes: 0 additions & 57 deletions impl/src/main/java/io/jsonwebtoken/impl/io/JwtDeserializer.java

This file was deleted.

Loading

0 comments on commit e45c27f

Please sign in to comment.