parent
ac17881087
commit
469768213d
@ -0,0 +1,26 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Chamilo\CoreBundle\Controller\OAuth2; |
||||
|
||||
use Chamilo\CoreBundle\ServiceHelper\AuthenticationConfigHelper; |
||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry; |
||||
use Symfony\Component\HttpFoundation\Response; |
||||
use Symfony\Component\Routing\Attribute\Route; |
||||
|
||||
class AzureProviderController extends AbstractProviderController |
||||
{ |
||||
#[Route('/connect/azure', name: 'chamilo.oauth2_azure_start')] |
||||
public function connect( |
||||
ClientRegistry $clientRegistry, |
||||
AuthenticationConfigHelper $authenticationConfigHelper, |
||||
): Response { |
||||
return $this->getStartResponse('azure', $clientRegistry, $authenticationConfigHelper); |
||||
} |
||||
|
||||
#[Route('/connect/azure/check', name: 'chamilo.oauth2_azure_check')] |
||||
public function connectCheck(): void {} |
||||
} |
@ -0,0 +1,83 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Chamilo\CoreBundle\Security\Authenticator\OAuth2; |
||||
|
||||
use Chamilo\CoreBundle\Entity\User; |
||||
use Chamilo\CoreBundle\Repository\Node\UserRepository; |
||||
use Chamilo\CoreBundle\ServiceHelper\AccessUrlHelper; |
||||
use Chamilo\CoreBundle\ServiceHelper\AuthenticationConfigHelper; |
||||
use Chamilo\CoreBundle\ServiceHelper\AzureAuthenticatorHelper; |
||||
use Doctrine\ORM\EntityManagerInterface; |
||||
use Doctrine\ORM\NonUniqueResultException; |
||||
use KnpU\OAuth2ClientBundle\Client\ClientRegistry; |
||||
use League\OAuth2\Client\Token\AccessToken; |
||||
use Symfony\Component\HttpFoundation\Request; |
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; |
||||
use Symfony\Component\Routing\RouterInterface; |
||||
use TheNetworg\OAuth2\Client\Provider\Azure; |
||||
|
||||
class AzureAuthenticator extends AbstractAuthenticator |
||||
{ |
||||
protected string $providerName = 'azure'; |
||||
|
||||
public function __construct( |
||||
ClientRegistry $clientRegistry, |
||||
RouterInterface $router, |
||||
UserRepository $userRepository, |
||||
AuthenticationConfigHelper $authenticationConfigHelper, |
||||
AccessUrlHelper $urlHelper, |
||||
EntityManagerInterface $entityManager, |
||||
private readonly AzureAuthenticatorHelper $azureHelper, |
||||
) { |
||||
parent::__construct( |
||||
$clientRegistry, |
||||
$router, |
||||
$userRepository, |
||||
$authenticationConfigHelper, |
||||
$urlHelper, |
||||
$entityManager |
||||
); |
||||
} |
||||
|
||||
public function supports(Request $request): ?bool |
||||
{ |
||||
return 'chamilo.oauth2_azure_check' === $request->attributes->get('_route'); |
||||
} |
||||
|
||||
/** |
||||
* @throws NonUniqueResultException |
||||
*/ |
||||
protected function userLoader(AccessToken $accessToken): User |
||||
{ |
||||
/** @var Azure $provider */ |
||||
$provider = $this->client->getOAuth2Provider(); |
||||
|
||||
$me = $provider->get('/me', $accessToken); |
||||
|
||||
if (empty($me['mail'])) { |
||||
throw new UnauthorizedHttpException( |
||||
'The mail field is empty in Azure AD and is needed to set the organisation email for this user.' |
||||
); |
||||
} |
||||
|
||||
if (empty($me['mailNickname'])) { |
||||
throw new UnauthorizedHttpException( |
||||
'The mailNickname field is empty in Azure AD and is needed to set the unique username for this user.' |
||||
); |
||||
} |
||||
|
||||
if (empty($me['objectId'])) { |
||||
throw new UnauthorizedHttpException( |
||||
'The id field is empty in Azure AD and is needed to set the unique Azure ID for this user.' |
||||
); |
||||
} |
||||
|
||||
$userId = $this->azureHelper->registerUser($me); |
||||
|
||||
return $this->userRepository->find($userId); |
||||
} |
||||
} |
@ -0,0 +1,198 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Chamilo\CoreBundle\ServiceHelper; |
||||
|
||||
use Chamilo\CoreBundle\Entity\ExtraField; |
||||
use Chamilo\CoreBundle\Entity\ExtraFieldValues; |
||||
use Chamilo\CoreBundle\Entity\User; |
||||
use Chamilo\CoreBundle\Repository\ExtraFieldRepository; |
||||
use Chamilo\CoreBundle\Repository\ExtraFieldValuesRepository; |
||||
use Chamilo\CoreBundle\Repository\Node\UserRepository; |
||||
use Doctrine\ORM\EntityManagerInterface; |
||||
use Doctrine\ORM\NonUniqueResultException; |
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; |
||||
|
||||
readonly class AzureAuthenticatorHelper |
||||
{ |
||||
public const EXTRA_FIELD_ORGANISATION_EMAIL = 'organisationemail'; |
||||
public const EXTRA_FIELD_AZURE_ID = 'azure_id'; |
||||
public const EXTRA_FIELD_AZURE_UID = 'azure_uid'; |
||||
|
||||
public function __construct( |
||||
private ExtraFieldValuesRepository $extraFieldValuesRepo, |
||||
private ExtraFieldRepository $extraFieldRepo, |
||||
private UserRepository $userRepository, |
||||
private EntityManagerInterface $entityManager, |
||||
private AccessUrlHelper $urlHelper, |
||||
) {} |
||||
|
||||
/** |
||||
* @throws NonUniqueResultException |
||||
*/ |
||||
public function registerUser(array $azureUserInfo, string $azureUidKey = 'objectId'): User |
||||
{ |
||||
if (empty($azureUserInfo)) { |
||||
throw new UnauthorizedHttpException('User info not found.'); |
||||
} |
||||
|
||||
[ |
||||
$firstNme, |
||||
$lastName, |
||||
$username, |
||||
$email, |
||||
$phone, |
||||
$authSource, |
||||
$active, |
||||
$extra, |
||||
] = $this->formatUserData($azureUserInfo, $azureUidKey); |
||||
|
||||
$userId = $this->getUserIdByVerificationOrder($azureUserInfo, $azureUidKey); |
||||
|
||||
if (empty($userId)) { |
||||
$user = (new User()) |
||||
->setCreatorId($this->userRepository->getRootUser()->getId()) |
||||
; |
||||
} else { |
||||
$user = $this->userRepository->find($userId); |
||||
} |
||||
|
||||
$user |
||||
->setFirstname($firstNme) |
||||
->setLastname($lastName) |
||||
->setEmail($email) |
||||
->setUsername($username) |
||||
->setPlainPassword('azure') |
||||
->setStatus(STUDENT) |
||||
->setAuthSource($authSource) |
||||
->setPhone($phone) |
||||
->setActive($active) |
||||
->setRoleFromStatus(STUDENT) |
||||
; |
||||
|
||||
$this->userRepository->updateUser($user); |
||||
|
||||
$url = $this->urlHelper->getCurrent(); |
||||
$url->addUser($user); |
||||
|
||||
$this->entityManager->flush(); |
||||
|
||||
$this->extraFieldValuesRepo->updateItemData( |
||||
$this->getOrganizationEmailField(), |
||||
$user, |
||||
$extra['extra_'.self::EXTRA_FIELD_ORGANISATION_EMAIL] |
||||
); |
||||
|
||||
$this->extraFieldValuesRepo->updateItemData( |
||||
$this->getAzureIdField(), |
||||
$user, |
||||
$extra['extra_'.self::EXTRA_FIELD_AZURE_ID] |
||||
); |
||||
|
||||
$this->extraFieldValuesRepo->updateItemData( |
||||
$this->getAzureUidField(), |
||||
$user, |
||||
$extra['extra_'.self::EXTRA_FIELD_AZURE_UID] |
||||
); |
||||
|
||||
return $user; |
||||
} |
||||
|
||||
private function getOrganizationEmailField() |
||||
{ |
||||
return $this->extraFieldRepo->findByVariable( |
||||
ExtraField::USER_FIELD_TYPE, |
||||
self::EXTRA_FIELD_ORGANISATION_EMAIL |
||||
); |
||||
} |
||||
|
||||
private function getAzureIdField() |
||||
{ |
||||
return $this->extraFieldRepo->findByVariable( |
||||
ExtraField::USER_FIELD_TYPE, |
||||
self::EXTRA_FIELD_AZURE_ID |
||||
); |
||||
} |
||||
|
||||
private function getAzureUidField() |
||||
{ |
||||
return $this->extraFieldRepo->findByVariable( |
||||
ExtraField::USER_FIELD_TYPE, |
||||
self::EXTRA_FIELD_AZURE_UID |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @throws NonUniqueResultException |
||||
*/ |
||||
public function getUserIdByVerificationOrder(array $azureUserData, string $azureUidKey = 'objectId'): ?int |
||||
{ |
||||
$selectedOrder = $this->getExistingUserVerificationOrder(); |
||||
|
||||
$organisationEmailField = $this->getOrganizationEmailField(); |
||||
$azureIdField = $this->getAzureIdField(); |
||||
$azureUidField = $this->getAzureUidField(); |
||||
|
||||
/** @var array<int, ExtraFieldValues> $positionsAndFields */ |
||||
$positionsAndFields = [ |
||||
1 => $this->extraFieldValuesRepo->findByVariableAndValue($organisationEmailField, $azureUserData['mail']), |
||||
2 => $this->extraFieldValuesRepo->findByVariableAndValue($azureIdField, $azureUserData['mailNickname']), |
||||
3 => $this->extraFieldValuesRepo->findByVariableAndValue($azureUidField, $azureUserData[$azureUidKey]), |
||||
]; |
||||
|
||||
foreach ($selectedOrder as $position) { |
||||
if (!empty($positionsAndFields[$position])) { |
||||
return $positionsAndFields[$position]->getItemId(); |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
public function getExistingUserVerificationOrder(): array |
||||
{ |
||||
return [1, 2, 3]; |
||||
} |
||||
|
||||
private function formatUserData( |
||||
array $azureUserData, |
||||
string $azureUidKey |
||||
): array { |
||||
$phone = null; |
||||
|
||||
if (isset($azureUserData['telephoneNumber'])) { |
||||
$phone = $azureUserData['telephoneNumber']; |
||||
} elseif (isset($azureUserData['businessPhones'][0])) { |
||||
$phone = $azureUserData['businessPhones'][0]; |
||||
} elseif (isset($azureUserData['mobilePhone'])) { |
||||
$phone = $azureUserData['mobilePhone']; |
||||
} |
||||
|
||||
// If the option is set to create users, create it |
||||
$firstNme = $azureUserData['givenName']; |
||||
$lastName = $azureUserData['surname']; |
||||
$email = $azureUserData['mail']; |
||||
$username = $azureUserData['userPrincipalName']; |
||||
$authSource = 'azure'; |
||||
$active = ($azureUserData['accountEnabled'] ? 1 : 0); |
||||
$extra = [ |
||||
'extra_'.self::EXTRA_FIELD_ORGANISATION_EMAIL => $azureUserData['mail'], |
||||
'extra_'.self::EXTRA_FIELD_AZURE_ID => $azureUserData['mailNickname'], |
||||
'extra_'.self::EXTRA_FIELD_AZURE_UID => $azureUserData[$azureUidKey], |
||||
]; |
||||
|
||||
return [ |
||||
$firstNme, |
||||
$lastName, |
||||
$username, |
||||
$email, |
||||
$phone, |
||||
$authSource, |
||||
$active, |
||||
$extra, |
||||
]; |
||||
} |
||||
} |
Loading…
Reference in new issue