GIDAuthStateMigrationTest.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <XCTest/XCTest.h>
  15. #import "GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h"
  16. #import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h"
  17. #if TARGET_OS_OSX
  18. #import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h"
  19. #endif // TARGET_OS_OSX
  20. @import GTMAppAuth;
  21. #ifdef SWIFT_PACKAGE
  22. @import AppAuth;
  23. @import OCMock;
  24. #else
  25. #import <AppAuth/AppAuth.h>
  26. #import <OCMock/OCMock.h>
  27. #endif
  28. static NSString *const kTokenURL = @"https://host.com/example/token/url";
  29. static NSString *const kCallbackPath = @"/callback/path";
  30. static NSString *const kKeychainName = @"keychain_name";
  31. static NSString *const kBundleID = @"com.google.GoogleSignInInternalSample.dev";
  32. static NSString *const kClientID =
  33. @"223520599684-kg64hfn0h950oureqacja2fltg00msv3.apps.googleusercontent.com";
  34. static NSString *const kDotReversedClientID =
  35. @"com.googleusercontent.apps.223520599684-kg64hfn0h950oureqacja2fltg00msv3";
  36. static NSString *const kSavedFingerprint = @"com.google.GoogleSignInInternalSample.dev-"
  37. "223520599684-kg64hfn0h950oureqacja2fltg00msv3.apps.googleusercontent.com-email profile";
  38. static NSString *const kSavedFingerprint_HostedDomain =
  39. @"com.google.GoogleSignInInternalSample.dev-"
  40. "223520599684-kg64hfn0h950oureqacja2fltg00msv3.apps.googleusercontent.com-email profile-"
  41. "hd=test.com";
  42. static NSString *const kGTMOAuth2PersistenceString = @"param1=value1&param2=value2";
  43. static NSString *const kAdditionalTokenRequestParametersPostfix = @"~~atrp";
  44. static NSString *const kAdditionalTokenRequestParameters = @"param3=value3&param4=value4";
  45. static NSString *const kFinalPersistenceString =
  46. @"param1=value1&param2=value2&param3=value3&param4=value4";
  47. static NSString *const kRedirectURI =
  48. @"com.googleusercontent.apps.223520599684-kg64hfn0h950oureqacja2fltg00msv3:/callback/path";
  49. static NSString *const kGTMAppAuthMigrationCheckPerformedKey = @"GID_MigrationCheckPerformed";
  50. static NSString *const kDataProtectedMigrationCheckPerformedKey =
  51. @"GID_DataProtectedMigrationCheckPerformed";
  52. static NSString *const kFingerprintService = @"fingerprint";
  53. NS_ASSUME_NONNULL_BEGIN
  54. @interface GIDAuthStateMigration ()
  55. + (nullable NSString *)passwordForService:(NSString *)service;
  56. /// Returns a `GTMAuthSession` given the provided token URL.
  57. ///
  58. /// This method enables using an instance of `GIDAuthStateMigration` that is created with a fake
  59. /// `GTMKeychainStore` and thereby minimizes mocking.
  60. - (nullable GTMAuthSession *)
  61. extractAuthSessionWithTokenURL:(NSURL *)tokenURL callbackPath:(NSString *)callbackPath;
  62. @end
  63. @interface GIDAuthStateMigrationTest : XCTestCase
  64. @end
  65. @implementation GIDAuthStateMigrationTest {
  66. id _mockUserDefaults;
  67. id _mockGTMAppAuthFetcherAuthorization;
  68. id _mockGIDAuthStateMigration;
  69. id _mockGTMKeychainStore;
  70. id _mockKeychainHelper;
  71. id _mockNSBundle;
  72. id _mockGIDSignInCallbackSchemes;
  73. id _mockGTMOAuth2Compatibility;
  74. #if TARGET_OS_OSX
  75. id _realLegacyGTMKeychainStore;
  76. #endif // TARGET_OS_OSX
  77. }
  78. - (void)setUp {
  79. [super setUp];
  80. _mockUserDefaults = OCMClassMock([NSUserDefaults class]);
  81. _mockGTMAppAuthFetcherAuthorization = OCMStrictClassMock([GTMAuthSession class]);
  82. _mockGIDAuthStateMigration = OCMStrictClassMock([GIDAuthStateMigration class]);
  83. _mockGTMKeychainStore = OCMStrictClassMock([GTMKeychainStore class]);
  84. _mockKeychainHelper = OCMProtocolMock(@protocol(GTMKeychainHelper));
  85. _mockNSBundle = OCMStrictClassMock([NSBundle class]);
  86. _mockGIDSignInCallbackSchemes = OCMStrictClassMock([GIDSignInCallbackSchemes class]);
  87. _mockGTMOAuth2Compatibility = OCMStrictClassMock([GTMOAuth2Compatibility class]);
  88. #if TARGET_OS_OSX
  89. GTMKeychainAttribute *fileBasedKeychain = [GTMKeychainAttribute useFileBasedKeychain];
  90. NSSet *attributes = [NSSet setWithArray:@[fileBasedKeychain]];
  91. _realLegacyGTMKeychainStore = [[GTMKeychainStore alloc] initWithItemName:kKeychainName
  92. keychainAttributes:attributes];
  93. #endif // TARGET_OS_OSX
  94. }
  95. - (void)tearDown {
  96. [_mockUserDefaults verify];
  97. [_mockUserDefaults stopMocking];
  98. [_mockGTMAppAuthFetcherAuthorization verify];
  99. [_mockGTMAppAuthFetcherAuthorization stopMocking];
  100. [_mockGIDAuthStateMigration verify];
  101. [_mockGIDAuthStateMigration stopMocking];
  102. [_mockGTMKeychainStore verify];
  103. [_mockGTMKeychainStore stopMocking];
  104. [_mockKeychainHelper verify];
  105. [_mockKeychainHelper stopMocking];
  106. [_mockNSBundle verify];
  107. [_mockNSBundle stopMocking];
  108. [_mockGIDSignInCallbackSchemes verify];
  109. [_mockGIDSignInCallbackSchemes stopMocking];
  110. [_mockGTMOAuth2Compatibility verify];
  111. [_mockGTMOAuth2Compatibility stopMocking];
  112. #if TARGET_OS_OSX
  113. [_realLegacyGTMKeychainStore removeAuthSessionWithError:nil];
  114. #endif // TARGET_OS_OSX
  115. [super tearDown];
  116. }
  117. #pragma mark - Tests
  118. #if TARGET_OS_OSX
  119. - (void)testMigrateIfNeeded_NoPreviousMigration_DataProtectedMigration {
  120. [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
  121. [[[_mockUserDefaults expect] andReturnValue:@NO] boolForKey:kDataProtectedMigrationCheckPerformedKey];
  122. [[_mockUserDefaults expect] setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
  123. [[_mockGTMKeychainStore expect] saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef];
  124. [[[_mockGTMKeychainStore expect] andReturn:kKeychainName] itemName];
  125. // set the auth session that will be migrated
  126. OIDAuthState *authState = [OIDAuthState testInstance];
  127. GTMAuthSession *authSession = [[GTMAuthSession alloc] initWithAuthState:authState];
  128. NSError *err;
  129. [_realLegacyGTMKeychainStore saveAuthSession:authSession error:&err];
  130. XCTAssertNil(err);
  131. GIDAuthStateMigration *migration =
  132. [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
  133. [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
  134. callbackPath:kCallbackPath
  135. isFreshInstall:NO];
  136. // verify that the auth session was removed during migration
  137. XCTAssertNil([_realLegacyGTMKeychainStore retrieveAuthSessionWithError:nil]);
  138. }
  139. - (void)testMigrateIfNeeded_KeychainFailure_DataProtectedMigration {
  140. [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
  141. [[[_mockUserDefaults expect] andReturnValue:@NO] boolForKey:kDataProtectedMigrationCheckPerformedKey];
  142. NSError *keychainSaveError = [NSError new];
  143. OCMStub([_mockGTMKeychainStore saveAuthSession:OCMOCK_ANY error:[OCMArg setTo:keychainSaveError]]);
  144. [[[_mockGTMKeychainStore expect] andReturn:kKeychainName] itemName];
  145. OIDAuthState *authState = [OIDAuthState testInstance];
  146. GTMAuthSession *authSession = [[GTMAuthSession alloc] initWithAuthState:authState];
  147. NSError *err;
  148. [_realLegacyGTMKeychainStore saveAuthSession:authSession error:&err];
  149. XCTAssertNil(err);
  150. GIDAuthStateMigration *migration =
  151. [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
  152. [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
  153. callbackPath:kCallbackPath
  154. isFreshInstall:NO];
  155. XCTAssertNil([_realLegacyGTMKeychainStore retrieveAuthSessionWithError:nil]);
  156. }
  157. #elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  158. - (void)testMigrateIfNeeded_NoPreviousMigration_GTMAppAuthMigration {
  159. [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
  160. [[[_mockUserDefaults expect] andReturnValue:@NO] boolForKey:kGTMAppAuthMigrationCheckPerformedKey];
  161. [[_mockUserDefaults expect] setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
  162. [[_mockGTMKeychainStore expect] saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef];
  163. [self setUpCommonExtractAuthorizationMocksWithFingerPrint:kSavedFingerprint];
  164. GIDAuthStateMigration *migration =
  165. [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
  166. [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
  167. callbackPath:kCallbackPath
  168. isFreshInstall:NO];
  169. }
  170. - (void)testMigrateIfNeeded_KeychainFailure_GTMAppAuthMigration {
  171. [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
  172. [[[_mockUserDefaults expect] andReturnValue:@NO] boolForKey:kGTMAppAuthMigrationCheckPerformedKey];
  173. NSError *keychainSaveError = [NSError new];
  174. OCMStub([_mockGTMKeychainStore saveAuthSession:OCMOCK_ANY error:[OCMArg setTo:keychainSaveError]]);
  175. [self setUpCommonExtractAuthorizationMocksWithFingerPrint:kSavedFingerprint];
  176. GIDAuthStateMigration *migration =
  177. [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
  178. [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
  179. callbackPath:kCallbackPath
  180. isFreshInstall:NO];
  181. }
  182. - (void)testExtractAuthorization {
  183. [self setUpCommonExtractAuthorizationMocksWithFingerPrint:kSavedFingerprint];
  184. GIDAuthStateMigration *migration =
  185. [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
  186. GTMAuthSession *authorization =
  187. [migration extractAuthSessionWithTokenURL:[NSURL URLWithString:kTokenURL]
  188. callbackPath:kCallbackPath];
  189. XCTAssertNotNil(authorization);
  190. }
  191. - (void)testExtractAuthorization_HostedDomain {
  192. [self setUpCommonExtractAuthorizationMocksWithFingerPrint:kSavedFingerprint_HostedDomain];
  193. GIDAuthStateMigration *migration =
  194. [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
  195. GTMAuthSession *authorization =
  196. [migration extractAuthSessionWithTokenURL:[NSURL URLWithString:kTokenURL]
  197. callbackPath:kCallbackPath];
  198. XCTAssertNotNil(authorization);
  199. }
  200. #endif // TARGET_OS_OSX
  201. - (void)testMigrateIfNeeded_HasPreviousMigration {
  202. [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
  203. #if TARGET_OS_OSX
  204. [[[_mockUserDefaults expect] andReturnValue:@YES] boolForKey:kDataProtectedMigrationCheckPerformedKey];
  205. [[_mockUserDefaults reject] setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
  206. #elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  207. [[[_mockUserDefaults expect] andReturnValue:@YES] boolForKey:kGTMAppAuthMigrationCheckPerformedKey];
  208. [[_mockUserDefaults reject] setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
  209. #endif // TARGET_OS_OSX
  210. GIDAuthStateMigration *migration =
  211. [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
  212. [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
  213. callbackPath:kCallbackPath
  214. isFreshInstall:NO];
  215. }
  216. - (void)testMigrateIfNeeded_isFreshInstall {
  217. [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
  218. #if TARGET_OS_OSX
  219. [[_mockUserDefaults expect] setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
  220. #elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  221. [[_mockUserDefaults expect] setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
  222. #endif // TARGET_OS_OSX
  223. GIDAuthStateMigration *migration =
  224. [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
  225. [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
  226. callbackPath:kCallbackPath
  227. isFreshInstall:YES];
  228. }
  229. #pragma mark - Helpers
  230. // Generate the service name for the stored additional token request parameters string.
  231. - (NSString *)additionalTokenRequestParametersKeyFromFingerprint:(NSString *)fingerprint {
  232. return [NSString stringWithFormat:@"%@%@", fingerprint, kAdditionalTokenRequestParametersPostfix];
  233. }
  234. - (void)setUpCommonExtractAuthorizationMocksWithFingerPrint:(NSString *)fingerprint {
  235. [[[_mockGIDAuthStateMigration expect] andReturn:fingerprint]
  236. passwordForService:kFingerprintService];
  237. (void)[[[_mockKeychainHelper expect] andReturn:kGTMOAuth2PersistenceString]
  238. passwordForService:fingerprint error:OCMArg.anyObjectRef];
  239. [[[_mockGTMKeychainStore expect] andReturn:_mockKeychainHelper] keychainHelper];
  240. [[[_mockNSBundle expect] andReturn:_mockNSBundle] mainBundle];
  241. [[[_mockNSBundle expect] andReturn:kBundleID] bundleIdentifier];
  242. [[[_mockGIDSignInCallbackSchemes expect] andReturn:_mockGIDSignInCallbackSchemes] alloc];
  243. (void)[[[_mockGIDSignInCallbackSchemes expect] andReturn:_mockGIDSignInCallbackSchemes]
  244. initWithClientIdentifier:kClientID];
  245. [[[_mockGIDSignInCallbackSchemes expect] andReturn:kDotReversedClientID] clientIdentifierScheme];
  246. [[[_mockGIDAuthStateMigration expect] andReturn:kAdditionalTokenRequestParameters]
  247. passwordForService:[self additionalTokenRequestParametersKeyFromFingerprint:fingerprint]];
  248. }
  249. @end
  250. NS_ASSUME_NONNULL_END