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 ++++++++++++++++++++- Firebase/Auth/Source/FIRAuthTokenResult.m | 110 +++++++++++++++++ Firebase/Auth/Source/FIRAuthTokenResult_Internal.h | 37 ++++++ Firebase/Auth/Source/FIRUser.m | 73 ++++++++++- Firebase/Auth/Source/Public/FIRAuthTokenResult.h | 66 ++++++++++ Firebase/Auth/Source/Public/FIRUser.h | 49 +++++++- Firebase/Auth/Source/Public/FirebaseAuth.h | 1 + 8 files changed, 518 insertions(+), 6 deletions(-) create mode 100644 Firebase/Auth/Source/FIRAuthTokenResult.m create mode 100644 Firebase/Auth/Source/FIRAuthTokenResult_Internal.h create mode 100644 Firebase/Auth/Source/Public/FIRAuthTokenResult.h 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. */ diff --git a/Firebase/Auth/Source/FIRAuthTokenResult.m b/Firebase/Auth/Source/FIRAuthTokenResult.m new file mode 100644 index 0000000..81e9920 --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthTokenResult.m @@ -0,0 +1,110 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthTokenResult_internal.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kExpirationDateKey + @brief The key used to encode the expirationDate property for NSSecureCoding. + */ +static NSString *const kExpirationDateKey = @"expiratinDate"; + +/** @var kTokenKey + @brief The key used to encode the token property for NSSecureCoding. + */ +static NSString *const kTokenKey = @"token"; + +/** @var kAuthDateKey + @brief The key used to encode the authDate property for NSSecureCoding. + */ +static NSString *const kAuthDateKey = @"authDate"; + +/** @var kIssuedDateKey + @brief The key used to encode the issuedDate property for NSSecureCoding. + */ +static NSString *const kIssuedDateKey = @"issuedDate"; + +/** @var kSignInProviderKey + @brief The key used to encode the signInProvider property for NSSecureCoding. + */ +static NSString *const kSignInProviderKey = @"signInProvider"; + +/** @var kClaimsKey + @brief The key used to encode the claims property for NSSecureCoding. + */ +static NSString *const kClaimsKey = @"claims"; + +@implementation FIRAuthTokenResult + +- (instancetype)initWithToken:(NSString *)token + expirationDate:(NSDate *)expirationDate + authDate:(NSDate *)authDate + issuedAtDate:(NSDate *)issuedAtDate + signInProvider:(NSString *)signInProvider + claims:(NSDictionary *)claims { + self = [super init]; + if (self) { + _token = token; + _expirationDate = expirationDate; + _authDate = authDate; + _issuedAtDate = issuedAtDate; + _signInProvider = signInProvider; + _claims = claims; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSString *token = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kTokenKey]; + NSDate *expirationDate = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kExpirationDateKey]; + NSDate *authDate = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kAuthDateKey]; + NSDate *issuedAtDate = + [aDecoder decodeObjectOfClass:[NSDate class] forKey:kAuthDateKey]; + NSString *signInProvider = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kSignInProviderKey]; + NSDictionary *claims = + [aDecoder decodeObjectOfClass:[NSDictionary class] forKey:kClaimsKey]; + + return [self initWithToken:token + expirationDate:expirationDate + authDate:authDate + issuedAtDate:issuedAtDate + signInProvider:signInProvider + claims:claims]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_token forKey:kTokenKey]; + [aCoder encodeObject:_expirationDate forKey:kExpirationDateKey]; + [aCoder encodeObject:_authDate forKey:kAuthDateKey]; + [aCoder encodeObject:_issuedAtDate forKey:kIssuedDateKey]; + [aCoder encodeObject:_signInProvider forKey:kSignInProviderKey]; + [aCoder encodeObject:_claims forKey:kClaimsKey]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRAuthTokenResult_Internal.h b/Firebase/Auth/Source/FIRAuthTokenResult_Internal.h new file mode 100644 index 0000000..2914f2a --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthTokenResult_Internal.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #import + + #import "FIRAuthTokenResult.h" + + NS_ASSUME_NONNULL_BEGIN + +/** @extension FIRAuthAPNSTokenResult + @brief An internal class used to expose internal methods of FIRAuthAPNSTokenResult. + */ +@interface FIRAuthTokenResult () + +- (instancetype)initWithToken:(NSString *)token + expirationDate:(NSDate *)expirationDate + authDate:(NSDate *)authDate + issuedAtDate:(NSDate *)issuedAtDate + signInProvider:(NSString *)signInProvider + claims:(NSDictionary *)claims; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRUser.m b/Firebase/Auth/Source/FIRUser.m index a77db75..fc7930f 100644 --- a/Firebase/Auth/Source/FIRUser.m +++ b/Firebase/Auth/Source/FIRUser.m @@ -29,6 +29,7 @@ #import "FIRAuth_Internal.h" #import "FIRAuthBackend.h" #import "FIRAuthRequestConfiguration.h" +#import "FIRAuthTokenResult_Internal.h" #import "FIRDeleteAccountRequest.h" #import "FIRDeleteAccountResponse.h" #import "FIREmailAuthProvider.h" @@ -784,18 +785,88 @@ static void callInMainThreadWithAuthDataResultAndError( - (void)getIDTokenForcingRefresh:(BOOL)forceRefresh completion:(nullable FIRAuthTokenCallback)completion { + [self getIDTokenResultForcingRefresh:forceRefresh + completion:^(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(tokenResult.token, error); + }); + } + }]; +} + +- (void)getIDTokenResultWithCompletion:(nullable FIRAuthTokenResultCallback)completion { + [self getIDTokenResultForcingRefresh:NO + completion:^(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(tokenResult, error); + }); + } + }]; +} + +- (void)getIDTokenResultForcingRefresh:(BOOL)forceRefresh + completion:(nullable FIRAuthTokenResultCallback)completion { dispatch_async(FIRAuthGlobalWorkQueue(), ^{ [self internalGetTokenForcingRefresh:forceRefresh callback:^(NSString *_Nullable token, NSError *_Nullable error) { + FIRAuthTokenResult *tokenResult; + if (token) { + tokenResult = [self parseIDToken:token error:&error]; + } if (completion) { dispatch_async(dispatch_get_main_queue(), ^{ - completion(token, error); + completion(tokenResult, error); }); } }]; }); } +- (FIRAuthTokenResult *)parseIDToken:(NSString *)token error:(NSError **)error { + error = nil; + NSArray *tokenStringArray = [token componentsSeparatedByString:@"."]; + // The token payload is always the second index of the array. + NSMutableString *tokenPayload = [[NSMutableString alloc] initWithString:tokenStringArray[1]]; + + // Pad the token payload with "=" signs if the payload's length is not a multple of 4. + int remainder = tokenPayload.length % 4 != 0; + if (remainder) { + while (remainder --) { + [tokenPayload appendString:@"="]; + } + } + NSData *decodedTokenPayloadData = + [[NSData alloc] initWithBase64EncodedString:tokenPayload + options:NSDataBase64DecodingIgnoreUnknownCharacters]; + NSDictionary *tokenPayloadDictionary = + [NSJSONSerialization JSONObjectWithData:decodedTokenPayloadData + options:kNilOptions + error:error]; + if (error) { + return nil; + } + + NSDate *expDate = + [NSDate dateWithTimeIntervalSinceNow:[tokenPayloadDictionary[@"exp"] doubleValue]]; + NSDate *authDate = + [NSDate dateWithTimeIntervalSinceNow:[tokenPayloadDictionary[@"auth_time"] doubleValue]]; + NSDate *issuedDate = + [NSDate dateWithTimeIntervalSinceNow:[tokenPayloadDictionary[@"iat"] doubleValue]]; + FIRAuthTokenResult *result = + [[FIRAuthTokenResult alloc] initWithToken:token + expirationDate:expDate + authDate:authDate + issuedAtDate:issuedDate + signInProvider:tokenPayloadDictionary[@"sign_in_provider"] + claims:tokenPayloadDictionary]; + return result; +} + - (void)getTokenForcingRefresh:(BOOL)forceRefresh completion:(nullable FIRAuthTokenCallback)completion { [self getIDTokenForcingRefresh:forceRefresh completion:completion]; diff --git a/Firebase/Auth/Source/Public/FIRAuthTokenResult.h b/Firebase/Auth/Source/Public/FIRAuthTokenResult.h new file mode 100644 index 0000000..11487dc --- /dev/null +++ b/Firebase/Auth/Source/Public/FIRAuthTokenResult.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRAuthTokenResult + @brief A data class containing the ID token JWT string and other properties associated with the + token including the decoded payload claims if available. + */ +NS_SWIFT_NAME(AuthTokenResult) +@interface FIRAuthTokenResult : NSObject + +/** @property token + @brief Stores the JWT string of the ID token. + */ +@property (nonatomic, readonly) NSString *token; + +/** @property expirationDate + @brief Stores the ID token's expiration date. + */ +@property (nonatomic, readonly) NSDate *expirationDate; + +/** @property authDate + @brief Stores the ID token's authentication date. + @remarks This is the date the user was signed in and NOT the date the token was refreshed. + */ +@property (nonatomic, readonly) NSDate *authDate; + +/** @property issuedAtDate + @brief Stores the date that the ID token was issued. + @remarks This is the date last refreshed and NOT the last authentication date. + */ +@property (nonatomic, readonly) NSDate *issuedAtDate; + +/** @property signInProvider + @brief Stores sign-in provider through which the token was obtained. + @remarks This does not necesssarily map to provider IDs. + */ +@property (nonatomic, readonly) NSString *signInProvider; + +/** @property claims + @brief Stores the entire payload of claims found on the ID token. This includes the standard + reserved claims as well as custom claims set by the developer via the Admin SDK. + */ +@property (nonatomic, readonly) NSDictionary *claims; + + + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/Public/FIRUser.h b/Firebase/Auth/Source/Public/FIRUser.h index 0cf872f..7198eac 100644 --- a/Firebase/Auth/Source/Public/FIRUser.h +++ b/Firebase/Auth/Source/Public/FIRUser.h @@ -20,6 +20,7 @@ #import "FIRAuthDataResult.h" #import "FIRUserInfo.h" +@class FIRAuthTokenResult; @class FIRPhoneAuthCredential; @class FIRUserProfileChangeRequest; @class FIRUserMetadata; @@ -39,6 +40,21 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^FIRAuthTokenCallback)(NSString *_Nullable token, NSError *_Nullable error) NS_SWIFT_NAME(AuthTokenCallback); +/** @typedef FIRAuthTokenResultCallback + @brief The type of block called when a token is ready for use. + @see FIRUser.getIDTokenResultWithCompletion: + @see FIRUser.getIDTokenResultForcingRefresh:withCompletion: + + @param tokenResult Optionally; an object containing the raw access token string as well as other + useful data pertaining to the token. + @param error Optionally; the error which occurred - or nil if the request was successful. + + @remarks One of: `token` or `error` will always be non-nil. + */ +typedef void (^FIRAuthTokenResultCallback)(FIRAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) + NS_SWIFT_NAME(AuthTokenResultCallback); + /** @typedef FIRUserProfileChangeCallback @brief The type of block called when a user profile change has finished. @@ -250,6 +266,34 @@ NS_SWIFT_NAME(User) - (void)reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *) credential completion:(nullable FIRAuthDataResultCallback) completion; +/** @fn getIDTokenResultWithCompletion: + @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + + @param completion Optionally; the block invoked when the token is available. Invoked + asynchronously on the main thread in the future. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)getIDTokenResultWithCompletion:(nullable FIRAuthTokenResultCallback)completion + NS_SWIFT_NAME(getIDTokenResult(completion:)); + +/** @fn getIDTokenResultForcingRefresh:completion: + @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + + @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason + other than an expiration. + @param completion Optionally; the block invoked when the token is available. Invoked + asynchronously on the main thread in the future. + + @remarks The authentication token will be refreshed (by making a network request) if it has + expired, or if `forceRefresh` is YES. + + @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. + */ +- (void)getIDTokenResultForcingRefresh:(BOOL)forceRefresh + completion:(nullable FIRAuthTokenResultCallback)completion + NS_SWIFT_NAME(getIDTokenResult(forcingRefresh:completion:)); + /** @fn getIDTokenWithCompletion: @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. @@ -259,7 +303,7 @@ NS_SWIFT_NAME(User) @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. */ - (void)getIDTokenWithCompletion:(nullable FIRAuthTokenCallback)completion - NS_SWIFT_NAME(getIDToken(completion:)); + NS_SWIFT_NAME(getIDToken(completion:)) __attribute__((deprecated)); /** @fn getTokenWithCompletion: @brief Please use `getIDTokenWithCompletion:` instead. @@ -286,7 +330,8 @@ NS_SWIFT_NAME(User) @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods. */ - (void)getIDTokenForcingRefresh:(BOOL)forceRefresh - completion:(nullable FIRAuthTokenCallback)completion; + completion:(nullable FIRAuthTokenCallback)completion + __attribute__((deprecated)); /** @fn getTokenForcingRefresh:completion: @brief Please use getIDTokenForcingRefresh:completion instead. diff --git a/Firebase/Auth/Source/Public/FirebaseAuth.h b/Firebase/Auth/Source/Public/FirebaseAuth.h index 409ac73..cd7e4a4 100644 --- a/Firebase/Auth/Source/Public/FirebaseAuth.h +++ b/Firebase/Auth/Source/Public/FirebaseAuth.h @@ -22,6 +22,7 @@ #import "FIRAuthCredential.h" #import "FIRAuthDataResult.h" #import "FIRAuthErrors.h" +#import "FIRAuthTokenResult.h" #import "FirebaseAuthVersion.h" #import "FIREmailAuthProvider.h" #import "FIRFacebookAuthProvider.h" -- cgit v1.2.3