diff --git a/dotnetv3/Cognito/README.md b/dotnetv3/Cognito/README.md index eb9c4e7777b..9158cda1f7e 100644 --- a/dotnetv3/Cognito/README.md +++ b/dotnetv3/Cognito/README.md @@ -34,7 +34,7 @@ These examples also require the following resources: To create these resources, run the AWS CloudFormation script in the -[resources/cdk/cognito_scenario_user_pool_with_mfa](../../../resources/cdk/cognito_scenario_user_pool_with_mfa) +[resources/cdk/cognito_scenario_user_pool_with_mfa](../../resources/cdk/cognito_scenario_user_pool_with_mfa) folder. This script outputs a user pool ID and a client ID that you can use to run the scenario. diff --git a/dotnetv4/Aurora/Actions/AuroraWrapper.cs b/dotnetv4/Aurora/Actions/AuroraWrapper.cs index 18c7646cc9e..9f469df9fb2 100644 --- a/dotnetv4/Aurora/Actions/AuroraWrapper.cs +++ b/dotnetv4/Aurora/Actions/AuroraWrapper.cs @@ -124,7 +124,7 @@ public async Task ModifyIntegerParametersInGroupAsync(string groupName, { foreach (var p in parameters) { - if (p.IsModifiable.Value && p.DataType == "integer") + if (p.IsModifiable.GetValueOrDefault() && p.DataType == "integer") { while (newValue == 0) { diff --git a/dotnetv4/Cognito/Actions/CognitoActions.csproj b/dotnetv4/Cognito/Actions/CognitoActions.csproj new file mode 100644 index 00000000000..653035419c0 --- /dev/null +++ b/dotnetv4/Cognito/Actions/CognitoActions.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + diff --git a/dotnetv4/Cognito/Actions/CognitoWrapper.cs b/dotnetv4/Cognito/Actions/CognitoWrapper.cs new file mode 100644 index 00000000000..188a6bb1cd2 --- /dev/null +++ b/dotnetv4/Cognito/Actions/CognitoWrapper.cs @@ -0,0 +1,347 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[Cognito.dotnetv4.CognitoWrapper] +using System.Net; + +namespace CognitoActions; + +/// +/// Methods to perform Amazon Cognito Identity Provider actions. +/// +public class CognitoWrapper +{ + private readonly IAmazonCognitoIdentityProvider _cognitoService; + + /// + /// Constructor for the wrapper class containing Amazon Cognito actions. + /// + /// The Amazon Cognito client object. + public CognitoWrapper(IAmazonCognitoIdentityProvider cognitoService) + { + _cognitoService = cognitoService; + } + + // snippet-start:[Cognito.dotnetv4.ListUserPools] + /// + /// List the Amazon Cognito user pools for an account. + /// + /// A list of UserPoolDescriptionType objects. + public async Task> ListUserPoolsAsync() + { + var userPools = new List(); + + var userPoolsPaginator = _cognitoService.Paginators.ListUserPools(new ListUserPoolsRequest()); + + await foreach (var response in userPoolsPaginator.Responses) + { + userPools.AddRange(response.UserPools); + } + + return userPools; + } + + // snippet-end:[Cognito.dotnetv4.ListUserPools] + + // snippet-start:[Cognito.dotnetv4.ListUsers] + /// + /// Get a list of users for the Amazon Cognito user pool. + /// + /// The user pool ID. + /// A list of users. + public async Task> ListUsersAsync(string userPoolId) + { + var request = new ListUsersRequest + { + UserPoolId = userPoolId + }; + + var users = new List(); + + var usersPaginator = _cognitoService.Paginators.ListUsers(request); + await foreach (var response in usersPaginator.Responses) + { + users.AddRange(response.Users); + } + + return users; + } + + // snippet-end:[Cognito.dotnetv4.ListUsers] + + // snippet-start:[Cognito.dotnetv4.AdminRespondToAuthChallenge] + /// + /// Respond to an admin authentication challenge. + /// + /// The name of the user. + /// The client ID. + /// The multi-factor authentication code. + /// The current application session. + /// The user pool ID. + /// The result of the authentication response. + public async Task AdminRespondToAuthChallengeAsync( + string userName, + string clientId, + string mfaCode, + string session, + string userPoolId) + { + Console.WriteLine("SOFTWARE_TOKEN_MFA challenge is generated"); + + var challengeResponses = new Dictionary(); + challengeResponses.Add("USERNAME", userName); + challengeResponses.Add("SOFTWARE_TOKEN_MFA_CODE", mfaCode); + + var respondToAuthChallengeRequest = new AdminRespondToAuthChallengeRequest + { + ChallengeName = ChallengeNameType.SOFTWARE_TOKEN_MFA, + ClientId = clientId, + ChallengeResponses = challengeResponses, + Session = session, + UserPoolId = userPoolId, + }; + + var response = await _cognitoService.AdminRespondToAuthChallengeAsync(respondToAuthChallengeRequest); + Console.WriteLine($"Response to Authentication {response.AuthenticationResult.TokenType}"); + return response.AuthenticationResult; + } + + // snippet-end:[Cognito.dotnetv4.AdminRespondToAuthChallenge] + + // snippet-start:[Cognito.dotnetv4.VerifySoftwareToken] + /// + /// Verify the TOTP and register for MFA. + /// + /// The name of the session. + /// The MFA code. + /// The status of the software token. + public async Task VerifySoftwareTokenAsync(string session, string code) + { + var tokenRequest = new VerifySoftwareTokenRequest + { + UserCode = code, + Session = session, + }; + + var verifyResponse = await _cognitoService.VerifySoftwareTokenAsync(tokenRequest); + + return verifyResponse.Status; + } + + // snippet-end:[Cognito.dotnetv4.VerifySoftwareToken] + + // snippet-start:[Cognito.dotnetv4.AssociateSoftwareToken] + /// + /// Get an MFA token to authenticate the user with the authenticator. + /// + /// The session name. + /// The session name. + public async Task AssociateSoftwareTokenAsync(string session) + { + var softwareTokenRequest = new AssociateSoftwareTokenRequest + { + Session = session, + }; + + var tokenResponse = await _cognitoService.AssociateSoftwareTokenAsync(softwareTokenRequest); + var secretCode = tokenResponse.SecretCode; + + Console.WriteLine($"Use the following secret code to set up the authenticator: {secretCode}"); + + return tokenResponse.Session; + } + + // snippet-end:[Cognito.dotnetv4.AssociateSoftwareToken] + + // snippet-start:[Cognito.dotnetv4.AdminInitiateAuth] + /// + /// Initiate an admin auth request. + /// + /// The client ID to use. + /// The ID of the user pool. + /// The username to authenticate. + /// The user's password. + /// The session to use in challenge-response. + public async Task AdminInitiateAuthAsync(string clientId, string userPoolId, string userName, string password) + { + var authParameters = new Dictionary(); + authParameters.Add("USERNAME", userName); + authParameters.Add("PASSWORD", password); + + var request = new AdminInitiateAuthRequest + { + ClientId = clientId, + UserPoolId = userPoolId, + AuthParameters = authParameters, + AuthFlow = AuthFlowType.ADMIN_USER_PASSWORD_AUTH, + }; + + var response = await _cognitoService.AdminInitiateAuthAsync(request); + return response.Session; + } + // snippet-end:[Cognito.dotnetv4.AdminInitiateAuth] + + // snippet-start:[Cognito.dotnetv4.InitiateAuth] + /// + /// Initiate authorization. + /// + /// The client Id of the application. + /// The name of the user who is authenticating. + /// The password for the user who is authenticating. + /// The response from the initiate auth request. + public async Task InitiateAuthAsync(string clientId, string userName, string password) + { + var authParameters = new Dictionary(); + authParameters.Add("USERNAME", userName); + authParameters.Add("PASSWORD", password); + + var authRequest = new InitiateAuthRequest + + { + ClientId = clientId, + AuthParameters = authParameters, + AuthFlow = AuthFlowType.USER_PASSWORD_AUTH, + }; + + var response = await _cognitoService.InitiateAuthAsync(authRequest); + Console.WriteLine($"Result Challenge is : {response.ChallengeName}"); + + return response; + } + // snippet-end:[Cognito.dotnetv4.InitiateAuth] + + // snippet-start:[Cognito.dotnetv4.ConfirmSignUp] + /// + /// Confirm that the user has signed up. + /// + /// The Id of this application. + /// The confirmation code sent to the user. + /// The username. + /// True if successful. + public async Task ConfirmSignupAsync(string clientId, string code, string userName) + { + var signUpRequest = new ConfirmSignUpRequest + { + ClientId = clientId, + ConfirmationCode = code, + Username = userName, + }; + + var response = await _cognitoService.ConfirmSignUpAsync(signUpRequest); + if (response.HttpStatusCode == HttpStatusCode.OK) + { + Console.WriteLine($"{userName} was confirmed"); + return true; + } + return false; + } + + // snippet-end:[Cognito.dotnetv4.ConfirmSignUp] + + // snippet-start:[Cognito.dotnetv4.ConfirmDevice] + /// + /// Initiates and confirms tracking of the device. + /// + /// The user's access token. + /// The key of the device from Amazon Cognito. + /// The device name. + /// + public async Task ConfirmDeviceAsync(string accessToken, string deviceKey, string deviceName) + { + var request = new ConfirmDeviceRequest + { + AccessToken = accessToken, + DeviceKey = deviceKey, + DeviceName = deviceName + }; + + var response = await _cognitoService.ConfirmDeviceAsync(request); + return response.UserConfirmationNecessary; + } + + // snippet-end:[Cognito.dotnetv4.ConfirmDevice] + + // snippet-start:[Cognito.dotnetv4.ResendConfirmationCode] + /// + /// Send a new confirmation code to a user. + /// + /// The Id of the client application. + /// The username of user who will receive the code. + /// The delivery details. + public async Task ResendConfirmationCodeAsync(string clientId, string userName) + { + var codeRequest = new ResendConfirmationCodeRequest + { + ClientId = clientId, + Username = userName, + }; + + var response = await _cognitoService.ResendConfirmationCodeAsync(codeRequest); + + Console.WriteLine($"Method of delivery is {response.CodeDeliveryDetails.DeliveryMedium}"); + + return response.CodeDeliveryDetails; + } + + // snippet-end:[Cognito.dotnetv4.ResendConfirmationCode] + + // snippet-start:[Cognito.dotnetv4.GetAdminUser] + /// + /// Get the specified user from an Amazon Cognito user pool with administrator access. + /// + /// The name of the user. + /// The Id of the Amazon Cognito user pool. + /// Async task. + public async Task GetAdminUserAsync(string userName, string poolId) + { + AdminGetUserRequest userRequest = new AdminGetUserRequest + { + Username = userName, + UserPoolId = poolId, + }; + + var response = await _cognitoService.AdminGetUserAsync(userRequest); + + Console.WriteLine($"User status {response.UserStatus}"); + return response.UserStatus; + } + + // snippet-end:[Cognito.dotnetv4.GetAdminUser] + + // snippet-start:[Cognito.dotnetv4.SignUp] + /// + /// Sign up a new user. + /// + /// The client Id of the application. + /// The username to use. + /// The user's password. + /// The email address of the user. + /// A Boolean value indicating whether the user was confirmed. + public async Task SignUpAsync(string clientId, string userName, string password, string email) + { + var userAttrs = new AttributeType + { + Name = "email", + Value = email, + }; + + var userAttrsList = new List(); + + userAttrsList.Add(userAttrs); + + var signUpRequest = new SignUpRequest + { + UserAttributes = userAttrsList, + Username = userName, + ClientId = clientId, + Password = password + }; + + var response = await _cognitoService.SignUpAsync(signUpRequest); + return response.HttpStatusCode == HttpStatusCode.OK; + } + + // snippet-end:[Cognito.dotnetv4.SignUp] +} + +// snippet-end:[Cognito.dotnetv4.CognitoWrapper] \ No newline at end of file diff --git a/dotnetv4/Cognito/Actions/HelloCognito.cs b/dotnetv4/Cognito/Actions/HelloCognito.cs new file mode 100644 index 00000000000..230a4d86799 --- /dev/null +++ b/dotnetv4/Cognito/Actions/HelloCognito.cs @@ -0,0 +1,64 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[Cognito.dotnetv4.HelloCognito] + +using LogLevel = Microsoft.Extensions.Logging.LogLevel; + +namespace CognitoActions; + +/// +/// A class that introduces the Amazon Cognito Identity Provider by listing the +/// user pools for the account. +/// +public class HelloCognito +{ + private static ILogger logger = null!; + + static async Task Main(string[] args) + { + // Set up dependency injection for Amazon Cognito. + using var host = Host.CreateDefaultBuilder(args) + .ConfigureLogging(logging => + logging.AddFilter("System", LogLevel.Debug) + .AddFilter("Microsoft", LogLevel.Information) + .AddFilter("Microsoft", LogLevel.Trace)) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + ) + .Build(); + + logger = LoggerFactory.Create(builder => { builder.AddConsole(); }) + .CreateLogger(); + + var amazonClient = host.Services.GetRequiredService(); + + Console.Clear(); + Console.WriteLine("Hello Amazon Cognito."); + Console.WriteLine("Let's get a list of your Amazon Cognito user pools."); + + var userPools = new List(); + + var userPoolsPaginator = amazonClient.Paginators.ListUserPools(new ListUserPoolsRequest()); + + await foreach (var response in userPoolsPaginator.Responses) + { + userPools.AddRange(response.UserPools); + } + + if (userPools.Count > 0) + { + userPools.ForEach(userPool => + { + Console.WriteLine($"{userPool.Name}\t{userPool.Id}"); + }); + } + else + { + Console.WriteLine("No user pools were found."); + } + } +} + +// snippet-end:[Cognito.dotnetv4.HelloCognito] \ No newline at end of file diff --git a/dotnetv4/Cognito/Actions/Usings.cs b/dotnetv4/Cognito/Actions/Usings.cs new file mode 100644 index 00000000000..5b7cea27136 --- /dev/null +++ b/dotnetv4/Cognito/Actions/Usings.cs @@ -0,0 +1,13 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[Cognito.dotnetv3.Usings] +global using Amazon.CognitoIdentityProvider; +global using Amazon.CognitoIdentityProvider.Model; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Logging.Console; +global using Microsoft.Extensions.Logging.Debug; + +// snippet-end:[Cognito.dotnetv3.Usings] \ No newline at end of file diff --git a/dotnetv4/Cognito/CognitoExamples.sln b/dotnetv4/Cognito/CognitoExamples.sln new file mode 100644 index 00000000000..694f56abe02 --- /dev/null +++ b/dotnetv4/Cognito/CognitoExamples.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32630.192 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actions", "Actions", "{7907FB6A-1353-4735-95DC-EEC5DF8C0649}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{B987097B-189C-4D0B-99BC-E67CD705BCA0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5455D423-2AFC-4BC6-B79D-9DC4270D8F7D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CognitoActions", "Actions\CognitoActions.csproj", "{796910FA-6E94-460B-8CB4-97DF01B9ADC8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CognitoBasics", "Scenarios\Cognito_Basics\CognitoBasics.csproj", "{B1731AE1-381F-4044-BEBE-269FF7E24B1F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CognitoTests", "Tests\CognitoTests.csproj", "{6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {796910FA-6E94-460B-8CB4-97DF01B9ADC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {796910FA-6E94-460B-8CB4-97DF01B9ADC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {796910FA-6E94-460B-8CB4-97DF01B9ADC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {796910FA-6E94-460B-8CB4-97DF01B9ADC8}.Release|Any CPU.Build.0 = Release|Any CPU + {B1731AE1-381F-4044-BEBE-269FF7E24B1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1731AE1-381F-4044-BEBE-269FF7E24B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1731AE1-381F-4044-BEBE-269FF7E24B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1731AE1-381F-4044-BEBE-269FF7E24B1F}.Release|Any CPU.Build.0 = Release|Any CPU + {6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {796910FA-6E94-460B-8CB4-97DF01B9ADC8} = {7907FB6A-1353-4735-95DC-EEC5DF8C0649} + {B1731AE1-381F-4044-BEBE-269FF7E24B1F} = {B987097B-189C-4D0B-99BC-E67CD705BCA0} + {6046A2FC-6A39-4C2D-8DD9-AA3740B17B88} = {5455D423-2AFC-4BC6-B79D-9DC4270D8F7D} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {870D888D-5C8B-4057-8722-F73ECF38E513} + EndGlobalSection +EndGlobal diff --git a/dotnetv4/Cognito/README.md b/dotnetv4/Cognito/README.md new file mode 100644 index 00000000000..677b1901dae --- /dev/null +++ b/dotnetv4/Cognito/README.md @@ -0,0 +1,138 @@ +# Amazon Cognito Identity Provider code examples for the SDK for .NET + +## Overview + +Shows how to use the AWS SDK for .NET to work with Amazon Cognito Identity Provider. + + + + +_Amazon Cognito Identity Provider handles user authentication and authorization for your web and mobile apps._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../README.md#Prerequisites) in the `dotnetv4` folder. + + + +These examples also require the following resources: + +* An existing Amazon Cognito user pool that is configured to allow self sign-up. +* A client ID to use for authenticating with Amazon Cognito. + + +To create these resources, run the AWS CloudFormation script in the +[resources/cdk/cognito_scenario_user_pool_with_mfa](../../resources/cdk/cognito_scenario_user_pool_with_mfa) +folder. This script outputs a user pool ID and a client ID that you can use to run +the scenario. + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [AdminGetUser](Actions/CognitoWrapper.cs#L288) +- [AdminInitiateAuth](Actions/CognitoWrapper.cs#L156) +- [AdminRespondToAuthChallenge](Actions/CognitoWrapper.cs#L72) +- [AssociateSoftwareToken](Actions/CognitoWrapper.cs#L133) +- [ConfirmDevice](Actions/CognitoWrapper.cs#L241) +- [ConfirmSignUp](Actions/CognitoWrapper.cs#L213) +- [InitiateAuth](Actions/CognitoWrapper.cs#L184) +- [ListUserPools](Actions/CognitoWrapper.cs#L25) +- [ListUsers](Actions/CognitoWrapper.cs#L46) +- [ResendConfirmationCode](Actions/CognitoWrapper.cs#L264) +- [SignUp](Actions/CognitoWrapper.cs#L311) +- [VerifySoftwareToken](Actions/CognitoWrapper.cs#L111) + +### Scenarios + +Code examples that show you how to accomplish a specific task by calling multiple +functions within the same service. + +- [Sign up a user with a user pool that requires MFA](Actions/CognitoWrapper.cs) + + + + + +## Run the examples + +### Instructions + +For general instructions to run the examples, see the +[README](../README.md#building-and-running-the-code-examples) in the `dotnetv4` folder. + +Some projects might include a settings.json file. Before compiling the project, +you can change these values to match your own account and resources. Alternatively, +add a settings.local.json file with your local settings, which will be loaded automatically +when the application runs. + +After the example compiles, you can run it from the command line. To do so, navigate to +the folder that contains the .csproj file and run the following command: + +``` +dotnet run +``` + +Alternatively, you can run the example from within your IDE. + + + + + + + +#### Sign up a user with a user pool that requires MFA + +This example shows you how to do the following: + +- Sign up and confirm a user with a username, password, and email address. +- Set up multi-factor authentication by associating an MFA application with the user. +- Sign in by using a password and an MFA code. + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../README.md#Tests) +in the `dotnetv4` folder. + + + + + + +## Additional resources + +- [Amazon Cognito Identity Provider Developer Guide](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html) +- [Amazon Cognito Identity Provider API Reference](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/Welcome.html) +- [SDK for .NET Amazon Cognito Identity Provider reference](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/CognitoIdentity/NCognitoIdentity.html) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/dotnetv4/Cognito/Scenarios/Cognito_Basics/CognitoBasics.cs b/dotnetv4/Cognito/Scenarios/Cognito_Basics/CognitoBasics.cs new file mode 100644 index 00000000000..a5418365f5f --- /dev/null +++ b/dotnetv4/Cognito/Scenarios/Cognito_Basics/CognitoBasics.cs @@ -0,0 +1,160 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[Cognito.dotnetv4.Main] + +using LogLevel = Microsoft.Extensions.Logging.LogLevel; + +namespace CognitoBasics; + +public static class CognitoBasics +{ + public static bool _interactive = true; + + public static async Task Main(string[] args) + { + // Set up dependency injection for Amazon Cognito. + using var host = Host.CreateDefaultBuilder(args) + .ConfigureLogging(logging => + logging.AddFilter("System", LogLevel.Debug) + .AddFilter("Microsoft", LogLevel.Information) + .AddFilter("Microsoft", LogLevel.Trace)) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + ) + .Build(); ; + + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("settings.json") // Load settings from .json file. + .AddJsonFile("settings.local.json", + true) // Optionally load local settings. + .Build(); + + var cognitoWrapper = host.Services.GetRequiredService(); + + await RunScenario(cognitoWrapper, configuration); + } + + /// + /// Run the example scenario. + /// + /// Wrapper for service actions. + /// Scenario configuration. + /// + public static async Task RunScenario(CognitoWrapper cognitoWrapper, IConfigurationRoot configuration) + { + Console.WriteLine(new string('-', 80)); + UiMethods.DisplayOverview(); + Console.WriteLine(new string('-', 80)); + + // clientId - The app client Id value that you get from the AWS CDK script. + var clientId = + configuration[ + "ClientId"]; // "*** REPLACE WITH CLIENT ID VALUE FROM CDK SCRIPT"; + + // poolId - The pool Id that you get from the AWS CDK script. + var poolId = + configuration["PoolId"]!; // "*** REPLACE WITH POOL ID VALUE FROM CDK SCRIPT"; + var userName = configuration["UserName"]; + var password = configuration["Password"]; + var email = configuration["Email"]; + + // If the username wasn't set in the configuration file, + // get it from the user now. + if (userName is null) + { + do + { + Console.Write("Username: "); + userName = Console.ReadLine(); + } while (string.IsNullOrEmpty(userName)); + } + + Console.WriteLine($"\nUsername: {userName}"); + + // If the password wasn't set in the configuration file, + // get it from the user now. + if (password is null) + { + do + { + Console.Write("Password: "); + password = Console.ReadLine(); + } while (string.IsNullOrEmpty(password)); + } + + // If the email address wasn't set in the configuration file, + // get it from the user now. + if (email is null) + { + do + { + Console.Write("Email: "); + email = Console.ReadLine(); + } while (string.IsNullOrEmpty(email)); + } + + // Now sign up the user. + Console.WriteLine($"\nSigning up {userName} with email address: {email}"); + await cognitoWrapper.SignUpAsync(clientId, userName, password, email); + + // Add the user to the user pool. + Console.WriteLine($"Adding {userName} to the user pool"); + await cognitoWrapper.GetAdminUserAsync(userName, poolId); + + UiMethods.DisplayTitle("Get confirmation code"); + Console.WriteLine($"Conformation code sent to {userName}."); + + Console.Write("Would you like to send a new code? (Y/N) "); + var answer = _interactive ? Console.ReadLine() : "y"; + + if (answer!.ToLower() == "y") + { + await cognitoWrapper.ResendConfirmationCodeAsync(clientId, userName); + Console.WriteLine("Sending a new confirmation code"); + } + + Console.Write("Enter confirmation code (from Email): "); + var code = _interactive ? Console.ReadLine() : "-"; + + await cognitoWrapper.ConfirmSignupAsync(clientId, code, userName); + + + UiMethods.DisplayTitle("Checking status"); + Console.WriteLine($"Rechecking the status of {userName} in the user pool"); + await cognitoWrapper.GetAdminUserAsync(userName, poolId); + + Console.WriteLine($"Setting up authenticator for {userName} in the user pool"); + var setupResponse = await cognitoWrapper.InitiateAuthAsync(clientId, userName, password); + + var setupSession = await cognitoWrapper.AssociateSoftwareTokenAsync(setupResponse.Session); + Console.Write("Enter the 6-digit code displayed in Google Authenticator: "); + var setupCode = _interactive ? Console.ReadLine() : "-"; + var setupResult = + await cognitoWrapper.VerifySoftwareTokenAsync(setupSession, setupCode); + Console.WriteLine($"Setup status: {setupResult}"); + + Console.WriteLine($"Now logging in {userName} in the user pool"); + var authSession = + await cognitoWrapper.AdminInitiateAuthAsync(clientId, poolId, userName, + password); + + Console.Write("Enter a new 6-digit code displayed in Google Authenticator: "); + var authCode = _interactive ? Console.ReadLine() : "-"; + var authResult = + await cognitoWrapper.AdminRespondToAuthChallengeAsync(userName, clientId, + authCode, authSession, poolId); + Console.WriteLine( + $"Authenticated and received access token: {authResult.AccessToken}"); + + + Console.WriteLine(new string('-', 80)); + Console.WriteLine("Cognito scenario is complete."); + Console.WriteLine(new string('-', 80)); + return true; + } +} + +// snippet-end:[Cognito.dotnetv4.Main] \ No newline at end of file diff --git a/dotnetv4/Cognito/Scenarios/Cognito_Basics/CognitoBasics.csproj b/dotnetv4/Cognito/Scenarios/Cognito_Basics/CognitoBasics.csproj new file mode 100644 index 00000000000..fdf7a548655 --- /dev/null +++ b/dotnetv4/Cognito/Scenarios/Cognito_Basics/CognitoBasics.csproj @@ -0,0 +1,29 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + settings.json + + + + diff --git a/dotnetv4/Cognito/Scenarios/Cognito_Basics/UIMethods.cs b/dotnetv4/Cognito/Scenarios/Cognito_Basics/UIMethods.cs new file mode 100644 index 00000000000..ccc9c967e24 --- /dev/null +++ b/dotnetv4/Cognito/Scenarios/Cognito_Basics/UIMethods.cs @@ -0,0 +1,44 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[Cognito.dotnetv4.UIMethods] +namespace CognitoBasics; + +/// +/// Some useful methods to make screen display easier. +/// +public static class UiMethods +{ + /// + /// Show information about the scenario. + /// + public static void DisplayOverview() + { + DisplayTitle("Welcome to the Amazon Cognito Demo"); + + Console.WriteLine("This example application does the following:"); + Console.WriteLine("\t 1. Signs up a user."); + Console.WriteLine("\t 2. Gets the user's confirmation status."); + Console.WriteLine("\t 3. Resends the confirmation code if the user requested another code."); + Console.WriteLine("\t 4. Confirms that the user signed up."); + Console.WriteLine("\t 5. Invokes the initiateAuth to sign in. This results in being prompted to set up TOTP (time-based one-time password). (The response is “ChallengeName”: “MFA_SETUP”)."); + Console.WriteLine("\t 6. Invokes the AssociateSoftwareToken method to generate a TOTP MFA private key. This can be used with Google Authenticator."); + Console.WriteLine("\t 7. Invokes the VerifySoftwareToken method to verify the TOTP and register for MFA."); + Console.WriteLine("\t 8. Invokes the AdminInitiateAuth to sign in again. This results in being prompted to submit a TOTP (Response: “ChallengeName”: “SOFTWARE_TOKEN_MFA”)."); + Console.WriteLine("\t 9. Invokes the AdminRespondToAuthChallenge to get back a token."); + } + + /// + /// Display a line of hyphens, the centered text of the title and another + /// line of hyphens. + /// + /// The string to be displayed. + public static void DisplayTitle(string strTitle) + { + Console.WriteLine(); + Console.WriteLine(strTitle); + Console.WriteLine(); + } +} + +// snippet-end:[Cognito.dotnetv4.UIMethods] \ No newline at end of file diff --git a/dotnetv4/Cognito/Scenarios/Cognito_Basics/Usings.cs b/dotnetv4/Cognito/Scenarios/Cognito_Basics/Usings.cs new file mode 100644 index 00000000000..8a06b87643b --- /dev/null +++ b/dotnetv4/Cognito/Scenarios/Cognito_Basics/Usings.cs @@ -0,0 +1,14 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[Cognito.dotnetv4.CognitoBasics.Usings] +global using Amazon.CognitoIdentityProvider; +global using CognitoActions; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Logging.Console; +global using Microsoft.Extensions.Logging.Debug; + +// snippet-end:[Cognito.dotnetv4.CognitoBasics.Usings] \ No newline at end of file diff --git a/dotnetv4/Cognito/Scenarios/Cognito_Basics/settings.json b/dotnetv4/Cognito/Scenarios/Cognito_Basics/settings.json new file mode 100644 index 00000000000..4bfac53daa4 --- /dev/null +++ b/dotnetv4/Cognito/Scenarios/Cognito_Basics/settings.json @@ -0,0 +1,9 @@ +{ + "ClientId": "client_id_from_cdk", + "PoolId": "client_id_from_cdk", + "UserName": "username", + "Password": "EXAMPLEPASSWORD", + "Email": "useremail", + "adminUserName": "admin", + "adminPassword": "EXAMPLEPASSWORD" +} diff --git a/dotnetv4/Cognito/Tests/CognitoBasicsTests.cs b/dotnetv4/Cognito/Tests/CognitoBasicsTests.cs new file mode 100644 index 00000000000..974973c7b8f --- /dev/null +++ b/dotnetv4/Cognito/Tests/CognitoBasicsTests.cs @@ -0,0 +1,198 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Net; +using Amazon.CognitoIdentityProvider; +using Amazon.CognitoIdentityProvider.Model; +using Amazon.Runtime; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Moq; + +namespace CognitoWrapperTests; + +/// +/// Tests for the Cognito scenario. +/// +public class CognitoBasicsTests +{ + private ILoggerFactory _loggerFactory = null!; + + [Trait("Category", "Unit")] + [Fact] + public async Task ScenarioTest() + { + // Arrange. + _loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + }); + + var mockCognitoService = new Mock(); + + mockCognitoService.Setup(client => client.Paginators.ListUserPools( + It.IsAny())) + .Returns(new TestUserPoolPaginator() as IListUserPoolsPaginator); + + mockCognitoService.Setup(client => client.Paginators.ListUserPools( + It.IsAny())) + .Returns(new TestUserPoolPaginator() as IListUserPoolsPaginator); + + mockCognitoService.Setup(client => client.AdminRespondToAuthChallengeAsync( + It.IsAny(), + It.IsAny())) + .Returns((AdminRespondToAuthChallengeRequest r, + CancellationToken token) => + { + return Task.FromResult(new AdminRespondToAuthChallengeResponse() + { + HttpStatusCode = HttpStatusCode.OK, + AuthenticationResult = new AuthenticationResultType() + }); + }); + + mockCognitoService.Setup(client => client.VerifySoftwareTokenAsync( + It.IsAny(), + It.IsAny())) + .Returns((VerifySoftwareTokenRequest r, + CancellationToken token) => + { + return Task.FromResult(new VerifySoftwareTokenResponse() + { + HttpStatusCode = HttpStatusCode.OK, + }); + }); + + mockCognitoService.Setup(client => client.AssociateSoftwareTokenAsync( + It.IsAny(), + It.IsAny())) + .Returns((AssociateSoftwareTokenRequest r, + CancellationToken token) => + { + return Task.FromResult(new AssociateSoftwareTokenResponse() + { + HttpStatusCode = HttpStatusCode.OK, + }); + }); + + mockCognitoService.Setup(client => client.AdminInitiateAuthAsync( + It.IsAny(), + It.IsAny())) + .Returns((AdminInitiateAuthRequest r, + CancellationToken token) => + { + return Task.FromResult(new AdminInitiateAuthResponse() + { + HttpStatusCode = HttpStatusCode.OK, + }); + }); + + mockCognitoService.Setup(client => client.InitiateAuthAsync( + It.IsAny(), + It.IsAny())) + .Returns((InitiateAuthRequest r, + CancellationToken token) => + { + return Task.FromResult(new InitiateAuthResponse() + { + HttpStatusCode = HttpStatusCode.OK, + }); + }); + + mockCognitoService.Setup(client => client.ConfirmSignUpAsync( + It.IsAny(), + It.IsAny())) + .Returns((ConfirmSignUpRequest r, + CancellationToken token) => + { + return Task.FromResult(new ConfirmSignUpResponse() + { + HttpStatusCode = HttpStatusCode.OK, + }); + }); + + mockCognitoService.Setup(client => client.ResendConfirmationCodeAsync( + It.IsAny(), + It.IsAny())) + .Returns((ResendConfirmationCodeRequest r, + CancellationToken token) => + { + return Task.FromResult(new ResendConfirmationCodeResponse() + { + HttpStatusCode = HttpStatusCode.OK, + CodeDeliveryDetails = new CodeDeliveryDetailsType() + }); + }); + + mockCognitoService.Setup(client => client.AdminGetUserAsync( + It.IsAny(), + It.IsAny())) + .Returns((AdminGetUserRequest r, + CancellationToken token) => + { + return Task.FromResult(new AdminGetUserResponse() + { + HttpStatusCode = HttpStatusCode.OK, + UserStatus = UserStatusType.CONFIRMED + }); + }); + + mockCognitoService.Setup(client => client.SignUpAsync( + It.IsAny(), + It.IsAny())) + .Returns((SignUpRequest r, + CancellationToken token) => + { + return Task.FromResult(new SignUpResponse() + { + HttpStatusCode = HttpStatusCode.OK, + }); + }); + + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("testsettings.json") // Load test settings from .json file. + .AddJsonFile("testsettings.local.json", + true) // Optionally load local settings. + .Build(); + + var wrapper = new CognitoWrapper(mockCognitoService.Object); + CognitoBasics.CognitoBasics._interactive = false; + + var success = + await CognitoBasics.CognitoBasics.RunScenario(wrapper, configuration); + Assert.True(success); + } + +} + + +/// +/// Mock Paginator for user pool response. +/// +public class TestUsersPaginator : IPaginator, IListUsersPaginator +{ + public IAsyncEnumerable PaginateAsync( + CancellationToken cancellationToken = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public IPaginatedEnumerable Responses { get; } = null!; + public IPaginatedEnumerable Users { get; } = null!; +} + +/// +/// Mock Paginator for user response. +/// +public class TestUserPoolPaginator : IPaginator, IListUserPoolsPaginator +{ + public IAsyncEnumerable PaginateAsync( + CancellationToken cancellationToken = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public IPaginatedEnumerable Responses { get; } = null!; + public IPaginatedEnumerable UserPools { get; } = null!; +} \ No newline at end of file diff --git a/dotnetv4/Cognito/Tests/CognitoTests.csproj b/dotnetv4/Cognito/Tests/CognitoTests.csproj new file mode 100644 index 00000000000..fb9883ad93d --- /dev/null +++ b/dotnetv4/Cognito/Tests/CognitoTests.csproj @@ -0,0 +1,38 @@ + + + + net8.0 + enable + enable + + false + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + PreserveNewest + + + PreserveNewest + testsettings.json + + + + + + + + + diff --git a/dotnetv4/Cognito/Tests/Usings.cs b/dotnetv4/Cognito/Tests/Usings.cs new file mode 100644 index 00000000000..d77a2d566c5 --- /dev/null +++ b/dotnetv4/Cognito/Tests/Usings.cs @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +global using CognitoActions; +global using Xunit; + +// Optional. +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/dotnetv4/Cognito/Tests/testsettings.json b/dotnetv4/Cognito/Tests/testsettings.json new file mode 100644 index 00000000000..eefdb2c8435 --- /dev/null +++ b/dotnetv4/Cognito/Tests/testsettings.json @@ -0,0 +1,8 @@ +{ + "UserName": "someuser", + "Email": "someone@example.com", + "Password": "AGoodPassword1234", + "UserPoolId": "IDENTIFY_POOL_ID", + "ClientId": "CLIENT_ID_FROM_CDK_SCRIPT", + "PoolId": "USER_POOL_ID_FROM_CDK_SCRIPT" +} diff --git a/dotnetv4/DotNetV4Examples.sln b/dotnetv4/DotNetV4Examples.sln index ab7be69d4d9..d46afcd8c1e 100644 --- a/dotnetv4/DotNetV4Examples.sln +++ b/dotnetv4/DotNetV4Examples.sln @@ -119,6 +119,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basics", "EC2\Scenarios\EC2 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EC2Actions", "EC2\Actions\EC2Actions.csproj", "{0633CB2B-3508-48E5-A8C2-427A83A5CA6E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cognito", "Cognito", "{F5214562-85F4-4FD8-B56D-C5D8E7914901}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CognitoTests", "Cognito\Tests\CognitoTests.csproj", "{63DC05A0-5B16-45A4-BDE5-90DD2E200507}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{D38A409C-EE40-4E70-B500-F3D6EF8E82A4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CognitoBasics", "Cognito\Scenarios\Cognito_Basics\CognitoBasics.csproj", "{38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CognitoActions", "Cognito\Actions\CognitoActions.csproj", "{1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -293,6 +303,18 @@ Global {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Debug|Any CPU.Build.0 = Debug|Any CPU {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|Any CPU.ActiveCfg = Release|Any CPU {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|Any CPU.Build.0 = Release|Any CPU + {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Release|Any CPU.Build.0 = Release|Any CPU + {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Release|Any CPU.Build.0 = Release|Any CPU + {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -349,6 +371,10 @@ Global {6C167F25-F97F-4854-8CD8-A2D446B6799B} = {9424FB14-B6DE-44CE-B675-AC2B57EC1E69} {D95519CA-BD27-45AE-B83B-3FB02E7AE445} = {6C167F25-F97F-4854-8CD8-A2D446B6799B} {0633CB2B-3508-48E5-A8C2-427A83A5CA6E} = {9424FB14-B6DE-44CE-B675-AC2B57EC1E69} + {63DC05A0-5B16-45A4-BDE5-90DD2E200507} = {F5214562-85F4-4FD8-B56D-C5D8E7914901} + {D38A409C-EE40-4E70-B500-F3D6EF8E82A4} = {F5214562-85F4-4FD8-B56D-C5D8E7914901} + {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99} = {D38A409C-EE40-4E70-B500-F3D6EF8E82A4} + {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1} = {F5214562-85F4-4FD8-B56D-C5D8E7914901} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {08502818-E8E1-4A91-A51C-4C8C8D4FF9CA}