Gems/AWSClientAuth/Code/Source/Authorization/AWSCognitoAuthorizationController.cpp (232 lines of code) (raw):
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AWSClientAuthBus.h>
#include <AWSCoreBus.h>
#include <Authorization/AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider.h>
#include <Authorization/AWSCognitoAuthorizationController.h>
#include <ResourceMapping/AWSResourceMappingBus.h>
#include <AWSClientAuthResourceMappingConstants.h>
#include <AzCore/EBus/Internal/BusContainer.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <aws/identity-management/auth/CognitoCachingCredentialsProvider.h>
namespace AWSClientAuth
{
constexpr char CognitoAmazonLoginsId[] = "www.amazon.com";
constexpr char CognitoGoogleLoginsId[] = "accounts.google.com";
constexpr char CognitoUserPoolIdFormat[] = "cognito-idp.%s.amazonaws.com/%s";
AWSCognitoAuthorizationController::AWSCognitoAuthorizationController()
{
AZ::Interface<IAWSCognitoAuthorizationRequests>::Register(this);
AWSCognitoAuthorizationRequestBus::Handler::BusConnect();
AuthenticationProviderNotificationBus::Handler::BusConnect();
AWSCore::AWSCredentialRequestBus::Handler::BusConnect();
m_persistentCognitoIdentityProvider = std::make_shared<AWSClientAuthPersistentCognitoIdentityProvider>();
m_persistentAnonymousCognitoIdentityProvider = std::make_shared<AWSClientAuthPersistentCognitoIdentityProvider>();
auto identityClient = AZ::Interface<IAWSClientAuthRequests>::Get()->GetCognitoIdentityClient();
m_cognitoCachingCredentialsProvider =
std::make_shared<AWSClientAuthCognitoCachingAuthenticatedCredentialsProvider>(
m_persistentCognitoIdentityProvider, identityClient);
m_cognitoCachingAnonymousCredentialsProvider =
std::make_shared<AWSClientAuthCachingAnonymousCredsProvider>(
m_persistentAnonymousCognitoIdentityProvider, identityClient);
}
AWSCognitoAuthorizationController::~AWSCognitoAuthorizationController()
{
m_cognitoCachingCredentialsProvider.reset();
m_persistentAnonymousCognitoIdentityProvider.reset();
m_persistentCognitoIdentityProvider.reset();
m_persistentAnonymousCognitoIdentityProvider.reset();
AWSCore::AWSCredentialRequestBus::Handler::BusDisconnect();
AuthenticationProviderNotificationBus::Handler::BusDisconnect();
AWSCognitoAuthorizationRequestBus::Handler::BusDisconnect();
AZ::Interface<IAWSCognitoAuthorizationRequests>::Unregister(this);
}
bool AWSCognitoAuthorizationController::Initialize()
{
AWSCore::AWSResourceMappingRequestBus::BroadcastResult(
m_awsAccountId, &AWSCore::AWSResourceMappingRequests::GetDefaultAccountId);
AWSCore::AWSResourceMappingRequestBus::BroadcastResult(
m_cognitoIdentityPoolId, &AWSCore::AWSResourceMappingRequests::GetResourceNameId, CognitoIdentityPoolIdResourceMappingKey);
if (m_awsAccountId.empty())
{
AZ_TracePrintf("AWSCognitoAuthorizationController", "AWS account id not not configured. Proceeding without it.");
}
if (m_cognitoIdentityPoolId.empty())
{
AZ_Warning("AWSCognitoAuthorizationController", !m_cognitoIdentityPoolId.empty(), "Missing Cognito Identity pool id in resource mappings.");
return false;
}
AZStd::string userPoolId;
AWSCore::AWSResourceMappingRequestBus::BroadcastResult(
userPoolId, &AWSCore::AWSResourceMappingRequests::GetResourceNameId, CognitoUserPoolIdResourceMappingKey);
AZ_Warning("AWSCognitoAuthorizationController", !userPoolId.empty(), "Missing Cognito User pool id in resource mappings. Cognito IDP authenticated identities will not work.");
AZStd::string defaultRegion;
AWSCore::AWSResourceMappingRequestBus::BroadcastResult(
defaultRegion, &AWSCore::AWSResourceMappingRequests::GetDefaultRegion);
m_formattedCognitoUserPoolId = AZStd::string::format(CognitoUserPoolIdFormat, defaultRegion.c_str(), userPoolId.c_str());
m_persistentCognitoIdentityProvider->Initialize(m_awsAccountId.c_str(), m_cognitoIdentityPoolId.c_str());
m_persistentAnonymousCognitoIdentityProvider->Initialize(m_awsAccountId.c_str(), m_cognitoIdentityPoolId.c_str());
return true;
}
void AWSCognitoAuthorizationController::Reset()
{
// Brackets for lock guard scopes
{
AZStd::lock_guard<AZStd::mutex> lock(m_persistentAnonymousCognitoIdentityProviderMutex);
m_persistentAnonymousCognitoIdentityProvider->ClearLogins();
m_persistentAnonymousCognitoIdentityProvider->ClearIdentity();
}
{
AZStd::lock_guard<AZStd::mutex> lock(m_persistentCognitoIdentityProviderMutex);
m_persistentCognitoIdentityProvider->ClearLogins();
m_persistentCognitoIdentityProvider->ClearIdentity();
}
}
AZStd::string AWSCognitoAuthorizationController::GetIdentityId()
{
// Give preference to authenticated credentials provider.
if (HasPersistedLogins())
{
AZStd::lock_guard<AZStd::mutex> lock(m_persistentCognitoIdentityProviderMutex);
return m_persistentCognitoIdentityProvider->GetIdentityId().c_str();
}
else
{
AZStd::lock_guard<AZStd::mutex> lock(m_persistentAnonymousCognitoIdentityProviderMutex);
return m_persistentAnonymousCognitoIdentityProvider->GetIdentityId().c_str();
}
}
bool AWSCognitoAuthorizationController::HasPersistedLogins()
{
AZStd::lock_guard<AZStd::mutex> lock(m_persistentCognitoIdentityProviderMutex);
return m_persistentCognitoIdentityProvider->HasLogins();
}
std::shared_ptr<Aws::Auth::AWSCredentialsProvider> AWSCognitoAuthorizationController::GetCognitoCredentialsProvider()
{
return m_cognitoCachingCredentialsProvider;
}
std::shared_ptr<Aws::Auth::AWSCredentialsProvider> AWSCognitoAuthorizationController::GetAnonymousCognitoCredentialsProvider()
{
return m_cognitoCachingAnonymousCredentialsProvider;
}
void AWSCognitoAuthorizationController::RequestAWSCredentialsAsync()
{
bool anonymous = true;
// Give preference to authenticated credentials provider.
if (m_persistentCognitoIdentityProvider->HasLogins())
{
anonymous = false;
}
else
{
AZ_Warning("AWSCognitoAuthorizationController", false, "No logins found. Fetching anonymous/unauthenticated credentials");
}
AZ::JobContext* jobContext = nullptr;
AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext);
AZ::Job* job = AZ::CreateJobFunction(
[this, anonymous]() {
Aws::Auth::AWSCredentials credentials;
// GetAWSCredentials makes Cognito GetId and GetCredentialsForIdentity Cognito identity pool API request if no valid cached credentials found.
if (anonymous)
{
AZStd::lock_guard<AZStd::mutex> lock(m_persistentAnonymousCognitoIdentityProviderMutex);
credentials = m_cognitoCachingAnonymousCredentialsProvider->GetAWSCredentials();
}
else
{
AZStd::lock_guard<AZStd::mutex> lock(m_persistentCognitoIdentityProviderMutex);
credentials = m_cognitoCachingCredentialsProvider->GetAWSCredentials();
}
if (!credentials.IsEmpty())
{
ClientAuthAWSCredentials clientAuthAWSCrendentials(credentials.GetAWSAccessKeyId().c_str(), credentials.GetAWSSecretKey().c_str(), credentials.GetSessionToken().c_str());
AWSClientAuth::AWSCognitoAuthorizationNotificationBus::Broadcast(
&AWSClientAuth::AWSCognitoAuthorizationNotifications::OnRequestAWSCredentialsSuccess, clientAuthAWSCrendentials);
}
else
{
AWSClientAuth::AWSCognitoAuthorizationNotificationBus::Broadcast(
&AWSClientAuth::AWSCognitoAuthorizationNotifications::OnRequestAWSCredentialsFail,
"Failed to get AWS credentials");
}
},
true, jobContext);
job->Start();
}
AZStd::string AWSCognitoAuthorizationController::GetAuthenticationProviderId(const ProviderNameEnum& providerName)
{
switch (providerName)
{
case ProviderNameEnum::AWSCognitoIDP:
{
return m_formattedCognitoUserPoolId;
}
case ProviderNameEnum::LoginWithAmazon:
{
return CognitoAmazonLoginsId;
}
case ProviderNameEnum::Google:
{
return CognitoGoogleLoginsId;
}
default:
{
return "";
}
}
}
void AWSCognitoAuthorizationController::PersistLoginsAndRefreshAWSCredentials(const AuthenticationTokens& authenticationTokens)
{
// lock to persist logins as the object is shared with Native SDK. Native SDK reads logins and persists identity id and expiry.
AZStd::lock_guard<AZStd::mutex> lock(m_persistentCognitoIdentityProviderMutex);
// Save logins to the shared persistent Cognito identity provider for authenticated authorization.
// Append logins to existing map.
Aws::Map<Aws::String, Aws::Auth::LoginAccessTokens> logins = m_persistentCognitoIdentityProvider->GetLogins();
Aws::Auth::LoginAccessTokens tokens;
tokens.accessToken = authenticationTokens.GetOpenIdToken().c_str();
logins[GetAuthenticationProviderId(authenticationTokens.GetProviderName()).c_str()] = tokens;
m_persistentCognitoIdentityProvider->PersistLogins(logins);
}
void AWSCognitoAuthorizationController::OnPasswordGrantSingleFactorSignInSuccess(const AWSClientAuth::AuthenticationTokens& authenticationTokens)
{
PersistLoginsAndRefreshAWSCredentials(authenticationTokens);
}
void AWSCognitoAuthorizationController::OnPasswordGrantMultiFactorConfirmSignInSuccess(
const AWSClientAuth::AuthenticationTokens& authenticationTokens)
{
PersistLoginsAndRefreshAWSCredentials(authenticationTokens);
}
void AWSCognitoAuthorizationController::OnDeviceCodeGrantConfirmSignInSuccess(
const AWSClientAuth::AuthenticationTokens& authenticationTokens)
{
PersistLoginsAndRefreshAWSCredentials(authenticationTokens);
}
void AWSCognitoAuthorizationController::OnRefreshTokensSuccess(const AWSClientAuth::AuthenticationTokens& authenticationTokens)
{
PersistLoginsAndRefreshAWSCredentials(authenticationTokens);
}
void AWSCognitoAuthorizationController::OnSignOut(const ProviderNameEnum& provideName)
{
// lock to persist logins as the object is shared with Native SDK.
AZStd::lock_guard<AZStd::mutex> lock(m_persistentCognitoIdentityProviderMutex);
m_persistentCognitoIdentityProvider->RemoveLogin(GetAuthenticationProviderId(provideName).c_str());
}
int AWSCognitoAuthorizationController::GetCredentialHandlerOrder() const
{
return AWSCore::CredentialHandlerOrder::COGNITO_IDENITY_POOL_CREDENTIAL_HANDLER;
}
std::shared_ptr<Aws::Auth::AWSCredentialsProvider> AWSCognitoAuthorizationController::GetCredentialsProvider()
{
// If logins are persisted default to using authenticated credentials provide.
// Check authenticated credentials to verify persisted logins are valid.
if (HasPersistedLogins())
{
// lock to protect logins being persisted.
AZStd::lock_guard<AZStd::mutex> lock(m_persistentCognitoIdentityProviderMutex);
if (!m_cognitoCachingCredentialsProvider->GetAWSCredentials().IsEmpty())
{
return m_cognitoCachingCredentialsProvider;
}
}
// Check anonymous credentials as they are optional settings in Cognito Identity pool.
if (m_cognitoIdentityPoolId.empty())
{
// If the identity pool isn't set, then the anonymous credential won't be found.
// Return null, instead of asking AWS for credentials to avoid failing AWS requests and AWS errors due to a null identity pool id.
return nullptr;
}
// Lock to protect getting identity id.
AZStd::lock_guard<AZStd::mutex> lock(m_persistentAnonymousCognitoIdentityProviderMutex);
if (!m_cognitoCachingAnonymousCredentialsProvider->GetAWSCredentials().IsEmpty())
{
AZ_Warning("AWSCognitoAuthorizationCredentialHandler", false, "No logins found. Using Anonymous credential provider");
return m_cognitoCachingAnonymousCredentialsProvider;
}
return nullptr;
}
} // namespace AWSClientAuth