<?php
namespace Platform\SecurityBundle\Listeners\OneRoster;
use Cms\CoreBundle\Entity\AbstractOneRosterEntity;
use Cms\CoreBundle\Entity\OneRosterSync;
use Cms\CoreBundle\Events\OneRosterEvents;
use Cms\CoreBundle\Model\Interfaces\OneRosterable\AbstractOneRosterableSubscriber;
use Doctrine\Common\Util\ClassUtils;
use Platform\QueueBundle\Event\AsyncEvent;
use Platform\SecurityBundle\Entity\Identity\Account;
use Platform\SecurityBundle\Entity\Identity\Group;
/**
* Class OneRosterPrepareSubscriber
* @package Platform\SecurityBundle\Listeners\OneRoster
*/
final class OneRosterPrepareSubscriber extends AbstractOneRosterableSubscriber
{
// TODO: before running one roster sync, need to change these in the database
const NAME_FORMAT = 'campussuite.platform.security.groups.fixed.one_roster.%s';
const NAMES = [
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__ADMINISTRATOR,
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__AIDE,
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__PROCTOR,
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__TEACHER,
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__STAFF,
];
// NEW
const ALIASES = [
'oneroster.global.all' => [
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__ADMINISTRATOR,
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__AIDE,
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__PROCTOR,
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__TEACHER,
AbstractOneRosterEntity::ENUMS__ROLE_TYPE__STAFF,
],
'oneroster.global.'.AbstractOneRosterEntity::ENUMS__ROLE_TYPE__ADMINISTRATOR => [AbstractOneRosterEntity::ENUMS__ROLE_TYPE__ADMINISTRATOR],
'oneroster.global.'.AbstractOneRosterEntity::ENUMS__ROLE_TYPE__AIDE => [AbstractOneRosterEntity::ENUMS__ROLE_TYPE__AIDE],
'oneroster.global.'.AbstractOneRosterEntity::ENUMS__ROLE_TYPE__PROCTOR => [AbstractOneRosterEntity::ENUMS__ROLE_TYPE__PROCTOR],
'oneroster.global.'.AbstractOneRosterEntity::ENUMS__ROLE_TYPE__TEACHER => [AbstractOneRosterEntity::ENUMS__ROLE_TYPE__TEACHER],
'oneroster.global.'.AbstractOneRosterEntity::ENUMS__ROLE_TYPE__STAFF => [AbstractOneRosterEntity::ENUMS__ROLE_TYPE__STAFF],
];
/**
* {@inheritdoc}
*/
static public function getSubscribedEvents(): array
{
return [
OneRosterEvents::EVENT__PREPARE => [
['groupsSync', 0],
['deactivateMissing', 0],
],
];
}
/**
* @param AsyncEvent $event
*/
public function groupsSync(AsyncEvent $event): void
{
// data should be an array with an id of a sync
$job = $this->loadJob($event);
// DEBUGGING
$event->getOutput()->writeln(sprintf(
'Sync #%s loaded',
$job->getIdentifier()
));
// ensure we are meant to process this
if ( ! $this->checkTypes($job->getSync(), [
OneRosterSync::STRATEGIES__SSO,
])) {
return;
}
// holder for groups to make
$groups = [];
// loop over all the groups we need to make
foreach (self::NAMES as $name) {
// fix the name
$name = sprintf(
self::NAME_FORMAT,
$name
);
// attempt to load the group by name
$group = $this->em->getRepository(Group::class)->findOneBy([
'name' => $this->translator->trans($name),
]);
// now if null, we need to make a new one
if (empty($group)) {
$group = new Group();
}
// update fields on the account
$group
->setName($this->translator->trans($name))
->setAlias($name)
->setFixed(true)
->setOneRosterId($job->getSync()->getDistrictId())
;
// attach to array of things to save
$groups[] = $group;
}
// NEW: loop over all the groups we need to make
foreach (array_keys(self::ALIASES) as $alias) {
// attempt to load the group by alias
$group = $this->em->getRepository(Group::class)->findOneBy([
'alias' => $alias,
]);
// now if null, we need to make a new one
if (empty($group)) {
$group = new Group();
}
// update fields on the account
$group
->setName($this->translator->trans(sprintf(
'campussuite.platform.security.groups.aliases.%s',
$alias
)))
->setAlias($alias)
->setFixed(true)
->setOneRosterId($job->getSync()->getDistrictId())
;
// attach to array of things to save
$groups[] = $group;
}
// cache the output
$output = array_map(
function (Group $grp) {
return sprintf(
' %s %s (%s | %s | %s)',
((empty($grp->getId())) ? 'Generating' : 'Updating'),
ClassUtils::getClass($grp),
$grp->getName(),
$grp->getOneRosterId(),
$grp->getId() ?: '-'
);
},
$groups
);
// let's save the groups
$this->em->saveAll($groups);
// DEBUGGING
$event->getOutput()->writeln($output);
}
/**
* @param AsyncEvent $event
*/
public function deactivateMissing(AsyncEvent $event): void
{
// data should be an array with an id of a sync
$job = $this->loadJob($event);
// run bulk query
// any account that has oneroster id that does not exist needs to be deactivated
$changes = $this->em->createQueryBuilder()
->update(Account::class, 'accounts')
->set('accounts.active', ':active')
->setParameter('active', false)
->andWhere('accounts.tenant = :tenant')// filter by tenant
->setParameter('tenant', $job->getTenant()->getId())
->andWhere('accounts.onerosterId IS NOT NULL')// for performance, only update ones not already without oneroster hooks
->andWhere($this->em->getExpressionBuilder()->notIn(
'accounts.onerosterId',
$this->em->createQueryBuilder()
->select('objects.sourcedId')
->from(AbstractOneRosterEntity::class, 'objects')
->andWhere('objects.tenant = :tenant')// we are making a string subquery, so the tenant var in the other qb will be used here eventually
->getDQL()
))// only match objects that are missing from stashed objects
->getQuery()
->execute();
// DEBUGGING
$event->getOutput()->writeln(sprintf(
'Deactivated %s accounts for sync #%s',
$changes,
$job->getIdentifier()
));
}
}