Skip to content

Commit 2111616

Browse files
committed
Fix bad parsing of JWT dates
1 parent 39176ba commit 2111616

File tree

8 files changed

+78
-20
lines changed

8 files changed

+78
-20
lines changed

Example/Auth/Tests/FIRAuthTests.m

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,16 +1863,17 @@ - (void)testUpdateCurrentUserFailureNetworkError {
18631863
[self waitForSignInWithAccessToken:kTestAccessToken
18641864
APIKey:kTestAPIKey
18651865
completion:nil];
1866-
NSString *kTestAPIKey2 = @"fakeAPIKey2";
1867-
FIRUser *user2 = [FIRAuth auth].currentUser;
1868-
user2.requestConfiguration = [[FIRAuthRequestConfiguration alloc]initWithAPIKey:kTestAPIKey2];
1869-
OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
1870-
.andDispatchError2([FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]);
1871-
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
1872-
[[FIRAuth auth] updateCurrentUser:user2 completion:^(NSError *_Nullable error) {
1873-
XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError);
1874-
[expectation fulfill];
1875-
}];
1866+
NSString *kTestAPIKey2 = @"fakeAPIKey2";
1867+
FIRUser *user2 = [FIRAuth auth].currentUser;
1868+
user2.requestConfiguration = [[FIRAuthRequestConfiguration alloc]initWithAPIKey:kTestAPIKey2];
1869+
NSError *underlyingError = [NSError errorWithDomain:@"Test Error" code:1 userInfo:nil];
1870+
OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
1871+
.andDispatchError2([FIRAuthErrorUtils networkErrorWithUnderlyingError:underlyingError]);
1872+
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
1873+
[[FIRAuth auth] updateCurrentUser:user2 completion:^(NSError *_Nullable error) {
1874+
XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError);
1875+
[expectation fulfill];
1876+
}];
18761877
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
18771878
OCMVerifyAll(_mockBackend);
18781879
}

Example/Auth/Tests/FIRPhoneAuthProviderTests.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,8 @@ - (void)testVerifyPhoneNumberInTestModeFailure {
453453
// Assert that the app credential is nil when in test mode.
454454
XCTAssertNil(request.appCredential);
455455
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
456-
callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]);
456+
NSError *underlying = [NSError errorWithDomain:@"Test Error" code:1 userInfo:nil];
457+
callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:underlying]);
457458
});
458459
});
459460

Example/Auth/Tests/FIRUserTests.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,8 +1151,8 @@ - (void)testGetIDTokenResultForcingRefreshFailure {
11511151
XCTAssertEqualObjects(request.APIKey, kAPIKey);
11521152

11531153
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
1154-
1155-
callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]);
1154+
NSError *underlying = [NSError errorWithDomain:@"Test Error" code:1 userInfo:nil];
1155+
callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:underlying]);
11561156
});
11571157
});
11581158
[user getIDTokenResultForcingRefresh:YES

Firebase/Auth/Source/FIRAuthErrorUtils.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@ NS_ASSUME_NONNULL_BEGIN
8585
*/
8686
+ (NSError *)unexpectedErrorResponseWithDeserializedResponse:(id)deserializedResponse;
8787

88+
/** @fn malformedJWTErrorWithToken:underlyingError:
89+
@brief Constructs an @c NSError with the code set to @c FIRAuthErrorCodeMalformedJWT and
90+
populates the userInfo dictionary with an error message, the bad token, and an underlying
91+
error that may have occurred when parsing.
92+
@param token The token that failed to parse.
93+
@param underlyingError The error that caused this error. If this parameter is nil, the
94+
NSUnderlyingErrorKey value will not be set.
95+
@remarks This error is returned when JWT parsing fails.
96+
@returns An @c FIRAuthErrorCodeMalformedJWT error wrapping an underlying error, if available.
97+
*/
98+
+ (NSError *)malformedJWTErrorWithToken:(NSString *)token
99+
underlyingError:(NSError *_Nullable)underlyingError;
100+
88101
/** @fn unexpectedResponseWithData:underlyingError:
89102
@brief Constructs an @c NSError with the @c FIRAuthInternalErrorCodeUnexpectedResponse
90103
code, and a populated @c FIRAuthErrorUserInfoDataKey key in the @c NSError.userInfo

Firebase/Auth/Source/FIRAuthErrorUtils.m

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,12 @@
413413
static NSString *const kFIRAuthErrorMessageInternalError = @"An internal error has occurred, "
414414
"print and inspect the error details for more information.";
415415

416+
/** @var kFIRAuthErrorMessageMalformedJWT
417+
@brief Error message constant describing @c FIRAuthErrorCodeMalformedJWT errors.
418+
*/
419+
static NSString *const kFIRAuthErrorMessageMalformedJWT =
420+
@"Failed to parse JWT. Check the userInfo dictionary for the full token.";
421+
416422
/** @var FIRAuthErrorDescription
417423
@brief The error descrioption, based on the error code.
418424
@remarks No default case so that we get a compiler warning if a new value was added to the enum.
@@ -531,6 +537,8 @@
531537
return kFIRAuthErrorMessageNullUser;
532538
case FIRAuthErrorCodeWebInternalError:
533539
return kFIRAuthErrorMessageWebInternalError;
540+
case FIRAuthErrorCodeMalformedJWT:
541+
return kFIRAuthErrorMessageMalformedJWT;
534542
}
535543
}
536544

@@ -652,6 +660,8 @@
652660
return @"ERROR_NULL_USER";
653661
case FIRAuthErrorCodeWebInternalError:
654662
return @"ERROR_WEB_INTERNAL_ERROR";
663+
case FIRAuthErrorCodeMalformedJWT:
664+
return @"ERROR_MALFORMED_JWT";
655665
}
656666
}
657667

@@ -735,6 +745,18 @@ + (NSError *)unexpectedErrorResponseWithDeserializedResponse:(id)deserializedRes
735745
}];
736746
}
737747

748+
+ (NSError *)malformedJWTErrorWithToken:(NSString *)token
749+
underlyingError:(NSError *_Nullable)underlyingError {
750+
NSMutableDictionary *userInfo =
751+
[NSMutableDictionary dictionaryWithObject:kFIRAuthErrorMessageMalformedJWT
752+
forKey:NSLocalizedDescriptionKey];
753+
[userInfo setObject:token forKey:FIRAuthErrorUserInfoDataKey];
754+
if (underlyingError != nil) {
755+
[userInfo setObject:underlyingError forKey:NSUnderlyingErrorKey];
756+
}
757+
return [self errorWithCode:FIRAuthInternalErrorCodeMalformedJWT userInfo:[userInfo copy]];
758+
}
759+
738760
+ (NSError *)unexpectedResponseWithData:(NSData *)data
739761
underlyingError:(NSError *)underlyingError {
740762
return [self errorWithCode:FIRAuthInternalErrorCodeUnexpectedResponse userInfo:@{

Firebase/Auth/Source/FIRAuthInternalErrors.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,9 @@ typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) {
376376
FIRAuthInternalErrorCodeNullUser =
377377
FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeNullUser,
378378

379+
FIRAuthInternalErrorCodeMalformedJWT =
380+
FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMalformedJWT,
381+
379382
/** @var FIRAuthInternalErrorCodeRPCRequestEncodingError
380383
@brief Indicates an error encoding the RPC request.
381384
@remarks This is typically due to some sort of unexpected input value.

Firebase/Auth/Source/FIRUser.m

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -851,9 +851,17 @@ - (void)getIDTokenResultForcingRefresh:(BOOL)forceRefresh
851851
"error" out parameter.
852852
*/
853853
- (FIRAuthTokenResult *)parseIDToken:(NSString *)token error:(NSError **)error {
854+
// Though this is an internal method, errors returned here are surfaced in user-visible
855+
// callbacks.
854856
*error = nil;
855857
NSArray *tokenStringArray = [token componentsSeparatedByString:@"."];
856858

859+
// The JWT should have three parts, though we only use the second in this method.
860+
if (tokenStringArray.count != 3) {
861+
*error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil];
862+
return nil;
863+
}
864+
857865
// The token payload is always the second index of the array.
858866
NSString *idToken = tokenStringArray[1];
859867

@@ -863,8 +871,10 @@ - (FIRAuthTokenResult *)parseIDToken:(NSString *)token error:(NSError **)error {
863871
[[idToken stringByReplacingOccurrencesOfString:@"_" withString:@"/"] mutableCopy];
864872

865873
// Replace "-" with "+"
866-
tokenPayload =
867-
[[tokenPayload stringByReplacingOccurrencesOfString:@"-" withString:@"+"] mutableCopy];
874+
[tokenPayload replaceOccurrencesOfString:@"-"
875+
withString:@"+"
876+
options:kNilOptions
877+
range:NSMakeRange(0, tokenPayload.length)];
868878

869879
// Pad the token payload with "=" signs if the payload's length is not a multiple of 4.
870880
while ((tokenPayload.length % 4) != 0) {
@@ -874,19 +884,22 @@ - (FIRAuthTokenResult *)parseIDToken:(NSString *)token error:(NSError **)error {
874884
[[NSData alloc] initWithBase64EncodedString:tokenPayload
875885
options:NSDataBase64DecodingIgnoreUnknownCharacters];
876886
if (!decodedTokenPayloadData) {
877-
*error = [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:token];
887+
*error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil];
878888
return nil;
879889
}
890+
NSError *jsonError = nil;
891+
NSJSONReadingOptions options = NSJSONReadingMutableContainers|NSJSONReadingAllowFragments;
880892
NSDictionary *tokenPayloadDictionary =
881893
[NSJSONSerialization JSONObjectWithData:decodedTokenPayloadData
882-
options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments
883-
error:error];
884-
if (*error) {
894+
options:options
895+
error:&jsonError];
896+
if (jsonError != nil) {
897+
*error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:jsonError];
885898
return nil;
886899
}
887900

888901
if (!tokenPayloadDictionary) {
889-
*error = [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:token];
902+
*error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil];
890903
return nil;
891904
}
892905

Firebase/Auth/Source/Public/FIRAuthErrors.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ typedef NS_ENUM(NSInteger, FIRAuthErrorCode) {
311311
/** Indicates an internal error occurred.
312312
*/
313313
FIRAuthErrorCodeInternalError = 17999,
314+
315+
/** Raised when a JWT fails to parse correctly. May be accompanied by an underlying error
316+
describing which step of the JWT parsing process failed.
317+
*/
318+
FIRAuthErrorCodeMalformedJWT = 18000,
314319
} NS_SWIFT_NAME(AuthErrorCode);
315320

316321
@end

0 commit comments

Comments
 (0)