mirror of https://github.com/jwtk/jjwt.git
* Ensured `NestedCollection`s do not need their `.and()` method called to apply collection changes. Instead, changes are applied immediately as they occur (via `.add`, `.remove`, etc), and `.and()` is now purely for returning to the parent builder if necessary/desired. * Updated associated JavaDoc with code examples to make the `.and()` method's purpose a little clearer. * Updated CHANGELOG.md Closes #916
This commit is contained in:
parent
afcd889832
commit
a0a123e848
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -1,5 +1,31 @@
|
||||||
## Release Notes
|
## Release Notes
|
||||||
|
|
||||||
|
### 0.12.5
|
||||||
|
|
||||||
|
This patch release:
|
||||||
|
|
||||||
|
* Ensures that builders' `NestedCollection` changes are applied to the collection immediately as mutation methods are called, no longer
|
||||||
|
requiring application developers to call `.and()` to 'commit' or apply a change. For example, prior to this release,
|
||||||
|
the following code did not apply changes:
|
||||||
|
```java
|
||||||
|
JwtBuilder builder = Jwts.builder();
|
||||||
|
builder.audience().add("an-audience"); // no .and() call
|
||||||
|
builder.compact(); // would not keep 'an-audience'
|
||||||
|
```
|
||||||
|
Now this code works as expected and all other `NestedCollection` instances like it apply changes immediately (e.g. when calling
|
||||||
|
`.add(value)`).
|
||||||
|
|
||||||
|
However, standard fluent builder chains are still recommended for readability when feasible, e.g.
|
||||||
|
|
||||||
|
```java
|
||||||
|
Jwts.builder()
|
||||||
|
.audience().add("an-audience").and() // allows fluent chaining
|
||||||
|
.subject("Joe")
|
||||||
|
// etc...
|
||||||
|
.compact()
|
||||||
|
```
|
||||||
|
See [Issue 916](https://github.com/jwtk/jjwt/issues/916).
|
||||||
|
|
||||||
### 0.12.4
|
### 0.12.4
|
||||||
|
|
||||||
This patch release includes various changes listed below.
|
This patch release includes various changes listed below.
|
||||||
|
|
|
@ -993,7 +993,7 @@ String jws = Jwts.builder()
|
||||||
|
|
||||||
.issuer("me")
|
.issuer("me")
|
||||||
.subject("Bob")
|
.subject("Bob")
|
||||||
.audience("you")
|
.audience().add("you").and()
|
||||||
.expiration(expiration) //a java.util.Date
|
.expiration(expiration) //a java.util.Date
|
||||||
.notBefore(notBefore) //a java.util.Date
|
.notBefore(notBefore) //a java.util.Date
|
||||||
.issuedAt(new Date()) // for example, now
|
.issuedAt(new Date()) // for example, now
|
||||||
|
|
|
@ -96,6 +96,17 @@ public interface ClaimsMutator<T extends ClaimsMutator<T>> {
|
||||||
* <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code></a> (audience) Claim
|
* <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code></a> (audience) Claim
|
||||||
* set, quietly ignoring any null, empty, whitespace-only, or existing value already in the set.
|
* set, quietly ignoring any null, empty, whitespace-only, or existing value already in the set.
|
||||||
*
|
*
|
||||||
|
* <p>When finished, the {@code audience} collection's {@link AudienceCollection#and() and()} method may be used
|
||||||
|
* to continue configuration. For example:</p>
|
||||||
|
* <blockquote><pre>
|
||||||
|
* Jwts.builder() // or Jwts.claims()
|
||||||
|
*
|
||||||
|
* .audience().add("anAudience")<b>.and() // return parent</b>
|
||||||
|
*
|
||||||
|
* .subject("Joe") // resume configuration...
|
||||||
|
* // etc...
|
||||||
|
* </pre></blockquote>
|
||||||
|
*
|
||||||
* @return the {@link AudienceCollection AudienceCollection} to use for {@code aud} configuration.
|
* @return the {@link AudienceCollection AudienceCollection} to use for {@code aud} configuration.
|
||||||
* @see AudienceCollection AudienceCollection
|
* @see AudienceCollection AudienceCollection
|
||||||
* @see AudienceCollection#single(String) AudienceCollection.single(String)
|
* @see AudienceCollection#single(String) AudienceCollection.single(String)
|
||||||
|
@ -221,6 +232,16 @@ public interface ClaimsMutator<T extends ClaimsMutator<T>> {
|
||||||
* A {@code NestedCollection} for setting {@link #audience()} values that also allows overriding the collection
|
* A {@code NestedCollection} for setting {@link #audience()} values that also allows overriding the collection
|
||||||
* to be a {@link #single(String) single string value} for legacy JWT recipients if necessary.
|
* to be a {@link #single(String) single string value} for legacy JWT recipients if necessary.
|
||||||
*
|
*
|
||||||
|
* <p>Because this interface extends {@link NestedCollection}, the {@link #and()} method may be used to continue
|
||||||
|
* parent configuration. For example:</p>
|
||||||
|
* <blockquote><pre>
|
||||||
|
* Jwts.builder() // or Jwts.claims()
|
||||||
|
*
|
||||||
|
* .audience().add("anAudience")<b>.and() // return parent</b>
|
||||||
|
*
|
||||||
|
* .subject("Joe") // resume parent configuration...
|
||||||
|
* // etc...</pre></blockquote>
|
||||||
|
*
|
||||||
* @param <P> the type of ClaimsMutator to return for method chaining.
|
* @param <P> the type of ClaimsMutator to return for method chaining.
|
||||||
* @see #single(String)
|
* @see #single(String)
|
||||||
* @since 0.12.0
|
* @since 0.12.0
|
||||||
|
|
|
@ -101,12 +101,25 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
|
||||||
* the parser encounters a Protected JWT that {@link ProtectedHeader#getCritical() requires} extensions, and
|
* the parser encounters a Protected JWT that {@link ProtectedHeader#getCritical() requires} extensions, and
|
||||||
* those extensions' header names are not specified via this method, the parser will reject that JWT.
|
* those extensions' header names are not specified via this method, the parser will reject that JWT.
|
||||||
*
|
*
|
||||||
|
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
|
||||||
|
* configuration, for example:</p>
|
||||||
|
* <blockquote><pre>
|
||||||
|
* parserBuilder.critical().add("headerName")<b>.{@link Conjunctor#and() and()} // etc...</b></pre></blockquote>
|
||||||
|
*
|
||||||
* <p><b>Extension Behavior</b></p>
|
* <p><b>Extension Behavior</b></p>
|
||||||
*
|
*
|
||||||
* <p>The {@code critical} collection only identifies header parameter names that are used in extensions supported
|
* <p>The {@code critical} collection only identifies header parameter names that are used in extensions supported
|
||||||
* by the application. <b>Application developers, <em>not JJWT</em>, MUST perform the associated extension behavior
|
* by the application. <b>Application developers, <em>not JJWT</em>, MUST perform the associated extension behavior
|
||||||
* using the parsed JWT</b>.</p>
|
* using the parsed JWT</b>.</p>
|
||||||
*
|
*
|
||||||
|
* <p><b>Continued Parser Configuration</b></p>
|
||||||
|
* <p>When finished, use the collection's
|
||||||
|
* {@link Conjunctor#and() and()} method to continue parser configuration, for example:
|
||||||
|
* <blockquote><pre>
|
||||||
|
* Jwts.parser()
|
||||||
|
* .critical().add("headerName").<b>{@link Conjunctor#and() and()} // return parent</b>
|
||||||
|
* // resume parser configuration...</pre></blockquote>
|
||||||
|
*
|
||||||
* @return the {@link NestedCollection} to use for {@code crit} configuration.
|
* @return the {@link NestedCollection} to use for {@code crit} configuration.
|
||||||
* @see ProtectedHeader#getCritical()
|
* @see ProtectedHeader#getCritical()
|
||||||
* @since 0.12.0
|
* @since 0.12.0
|
||||||
|
@ -557,7 +570,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
|
||||||
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
|
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
|
||||||
* configuration, for example:</p>
|
* configuration, for example:</p>
|
||||||
* <blockquote><pre>
|
* <blockquote><pre>
|
||||||
* parserBuilder.enc().add(anAeadAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
|
* parserBuilder.enc().add(anAeadAlgorithm)<b>.{@link Conjunctor#and() and()} // etc...</b></pre></blockquote>
|
||||||
*
|
*
|
||||||
* <p><b>Standard Algorithms and Overrides</b></p>
|
* <p><b>Standard Algorithms and Overrides</b></p>
|
||||||
*
|
*
|
||||||
|
@ -597,7 +610,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
|
||||||
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
|
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
|
||||||
* configuration, for example:</p>
|
* configuration, for example:</p>
|
||||||
* <blockquote><pre>
|
* <blockquote><pre>
|
||||||
* parserBuilder.key().add(aKeyAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
|
* parserBuilder.key().add(aKeyAlgorithm)<b>.{@link Conjunctor#and() and()} // etc...</b></pre></blockquote>
|
||||||
*
|
*
|
||||||
* <p><b>Standard Algorithms and Overrides</b></p>
|
* <p><b>Standard Algorithms and Overrides</b></p>
|
||||||
*
|
*
|
||||||
|
@ -639,7 +652,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
|
||||||
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
|
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
|
||||||
* configuration, for example:</p>
|
* configuration, for example:</p>
|
||||||
* <blockquote><pre>
|
* <blockquote><pre>
|
||||||
* parserBuilder.sig().add(aSignatureAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
|
* parserBuilder.sig().add(aSignatureAlgorithm)<b>.{@link Conjunctor#and() and()} // etc...</b></pre></blockquote>
|
||||||
*
|
*
|
||||||
* <p><b>Standard Algorithms and Overrides</b></p>
|
* <p><b>Standard Algorithms and Overrides</b></p>
|
||||||
*
|
*
|
||||||
|
@ -680,7 +693,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
|
||||||
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
|
* <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
|
||||||
* configuration, for example:</p>
|
* configuration, for example:</p>
|
||||||
* <blockquote><pre>
|
* <blockquote><pre>
|
||||||
* parserBuilder.zip().add(aCompressionAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
|
* parserBuilder.zip().add(aCompressionAlgorithm)<b>.{@link Conjunctor#and() and()} // etc...</b></pre></blockquote>
|
||||||
*
|
*
|
||||||
* <p><b>Standard Algorithms and Overrides</b></p>
|
* <p><b>Standard Algorithms and Overrides</b></p>
|
||||||
*
|
*
|
||||||
|
|
|
@ -33,9 +33,11 @@ public interface ProtectedHeaderMutator<T extends ProtectedHeaderMutator<T>> ext
|
||||||
/**
|
/**
|
||||||
* Configures names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
|
* Configures names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
|
||||||
* understood and supported by the JWT recipient. When finished, use the collection's
|
* understood and supported by the JWT recipient. When finished, use the collection's
|
||||||
* {@link Conjunctor#and() and()} method to return to the header builder, for example:
|
* {@link Conjunctor#and() and()} method to continue header configuration, for example:
|
||||||
* <blockquote><pre>
|
* <blockquote><pre>
|
||||||
* builder.critical().add("headerName").{@link Conjunctor#and() and()} // etc...</pre></blockquote>
|
* headerBuilder
|
||||||
|
* .critical().add("headerName").<b>{@link Conjunctor#and() and()} // return parent</b>
|
||||||
|
* // resume header configuration...</pre></blockquote>
|
||||||
*
|
*
|
||||||
* @return the {@link NestedCollection} to use for {@code crit} configuration.
|
* @return the {@link NestedCollection} to use for {@code crit} configuration.
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11">JWS <code>crit</code> (Critical) Header Parameter</a>
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11">JWS <code>crit</code> (Critical) Header Parameter</a>
|
||||||
|
|
|
@ -17,7 +17,12 @@ package io.jsonwebtoken.lang;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link CollectionMutator} that can return access to its parent via the {@link Conjunctor#and() and()} method for
|
* A {@link CollectionMutator} that can return access to its parent via the {@link Conjunctor#and() and()} method for
|
||||||
* continued configuration.
|
* continued configuration. For example:
|
||||||
|
* <blockquote><pre>
|
||||||
|
* builder
|
||||||
|
* .aNestedCollection()// etc...
|
||||||
|
* <b>.and() // return parent</b>
|
||||||
|
* // resume parent configuration...</pre></blockquote>
|
||||||
*
|
*
|
||||||
* @param <E> the type of elements in the collection
|
* @param <E> the type of elements in the collection
|
||||||
* @param <P> the parent to return
|
* @param <P> the parent to return
|
||||||
|
|
|
@ -109,9 +109,9 @@ public interface JwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilde
|
||||||
* the key is intended to be used. When finished, use the collection's {@link Conjunctor#and() and()} method to
|
* the key is intended to be used. When finished, use the collection's {@link Conjunctor#and() and()} method to
|
||||||
* return to the JWK builder, for example:
|
* return to the JWK builder, for example:
|
||||||
* <blockquote><pre>
|
* <blockquote><pre>
|
||||||
* jwkBuilder.operations().add(aKeyOperation).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
|
* jwkBuilder.operations().add(aKeyOperation)<b>.{@link Conjunctor#and() and()} // etc...</b></pre></blockquote>
|
||||||
*
|
*
|
||||||
* <p>The {@code and()} method will throw an {@link IllegalArgumentException} if any of the specified
|
* <p>The {@code add()} method(s) will throw an {@link IllegalArgumentException} if any of the specified
|
||||||
* {@code KeyOperation}s are not permitted by the JWK's
|
* {@code KeyOperation}s are not permitted by the JWK's
|
||||||
* {@link #operationPolicy(KeyOperationPolicy) operationPolicy}. See that documentation for more
|
* {@link #operationPolicy(KeyOperationPolicy) operationPolicy}. See that documentation for more
|
||||||
* information on security vulnerabilities when using the same key with multiple algorithms.</p>
|
* information on security vulnerabilities when using the same key with multiple algorithms.</p>
|
||||||
|
|
|
@ -107,9 +107,8 @@ public class DefaultJweHeaderMutator<T extends JweHeaderMutator<T>>
|
||||||
public NestedCollection<String, T> critical() {
|
public NestedCollection<String, T> critical() {
|
||||||
return new DefaultNestedCollection<String, T>(self(), this.DELEGATE.get(DefaultProtectedHeader.CRIT)) {
|
return new DefaultNestedCollection<String, T>(self(), this.DELEGATE.get(DefaultProtectedHeader.CRIT)) {
|
||||||
@Override
|
@Override
|
||||||
public T and() {
|
protected void changed() {
|
||||||
put(DefaultProtectedHeader.CRIT, Collections.asSet(getCollection()));
|
put(DefaultProtectedHeader.CRIT, Collections.asSet(getCollection()));
|
||||||
return super.and();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,9 +220,8 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
|
||||||
public NestedCollection<String, JwtParserBuilder> critical() {
|
public NestedCollection<String, JwtParserBuilder> critical() {
|
||||||
return new DefaultNestedCollection<String, JwtParserBuilder>(this, this.critical) {
|
return new DefaultNestedCollection<String, JwtParserBuilder>(this, this.critical) {
|
||||||
@Override
|
@Override
|
||||||
public JwtParserBuilder and() {
|
protected void changed() {
|
||||||
critical = Collections.asSet(getCollection());
|
critical = Collections.asSet(getCollection());
|
||||||
return super.and();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -304,9 +303,8 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
|
||||||
public NestedCollection<CompressionAlgorithm, JwtParserBuilder> zip() {
|
public NestedCollection<CompressionAlgorithm, JwtParserBuilder> zip() {
|
||||||
return new DefaultNestedCollection<CompressionAlgorithm, JwtParserBuilder>(this, this.zipAlgs.values()) {
|
return new DefaultNestedCollection<CompressionAlgorithm, JwtParserBuilder>(this, this.zipAlgs.values()) {
|
||||||
@Override
|
@Override
|
||||||
public JwtParserBuilder and() {
|
protected void changed() {
|
||||||
zipAlgs = new IdRegistry<>(StandardCompressionAlgorithms.NAME, getCollection());
|
zipAlgs = new IdRegistry<>(StandardCompressionAlgorithms.NAME, getCollection());
|
||||||
return super.and();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -315,9 +313,8 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
|
||||||
public NestedCollection<AeadAlgorithm, JwtParserBuilder> enc() {
|
public NestedCollection<AeadAlgorithm, JwtParserBuilder> enc() {
|
||||||
return new DefaultNestedCollection<AeadAlgorithm, JwtParserBuilder>(this, this.encAlgs.values()) {
|
return new DefaultNestedCollection<AeadAlgorithm, JwtParserBuilder>(this, this.encAlgs.values()) {
|
||||||
@Override
|
@Override
|
||||||
public JwtParserBuilder and() {
|
public void changed() {
|
||||||
encAlgs = new IdRegistry<>(StandardEncryptionAlgorithms.NAME, getCollection());
|
encAlgs = new IdRegistry<>(StandardEncryptionAlgorithms.NAME, getCollection());
|
||||||
return super.and();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -326,9 +323,8 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
|
||||||
public NestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder> sig() {
|
public NestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder> sig() {
|
||||||
return new DefaultNestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder>(this, this.sigAlgs.values()) {
|
return new DefaultNestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder>(this, this.sigAlgs.values()) {
|
||||||
@Override
|
@Override
|
||||||
public JwtParserBuilder and() {
|
public void changed() {
|
||||||
sigAlgs = new IdRegistry<>(StandardSecureDigestAlgorithms.NAME, getCollection());
|
sigAlgs = new IdRegistry<>(StandardSecureDigestAlgorithms.NAME, getCollection());
|
||||||
return super.and();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -337,9 +333,8 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
|
||||||
public NestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder> key() {
|
public NestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder> key() {
|
||||||
return new DefaultNestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder>(this, this.keyAlgs.values()) {
|
return new DefaultNestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder>(this, this.keyAlgs.values()) {
|
||||||
@Override
|
@Override
|
||||||
public JwtParserBuilder and() {
|
public void changed() {
|
||||||
keyAlgs = new IdRegistry<>(StandardKeyAlgorithms.NAME, getCollection());
|
keyAlgs = new IdRegistry<>(StandardKeyAlgorithms.NAME, getCollection());
|
||||||
return super.and();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,12 +130,12 @@ public class DelegatingClaimsMutator<T extends MapMutator<String, Object, T> & C
|
||||||
@Override
|
@Override
|
||||||
public T single(String audience) {
|
public T single(String audience) {
|
||||||
return audienceSingle(audience);
|
return audienceSingle(audience);
|
||||||
|
// DO NOT call changed() here - we don't want to replace the value with a collection
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T and() {
|
protected void changed() {
|
||||||
put(DefaultClaims.AUDIENCE, Collections.asSet(getCollection()));
|
put(DefaultClaims.AUDIENCE, Collections.asSet(getCollection()));
|
||||||
return super.and();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,38 +37,52 @@ public class DefaultCollectionMutator<E, M extends CollectionMutator<E, M>> impl
|
||||||
return (M) this;
|
return (M) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private boolean doAdd(E e) {
|
||||||
public M add(E e) {
|
if (Objects.isEmpty(e)) return false;
|
||||||
if (Objects.isEmpty(e)) return self();
|
|
||||||
if (e instanceof Identifiable && !Strings.hasText(((Identifiable) e).getId())) {
|
if (e instanceof Identifiable && !Strings.hasText(((Identifiable) e).getId())) {
|
||||||
String msg = e.getClass() + " getId() value cannot be null or empty.";
|
String msg = e.getClass() + " getId() value cannot be null or empty.";
|
||||||
throw new IllegalArgumentException(msg);
|
throw new IllegalArgumentException(msg);
|
||||||
}
|
}
|
||||||
this.collection.remove(e);
|
return this.collection.add(e);
|
||||||
this.collection.add(e);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public M add(E e) {
|
||||||
|
if (doAdd(e)) changed();
|
||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public M remove(E e) {
|
public M remove(E e) {
|
||||||
this.collection.remove(e);
|
if (this.collection.remove(e)) changed();
|
||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public M add(Collection<? extends E> c) {
|
public M add(Collection<? extends E> c) {
|
||||||
|
boolean changed = false;
|
||||||
for (E element : Collections.nullSafe(c)) {
|
for (E element : Collections.nullSafe(c)) {
|
||||||
add(element);
|
changed = doAdd(element) || changed;
|
||||||
}
|
}
|
||||||
|
if (changed) changed();
|
||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public M clear() {
|
public M clear() {
|
||||||
|
boolean changed = !Collections.isEmpty(this.collection);
|
||||||
this.collection.clear();
|
this.collection.clear();
|
||||||
|
if (changed) changed();
|
||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for subclasses that wish to be notified if the internal collection has changed via builder mutation
|
||||||
|
* methods.
|
||||||
|
*/
|
||||||
|
protected void changed() {
|
||||||
|
}
|
||||||
|
|
||||||
protected Collection<E> getCollection() {
|
protected Collection<E> getCollection() {
|
||||||
return Collections.immutable(this.collection);
|
return Collections.immutable(this.collection);
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,11 +111,10 @@ abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends Jwk
|
||||||
public NestedCollection<KeyOperation, T> operations() {
|
public NestedCollection<KeyOperation, T> operations() {
|
||||||
return new DefaultNestedCollection<KeyOperation, T>(self(), this.DELEGATE.getOperations()) {
|
return new DefaultNestedCollection<KeyOperation, T>(self(), this.DELEGATE.getOperations()) {
|
||||||
@Override
|
@Override
|
||||||
public T and() {
|
protected void changed() {
|
||||||
Collection<? extends KeyOperation> c = getCollection();
|
Collection<? extends KeyOperation> c = getCollection();
|
||||||
opsPolicy.validate(c);
|
opsPolicy.validate(c);
|
||||||
DELEGATE.setOperations(c);
|
DELEGATE.setOperations(c);
|
||||||
return super.and();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -791,4 +791,47 @@ class DefaultJwtBuilderTest {
|
||||||
assertEquals three, claims.aud
|
assertEquals three, claims.aud
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if a .audience() builder is used, and its .and() method is not called, the change to the
|
||||||
|
* audience is still applied when building the JWT.
|
||||||
|
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
|
||||||
|
* @since JJWT_RELEASE_VERSION
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAudienceWithoutConjunction() {
|
||||||
|
def aud = 'my-web'
|
||||||
|
def builder = Jwts.builder()
|
||||||
|
builder.audience().add(aud) // no .and() call
|
||||||
|
def jwt = builder.compact()
|
||||||
|
|
||||||
|
// assert that the resulting claims has the audience array set as expected:
|
||||||
|
def parsed = Jwts.parser().unsecured().build().parseUnsecuredClaims(jwt)
|
||||||
|
assertEquals aud, parsed.payload.getAudience()[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if a .header().critical() builder is used, and its .and() method is not called, the change to the
|
||||||
|
* crit collection is still applied when building the header.
|
||||||
|
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
|
||||||
|
* @since JJWT_RELEASE_VERSION
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testCritWithoutConjunction() {
|
||||||
|
def crit = 'test'
|
||||||
|
def builder = Jwts.builder().issuer('me')
|
||||||
|
def headerBuilder = builder.header()
|
||||||
|
headerBuilder.critical().add(crit) // no .and() method
|
||||||
|
headerBuilder.add(crit, 'foo') // no .and() method
|
||||||
|
builder.signWith(TestKeys.HS256)
|
||||||
|
def jwt = builder.compact()
|
||||||
|
|
||||||
|
def headerBytes = Decoders.BASE64URL.decode(jwt.split('\\.')[0])
|
||||||
|
def headerMap = Services.get(Deserializer).deserialize(Streams.reader(headerBytes)) as Map<String, ?>
|
||||||
|
|
||||||
|
def expected = [crit] as Set<String>
|
||||||
|
def val = headerMap.get('crit') as Set<String>
|
||||||
|
assertNotNull val
|
||||||
|
assertEquals expected, val
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -507,6 +507,22 @@ class DefaultJwtHeaderBuilderTest {
|
||||||
assertEquals expected, header.getCritical()
|
assertEquals expected, header.getCritical()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if a .critical() builder is used, and its .and() method is not called, the change to the
|
||||||
|
* crit collection is still applied when building the header.
|
||||||
|
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
|
||||||
|
* @since JJWT_RELEASE_VERSION
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testCritWithoutConjunction() {
|
||||||
|
def crit = 'test'
|
||||||
|
def builder = jws()
|
||||||
|
builder.add(crit, 'foo').critical().add(crit) // no .and() method
|
||||||
|
def header = builder.build() as ProtectedHeader
|
||||||
|
def expected = [crit] as Set<String>
|
||||||
|
assertEquals expected, header.getCritical()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCritSingleNullIgnored() {
|
void testCritSingleNullIgnored() {
|
||||||
def crit = 'test'
|
def crit = 'test'
|
||||||
|
|
|
@ -48,6 +48,22 @@ class DefaultJwtParserBuilderTest {
|
||||||
assertTrue builder.@critical.isEmpty()
|
assertTrue builder.@critical.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if a .critical() builder is used, and its .and() method is not called, the change to the
|
||||||
|
* crit collection is still applied when building the parser.
|
||||||
|
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
|
||||||
|
* @since JJWT_RELEASE_VERSION
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testCriticalWithoutConjunction() {
|
||||||
|
builder.critical().add('foo') // no .and() call
|
||||||
|
assertFalse builder.@critical.isEmpty()
|
||||||
|
assertTrue builder.@critical.contains('foo')
|
||||||
|
def parser = builder.build()
|
||||||
|
assertFalse parser.@critical.isEmpty()
|
||||||
|
assertTrue parser.@critical.contains('foo')
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSetProvider() {
|
void testSetProvider() {
|
||||||
Provider provider = createMock(Provider)
|
Provider provider = createMock(Provider)
|
||||||
|
@ -173,6 +189,21 @@ class DefaultJwtParserBuilderTest {
|
||||||
assertSame codec, parser.zipAlgs.locate(header)
|
assertSame codec, parser.zipAlgs.locate(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if a .zip() builder is used, and its .and() method is not called, the change to the
|
||||||
|
* compression algorithm collection is still applied when building the parser.
|
||||||
|
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
|
||||||
|
* @since JJWT_RELEASE_VERSION
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAddCompressionAlgorithmWithoutConjunction() {
|
||||||
|
def codec = new TestCompressionCodec(id: 'test')
|
||||||
|
builder.zip().add(codec) // no .and() call
|
||||||
|
def parser = builder.build()
|
||||||
|
def header = Jwts.header().add('zip', codec.getId()).build()
|
||||||
|
assertSame codec, parser.zipAlgs.locate(header)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAddCompressionAlgorithmsOverrideDefaults() {
|
void testAddCompressionAlgorithmsOverrideDefaults() {
|
||||||
def header = Jwts.header().add('zip', 'DEF').build()
|
def header = Jwts.header().add('zip', 'DEF').build()
|
||||||
|
@ -211,6 +242,21 @@ class DefaultJwtParserBuilderTest {
|
||||||
assertSame custom, parser.encAlgs.apply(header) // custom one, not standard impl
|
assertSame custom, parser.encAlgs.apply(header) // custom one, not standard impl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if an .enc() builder is used, and its .and() method is not called, the change to the
|
||||||
|
* encryption algorithm collection is still applied when building the parser.
|
||||||
|
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
|
||||||
|
* @since JJWT_RELEASE_VERSION
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAddEncryptionAlgorithmWithoutConjunction() {
|
||||||
|
def alg = new TestAeadAlgorithm(id: 'test')
|
||||||
|
builder.enc().add(alg) // no .and() call
|
||||||
|
def parser = builder.build() as DefaultJwtParser
|
||||||
|
def header = Jwts.header().add('alg', 'foo').add('enc', alg.getId()).build() as JweHeader
|
||||||
|
assertSame alg, parser.encAlgs.apply(header)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCaseSensitiveEncryptionAlgorithm() {
|
void testCaseSensitiveEncryptionAlgorithm() {
|
||||||
def alg = Jwts.ENC.A256GCM
|
def alg = Jwts.ENC.A256GCM
|
||||||
|
@ -239,6 +285,23 @@ class DefaultJwtParserBuilderTest {
|
||||||
assertSame custom, parser.keyAlgs.apply(header) // custom one, not standard impl
|
assertSame custom, parser.keyAlgs.apply(header) // custom one, not standard impl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if an .key() builder is used, and its .and() method is not called, the change to the
|
||||||
|
* key algorithm collection is still applied when building the parser.
|
||||||
|
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
|
||||||
|
* @since JJWT_RELEASE_VERSION
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAddKeyAlgorithmWithoutConjunction() {
|
||||||
|
def alg = new TestKeyAlgorithm(id: 'test')
|
||||||
|
builder.key().add(alg) // no .and() call
|
||||||
|
def parser = builder.build() as DefaultJwtParser
|
||||||
|
def header = Jwts.header()
|
||||||
|
.add('enc', 'foo')
|
||||||
|
.add('alg', alg.getId()).build() as JweHeader
|
||||||
|
assertSame alg, parser.keyAlgs.apply(header)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCaseSensitiveKeyAlgorithm() {
|
void testCaseSensitiveKeyAlgorithm() {
|
||||||
def alg = Jwts.KEY.A256GCMKW
|
def alg = Jwts.KEY.A256GCMKW
|
||||||
|
@ -268,6 +331,21 @@ class DefaultJwtParserBuilderTest {
|
||||||
assertSame custom, parser.sigAlgs.apply(header) // custom one, not standard impl
|
assertSame custom, parser.sigAlgs.apply(header) // custom one, not standard impl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if an .sig() builder is used, and its .and() method is not called, the change to the
|
||||||
|
* signature algorithm collection is still applied when building the parser.
|
||||||
|
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
|
||||||
|
* @since JJWT_RELEASE_VERSION
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAddSignatureAlgorithmWithoutConjunction() {
|
||||||
|
def alg = new TestMacAlgorithm(id: 'test')
|
||||||
|
builder.sig().add(alg) // no .and() call
|
||||||
|
def parser = builder.build() as DefaultJwtParser
|
||||||
|
def header = Jwts.header().add('alg', alg.getId()).build() as JwsHeader
|
||||||
|
assertSame alg, parser.sigAlgs.apply(header)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCaseSensitiveSignatureAlgorithm() {
|
void testCaseSensitiveSignatureAlgorithm() {
|
||||||
def alg = Jwts.SIG.HS256
|
def alg = Jwts.SIG.HS256
|
||||||
|
|
|
@ -27,11 +27,18 @@ import static org.junit.Assert.*
|
||||||
*/
|
*/
|
||||||
class DefaultCollectionMutatorTest {
|
class DefaultCollectionMutatorTest {
|
||||||
|
|
||||||
|
private int changeCount
|
||||||
private DefaultCollectionMutator m
|
private DefaultCollectionMutator m
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void setUp() {
|
void setUp() {
|
||||||
m = new DefaultCollectionMutator(null)
|
changeCount = 0
|
||||||
|
m = new DefaultCollectionMutator(null) {
|
||||||
|
@Override
|
||||||
|
protected void changed() {
|
||||||
|
changeCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -51,9 +58,17 @@ class DefaultCollectionMutatorTest {
|
||||||
void add() {
|
void add() {
|
||||||
def val = 'hello'
|
def val = 'hello'
|
||||||
m.add(val)
|
m.add(val)
|
||||||
|
assertEquals 1, changeCount
|
||||||
assertEquals Collections.singleton(val), m.getCollection()
|
assertEquals Collections.singleton(val), m.getCollection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addDuplicateDoesNotTriggerChange() {
|
||||||
|
m.add('hello')
|
||||||
|
m.add('hello') //already in the set, no change should be reflected
|
||||||
|
assertEquals 1, changeCount
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void addCollection() {
|
void addCollection() {
|
||||||
def vals = ['hello', 'world']
|
def vals = ['hello', 'world']
|
||||||
|
@ -67,6 +82,17 @@ class DefaultCollectionMutatorTest {
|
||||||
assertFalse i.hasNext()
|
assertFalse i.hasNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if a collection is added, each internal addition to the collection doesn't call changed(); instead
|
||||||
|
* changed() is only called once after they've all been added to the collection
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void addCollectionTriggersSingleChange() {
|
||||||
|
def c = ['hello', 'world']
|
||||||
|
m.add(c)
|
||||||
|
assertEquals 1, changeCount // only one change triggered, not c.size()
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException)
|
@Test(expected = IllegalArgumentException)
|
||||||
void addIdentifiableWithNullId() {
|
void addIdentifiableWithNullId() {
|
||||||
def e = new Identifiable() {
|
def e = new Identifiable() {
|
||||||
|
@ -96,6 +122,12 @@ class DefaultCollectionMutatorTest {
|
||||||
assertEquals Collections.singleton('world'), m.getCollection()
|
assertEquals Collections.singleton('world'), m.getCollection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removeMissingDoesNotTriggerChange() {
|
||||||
|
m.remove('foo') // not in the collection, no change should be registered
|
||||||
|
assertEquals 0, changeCount
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void clear() {
|
void clear() {
|
||||||
m.add('one').add('two').add(['three', 'four'])
|
m.add('one').add('two').add(['three', 'four'])
|
||||||
|
|
|
@ -239,7 +239,21 @@ class AbstractJwkBuilderTest {
|
||||||
.related(Jwks.OP.VERIFY.id).build()
|
.related(Jwks.OP.VERIFY.id).build()
|
||||||
def builder = builder().operationPolicy(Jwks.OP.policy().add(op).build())
|
def builder = builder().operationPolicy(Jwks.OP.policy().add(op).build())
|
||||||
def jwk = builder.operations().add(Collections.setOf(op, Jwks.OP.VERIFY)).and().build() as Jwk
|
def jwk = builder.operations().add(Collections.setOf(op, Jwks.OP.VERIFY)).and().build() as Jwk
|
||||||
assertSame op, jwk.getOperations().find({it.id == 'sign'})
|
assertSame op, jwk.getOperations().find({ it.id == 'sign' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that if a .operations() builder is used, and its .and() method is not called, the change to the
|
||||||
|
* operations collection is still applied when building the JWK.
|
||||||
|
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
|
||||||
|
* @since JJWT_RELEASE_VERSION
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testOperationsWithoutConjunction() {
|
||||||
|
def builder = builder()
|
||||||
|
builder.operations().clear().add(Jwks.OP.DERIVE_BITS) // no .and() call
|
||||||
|
def jwk = builder.build()
|
||||||
|
assertEquals(Jwks.OP.DERIVE_BITS, jwk.getOperations()[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue