Skip to content

Commit e854f0f

Browse files
committed
handle case when token cannot be found
1 parent 28c0757 commit e854f0f

File tree

2 files changed

+85
-50
lines changed

2 files changed

+85
-50
lines changed

src/main/java/uk/ac/cam/cl/dtg/segue/auth/MicrosoftAuthenticator.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,10 @@ public String exchangeCode(String authorizationCode) throws CodeExchangeExceptio
128128

129129
@Override
130130
public UserFromAuthProvider getUserInfo(String internalProviderReference) throws AuthenticatorSecurityException {
131-
String tokenStr = Objects.requireNonNull(credentialStore.getIfPresent(internalProviderReference));
131+
String tokenStr = credentialStore.getIfPresent(internalProviderReference);
132+
if (null == tokenStr) {
133+
throw new AuthenticatorSecurityException("Token verification: TOKEN_MISSING");
134+
}
132135
var token = parseAndVerifyToken(tokenStr);
133136
// TODO: to support sign-ups, parse more info
134137
return new UserFromAuthProvider(
@@ -151,7 +154,7 @@ private ConfidentialClientApplication client() {
151154
private DecodedJWT parseAndVerifyToken (String tokenStr) throws AuthenticatorSecurityException {
152155
// validating id token based on requirements at
153156
// https://learn.microsoft.com/en-us/entra/identity-platform/id-tokens
154-
// I've ignored "nonce" validation as we skipped that with other clients also
157+
// I've ignored "nonce" validation as RaspberryPi Authenticator also skips it
155158
var token = JWT.decode(tokenStr);
156159
var keyId = token.getKeyId();
157160
if (null == keyId) {

src/test/java/uk/ac/cam/cl/dtg/segue/auth/MicrosoftAuthenticatorTest.java

Lines changed: 80 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
import org.json.JSONArray;
1919
import org.json.JSONObject;
2020
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.function.ThrowingSupplier;
2122
import uk.ac.cam.cl.dtg.isaac.dos.users.UserFromAuthProvider;
2223
import uk.ac.cam.cl.dtg.segue.auth.exceptions.AuthenticatorSecurityException;
2324

2425
import java.io.IOException;
26+
import java.net.MalformedURLException;
2527
import java.security.KeyPair;
2628
import java.security.KeyPairGenerator;
2729
import java.security.NoSuchAlgorithmException;
@@ -38,7 +40,7 @@
3840

3941
class MicrosoftAuthenticatorTest extends Helpers{
4042
@Test
41-
void getUserInfo_validToken_returnsUserInformation() throws Exception {
43+
void getUserInfo_validToken_returnsUserInformation() throws Throwable {
4244
String token = signedToken(validSigningKey, t -> t
4345
.withIssuedAt(oneHourAgo)
4446
.withNotBefore(oneHourAgo)
@@ -49,9 +51,31 @@ void getUserInfo_validToken_returnsUserInformation() throws Exception {
4951
assertEquals("test@example.com", userInfo.getEmail());
5052
}
5153

54+
@Test
55+
void getUserInfo_nullToStore_throwsError() {
56+
assertThrows(NullPointerException.class, () -> testGetUserInfo(null));
57+
}
58+
59+
@Test
60+
void getUserInfo_noSuchToken_throwsError() throws MalformedURLException {
61+
var store = getStore();
62+
var subject = new MicrosoftAuthenticator(
63+
clientId, "", "", "http://localhost:8888/keys"
64+
) {{
65+
MicrosoftAuthenticator.credentialStore = store;
66+
}};
67+
var error = assertThrows(AuthenticatorSecurityException.class, () -> subject.getUserInfo("no_token_for_id"));
68+
assertEquals("Token verification: TOKEN_MISSING", error.getMessage());
69+
}
70+
5271
@Test
5372
void getUserInfo_tokenSignatureNoKeyId_throwsError() {
54-
String token = signedToken(validSigningKey, t -> t.withKeyId((String) null));
73+
String token = signedToken(validSigningKey, t -> t
74+
.withIssuedAt(oneHourAgo)
75+
.withNotBefore(oneHourAgo)
76+
.withExpiresAt(inOneHour)
77+
.withAudience(clientId)
78+
.withKeyId((String) null));
5579
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
5680
assertEquals("Token verification: NO_KEY_ID", error.getMessage());
5781
}
@@ -118,10 +142,10 @@ void getUserInfo_tokenNoIat_throwsError() {
118142
@Test
119143
void getUserInfo_tokenNullIat_throwsError() {
120144
String token = signedToken(validSigningKey, t -> t
121-
.withExpiresAt(inOneHour)
122-
.withNotBefore(oneHourAgo)
123-
.withAudience(clientId)
124-
.withIssuedAt((Date) null)
145+
.withExpiresAt(inOneHour)
146+
.withNotBefore(oneHourAgo)
147+
.withAudience(clientId)
148+
.withIssuedAt((Date) null)
125149
);
126150
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
127151
assertEquals("Token verification: NULL_ISSUED_AT", error.getMessage());
@@ -130,10 +154,10 @@ void getUserInfo_tokenNullIat_throwsError() {
130154
@Test
131155
void getUserInfo_tokenIatFuture_throwsError() {
132156
String token = signedToken(validSigningKey, t -> t
133-
.withExpiresAt(inOneHour)
134-
.withNotBefore(oneHourAgo)
135-
.withAudience(clientId)
136-
.withIssuedAt(inOneHour)
157+
.withExpiresAt(inOneHour)
158+
.withNotBefore(oneHourAgo)
159+
.withAudience(clientId)
160+
.withIssuedAt(inOneHour)
137161
);
138162
var error = assertThrows(IncorrectClaimException.class, () -> testGetUserInfo(token));
139163
assertEquals(String.format("The Token can't be used before %s.", inOneHour), error.getMessage());
@@ -142,9 +166,9 @@ void getUserInfo_tokenIatFuture_throwsError() {
142166
@Test
143167
void getUserInfo_tokenNoNbf_throwsError() {
144168
String token = signedToken(validSigningKey, t -> t
145-
.withExpiresAt(inOneHour)
146-
.withIssuedAt(oneHourAgo)
147-
.withAudience(clientId)
169+
.withExpiresAt(inOneHour)
170+
.withIssuedAt(oneHourAgo)
171+
.withAudience(clientId)
148172
);
149173
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
150174
assertEquals("Token verification: NULL_NOT_BEFORE", error.getMessage());
@@ -153,10 +177,10 @@ void getUserInfo_tokenNoNbf_throwsError() {
153177
@Test
154178
void getUserInfo_tokenNullNbf_throwsError() {
155179
String token = signedToken(validSigningKey, t -> t
156-
.withIssuedAt(oneHourAgo)
157-
.withExpiresAt(inOneHour)
158-
.withAudience(clientId)
159-
.withNotBefore((Date) null)
180+
.withIssuedAt(oneHourAgo)
181+
.withExpiresAt(inOneHour)
182+
.withAudience(clientId)
183+
.withNotBefore((Date) null)
160184
);
161185
var error = assertThrows(AuthenticatorSecurityException.class, () -> testGetUserInfo(token));
162186
assertEquals("Token verification: NULL_NOT_BEFORE", error.getMessage());
@@ -165,10 +189,10 @@ void getUserInfo_tokenNullNbf_throwsError() {
165189
@Test
166190
void getUserInfo_tokenNbfFuture_throwsError() {
167191
String token = signedToken(validSigningKey, t -> t
168-
.withExpiresAt(inOneHour)
169-
.withIssuedAt(oneHourAgo)
170-
.withAudience(clientId)
171-
.withNotBefore(inOneHour)
192+
.withExpiresAt(inOneHour)
193+
.withIssuedAt(oneHourAgo)
194+
.withAudience(clientId)
195+
.withNotBefore(inOneHour)
172196
);
173197
var error = assertThrows(IncorrectClaimException.class, () -> testGetUserInfo(token));
174198
assertEquals(String.format("The Token can't be used before %s.", inOneHour), error.getMessage());
@@ -177,8 +201,8 @@ void getUserInfo_tokenNbfFuture_throwsError() {
177201
@Test
178202
void getUserInfo_tokenNoAud_throwsError() {
179203
String token = signedToken(validSigningKey, t -> t
180-
.withExpiresAt(inOneHour)
181-
.withIssuedAt(oneHourAgo)
204+
.withExpiresAt(inOneHour)
205+
.withIssuedAt(oneHourAgo)
182206
);
183207
var error = assertThrows(MissingClaimException.class, () -> testGetUserInfo(token));
184208
assertEquals("The Claim 'aud' is not present in the JWT.", error.getMessage());
@@ -187,10 +211,10 @@ void getUserInfo_tokenNoAud_throwsError() {
187211
@Test
188212
void getUserInfo_tokenNullAud_throwsError() {
189213
String token = signedToken(validSigningKey, t -> t
190-
.withIssuedAt(oneHourAgo)
191-
.withExpiresAt(inOneHour)
192-
.withNotBefore(oneHourAgo)
193-
.withAudience((String) null)
214+
.withIssuedAt(oneHourAgo)
215+
.withExpiresAt(inOneHour)
216+
.withNotBefore(oneHourAgo)
217+
.withAudience((String) null)
194218
);
195219
var error = assertThrows(IncorrectClaimException.class, () -> testGetUserInfo(token));
196220
assertEquals("The Claim 'aud' value doesn't contain the required audience.", error.getMessage());
@@ -199,46 +223,54 @@ void getUserInfo_tokenNullAud_throwsError() {
199223
@Test
200224
void getUserInfo_tokenAudIncorrect_throwsError() {
201225
String token = signedToken(validSigningKey, t -> t
202-
.withIssuedAt(oneHourAgo)
203-
.withExpiresAt(inOneHour)
204-
.withNotBefore(oneHourAgo)
205-
.withAudience("intended_for_somebody_else")
226+
.withIssuedAt(oneHourAgo)
227+
.withExpiresAt(inOneHour)
228+
.withNotBefore(oneHourAgo)
229+
.withAudience("intended_for_somebody_else")
206230
);
207231
var error = assertThrows(IncorrectClaimException.class, () -> testGetUserInfo(token));
208232
assertEquals("The Claim 'aud' value doesn't contain the required audience.", error.getMessage());
209233
}
210234
}
211235

212236
class Helpers {
213-
public static UserFromAuthProvider testGetUserInfo(String token) throws Exception {
237+
static UserFromAuthProvider testGetUserInfo(String token) throws Throwable {
238+
var store = getStore();
239+
var subject = new MicrosoftAuthenticator(
240+
clientId, "", "", "http://localhost:8888/keys"
241+
) {{
242+
MicrosoftAuthenticator.credentialStore = store;
243+
}};
244+
store.put("the_internal_id", token);
245+
return withKeyServer(() -> subject.getUserInfo("the_internal_id"));
246+
}
247+
248+
static <T> T withKeyServer(ThrowingSupplier<T> fn) throws Throwable {
214249
var keyServer = TestKeyServer.withKey(validSigningKey).start(8888);
215-
Cache<String, String> store = CacheBuilder.newBuilder()
216-
.expireAfterAccess(10, TimeUnit.MINUTES)
217-
.build();
218250
try {
219-
var subject = new MicrosoftAuthenticator(
220-
clientId, "", "", "http://localhost:8888/keys"
221-
) {{
222-
MicrosoftAuthenticator.credentialStore = store;
223-
}};
224-
store.put("the_internal_id", token);
225-
return subject.getUserInfo("the_internal_id");
251+
return fn.get();
226252
} finally {
227253
keyServer.stop();
228254
}
229255
}
230256

231-
public static String signedToken(TestKeyPair key, Function<JWTCreator.Builder, JWTCreator.Builder> fn) {
257+
static String signedToken(TestKeyPair key, Function<JWTCreator.Builder, JWTCreator.Builder> fn) {
232258
var algorithm = Algorithm.RSA256(key.publicKey(), key.privateKey());
233259
var token = fn.apply(JWT.create().withKeyId(key.id()));
234260
return token.sign(algorithm);
235261
}
236262

237-
public static TestKeyPair validSigningKey = new TestKeyPair();
238-
public static TestKeyPair invalidSigningKey = new TestKeyPair();
239-
public static Instant oneHourAgo = Instant.now().minusSeconds(60 * 60).truncatedTo(ChronoUnit.SECONDS);
240-
public static Instant inOneHour = Instant.now().plusSeconds(60 * 60).truncatedTo(ChronoUnit.SECONDS);
241-
public static String clientId = "the_client_id";
263+
static Cache<String, String> getStore() {
264+
return CacheBuilder.newBuilder()
265+
.expireAfterAccess(10, TimeUnit.MINUTES)
266+
.build();
267+
}
268+
269+
static TestKeyPair validSigningKey = new TestKeyPair();
270+
static TestKeyPair invalidSigningKey = new TestKeyPair();
271+
static Instant oneHourAgo = Instant.now().minusSeconds(60 * 60).truncatedTo(ChronoUnit.SECONDS);
272+
static Instant inOneHour = Instant.now().plusSeconds(60 * 60).truncatedTo(ChronoUnit.SECONDS);
273+
static String clientId = "the_client_id";
242274
}
243275

244276
class TestKeyServer {

0 commit comments

Comments
 (0)