Files
UnrealEngine/Engine/Source/Editor/PluginWarden/Private/PluginWardenAuthorizer.cpp
2025-05-18 13:04:45 +08:00

314 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PluginWardenAuthorizer.h"
#include "PluginWardenModule.h"
#include "Containers/Ticker.h"
#include "Async/TaskGraphInterfaces.h"
#include "Editor.h"
#include "Logging/LogMacros.h"
#include "IPortalServiceLocator.h"
#include "Account/IPortalUserLogin.h"
#include "Application/IPortalApplicationWindow.h"
#include "ILauncherPlatform.h"
#include "LauncherPlatformModule.h"
extern TSet<FString> AuthorizedPlugins;
DEFINE_LOG_CATEGORY(PluginWarden);
enum
{
MaxAuthorizationRetries = 3, // Max number of authorization check to retry when the entitlement wasn't retrieved yet
MaxSigninRetries = 3, // Max number of prompt user for sign-in to retry when the Launcher fails to handle the prompt request
MaxLauncherRetries = 3, // Max number of Launcher start to retry when it's detected that it's not available for user sign-in status check
GeneralWaitingTimeout = 15, // Timeout in secs to use for various waiting phases
SigninWaitingTimeout = 120, // Timeout in secs to use during the Waiting for sign-in phase
UserDetailsCheckPeriod = 10 // The periodicity in secs to check for the user details during the Waiting for sign-in phase
};
#define LOCTEXT_NAMESPACE "PluginWardenAuthorization"
FPluginWardenAuthorizer::FPluginWardenAuthorizer(const FText& InPluginFriendlyName, const FString& InPluginItemId, const FString& InPluginOfferId, const EEntitlementCacheLevelRequest InCacheLevel)
: CurrentState(EPluginAuthorizationState::Initializing)
, CacheLevel(InCacheLevel)
, WaitingTime(0)
, PluginFriendlyName(InPluginFriendlyName)
, PluginItemId(InPluginItemId)
, PluginOfferId(InPluginOfferId)
, NumAuthorizationRetries(0)
, NumSignInRetries(0)
, NumLauncherRetries(0)
, CurrentWaitLoopNumber(0)
{
TSharedRef<IPortalServiceLocator> ServiceLocator = GEditor->GetServiceLocator();
PortalWindowService = ServiceLocator->GetServiceRef<IPortalApplicationWindow>();
PortalUserService = ServiceLocator->GetServiceRef<IPortalUser>();
PortalUserLoginService = ServiceLocator->GetServiceRef<IPortalUserLogin>();
}
EPluginAuthorizationState FPluginWardenAuthorizer::UpdateAuthorizationState(float DeltaTime)
{
EPluginAuthorizationState NewState = CurrentState;
switch ( CurrentState )
{
case EPluginAuthorizationState::Initializing:
{
WaitingTime = 0;
if ( PortalWindowService->IsAvailable() && PortalUserService->IsAvailable() )
{
NewState = EPluginAuthorizationState::AuthorizePlugin;
}
else
{
NewState = EPluginAuthorizationState::StartLauncher;
}
break;
}
case EPluginAuthorizationState::StartLauncher:
{
WaitingTime = 0;
ILauncherPlatform* LauncherPlatform = FLauncherPlatformModule::Get();
if (LauncherPlatform != nullptr )
{
if ( !FPlatformProcess::IsApplicationRunning(TEXT("EpicGamesLauncher")) &&
!FPlatformProcess::IsApplicationRunning(TEXT("EpicGamesLauncher-Mac-Shipping")) )
{
FOpenLauncherOptions SilentOpen;
if (LauncherPlatform->OpenLauncher(SilentOpen) )
{
NewState = EPluginAuthorizationState::StartLauncher_Waiting;
}
else
{
NewState = EPluginAuthorizationState::LauncherStartFailed;
}
}
else
{
// If the process is found to be running already, move into the next state.
NewState = EPluginAuthorizationState::StartLauncher_Waiting;
}
}
else
{
NewState = EPluginAuthorizationState::LauncherStartFailed;
}
break;
}
case EPluginAuthorizationState::StartLauncher_Waiting:
{
UE_LOG(PluginWarden, Log, TEXT("Waiting for launcher to run for the past %f seconds"), WaitingTime);
if ( FPlatformProcess::IsApplicationRunning(TEXT("EpicGamesLauncher")) && PortalWindowService->IsAvailable() && PortalUserService->IsAvailable() )
{
NewState = EPluginAuthorizationState::AuthorizePlugin;
}
else
{
WaitingTime += DeltaTime;
}
break;
}
case EPluginAuthorizationState::AuthorizePlugin:
{
WaitingTime = 0;
EntitlementResult = PortalUserService->IsEntitledToItem(PluginItemId, CacheLevel);
NewState = EPluginAuthorizationState::AuthorizePlugin_Waiting;
break;
}
case EPluginAuthorizationState::AuthorizePlugin_Waiting:
{
WaitingTime += DeltaTime;
check(EntitlementResult.GetFuture().IsValid());
if ( EntitlementResult.GetFuture().IsReady() )
{
FPortalUserIsEntitledToItemResult Entitlement = EntitlementResult.GetFuture().Get();
if ( Entitlement.IsEntitled )
{
NewState = EPluginAuthorizationState::Authorized;
}
else
{
NewState = EPluginAuthorizationState::IsUserSignedIn;
}
}
break;
}
case EPluginAuthorizationState::IsUserSignedIn:
{
WaitingTime = 0;
UserDetailsResult = PortalUserService->GetUserDetails();
NewState = EPluginAuthorizationState::IsUserSignedIn_Waiting;
break;
}
case EPluginAuthorizationState::IsUserSignedIn_Waiting:
{
WaitingTime += DeltaTime;
check(UserDetailsResult.GetFuture().IsValid());
if ( UserDetailsResult.GetFuture().IsReady() )
{
FPortalUserDetails UserDetails = UserDetailsResult.GetFuture().Get();
if ( UserDetails.IsSignedIn )
{
FPortalUserIsEntitledToItemResult Entitlement = EntitlementResult.GetFuture().Get();
if ( Entitlement.RetrievedFromCacheLevel == EEntitlementCacheLevelRetrieved::None )
{
// This is the case where there is no cached entitlement data and the latest entitlements have yet to be
// retrieved so we still don't know if the user is entitled so try again
if ( ++NumAuthorizationRetries < MaxAuthorizationRetries )
{
NewState = EPluginAuthorizationState::AuthorizePlugin;
}
else
{
// Give up and assume the user wasn't entitled
NewState = EPluginAuthorizationState::Unauthorized;
}
}
else
{
// The entitlement check was valid, the user is signed in and is not entitled, so clearly Unauthorized
NewState = EPluginAuthorizationState::Unauthorized;
}
}
else
{
// If they're not signed in, but they were unauthorized, they may have purchased it
// they may just need to sign-in.
if ( PortalUserLoginService->IsAvailable() )
{
NewState = EPluginAuthorizationState::SigninRequired;
}
else if ( ++NumLauncherRetries < MaxLauncherRetries )
{
// During testing, there's a flow where it goes through StartLauncher -> StartLauncher_Waiting -> AuthorizePlugin and
// ends up here without the Launcher actually running so try starting the Launcher again
NewState = EPluginAuthorizationState::StartLauncher;
}
else
{
// Give up and tell the user to sign in manually
NewState = EPluginAuthorizationState::SigninFailed;
}
}
}
break;
}
case EPluginAuthorizationState::SigninRequired:
{
WaitingTime = 0;
UserSigninResult = PortalUserLoginService->PromptUserForSignIn();
NewState = EPluginAuthorizationState::SigninRequired_Waiting;
break;
}
case EPluginAuthorizationState::SigninRequired_Waiting:
{
// We don't advance the wait time in the sign-required state, as this may take a long time.
check(UserSigninResult.GetFuture().IsValid());
if ( UserSigninResult.GetFuture().IsReady() )
{
// Note that the result of PromptUserForSignIn only says whether the portal successfully received and handled the request.
// It doesn't mean that the user signed in successfully or even that the existing user signed out.
bool IsUserPromptHandled = UserSigninResult.GetFuture().Get();
if ( IsUserPromptHandled )
{
// In this case, we only know that the user prompt was successful but we assume that the user signed in and that we can retrieve the details
UserDetailsResult = PortalUserService->GetUserDetails();
NewState = EPluginAuthorizationState::Signin_Waiting;
WaitingTime = 0;
}
else
{
// This state doesn't mean that the user is unauthorized but that the portal user login service wasn't able to handle the request
// So try again
if ( ++NumSignInRetries < MaxSigninRetries )
{
if (PortalUserLoginService->IsAvailable())
{
NewState = EPluginAuthorizationState::SigninRequired;
}
else
{
NewState = EPluginAuthorizationState::StartLauncher;
}
}
else
{
// Give up and tell the user to sign in manually
NewState = EPluginAuthorizationState::SigninFailed;
}
}
}
break;
}
// We stay in the Signin_Waiting state until the user is signed in or the timeout is reached.
case EPluginAuthorizationState::Signin_Waiting:
{
WaitingTime += DeltaTime;
UE_LOG(PluginWarden, Log, TEXT("Waiting for sign in for the past %f seconds"), WaitingTime);
check(UserDetailsResult.GetFuture().IsValid());
if ( UserDetailsResult.GetFuture().IsReady() )
{
FPortalUserDetails UserDetails = UserDetailsResult.GetFuture().Get();
int CurrentWaitSec = ((int)WaitingTime);
if ( UserDetails.IsSignedIn )
{
// If the user is now signed in, we can check for authorization again
NewState = EPluginAuthorizationState::AuthorizePlugin;
}
else if ( ( CurrentWaitSec % UserDetailsCheckPeriod ) == 0 && CurrentWaitSec != CurrentWaitLoopNumber )
{
// Every check period, try getting the UserDetails once to see if there's been any update in the signed-in status
UserDetailsResult = PortalUserService->GetUserDetails();
}
}
break;
}
}
// If we're in a waiting state, check to see if we're over the timeout period.
switch ( NewState )
{
case EPluginAuthorizationState::StartLauncher_Waiting:
case EPluginAuthorizationState::AuthorizePlugin_Waiting:
case EPluginAuthorizationState::IsUserSignedIn_Waiting:
case EPluginAuthorizationState::SigninRequired_Waiting:
{
if ( WaitingTime > (float)GeneralWaitingTimeout)
{
NewState = EPluginAuthorizationState::Timeout;
}
break;
}
case EPluginAuthorizationState::Signin_Waiting:
{
if ( WaitingTime > (float)SigninWaitingTimeout )
{
NewState = EPluginAuthorizationState::SigninFailed;
}
}
break;
}
CurrentState = NewState;
return CurrentState;
}
#undef LOCTEXT_NAMESPACE