diff --git a/swift/example_code/identity-resolvers/cognito-resolver/Package.swift b/swift/example_code/identity-resolvers/cognito-resolver/Package.swift new file mode 100644 index 00000000000..458af4b323c --- /dev/null +++ b/swift/example_code/identity-resolvers/cognito-resolver/Package.swift @@ -0,0 +1,42 @@ +// swift-tools-version:5.9 +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// The swift-tools-version declares the minimum version of Swift required to +// build this package. + +import PackageDescription + +let package = Package( + name: "cognito-resolver", + // Let Xcode know the minimum Apple platforms supported. + platforms: [ + .macOS(.v11), + .iOS(.v13) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package( + url: "https://github.com/awslabs/aws-sdk-swift", + from: "1.0.0" + ), + .package( + url: "https://github.com/apple/swift-argument-parser.git", + branch: "main" + ), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "cognito-resolver", + dependencies: [ + .product(name: "AWSCognitoIdentity", package: "aws-sdk-swift"), + .product(name: "AWSIAM", package: "aws-sdk-swift"), + .product(name: "AWSSTS", package: "aws-sdk-swift"), + .product(name: "AWSS3", package: "aws-sdk-swift"), + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ], + path: "Sources"), + ] +) diff --git a/swift/example_code/identity-resolvers/cognito-resolver/Sources/Example.swift b/swift/example_code/identity-resolvers/cognito-resolver/Sources/Example.swift new file mode 100644 index 00000000000..ea3f95c0e16 --- /dev/null +++ b/swift/example_code/identity-resolvers/cognito-resolver/Sources/Example.swift @@ -0,0 +1,255 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[swift.identity.cognito.imports] +import AWSCognitoIdentity +import AWSSDKIdentity +import AWSSTS +// snippet-end:[swift.identity.cognito.imports] +import AWSIAM +import AWSS3 +import Foundation + +/// Contains the data and code for the main body of the example. +class Example { + let region: String + + var cognitoIdentityClient: CognitoIdentityClient! + var iamClient: IAMClient! + var roleName: String + + /// The name of the AWS Cognito Identity Pool to use. + let identityPoolName: String + /// The ID of the Identity Pool. + var identityPoolID: String! + + /// The name of the managed policy granting Amazon S3 permissions. + let managedPolicyName: String + /// The ARN of the managed policy granting S3 permissions. + var managedPolicyArn: String? + + /// Initialize the example. + /// + /// - Parameter region: The AWS Region to operate in. + /// + /// - Throws: Any AWS errors thrown by IAM or Cognito. + /// + /// ^ Note: IAM must always use `us-east-1`, so it doesn't use the value + /// of the `region` parameter. + init(region: String) throws { + self.region = region + + self.identityPoolName = "cognito-resolver-example-\(UUID().uuidString.split(separator: "-").first!.lowercased())" + cognitoIdentityClient = try CognitoIdentityClient(region: region) + + self.roleName = "cognito-unauth-\(identityPoolName)" + iamClient = try IAMClient(region: "us-east-1") + + self.managedPolicyName = "cognito-policy-\(identityPoolName)" + } + + /// The body of the example. + /// + /// - Throws: Errors from IAM, STS, or Cognito. + func run() async throws { + // Set up the cleanup function to run automatically when this object + // is discarded. This way, we clean up AWS artifacts whether the run + // is successful or an error occurs. + + defer { + blocking { + await self.cleanup() + } + } + + // Create an identity pool to use for this example. + + print("Creating a Cognito identity pool named \(identityPoolName)...") + identityPoolID = try await cognitoIdentityClient.createIdentityPool( + input: CreateIdentityPoolInput( + allowUnauthenticatedIdentities: true, + identityPoolName: identityPoolName + ) + ).identityPoolId + + // Create an IAM role for unauthenticated users. + + let trustPolicy = """ + { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": {"Federated": "cognito-identity.amazonaws.com"}, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": {"cognito-identity.amazonaws.com:aud": "\(identityPoolID!)"}, + "ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "unauthenticated"} + } + }] + } + """ + + print("Creating an IAM role named \(roleName)...") + let createRoleInput = CreateRoleInput( + assumeRolePolicyDocument: trustPolicy, + roleName: roleName + ) + let createRoleOutput = try await iamClient.createRole(input: createRoleInput) + + guard let role = createRoleOutput.role else { + print("*** No role returned by CreateRole!") + return + } + + // Wait for the role to be available. + + print("Waiting for the role to be available...") + try await Task.sleep(nanoseconds: 10_000_000_000) // Wait 10 seconds + + // Assign the role to the identity pool. + + print("Setting the identity pool's roles...") + _ = try await cognitoIdentityClient.setIdentityPoolRoles( + input: SetIdentityPoolRolesInput( + identityPoolId: identityPoolID, + roles: ["unauthenticated": role.arn!] + ) + ) + + //====================================================================== + // Resolve an identity using the Cognito credential identity resolver + // with the AWS STS function getCallerIdentity(input:). This is done + // by configuring the STS client to use the Cognito credentials + // resolver. + //====================================================================== + + // snippet-start:[swift.identity.cognito.resolve] + // Create a Cognito credential resolver that uses the Cognito Identity + // Pool created above. + let cognitoCredentialResolver = try CognitoAWSCredentialIdentityResolver( + identityPoolId: identityPoolID, + identityPoolRegion: region + ) + + // Create an AWS STS client that uses the new Cognito credential + // resolver to do credential identity resolution. + let cognitoSTSConfig = try await STSClient.STSClientConfiguration( + awsCredentialIdentityResolver: cognitoCredentialResolver, + region: "us-east-1" + ) + let cognitoSTSClient = STSClient(config: cognitoSTSConfig) + + let output = try await cognitoSTSClient.getCallerIdentity( + input: GetCallerIdentityInput() + ) + + print("Authenticated with AWS using Cognito!") + print(" ARN: \(output.arn ?? "")") + print(" Account ID: \(output.account ?? "")") + print(" User ID: \(output.userId ?? "")") + // snippet-end:[swift.identity.cognito.resolve] + + //====================================================================== + // Add a managed policy to the role to allow access to the AWS S3 + // function ListBuckets. + //====================================================================== + + print("Creating a managed policy to allow listing S3 buckets...") + let createPolicyOutput = try await iamClient.createPolicy( + input: CreatePolicyInput( + policyDocument: """ + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:ListAllMyBuckets", + "Resource": "arn:aws:s3:::*" + } + ] + } + """, + policyName: managedPolicyName + ) + ) + + guard let managedPolicy = createPolicyOutput.policy else { + print("No policy returned by CreatePolicy!") + return + } + + managedPolicyArn = managedPolicy.arn + + print("Attaching the policy to the IAM role...") + _ = try await iamClient.attachRolePolicy( + input: AttachRolePolicyInput( + policyArn: managedPolicy.arn, + roleName: roleName + ) + ) + + // Wait for the policy to attach. + + print("Waiting for the policy to attach to the role...") + try await Task.sleep(nanoseconds: 10_000_000_000) // Wait 10 seconds + + //====================================================================== + // This is where you can do tasks using the returned AWS credentials. + // In this example, we list S3 buckets. + //====================================================================== + + // snippet-start:[swift.identity.cognito.s3] + let s3Config = try await S3Client.S3ClientConfiguration( + awsCredentialIdentityResolver: cognitoCredentialResolver, + region: region + ) + let s3Client = S3Client(config: s3Config) + + let listBucketsOutput = try await s3Client.listBuckets( + input: ListBucketsInput() + ) + // snippet-end:[swift.identity.cognito.s3] + guard let buckets = listBucketsOutput.buckets else { + print("No buckets returned by S3!") + return + } + + print("Found \(buckets.count) S3 buckets:") + for bucket in buckets { + print(" \(bucket.name ?? "")") + } + } + + /// Clean up by deleting AWS assets created by the example. Ignores + /// errors since this is just simple cleanup work. + func cleanup() async { + print("Deleting the identity pool...") + _ = try? await cognitoIdentityClient.deleteIdentityPool( + input: DeleteIdentityPoolInput(identityPoolId: identityPoolID) + ) + + print("Deleting the policy...") + if managedPolicyArn != nil { + _ = try? await iamClient.deletePolicy( + input: DeletePolicyInput(policyArn: managedPolicyArn) + ) + } + + print ("Deleting the IAM role...") + _ = try? await iamClient.deleteRole( + input: DeleteRoleInput(roleName: roleName) + ) + } + + /// Create a function that blocks the caller until execution is complete. + /// + /// - Parameter block: The function to call and wait for its return. + private func blocking(_ block: @escaping @Sendable () async -> Void) { + let semaphore = DispatchSemaphore(value: 0) + Task { + await block() + semaphore.signal() + } + semaphore.wait() + } +} \ No newline at end of file diff --git a/swift/example_code/identity-resolvers/cognito-resolver/Sources/entry.swift b/swift/example_code/identity-resolvers/cognito-resolver/Sources/entry.swift new file mode 100644 index 00000000000..917861d533e --- /dev/null +++ b/swift/example_code/identity-resolvers/cognito-resolver/Sources/entry.swift @@ -0,0 +1,57 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +/// A simple example that shows how to use the AWS SDK for Swift to +/// authenticate using Amazon Cognito. + +// snippet-start:[swift.identity.cognito.imports] +import ArgumentParser +import AWSCognitoIdentity +import AWSIAM +import AWSS3 +import AWSSDKIdentity +import AWSSTS +import Foundation +import SmithyIdentity +// snippet-end:[swift.identity.cognito.imports] + +struct ExampleCommand: ParsableCommand { + @Option(help: "AWS Region name") + var region = "us-east-1" + + static var configuration = CommandConfiguration( + commandName: "cognito-resolver", + abstract: """ + Demonstrates how to use a Cognito credential identity resolver with the + AWS SDK for Swift. + """, + discussion: """ + """ + ) + + /// Called by ``main()`` to do the actual running of the AWS + /// example. + func runAsync() async throws { + let example = try Example(region: region) + + try await example.run() + } +} + +/// The program's asynchronous entry point. +@main +struct Main { + /// The function that serves as the main asynchronous entry point for the + /// example. It parses the command line using the Swift Argument Parser, + /// then calls the `runAsync()` function to run the example itself. + static func main() async { + let args = Array(CommandLine.arguments.dropFirst()) + + do { + let command = try ExampleCommand.parse(args) + try await command.runAsync() + } catch { + ExampleCommand.exit(withError: error) + } + } +}