View Javadoc

1   package com.atlassian.asap.service.core.impl;
2   
3   import com.atlassian.asap.api.Jwt;
4   import com.atlassian.asap.api.JwtBuilder;
5   import com.atlassian.asap.api.JwtClaims;
6   import com.google.common.base.MoreObjects;
7   import com.google.common.collect.ImmutableSet;
8   import org.hamcrest.Description;
9   import org.hamcrest.TypeSafeMatcher;
10  
11  import javax.json.JsonObject;
12  import javax.json.JsonValue;
13  import java.time.Duration;
14  import java.util.HashMap;
15  import java.util.Map;
16  import java.util.Optional;
17  import java.util.Set;
18  
19  import static java.util.Objects.requireNonNull;
20  
21  /**
22   * Matcher for JWT fields.
23   * <p>
24   * Verifies as much or as little as it is told to.
25   * </p>
26   */
27  final class JwtMatcher extends TypeSafeMatcher<Jwt> {
28      private Optional<String> issuer = Optional.empty();
29      private Optional<String> keyId = Optional.empty();
30      private Optional<Optional<String>> subject = Optional.empty();
31      private Optional<Set<String>> audience = Optional.empty();
32      private Map<String, JsonValue> customClaims = new HashMap<>();
33      private Duration expiration = JwtBuilder.DEFAULT_LIFETIME;
34  
35      private JwtMatcher() {
36      }
37  
38      static JwtMatcher jwtMatcher() {
39          return new JwtMatcher();
40      }
41  
42      JwtMatcher issuer(String issuer) {
43          this.issuer = Optional.of(issuer);
44          return this;
45      }
46  
47      JwtMatcher keyId(String keyId) {
48          this.keyId = Optional.of(keyId);
49          return this;
50      }
51  
52      JwtMatcher subject(Optional<String> subject) {
53          this.subject = Optional.of(subject);
54          return this;
55      }
56  
57      JwtMatcher audience(String... audience) {
58          this.audience = Optional.of(ImmutableSet.copyOf(audience));
59          return this;
60      }
61  
62      JwtMatcher expiration(Duration duration) {
63          this.expiration = requireNonNull(duration);
64          return this;
65      }
66  
67      JwtMatcher customClaim(String key, JsonValue expectedValue) {
68          requireNonNull(key, "key");
69          requireNonNull(expectedValue, "expectedValue");
70          this.customClaims.put(key, expectedValue);
71          return this;
72      }
73  
74      @Override
75      @SuppressWarnings("checkstyle:BooleanExpressionComplexity")
76      protected boolean matchesSafely(Jwt jwt) {
77          final JwtClaims claims = jwt.getClaims();
78          final Duration expiration = Duration.between(claims.getIssuedAt(), claims.getExpiry());
79          return matches(keyId, jwt.getHeader().getKeyId())
80                  && matches(issuer, claims.getIssuer())
81                  && matches(subject, claims.getSubject())
82                  && matches(audience, claims.getAudience())
83                  && this.expiration.equals(expiration)
84                  && hasAllExpectedCustomClaims(jwt);
85      }
86  
87      private boolean hasAllExpectedCustomClaims(Jwt jwt) {
88          final JsonObject json = jwt.getClaims().getJson();
89          return customClaims.entrySet().stream()
90                  .allMatch(entry -> entry.getValue().equals(json.get(entry.getKey())));
91      }
92  
93      @Override
94      public void describeTo(Description description) {
95          MoreObjects.ToStringHelper toString = MoreObjects.toStringHelper("Jwt");
96          keyId.ifPresent(kid -> toString.add("kid", kid));
97          issuer.ifPresent(iss -> toString.add("iss", iss));
98          subject.ifPresent(sub -> toString.add("sub", sub));
99          audience.ifPresent(aud -> toString.add("aud", aud));
100         toString.add("exp-iat", expiration);
101         if (!customClaims.isEmpty()) {
102             toString.add("custom", customClaims);
103         }
104         description.appendText("Jwt with constraints ").appendValue(toString.toString());
105     }
106 
107     private static <T> boolean matches(Optional<T> optionalExpectedValue, T actualValue) {
108         return optionalExpectedValue
109                 .map(expected -> expected.equals(actualValue))
110                 .orElse(true);
111     }
112 }