<?php
namespace PaperKite\EmployeeApi\Service;
use Lightbulb\Service\Image\InitialAvatarGenerator;
use Lightbulb\Symfony\Exception\ServiceUnavailableException;
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Error;
use OneLogin\Saml2\ValidationError;
use PaperKite\Common\Service\DepartmentService;
use PaperKite\EmployeeApi\Entity\EmployeeInterface;
class SamlService
{
private Auth $samlAuth;
/**
* @throws Error
* @throws ServiceUnavailableException
*/
public function __construct(
private EmployeeService $employeeService,
private DepartmentService $departmentService,
private InitialAvatarGenerator $initialAvatarGenerator,
array $samlConfig,
string $certificatePath,
) {
if (false === file_exists($certificatePath)) {
throw new ServiceUnavailableException('Misconfigured server.');
}
$samlConfig['idp']['x509cert'] = file_get_contents($certificatePath);
$this->samlAuth = new Auth($samlConfig);
}
/**
* @throws Error
*/
public function getSsoLoginUrl($returnUrl = null): ?string
{
// Try Windows Integrated Authentication by default
// This will work for domain-joined machines with proper browser configuration
$parameters = [
'AuthnContextClassRef' => 'urn:federation:authentication:windows',
];
// Don't use passive mode - let ADFS handle the authentication flow
// ADFS will automatically fall back to forms authentication if Windows auth fails
return $this->samlAuth->login($returnUrl, $parameters, false, false, true);
}
/**
* @throws Error
*/
public function getSsoLogoutUrl($returnUrl = null): ?string
{
return $this->samlAuth->logout($returnUrl, [], null, null, true);
}
/**
* @throws ServiceUnavailableException
* @throws ValidationError
* @throws Error
*/
public function handleSamlToken(): EmployeeInterface
{
$this->samlAuth->processResponse();
/* @noinspection PhpUnreachableStatementInspection */
if ($this->samlAuth->isAuthenticated()) {
$userUniqueId = $this->samlAuth->getNameId();
// Check for user in db
$storedEmployee = null;
$employee = $this->employeeService->findEmployeeByUserName($this->samlAuth->getNameId());
if (null === $employee) {
$employee = $this->employeeService->createEmployeeEntity(
$userUniqueId,
['ROLE_USER'],
$userUniqueId,
);
} else {
$storedEmployee = clone $employee;
}
// Handle department
$department = null;
if (false === empty($this->getSamlAttributeValues('Department')[0])) {
$department = $this->departmentService->getByExternalIdCreateIfNotExist($this->getSamlAttributeValues('Department')[0]);
}
// Handle avatar url
$avatarImageUrl = false === empty($employee->getAvatarImageUrl())
? $employee->getAvatarImageUrl()
: $this->initialAvatarGenerator->generateAsBase64Url($userUniqueId);
// Set the new values to the entity
$employee
->setDepartment($department)
->setAvatarImageUrl($avatarImageUrl)
->setPhone($this->getSamlAttributeValues('Telephone-Number')[0])
->setPassword('thisFieldWillNotBeUsedForAuthentication')
->setFirstName($this->getSamlAttributeValues('givenname')[0])
->setLastName($this->getSamlAttributeValues('surname')[0])
->setEmail($this->getSamlAttributeValues('emailaddress')[0])
->setDeletedAt(null)
->setRoles($this->retrieveRolesFromLdapEmployee());
$otherTelephone = $this->getSamlAttributeValues('otherTelephone');
if (false === empty($otherTelephone) && false === empty($otherTelephone[0])) {
$employee->setDepartmentPhone($otherTelephone[0]);
}
// Only persist when there was a change
if (null === $storedEmployee || true === $storedEmployee->hasDifferences($employee)) {
$this->employeeService->storeEmployeeEntity($employee);
}
return $employee;
} else {
throw new ServiceUnavailableException('SSO Login issue');
}
}
private function getSamlAttributeValues(string $key): array
{
foreach ($this->samlAuth->getAttributes() as $attributeKey => $values) {
if (true === str_contains($attributeKey, $key)) {
return $values;
}
}
return [];
}
private function retrieveRolesFromLdapEmployee(): array
{
$roles = ['ROLE_USER'];
foreach ($this->getSamlAttributeValues('Group') as $ldapRole) {
switch ($ldapRole) {
case 'PPL_super_admin':
$roles[] = 'ROLE_SUPER_ADMIN';
break;
case 'PPL_admin':
$roles[] = 'ROLE_CMCM_ADMIN';
break;
case 'PPL_department_manager':
$roles[] = 'ROLE_DEPARTMENT_MANAGER';
break;
case 'PPL_marketing':
$roles[] = 'ROLE_ADMIN_MARKETING';
break;
case 'PPL_support_messages':
$roles[] = 'ROLE_ADMIN_SUPPORT';
break;
case 'PPL_employee':
$roles[] = 'ROLE_EMPLOYEE';
break;
}
}
return $roles;
}
public function ssoLogout(): ?string
{
return $this->samlAuth->logout(null, [], null, null, true);
}
}