src/EmployeeApi/Service/SamlService.php line 21

Open in your IDE?
  1. <?php
  2. namespace PaperKite\EmployeeApi\Service;
  3. use Lightbulb\Service\Image\InitialAvatarGenerator;
  4. use Lightbulb\Symfony\Exception\ServiceUnavailableException;
  5. use OneLogin\Saml2\Auth;
  6. use OneLogin\Saml2\Error;
  7. use OneLogin\Saml2\ValidationError;
  8. use PaperKite\Common\Service\DepartmentService;
  9. use PaperKite\EmployeeApi\Entity\EmployeeInterface;
  10. class SamlService
  11. {
  12.     private Auth $samlAuth;
  13.     /**
  14.      * @throws Error
  15.      * @throws ServiceUnavailableException
  16.      */
  17.     public function __construct(
  18.         private EmployeeService $employeeService,
  19.         private DepartmentService $departmentService,
  20.         private InitialAvatarGenerator $initialAvatarGenerator,
  21.         array $samlConfig,
  22.         string $certificatePath,
  23.     ) {
  24.         if (false === file_exists($certificatePath)) {
  25.             throw new ServiceUnavailableException('Misconfigured server.');
  26.         }
  27.         $samlConfig['idp']['x509cert'] = file_get_contents($certificatePath);
  28.         $this->samlAuth = new Auth($samlConfig);
  29.     }
  30.     /**
  31.      * @throws Error
  32.      */
  33.     public function getSsoLoginUrl($returnUrl null): ?string
  34.     {
  35.         // Try Windows Integrated Authentication by default
  36.         // This will work for domain-joined machines with proper browser configuration
  37.         $parameters = [
  38.             'AuthnContextClassRef' => 'urn:federation:authentication:windows',
  39.         ];
  40.         // Don't use passive mode - let ADFS handle the authentication flow
  41.         // ADFS will automatically fall back to forms authentication if Windows auth fails
  42.         return $this->samlAuth->login($returnUrl$parametersfalsefalsetrue);
  43.     }
  44.     /**
  45.      * @throws Error
  46.      */
  47.     public function getSsoLogoutUrl($returnUrl null): ?string
  48.     {
  49.         return $this->samlAuth->logout($returnUrl, [], nullnulltrue);
  50.     }
  51.     /**
  52.      * @throws ServiceUnavailableException
  53.      * @throws ValidationError
  54.      * @throws Error
  55.      */
  56.     public function handleSamlToken(): EmployeeInterface
  57.     {
  58.         $this->samlAuth->processResponse();
  59.         /* @noinspection PhpUnreachableStatementInspection */
  60.         if ($this->samlAuth->isAuthenticated()) {
  61.             $userUniqueId $this->samlAuth->getNameId();
  62.             // Check for user in db
  63.             $storedEmployee null;
  64.             $employee $this->employeeService->findEmployeeByUserName($this->samlAuth->getNameId());
  65.             if (null === $employee) {
  66.                 $employee $this->employeeService->createEmployeeEntity(
  67.                     $userUniqueId,
  68.                     ['ROLE_USER'],
  69.                     $userUniqueId,
  70.                 );
  71.             } else {
  72.                 $storedEmployee = clone $employee;
  73.             }
  74.             // Handle department
  75.             $department null;
  76.             if (false === empty($this->getSamlAttributeValues('Department')[0])) {
  77.                 $department $this->departmentService->getByExternalIdCreateIfNotExist($this->getSamlAttributeValues('Department')[0]);
  78.             }
  79.             // Handle avatar url
  80.             $avatarImageUrl false === empty($employee->getAvatarImageUrl())
  81.                 ? $employee->getAvatarImageUrl()
  82.                 : $this->initialAvatarGenerator->generateAsBase64Url($userUniqueId);
  83.             // Set the new values to the entity
  84.             $employee
  85.                 ->setDepartment($department)
  86.                 ->setAvatarImageUrl($avatarImageUrl)
  87.                 ->setPhone($this->getSamlAttributeValues('Telephone-Number')[0])
  88.                 ->setPassword('thisFieldWillNotBeUsedForAuthentication')
  89.                 ->setFirstName($this->getSamlAttributeValues('givenname')[0])
  90.                 ->setLastName($this->getSamlAttributeValues('surname')[0])
  91.                 ->setEmail($this->getSamlAttributeValues('emailaddress')[0])
  92.                 ->setDeletedAt(null)
  93.                 ->setRoles($this->retrieveRolesFromLdapEmployee());
  94.             $otherTelephone $this->getSamlAttributeValues('otherTelephone');
  95.             if (false === empty($otherTelephone) && false === empty($otherTelephone[0])) {
  96.                 $employee->setDepartmentPhone($otherTelephone[0]);
  97.             }
  98.             // Only persist when there was a change
  99.             if (null === $storedEmployee || true === $storedEmployee->hasDifferences($employee)) {
  100.                 $this->employeeService->storeEmployeeEntity($employee);
  101.             }
  102.             return $employee;
  103.         } else {
  104.             throw new ServiceUnavailableException('SSO Login issue');
  105.         }
  106.     }
  107.     private function getSamlAttributeValues(string $key): array
  108.     {
  109.         foreach ($this->samlAuth->getAttributes() as $attributeKey => $values) {
  110.             if (true === str_contains($attributeKey$key)) {
  111.                 return $values;
  112.             }
  113.         }
  114.         return [];
  115.     }
  116.     private function retrieveRolesFromLdapEmployee(): array
  117.     {
  118.         $roles = ['ROLE_USER'];
  119.         foreach ($this->getSamlAttributeValues('Group') as $ldapRole) {
  120.             switch ($ldapRole) {
  121.                 case 'PPL_super_admin':
  122.                     $roles[] = 'ROLE_SUPER_ADMIN';
  123.                     break;
  124.                 case 'PPL_admin':
  125.                     $roles[] = 'ROLE_CMCM_ADMIN';
  126.                     break;
  127.                 case 'PPL_department_manager':
  128.                     $roles[] = 'ROLE_DEPARTMENT_MANAGER';
  129.                     break;
  130.                 case 'PPL_marketing':
  131.                     $roles[] = 'ROLE_ADMIN_MARKETING';
  132.                     break;
  133.                 case 'PPL_support_messages':
  134.                     $roles[] = 'ROLE_ADMIN_SUPPORT';
  135.                     break;
  136.                 case 'PPL_employee':
  137.                     $roles[] = 'ROLE_EMPLOYEE';
  138.                     break;
  139.             }
  140.         }
  141.         return $roles;
  142.     }
  143.     public function ssoLogout(): ?string
  144.     {
  145.         return $this->samlAuth->logout(null, [], nullnulltrue);
  146.     }
  147. }