Skip to content

Commit f0bc8df

Browse files
authored
fix(Auth): Fixing keychain migration issue (#2978)
* fix(Auth): Fixing keychain migration issue * adding integration tests * worked on review comments
1 parent 1052508 commit f0bc8df

File tree

6 files changed

+129
-41
lines changed

6 files changed

+129
-41
lines changed

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/Resolvers/Authorization/AuthorizationState+Resolver.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ extension AuthorizationState {
247247
if case .sessionEstablished(let credentials) = event.isAuthorizationEvent {
248248
return .init(newState: .sessionEstablished(credentials))
249249
}
250+
251+
if case .throwError(let error) = event.isAuthorizationEvent {
252+
return .init(newState: .error(error))
253+
}
250254
return .from(oldState)
251255

252256
case .error(let error):

AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/SessionTests/SignedInAuthSessionTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,36 @@ class SignedInAuthSessionTests: AWSAuthBaseTest {
235235
}
236236
await waitForExpectations(timeout: networkTimeout)
237237
}
238+
239+
/// Test if successful session is retrieved before and after a user sign in
240+
///
241+
/// - Given: A signed out Amplify Auth Category
242+
/// - When:
243+
/// - I call fetchAuthSession
244+
/// - Then:
245+
/// - I get a signed out session
246+
/// - After that When:
247+
/// - I sign in to the Auth Category
248+
/// - Then:
249+
/// - I should receive a valid session in signed in state
250+
/// - After that When:
251+
/// - I call fetchAuthSession
252+
/// - Then:
253+
/// - I should receive a valid session in signed in state
254+
func testSuccessfulSessionFetchAndSignIn() async throws {
255+
let username = "integTest\(UUID().uuidString)"
256+
let password = "P123@\(UUID().uuidString)"
257+
258+
var session = try await Amplify.Auth.fetchAuthSession()
259+
XCTAssertFalse(session.isSignedIn, "Session state should be signed out")
260+
261+
let didSucceed = try await AuthSignInHelper.registerAndSignInUser(
262+
username: username,
263+
password: password,
264+
email: defaultTestEmail)
265+
XCTAssertTrue(didSucceed, "SignIn operation failed")
266+
267+
session = try await Amplify.Auth.fetchAuthSession()
268+
XCTAssertTrue(session.isSignedIn, "Session state should be signed In")
269+
}
238270
}

AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStore.swift

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public struct KeychainStore: KeychainStoreBehavior {
9999
/// - Parameter key: A String key use to look up the value in the Keychain
100100
/// - Returns: A data value
101101
public func _getData(_ key: String) throws -> Data {
102-
var query = attributes.query()
102+
var query = attributes.defaultGetQuery()
103103

104104
query[Constants.MatchLimit] = Constants.MatchLimitOne
105105
query[Constants.ReturnData] = kCFBooleanTrue
@@ -142,24 +142,26 @@ public struct KeychainStore: KeychainStoreBehavior {
142142
/// - value: A data value to store in Keychain
143143
/// - key: A String key for the value to store in the Keychain
144144
public func _set(_ value: Data, key: String) throws {
145-
var query = attributes.query()
146-
query[Constants.AttributeAccount] = key
145+
var getQuery = attributes.defaultGetQuery()
146+
getQuery[Constants.AttributeAccount] = key
147147

148-
let fetchStatus = SecItemCopyMatching(query as CFDictionary, nil)
148+
let fetchStatus = SecItemCopyMatching(getQuery as CFDictionary, nil)
149149
switch fetchStatus {
150150
case errSecSuccess:
151151

152152
var attributesToUpdate = [String: Any]()
153153
attributesToUpdate[Constants.ValueData] = value
154154

155-
let updateStatus = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
155+
let updateStatus = SecItemUpdate(getQuery as CFDictionary, attributesToUpdate as CFDictionary)
156156
if updateStatus != errSecSuccess {
157157
throw KeychainStoreError.securityError(updateStatus)
158158
}
159159
case errSecItemNotFound:
160-
let attributes = attributes.fetchAll(for: key, and: value)
160+
var attributesToSet = attributes.defaultSetQuery()
161+
attributesToSet[Constants.AttributeAccount] = key
162+
attributesToSet[Constants.ValueData] = value
161163

162-
let addStatus = SecItemAdd(attributes as CFDictionary, nil)
164+
let addStatus = SecItemAdd(attributesToSet as CFDictionary, nil)
163165
if addStatus != errSecSuccess {
164166
throw KeychainStoreError.securityError(addStatus)
165167
}
@@ -173,7 +175,7 @@ public struct KeychainStore: KeychainStoreBehavior {
173175
/// This System Programming Interface (SPI) may have breaking changes in future updates.
174176
/// - Parameter key: A String key to delete the key-value pair
175177
public func _remove(_ key: String) throws {
176-
var query = attributes.query()
178+
var query = attributes.defaultGetQuery()
177179
query[Constants.AttributeAccount] = key
178180

179181
let status = SecItemDelete(query as CFDictionary)
@@ -186,7 +188,7 @@ public struct KeychainStore: KeychainStoreBehavior {
186188
/// Removes all key-value pair in the Keychain.
187189
/// This System Programming Interface (SPI) may have breaking changes in future updates.
188190
public func _removeAll() throws {
189-
var query = attributes.query()
191+
var query = attributes.defaultGetQuery()
190192
#if !os(iOS) && !os(watchOS) && !os(tvOS)
191193
query[Constants.MatchLimit] = Constants.MatchLimitAll
192194
#endif

AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStoreAttributes.swift

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,26 @@ struct KeychainStoreAttributes {
1212
var itemClass: String = KeychainStore.Constants.ClassGenericPassword
1313
var service: String
1414
var accessGroup: String?
15-
16-
var label: String?
17-
var comment: String?
15+
1816
}
1917

2018
extension KeychainStoreAttributes {
2119

22-
func query() -> [String: Any] {
20+
func defaultGetQuery() -> [String: Any] {
2321
var query: [String: Any] = [
2422
KeychainStore.Constants.Class: itemClass,
2523
KeychainStore.Constants.AttributeService: service
2624
]
2725
if let accessGroup = accessGroup {
2826
query[KeychainStore.Constants.AttributeAccessGroup] = accessGroup
2927
}
30-
query[KeychainStore.Constants.AttributeAccessible] = KeychainStore.Constants.AttributeAccessibleAfterFirstUnlockThisDeviceOnly
31-
query[KeychainStore.Constants.UseDataProtectionKeyChain] = kCFBooleanTrue
3228
return query
3329
}
3430

35-
func fetchAll(for key: String?, and value: Data) -> [String: Any] {
36-
37-
var attributes: [String: Any]
38-
39-
if key != nil {
40-
attributes = query()
41-
attributes[KeychainStore.Constants.AttributeAccount] = key
42-
} else {
43-
attributes = [String: Any]()
44-
}
45-
46-
attributes[KeychainStore.Constants.ValueData] = value
47-
48-
if label != nil {
49-
attributes[KeychainStore.Constants.AttributeLabel] = label
50-
}
51-
if comment != nil {
52-
attributes[KeychainStore.Constants.AttributeComment] = comment
53-
}
54-
55-
return attributes
56-
31+
func defaultSetQuery() -> [String: Any] {
32+
var query: [String: Any] = defaultGetQuery()
33+
query[KeychainStore.Constants.AttributeAccessible] = KeychainStore.Constants.AttributeAccessibleAfterFirstUnlockThisDeviceOnly
34+
query[KeychainStore.Constants.UseDataProtectionKeyChain] = kCFBooleanTrue
35+
return query
5736
}
5837
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import XCTest
9+
@testable import Amplify
10+
@testable import AWSPluginsCore
11+
12+
class KeychainStoreAttributesTests: XCTestCase {
13+
14+
var keychainStoreAttribute: KeychainStoreAttributes!
15+
16+
/// Given: an instance of `KeychainStoreAttributes`
17+
/// When: `keychainStoreAttribute.defaultGetQuery()` is invoked with a required service param
18+
/// Then: Validate if the attributes contain the correct get query params
19+
/// - AttributeService
20+
/// - Class
21+
func testDefaultGetQuery() {
22+
keychainStoreAttribute = KeychainStoreAttributes(service: "someService")
23+
24+
let defaultGetAttributes = keychainStoreAttribute.defaultGetQuery()
25+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.AttributeService] as? String, "someService")
26+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.Class] as? String, KeychainStore.Constants.ClassGenericPassword)
27+
XCTAssertNil(defaultGetAttributes[KeychainStore.Constants.AttributeAccessGroup] as? String)
28+
XCTAssertNil(defaultGetAttributes[KeychainStore.Constants.AttributeAccessible] as? String)
29+
XCTAssertNil(defaultGetAttributes[KeychainStore.Constants.UseDataProtectionKeyChain] as? String)
30+
}
31+
32+
/// Given: an instance of `KeychainStoreAttributes`
33+
/// When: `keychainStoreAttribute.defaultGetQuery()` is invoked with a required service param and access group
34+
/// Then: Validate if the attributes contain the correct get query params
35+
/// - AttributeService
36+
/// - Class
37+
/// - AttributeAccessGroup
38+
func testDefaultGetQueryWithAccessGroup() {
39+
keychainStoreAttribute = KeychainStoreAttributes(service: "someService", accessGroup: "someAccessGroup")
40+
41+
let defaultGetAttributes = keychainStoreAttribute.defaultGetQuery()
42+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.AttributeService] as? String, "someService")
43+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.Class] as? String, KeychainStore.Constants.ClassGenericPassword)
44+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.AttributeAccessGroup] as? String, "someAccessGroup")
45+
XCTAssertNil(defaultGetAttributes[KeychainStore.Constants.AttributeAccessible] as? String)
46+
XCTAssertNil(defaultGetAttributes[KeychainStore.Constants.UseDataProtectionKeyChain] as? String)
47+
}
48+
49+
/// Given: an instance of `KeychainStoreAttributes`
50+
/// When: `keychainStoreAttribute.defaultSetQuery()` is invoked with a required service param and access group
51+
/// Then: Validate if the attributes contain the correct get query params
52+
/// - AttributeService
53+
/// - Class
54+
/// - AttributeAccessGroup
55+
/// - AttributeAccessible
56+
/// - UseDataProtectionKeyChain
57+
func testDefaultSetQueryWithAccessGroup() {
58+
keychainStoreAttribute = KeychainStoreAttributes(service: "someService", accessGroup: "someAccessGroup")
59+
60+
let defaultGetAttributes = keychainStoreAttribute.defaultSetQuery()
61+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.AttributeService] as? String, "someService")
62+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.Class] as? String, KeychainStore.Constants.ClassGenericPassword)
63+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.AttributeAccessGroup] as? String, "someAccessGroup")
64+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.AttributeAccessible] as? String, KeychainStore.Constants.AttributeAccessibleAfterFirstUnlockThisDeviceOnly)
65+
XCTAssertEqual(defaultGetAttributes[KeychainStore.Constants.UseDataProtectionKeyChain] as? Bool, true)
66+
}
67+
68+
override func tearDown() {
69+
keychainStoreAttribute = nil
70+
}
71+
}

0 commit comments

Comments
 (0)