Ensured various builder collection argument methods were semantically the same (append operations, not one append, one replace) (#830)

This commit is contained in:
lhazlewood 2023-09-15 09:42:44 -07:00 committed by GitHub
parent 8cb59d760b
commit bf5d81cbb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 235 additions and 59 deletions

View File

@ -157,7 +157,7 @@ enforcement.
* Convenient and readable [fluent](http://en.wikipedia.org/wiki/Fluent_interface) interfaces, great for IDE * Convenient and readable [fluent](http://en.wikipedia.org/wiki/Fluent_interface) interfaces, great for IDE
auto-completion to write code quickly auto-completion to write code quickly
* Fully RFC specification compliant on all implemented functionality, tested against RFC-specified test vectors * Fully RFC specification compliant on all implemented functionality, tested against RFC-specified test vectors
* Stable implementation with over 1,300+ tests and enforced 100% test code coverage. Every single method, statement * Stable implementation with over 1,400+ tests and enforced 100% test code coverage. Every single method, statement
and conditional branch variant in the entire codebase is tested and required to pass on every build. and conditional branch variant in the entire codebase is tested and required to pass on every build.
* Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with all standard JWS algorithms: * Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with all standard JWS algorithms:

View File

@ -97,7 +97,7 @@ public interface ClaimsMutator<T extends ClaimsMutator<T>> {
* data type for recipients. * data type for recipients.
* *
* @param aud the value to use as the {@code aud} Claim single-String value (and not an array of Strings), or * @param aud the value to use as the {@code aud} Claim single-String value (and not an array of Strings), or
* {@code null} to remove the property from the JSON map. * {@code null}, empty or whitespace to remove the property from the JSON map.
* @return the instance for method chaining * @return the instance for method chaining
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
* @deprecated This is technically not deprecated because the JWT RFC mandates support for single string values, * @deprecated This is technically not deprecated because the JWT RFC mandates support for single string values,
@ -108,22 +108,26 @@ public interface ClaimsMutator<T extends ClaimsMutator<T>> {
T audienceSingle(String aud); T audienceSingle(String aud);
/** /**
* Adds the specified {@code aud} value to the {@link #audience(Collection) audience} Claim set (JSON Array). This * Adds (appends) the specified {@code aud} value to the {@link #audience(Collection) audience} Claim set
* method may be called multiple times. * (JSON Array) unless it is {@code null}, empty, whitespace-only or already exists in the set.
*
* <p>This method may be called multiple times.</p>
* *
* @param aud a JWT {@code aud} value to add to the {@link #audience(Collection) audience} Claim set. * @param aud a JWT {@code aud} value to add to the {@link #audience(Collection) audience} Claim set.
* @return the {@code Claims} instance for method chaining. * @return the {@code Claims} instance for method chaining.
* @throws IllegalArgumentException if the {@code aud} argument is null or empty. * @throws IllegalArgumentException if the {@code aud} argument is null or empty.
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
T audience(String aud) throws IllegalArgumentException; T audience(String aud);
/** /**
* Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code></a> (audience) * Adds (appends) the specified values to the JWT
* Claim set, replacing any previous value(s). * <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.
* *
* @param aud the values to set as the {@code aud} Claim set (JSON Array), or {@code null}/empty to remove the * <p>This method may be called multiple times.</p>
* {@code aud} claim from the JSON map entirely. *
* @param aud the values to add to the {@code aud} Claim set (JSON Array)
* @return the instance for method chaining * @return the instance for method chaining
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */

View File

@ -102,8 +102,8 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
* *
* <p><b>Extension Behavior</b></p> * <p><b>Extension Behavior</b></p>
* *
* <p>The {@code crit} set only identifies header parameter names that are used in extensions supported by the * <p>The {@code crit} set argument only identifies header parameter names that are used in extensions supported
* 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>
* *
* @param crit the header parameter names used in JWT extensions supported by the application. * @param crit the header parameter names used in JWT extensions supported by the application.

View File

@ -19,7 +19,7 @@ import io.jsonwebtoken.security.PublicJwk;
import io.jsonwebtoken.security.X509Mutator; import io.jsonwebtoken.security.X509Mutator;
import java.net.URI; import java.net.URI;
import java.util.Set; import java.util.Collection;
/** /**
* Mutation (modifications) to a {@link ProtectedHeader Header} instance. * Mutation (modifications) to a {@link ProtectedHeader Header} instance.
@ -30,17 +30,29 @@ import java.util.Set;
public interface ProtectedHeaderMutator<T extends ProtectedHeaderMutator<T>> extends HeaderMutator<T>, X509Mutator<T> { public interface ProtectedHeaderMutator<T extends ProtectedHeaderMutator<T>> extends HeaderMutator<T>, X509Mutator<T> {
/** /**
* Sets the header parameter names that use extensions to the JWT or JWA specification that <em>MUST</em> * Adds the name of a header parameter used by a JWT or JWA specification extension that <em>MUST</em> be understood
* be understood and supported by the JWT recipient. A {@code null} value will remove the * and supported by the JWT recipient. A {@code null}, empty, whitespace-only or already existing value is ignored.
* property from the JSON map.
* *
* @param crit the header parameter names that use extensions to the JWT or JWA specification that <em>MUST</em> * @param crit the name of a header parameter used by a JWT or JWA specification extension that <em>MUST</em> be
* be understood and supported by the JWT recipient. * understood and supported by the JWT recipient.
* @return the header for method chaining. * @return the header for method chaining.
* @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>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.13">JWS <code>crit</code> (Critical) Header Parameter</a> * @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.13">JWS <code>crit</code> (Critical) Header Parameter</a>
*/ */
T critical(Set<String> crit); T critical(String crit);
/**
* Adds names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
* understood and supported by the JWT recipient. {@code null}, empty, whitespace-only or already existing
* values are ignored.
*
* @param crit names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
* understood and supported by the JWT recipient
* @return the header for method chaining.
* @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/rfc7516.html#section-4.1.13">JWS <code>crit</code> (Critical) Header Parameter</a>
*/
T critical(Collection<String> crit);
/** /**
* Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the * Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the

View File

@ -180,7 +180,6 @@ public final class Collections {
/** /**
* Returns a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise. * Returns a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise.
* Returns the specified set if not null or {@link #emptySet()} otherwise.
* *
* @param s the set to check for null * @param s the set to check for null
* @param <T> type of elements in the set * @param <T> type of elements in the set
@ -191,6 +190,18 @@ public final class Collections {
return s == null ? Collections.<T>emptySet() : s; return s == null ? Collections.<T>emptySet() : s;
} }
/**
* Returns a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise.
*
* @param c the collection to check for null
* @param <T> type of elements in the collection
* @return a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise.
* @since JJWT_RELEASE_VERSION
*/
public static <T> Collection<T> nullSafe(Collection<T> c) {
return c == null ? Collections.<T>emptyList() : c;
}
/** /**
* Return <code>true</code> if the supplied Collection is <code>null</code> * Return <code>true</code> if the supplied Collection is <code>null</code>
* or empty. Otherwise, return <code>false</code>. * or empty. Otherwise, return <code>false</code>.

View File

@ -138,14 +138,14 @@ public interface JwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilde
* *
* @param operation the value to add to the JWK {@code key_ops} value set * @param operation the value to add to the JWK {@code key_ops} value set
* @return the builder for method chaining. * @return the builder for method chaining.
* @throws IllegalArgumentException if {@code op} is {@code null} or if the operation is not permitted * @throws IllegalArgumentException if the {@code op} is not permitted by the operations
* by the operations {@link #operationPolicy(KeyOperationPolicy) policy}. * {@link #operationPolicy(KeyOperationPolicy) policy}.
* @see Jwks.OP * @see Jwks.OP
*/ */
T operation(KeyOperation operation) throws IllegalArgumentException; T operation(KeyOperation operation) throws IllegalArgumentException;
/** /**
* Sets the JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops} * Adds {@code ops} to the JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops}
* (Key Operations) Parameter</a> values. * (Key Operations) Parameter</a> values.
* *
* <p>The {@code key_ops} (key operations) parameter identifies the operation(s) for which the key is * <p>The {@code key_ops} (key operations) parameter identifies the operation(s) for which the key is
@ -176,10 +176,10 @@ public interface JwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilde
* <p>For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP} * <p>For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP}
* constants.</p> * constants.</p>
* *
* @param ops the JWK {@code key_ops} value set, or {@code null} if not present. * @param ops the {@code KeyOperation} values to add to the JWK {@code key_ops} value set
* @return the builder for method chaining. * @return the builder for method chaining.
* @throws IllegalArgumentException {@code ops} is {@code null} or empty, or if any of the operations are not * @throws IllegalArgumentException if any of the operations are not permitted by the operations
* permitted by the operations {@link #operationPolicy(KeyOperationPolicy) policy}. * {@link #operationPolicy(KeyOperationPolicy) policy}.
* @see Jwks.OP * @see Jwks.OP
*/ */
T operations(Collection<KeyOperation> ops) throws IllegalArgumentException; T operations(Collection<KeyOperation> ops) throws IllegalArgumentException;

View File

@ -52,7 +52,7 @@ class CollectionsTest {
@Test @Test
void testNullSafeSetWithNullArgument() { void testNullSafeSetWithNullArgument() {
def set = Collections.nullSafe(null) def set = Collections.nullSafe((Set)null)
assertNotNull set assertNotNull set
assertTrue set.isEmpty() assertTrue set.isEmpty()
} }
@ -70,4 +70,25 @@ class CollectionsTest {
def b = Collections.nullSafe(a) def b = Collections.nullSafe(a)
assertSame a, b assertSame a, b
} }
@Test
void testNullSafeCollectionWithNullArgument() {
Collection c = Collections.nullSafe((Collection)null)
assertNotNull c
assertTrue c.isEmpty()
}
@Test
void testNullSafeCollectionWithEmptyArgument() {
Collection a = new LinkedHashSet() as Collection
def b = Collections.nullSafe(a)
assertSame a, b
}
@Test
void testNullSafeCollectionWithNonEmptyArgument() {
Collection a = ["hello"] as Collection<String>
def b = Collections.nullSafe(a)
assertSame a, b
}
} }

View File

@ -19,11 +19,14 @@ import io.jsonwebtoken.JweHeaderMutator;
import io.jsonwebtoken.impl.lang.DelegatingMapMutator; import io.jsonwebtoken.impl.lang.DelegatingMapMutator;
import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.impl.security.X509BuilderSupport; import io.jsonwebtoken.impl.security.X509BuilderSupport;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.PublicJwk; import io.jsonwebtoken.security.PublicJwk;
import java.net.URI; import java.net.URI;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -102,8 +105,29 @@ public class DefaultJweHeaderMutator<T extends JweHeaderMutator<T>>
// ============================================================= // =============================================================
@Override @Override
public T critical(Set<String> crit) { public T critical(String crit) {
return put(DefaultProtectedHeader.CRIT, crit); crit = Strings.clean(crit);
if (Strings.hasText(crit)) {
critical(Collections.setOf(crit));
}
return self();
}
@Override
public T critical(Collection<String> crit) {
if (!Collections.isEmpty(crit)) {
Set<String> existing = Collections.nullSafe(this.DELEGATE.get(DefaultProtectedHeader.CRIT));
Set<String> set = new LinkedHashSet<>(existing.size() + crit.size());
set.addAll(existing);
for (String s : crit) {
s = Strings.clean(s);
if (s != null) {
set.add(s);
}
}
put(DefaultProtectedHeader.CRIT, set);
}
return self();
} }
@Override @Override

View File

@ -19,7 +19,6 @@ import io.jsonwebtoken.ClaimsMutator;
import io.jsonwebtoken.impl.lang.DelegatingMapMutator; import io.jsonwebtoken.impl.lang.DelegatingMapMutator;
import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.impl.lang.Parameters;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.MapMutator; import io.jsonwebtoken.lang.MapMutator;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
@ -88,8 +87,7 @@ public class DelegatingClaimsMutator<T extends MapMutator<String, Object, T> & C
put(DefaultClaims.AUDIENCE, Collections.setOf(existing)); // replace as Set put(DefaultClaims.AUDIENCE, Collections.setOf(existing)); // replace as Set
} }
} }
Set<String> aud = get(DefaultClaims.AUDIENCE); return get(DefaultClaims.AUDIENCE);
return aud != null ? aud : Collections.<String>emptySet();
} }
@Override @Override
@ -108,16 +106,28 @@ public class DelegatingClaimsMutator<T extends MapMutator<String, Object, T> & C
@Override @Override
public T audience(String aud) { public T audience(String aud) {
aud = Assert.hasText(Strings.clean(aud), "Audience string value cannot be null or empty."); aud = Strings.clean(aud);
Set<String> set = new LinkedHashSet<>(getAudience()); if (Strings.hasText(aud)) {
set.add(aud); audience(java.util.Collections.singleton(aud));
return audience(set); }
return self();
} }
@Override @Override
public T audience(Collection<String> aud) { public T audience(Collection<String> aud) {
getAudience(); //coerce to Set<String> if necessary if (!Collections.isEmpty(aud)) {
return put(DefaultClaims.AUDIENCE, Collections.asSet(aud)); Set<String> existing = Collections.nullSafe(getAudience());
Set<String> set = new LinkedHashSet<>(existing.size() + aud.size());
set.addAll(existing);
for (String s : aud) {
s = Strings.clean(s);
if (s != null) {
set.add(s);
}
}
put(DefaultClaims.AUDIENCE, set);
}
return self();
} }
@Override @Override

View File

@ -109,18 +109,14 @@ abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends Jwk
@Override @Override
public T operation(KeyOperation operation) throws IllegalArgumentException { public T operation(KeyOperation operation) throws IllegalArgumentException {
Assert.notNull(operation, "KeyOperation cannot be null."); return operation != null ? operations(Collections.setOf(operation)) : self();
return operations(Collections.setOf(operation));
} }
@Override @Override
public T operations(Collection<KeyOperation> ops) { public T operations(Collection<KeyOperation> ops) {
Assert.notEmpty(ops, "KeyOperations collection argument cannot be null or empty."); Set<KeyOperation> set = new LinkedHashSet<>(Collections.nullSafe(ops)); // new ones override existing ones
Set<KeyOperation> set = new LinkedHashSet<>(ops); // new ones override existing ones Set<KeyOperation> existing = Collections.nullSafe(this.DELEGATE.getOperations());
Set<KeyOperation> existing = this.DELEGATE.getOperations();
if (!Collections.isEmpty(existing)) {
set.addAll(existing); set.addAll(existing);
}
this.opsPolicy.validate(set); this.opsPolicy.validate(set);
this.DELEGATE.setOperations(set); this.DELEGATE.setOperations(set);
return self(); return self();

View File

@ -647,6 +647,18 @@ class DefaultJwtBuilderTest {
assertEquals aud, Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt).payload.getAudience().iterator().next() assertEquals aud, Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt).payload.getAudience().iterator().next()
} }
@Test
void testAudienceNullString() {
def jwt = Jwts.builder().subject('me').audience(null).compact()
assertNull Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
@Test
void testAudienceEmptyString() {
def jwt = Jwts.builder().subject('me').audience(' ').compact()
assertNull Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
@Test @Test
void testAudienceMultipleTimes() { void testAudienceMultipleTimes() {
def one = 'one' def one = 'one'
@ -657,6 +669,28 @@ class DefaultJwtBuilderTest {
assertTrue aud.contains(two) assertTrue aud.contains(two)
} }
@Test
void testAudienceNullCollection() {
Collection c = null
def jwt = Jwts.builder().subject('me').audience(c).compact()
assertNull Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
@Test
void testAudienceEmptyCollection() {
Collection c = new ArrayList()
def jwt = Jwts.builder().subject('me').audience(c).compact()
assertNull Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
@Test
void testAudienceCollectionWithNullElement() {
Collection c = new ArrayList()
c.add(null)
def jwt = Jwts.builder().subject('me').audience(c).compact()
assertNull Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt).payload.getAudience()
}
/** /**
* Asserts that if someone calls builder.audienceSingle and then audience(String), that the audience value * Asserts that if someone calls builder.audienceSingle and then audience(String), that the audience value
* will automatically be coerced from a String to a Set<String> and contain both elements. * will automatically be coerced from a String to a Set<String> and contain both elements.
@ -693,17 +727,17 @@ class DefaultJwtBuilderTest {
/** /**
* Asserts that if someone calls builder.audienceSingle and then audience(Collection), the builder coerces the * Asserts that if someone calls builder.audienceSingle and then audience(Collection), the builder coerces the
* aud to a Set<String> and only the elements in the Collection will be applied since audience(Collection) is a * aud to a Set<String> and all elements will be applied since audience(Collection).
* full-replacement operation.
*/ */
@Test @Test
void testAudienceSingleThenAudienceCollection() { void testAudienceSingleThenAudienceCollection() {
def single = 'one' def single = 'one'
def collection = ['two', 'three'] as Set<String> def collection = ['two', 'three'] as Set<String>
def expected = ['one', 'two', 'three'] as Set<String>
def jwt = Jwts.builder().audienceSingle(single).audience(collection).compact() def jwt = Jwts.builder().audienceSingle(single).audience(collection).compact()
def aud = Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt).payload.getAudience() def aud = Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt).payload.getAudience()
assertEquals collection.size(), aud.size() assertEquals expected.size(), aud.size()
assertTrue aud.containsAll(collection) assertTrue aud.contains(single) && aud.containsAll(collection)
} }
/** /**

View File

@ -496,21 +496,46 @@ class DefaultJwtHeaderBuilderTest {
} }
@Test @Test
void testCritNullRemovesValues() { void testCritSingle() {
def crit = ['test'] as Set<String> def crit = 'test'
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader def header = jws().add(crit, 'foo').critical(crit).build() as ProtectedHeader
assertEquals crit, header.getCritical() def expected = [crit] as Set<String>
header = builder.critical(null).build() assertEquals expected, header.getCritical()
assertFalse header.containsKey('crit') // removed
} }
@Test @Test
void testCritEmptyRemovesValues() { void testCritSingleNullIgnored() {
def crit = 'test'
def expected = [crit] as Set<String>
def header = jws().add(crit, 'foo').critical(crit).build() as ProtectedHeader
assertEquals expected, header.getCritical()
header = builder.critical((String) null).build() as ProtectedHeader // ignored
assertEquals expected, header.getCritical() // nothing changed
}
@Test
void testCritNullCollectionIgnored() {
def crit = ['test'] as Set<String>
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader
assertEquals crit, header.getCritical()
header = builder.critical((Collection) null).build() as ProtectedHeader
assertEquals crit, header.getCritical() // nothing changed
}
@Test
void testCritCollectionWithNullElement() {
def crit = [null] as Set<String>
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader
assertNull header.getCritical()
}
@Test
void testCritEmptyIgnored() {
def crit = ['test'] as Set<String> def crit = ['test'] as Set<String>
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader
assertEquals crit, header.getCritical() assertEquals crit, header.getCritical()
header = builder.critical([] as Set<String>).build() header = builder.critical([] as Set<String>).build()
assertFalse header.containsKey('crit') // removed assertEquals crit, header.getCritical() // ignored
} }
/** /**

View File

@ -142,11 +142,50 @@ class JwksTest {
testProperty('id', 'kid', srandom()) testProperty('id', 'kid', srandom())
} }
@Test
void testSingleOperation() {
def op = Jwks.OP.ENCRYPT
def expected = [op] as Set<KeyOperation>
def canonical = [op.getId()] as Set<String>
def jwk = Jwks.builder().key(TestKeys.A128GCM).operation(op).build()
assertEquals expected, jwk.getOperations()
assertEquals canonical, jwk.get(AbstractJwk.KEY_OPS.id)
}
@Test
void testSingleOperationNull() {
def jwk = Jwks.builder().key(TestKeys.A128GCM).operation(null).build() //ignored null
assertNull jwk.getOperations() //nothing added
assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id)
}
@Test
void testSingleOperationAppends() {
def expected = [Jwks.OP.ENCRYPT, Jwks.OP.DECRYPT] as Set<KeyOperation>
def jwk = Jwks.builder().key(TestKeys.A128GCM)
.operation(Jwks.OP.ENCRYPT).operation(Jwks.OP.DECRYPT)
.build()
assertEquals expected, jwk.getOperations()
}
@Test @Test
void testOperations() { void testOperations() {
def val = [Jwks.OP.SIGN, Jwks.OP.VERIFY] as Set<KeyOperation> def val = [Jwks.OP.SIGN, Jwks.OP.VERIFY] as Set<KeyOperation>
def canonical = Collections.setOf('sign', 'verify') def jwk = Jwks.builder().key(TestKeys.A128GCM).operations(val).build()
testProperty('operations', 'key_ops', val, canonical) assertEquals val, jwk.getOperations()
}
@Test
void testOperationsNull() {
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations(null).build()
assertNull jwk.getOperations()
assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id)
}
@Test
void testOperationsEmpty() {
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations(Collections.emptyList()).build()
assertNull jwk.getOperations()
} }
@Test @Test