Skip to content

Commit 083ebbe

Browse files
authored
chore: kickoff release
2 parents 478bcc9 + 2dad52b commit 083ebbe

12 files changed

Lines changed: 261 additions & 78 deletions

File tree

AmplifyPlugins/Analytics/Tests/AnalyticsHostApp/AnalyticsHostApp.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@
902902
PRODUCT_BUNDLE_IDENTIFIER = com.amazon.aws.amplify.swift.AnalyticsHostAppWatch.watchkitapp;
903903
PRODUCT_NAME = "$(TARGET_NAME)";
904904
SDKROOT = watchos;
905-
SKIP_INSTALL = YES;
905+
SKIP_INSTALL = NO;
906906
SWIFT_EMIT_LOC_STRINGS = YES;
907907
SWIFT_VERSION = 5.0;
908908
TARGETED_DEVICE_FAMILY = 4;
@@ -932,7 +932,7 @@
932932
PRODUCT_BUNDLE_IDENTIFIER = com.amazon.aws.amplify.swift.AnalyticsHostAppWatch.watchkitapp;
933933
PRODUCT_NAME = "$(TARGET_NAME)";
934934
SDKROOT = watchos;
935-
SKIP_INSTALL = YES;
935+
SKIP_INSTALL = NO;
936936
SWIFT_EMIT_LOC_STRINGS = YES;
937937
SWIFT_VERSION = 5.0;
938938
TARGETED_DEVICE_FAMILY = 4;

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/WebAuthn/PlatformWebAuthnCredentials.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,12 @@ extension PlatformWebAuthnCredentials: CredentialRegistrantProtocol {
113113
name: options.user.name,
114114
userID: options.user.id
115115
)
116+
#if !os(visionOS)
117+
// `excludedCredentials` is not available on visionOS
116118
platformKeyRequest.excludedCredentials = options.excludeCredentials.compactMap { credential in
117119
return .init(credentialID: credential.id)
118120
}
121+
#endif
119122

120123
return try await withCheckedThrowingContinuation { continuation in
121124
registrationContinuation = continuation

AmplifyPlugins/Auth/Tests/AuthWebAuthnApp/AuthWebAuthnAppUITests/AuthWebAuthnAppUITests.swift

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import XCTest
99

1010
final class AuthWebAuthnAppUITests: XCTestCase {
11-
private let timeout = TimeInterval(6)
11+
private let timeout = TimeInterval(30)
1212
private let app = XCUIApplication()
1313
private var username: String!
1414
private var signUpButton: XCUIElement!
@@ -72,20 +72,14 @@ final class AuthWebAuthnAppUITests: XCTestCase {
7272
@MainActor
7373
func testWebAuthnAPIs() async throws {
7474
// 1. Associate new WebAuthn Credential
75+
let associateContinueButton = springboard.otherElements["ASAuthorizationControllerContinueButton"]
7576
let associateAttempt = await attempt {
7677
associateButton.tap()
77-
return !waitForResult("Associate WebAuthn Credential failed:", timeout: 1)
78+
return associateContinueButton.waitForExistence(timeout: timeout)
7879
}
7980

8081
guard associateAttempt else {
81-
XCTFail("Failed to trigger the Associate WebAuthn Credential workflow: \(lastResult)")
82-
return
83-
}
84-
85-
// Wait for the "Continue" button to appear in the FaceID popover and tap it
86-
let associateContinueButton = springboard.otherElements["ASAuthorizationControllerContinueButton"]
87-
guard associateContinueButton.waitForExistence(timeout: timeout) else {
88-
XCTFail("Failed to find the 'Continue' button to Associate new WebAuthn credential")
82+
XCTFail("Failed to find the 'Continue' button to Associate new WebAuthn credential: \(lastResult)")
8983
return
9084
}
9185
associateContinueButton.tap()
@@ -112,20 +106,14 @@ final class AuthWebAuthnAppUITests: XCTestCase {
112106
}
113107

114108
// 4. Sign in with WebAuthn
109+
let signInContinueButton = springboard.otherElements["ASAuthorizationControllerContinueButton"]
115110
let signInAttempt = await attempt {
116111
signInButton.tap()
117-
return !waitForResult("Sign In failed:", timeout: 1)
112+
return signInContinueButton.waitForExistence(timeout: timeout)
118113
}
119114

120115
guard signInAttempt else {
121-
XCTFail("Failed to trigger the Assert WebAuthn Credential workflow: \(lastResult)")
122-
return
123-
}
124-
125-
// Wait for the "Continue" button to appear in the FaceID popover
126-
let signInContinueButton = springboard.otherElements["ASAuthorizationControllerContinueButton"]
127-
guard signInContinueButton.waitForExistence(timeout: timeout) else {
128-
XCTFail("Failed to find the 'Continue' button to Sign In with WebAuthn")
116+
XCTFail("Failed to find the 'Continue' button to Sign In with WebAuthn: \(lastResult)")
129117
return
130118
}
131119

AmplifyPlugins/Auth/Tests/AuthWebAuthnApp/LocalServer/index.mjs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,21 @@ app.use(express.json())
66

77
const bundleId = "com.amazon.aws.amplify.swift.AuthWebAuthnApp"
88

9-
const run = (cmd) => {
9+
// Simulator device identifiers are either a UUID (UDID) or the literal "booted".
10+
// Validating up front rejects any value that could be used to smuggle shell
11+
// metacharacters into the commands below.
12+
const deviceIdPattern = /^([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}|booted)$/
13+
14+
const isValidDeviceId = (deviceId) => typeof deviceId === "string" && deviceIdPattern.test(deviceId)
15+
16+
// Run a command without invoking a shell. Arguments are passed as an array so
17+
// user-supplied values (e.g. deviceId) are never interpreted by /bin/sh,
18+
// preventing command injection.
19+
const run = (file, args) => {
1020
return new Promise((resolve, reject) => {
11-
childProcess.exec(cmd, (error, stdout, stderror) => {
21+
childProcess.execFile(file, args, (error, stdout, stderror) => {
1222
if (error) {
13-
console.warn("Failed to execute cmd:", cmd)
23+
console.warn("Failed to execute:", file, args)
1424
reject(stderror)
1525
} else {
1626
resolve(stdout)
@@ -22,9 +32,11 @@ const run = (cmd) => {
2232
app.post('/uninstall', async (req, res) => {
2333
console.log("POST /uninstall ")
2434
const { deviceId } = req.body
35+
if (!isValidDeviceId(deviceId)) {
36+
return res.status(400).send("Invalid deviceId")
37+
}
2538
try {
26-
const cmd = `xcrun simctl uninstall ${deviceId} ${bundleId}`
27-
await run(cmd)
39+
await run("xcrun", ["simctl", "uninstall", deviceId, bundleId])
2840
res.send("Done")
2941
} catch (error) {
3042
console.error("Failed to uninstall app", error)
@@ -35,9 +47,11 @@ app.post('/uninstall', async (req, res) => {
3547
app.post('/boot', async (req, res) => {
3648
console.log("POST /boot ")
3749
const { deviceId } = req.body
50+
if (!isValidDeviceId(deviceId)) {
51+
return res.status(400).send("Invalid deviceId")
52+
}
3853
try {
39-
const cmd = `xcrun simctl bootstatus ${deviceId} -b`
40-
await run(cmd)
54+
await run("xcrun", ["simctl", "bootstatus", deviceId, "-b"])
4155
res.send("Done")
4256
} catch (error) {
4357
console.error("Failed to boot the device", error)
@@ -48,9 +62,12 @@ app.post('/boot', async (req, res) => {
4862
app.post('/enroll', async (req, res) => {
4963
console.log("POST /enroll ")
5064
const { deviceId } = req.body
65+
if (!isValidDeviceId(deviceId)) {
66+
return res.status(400).send("Invalid deviceId")
67+
}
5168
try {
52-
const cmd = `xcrun simctl spawn ${deviceId} notifyutil -s com.apple.BiometricKit.enrollmentChanged '1' && xcrun simctl spawn ${deviceId} notifyutil -p com.apple.BiometricKit.enrollmentChanged`
53-
await run(cmd)
69+
await run("xcrun", ["simctl", "spawn", deviceId, "notifyutil", "-s", "com.apple.BiometricKit.enrollmentChanged", "1"])
70+
await run("xcrun", ["simctl", "spawn", deviceId, "notifyutil", "-p", "com.apple.BiometricKit.enrollmentChanged"])
5471
res.send("Done")
5572
} catch (error) {
5673
console.error("Failed to enroll biometrics in the device", error)
@@ -62,9 +79,11 @@ app.post('/enroll', async (req, res) => {
6279
app.post('/match', async (req, res) => {
6380
console.log("POST /match ")
6481
const { deviceId } = req.body
82+
if (!isValidDeviceId(deviceId)) {
83+
return res.status(400).send("Invalid deviceId")
84+
}
6585
try {
66-
const cmd = `xcrun simctl spawn ${deviceId} notifyutil -p com.apple.BiometricKit_Sim.fingerTouch.match`
67-
await run(cmd)
86+
await run("xcrun", ["simctl", "spawn", deviceId, "notifyutil", "-p", "com.apple.BiometricKit_Sim.fingerTouch.match"])
6887
res.send("Done")
6988
} catch (error) {
7089
console.error("Failed to match biometrics", error)

AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthAWSCredentialsProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public protocol AWSCredentialsProvider {
4444
- accessKeyId: A unique identifier.
4545
- secretAccessKey: A secret key used to sign requests cryptographically.
4646
*/
47-
public protocol AWSCredentials {
47+
public protocol AWSCredentials: Sendable {
4848

4949
/// A unique identifier.
5050
var accessKeyId: String { get }

AmplifyPlugins/Notifications/Push/Tests/PushNotificationHostApp/LocalServer/index.mjs

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,21 @@ app.use(express.json())
66

77
const bundleId = "com.aws.amplify.notification.PushNotificationHostApp"
88

9-
const run = (cmd) => {
9+
// Simulator device identifiers are either a UUID (UDID) or the literal "booted".
10+
// Validating up front rejects any value that could be used to smuggle shell
11+
// metacharacters into the commands below.
12+
const deviceIdPattern = /^([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}|booted)$/
13+
14+
const isValidDeviceId = (deviceId) => typeof deviceId === "string" && deviceIdPattern.test(deviceId)
15+
16+
// Run a command without invoking a shell. Arguments are passed as an array so
17+
// user-supplied values (e.g. deviceId) are never interpreted by /bin/sh,
18+
// preventing command injection.
19+
const run = (file, args) => {
1020
return new Promise((resolve, reject) => {
11-
childProcess.exec(cmd, (error, stdout, stderror) => {
21+
childProcess.execFile(file, args, (error, stdout, stderror) => {
1222
if (error) {
13-
console.warn("Failed to execute cmd:", cmd)
23+
console.warn("Failed to execute:", file, args)
1424
reject(stderror)
1525
} else {
1626
resolve(stdout)
@@ -19,6 +29,23 @@ const run = (cmd) => {
1929
})
2030
}
2131

32+
// Run a command without a shell and feed the given string to its stdin.
33+
// Used in place of `echo '<json>' | xcrun simctl push ... -` so the payload
34+
// never passes through a shell.
35+
const runWithStdin = (file, args, input) => {
36+
return new Promise((resolve, reject) => {
37+
const child = childProcess.execFile(file, args, (error, stdout, stderror) => {
38+
if (error) {
39+
console.warn("Failed to execute:", file, args)
40+
reject(stderror)
41+
} else {
42+
resolve(stdout)
43+
}
44+
})
45+
child.stdin.end(input)
46+
})
47+
}
48+
2249
/**
2350
* Trigger a new push notification.
2451
* Run `xcrun simctl push ...` command under the hood
@@ -35,6 +62,10 @@ app.post("/notifications", async (req, res) => {
3562
deviceId
3663
} = req.body
3764

65+
if (!isValidDeviceId(deviceId)) {
66+
return res.status(400).send("Invalid deviceId")
67+
}
68+
3869
const apns = {
3970
aps: {
4071
alert: {
@@ -46,8 +77,9 @@ app.post("/notifications", async (req, res) => {
4677
data: data ?? {}
4778
}
4879
try {
49-
const cmd = `echo '${JSON.stringify(apns)}' | xcrun simctl push ${deviceId} ${bundleId} -`
50-
await run(cmd)
80+
// Read the payload from stdin ("-") rather than interpolating it into a
81+
// shell pipeline.
82+
await runWithStdin("xcrun", ["simctl", "push", deviceId, bundleId, "-"], JSON.stringify(apns))
5183
res.send("Done")
5284
} catch (error) {
5385
console.log("Failed to trigger notification", error)
@@ -59,9 +91,11 @@ app.post("/notifications", async (req, res) => {
5991
app.post('/uninstall', async (req, res) => {
6092
console.log("POST /uninstall ")
6193
const { deviceId } = req.body
94+
if (!isValidDeviceId(deviceId)) {
95+
return res.status(400).send("Invalid deviceId")
96+
}
6297
try {
63-
const cmd = `xcrun simctl uninstall ${deviceId} ${bundleId}`
64-
await run(cmd)
98+
await run("xcrun", ["simctl", "uninstall", deviceId, bundleId])
6599
res.send("Done")
66100
} catch (error) {
67101
console.error("Failed to uninstall app", error)
@@ -72,9 +106,11 @@ app.post('/uninstall', async (req, res) => {
72106
app.post('/boot', async (req, res) => {
73107
console.log("POST /boot ")
74108
const { deviceId } = req.body
109+
if (!isValidDeviceId(deviceId)) {
110+
return res.status(400).send("Invalid deviceId")
111+
}
75112
try {
76-
const cmd = `xcrun simctl bootstatus ${deviceId} -b`
77-
await run(cmd)
113+
await run("xcrun", ["simctl", "bootstatus", deviceId, "-b"])
78114
res.send("Done")
79115
} catch (error) {
80116
console.error("Failed to boot the device", error)
@@ -84,4 +120,4 @@ app.post('/boot', async (req, res) => {
84120

85121
app.listen(9293, () => {
86122
console.log("Starting server")
87-
})
123+
})

AmplifyPlugins/Notifications/Push/Tests/PushNotificationHostApp/PushNotificationHostApp.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@
868868
PRODUCT_BUNDLE_IDENTIFIER = com.aws.amplify.notification.PushNotificationHostApp;
869869
PRODUCT_NAME = "$(TARGET_NAME)";
870870
SDKROOT = watchos;
871-
SKIP_INSTALL = YES;
871+
SKIP_INSTALL = NO;
872872
SWIFT_EMIT_LOC_STRINGS = YES;
873873
SWIFT_VERSION = 5.0;
874874
TARGETED_DEVICE_FAMILY = 4;
@@ -899,7 +899,7 @@
899899
PRODUCT_BUNDLE_IDENTIFIER = com.aws.amplify.notification.PushNotificationHostApp;
900900
PRODUCT_NAME = "$(TARGET_NAME)";
901901
SDKROOT = watchos;
902-
SKIP_INSTALL = YES;
902+
SKIP_INSTALL = NO;
903903
SWIFT_EMIT_LOC_STRINGS = YES;
904904
SWIFT_VERSION = 5.0;
905905
TARGETED_DEVICE_FAMILY = 4;

AmplifyPlugins/Predictions/AWSPredictionsPlugin/Signing/SigV4Signer.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,13 @@ struct SigV4Signer {
202202
serviceName: serviceName
203203
)
204204

205+
let existingQuery = URLComponents(url: url, resolvingAgainstBaseURL: false)?
206+
.queryItems?
207+
.map { "\($0.name)=\($0.value ?? "")" }
208+
.joined(separator: "&")
209+
205210
let canonicalQueryString = _canonicalQueryString(
206-
query: url.query,
211+
query: existingQuery,
207212
signedHeaders: signedHeaders,
208213
timestamp: timestamp,
209214
credentialScope: credentialScope,
@@ -325,7 +330,7 @@ struct SigV4Signer {
325330

326331
let sorted = canonicalQueryString.split(separator: "&")
327332
.map {
328-
String($0).split(separator: "=")
333+
String($0).split(separator: "=", maxSplits: 1)
329334
.map(String.init)
330335
.map(PercentEncoding.uri.encode)
331336
.joined(separator: "=")

0 commit comments

Comments
 (0)