<?php
namespace Cms\CoreBundle\Model\Contexts;
use Cms\CoreBundle\Model\Context;
use Cms\CoreBundle\Util\Doctrine\EntityManager;
use Doctrine\DBAL\Types\Types;
use Platform\SecurityBundle\Entity\Identity\Account;
use Cms\TenantBundle\Entity\Tenant;
use Cms\TenantBundle\Util\TenantDoctrineFilter;
use Platform\SecurityBundle\Service\AccountProvider;
use Products\NotificationsBundle\Entity\Profile;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
/**
* Class GlobalContext
*
* @package Cms\CoreBundle\Model\Contexts
*
* @method Tenant|null getTenant()
* @method string getMode()
* @method GlobalContext clearTenant()
* @method GlobalContext clearMode()
*/
final class GlobalContext extends Context
{
/**
* @var EntityManager
*/
private EntityManager $em;
/**
* @var TokenStorageInterface
*/
private TokenStorageInterface $tokenStorage;
/**
* @var AccountProvider
*/
private AccountProvider $accountProvider;
/**
* @var string
*/
private string $dashboardHostname;
/**
* @param EntityManager $em
* @param TokenStorageInterface $tokenStorage
* @param AccountProvider $accountProvider
* @param string $mode
* @param string $dashboardHostname
*/
public function __construct(
EntityManager $em,
TokenStorageInterface $tokenStorage,
AccountProvider $accountProvider,
string $mode,
string $dashboardHostname
)
{
parent::__construct(
array(
'tenant' => Context::LOCKED,
'mode' => Context::LOCKED,
)
);
$this->em = $em;
$this->tokenStorage = $tokenStorage;
$this->accountProvider = $accountProvider;
$this->escort(
function () use ($mode) {
$this->setMode($mode);
}
);
$this->dashboardHostname = $dashboardHostname;
}
/**
* Quick helper function to return a dashboard hostname.
* Paramter allows to choose a tenanted (such as "foobar.app.campussuite.com") or a tenant-less (such as "app.campussuite.com") domain.
*
* @param bool $tenantless
* @return string
*/
public function getDashboard(bool $tenantless = false): string
{
$hostname = $this->dashboardHostname;
if ($tenantless !== true) {
if ($this->getTenant() === null) {
throw new \RuntimeException();
}
$hostname = sprintf(
'%s.%s',
$this->getTenant()->getSlug(),
$hostname
);
}
return $hostname;
}
/**
* Quick helper function to return a dashboard URL.
* Paramter allows to choose a tenanted (such as "foobar.app.campussuite.com") or a tenant-less (such as "app.campussuite.com") domain.
*
* @param bool $tenantless
* @return string
*/
public function getDashboardUrl(bool $tenantless = false): string
{
return sprintf(
'https://%s/',
$this->getDashboard($tenantless)
);
}
/**
* Returns the account of the user who is currently logged in and authenticated with the system.
* In any context, like in the dashboard, when a user should be active, this function should not return NULL.
* For public-facing, unathenticated pages and things like CLI commands, this may return NULL.
* When impersonating, this function will still return the "original" account, NOT that account that has been chosen by that user to be impersonated.
*
* @return Account|null
*/
public function getAuthenticatedAccount(): ?Account
{
// obtain the token
$token = $this->tokenStorage->getToken();
if ( ! $token) {
return null;
}
// get the user from the token
$user = $token->getUser();
// if we are in an impersonation situation, get the user from the original token
// this user may need to be refreshed...
if ($token instanceof SwitchUserToken) {
$user = $token->getOriginalToken()->getUser();
if ($user) {
$user = $this->accountProvider->refreshUser($user);
}
}
if ($user instanceof Profile) {
return null;
}
// make sure we have a proper type
if ( ! $user instanceof Account) {
throw new \LogicException();
}
return $user;
}
/**
* Will return the account of the user being impersonated if impersonation is in effect.
* This is the account that the authenticated user has selected to impersonate.
* If no impersonation is being done, this returns NULL.
*
* @return Account|null
*/
public function getImpersonatedAccount(): ?Account
{
// obtain the token
$token = $this->tokenStorage->getToken();
// must be an impersonation token, grab the user from it
if ($token instanceof SwitchUserToken) {
return $token->getUser();
}
// not impersonating
return null;
}
/**
* Returns the account for which security checks should run against by default.
* If an account is being impersonated, that object will be returned.
* Otherwise, it returns the currently authenticated account.
* This is the general function to be used when trying to get the "current user" in a situation, unless there is a need to differentiate authenticated versus impersonated account.
*
* @return Account|null
*/
public function getEffectiveAccount(): ?Account
{
// profiler toolbar is not loading on /_portal/login if the return type is not Account|null
return $this->getImpersonatedAccount() ?: $this->getAuthenticatedAccount();
}
/**
* @return bool
*/
public function isImpersonating(): bool
{
return (bool) $this->getImpersonatedAccount();
}
/**
* TODO: remove this function and fix all uses; should be using the "kernel.environment" parameter instead.
*
* @param string $value
* @return $this
* @deprecated
*/
public function setMode(string $value): self
{
// ensure mode is legit
switch ($value) {
case 'dev':
case 'test':
case 'prod':
break;
default:
throw new \RuntimeException();
}
// call parent to actually set
return $this->set('mode', $value);
}
/**
* Sets, or clears, the currently associated tenant with the request/command.
* This stores the given tenant and enables it on the core background mechanisms that are responsible for attaching the tenant information to each tenanted record in the database.
*
* @param Tenant|null $value
* @return $this
*/
public function setTenant(Tenant $value = null): self
{
// branch on null values
if ($value !== null) {
// enable doctrine filter
$this->em
->getFilters()
->enable(TenantDoctrineFilter::FILTER)
->setParameter(
'id',
$value->getId(),
Types::INTEGER,
);
} else if ($this->em->getFilters()->isEnabled(TenantDoctrineFilter::FILTER)) {
// disable the filter
$this->em
->getFilters()
->disable(TenantDoctrineFilter::FILTER)
->setParameter(
'id',
0,
Types::INTEGER,
);
}
// call our parent to set value
return $this->set('tenant', $value);
}
}