123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- /*
- * Copyright 2017 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 "FIRSecureTokenService.h"
-
- #import "FIRAuth.h"
- #import "FIRAuthKeychain.h"
- #import "FIRAuthSerialTaskQueue.h"
- #import "FIRAuthBackend.h"
- #import "FIRAuthRequestConfiguration.h"
- #import "FIRSecureTokenRequest.h"
- #import "FIRSecureTokenResponse.h"
-
- NS_ASSUME_NONNULL_BEGIN
-
- /** @var kAPIKeyCodingKey
- @brief The key used to encode the APIKey for NSSecureCoding.
- */
- static NSString *const kAPIKeyCodingKey = @"APIKey";
-
- /** @var kRefreshTokenKey
- @brief The key used to encode the refresh token for NSSecureCoding.
- */
- static NSString *const kRefreshTokenKey = @"refreshToken";
-
- /** @var kAccessTokenKey
- @brief The key used to encode the access token for NSSecureCoding.
- */
- static NSString *const kAccessTokenKey = @"accessToken";
-
- /** @var kAccessTokenExpirationDateKey
- @brief The key used to encode the access token expiration date for NSSecureCoding.
- */
- static NSString *const kAccessTokenExpirationDateKey = @"accessTokenExpirationDate";
-
- /** @var kFiveMinutes
- @brief Five minutes (in seconds.)
- */
- static const NSTimeInterval kFiveMinutes = 5 * 60;
-
- @interface FIRSecureTokenService ()
- - (instancetype)init NS_DESIGNATED_INITIALIZER;
- @end
-
- @implementation FIRSecureTokenService {
- /** @var _taskQueue
- @brief Used to serialize all requests for access tokens.
- */
- FIRAuthSerialTaskQueue *_taskQueue;
-
- /** @var _authorizationCode
- @brief An authorization code which needs to be exchanged for Secure Token Service tokens.
- */
- NSString *_Nullable _authorizationCode;
-
- /** @var _accessToken
- @brief The currently cached access token. Or |nil| if no token is currently cached.
- */
- NSString *_Nullable _accessToken;
- }
-
- - (instancetype)init {
- self = [super init];
- if (self) {
- _taskQueue = [[FIRAuthSerialTaskQueue alloc] init];
- }
- return self;
- }
-
- - (instancetype)initWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration
- authorizationCode:(NSString *)authorizationCode {
- self = [self init];
- if (self) {
- _requestConfiguration = requestConfiguration;
- _authorizationCode = [authorizationCode copy];
- }
- return self;
- }
-
- - (instancetype)initWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration
- accessToken:(nullable NSString *)accessToken
- accessTokenExpirationDate:(nullable NSDate *)accessTokenExpirationDate
- refreshToken:(NSString *)refreshToken {
- self = [self init];
- if (self) {
- _requestConfiguration = requestConfiguration;
- _accessToken = [accessToken copy];
- _accessTokenExpirationDate = [accessTokenExpirationDate copy];
- _refreshToken = [refreshToken copy];
- }
- return self;
- }
-
- - (void)fetchAccessTokenForcingRefresh:(BOOL)forceRefresh
- callback:(FIRFetchAccessTokenCallback)callback {
- [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock complete) {
- if (!forceRefresh && [self hasValidAccessToken]) {
- complete();
- callback(self->_accessToken, nil, NO);
- } else {
- [self requestAccessToken:^(NSString *_Nullable token,
- NSError *_Nullable error,
- BOOL tokenUpdated) {
- complete();
- callback(token, error, tokenUpdated);
- }];
- }
- }];
- }
-
- - (NSString *)rawAccessToken {
- return _accessToken;
- }
-
- #pragma mark - NSSecureCoding
-
- + (BOOL)supportsSecureCoding {
- return YES;
- }
-
- - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
- NSString *refreshToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:kRefreshTokenKey];
- NSString *accessToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:kAccessTokenKey];
- NSDate *accessTokenExpirationDate =
- [aDecoder decodeObjectOfClass:[NSDate class] forKey:kAccessTokenExpirationDateKey];
- if (!refreshToken) {
- return nil;
- }
- self = [self init];
- if (self) {
- _refreshToken = refreshToken;
- _accessToken = accessToken;
- _accessTokenExpirationDate = accessTokenExpirationDate;
- }
- return self;
- }
-
- - (void)encodeWithCoder:(NSCoder *)aCoder {
- // The API key is encoded even it is not used in decoding to be compatible with previous versions
- // of the library.
- [aCoder encodeObject:_requestConfiguration.APIKey forKey:kAPIKeyCodingKey];
- // Authorization code is not encoded because it is not long-lived.
- [aCoder encodeObject:_refreshToken forKey:kRefreshTokenKey];
- [aCoder encodeObject:_accessToken forKey:kAccessTokenKey];
- [aCoder encodeObject:_accessTokenExpirationDate forKey:kAccessTokenExpirationDateKey];
- }
-
- #pragma mark - Private methods
-
- /** @fn requestAccessToken:
- @brief Makes a request to STS for an access token.
- @details This handles both the case that the token has not been granted yet and that it just
- needs to be refreshed. The caller is responsible for making sure that this is occurring in
- a @c _taskQueue task.
- @param callback Called when the fetch is complete. Invoked asynchronously on the main thread in
- the future.
- @remarks Because this method is guaranteed to only be called from tasks enqueued in
- @c _taskQueue, we do not need any @synchronized guards around access to _accessToken/etc.
- since only one of those tasks is ever running at a time, and those tasks are the only
- access to and mutation of these instance variables.
- */
- - (void)requestAccessToken:(FIRFetchAccessTokenCallback)callback {
- FIRSecureTokenRequest *request;
- if (_refreshToken.length) {
- request = [FIRSecureTokenRequest refreshRequestWithRefreshToken:_refreshToken
- requestConfiguration:_requestConfiguration];
- } else {
- request = [FIRSecureTokenRequest authCodeRequestWithCode:_authorizationCode
- requestConfiguration:_requestConfiguration];
- }
- [FIRAuthBackend secureToken:request
- callback:^(FIRSecureTokenResponse *_Nullable response,
- NSError *_Nullable error) {
- BOOL tokenUpdated = NO;
- NSString *newAccessToken = response.accessToken;
- if (newAccessToken.length && ![newAccessToken isEqualToString:self->_accessToken]) {
- self->_accessToken = [newAccessToken copy];
- self->_accessTokenExpirationDate = response.approximateExpirationDate;
- tokenUpdated = YES;
- }
- NSString *newRefreshToken = response.refreshToken;
- if (newRefreshToken.length &&
- ![newRefreshToken isEqualToString:self->_refreshToken]) {
- self->_refreshToken = [newRefreshToken copy];
- tokenUpdated = YES;
- }
- callback(newAccessToken, error, tokenUpdated);
- }];
- }
-
- - (BOOL)hasValidAccessToken {
- return _accessToken && [_accessTokenExpirationDate timeIntervalSinceNow] > kFiveMinutes;
- }
-
- @end
-
- NS_ASSUME_NONNULL_END
|