<?php
namespace Elements\Bundle\AzureAuthBundle\Controller;
use Elements\Bundle\AzureAuthBundle\DependencyInjection\ElementsAzureAuthExtension;
use Elements\Bundle\AzureAuthBundle\Service\RestrictionService;
use Pimcore\Model\User;
use Pimcore\File;
use Pimcore\Model\WebsiteSetting;
use Pimcore\Tool;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Exception;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use TheNetworg\OAuth2\Client\Provider\Azure as AzureProvider;
use TheNetworg\OAuth2\Client\Token\AccessToken as AzureAccessToken;
class AuthController extends AbstractController implements LoggerAwareInterface
{
use LoggerAwareTrait;
public function endpointAction(Request $request, RestrictionService $restrictionService) {
if (!$restrictionService->checkIpAddresses($request->getClientIp())) {
throw new AccessDeniedException();
}
$session = $request->getSession();
/** @var SessionBagInterface $sessionBag */
$sessionBag = $session->getBag('azure_auth');
$provider = new AzureProvider([
'clientId' => $this->getParameter('elements.azure_auth.clientId'),
'clientSecret' => $this->getParameter('elements.azure_auth.clientSecret'),
'redirectUri' => $this->getRedirectUrl($request),
'tenant' => $this->getParameter('elements.azure_auth.tenant'),
'defaultEndPointVersion' => '2.0'
]);
// Set to use v2 API, skip the line or set the value to Azure::ENDPOINT_VERSION_1_0 if willing to use v1 API
$provider->defaultEndPointVersion = \TheNetworg\OAuth2\Client\Provider\Azure::ENDPOINT_VERSION_2_0;
$baseGraphUri = $provider->getRootMicrosoftGraphUri(null);
$provider->scope = 'openid ' . $baseGraphUri . '/User.Read';
if (isset($_GET['code']) && $sessionBag->has('OAuth2.state') && isset($_GET['state'])) {
if ($_GET['state'] == $sessionBag->get('OAuth2.state')) {
$sessionBag->remove('OAuth2.state');
// Try to get an access token (using the authorization code grant)
/** @var AzureAccessToken $token */
$token = $provider->getAccessToken('authorization_code', [
'scope' => $provider->scope,
'code' => $_GET['code'],
'response_type' => 'code id_token',
]);
try {
$data = $this->consolidateData($provider, $token);
$email = $data['mail'];
$isDomainPermitted = false;
if (isset($email)) {
foreach ($this->getParameter(ElementsAzureAuthExtension::PERMITTED_DOMAINS) as $domain) {
if (str_ends_with($email, $domain)) {
$isDomainPermitted = true;
break;
}
}
}
if(isset($email) && $isDomainPermitted) {
$sessionBag->set('azureUser', $data);
$sessionBag->set('azureAccessToken', $token);
if ($this->getParameter(ElementsAzureAuthExtension::CHECK_USER_LOGIN_PERMISSION) == true) {
if (WebsiteSetting::getByName('AzureLoginPreselect') instanceof WebsiteSetting){
$allowArray = explode(',', (WebsiteSetting::getByName('AzureLoginPreselect')->getData() ?? ''));
if (!in_array($data['userPrincipalName'], $allowArray)) {
$sessionBag->clear();
return $this->redirect("/admin");
}
}
}
$user = $this->loadUser($data,$provider, $token);
if($user instanceof User && $user->getId() && $user->isActive()) {
Tool\Session::useSession(function (AttributeBagInterface $adminSession) use ($user) {
$adminSession->set('user', $user);
Tool\Session::regenerateId();
});
return $this->redirect("/admin");
} else {
$message = sprintf("User with email %s doesn't exist ... Sorry!", $email);
$this->logger->error($message);
throw new \Exception($message);
}
} else {
$message = sprintf("%s it seems that your email domain is currently not permitted ... Sorry!", $email);
$this->logger->error($message);
throw new \Exception($message);
}
} catch (Exception $e) {
// Failed to get user details
//exit('Oh dear...');
$this->logger->error($e->getMessage());
throw $e;
}
} else {
$message = 'Invalid state';
$this->logger->error($message);
echo $message;
exit;
}
} else {
$authorizationUrl = $provider->getAuthorizationUrl(['scope' => $provider->scope]);
$sessionBag->set('OAuth2.state', $provider->getState());
return $this->redirect($authorizationUrl);
}
}
/**
* @param User $user
* @param AzureProvider $provider
* @param AzureAccessToken $token
* @return void
*/
private function updateUserImage(User $user, AzureProvider $provider, AzureAccessToken $token)
{
try{
$photo = $provider->get($provider->getRootMicrosoftGraphUri($token) . '/v1.0/me/photo/$value', $token);
if($photo){
$tmpFile = PIMCORE_SYSTEM_TEMP_DIRECTORY . '/azure-image-download-' . $user->getId() . ".jpg";
File::put($tmpFile, $photo);
$user->setImage($tmpFile);
$user->save();
unlink($tmpFile);
}
} catch (\Exception $e){
$this->logger->error($e->getMessage());
}
}
/**
* @param AzureProvider $provider
* @param AzureAccessToken $token
* @return array|mixed|string
*/
private function consolidateData(AzureProvider $provider, AzureAccessToken $token)
{
$data = $provider->get($provider->getRootMicrosoftGraphUri($token) . '/v1.0/me', $token);
$additionalData = $provider->get($provider->getRootMicrosoftGraphUri($token) . '/v1.0/users/' . $data['id'] . '?$select=onPremisesSamAccountName', $token);
$data['userPrincipalName'] = strtolower($data['userPrincipalName']);
$data['mail'] = strtolower($data['mail']);
$data['onPremisesSamAccountName'] = $additionalData['onPremisesSamAccountName'];
return $data;
}
/**
* @param array $data
* @return User
* @throws Exception
*/
protected function loadUser(array $data, AzureProvider $provider, AzureAccessToken $token)
{
$email = $data['mail'] ?? $data['userPrincipalName'];
/** @var User $user */
$user = User::getByName($email);
// rewrite user to email address
if (!$user) {
$user = $this->rewriteEmail($data);
}
// check if user exists, if not create one
if (!$user) {
$user = $this->createUser($data, $provider, $token);
}
// temp.
if (!$user->isAdmin() && $this->getParameter(ElementsAzureAuthExtension::CREATE_USER_AS_ADMIN) == true) {
$user->setAdmin(true);
$user->save();
}
if (!$user->isActive()) {
$user->setActive(true);
}
if(method_exists($user, 'setLastLogin')) {
$user->setLastLogin(time());
}
$user->save();
return $user;
}
/**
* @param array $data
* @param AzureProvider $provider
* @param AzureAccessToken $token
* @return User
* @throws Exception
*/
protected function createUser(array $data, AzureProvider $provider, AzureAccessToken $token)
{
$email = $data['mail'] ?? $data['userPrincipalName'];
if (str_contains($email, '@')) {
$domain = explode('@', $email)[1]; // elements.at or any other company domain
} else {
$domain = 'elements.at';
}
$folder = User\Folder::getByName($domain);
if (!$folder) {
$folder = new User\Folder();
$folder->setName($domain);
$folder->setParentId(0);
$folder->save();
}
$user = new User;
$user->setParentId($folder->getId());
$user->setName($email);
$user->setActive(true);
if($isAdmin = $this->getParameter(ElementsAzureAuthExtension::CREATE_USER_AS_ADMIN)) {
$user->setAdmin($isAdmin);
}
if($roleNames = $this->getParameter(ElementsAzureAuthExtension::CREATE_USER_WITH_ROLES)) {
$rolesIds = [];
foreach ($roleNames as $roleName) {
$role = User\Role::getByName($roleName);
if($role) {
$rolesIds[] = $role->getId();
}
}
$user->setRoles($rolesIds);
}
$user->setFirstname($data['givenName']);
$user->setLastname($data['surname']);
$user->setEmail($email);
$user->setPassword($data['id'] . microtime(true) . uniqid());
$user->save();
$this->updateUserImage($user, $provider, $token);
return $user;
}
/**
* @param array $data
* @return User|null
* @throws Exception
*/
private function rewriteEmail(array $data)
{
if (!isset($data['onPremisesSamAccountName'])) {
return null;
}
// fetch user with old name
$user = User::getByName($data['onPremisesSamAccountName']);
if($user){
// rewrite
$email = $data['mail'] ?? $data['userPrincipalName'];
$user->setName($email);
$user->save();
}
return $user;
}
private function getRedirectUrl(Request $request)
{
$redirectUri = 'https://' . $request->getHttpHost() . $request->getPathInfo();
if(strpos($request->headers->get('x-original-host'), '.expose.eledevs.com') == true || strpos($request->headers->get('x-original-host'), '.expose.vdevs.at') == true) {
$redirectUri = str_replace(':8080', '', 'https://' . $request->headers->get('x-original-host')) . $request->getPathInfo();
}
return $redirectUri;
}
}