Skip to content

Commit 4035dbf

Browse files
authored
Merge branch 'main' into hosted-ui-logout-ignore-user-cancel
2 parents d1b9685 + aa1b0ca commit 4035dbf

27 files changed

+1354
-2083
lines changed

.github/workflows/codeql.yml

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ jobs:
4747
with:
4848
languages: ${{ matrix.language }}
4949
config-file: ./.github/codeql/config.yml
50-
debug: true
5150

5251
- name: Perform CodeQL Analysis
5352
uses: github/codeql-action/analyze@822fe5ef9a15bd752ef127e9ff6eac38ec37dd9c

.github/workflows/stress_test.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ concurrency:
1414

1515
jobs:
1616
prepare-for-test:
17-
runs-on: macos-latest
17+
runs-on: macos-15
1818
environment: IntegrationTest
1919
outputs:
2020
destination: ${{ steps.platform.outputs.destination }}
@@ -40,7 +40,7 @@ jobs:
4040

4141
auth-stress-test:
4242
needs: prepare-for-test
43-
runs-on: macos-latest
43+
runs-on: macos-15
4444
environment: IntegrationTest
4545
env:
4646
DESTINATION: ${{ needs.prepare-for-test.outputs.destination }}
@@ -71,7 +71,7 @@ jobs:
7171

7272
geo-stress-test:
7373
needs: prepare-for-test
74-
runs-on: macos-latest
74+
runs-on: macos-15
7575
environment: IntegrationTest
7676
env:
7777
DESTINATION: ${{ needs.prepare-for-test.outputs.destination }}
@@ -102,7 +102,7 @@ jobs:
102102

103103
storage-stress-test:
104104
needs: prepare-for-test
105-
runs-on: macos-latest
105+
runs-on: macos-15
106106
environment: IntegrationTest
107107
env:
108108
DESTINATION: ${{ needs.prepare-for-test.outputs.destination }}
@@ -133,7 +133,7 @@ jobs:
133133

134134
datastore-stress-test:
135135
needs: prepare-for-test
136-
runs-on: macos-latest
136+
runs-on: macos-15
137137
environment: IntegrationTest
138138
env:
139139
DESTINATION: ${{ needs.prepare-for-test.outputs.destination }}
@@ -164,7 +164,7 @@ jobs:
164164

165165
graphql-api-stress-test:
166166
needs: prepare-for-test
167-
runs-on: macos-latest
167+
runs-on: macos-15
168168
environment: IntegrationTest
169169
env:
170170
DESTINATION: ${{ needs.prepare-for-test.outputs.destination }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 Foundation
9+
10+
/// A structure representing an access group for managing keychain items.
11+
public struct AccessGroup {
12+
/// The name of the access group.
13+
public let name: String?
14+
15+
/// A flag indicating whether to migrate keychain items.
16+
public let migrateKeychainItems: Bool
17+
18+
/**
19+
Initializes an `AccessGroup` with the specified name and migration option.
20+
21+
- Parameter name: The name of the access group.
22+
- Parameter migrateKeychainItemsOfUserSession: A flag indicating whether to migrate keychain items. Defaults to `false`.
23+
*/
24+
public init(name: String, migrateKeychainItemsOfUserSession: Bool = false) {
25+
self.init(name: name, migrateKeychainItems: migrateKeychainItemsOfUserSession)
26+
}
27+
28+
/**
29+
Creates an `AccessGroup` instance with no specified name.
30+
31+
- Parameter migrateKeychainItemsOfUserSession: A flag indicating whether to migrate keychain items.
32+
- Returns: An `AccessGroup` instance with the migration option set.
33+
*/
34+
public static func none(migrateKeychainItemsOfUserSession: Bool) -> AccessGroup {
35+
return .init(migrateKeychainItems: migrateKeychainItemsOfUserSession)
36+
}
37+
38+
/**
39+
A static property representing an `AccessGroup` with no name and no migration.
40+
41+
- Returns: An `AccessGroup` instance with no name and the migration option set to `false`.
42+
*/
43+
public static var none: AccessGroup {
44+
return .none(migrateKeychainItemsOfUserSession: false)
45+
}
46+
47+
private init(name: String? = nil, migrateKeychainItems: Bool) {
48+
self.name = name
49+
self.migrateKeychainItems = migrateKeychainItems
50+
}
51+
}

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,11 @@ extension AWSCognitoAuthPlugin {
186186
}
187187

188188
private func makeCredentialStore() -> AmplifyAuthCredentialStoreBehavior {
189-
AWSCognitoAuthCredentialStore(authConfiguration: authConfiguration)
189+
return AWSCognitoAuthCredentialStore(
190+
authConfiguration: authConfiguration,
191+
accessGroup: secureStoragePreferences?.accessGroup?.name,
192+
migrateKeychainItemsOfUserSession: secureStoragePreferences?.accessGroup?.migrateKeychainItems ?? false
193+
)
190194
}
191195

192196
private func makeLegacyKeychainStore(service: String) -> KeychainStoreBehavior {

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin.swift

+9-7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public final class AWSCognitoAuthPlugin: AWSCognitoAuthPluginBehavior {
3535
/// The user network preferences for timeout and retry
3636
let networkPreferences: AWSCognitoNetworkPreferences?
3737

38+
/// The user secure storage preferences for access group
39+
let secureStoragePreferences: AWSCognitoSecureStoragePreferences?
40+
3841
@_spi(InternalAmplifyConfiguration)
3942
internal(set) public var jsonConfiguration: JSONValue?
4043

@@ -43,15 +46,14 @@ public final class AWSCognitoAuthPlugin: AWSCognitoAuthPluginBehavior {
4346
return "awsCognitoAuthPlugin"
4447
}
4548

46-
/// Instantiates an instance of the AWSCognitoAuthPlugin.
47-
public init() {
48-
self.networkPreferences = nil
49-
}
50-
51-
/// Instantiates an instance of the AWSCognitoAuthPlugin with custom network preferences
49+
/// Instantiates an instance of the AWSCognitoAuthPlugin with optional custom network
50+
/// preferences and optional custom secure storage preferences
5251
/// - Parameters:
5352
/// - networkPreferences: network preferences
54-
public init(networkPreferences: AWSCognitoNetworkPreferences) {
53+
/// - secureStoragePreferences: secure storage preferences
54+
public init(networkPreferences: AWSCognitoNetworkPreferences? = nil,
55+
secureStoragePreferences: AWSCognitoSecureStoragePreferences = AWSCognitoSecureStoragePreferences()) {
5556
self.networkPreferences = networkPreferences
57+
self.secureStoragePreferences = secureStoragePreferences
5658
}
5759
}

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/ClientBehavior/AWSCognitoAuthPlugin+ClientBehavior.swift

+5-4
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ extension AWSCognitoAuthPlugin: AuthCategoryBehavior {
114114
public func fetchAuthSession(options: AuthFetchSessionRequest.Options?) async throws -> AuthSession {
115115
let options = options ?? AuthFetchSessionRequest.Options()
116116
let request = AuthFetchSessionRequest(options: options)
117-
let task = AWSAuthFetchSessionTask(
118-
request,
119-
environment: authEnvironment,
120-
authStateMachine: authStateMachine)
117+
let forceReconfigure = secureStoragePreferences?.accessGroup?.name != nil
118+
let task = AWSAuthFetchSessionTask(request,
119+
authStateMachine: authStateMachine,
120+
configuration: authConfiguration,
121+
forceReconfigure: forceReconfigure)
121122
return try await taskQueue.sync {
122123
return try await task.value
123124
} as! AuthSession

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/CredentialStorage/AWSCognitoAuthCredentialStore.swift

+64-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct AWSCognitoAuthCredentialStore {
1313

1414
// Credential store constants
1515
private let service = "com.amplify.awsCognitoAuthPlugin"
16+
private let sharedService = "com.amplify.awsCognitoAuthPluginShared"
1617
private let sessionKey = "session"
1718
private let deviceMetadataKey = "deviceMetadata"
1819
private let deviceASFKey = "deviceASF"
@@ -25,14 +26,40 @@ struct AWSCognitoAuthCredentialStore {
2526
private var isKeychainConfiguredKey: String {
2627
"\(userDefaultsNameSpace).isKeychainConfigured"
2728
}
29+
/// This UserDefaults Key is use to retrieve the stored access group to determine
30+
/// which access group the migration should happen from
31+
/// If none is found, the unshared service is used for migration and all items
32+
/// under that service are queried
33+
private var accessGroupKey: String {
34+
"\(userDefaultsNameSpace).accessGroup"
35+
}
2836

2937
private let authConfiguration: AuthConfiguration
3038
private let keychain: KeychainStoreBehavior
3139
private let userDefaults = UserDefaults.standard
40+
private let accessGroup: String?
3241

33-
init(authConfiguration: AuthConfiguration, accessGroup: String? = nil) {
42+
init(
43+
authConfiguration: AuthConfiguration,
44+
accessGroup: String? = nil,
45+
migrateKeychainItemsOfUserSession: Bool = false
46+
) {
3447
self.authConfiguration = authConfiguration
35-
self.keychain = KeychainStore(service: service, accessGroup: accessGroup)
48+
self.accessGroup = accessGroup
49+
if let accessGroup {
50+
self.keychain = KeychainStore(service: sharedService, accessGroup: accessGroup)
51+
} else {
52+
self.keychain = KeychainStore(service: service)
53+
}
54+
55+
let oldAccessGroup = retrieveStoredAccessGroup()
56+
if migrateKeychainItemsOfUserSession {
57+
try? migrateKeychainItemsToAccessGroup()
58+
} else if oldAccessGroup == nil && oldAccessGroup != accessGroup {
59+
try? KeychainStore(service: service)._removeAll()
60+
}
61+
62+
saveStoredAccessGroup()
3663

3764
if !userDefaults.bool(forKey: isKeychainConfiguredKey) {
3865
try? clearAllCredentials()
@@ -181,6 +208,39 @@ extension AWSCognitoAuthCredentialStore: AmplifyAuthCredentialStoreBehavior {
181208
private func clearAllCredentials() throws {
182209
try keychain._removeAll()
183210
}
211+
212+
private func retrieveStoredAccessGroup() -> String? {
213+
return userDefaults.string(forKey: accessGroupKey)
214+
}
215+
216+
private func saveStoredAccessGroup() {
217+
if let accessGroup {
218+
userDefaults.set(accessGroup, forKey: accessGroupKey)
219+
} else {
220+
userDefaults.removeObject(forKey: accessGroupKey)
221+
}
222+
}
223+
224+
private func migrateKeychainItemsToAccessGroup() throws {
225+
let oldAccessGroup = retrieveStoredAccessGroup()
226+
227+
if oldAccessGroup == accessGroup {
228+
log.info("[AWSCognitoAuthCredentialStore] Stored access group is the same as current access group, aborting migration")
229+
return
230+
}
231+
232+
let oldService = oldAccessGroup != nil ? sharedService : service
233+
let newService = accessGroup != nil ? sharedService : service
234+
235+
do {
236+
try KeychainStoreMigrator(oldService: oldService, newService: newService, oldAccessGroup: oldAccessGroup, newAccessGroup: accessGroup).migrate()
237+
} catch {
238+
log.error("[AWSCognitoAuthCredentialStore] Migration has failed")
239+
return
240+
}
241+
242+
log.verbose("[AWSCognitoAuthCredentialStore] Migration of keychain items from old access group to new access group successful")
243+
}
184244

185245
}
186246

@@ -204,3 +264,5 @@ private extension AWSCognitoAuthCredentialStore {
204264
}
205265

206266
}
267+
268+
extension AWSCognitoAuthCredentialStore: DefaultLogger { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 Foundation
9+
import Amplify
10+
11+
/// A struct to store preferences for how the plugin uses storage
12+
public struct AWSCognitoSecureStoragePreferences {
13+
14+
/// The access group that the keychain will use for auth items
15+
public let accessGroup: AccessGroup?
16+
17+
/// Creates an intstance of AWSCognitoSecureStoragePreferences
18+
/// - Parameters:
19+
/// - accessGroup: access group to be used
20+
public init(accessGroup: AccessGroup? = nil) {
21+
self.accessGroup = accessGroup
22+
}
23+
}

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift

+16-3
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,35 @@ class AWSAuthFetchSessionTask: AuthFetchSessionTask, DefaultLogger {
1313
private let authStateMachine: AuthStateMachine
1414
private let fetchAuthSessionHelper: FetchAuthSessionOperationHelper
1515
private let taskHelper: AWSAuthTaskHelper
16+
private let configuration: AuthConfiguration
17+
private let forceReconfigure: Bool
1618

1719
var eventName: HubPayloadEventName {
1820
HubPayload.EventName.Auth.fetchSessionAPI
1921
}
2022

21-
init(_ request: AuthFetchSessionRequest,
22-
environment: Environment,
23-
authStateMachine: AuthStateMachine) {
23+
init(
24+
_ request: AuthFetchSessionRequest,
25+
authStateMachine: AuthStateMachine,
26+
configuration: AuthConfiguration,
27+
forceReconfigure: Bool = false
28+
) {
2429
self.request = request
2530
self.authStateMachine = authStateMachine
2631
self.fetchAuthSessionHelper = FetchAuthSessionOperationHelper()
2732
self.fetchAuthSessionHelper.environment = environment
2833
self.taskHelper = AWSAuthTaskHelper(authStateMachine: authStateMachine)
34+
self.configuration = configuration
35+
self.forceReconfigure = forceReconfigure
2936
}
3037

3138
func execute() async throws -> AuthSession {
39+
log.verbose("Starting execution")
40+
if forceReconfigure {
41+
log.verbose("Reconfiguring auth state machine for keychain sharing")
42+
let event = AuthEvent(eventType: .reconfigure(configuration))
43+
await authStateMachine.send(event)
44+
}
3245
await taskHelper.didStateMachineConfigured()
3346
let doesNeedForceRefresh = request.options.forceRefresh
3447
return try await fetchAuthSessionHelper.fetch(authStateMachine,

0 commit comments

Comments
 (0)