<?php
/**
 * Copyright 2005-2023 de Solis Soluções Livres Ltda.
 *
 * Este arquivo é parte do programa Gnuteca.
 *
 * O Gnuteca é um software de propriedade da SOLIS, sendo desenvolvido
 * e mantido exclusivamente por esta empresa.
 *
 * A licença de uso está disponível mediante aquisição exclusiva junto à
 * SOLIS. A licença é concedida sem caráter de exclusividade ao licenciado.
 * Os direitos de uso são perpétuos.
 *
 * Embora os códigos fontes sejam fornecidos, o software é de propriedade
 * da SOLIS, não sendo permitido ao adquirente da licença a sua revenda,
 * empréstimo ou cessão (onerosa ou não) à terceiros. Também não é permitido,
 * a qualquer título e tempo, promover no software qualquer tipo de alienação,
 * reprodução, distribuição, divulgação, registro, licenciamento, transferência
 * ou qualquer outro ato que prejudique ou comprometa os direitos de propriedade
 * de software, o nome e a imagem da sua proprietária e do próprio software,
 * além de configurar concorrência à SOLIS.
 *
 * O licenciado, com o acesso ao código fonte do software, terá o direito de
 * promover mudanças no respectivo código. No entanto, nas situações em que ele
 * contar com o suporte oficial prestado pela SOLIS, não poderá promover mudanças
 * no código fonte, sob pena de perda do referido suporte.
 *
 * Para conhecer em detalhes o Termo de Licenciamento do Software Gnuteca
 * leia o arquivo “LICENCA.txt” disponível junto ao código deste software.
 * 
 * 
 * Class GAzure.
 *
 *
 *
 *
 *
 **/

$MIOLO = MIOLO::getInstance();
$MIOLO->getClass('gnuteca3', 'azure/Oauth');

class GAzure
{
    private $Token;
    private $userData;
    private $userName;
    private $oAuthVerifier;
    private $oAuthChallenge;
    private $oAuthChallengeMethod;
    private $userRoles;
    private $isLoggedIn;
    private $oAuth;

    public function __construct()
    {
        $MIOLO = MIOLO::getInstance();

        define('_OAUTH_LOGOUT', 'https://login.microsoftonline.com/common/wsfederation?wa=wsignout1.0');
        define('_OAUTH_SCOPE', 'openid%20offline_access%20profile%20user.read%20directory.read.all');
        define('_OAUTH_METHOD', 'secret');
    }

    public function obterURLConfig()
    {
        $MIOLO = MIOLO::getInstance();

        self::log("Gereação de URL de autenticação");

        // Generate the code verifier and challenge
        $this->oAuthChallenge();
        $_SESSION['oAuthVerifier'] = $this->oAuthVerifier;

        // Generate a session key and store in cookie, then populate database
        $sessionKey = $this->uuid();
        $_SESSION['sessionkey'] = $sessionKey;

        $urlRedirect = html_entity_decode($MIOLO->getActionURL('gnuteca3', 'azureConfig'));

        // Redirect to Azure AD login page
        $oAuthURL = 'https://login.microsoftonline.com/' . AZURE_ID_DIRETORIO . '/oauth2/v2.0/' . 'authorize?response_type=code&client_id=' . AZURE_ID_APLICATIVO . '&redirect_uri=' . urlencode($urlRedirect . '') . '&scope=' . _OAUTH_SCOPE . '&code_challenge=' . $this->oAuthChallenge . '&code_challenge_method=' . $this->oAuthChallengeMethod;
        
        return $oAuthURL;
    }

    public function obterURL()
    {
        $MIOLO = MIOLO::getInstance();

        // Generate the code verifier and challenge
        $this->oAuthChallenge();

        $_SESSION['oAuthVerifier'] = $this->oAuthVerifier;

        // Generate a session key and store in cookie, then populate database
        $sessionKey = $this->uuid();
        $_SESSION['sessionkey'] = $sessionKey;

        $urlRedirect = html_entity_decode($MIOLO->getActionURL('gnuteca3', 'azure'));

        // Redirect to Azure AD login page
        $oAuthURL = 'https://login.microsoftonline.com/' . AZURE_ID_DIRETORIO . '/oauth2/v2.0/' . 'authorize?response_type=code&client_id=' . AZURE_ID_APLICATIVO . '&redirect_uri=' . urlencode($urlRedirect) . '&scope=' . _OAUTH_SCOPE . '&code_challenge=' . $this->oAuthChallenge . '&code_challenge_method=' . $this->oAuthChallengeMethod;
        
        return $oAuthURL;
    }

    public function getDataAndTokens($code, $urlRedirect)
    {
        $codeVerifier = $_SESSION['oAuthVerifier'];

        self::log("Gereação de tokens");
        $MIOLO = MIOLO::getInstance();

        $oAuth = new Oauth();

        $oauthRequest = $oAuth->generateRequest('grant_type=authorization_code&client_id=' . AZURE_ID_APLICATIVO . '&redirect_uri=' . urlencode($urlRedirect . '') . '&code=' . $code . '&code_verifier=' . $codeVerifier);
        $response = $oAuth->postRequest('token', $oauthRequest);

        $reply = json_decode($response);

        $this->Token = $reply->access_token;
        $data = base64_decode(explode('.', $reply->id_token)[1]);

        $tokens = $reply;
        $tokens->data = json_decode($data);

        return $tokens;
    }

    public function refreshTokens()
    {
        self::log("Renovação de tokens");
        $oAuth = new Oauth();

        $oauthRequest = $oAuth->generateRequest('grant_type=refresh_token&refresh_token=' . AZURE_REFRESH_TOKEN . '&client_id=' . AZURE_ID_APLICATIVO . '&scope=' . _OAUTH_SCOPE);
        $response = $oAuth->postRequest('token', $oauthRequest);
        $reply = json_decode($response);

        // Define diretamente o valor do parâmetro para a importação ocorrer corretamente
        self::saveBearerToken($reply->access_token);
        self::saveRefreshToken($reply->refresh_token);
    }

    private function checkUserRole($role) 
    {
	    // Check that the requested role has been assigned to the user
	    if (in_array($role, $this->userRoles)) 
        {
	        return 1;
	    }
	    
        return;
    }

    private function uuid() 
    {
        //uuid function is not my code, but unsure who the original author is. KN
        //uuid version 4
        return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            // 32 bits for "time_low"
            mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
            // 16 bits for "time_mid"
            mt_rand( 0, 0xffff ),
            // 16 bits for "time_hi_and_version",
            // four most significant bits holds version number 4
            mt_rand( 0, 0x0fff ) | 0x4000,
            // 16 bits, 8 bits for "clk_seq_hi_res",
            // 8 bits for "clk_seq_low",
            // two most significant bits holds zero and one for variant DCE1.1
            mt_rand( 0, 0x3fff ) | 0x8000,
            // 48 bits for "node"
            mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
        );
    }

    private function oAuthChallenge() 
    {
        // Function to generate code verifier and code challenge for oAuth login. See RFC7636 for details. 
        $verifier = $this->oAuthVerifier;

        if (!$this->oAuthVerifier) 
        {
            $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
            $charLen = strlen($chars) - 1;
            $verifier = '';
            for ($i = 0; $i < 128; $i++) 
            {
                $verifier .= $chars[mt_rand(0, $charLen)];
            }

            $this->oAuthVerifier = $verifier;
        }

        // Challenge = Base64 Url Encode ( SHA256 ( Verifier ) )
        // Pack (H) to convert 64 char hash into 32 byte hex
        // As there is no B64UrlEncode we use strtr to swap +/ for -_ and then strip off the =
        $this->oAuthChallenge = str_replace('=', '', strtr(base64_encode(pack('H*', hash('sha256', $verifier))), '+/', '-_'));
        $this->oAuthChallengeMethod = 'S256';
    }

    public function getToken()
    {
        return $this->Token;
    }

    public static function saveIDAplicativo($value)
    {
        $MIOLO = MIOLO::getInstance();

        $busPreference = $MIOLO->getBusiness('gnuteca3', 'BusPreference');
        $busPreference->getPreference('GNUTECA3', 'AZURE_ID_APLICATIVO', true);
        $busPreference->configValue = $value;
        $busPreference->updatePreference();
        self::log("Parâmetro AZURE_ID_APLICATIVO atualizado com valor: " . $value);
    }

    public static function saveIDDiretorio($value)
    {
        $MIOLO = MIOLO::getInstance();

        $busPreference = $MIOLO->getBusiness('gnuteca3', 'BusPreference');
        $busPreference->getPreference('GNUTECA3', 'AZURE_ID_DIRETORIO', true);
        $busPreference->configValue = $value;
        $busPreference->updatePreference();
        self::log("Parâmetro AZURE_ID_DIRETORIO atualizado com valor: " . $value);
    }

    public static function saveSecret($value)
    {
        $MIOLO = MIOLO::getInstance();

        $busPreference = $MIOLO->getBusiness('gnuteca3', 'BusPreference');
        $busPreference->getPreference('GNUTECA3', 'AZURE_SECRET', true);
        $busPreference->configValue = $value;
        $busPreference->updatePreference();
        self::log("Parâmetro AZURE_SECRET atualizado com valor: " . $value);
    }

    public static function saveBearerToken($value)
    {
        $MIOLO = MIOLO::getInstance();

        $busPreference = $MIOLO->getBusiness('gnuteca3', 'BusPreference');
        $busPreference->getPreference('GNUTECA3', 'AZURE_BEARER_TOKEN', true);
        $busPreference->configValue = $value;
        $busPreference->updatePreference();
        define('AZURE_BEARER_TOKEN', $value);
        self::log("Parâmetro AZURE_BEARER_TOKEN atualizado com valor: " . $value);
    }

    public static function saveRefreshToken($value)
    {
        $MIOLO = MIOLO::getInstance();

        $busPreference = $MIOLO->getBusiness('gnuteca3', 'BusPreference');
        $busPreference->getPreference('GNUTECA3', 'AZURE_REFRESH_TOKEN', true);
        $busPreference->configValue = $value;
        $busPreference->updatePreference();
        define('AZURE_REFRESH_TOKEN', $value);
        self::log("Parâmetro AZURE_REFRESH_TOKEN atualizado com valor: " . $value);
    }

    public static function log($value)
    {
        $MIOLO = MIOLO::getInstance();

        $path =$MIOLO->getAbsolutePath() . '/var/log/gnuteca3-azure.log';
        file_put_contents($path, date('d/m/Y H:i:s'). ' - ' . $value . "\n", FILE_APPEND);
    }
}

?>