src/Platform/SecurityBundle/Listeners/OneRoster/OneRosterLinkUserSubscriber.php line 178

Open in your IDE?
  1. <?php
  2. namespace Platform\SecurityBundle\Listeners\OneRoster;
  3. use Cms\CoreBundle\Entity\OneRoster\OneRosterUser;
  4. use Cms\CoreBundle\Entity\OneRosterSync;
  5. use Cms\CoreBundle\Events\OneRosterLinkEvent;
  6. use Cms\CoreBundle\Model\Interfaces\OneRosterable\AbstractOneRosterableSubscriber;
  7. use Doctrine\Common\Util\ClassUtils;
  8. use Platform\SecurityBundle\Entity\Access\GroupAccount;
  9. use Platform\SecurityBundle\Entity\Identity\Account;
  10. use Platform\SecurityBundle\Entity\Identity\Group;
  11. /**
  12.  * Class OneRosterLinkUserSubscriber
  13.  * @package Platform\SecurityBundle\Listeners\OneRoster
  14.  */
  15. final class OneRosterLinkUserSubscriber extends AbstractOneRosterableSubscriber
  16. {
  17.     /**
  18.      * {@inheritdoc}
  19.      */
  20.     static public function getSubscribedEvents(): array
  21.     {
  22.         return [
  23.             OneRosterLinkEvent::EVENT__USER => [
  24.                 ['groupSyncOld'0],
  25.                 ['groupsSync'0],
  26.             ],
  27.         ];
  28.     }
  29.     /**
  30.      * @param OneRosterLinkEvent $event
  31.      */
  32.     public function groupSyncOld(OneRosterLinkEvent $event): void
  33.     {
  34.         // ensure we are meant to process this
  35.         if ( ! $this->checkTypes($event->getSync(), [
  36.             OneRosterSync::STRATEGIES__SSO,
  37.         ])) {
  38.             return;
  39.         }
  40.         // get the user
  41.         $user $event->getEntity();
  42.         if ( ! $user instanceof OneRosterUser) {
  43.             throw new \Exception(sprintf(
  44.                 'SSO: User is not of proper type, got "%s".',
  45.                 ClassUtils::getClass($user)
  46.             ));
  47.         }
  48.         // check and make sure that we are of a proper role
  49.         if ( ! $user->isRoleStaff()) {
  50.             // DEBUGGING
  51.             $event->getOutput()->writeln(sprintf(
  52.                 'User role for #%s is "%s", skipping...',
  53.                 $user->getSourcedId(),
  54.                 $user->getRole()
  55.             ));
  56.             // quit early, nothing else to do since not staff
  57.             return;
  58.         }
  59.         // attempt to load an account for us
  60.         $account $this->em->getRepository(Account::class)->findOneByOneRosterId(
  61.             $user->getSourcedId()
  62.         );
  63.         // if we don't have one, that may be a problem
  64.         if (empty($account)) {
  65.             // if the user doesn't have an email, this isn't weird as they would have been skipped over in the process phase
  66.             if ( ! $user->getEmail()) {
  67.                 // DEBUGGING
  68.                 $event->getOutput()->writeln(sprintf(
  69.                     'User missing email for #%s, skipping...',
  70.                     $user->getSourcedId()
  71.                 ));
  72.                 // quit early, nothing else to do since not staff
  73.                 return;
  74.             } else {
  75.                 // NOTE: JEZ 2022-01-20
  76.                 // there are many reasons why this would happen
  77.                 // some are legit, and some could be bugs in the syncing code
  78.                 // most are probably legit, so no longer throwing errors for this stuff
  79.                 // if the email is set and nothing matched, then we have an issue...
  80.                 /*
  81.                 throw new \Exception(
  82.                     'SSO: Could not find user account by OneRoster ID.'
  83.                 );
  84.                 */
  85.                 // DEBUGGING
  86.                 $event->getOutput()->writeln(sprintf(
  87.                     'User absent from database for #%s, skipping...',
  88.                     $user->getSourcedId()
  89.                 ));
  90.                 // quit early, nothing else to do since not staff
  91.                 return;
  92.             }
  93.         }
  94.         // we need to get the groups tied to us for oneroster
  95.         $group $this->em->getRepository(Group::class)->findOneBy([
  96.             'name' => $this->translator->trans(sprintf(
  97.                 OneRosterPrepareSubscriber::NAME_FORMAT,
  98.                 $user->getRole()
  99.             )),
  100.         ]);
  101.         if (empty($group)) {
  102.             throw new \Exception(sprintf(
  103.                 'SSO: Could not load security group for role "%s".',
  104.                 $user->getRole()
  105.             ));
  106.         }
  107.         // get our existing group association
  108.         $membership $this->em->getRepository(GroupAccount::class)->findByAccountAndGroup(
  109.             $account,
  110.             $group
  111.         );
  112.         // if we don't have one, need to make one
  113.         if ( ! $membership) {
  114.             $membership = new GroupAccount();
  115.         }
  116.         // set things
  117.         $membership
  118.             ->setAlias(sprintf(
  119.                 'campussuite.platform.security.membership.%s.%s',
  120.                 $group->getId(),
  121.                 $account->getId()
  122.             ))
  123.             ->setFixed(true)
  124.             ->setAccount($account)
  125.             ->setGroup($group);
  126.         // oneroster stuff
  127.         $membership
  128.             ->setOneRosterId($user->getSourcedId())
  129.             ->setOneRosterArchived($user->isStatusToBeDeleted());
  130.         // cache the output
  131.         $output sprintf(
  132.             '    %s    %s    (%s >> %s | %s | %s)',
  133.             (empty($membership->getId())) ? 'Generating' 'Updating',
  134.             ClassUtils::getClass($membership),
  135.             $membership->getAccount()->getUserIdentifier(),
  136.             $membership->getGroup()->getName(),
  137.             $membership->getOneRosterId(),
  138.             $membership->getId() ?: '-'
  139.         );
  140.         // do update
  141.         $this->em->save($membership);
  142.         // DEBUGGING
  143.         $event->getOutput()->writeln($output);
  144.     }
  145.     /**
  146.      * @param OneRosterLinkEvent $event
  147.      */
  148.     public function groupsSync(OneRosterLinkEvent $event): void
  149.     {
  150.         // ensure we are meant to process this
  151.         if ( ! $this->checkTypes($event->getSync(), [
  152.             OneRosterSync::STRATEGIES__SSO,
  153.         ])) {
  154.             return;
  155.         }
  156.         // get the user
  157.         $user $event->getEntity();
  158.         if ( ! $user instanceof OneRosterUser) {
  159.             throw new \Exception(sprintf(
  160.                 'SSO: User is not of proper type, got "%s".',
  161.                 ClassUtils::getClass($user)
  162.             ));
  163.         }
  164.         // check and make sure that we are of a proper role
  165.         if ( ! $user->isRoleStaff()) {
  166.             // DEBUGGING
  167.             $event->getOutput()->writeln(sprintf(
  168.                 'User role for #%s is "%s", skipping...',
  169.                 $user->getSourcedId(),
  170.                 $user->getRole()
  171.             ));
  172.             // quit early, nothing else to do since not staff
  173.             return;
  174.         }
  175.         // attempt to load an account for us
  176.         $account $this->em->getRepository(Account::class)->findOneByOneRosterId(
  177.             $user->getSourcedId()
  178.         );
  179.         // if we don't have one, that may be a problem
  180.         if (empty($account)) {
  181.             // if the user doesn't have an email, this isn't weird as they would have been skipped over in the process phase
  182.             if ( ! $user->getEmail()) {
  183.                 // DEBUGGING
  184.                 $event->getOutput()->writeln(sprintf(
  185.                     'User missing email for #%s, skipping...',
  186.                     $user->getSourcedId()
  187.                 ));
  188.                 // quit early, nothing else to do since not staff
  189.                 return;
  190.             } else {
  191.                 // NOTE: JEZ 2022-01-20
  192.                 // there are many reasons why this would happen
  193.                 // some are legit, and some could be bugs in the syncing code
  194.                 // most are probably legit, so no longer throwing errors for this stuff
  195.                 // if the email is set and nothing matched, then we have an issue...
  196.                 /*
  197.                 throw new \Exception(
  198.                     'SSO: Could not find user account by OneRoster ID.'
  199.                 );
  200.                 */
  201.                 // DEBUGGING
  202.                 $event->getOutput()->writeln(sprintf(
  203.                     'User absent from database for #%s, skipping...',
  204.                     $user->getSourcedId()
  205.                 ));
  206.                 // quit early, nothing else to do since not staff
  207.                 return;
  208.             }
  209.         }
  210.         // determine the aliases we need to look for
  211.         $aliases array_values(array_filter(array_unique([
  212.             'oneroster.global.all',
  213.             $user->getRole() ? 'oneroster.global.'.$user->getRole() : null,
  214.             ...array_merge(...array_map(
  215.                 function (array $org) use ($user) {
  216.                     return [
  217.                         sprintf(
  218.                             'oneroster.school.all.%s',
  219.                             $org['sourcedId']
  220.                         ),
  221.                         sprintf(
  222.                             'oneroster.school.%s.%s',
  223.                             $user->getRole(),
  224.                             $org['sourcedId']
  225.                         ),
  226.                     ];
  227.                 },
  228.                 $user->getOrgs()
  229.             ))
  230.         ])));
  231.         // we need to get the groups tied to us for oneroster
  232.         $groups $this->em->getRepository(Group::class)->findBy([
  233.             'alias' => $aliases,
  234.         ]);
  235.         if (empty($groups)) {
  236.             throw new \Exception(sprintf(
  237.                 'SSO: Could not load security groups for role "%s".',
  238.                 $user->getRole()
  239.             ));
  240.         }
  241.         // TODO: should we check that the number of groups found matches the number of aliases?
  242.         // find all of our existing memberships that are covered by this code
  243.         /** @var GroupAccount[] $memberships */
  244.         $memberships $this->em->getRepository(GroupAccount::class)->createQueryBuilder('memberships')
  245.             ->andWhere('memberships.account = :account')
  246.             ->setParameter('account'$account)
  247.             ->andWhere('memberships.alias LIKE :alias')
  248.             ->setParameter('alias''oneroster.%')
  249.             ->getQuery()
  250.             ->getResult();
  251.         // loop over the groups
  252.         $managed = [];
  253.         foreach ($groups as $group) {
  254.             // try to find an existing relationship
  255.             $membership null;
  256.             foreach ($memberships as $thing) {
  257.                 if ($thing->getGroup() === $group) {
  258.                     $membership $thing;
  259.                     break;
  260.                 }
  261.             }
  262.             // if we don't have one, we may need to make one
  263.             if ( ! $membership) {
  264.                 // first, see if the person has been added to this group manually
  265.                 // if so, it will become managed by syncing code
  266.                 $membership $this->em->getRepository(GroupAccount::class)->findByAccountAndGroup(
  267.                     $account,
  268.                     $group
  269.                 );
  270.                 // if we still don't have one, that means we need to make a brand new one
  271.                 if ( ! $membership) {
  272.                     $membership = new GroupAccount();
  273.                 }
  274.             }
  275.             // set things
  276.             $managed[] = $membership
  277.                 ->setAlias(sprintf(
  278.                     'oneroster.%s.%s',
  279.                     $group->getOneRosterId(),
  280.                     $account->getOneRosterId()
  281.                 ))
  282.                 ->setFixed(true)
  283.                 ->setAccount($account)
  284.                 ->setGroup($group);
  285.             // oneroster stuff
  286.             $membership
  287.                 ->setOneRosterId($user->getSourcedId())
  288.                 ->setOneRosterArchived($user->isStatusToBeDeleted());
  289.         }
  290.         // DEBUGGING
  291.         array_walk($managed, function (GroupAccount $membership) use ($event) {
  292.             $event->getOutput()->writeln(sprintf(
  293.                 '    %s    %s (%s >> %s | %s | %s)',
  294.                 (empty($membership->getId())) ? 'Generating' 'Updating',
  295.                 ClassUtils::getClass($membership),
  296.                 $membership->getAccount()->getEmail(),
  297.                 $membership->getGroup()->getName(),
  298.                 $membership->getOneRosterId(),
  299.                 $membership->getId() ?: '-'
  300.             ));
  301.         });
  302.         // go ahead and save the things we know want to keep
  303.         // this allows us to get ids that are needed in the following checks
  304.         $this->em->saveAll($managed);
  305.         // determine which ones we need to remove
  306.         $removals array_udiff($memberships$managed, function (GroupAccount $aGroupAccount $b) {
  307.             return ($a === $b) ? $a->getId() <=> $b->getId();
  308.         });
  309.         // DEBUGGING
  310.         array_walk($removals, function (GroupAccount $membership) use ($event) {
  311.             $event->getOutput()->writeln(sprintf(
  312.                 '    Deleting    %s (%s >> %s | %s | %s)',
  313.                 ClassUtils::getClass($membership),
  314.                 $membership->getAccount()->getEmail(),
  315.                 $membership->getGroup()->getName(),
  316.                 $membership->getOneRosterId(),
  317.                 $membership->getId() ?: '-'
  318.             ));
  319.         });
  320.         // remove things we no longer need
  321.         $this->em->deleteAll($removals);
  322.     }
  323. }