diff --git a/Projects/AccountSDKIOSWeb/AccountSDKIOSWeb.xcodeproj/project.pbxproj b/Projects/AccountSDKIOSWeb/AccountSDKIOSWeb.xcodeproj/project.pbxproj index 34a687cb..75dabe6b 100644 --- a/Projects/AccountSDKIOSWeb/AccountSDKIOSWeb.xcodeproj/project.pbxproj +++ b/Projects/AccountSDKIOSWeb/AccountSDKIOSWeb.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -44,9 +44,6 @@ 2489C9B026733A0900AA0A4C /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2489C98E26733A0900AA0A4C /* Storage.swift */; }; 2489C9B126733A0900AA0A4C /* StateStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2489C98F26733A0900AA0A4C /* StateStorage.swift */; }; 2489C9B226733A0900AA0A4C /* UserDefaultsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2489C99026733A0900AA0A4C /* UserDefaultsStorage.swift */; }; - 2489C9B326733A0900AA0A4C /* LegacyKeychainSessionStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2489C99326733A0900AA0A4C /* LegacyKeychainSessionStorage.swift */; }; - 2489C9B426733A0900AA0A4C /* MigratingKeychainCompatStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2489C99426733A0900AA0A4C /* MigratingKeychainCompatStorage.swift */; }; - 2489C9B526733A0900AA0A4C /* LegacyKeychainTokenStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2489C99526733A0900AA0A4C /* LegacyKeychainTokenStorage.swift */; }; 2489C9B726733A0900AA0A4C /* KeychainSessionStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2489C99726733A0900AA0A4C /* KeychainSessionStorage.swift */; }; 2489C9B826733A0900AA0A4C /* SessionStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2489C99826733A0900AA0A4C /* SessionStorage.swift */; }; 2489C9B926733A0900AA0A4C /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2489C99926733A0900AA0A4C /* URLExtensions.swift */; }; @@ -93,7 +90,6 @@ CE3E98962782E6C800B2F403 /* KeyWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3E98952782E6C800B2F403 /* KeyWindow.swift */; }; CE45FE0027C6355300DBD8FC /* MFATypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE45FDFF27C6355300DBD8FC /* MFATypeTests.swift */; }; CE5225D42747D6C9009E015E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE5225CD2747D69A009E015E /* Assets.xcassets */; }; - CE5E5B472735416000E18EA7 /* LegacyKeychainTokenStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_40 /* LegacyKeychainTokenStorageTests.swift */; }; CE5FA22B284DDAE800792C12 /* FontManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5FA22A284DDAE800792C12 /* FontManager.swift */; }; CE5FA230284DED5500792C12 /* Inter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5FA22F284DED5500792C12 /* Inter.swift */; }; CE699DD02732A2CC009EE30D /* KeychainStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE699DCF2732A2CC009EE30D /* KeychainStorageMock.swift */; }; @@ -126,8 +122,6 @@ OBJ_251 /* JOSEUtilTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_50 /* JOSEUtilTests.swift */; }; OBJ_252 /* JWKSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_51 /* JWKSTests.swift */; }; OBJ_253 /* JOSEUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_52 /* JOSEUtil.swift */; }; - OBJ_254 /* LegacyKeychainSessionStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_55 /* LegacyKeychainSessionStorageTests.swift */; }; - OBJ_255 /* MigratingKeychainCompatStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_56 /* MigratingKeychainCompatStorageTests.swift */; }; OBJ_256 /* StateStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_57 /* StateStorageTests.swift */; }; OBJ_257 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* UserTests.swift */; }; OBJ_260 /* AccountSDKIOSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "AccountSDKIOSWeb::AccountSDKIOSWeb::Product" /* AccountSDKIOSWeb.framework */; }; @@ -195,9 +189,6 @@ 2489C98E26733A0900AA0A4C /* Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; 2489C98F26733A0900AA0A4C /* StateStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateStorage.swift; sourceTree = ""; }; 2489C99026733A0900AA0A4C /* UserDefaultsStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsStorage.swift; sourceTree = ""; }; - 2489C99326733A0900AA0A4C /* LegacyKeychainSessionStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyKeychainSessionStorage.swift; sourceTree = ""; }; - 2489C99426733A0900AA0A4C /* MigratingKeychainCompatStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigratingKeychainCompatStorage.swift; sourceTree = ""; }; - 2489C99526733A0900AA0A4C /* LegacyKeychainTokenStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyKeychainTokenStorage.swift; sourceTree = ""; }; 2489C99726733A0900AA0A4C /* KeychainSessionStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainSessionStorage.swift; sourceTree = ""; }; 2489C99826733A0900AA0A4C /* SessionStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionStorage.swift; sourceTree = ""; }; 2489C99926733A0900AA0A4C /* URLExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLExtensions.swift; sourceTree = ""; }; @@ -275,7 +266,6 @@ CEFF10EF284E01E5006ABAB4 /* UIFont+Custom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIFont+Custom.swift"; sourceTree = ""; }; OBJ_178 /* cuckoo */ = {isa = PBXFileReference; lastKnownFileType = folder; path = cuckoo; sourceTree = SOURCE_ROOT; }; OBJ_182 /* generate_mocks.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = generate_mocks.sh; sourceTree = ""; }; - OBJ_40 /* LegacyKeychainTokenStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyKeychainTokenStorageTests.swift; sourceTree = ""; }; OBJ_42 /* Await.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Await.swift; sourceTree = ""; }; OBJ_43 /* ClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTests.swift; sourceTree = ""; }; OBJ_44 /* Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; @@ -284,8 +274,6 @@ OBJ_50 /* JOSEUtilTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JOSEUtilTests.swift; sourceTree = ""; }; OBJ_51 /* JWKSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKSTests.swift; sourceTree = ""; }; OBJ_52 /* JOSEUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JOSEUtil.swift; sourceTree = ""; }; - OBJ_55 /* LegacyKeychainSessionStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyKeychainSessionStorageTests.swift; sourceTree = ""; }; - OBJ_56 /* MigratingKeychainCompatStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigratingKeychainCompatStorageTests.swift; sourceTree = ""; }; OBJ_57 /* StateStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStorageTests.swift; sourceTree = ""; }; OBJ_58 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -556,7 +544,6 @@ 2489C99126733A0900AA0A4C /* Keychain */ = { isa = PBXGroup; children = ( - 2489C99226733A0900AA0A4C /* Compat */, 2489C99726733A0900AA0A4C /* KeychainSessionStorage.swift */, CEC8D35C27329F830002145D /* KeychainStorage.swift */, CE6AB47C276A2A7300E11A16 /* SharedKeychainSessionStorageFactory.swift */, @@ -564,16 +551,6 @@ path = Keychain; sourceTree = ""; }; - 2489C99226733A0900AA0A4C /* Compat */ = { - isa = PBXGroup; - children = ( - 2489C99326733A0900AA0A4C /* LegacyKeychainSessionStorage.swift */, - 2489C99426733A0900AA0A4C /* MigratingKeychainCompatStorage.swift */, - 2489C99526733A0900AA0A4C /* LegacyKeychainTokenStorage.swift */, - ); - path = Compat; - sourceTree = ""; - }; 2489C9A126733A0900AA0A4C /* API */ = { isa = PBXGroup; children = ( @@ -834,23 +811,12 @@ OBJ_53 /* Storage */ = { isa = PBXGroup; children = ( - OBJ_54 /* Compat */, OBJ_57 /* StateStorageTests.swift */, 244EAACB275A2E96008A1285 /* SessionStorageTests.swift */, ); path = Storage; sourceTree = ""; }; - OBJ_54 /* Compat */ = { - isa = PBXGroup; - children = ( - OBJ_40 /* LegacyKeychainTokenStorageTests.swift */, - OBJ_55 /* LegacyKeychainSessionStorageTests.swift */, - OBJ_56 /* MigratingKeychainCompatStorageTests.swift */, - ); - path = Compat; - sourceTree = ""; - }; OBJ_7 /* Sources */ = { isa = PBXGroup; children = ( @@ -1046,6 +1012,7 @@ /* Begin PBXShellScriptBuildPhase section */ CE754A5527102F6D000F2EA9 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -1065,6 +1032,7 @@ }; CE754A58271030DF000F2EA9 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -1083,6 +1051,7 @@ }; CE9B31A52809873B001903B0 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -1161,7 +1130,6 @@ 2489C9BC26733A0900AA0A4C /* RetryPolicy.swift in Sources */, 2472EA2F275E98C50037433D /* DataModels.swift in Sources */, CEB3F82827295F4F009F440C /* AuthenticatedURLSession.swift in Sources */, - 2489C9B426733A0900AA0A4C /* MigratingKeychainCompatStorage.swift in Sources */, CE99896A270EFE0800CB9E95 /* KeychainStorageError.swift in Sources */, 246B035A27071E6E0041F15A /* Client+AppTransfer.swift in Sources */, CE6AB47D276A2A7300E11A16 /* SharedKeychainSessionStorageFactory.swift in Sources */, @@ -1172,7 +1140,6 @@ CEFF10F0284E01E5006ABAB4 /* UIFont+Custom.swift in Sources */, 24EB1506267A0E44002D9628 /* APIRetryPolicy.swift in Sources */, 248440172692E97700CE88F2 /* ClientConfiguration.swift in Sources */, - 2489C9B326733A0900AA0A4C /* LegacyKeychainSessionStorage.swift in Sources */, 24EB1512267A10C7002D9628 /* InternalSchibstedAccountAPIResponses.swift in Sources */, 2489C9A526733A0900AA0A4C /* TokenHandler.swift in Sources */, CE3E98962782E6C800B2F403 /* KeyWindow.swift in Sources */, @@ -1181,7 +1148,6 @@ 24EB1526267A17CC002D9628 /* HTTPError.swift in Sources */, 24EB156F267A2482002D9628 /* IdTokenValidator.swift in Sources */, 24209499273D026C004CF03D /* LinksView.swift in Sources */, - 2489C9B526733A0900AA0A4C /* LegacyKeychainTokenStorage.swift in Sources */, 2489C9B226733A0900AA0A4C /* UserDefaultsStorage.swift in Sources */, 2449DD312677527F00FE7197 /* URLBuilder+AuthorizationRequest.swift in Sources */, 2489C9AB26733A0900AA0A4C /* Version.swift in Sources */, @@ -1222,7 +1188,6 @@ OBJ_253 /* JOSEUtil.swift in Sources */, 244EAAD0275A39A7008A1285 /* MockSessionStorageProtocol.swift in Sources */, CED185E42785A492001FB0F6 /* UIWindow+VisibleVCTests.swift in Sources */, - CE5E5B472735416000E18EA7 /* LegacyKeychainTokenStorageTests.swift in Sources */, A95F1A0025826A59000D19F5 /* HTTPClientWithURLSessionTests.swift in Sources */, CED185E82785AD5A001FB0F6 /* SimplifiedLoginViewModelTests.swift in Sources */, 24BF4C9E27282333008F11F3 /* TokenRefreshRequestHandlerTests.swift in Sources */, @@ -1231,9 +1196,7 @@ A93EFCFE26316CFB008200D1 /* SchibstedAccountAPIResponsesTests.swift in Sources */, CE45FE0027C6355300DBD8FC /* MFATypeTests.swift in Sources */, CED92B7B272C15D800572645 /* MockURLProtocol.swift in Sources */, - OBJ_254 /* LegacyKeychainSessionStorageTests.swift in Sources */, 2473CBA02678AED60001B861 /* URLBuilderTests.swift in Sources */, - OBJ_255 /* MigratingKeychainCompatStorageTests.swift in Sources */, OBJ_256 /* StateStorageTests.swift in Sources */, A93EFCD42630131F008200D1 /* SchibstedAccountAPITests.swift in Sources */, A95F1A2025826AED000D19F5 /* IdTokenClaimsTests.swift in Sources */, diff --git a/Projects/AccountSDKIOSWeb/Tests/AccountSDKIOSWebTests/Storage/Compat/LegacyKeychainSessionStorageTests.swift b/Projects/AccountSDKIOSWeb/Tests/AccountSDKIOSWebTests/Storage/Compat/LegacyKeychainSessionStorageTests.swift deleted file mode 100644 index 4dc05915..00000000 --- a/Projects/AccountSDKIOSWeb/Tests/AccountSDKIOSWebTests/Storage/Compat/LegacyKeychainSessionStorageTests.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright © 2022 Schibsted. -// Licensed under the terms of the MIT license. See LICENSE in the project root. -// - -import XCTest -import Cuckoo -@testable import AccountSDKIOSWeb - -final class LegacyKeychainSessionStorageTests: XCTestCase { - private static var jwsUtil: JWSUtil! - - override class func setUp() { - jwsUtil = JWSUtil() - } - - func testGetWithoutLegacyTokenData() { - let mockTokenStorage = MockLegacyKeychainTokenStorage() - stub(mockTokenStorage) { mock in - when(mock.get()).thenReturn([]) - } - - XCTAssertNil(LegacyKeychainSessionStorage(storage: mockTokenStorage).get(forClientId: "testClient")) - } - - func testGetLegacyTokenDataMappedToUserSession() throws { - let clientId = "client1" - let legacyTokenData = try createLegacyTokenData(clientId: clientId) - - let mockTokenStorage = MockLegacyKeychainTokenStorage() - stub(mockTokenStorage) { mock in - when(mock.get()).thenReturn([legacyTokenData]) - } - - let result = LegacyKeychainSessionStorage(storage: mockTokenStorage).get(forClientId: clientId) - XCTAssertEqual(result?.clientId, clientId) - XCTAssertEqual(result?.accessToken, legacyTokenData.accessToken) - XCTAssertEqual(result?.refreshToken, legacyTokenData.refreshToken) - } - - func testGetReturnsNewestTokens() throws { - let clientId = "client1" - let oldestTokenData = try createLegacyTokenData(clientId: clientId) - let newestTokenData = try createLegacyTokenData(clientId: clientId) - - let mockTokenStorage = MockLegacyKeychainTokenStorage() - stub(mockTokenStorage) { mock in - when(mock.get()).thenReturn([oldestTokenData, newestTokenData]) - } - - let result = LegacyKeychainSessionStorage(storage: mockTokenStorage).get(forClientId: clientId) - XCTAssertEqual(result?.clientId, clientId) - XCTAssertEqual(result?.accessToken, newestTokenData.accessToken) - XCTAssertEqual(result?.refreshToken, newestTokenData.refreshToken) - } - - func testGetDiscardsTokenForOtherClient() throws { - let legacyTokenData = try createLegacyTokenData(clientId: "client1") - let mockTokenStorage = MockLegacyKeychainTokenStorage() - stub(mockTokenStorage) { mock in - when(mock.get()).thenReturn([legacyTokenData]) - } - - XCTAssertNil(LegacyKeychainSessionStorage(storage: mockTokenStorage).get(forClientId: "otherClient")) - } - - private func createLegacyTokenData(clientId: String) throws -> LegacyTokenData { - let accessTokenData = try JSONSerialization.data(withJSONObject: ["client_id": clientId]) - let accessToken = LegacyKeychainSessionStorageTests.jwsUtil.createJWS(payload: accessTokenData, keyId: "testKeyId") - - return LegacyTokenData(accessToken: accessToken, refreshToken: "refreshToken1") - } -} diff --git a/Projects/AccountSDKIOSWeb/Tests/AccountSDKIOSWebTests/Storage/Compat/LegacyKeychainTokenStorageTests.swift b/Projects/AccountSDKIOSWeb/Tests/AccountSDKIOSWebTests/Storage/Compat/LegacyKeychainTokenStorageTests.swift deleted file mode 100644 index c142bd82..00000000 --- a/Projects/AccountSDKIOSWeb/Tests/AccountSDKIOSWebTests/Storage/Compat/LegacyKeychainTokenStorageTests.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright © 2022 Schibsted. -// Licensed under the terms of the MIT license. See LICENSE in the project root. -// - -import XCTest -@testable import AccountSDKIOSWeb - -final class LegacyKeychainTokenStorageTests: XCTestCase { - - var keychainMock: KeychainStoring? - let accountString = "SchibstedID" - - override func setUp() { - self.keychainMock = KeychainStorageMock() - } - - func testNoExistingLegacyData() { - let keychainStorage = LegacyKeychainTokenStorage(keychain: keychainMock!) - XCTAssertEqual(keychainStorage.get(), []) - } - - func testIncorrectlySerialisedData() { - let testData = "invalid_data".data(using: .utf8) - try? self.keychainMock!.setValue(testData!, forAccount: nil, accessGroup: nil) - XCTAssertEqual(self.keychainMock!.getAll().count, 1) - - let keychainStorage = LegacyKeychainTokenStorage(keychain: keychainMock!) - XCTAssertEqual(keychainStorage.get(), []) - } - - func testSerialisedInvalidData() { - let testData = try? NSKeyedArchiver.archivedData(withRootObject: ["key": "value"], requiringSecureCoding: false) - try? self.keychainMock!.setValue(testData!, forAccount: nil, accessGroup: nil) - XCTAssertEqual(self.keychainMock!.getAll().count, 1) - - let keychainStorage = LegacyKeychainTokenStorage(keychain: keychainMock!) - XCTAssertEqual(keychainStorage.get(), []) - } - - func testExistingLegacyData() { - let tokens = [ - "accessToken1": [ - "refresh_token": "refreshToken1", - "id_token": "idToken1" - ], - "accessToken2": [ - "refresh_token": "refreshToken2", - "id_token": "idToken2" - ] - ] - let data = try? NSKeyedArchiver.archivedData(withRootObject: ["logged_in_users": tokens], requiringSecureCoding: false) - try? self.keychainMock!.setValue(data!, forAccount: accountString, accessGroup: nil) - XCTAssertEqual(self.keychainMock!.getAll().count, 1) - - let tokenDataArray = LegacyKeychainTokenStorage(keychain: keychainMock!).get() - XCTAssertEqual(tokenDataArray.count, 2) - XCTAssertTrue(tokenDataArray.contains(LegacyTokenData(accessToken: "accessToken2", refreshToken: "refreshToken2"))) - XCTAssertTrue(tokenDataArray.contains(LegacyTokenData(accessToken: "accessToken1", refreshToken: "refreshToken1"))) - } - - func testExistingLegacyDataWithoutRefreshTokenIsIgnored() { - let tokens = [ - "accessToken1": [ - "refresh_token": "refreshToken1", - "id_token": "idToken1" - ], - "accessToken2": [ - "id_token": "idToken2" - ] - ] - let data = try? NSKeyedArchiver.archivedData(withRootObject: ["logged_in_users": tokens], requiringSecureCoding: false) - try? self.keychainMock!.setValue(data!, forAccount: accountString, accessGroup: nil) - XCTAssertEqual(self.keychainMock!.getAll().count, 1) - - XCTAssertEqual(LegacyKeychainTokenStorage(keychain: keychainMock!).get(), [ - LegacyTokenData(accessToken: "accessToken1", refreshToken: "refreshToken1") - ]) - } - - func testRemove() { - let tokens = [ - "accessToken1": [ - "refresh_token": "refreshToken1", - "id_token": "idToken1" - ] - ] - let data = try? NSKeyedArchiver.archivedData(withRootObject: ["logged_in_users": tokens], requiringSecureCoding: false) - try? self.keychainMock!.setValue(data!, forAccount: accountString, accessGroup: nil) - XCTAssertEqual(self.keychainMock!.getAll().count, 1) - - let keychainStorage = LegacyKeychainTokenStorage(keychain: keychainMock!) - XCTAssertEqual(keychainStorage.get(), [ - LegacyTokenData(accessToken: "accessToken1", refreshToken: "refreshToken1") - ]) - keychainStorage.remove() - XCTAssertEqual(keychainStorage.get(), []) - } - - func testSettingLegacyToken() { - - let tokenDictionary: [String : Any] = [ - "accessToken": "foo", - "refreshToken": "bar", - "userID": "foobar" - ] - - guard let data = try? JSONSerialization.data(withJSONObject: tokenDictionary, options: []) else { - XCTFail("Cannot serialize test input") - return - } - - let keychainStorage = LegacyKeychainTokenStorage(keychain: keychainMock!) - do { - try keychainStorage.set(legacySDKtokenData: data) - } catch (let error) { - XCTFail("Cannot set legacy token data in keychain \(error.localizedDescription)") - } - - XCTAssertEqual(keychainStorage.get(), [ - LegacyTokenData(accessToken: "foo", refreshToken: "bar") - ]) - } -} diff --git a/Projects/AccountSDKIOSWeb/Tests/AccountSDKIOSWebTests/Storage/Compat/MigratingKeychainCompatStorageTests.swift b/Projects/AccountSDKIOSWeb/Tests/AccountSDKIOSWebTests/Storage/Compat/MigratingKeychainCompatStorageTests.swift deleted file mode 100644 index 620055fd..00000000 --- a/Projects/AccountSDKIOSWeb/Tests/AccountSDKIOSWebTests/Storage/Compat/MigratingKeychainCompatStorageTests.swift +++ /dev/null @@ -1,318 +0,0 @@ -// -// Copyright © 2022 Schibsted. -// Licensed under the terms of the MIT license. See LICENSE in the project root. -// - -import XCTest -import Cuckoo -@testable import AccountSDKIOSWeb - -final class MigratingKeychainCompatStorageTests: XCTestCase { - func testStoreOnlyWritesToNewStorage() { - let userSession = UserSession(clientId: "client1", userTokens: Fixtures.userTokens, updatedAt: Date()) - - let legacyStorage = MockLegacyKeychainSessionStorage() - let newStorage = MockKeychainSessionStorage(service: "test") - stub(newStorage) { mock in - when(mock.store(equal(to: userSession), accessGroup: any(), completion: anyClosure())).then { _, _, completion in - completion(.success()) - } - } - - let migratingStorage = MigratingKeychainCompatStorage(from: legacyStorage, - to: newStorage, - legacyClient: Client(configuration: Fixtures.clientConfig), - legacyClientSecret: "", - makeTokenRequest: { _, _, _ in }) - migratingStorage.store(userSession, accessGroup: nil) { _ in } - - verify(newStorage).store(equal(to: userSession), accessGroup: any(), completion: anyClosure()) - verifyNoMoreInteractions(legacyStorage) - } - - func testGetAllOnlyReadsNewStorage() { - let userSession = UserSession(clientId: "client1", userTokens: Fixtures.userTokens, updatedAt: Date()) - - let legacyStorage = MockLegacyKeychainSessionStorage() - let newStorage = MockKeychainSessionStorage(service: "test") - stub(newStorage) { mock in - when(mock.getAll()).thenReturn([userSession]) - } - - let migratingStorage = MigratingKeychainCompatStorage(from: legacyStorage, - to: newStorage, - legacyClient: Client(configuration: Fixtures.clientConfig), - legacyClientSecret: "", - makeTokenRequest: { _, _, _ in }) - _ = migratingStorage.getAll() - - verify(newStorage).getAll() - verifyNoMoreInteractions(legacyStorage) - } - - func testRemoveFromBothStorages() { - let clientId = "client1" - - let legacyStorage = MockLegacyKeychainSessionStorage() - let newStorage = MockKeychainSessionStorage(service: "test") - stub(newStorage) { mock in - when(mock.remove(forClientId: equal(to: clientId))).thenDoNothing() - } - stub(legacyStorage) { mock in - when(mock.remove()).thenDoNothing() - } - - let migratingStorage = MigratingKeychainCompatStorage(from: legacyStorage, to: newStorage, - legacyClient: Client(configuration: Fixtures.clientConfig), - legacyClientSecret: "", - makeTokenRequest: { _, _, _ in }) - migratingStorage.remove(forClientId: clientId) - - verify(newStorage).remove(forClientId: equal(to: clientId)) - verify(legacyStorage).remove() - verifyNoMoreInteractions(legacyStorage) - } - - func testGetPrefersNewStorage() { - let clientId = "client1" - let userSession = UserSession(clientId: clientId, userTokens: Fixtures.userTokens, updatedAt: Date()) - - let legacyStorage = MockLegacyKeychainSessionStorage() - let newStorage = MockKeychainSessionStorage(service: "test") - - stub(newStorage) { mock in - when(mock.get(forClientId: clientId, completion: anyClosure())) - .then{ clientId, completion in - completion(userSession) - } - } - - let migratingStorage = MigratingKeychainCompatStorage(from: legacyStorage, to: newStorage, - legacyClient: Client(configuration: Fixtures.clientConfig), - legacyClientSecret: "", - makeTokenRequest: { _, _, _ in }) - - migratingStorage.get(forClientId: clientId) { retrievedUserSession in - XCTAssertEqual(retrievedUserSession, userSession) - } - - verify(newStorage).get(forClientId: clientId, completion: anyClosure()) - verifyNoMoreInteractions(legacyStorage) - } - - func testGetMigratesExistingLegacySession() { - let clientId = "client1" - let legacyUserSession = LegacyUserSession(clientId: clientId, accessToken: "access-token", refreshToken: "refresh-token", updatedAt: Date()) - - let legacyStorage = MockLegacyKeychainSessionStorage() - stub(legacyStorage) { mock in - when(mock.get(forClientId: equal(to: clientId))).thenReturn(legacyUserSession) - when(mock.remove()).thenDoNothing() - } - let newStorage = MockKeychainSessionStorage(service: "test") - stub(newStorage) { mock in - when(mock.get(forClientId: equal(to: clientId), completion: anyClosure())) - .then{ _, completion in - completion(nil) - } - when(mock.store(any(), accessGroup: "", completion: anyClosure())).thenDoNothing() - } - - let migratingStorage = MigratingKeychainCompatStorage(from: legacyStorage, to: newStorage, - legacyClient: Client(configuration: Fixtures.clientConfig), - legacyClientSecret: "", - makeTokenRequest: { _, _, _ in }) - - migratingStorage.get(forClientId: clientId) { retrievedUserSession in - XCTAssertEqual(retrievedUserSession, nil) - } - - // TODO: Fixup on untangling User and Client -// verify(newStorage).get(forClientId: equal(to: clientId), completion: anyClosure()) -// verify(legacyStorage).get(forClientId: equal(to: clientId)) -// verify(newStorage).store(equal(to: legacyUserSession)) -// verify(legacyStorage).remove() - } - - func testGetReturnsNilIfNoSessionExists() { - let clientId = "client1" - - let legacyStorage = MockLegacyKeychainSessionStorage() - stub(legacyStorage) { mock in - when(mock.get(forClientId: equal(to: clientId))).thenReturn(nil) - } - let newStorage = MockKeychainSessionStorage(service: "test") - stub(newStorage) { mock in - when(mock.get(forClientId: clientId, completion: anyClosure())) - .then{ _, completion in completion(nil) } - } - - let migratingStorage = MigratingKeychainCompatStorage(from: legacyStorage, to: newStorage, - legacyClient: Client(configuration: Fixtures.clientConfig), - legacyClientSecret: "", - makeTokenRequest: { _, _, _ in }) - - migratingStorage.get(forClientId: clientId) { retrievedUserSession in - XCTAssertNil(retrievedUserSession) - } - - verify(newStorage).get(forClientId: equal(to: clientId), completion: anyClosure()) - verify(legacyStorage).get(forClientId: clientId) - } -} - -final class OldSDKClientTests: XCTestCase { - - func testOneTimeCodeWithRefreshOnCodeExchangeFailure401() throws { - let expectedCode = "A code string" - let expectedResponse = SchibstedAccountAPIResponse(data: CodeExchangeResponse(code: expectedCode)) - let mockHTTPClient = MockHTTPClient() - stub(mockHTTPClient) {mock in - when(mock.execute(request: any(), withRetryPolicy: any(), completion: anyClosure())) - .then { _, _, completion in - completion(.success(expectedResponse)) - } - } - - let expectedTokenRefreshResponse = TokenResponse(accessToken: Fixtures.userTokens.accessToken, - refreshToken: nil, - idToken: nil, - scope: nil, - expiresIn: 1337) - let mockApi = MockSchibstedAccountAPI(baseURL: Fixtures.clientConfig.serverURL, sessionServiceURL: Fixtures.clientConfig.sessionServiceURL) - var codeExchangeCallCount = 0 - stub(mockApi) { mock in - when(mock.oldSDKCodeExchange(with: any(), clientId: any(), oldSDKAccessToken: any(), completion: anyClosure())) - .then{ _, _, _, completion in - codeExchangeCallCount += 1 - completion(.failure(HTTPError.errorResponse(code: 401, body: nil))) - } - when(mock.oldSDKRefresh(with: any(), refreshToken: any(), clientId: any(), clientSecret: any(), completion: anyClosure())) - .then { _, _, _, _, completion in - completion(.success(expectedTokenRefreshResponse)) - } - } - - - let sut = OldSDKClient(clientId: "", clientSecret: "", api: mockApi, legacyAccessToken: "foo", legacyRefreshToken: "bar", httpClient: mockHTTPClient) - Await.until { done in - sut.oneTimeCodeWithOldSDKRefresh(newSDKClientId: "") { result in - switch result { - case .failure(.errorResponse(let errorCode, _)): - XCTAssertEqual(codeExchangeCallCount, 2, "Code Exchange should only be called 2 times on 401 failure.") - XCTAssertEqual(401, errorCode) - default: - XCTFail("Unexpected result \(result)") - } - - done() - } - } - } - - func testOneTimeCodeWithRefreshOnCodeExchangeSuccess() throws { - let expectedCode = "A code string" - let expectedResponse = SchibstedAccountAPIResponse(data: CodeExchangeResponse(code: expectedCode)) - let mockHTTPClient = MockHTTPClient() - stub(mockHTTPClient) {mock in - when(mock.execute(request: any(), withRetryPolicy: any(), completion: anyClosure())) - .then { _, _, completion in - completion(.success(expectedResponse)) - } - } - - let expectedTokenRefreshResponse = TokenResponse(accessToken: Fixtures.userTokens.accessToken, - refreshToken: nil, - idToken: nil, - scope: nil, - expiresIn: 1337) - let mockApi = MockSchibstedAccountAPI(baseURL: Fixtures.clientConfig.serverURL, sessionServiceURL: Fixtures.clientConfig.sessionServiceURL) - var codeExchangeCallCount = 0 - stub(mockApi) { mock in - when(mock.oldSDKCodeExchange(with: any(), clientId: any(), oldSDKAccessToken: any(), completion: anyClosure())) - .then{ _, _, _, completion in - codeExchangeCallCount += 1 - completion(.success(expectedResponse)) - } - when(mock.oldSDKRefresh(with: any(), refreshToken: any(), clientId: any(), clientSecret: any(), completion: anyClosure())) - .then { _, _, _, _, completion in - completion(.success(expectedTokenRefreshResponse)) - } - } - - - let sut = OldSDKClient(clientId: "", clientSecret: "", api: mockApi, legacyAccessToken: "foo", legacyRefreshToken: "bar", httpClient: mockHTTPClient) - Await.until { done in - sut.oneTimeCodeWithOldSDKRefresh(newSDKClientId: "") { result in - switch result { - case .success(let code): - XCTAssertEqual(codeExchangeCallCount, 1, "Code Exchange should only be called once.") - XCTAssertEqual(expectedCode, code) - default: - XCTFail("Unexpected result \(result)") - } - - done() - } - } - } - - func testOLDSDKRefresh() throws { - let expectedResponse = TokenResponse(accessToken: Fixtures.userTokens.accessToken, - refreshToken: nil, - idToken: nil, - scope: nil, - expiresIn: 1337) - - let mockHTTPClient = MockHTTPClient() - stub(mockHTTPClient) {mock in - when(mock.execute(request: any(), withRetryPolicy: any(), completion: anyClosure())) - .then { _, _, completion in - completion(.success(expectedResponse)) - } - } - - let api = Fixtures.schibstedAccountAPI - let sut = OldSDKClient(clientId: "", clientSecret: "", api: api, legacyAccessToken: "accessToken", legacyRefreshToken: "foo", httpClient: mockHTTPClient) - - Await.until { done in - sut.oldSDKRefresh(refreshToken: "") { result in - switch result { - case .success(let refreshedToken): - XCTAssertEqual(refreshedToken, "accessToken") - default: - XCTFail("Unexpected result \(result)") - } - done() - } - } - } - - func testOneTimeCode() throws { - let expectedCode = "A code string" - let expectedResponse = SchibstedAccountAPIResponse(data: CodeExchangeResponse(code: expectedCode)) - - let mockHTTPClient = MockHTTPClient() - stub(mockHTTPClient) {mock in - when(mock.execute(request: any(), withRetryPolicy: any(), completion: anyClosure())) - .then { _, _, completion in - completion(.success(expectedResponse)) - } - } - - let api = Fixtures.schibstedAccountAPI - let sut = OldSDKClient(clientId: "", clientSecret: "", api: api, legacyAccessToken: "access-token", legacyRefreshToken: "refresh-token", httpClient: mockHTTPClient) - - Await.until { done in - sut.oneTimeCode(newSDKClientId: "", oldSDKAccessToken: "") { result in - switch result { - case .success(let code): - XCTAssertEqual(code, expectedCode) - default: - XCTFail("Unexpected result \(result)") - } - done() - } - } - } -} diff --git a/README.md b/README.md index 8f2103a1..80ab6d4f 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,6 @@ To implement login with Schibsted account in your app, please first have a look This will help you create a client and configure the necessary data. **Note:** This SDK requires your client to be registered as a `public_mobile_client` in Self Service (see [getting started documentation](https://docs.schibsted.io/schibsted-account/gettingstarted/) for more help). - -**Note:** If you have implemented the [Old Schibsted SDK](https://github.com/schibsted/account-sdk-ios) in your app, and want these users to remain logged in, do not forget to add the SessionStorageConfig on instantiating your Client, `Client(configuration:sessionStorageConfig:httpClient:)`. The function `Client.resumeLastLoggedInUser(completion: @escaping (User?) -> Void)` will then handle upgrading the user old SDK credentials. ### Requirements diff --git a/Sources/AccountSDKIOSWeb/Client/Client+AppTransfer.swift b/Sources/AccountSDKIOSWeb/Client/Client+AppTransfer.swift index a29f124b..324de31a 100644 --- a/Sources/AccountSDKIOSWeb/Client/Client+AppTransfer.swift +++ b/Sources/AccountSDKIOSWeb/Client/Client+AppTransfer.swift @@ -10,8 +10,6 @@ extension Client { public enum AppTransfer { case preTransfer(_ clientId: String, _ accessGroup: String?) case postTransfer(accessGroup: String?, completion: (Result) -> Void) - /// Only needs to be invoked if the old SDK was used to store login data pre App Transfer. - case postTransferFromOldSDK(accessGroup: String?, completion: (Result) -> Void) case clear // swiftlint:disable nesting @@ -25,17 +23,13 @@ extension Client { try Client.storeOnDevice(clientId: clientId, key: key, accessGroup: accessGroup) case .postTransfer(let accessGroup, let completion): Client.loadFromDeviceToKeychain(key: key, accessGroup: accessGroup, completion: completion) - case .postTransferFromOldSDK(let accessGroup, let completion): - try Client.loadOldSDKDataFromDeviceToKeychain(key: key, accessGroup: accessGroup, completion: completion) case .clear: Client.clearStoredUserOnDevice(key: keyPrefix + key) - Client.clearStoredUserOnDevice(key: oldSDKKeyPrefix + key) } } } private static let keyPrefix = "new-sdk-app-transfer-" - private static let oldSDKKeyPrefix = "old-sdk-app-transfer-" private static func storeOnDevice(clientId: String, key: String, accessGroup: String?) throws { do { @@ -78,24 +72,6 @@ extension Client { } } - private static func loadOldSDKDataFromDeviceToKeychain(key: String, accessGroup: String?, completion: @escaping (Result) -> Void) throws { - let key = Client.oldSDKKeyPrefix + key - guard let tokenData = UserDefaults.standard.object(forKey: key) as? Data else { - completion(.failure(AppTransfer.AppTransferError.userSessionNotFound)) - return - } - - do { - let legacyKeychain = LegacyKeychainTokenStorage(accessGroup: accessGroup) - try legacyKeychain.set(legacySDKtokenData: tokenData) - Client.clearStoredUserOnDevice(key: key) - completion(.success()) - } catch { - SchibstedAccountLogger.instance.info("failed from to storeFromDeviceToKeychain: \(error.localizedDescription)") - completion(.failure(error)) - } - } - private static func clearStoredUserOnDevice(key: String) { UserDefaults.standard.removeObject(forKey: key) } diff --git a/Sources/AccountSDKIOSWeb/Client/Client.swift b/Sources/AccountSDKIOSWeb/Client/Client.swift index 5b974334..f06c6071 100644 --- a/Sources/AccountSDKIOSWeb/Client/Client.swift +++ b/Sources/AccountSDKIOSWeb/Client/Client.swift @@ -8,33 +8,6 @@ import Foundation public typealias LoginResultHandler = (Result) -> Void -/// Configuration struct used for supporting migration from the old to the new SDK. -public struct SessionStorageConfig { - let legacyClientId: String - let legacyClientSecret: String - let accessGroup: String? - let legacyAccessGroup: String? - - /** - Initialize the SessionStorageConfig struct for given client IDs and access group. - - - parameter legacyClientID: The clientID from old SDK. - - parameter legacyClientSecret: The client secret used in old SDK. - - parameter accessGroup: Optional prefered access group name in new SDK. - - parameter legacyAccessGroup: Optional access group name from old SDK. - - */ - public init(legacyClientID: String, - legacyClientSecret: String, - accessGroup: String? = nil, - legacyAccessGroup: String? = nil) { - self.legacyClientId = legacyClientID - self.accessGroup = accessGroup - self.legacyAccessGroup = legacyAccessGroup - self.legacyClientSecret = legacyClientSecret - } -} - /// Default implementation of `ASWebAuthenticationPresentationContextProviding` for the ASWebAuthenticationSession. public class ASWebAuthSessionContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding { public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { @@ -94,73 +67,6 @@ public class Client: CustomStringConvertible { tracker: tracker) } - /** - Initialize the Client to support migration from Legacy SchibstedAccount SDK to the new Schibsted account keychain storage using UserSession. - - - parameter configuration: The ClientConfiguration instance. - - parameter appIdentifierPrefix: Optional AppIdentifierPrefix (Apple team ID). When provided, SDK switches to shared keychain and Simplified Login feature can be used. This value will overule the value of sessionStorageConfig.accessGroup. - - parameter sessionStorageConfig: The configuration struct used in migration process - - parameter tracker: The tracking event implementation that will be called at various spots - - parameter httpClient: Optional object performs to HTTPClient protocol. If not provided a default implementation is used. - - */ - // swiftlint:disable function_body_length - public convenience init(configuration: ClientConfiguration, - appIdentifierPrefix: String? = nil, - sessionStorageConfig: SessionStorageConfig, - tracker: TrackingEventsHandler? = nil, - httpClient: HTTPClient? = nil) { - - let chttpClient = httpClient ?? HTTPClientWithURLSession() - - let legacySessionStorage = LegacyKeychainSessionStorage(accessGroup: sessionStorageConfig.legacyAccessGroup) - let newSessionStorage = SharedKeychainSessionStorageFactory() - .makeKeychain(clientId: configuration.clientId, - service: Client.keychainServiceName, - accessGroup: sessionStorageConfig.accessGroup, - appIdentifierPrefix: appIdentifierPrefix) - - let legacyClientConfiguration = ClientConfiguration(env: configuration.env, - serverURL: configuration.serverURL, - sessionServiceURL: configuration.sessionServiceURL, - clientId: sessionStorageConfig.legacyClientId, - redirectURI: URL(string: "http://")!) - let jwks = RemoteJWKS( - jwksURI: configuration.serverURL.appendingPathComponent("/oauth/jwks"), - httpClient: chttpClient) - let tokenHandler = TokenHandler(configuration: configuration, - httpClient: chttpClient, - jwks: jwks) - let stateStorage = StateStorage() - - // Initializing LegacyClient with all the same properties as regular client. Except for the configuration. - // MigratingKeychainCompatStorage needs a legacyClient. Client needs a MigratingKeychainCompatStorage. Untangle - let legacyClient = Client(configuration: legacyClientConfiguration, - sessionStorage: newSessionStorage, - stateStorage: stateStorage, - httpClient: chttpClient, - jwks: jwks, - tokenHandler: tokenHandler) - let makeTokenCallback = { authCode, authState, completion in - tokenHandler.makeTokenRequest(authCode: authCode, - authState: authState, - completion: completion) - } - let sessionStorage = MigratingKeychainCompatStorage(from: legacySessionStorage, - to: newSessionStorage, - legacyClient: legacyClient, - legacyClientSecret: sessionStorageConfig.legacyClientSecret, - makeTokenRequest: makeTokenCallback) - - self.init(configuration: configuration, - sessionStorage: sessionStorage, - stateStorage: stateStorage, - httpClient: chttpClient, - jwks: jwks, - tokenHandler: tokenHandler, - tracker: tracker) - } - init(configuration: ClientConfiguration, sessionStorage: SessionStorage, stateStorage: StateStorage, diff --git a/Sources/AccountSDKIOSWeb/Lib/Storage/Keychain/Compat/LegacyKeychainSessionStorage.swift b/Sources/AccountSDKIOSWeb/Lib/Storage/Keychain/Compat/LegacyKeychainSessionStorage.swift deleted file mode 100644 index a0fe9020..00000000 --- a/Sources/AccountSDKIOSWeb/Lib/Storage/Keychain/Compat/LegacyKeychainSessionStorage.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright © 2022 Schibsted. -// Licensed under the terms of the MIT license. See LICENSE in the project root. -// - -import Foundation -import JOSESwift - -class LegacyKeychainSessionStorage { - private let storage: LegacyKeychainTokenStorage - - convenience init(accessGroup: String? = nil) { - self.init(storage: LegacyKeychainTokenStorage(accessGroup: accessGroup)) - } - - init(storage: LegacyKeychainTokenStorage) { - self.storage = storage - } - - func get(forClientId: String) -> LegacyUserSession? { - let sessions = storage.get() - .compactMap(toLegacyUserSession(_:)) - .filter { $0.clientId == forClientId } // filter tokens only for the requested client - - // return the newest token, based on 'iat' claim in ID Token - return sessions.sorted { $0.updatedAt > $1.updatedAt }.first - } - - func remove() { - storage.remove() - } - - private func toLegacyUserSession(_ legacyTokenData: LegacyTokenData) -> LegacyUserSession? { - let validatedAccessToken = validateTokenFormat(legacyTokenData.accessToken) - guard let accessTokenClaims = unverifiedClaims(from: validatedAccessToken), - let clientId = accessTokenClaims["client_id"] as? String else { - return nil - } - return LegacyUserSession(clientId: clientId, - accessToken: validatedAccessToken, - refreshToken: legacyTokenData.refreshToken, - updatedAt: Date()) - } - - private func unverifiedClaims(from token: String) -> [String: Any]? { - guard let jws = try? JWS(compactSerialization: token) else { - return nil - } - - return try? JSONSerialization.jsonObject(with: jws.payload.data()) as? [String: Any] - } - - // Access token saved by the old SDK sometimes has a wrong first character. This leads to JWS token decoding error and migration failure. To prevent this issue swapping characters is make before decoding. - private func validateTokenFormat(_ token: String) -> String { - var validToken = token - if !validToken.starts(with: "e") { - validToken = "e\(token.dropFirst())" - } - return validToken - } -} diff --git a/Sources/AccountSDKIOSWeb/Lib/Storage/Keychain/Compat/LegacyKeychainTokenStorage.swift b/Sources/AccountSDKIOSWeb/Lib/Storage/Keychain/Compat/LegacyKeychainTokenStorage.swift deleted file mode 100644 index 562ad5bb..00000000 --- a/Sources/AccountSDKIOSWeb/Lib/Storage/Keychain/Compat/LegacyKeychainTokenStorage.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// Copyright © 2022 Schibsted. -// Licensed under the terms of the MIT license. See LICENSE in the project root. -// - -import Foundation - -internal struct LegacyTokenData: Equatable, Codable { - let accessToken: String - let refreshToken: String -} - -internal struct LegacyUserSession: Codable, Equatable { - let clientId: String - let accessToken: String - let refreshToken: String - let updatedAt: Date -} - -class LegacyKeychainTokenStorage { - private let service = "swift.keychain.service" - private let account = "SchibstedID" - enum KeychainKey { - static let refreshToken = "refresh_token" - static let idToken = "id_token" - static let loggedInUsers = "logged_in_users" - static let userID = "user_id" - } - - private let keychain: KeychainStoring - - init(accessGroup: String? = nil) { - keychain = KeychainStorage(forService: service, accessGroup: accessGroup) - } - - // Now just for test purposes (proper dependency injection in the future) - init(keychain: KeychainStoring) { - self.keychain = keychain - } - - /** - == Keychain JSON structure == - "logged_in_users": { - : { refresh_token: , id_token: , user_id: } - : { refresh_token: , id_token: , user_id: } - ... - : { refresh_token: , id_token: , user_id: } - } - */ - func get() -> [LegacyTokenData] { - let maybeData: Data? - do { - maybeData = try keychain.getValue(forAccount: account) - } catch { - SchibstedAccountLogger.instance.error("\(error.localizedDescription)") - return [] - } - - guard let data = maybeData else { - return [] - } - - guard let deserialised = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [String: Any] else { - SchibstedAccountLogger.instance.error("Failed to deserialise legacy keychain data") - return [] - } - - guard let parsed = deserialised[Self.KeychainKey.loggedInUsers] as? [String: [String: Any]] else { - SchibstedAccountLogger.instance.error("Failed to parse legacy keychain data") - return [] - } - - let storedTokens: [LegacyTokenData] = parsed.compactMap { (accessToken, data) in - guard let refreshToken = data[Self.KeychainKey.refreshToken] as? String else { - return nil - } - return LegacyTokenData(accessToken: accessToken, refreshToken: refreshToken) - } - - return storedTokens - } - - /** - == LegacySDKtokenData data structure == - { - "idToken":{ - "string": - }, - "refreshToken":, - "accessToken": - } - */ - func set(legacySDKtokenData: Data) throws { - guard let dict = try JSONSerialization.jsonObject(with: legacySDKtokenData, options: []) as? [String: Any] else { - throw KeychainStorageError.storeError(reason: .invalidData(reason: "Cannot deserialize legacySDKtokenData")) - } - - guard let accessToken = dict["accessToken"] as? String else { - throw KeychainStorageError.storeError(reason: .invalidData(reason: "Missed accessToken")) - } - - guard let refreshToken = dict["refreshToken"] as? String else { - throw KeychainStorageError.storeError(reason: .invalidData(reason: "Missed refreshToken")) - } - - // Build as Legacy Keychain JSON structure - var loggedInUsers: [String: [String: String]] = [:] - let loggedInUser: [String: String] = [Self.KeychainKey.refreshToken: refreshToken] - - loggedInUsers[accessToken] = loggedInUser - let keychainData = [Self.KeychainKey.loggedInUsers: loggedInUsers] - - let data = try NSKeyedArchiver.archivedData(withRootObject: keychainData, requiringSecureCoding: true) - try keychain.setValue(data, forAccount: account, accessGroup: nil) - } - - func remove() { - do { - try keychain.removeValue(forAccount: account) - } catch { - SchibstedAccountLogger.instance.error("\(error.localizedDescription)") - } - } -} diff --git a/Sources/AccountSDKIOSWeb/Lib/Storage/Keychain/Compat/MigratingKeychainCompatStorage.swift b/Sources/AccountSDKIOSWeb/Lib/Storage/Keychain/Compat/MigratingKeychainCompatStorage.swift deleted file mode 100644 index bce803a7..00000000 --- a/Sources/AccountSDKIOSWeb/Lib/Storage/Keychain/Compat/MigratingKeychainCompatStorage.swift +++ /dev/null @@ -1,226 +0,0 @@ -// -// Copyright © 2022 Schibsted. -// Licensed under the terms of the MIT license. See LICENSE in the project root. -// - -import Foundation - -class MigratingKeychainCompatStorage: SessionStorage { - var accessGroup: String? { - return newStorage.accessGroup - } - private let newStorage: KeychainSessionStorage - private let legacyStorage: LegacyKeychainSessionStorage - private let legacyClient: Client - private let legacyClientSecret: String - private let makeTokenRequest: (_ authCode: String, - _ authState: AuthState?, - _ completion: @escaping (Result) -> Void) -> Void - private var oldSDKClient: OldSDKClient? - - // swiftlint:disable identifier_name - init(from: LegacyKeychainSessionStorage, - to: KeychainSessionStorage, - legacyClient: Client, - legacyClientSecret: String, - makeTokenRequest: @escaping (_ authCode: String, - _ authState: AuthState?, - _ completion: @escaping (Result) -> Void) -> Void) { - self.newStorage = to - self.legacyStorage = from - self.legacyClient = legacyClient - self.legacyClientSecret = legacyClientSecret - self.makeTokenRequest = makeTokenRequest - } - - func store(_ value: UserSession, accessGroup: String? = nil, completion: @escaping (Result) -> Void) { - // only delegate to new storage; no need to store in legacy storage - newStorage.store(value, completion: completion) - - } - - func get(forClientId: String, completion: @escaping (UserSession?) -> Void ) { - // try new storage first - newStorage.get(forClientId: forClientId) { session in - if let session = session { - completion(session) - return - } - - // if no existing session found, look in legacy storage with - guard let legacySession = self.legacyStorage.get(forClientId: self.legacyClient.configuration.clientId) else { - completion(nil) - return - } - - self.migrateLegacyUserSession(forClientId: forClientId, - legacySession: legacySession, - completion: completion) - } - } - - private func migrateLegacyUserSession(forClientId: String, - legacySession: LegacyUserSession, - completion: @escaping (UserSession?) -> Void) { - - self.oldSDKClient = OldSDKClient(clientId: legacyClient.configuration.clientId, - clientSecret: self.legacyClientSecret, - api: legacyClient.schibstedAccountAPI, - legacyAccessToken: legacySession.accessToken, - legacyRefreshToken: legacySession.refreshToken) - oldSDKClient?.oneTimeCodeWithOldSDKRefresh(newSDKClientId: forClientId) { result in - switch result { - case .success(let code): - self.makeTokenRequest(code, nil) { result in - switch result { - case .success(let tokenResult): - let newUserSession = UserSession(clientId: forClientId, - userTokens: tokenResult.userTokens, - updatedAt: Date()) - self.newStorage.store(newUserSession) { output in - switch output { - case .success: - self.legacyStorage.remove() - completion(newUserSession) - case .failure(let error): - SchibstedAccountLogger.instance.error("\(error.localizedDescription)") - completion(nil) - } - } - case .failure(let error): - SchibstedAccountLogger.instance.info("Token error response: \(error.localizedDescription)") - completion(nil) - } - } - case .failure(let error): - SchibstedAccountLogger.instance - .info("Failed to migrate tokens. With error: \(error.localizedDescription)") - completion(nil) - } - } - } - - func getAll() -> [UserSession] { - // only delegate to new storage; this functionality is not supported by legacyStorage - return newStorage.getAll() - } - - func remove(forClientId: String) { - newStorage.remove(forClientId: forClientId) - // data should have already been removed from legacy storage during migration. But could fail. - legacyStorage.remove() - } -} - -/// OldSDKClient is responsible for exchanging Old SDK Token to an Authorization code. -class OldSDKClient { - let clientId: String - let clientSecret: String - let api: SchibstedAccountAPI - let legacyAccessToken: String - let legacyRefreshToken: String - var httpClient: HTTPClient - - init(clientId: String, - clientSecret: String, - api: SchibstedAccountAPI, - legacyAccessToken: String, - legacyRefreshToken: String, - httpClient: HTTPClient) { - - self.clientId = clientId - self.clientSecret = clientSecret - self.api = api - self.legacyAccessToken = legacyAccessToken - self.legacyRefreshToken = legacyRefreshToken - self.httpClient = httpClient - } - - convenience init(clientId: String, - clientSecret: String, - api: SchibstedAccountAPI, - legacyAccessToken: String, - legacyRefreshToken: String) { - - let httpClient = HTTPClientWithURLSession() - self.init(clientId: clientId, - clientSecret: clientSecret, - api: api, - legacyAccessToken: legacyAccessToken, - legacyRefreshToken: legacyRefreshToken, httpClient: httpClient) - } - - func oneTimeCodeWithOldSDKRefresh(newSDKClientId: String, - completion: @escaping HTTPResultHandler) { - api.oldSDKCodeExchange(with: httpClient, - clientId: newSDKClientId, - oldSDKAccessToken: legacyAccessToken) { (requestResult: Result, HTTPError>) in - switch requestResult { - case .failure(.errorResponse(let code, let body)): - // 401 might indicate expired access token - if code == 401 { - let refreshToken = self.legacyRefreshToken - self.oldSDKRefresh(refreshToken: refreshToken) { result in - switch result { - case .success(let newToken): // retry the request with fresh tokens - self.oneTimeCode(newSDKClientId: newSDKClientId, - oldSDKAccessToken: newToken, - completion: completion) - case .failure(let error): - SchibstedAccountLogger.instance.info("Failed to refresh legacy tokens: \(error)") - completion(.failure(.unexpectedError(underlying: error))) - } - } - } else { - let error = HTTPError.errorResponse(code: code, body: body) - SchibstedAccountLogger.instance.info("Failed legacy code exchange: \(error)") - completion(.failure(error)) - } - case .failure(let error): - SchibstedAccountLogger.instance.info("Failed legacy code exchange: \(error)") - completion(.failure(error)) - case .success( let response): - let code = response.data.code - completion(.success(code)) - } - } - } - - func oldSDKRefresh(refreshToken: String, completion: @escaping (Result) -> Void) { - - let resultHandler: HTTPResultHandler = { result in - switch result { - case .success(let tokenResponse): - completion(.success(tokenResponse.accessToken)) - case .failure(let error): - SchibstedAccountLogger.instance - .info("Failed to migrate tokens. With error: \(error.localizedDescription)") - completion(.failure(error)) - } - } - - api.oldSDKRefresh(with: httpClient, - refreshToken: refreshToken, - clientId: clientId, - clientSecret: clientSecret, - completion: resultHandler) - } - - func oneTimeCode(newSDKClientId: String, - oldSDKAccessToken: String, - completion: @escaping HTTPResultHandler) { - - self.api.oldSDKCodeExchange(with: self.httpClient, - clientId: newSDKClientId, - oldSDKAccessToken: oldSDKAccessToken) { (requestResult: Result, HTTPError>) in // swiftlint:disable:this line_length - switch requestResult { - case .failure( let error): - SchibstedAccountLogger.instance.info("Failed legacy code exchange with refreshed token: \(error)") - completion(.failure(error)) - case .success( let response): - let code = response.data.code - completion(.success(code)) - } - } - } -} diff --git a/Sources/AccountSDKIOSWeb/Resources/en.lproj/Localizable.strings b/Sources/AccountSDKIOSWeb/Resources/en.lproj/Localizable.strings index 0e2b23aa..1f8adaae 100644 --- a/Sources/AccountSDKIOSWeb/Resources/en.lproj/Localizable.strings +++ b/Sources/AccountSDKIOSWeb/Resources/en.lproj/Localizable.strings @@ -3,6 +3,6 @@ "SimplifiedWidget.continueWithoutLogin" = "Continue without logging in"; "SimplifiedWidget.shortFooter" = "With a Schibsted account, you can log in to all Schibsted services."; "SimplifiedWidget.privacyPolicy" = "Privacy policy"; -"SimplifiedWidget.privacyPolicyLink" = "https://info.privacy.schibsted.com/privacy-and-cookie-policy-english-schibsted-norge/"; +"SimplifiedWidget.privacyPolicyLink" = "https://info.privacy.schibsted.com/en/privacy-and-cookie-policy-english-schibsted-sverige/"; "SimplifiedWidget.notYou" = "Not you?"; "SimplifiedWidget.loginWithDifferentAccount" = "Switch account";