diff --git a/cmake/sdksCommon.cmake b/cmake/sdksCommon.cmake index 7eb26f8e40b..f3addce30f5 100644 --- a/cmake/sdksCommon.cmake +++ b/cmake/sdksCommon.cmake @@ -87,6 +87,7 @@ list(APPEND HIGH_LEVEL_SDK_LIST "text-to-speech") set(SDK_TEST_PROJECT_LIST "") list(APPEND SDK_TEST_PROJECT_LIST "cloudfront:tests/aws-cpp-sdk-cloudfront-integration-tests") list(APPEND SDK_TEST_PROJECT_LIST "cognito-identity:tests/aws-cpp-sdk-cognitoidentity-integration-tests") +list(APPEND SDK_TEST_PROJECT_LIST "cognito-idp:tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests") list(APPEND SDK_TEST_PROJECT_LIST "core:tests/aws-cpp-sdk-core-tests") list(APPEND SDK_TEST_PROJECT_LIST "dynamodb:tests/aws-cpp-sdk-dynamodb-integration-tests") list(APPEND SDK_TEST_PROJECT_LIST "dynamodb:tests/aws-cpp-sdk-dynamodb-unit-tests") diff --git a/tests/android-unified-tests/CMakeLists.txt b/tests/android-unified-tests/CMakeLists.txt index 382b291062f..b6de1b3f4be 100644 --- a/tests/android-unified-tests/CMakeLists.txt +++ b/tests/android-unified-tests/CMakeLists.txt @@ -19,6 +19,7 @@ file(GLOB SQS_SRC "${AWS_NATIVE_SDK_ROOT}/aws-cpp-sdk-sqs-integration-tests/Queu file(GLOB S3_SRC "${AWS_NATIVE_SDK_ROOT}/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp") file(GLOB LAMBDA_SRC "${AWS_NATIVE_SDK_ROOT}/aws-cpp-sdk-lambda-integration-tests/FunctionTest.cpp") file(GLOB COGNITO_IDENTITY_IDENTITY_POOL_SRC "${AWS_NATIVE_SDK_ROOT}/aws-cpp-sdk-cognitoidentity-integration-tests/IdentityPoolOperationTest.cpp") +file(GLOB COGNITO_IDENTITY_IDENTITY_POOL_SRC "${AWS_NATIVE_SDK_ROOT}/aws-cpp-sdk-cognitoidentityprovider-integration-tests/OperationTest.cpp") file(GLOB TRANSFER_SRC "${AWS_NATIVE_SDK_ROOT}/aws-cpp-sdk-transfer-tests/TransferTests.cpp") file(GLOB IDENTITY_MANAGEMENT_SRC "${AWS_NATIVE_SDK_ROOT}/aws-cpp-sdk-identity-management-tests/auth/*.cpp") file(GLOB ENCRYPTION_TESTS_SRC "${AWS_NATIVE_SDK_ROOT}/aws-cpp-sdk-s3-encryption-tests/CryptoModulesTest.cpp" diff --git a/tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests/CMakeLists.txt b/tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests/CMakeLists.txt new file mode 100644 index 00000000000..0319d478926 --- /dev/null +++ b/tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests/CMakeLists.txt @@ -0,0 +1,46 @@ +add_project(aws-cpp-sdk-cognitoidentityprovider-integration-tests + "Tests for the AWS Cognito IDP C++ SDK" + aws-cpp-sdk-cognito-idp + testing-resources + aws-cpp-sdk-core +) + +# Headers are included in the source so that they show up in Visual Studio. +# They are included elsewhere for consistency. + +file(GLOB AWS_COGNITO_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" +) + +file(GLOB AWS_COGNITO_INTEGRATION_TESTS_SRC + ${AWS_COGNITO_SRC} +) + +if(MSVC AND BUILD_SHARED_LIBS) + add_definitions(-DGTEST_LINKED_AS_SHARED_LIBRARY=1) +endif() + +enable_testing() + +if(PLATFORM_ANDROID AND BUILD_SHARED_LIBS) + add_library(${PROJECT_NAME} ${AWS_COGNITO_INTEGRATION_TESTS_SRC}) +else() + add_executable(${PROJECT_NAME} ${AWS_COGNITO_INTEGRATION_TESTS_SRC}) +endif() + +set_compiler_flags(${PROJECT_NAME}) +set_compiler_warnings(${PROJECT_NAME}) + +find_package(OpenSSL REQUIRED) +# Include directories for OpenSSL headers +include_directories(${OPENSSL_INCLUDE_DIR}) + +target_link_libraries(${PROJECT_NAME} ${PROJECT_LIBS} OpenSSL::Crypto) + + + +if(MSVC AND BUILD_SHARED_LIBS) + set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS + "/DELAYLOAD:aws-cpp-sdk-access-management.dll /DELAYLOAD:aws-cpp-sdk-cognito-idp /DELAYLOAD:aws-cpp-sdk-iam /DELAYLOAD:aws-cpp-sdk-core.dll") + target_link_libraries(${PROJECT_NAME} delayimp.lib) +endif() diff --git a/tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests/OperationTest.cpp b/tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests/OperationTest.cpp new file mode 100644 index 00000000000..f137a1cec43 --- /dev/null +++ b/tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests/OperationTest.cpp @@ -0,0 +1,379 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace Aws::CognitoIdentityProvider; +using namespace Aws::CognitoIdentityProvider::Model; +using namespace Aws::Client; +using namespace Aws::Region; + +namespace +{ +static const char* ALLOCATION_TAG = "IdentityProviderOperationTest"; + + +class IdentityProviderOperationTest : public ::testing::Test +{ +public: + IdentityProviderOperationTest() : + client(nullptr) + {} + + std::shared_ptr client; + Aws::String testTrace; + +protected: + + const Aws::String m_clientName{"testClient"}; + Aws::String m_userPoolId; + Aws::String m_clientId; + const Aws::String m_username{"testUser"}; + Aws::String m_clientSecret; + + void SetUp() + { + Aws::Client::ClientConfiguration config; + config.region = AWS_TEST_REGION; + + client = Aws::MakeShared(ALLOCATION_TAG, config); + + //make user pool + m_userPoolId = createUserPool("TestPool"); + + ASSERT_TRUE(!m_userPoolId.empty()); + + //make client + if(!m_userPoolId.empty()) + { + auto ret = createPoolClient(m_userPoolId, m_clientName); + m_clientId = ret.first; + m_clientSecret = ret.second; + } + + ASSERT_TRUE(!m_clientId.empty()); + ASSERT_TRUE(!m_clientSecret.empty()); + + //create user + if(!m_userPoolId.empty() && !m_clientId.empty()) + { + ASSERT_TRUE(createUser(m_username, "Password123!", m_userPoolId)); + } + + } + + void TearDown() + { + + //delete user + if(!m_userPoolId.empty()) + { + ASSERT_TRUE(deleteUser(m_username, m_userPoolId)); + } + + // delete client + if(!m_userPoolId.empty() && !m_clientId.empty()) + { + ASSERT_TRUE(deletePoolClient(m_userPoolId, m_clientId)); + } + + // delete user pool + if(!m_userPoolId.empty()) + { + ASSERT_TRUE(deleteUserPool(m_userPoolId)); + } + + + if (::testing::Test::HasFailure()) + { + AWS_LOGSTREAM_DEBUG(ALLOCATION_TAG, "Test traces: " << testTrace); + } + testTrace.erase(); + } + + private: + Aws::String createUserPool(Aws::String poolName) + { + if(poolName.empty()) + { + return {}; + } + Aws::CognitoIdentityProvider::Model::CreateUserPoolRequest userPoolRequest; + userPoolRequest.SetPoolName(poolName); + + CreateUserPoolOutcome userPoolOutcome = client->CreateUserPool(userPoolRequest); + if (!userPoolOutcome.IsSuccess()) { + return {}; + } + + auto poolId = userPoolOutcome.GetResult().GetUserPool().GetId(); + AWS_LOGSTREAM_DEBUG(ALLOCATION_TAG, "Created User Pool with ID: " << poolId << " for pool name="<< poolName ); + return poolId; + } + + bool deleteUserPool(const Aws::String& poolId) + { + if (poolId.empty()) + { + return false; + } + Aws::CognitoIdentityProvider::Model::DeleteUserPoolRequest deletePoolRequest; + deletePoolRequest.SetUserPoolId(poolId); + + auto deletePoolOutcome = client->DeleteUserPool(deletePoolRequest); + if (!deletePoolOutcome.IsSuccess()) { + AWS_LOGSTREAM_ERROR(ALLOCATION_TAG, "Failed to delete User Pool: " << deletePoolOutcome.GetError().GetMessage()); + } + else + { + AWS_LOGSTREAM_DEBUG(ALLOCATION_TAG, "Deleted User Pool with ID: " << poolId); + } + return deletePoolOutcome.IsSuccess(); + } + + bool createUser(const Aws::String& username, const Aws::String& password, const Aws::String& userPoolId) + { + if (userPoolId.empty() || username.empty() || password.empty()) + { + return false; + } + + Aws::CognitoIdentityProvider::Model::AdminCreateUserRequest createUserRequest; + createUserRequest.SetUserPoolId(userPoolId); + createUserRequest.SetUsername(username); + + Aws::CognitoIdentityProvider::Model::AttributeType emailAttribute; + emailAttribute.SetName("email"); + emailAttribute.SetValue("testuser@example.com"); + createUserRequest.AddUserAttributes(emailAttribute); + + // Set a temporary password + createUserRequest.SetTemporaryPassword(password); + + // Suppress sending an email to the user + createUserRequest.SetMessageAction(Aws::CognitoIdentityProvider::Model::MessageActionType::SUPPRESS); + + auto outcome = client->AdminCreateUser(createUserRequest); + if (outcome.IsSuccess()) { + AWS_LOGSTREAM_DEBUG(ALLOCATION_TAG, "User created successfully: " << outcome.GetResult().GetUser().GetUsername()); + } else { + AWS_LOGSTREAM_ERROR(ALLOCATION_TAG, "Failed to create user: " << outcome.GetError().GetMessage()); + } + + return outcome.IsSuccess(); + } + + bool deleteUser(const Aws::String& username, const Aws::String& userPoolId) + { + if (username.empty() || userPoolId.empty()) + { + return false; + } + + Aws::CognitoIdentityProvider::Model::AdminDeleteUserRequest deleteUserRequest; + deleteUserRequest.SetUserPoolId(userPoolId); + deleteUserRequest.SetUsername(username); + auto deleteUserOutcome = client->AdminDeleteUser(deleteUserRequest); + if (!deleteUserOutcome.IsSuccess()) { + AWS_LOGSTREAM_ERROR(ALLOCATION_TAG, "Failed to delete user: " << deleteUserOutcome.GetError().GetMessage()); + } + else + { + AWS_LOGSTREAM_DEBUG(ALLOCATION_TAG, "Deleted test user: " << username); + } + return deleteUserOutcome.IsSuccess(); + } + + + std::pair createPoolClient(const Aws::String& userPoolId, const Aws::String& clientName) + { + if(userPoolId.empty() || clientName.empty()) + { + return {}; + } + + Aws::CognitoIdentityProvider::Model::CreateUserPoolClientRequest request; + request.SetUserPoolId(userPoolId); + request.SetClientName(clientName); + + request.SetGenerateSecret(true); + Aws::Vector authFlows{Aws::CognitoIdentityProvider::Model::ExplicitAuthFlowsType::ALLOW_USER_SRP_AUTH, Aws::CognitoIdentityProvider::Model::ExplicitAuthFlowsType::ALLOW_REFRESH_TOKEN_AUTH}; + request.SetExplicitAuthFlows(authFlows); + + Aws::CognitoIdentityProvider::Model::CreateUserPoolClientOutcome outcome = client->CreateUserPoolClient(request); + Aws::String clientId, clientSecret; + if (outcome.IsSuccess()) + { + clientId = outcome.GetResult().GetUserPoolClient().GetClientId(); + clientSecret = outcome.GetResult().GetUserPoolClient().GetClientSecret(); + AWS_LOGSTREAM_DEBUG(ALLOCATION_TAG, "User Pool Client created successfully. Client ID: " << clientId ); + } + return {clientId,clientSecret}; + } + + bool deletePoolClient(const Aws::String& userPoolId, const Aws::String& clientId) + { + // Set up the request for deleting the User Pool Client + Aws::CognitoIdentityProvider::Model::DeleteUserPoolClientRequest request; + request.SetUserPoolId(userPoolId); + request.SetClientId(clientId); + + // Make the call to delete the user pool client + auto outcome = client->DeleteUserPoolClient(request); + if (outcome.IsSuccess()) + { + AWS_LOGSTREAM_DEBUG(ALLOCATION_TAG, "User Pool Client deleted successfully."); + } + return outcome.IsSuccess(); + } + +}; + + +Aws::String ComputeSecretHash(const Aws::String &userPoolClientId, const Aws::String &clientSecret, const Aws::String &userName) +{ + const auto message = userName + userPoolClientId; + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int len = 0; + + HMAC(EVP_sha256(), + clientSecret.c_str(), clientSecret.length(), + (unsigned char*)message.c_str(), message.length(), + hash, &len); + + // Base64 encode the hash + char encoded[128]; + EVP_EncodeBlock((unsigned char*)encoded, hash, len); + + return std::string(encoded); +} +// https://github.com/aws/amazon-cognito-identity-js/blob/master/src/AuthenticationHelper.js#L22 +const char* initN = + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"; +// https://github.com/aws/amazon-cognito-identity-js/blob/master/src/AuthenticationHelper.js# +const char* generator = "2"; + + +class SRPClient { + public: + SRPClient(const std::string& N_hex, const std::string& g):N(BN_new()), g(BN_new()), a(nullptr), A(BN_new()), ctx(BN_CTX_new()) + { + //convert the hex string N_hex into a BIGNUM (OpenSSL's representation of large integers) + AWS_FATAL_ASSERT(BN_hex2bn(&N, N_hex.c_str())); + //convert the decimal string g into a BIGNUM + AWS_FATAL_ASSERT(BN_dec2bn(&this->g, g.c_str())); + + generatePrivateKey(256); + } + ~SRPClient() + { + BN_free(N); + BN_free(g); + BN_free(a); + BN_free(A); + BN_CTX_free(ctx); + } + + + private: + /* + N: A large prime number, a part of the SRP protocol. We use the N used + g: A generator used in the SRP protocol. + a: The client's private key. + A: The client's public key derived from the private key. + ctx: A context structure for OpenSSL's cryptographic operations. + + */ + BIGNUM* N{nullptr}; + BIGNUM* g{nullptr}; + BIGNUM* a{nullptr}; + BIGNUM* A{nullptr}; + BN_CTX* ctx{nullptr}; + + void generatePrivateKey(int bits) + { + a = BN_new(); + AWS_FATAL_ASSERT(BN_rand(a, bits, -1, 0)); + } + + public: + //Calculate the client’s public key A using the formula A = g^a (mod N) where a is the client’s private key and g is the generator + //We use OpenSSL function here + Aws::String calculateA() const + { + AWS_FATAL_ASSERT(BN_mod_exp(A, g, a, N, ctx)); + char* A_hex = BN_bn2hex(A); + Aws::String A_str(A_hex); + OPENSSL_free(A_hex); + return A_str; + } +}; + + + +TEST_F(IdentityProviderOperationTest, testSecret) +{ + SRPClient srpclient(initN, generator); + Aws::Map authParameters; + authParameters["USERNAME"] = m_username; + authParameters["SECRET_HASH"] = ComputeSecretHash(m_clientId, m_clientSecret, m_username); + auto srpa = srpclient.calculateA(); + ASSERT_TRUE(!srpa.empty()); + authParameters["SRP_A"] = srpa; + + Aws::CognitoIdentityProvider::Model::InitiateAuthRequest authRequest; + authRequest.SetClientId(m_clientId); + authRequest.SetAuthFlow(Aws::CognitoIdentityProvider::Model::AuthFlowType::USER_SRP_AUTH); + authRequest.SetAuthParameters(authParameters); + Aws::CognitoIdentityProvider::Model::InitiateAuthOutcome authResult = client->InitiateAuth( authRequest ); + EXPECT_TRUE(authResult.IsSuccess()); +} + +} diff --git a/tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests/RunTests.cpp b/tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests/RunTests.cpp new file mode 100644 index 00000000000..06dc9951d5d --- /dev/null +++ b/tests/aws-cpp-sdk-cognitoidentityprovider-integration-tests/RunTests.cpp @@ -0,0 +1,30 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + Aws::Testing::SetDefaultSigPipeHandler(); + Aws::SDKOptions options; + options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug; + AWS_BEGIN_MEMORY_TEST_EX(options, 1024, 128); + + Aws::Testing::InitPlatformTest(options); + Aws::Testing::ParseArgs(argc, argv); + + Aws::InitAPI(options); + ::testing::InitGoogleTest(&argc, argv); + int exitCode = RUN_ALL_TESTS(); + + Aws::ShutdownAPI(options); + AWS_END_MEMORY_TEST_EX; + Aws::Testing::ShutdownPlatformTest(options); + return exitCode; +} diff --git a/tools/scripts/run_integration_tests.py b/tools/scripts/run_integration_tests.py index 32783ffff01..31357af3a21 100644 --- a/tools/scripts/run_integration_tests.py +++ b/tools/scripts/run_integration_tests.py @@ -54,7 +54,9 @@ def main(): "aws-cpp-sdk-elasticfilesystem-integration-tests", "aws-cpp-sdk-rds-integration-tests", "aws-cpp-sdk-ec2-integration-tests", - "aws-cpp-sdk-timestream-query-integration-tests"] + "aws-cpp-sdk-timestream-query-integration-tests", + "aws-cpp-sdk-cognitoidentityprovider-integration-tests" + ] # check for existence of these binaries before adding them to tests # as they will not always be present