From ea490a2c6492e41e892397e044477f778ce358b8 Mon Sep 17 00:00:00 2001 From: Zsika Phillip Date: Thu, 29 Mar 2018 18:16:45 -0700 Subject: Custom claims client api (#1004) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds custom claims API to client * Ammends branch Adds: - Deprecation messages - Fixes auth result keys - Ammends sample app - Adds unit tests * fixes typo switches “to” to “so” --- Example/Auth/Sample/MainViewController.m | 52 +++++++++++- Example/Auth/Tests/FIRUserTests.m | 136 ++++++++++++++++++++++++++++++- 2 files changed, 185 insertions(+), 3 deletions(-) (limited to 'Example') diff --git a/Example/Auth/Sample/MainViewController.m b/Example/Auth/Sample/MainViewController.m index f6893d1..18f1e02 100644 --- a/Example/Auth/Sample/MainViewController.m +++ b/Example/Auth/Sample/MainViewController.m @@ -28,6 +28,7 @@ #import "FIROAuthProvider.h" #import "FIRPhoneAuthCredential.h" #import "FIRPhoneAuthProvider.h" +#import "FIRAuthTokenResult.h" #import "FirebaseAuth.h" #import "CustomTokenDataEntryViewController.h" #import "FacebookAuthProvider.h" @@ -55,6 +56,16 @@ typedef void (^testAutomationCallback)(NSError *_Nullable error); */ static NSString *const kTokenGetButtonText = @"Get Token"; +/** @var kGetTokenResultButtonText + @brief The text of the "Get Token Result" button. + */ +static NSString *const kGetTokenResultButtonText = @"Get Token Result"; + +/** @var kGetTokenResultForceButtonText + @brief The text of the "Force Token Result" button. + */ +static NSString *const kGetTokenResultForceButtonText = @"Force Token Result"; + /** @var kTokenRefreshButtonText @brief The text of the "Refresh Token" button. */ @@ -826,7 +837,11 @@ typedef enum { [StaticContentTableViewCell cellWithTitle:kTokenGetButtonText action:^{ [weakSelf getUserTokenWithForce:NO]; }], [StaticContentTableViewCell cellWithTitle:kTokenRefreshButtonText - action:^{ [weakSelf getUserTokenWithForce:YES]; }] + action:^{ [weakSelf getUserTokenWithForce:YES]; }], + [StaticContentTableViewCell cellWithTitle:kGetTokenResultButtonText + action:^{ [weakSelf getUserTokenResultWithForce:NO]; }], + [StaticContentTableViewCell cellWithTitle:kGetTokenResultForceButtonText + action:^{ [weakSelf getUserTokenResultWithForce:YES]; }], ]], [StaticContentTableViewSection sectionWithTitle:kSectionTitleLinkUnlinkAccounts cells:@[ [StaticContentTableViewCell cellWithTitle:kLinkWithGoogleText @@ -2101,13 +2116,46 @@ static NSDictionary *parseURL(NSString *urlString) { } /** @fn getUserTokenWithForce: - @brief Gets the token from @c FIRUser , optionally a refreshed one. + @brief Gets the token from @c FIRUser, optionally a refreshed one. @param force Whether the refresh is forced or not. */ - (void)getUserTokenWithForce:(BOOL)force { [[self user] getIDTokenForcingRefresh:force completion:[self tokenCallback]]; } +/** @fn getUserTokenResultWithForce: + @brief Gets the token result object from @c FIRUser, optionally a refreshed one. + @param force Whether the refresh is forced or not. + */ +- (void)getUserTokenResultWithForce:(BOOL)force { + + [[self user] getIDTokenResultForcingRefresh:force + completion:^(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + if (error) { + [self showMessagePromptWithTitle:kTokenRefreshErrorAlertTitle + message:error.localizedDescription + showCancelButton:NO + completion:nil]; + [self logFailure:@"refresh token failed" error:error]; + return; + } + [self logSuccess:@"refresh token succeeded."]; + NSMutableString *message = + [[NSMutableString alloc] initWithString: + [NSString stringWithFormat:@"Token : %@\n", tokenResult.token]]; + [message appendString:[NSString stringWithFormat:@"Auth Date : %@\n", tokenResult.authDate]]; + [message appendString: + [NSString stringWithFormat:@"EXP Date : %@\n", tokenResult.expirationDate]]; + [message appendString: + [NSString stringWithFormat:@"Issued Date : %@\n", tokenResult.issuedAtDate]]; + [self showMessagePromptWithTitle:kTokenRefreshedAlertTitle + message:message + showCancelButton:NO + completion:nil]; + }]; +} + /** @fn getAppTokenWithForce: @brief Gets the token from @c FIRApp , optionally a refreshed one. @param force Whether the refresh is forced or not. diff --git a/Example/Auth/Tests/FIRUserTests.m b/Example/Auth/Tests/FIRUserTests.m index 68ee265..be269ee 100644 --- a/Example/Auth/Tests/FIRUserTests.m +++ b/Example/Auth/Tests/FIRUserTests.m @@ -29,6 +29,10 @@ #import "FIRAuthBackend.h" #import "FIRAuthGlobalWorkQueue.h" #import "FIRAuthOperationType.h" +#import "FIRAuthTokenResult.h" +#import "FIRSecureTokenService.h" +#import "FIRSecureTokenRequest.h" +#import "FIRSecureTokenResponse.h" #import "FIRGetAccountInfoRequest.h" #import "FIRGetAccountInfoResponse.h" #import "FIRSetAccountInfoRequest.h" @@ -61,7 +65,16 @@ static NSString *const kAPIKey = @"FAKE_API_KEY"; /** @var kAccessToken @brief The fake access token. */ -static NSString *const kAccessToken = @"ACCESS_TOKEN"; +static NSString *const kAccessToken = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZDBhODBiYzh" + "jM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmItc2EtdXBncm" + "FkZWQiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZCIs" + "InN1YiI6InRlc3Rfc3ViIiwiaWF0IjoxNTIyMzYwNTU3LCJleHAiOjE1MjIzNjQxNTcsImVtYWlsIjoiYXVuaXRlc3R1c2" + "VyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6" + "WyJhdW5pdGVzdHVzZXJAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0=.WFQqSrpVnxx7m" + "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC" + "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN" + "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW" + "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA"; /** @var kNewAccessToken @brief A new value for the fake access token. @@ -233,6 +246,18 @@ static NSTimeInterval const kLastSignInDateTimeIntervalInSeconds = 1505858583; */ static const NSTimeInterval kExpectationTimeout = 2; +/** @extention FIRSecureTokenService + @brief Extends the FIRSecureTokenService class to expose one private method for testing only. + */ +@interface FIRSecureTokenService () + +/** @fn hasValidAccessToken + @brief private method exposed so it can be mocked to prevent the fake expiration date from + affecting the the unit tests. + */ +- (BOOL)hasValidAccessToken; +@end + /** @class FIRUserTests @brief Tests for @c FIRUser . */ @@ -899,6 +924,115 @@ static const NSTimeInterval kExpectationTimeout = 2; OCMVerifyAll(_mockBackend); } +/** @fn testGetIDTokenResultSuccess + @brief Tests the flow of a successful @c getIDTokenResultWithCompletion: call. + */ +- (void)testGetIDTokenResultSuccess { + id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); + OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); + OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); + OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); + OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + id mockSecureTokenService = OCMClassMock([FIRSecureTokenService class]); + OCMStub([mockSecureTokenService hasValidAccessToken]).andReturn(YES); + [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser + completion:^(FIRUser *user) { + [user getIDTokenResultWithCompletion:^(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNil(error); + XCTAssertEqualObjects(tokenResult.token, kAccessToken); + XCTAssertTrue(tokenResult.issuedAtDate && + [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]); + XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]); + XCTAssertTrue(tokenResult.expirationDate && + [tokenResult.expirationDate isKindOfClass:[NSDate class]]); + XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testGetIDTokenResultForcingRefreshSuccess + @brief Tests the flow of a successful @c getIDTokenResultForcingRefresh:completion: call. + */ +- (void)testGetIDTokenResultForcingRefreshSuccess { + id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); + OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); + OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); + OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); + OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser + completion:^(FIRUser *user) { + OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]]) + .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request, + FIRSecureTokenResponseCallback callback) { + XCTAssertEqualObjects(request.APIKey, kAPIKey); + + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + id mockSecureTokenResponse = OCMClassMock([FIRSecureTokenResponse class]); + OCMStub([mockSecureTokenResponse accessToken]).andReturn(kAccessToken); + callback(mockSecureTokenResponse, nil); + }); + }); + [user getIDTokenResultForcingRefresh:YES + completion:^(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNil(error); + XCTAssertEqualObjects(tokenResult.token, kAccessToken); + XCTAssertTrue(tokenResult.issuedAtDate && + [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]); + XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]); + XCTAssertTrue(tokenResult.expirationDate && + [tokenResult.expirationDate isKindOfClass:[NSDate class]]); + XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testGetIDTokenResultForcingRefreshFailure + @brief Tests the flow of a failed @c getIDTokenResultForcingRefresh:completion: call. + */ +- (void)testGetIDTokenResultForcingRefreshFailure { + id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); + OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); + OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); + OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); + OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser + completion:^(FIRUser *user) { + OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]]) + .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request, + FIRSecureTokenResponseCallback callback) { + XCTAssertEqualObjects(request.APIKey, kAPIKey); + + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + + callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]); + }); + }); + [user getIDTokenResultForcingRefresh:YES + completion:^(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNil(tokenResult); + XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + /** @fn testReloadFailure @brief Tests the flow of a failed @c reloadWithCompletion: call. */ -- cgit v1.2.3