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” --- 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 + 6 files changed, 333 insertions(+), 3 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 (limited to 'Firebase/Auth') 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