<?php
// src/Security/UserLoader.php
namespace App\Security;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\AppUtils\AppGoogleClient;
use Symfony\Component\HttpFoundation\RequestStack;
use App\Entity\Customer;
use App\Entity\CustomerOtherDomain; //domini alias o sottodomini google Workspace
class UserLoader implements UserProviderInterface, PasswordUpgraderInterface
{
// proprietà aggiuntive
private $customerID="";
private $em;
private $session;
//costruttore per iniettare strumenti utili negli altri metodi
public function __construct(EntityManagerInterface $em, RequestStack $requestStack){
$this->session = $requestStack->getSession(); //recupera la sessione e ne salva il riferimento nell'autenticatore
$this->em = $em; //recupera l'entity manager doctrine
}
/**
* The loadUserByIdentifier() method was introduced in Symfony 5.3.
* In previous versions it was called loadUserByUsername()
*
* Symfony calls this method if you use features like switch_user
* or remember_me. If you're not using these features, you do not
* need to implement this method.
*
* @throws UserNotFoundException if the user is not found
*/
public function loadUserByIdentifier(string $identifier): UserInterface
{
//recupera la sessione per poter salvare i dati utili
if (filter_var($identifier, FILTER_VALIDATE_EMAIL)) {
$email = $identifier;
$emailesplosa=explode('@',$email); // esplode l'indirizzo mail in due stringhe prima e dopo la chiocciola @
$username=$emailesplosa[0]; //estrae lo username dalla mail
$dominio = $emailesplosa[1]; //estrae il dominio dalla mail
} else {
throw new \Exception("Identitificativo {$identifier} non riconosciuto come email valida");
}
//### PORZIONE PER SUPERADMIN SCAN2GO, azionata soltanto se in sessione c'è l'apposito trigger
if ( isset($_ENV['SUPERADMINS']) && is_string($_ENV['SUPERADMINS']) && $_ENV['SUPERADMINS'] != "" && $this->session->get('SuperAdminAuth') ){
$superadminsArray = json_decode($_ENV['SUPERADMINS']); //dopo aver controllato che esista, prelevo l'array dei superadmin dall'environment
if ($superadminsArray != false && is_array($superadminsArray)){
//in questo caso debbo verificare che l'utente sia superadmin
if ( in_array($email,$superadminsArray) ){
//in questo caso ho verificato che è un superadmin, quindi creo e restituisco l'oggetto utente
//creo l'utente
$utente = new User;
$utente->setEmail($email);
$ruoliSA=['ROLE_SUPERADMIN'];
$utente->setRoles($ruoliSA);
//lo salvo in sessione e poi lo ritorno
$this->session->set('email',$email);
$this->session->set('roles',$ruoliSA);
//salvo altri dati utili in sessione
//nome utente (troncato a 30 caratteri) e nome utente completo
$un = explode('@', $email)[0]; //separo il nome utente dall'indirizzo email
if(strlen($un)>30){ //nel caso in cui l'utente sia più lungo di 30 caratteri, taglio in modo da non infastidire il layout interfaccia
$unc = $un;
$un = substr($un,0,30).'...'; //sovrascrivo il nomeutente con i primi 30 caratteri dell'originale e aggiungo tre puntini di sospensione
}
else {$unc = null;}
$this->session->set('nomeutente',$un); //salvo in sessione il nomeutente
$this->session->set('nomeutentecompleto',$unc); //salvo in sessione il nomeutentecompleto
//Chiudo il service e l'AGC inutile in linea di massima, tanto per precauzione
$service = null;
$AGC = null;
return $utente;
} else {} //prosegue i controlli normalmente
} else {} //prosegue i controlli normalmente
} else {
$this->session->remove('SuperAdminAuth'); //per sicurezza rimuovo dalla sessione il valore trigger
} //prosegue i controlli normalmente
//### FINE PORZIONE PER SUPERADMIN SCAN2GO
//PORZIONE DI CODICE PER DOMINI ALIAS / SOTTODOMINI ETC.
$customerOtherDomain = $this->session->get('customerOtherDomain'); //leggo dalla sessione l'eventuale presenza del parametro che segnala il fatto che l'utente usa un dominio alternativo
$customerOtherDomain = (int) $customerOtherDomain; //converto in intero
if ( $customerOtherDomain != null && $customerOtherDomain > 0 ){
$tempRecord=$this->em->getRepository(Customer::class)->find($customerOtherDomain);
if ($tempRecord != null){
//tutto ok, sovrascrivo $dominio con il gDomain corrispondente al tempRecord, di modo da evitare problemi
$dominio = $tempRecord->getGDomain();
}
else { //in teoria non ci troviamo MAI in questa situazione, dato che questi controlli sono già stati passati al passaggio precedente in PfSenseTokenAuthenticator
throw new UserNotFoundException("Customer Other Domain: {$dominio} non riconosciuto! Cid:{$customerOtherDomain}");
}
} else { //nel caso in cui non ci troviamo in corrispondenza di alias / sottodomini alternativi etc. eseguo il codice normale
//dentro questo else c'era la porzione di codice eseguita in precedenza
$tempRecord=$this->em->getRepository(Customer::class)->findOneBy([ //ricerca il record customer corrispondente al dominio passato
'gDomain' => $dominio
]);
}
//FINE PORZIONE DI CODICE PER DOMINI ALIAS / SOTTODOMINI ETC.
$codMec = $tempRecord->getCodMec();
$customerID = $tempRecord->getGCustomerId();
//Non occorre che faccia check particolari, in quanto l'app authenticator dello step precedente li ha già fatti
try {
$scopesArray=json_decode($_ENV['GAPI_SCOPES'],true); //permessi da richiedere sulle GAPI
$AGC = new AppGoogleClient($this->em,$codMec,$customerID,false,$scopesArray); // istanzia il client em doctrine, meccanografico,google customer id, batch mode, array di scopes gapi
} catch (\Exception $e){
$errore = $e->getMessage();
$this->session->set('googleAuthUrl',$errore);
$this->session->set('customer',$tempRecord);
throw new UserNotFoundException($errore);
}
try {
$service = new \Google_Service_Directory($AGC->getClient()); // recupera il service della Directory
//$this->session->set('scopesDebug',$_ENV['GAPI_SCOPES']);
} catch (\Exception $e){
$errore = $e->getMessage();
//$this->session->set('scopesDebug',$_ENV['GAPI_SCOPES']);
throw new UserNotFoundException($errore);
}
$utenti = $service->users; //recupera l'oggetto che lavora sugli utenti della directory
$membri = $service->members; //recupera l'oggetto che lavora sulle memberships a qualsiasi gruppo della gSuite in questione
$ruoliutente=array(); //array vuoto in cui fare il push dei ruoli
$contatoreOccorrenze=0;
//### Prelevo i gruppi gSuite sui quali fare le assegnazioni dei ruoli, direttamente dal database
$gruppoGsuiteOperatori=$tempRecord->getGruppoGsuiteOperatori();
$gruppoGsuiteAmministratori=$tempRecord->getGruppoGsuiteAmministratori();
$gruppoGsuiteIncaricati=$tempRecord->getGruppoGsuiteIncaricati();
$mappaturaGSuiteApp=array(
$gruppoGsuiteAmministratori => 'ROLE_ADMIN',
$gruppoGsuiteOperatori => 'ROLE_OPERATORE',
);
//### Per retrocompatibilità, procedo all'inserimento del controllo del ruolo incaricato, solamente quando esiste nei gruppi definiti all'interno del customer
if ($gruppoGsuiteIncaricati != null){
$mappaturaGSuiteApp[$gruppoGsuiteIncaricati] = 'ROLE_INCARICATO';
} else {
//### Implementazione gruppi di incaricati multipli (abbinamento per plesso)
//prelevo la configurazione extra del customer
$configCustomer = $tempRecord->getConfig();
if ($configCustomer != null && isset($configCustomer['gruppiIncaricatiEPlessi']) && $configCustomer['gruppiIncaricatiEPlessi'] != null && is_array($configCustomer['gruppiIncaricatiEPlessi'])){
//$configCustomer['gruppiIncaricatiEPlessi'] è un array in cui ciascun elemento è un abbinamento plesso -> gruppoIncaricati
$abbinamentiPlessoIncaricati = $configCustomer['gruppiIncaricatiEPlessi'];
//ciclo gli abbinamenti
$gruppiAbbinatiPrecedenti = [];
$plessiAbbinati = [];
foreach ($abbinamentiPlessoIncaricati as $plesso=>$gruppoInc){
//caso in cui lo stesso gruppo è abbinato a più plessi, risparmio una chiamata api e procedo già ad aggiungere il plesso tra quelli abbinati al delegato corrente
if ( in_array($gruppoInc,$gruppiAbbinatiPrecedenti) ){
array_push($plessiAbbinati,$plesso); //inserisco nei plessi abbinati quello iterato
$effettuaApiCall = false; //disabilito l'if successivo
} else {
$effettuaApiCall = true; //abilito l'if successivo
}
//nel caso in cui sia effettivamente membro del gruppo
if ($effettuaApiCall && $membri->hasMember($gruppoInc.'@'.$dominio,$email)->getIsMember()){
array_push($plessiAbbinati,$plesso); //inserisco nei plessi abbinati quello iterato
array_push($gruppiAbbinatiPrecedenti,$gruppoInc); //inserisco il gruppo corrente in quelli già abbinati al delegato corrente
} else {}
}
if (count($gruppiAbbinatiPrecedenti)>0){
//se ho effettuato almeno un abbinamento attribuisco il ruolo di incaricato
array_push($ruoliutente,'ROLE_INCARICATO');
$this->session->set('plessiAbbinati',$plessiAbbinati); //salvo in sessione l'abbinamento plesso
} else {}
} else {} //nel caso in cui non sia implementata nel customer, non faccio più niente
}
//Check esistenza in GSuite , verifica prima se l'utente è un admin di gsuite nel caso gli da ROLE_ADMIN
$utente=$utenti->get($email);
if( $utente!=null ){
//PORZIONE COMMENTATA e sostituita di riga sottostante PER EVITARE CHE IL GSADMIN RICEVA RUOLO ADMIN
/*if($utente->getIsAdmin()){ //nel caso in cui l'utente selezionato sia admin gsuite
array_push($ruoliutente,'ROLE_ADMIN'); //gli do il ruolo di admin gsuite
}
else {
array_push($ruoliutente,'ROLE_USER'); //ruolo utente viene attribuito a tutti
}*/
array_push($ruoliutente,'ROLE_USER'); //ruolo utente viene attribuito a tutti
}
else {
//Se l'utente è null
throw new \Exception("Nessun Utente Rintracciato con email = {$identifier}");
}
$primoCheck=true; //il flag del primo check mi serve per bypassare gli altri check nel caso in cui l'utente venga riconosciuto come amministratore (eredita automaticamente tutti i ruoli inferiori nella gerarchia)
foreach($mappaturaGSuiteApp as $gruppo_gsuite=>$ruolo_attribuito){
if ($membri->hasMember($gruppo_gsuite.'@'.$dominio,$email)->getIsMember()){
array_push($ruoliutente,$ruolo_attribuito);
$contatoreOccorrenze++;
}
if(($contatoreOccorrenze>0) && $primoCheck){ //qualora l'utente sia stato riconosciuto come app_admin, interrompo perché eredita già il ruolo app_docente in automatico, per via della gerarchia
break;
}
$primoCheck=false; //impone a false il valore del primocheck, in vista dei prossimi giri di controllo
}
//se c'è stata almeno un'occorrenza nell'appartenenza ai gruppi, procedo alla creazione dell'utente
//if($contatoreOccorrenze>0){
//creo l'utente
$utente = new User;
$utente->setEmail($email);
$utente->setRoles($ruoliutente);
//lo salvo in sessione e poi lo ritorno
$this->session->set('email',$email);
$this->session->set('roles',$ruoliutente);
$this->session->set('dominio',$dominio);
$codMec=$tempRecord->getCodMec();
$this->session->set('codMec',$codMec);
$sqlIDC=$tempRecord->getId();
$this->session->set('sqlIDC',$sqlIDC);
$customerID=$tempRecord->getGCustomerId();
$this->session->set('gCID',$customerID);
//salvo altri dati utili in sessione
//nome utente (troncato a 30 caratteri) e nome utente completo
$un = explode('@', $email)[0]; //separo il nome utente dall'indirizzo email
if(strlen($un)>30){ //nel caso in cui l'utente sia più lungo di 30 caratteri, taglio in modo da non infastidire il layout interfaccia
$unc = $un;
$un = substr($un,0,30).'...'; //sovrascrivo il nomeutente con i primi 30 caratteri dell'originale e aggiungo tre puntini di sospensione
}
else {$unc = null;}
$this->session->set('nomeutente',$un); //salvo in sessione il nomeutente
$this->session->set('nomeutentecompleto',$unc); //salvo in sessione il nomeutentecompleto
//Chiudo il service e l'AGC inutile in linea di massima, tanto per precauzione
$service = null;
$AGC = null;
return $utente;
}
public function loadUserByUsername($username): UserInterface
{
throw new \Exception('TODO: fill in loadUserByUsername() inside '.__FILE__);
}
/**
* Refreshes the user after being reloaded from the session.
*
* When a user is logged in, at the beginning of each request, the
* User object is loaded from the session and then this method is
* called. Your job is to make sure the user's data is still fresh by,
* for example, re-querying for fresh User data.
*
* If your firewall is "stateless: true" (for a pure API), this
* method is not called.
*
* @return UserInterface
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
}
if( ($this->session->get('userRefresh') !== null) && ($this->session->get('userRefresh') == true) ){
//INSERIRE IL REFRESH DELL'UTENTE, idealmente un richiamo al loadUserByIdentifier
}
else {
return $user;
}
// Return a User object after making sure its data is "fresh".
// Or throw a UserNotFoundException if the user no longer exists.
throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
}
/**
* Tells Symfony to use this provider for this User class.
*/
public function supportsClass(string $class): bool
{
return User::class === $class || is_subclass_of($class, User::class);
}
/**
* Upgrades the hashed password of a user, typically for using a better hash algorithm.
*/
public function upgradePassword(UserInterface $user, string $newHashedPassword): void
{
// TODO: when hashed passwords are in use, this method should:
// 1. persist the new password in the user storage
// 2. update the $user object with $user->setPassword($newHashedPassword);
}
}