1 package com.atlassian.asap.core.validator;
2
3 import com.atlassian.asap.api.Jwt;
4 import com.atlassian.asap.api.JwtBuilder;
5 import com.atlassian.asap.core.exception.InvalidClaimException;
6 import com.atlassian.asap.core.exception.TokenExpiredException;
7 import com.atlassian.asap.core.exception.TokenTooEarlyException;
8 import com.google.common.collect.ImmutableSet;
9 import org.junit.Before;
10 import org.junit.Test;
11 import org.junit.runner.RunWith;
12 import org.mockito.runners.MockitoJUnitRunner;
13
14 import java.time.Clock;
15 import java.time.Duration;
16 import java.time.Instant;
17 import java.time.ZoneId;
18 import java.time.temporal.ChronoUnit;
19 import java.util.Optional;
20
21 @RunWith(MockitoJUnitRunner.class)
22 public class JwtClaimsValidatorTest {
23 private static final String JWT_ID = "some-id";
24 private static final String VALID_AUDIENCE = "valid-audience";
25 private static final String VALID_AUDIENCE2 = "valid-audience-2";
26 private static final String VALID_ISSUER = "valid-issuer";
27 private static final String VALID_SUBJECT = "valid-subject";
28 private static final Instant NOW = Instant.EPOCH;
29 private static final Instant THE_PAST = NOW.minus(1, ChronoUnit.DAYS);
30 private static final Instant THE_FUTURE = NOW.plus(1, ChronoUnit.DAYS);
31
32 private static final Jwt VALID_SELF_ISSUED_TOKEN = JwtBuilder.newJwt()
33 .issuedAt(NOW)
34 .notBefore(Optional.of(NOW))
35 .expirationTime(NOW.plusSeconds(60))
36 .keyId(VALID_ISSUER + "/some-key")
37 .issuer(VALID_ISSUER)
38 .audience(VALID_AUDIENCE)
39 .jwtId(JWT_ID)
40 .build();
41
42 private JwtClaimsValidator validator;
43
44 @Before
45 public void createValidator() {
46 validator = new JwtClaimsValidator(Clock.fixed(NOW, ZoneId.of("UTC+0")));
47 }
48
49 private void runValidate(Jwt jwt) throws Exception {
50 validator.validate(jwt, ImmutableSet.of(VALID_AUDIENCE));
51 }
52
53 @Test
54 public void shouldAcceptSelfIssuedValidClaims() throws Exception {
55 runValidate(VALID_SELF_ISSUED_TOKEN);
56 }
57
58 @Test
59 public void shouldAcceptJwtIfIssuerDoesNotMatchSubject() throws Exception {
60 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
61 .subject(Optional.of(VALID_SUBJECT))
62 .build();
63 runValidate(jwt);
64 }
65
66
67
68 @Test(expected = InvalidClaimException.class)
69 public void shouldRejectJwtIfIssuerIsBlank() throws Exception {
70 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
71 .issuer(" ")
72 .build();
73 runValidate(jwt);
74 }
75
76 @Test(expected = InvalidClaimException.class)
77 public void shouldRejectJwtIfKidDoesNotStartWithIssuer() throws Exception {
78 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
79 .keyId("some-other/key")
80 .build();
81 runValidate(jwt);
82 }
83
84 @Test
85 public void shouldAcceptJwtIfAudienceIsOneOfValidAudiences() throws Exception {
86 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
87 .subject(Optional.of(VALID_SUBJECT))
88 .build();
89 validator.validate(jwt, ImmutableSet.of(VALID_AUDIENCE, VALID_AUDIENCE2));
90 }
91
92 @Test(expected = InvalidClaimException.class)
93 public void shouldRejectJwtIfTheAudienceIsNotValid() throws Exception {
94 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
95 .audience("invalid-audience")
96 .build();
97 runValidate(jwt);
98 }
99
100 @Test(expected = InvalidClaimException.class)
101 public void shouldRejectJwtIfTheAudienceIsNotOneOfValidAudiences() throws Exception {
102 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
103 .audience("invalid-audience")
104 .build();
105 validator.validate(jwt, ImmutableSet.of(VALID_AUDIENCE, VALID_AUDIENCE2));
106 }
107
108
109
110 @Test(expected = InvalidClaimException.class)
111 public void shouldRejectJwtThatWasIssuedImmediatelyAfterItsExpiry() throws Exception {
112 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
113 .issuedAt(VALID_SELF_ISSUED_TOKEN.getClaims().getExpiry().plusSeconds(1))
114 .build();
115 runValidate(jwt);
116 }
117
118 @Test(expected = InvalidClaimException.class)
119 public void shouldRejectJwtThatWasValidImmediatelyBeforeTheTimeItWasIssued() throws Exception {
120 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
121 .notBefore(Optional.of(VALID_SELF_ISSUED_TOKEN.getClaims().getIssuedAt().minusSeconds(1)))
122 .build();
123 runValidate(jwt);
124 }
125
126 @Test(expected = InvalidClaimException.class)
127 public void shouldRejectJwtThatIsNeverValid() throws Exception {
128 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
129 .notBefore(Optional.of(VALID_SELF_ISSUED_TOKEN.getClaims().getExpiry().plusSeconds(1)))
130 .build();
131 runValidate(jwt);
132 }
133
134 @Test(expected = InvalidClaimException.class)
135 public void shouldRejectJwtIfLifetimeExceedsOneHour() throws Exception {
136 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
137 .expirationTime(NOW.plus(2, ChronoUnit.HOURS))
138 .build();
139 runValidate(jwt);
140 }
141
142 @Test
143 public void shouldAcceptJwtIfLifetimeDoesntExceedsMaxLifetime() throws Exception {
144 JwtClaimsValidator validator = new JwtClaimsValidator(Clock.fixed(NOW, ZoneId.of("UTC+0")), Duration.ofDays(30));
145 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
146 .expirationTime(NOW.plus(29, ChronoUnit.DAYS))
147 .build();
148 validator.validate(jwt, ImmutableSet.of(VALID_AUDIENCE));
149 }
150
151
152
153 @Test(expected = TokenExpiredException.class)
154 public void shouldRejectJwtThatHasExpired() throws Exception {
155 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
156 .expirationTime(THE_PAST)
157 .issuedAt(THE_PAST)
158 .notBefore(Optional.<Instant>empty())
159 .build();
160 runValidate(jwt);
161 }
162
163 @Test(expected = TokenTooEarlyException.class)
164 public void shouldRejectJwtThatIsNotValidYetWhenUsingNotBefore() throws Exception {
165 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
166 .issuedAt(THE_FUTURE)
167 .notBefore(Optional.of(THE_FUTURE))
168 .expirationTime(THE_FUTURE.plusSeconds(1))
169 .build();
170 runValidate(jwt);
171 }
172
173 @Test(expected = TokenTooEarlyException.class)
174 public void shouldRejectJwtThatIsNotValidYetWhenNotUsingNotBefore() throws Exception {
175 Jwt jwt = JwtBuilder.copyJwt(VALID_SELF_ISSUED_TOKEN)
176 .issuedAt(THE_FUTURE)
177 .notBefore(Optional.<Instant>empty())
178 .expirationTime(THE_FUTURE.plusSeconds(1))
179 .build();
180 runValidate(jwt);
181 }
182 }