<?php

/**
 * Copyright Maarch since 2008 under licence GPLv3.
 * See LICENCE.txt file at the root folder for more details.
 * This file is part of Maarch software.
 */

/**
 * @brief Authentication Controller
 *
 * @author dev@maarch.org
 */

namespace SrcCore\controllers;

use Configuration\models\ConfigurationModel;
use Email\controllers\EmailController;
use Firebase\JWT\JWT;
use History\controllers\HistoryController;
use Parameter\models\ParameterModel;
use Respect\Validation\Validator;
use Slim\Http\Request;
use Slim\Http\Response;
use SrcCore\models\AuthenticationModel;
use SrcCore\models\CoreConfigModel;
use SrcCore\models\CurlModel;
use SrcCore\models\PasswordModel;
use SrcCore\models\ValidatorModel;
use Stevenmaguire\OAuth2\Client\Provider\Keycloak;
use User\models\UserModel;

class AuthenticationController
{
    const MAX_DURATION_TOKEN = 30; //Minutes
    const ROUTES_WITHOUT_AUTHENTICATION = [
        'GET/authenticationInformations', 'PUT/versionsUpdateSQL', 'GET/validUrl', 'GET/authenticate/token', 'GET/images', 'POST/password', 'PUT/password', 'GET/passwordRules',
        'GET/jnlp/{jnlpUniqueId}', 'GET/onlyOffice/mergedFile', 'POST/onlyOfficeCallback', 'POST/authenticate',
        'GET/wopi/files/{id}', 'GET/wopi/files/{id}/contents', 'POST/wopi/files/{id}/contents','GET/onlyOffice/content', 'GET/languages/{lang}', 'GET/dev/lang'
    ];

    public function getInformations(Request $request, Response $response)
    {
        $path = CoreConfigModel::getConfigPath();
        if (!file_exists($path)) {
            return $response->withStatus(403)->withJson(['errors' => 'No configuration file found']);
        }
        $hashedPath = md5($path);

        $appName   = CoreConfigModel::getApplicationName();
        $parameter = ParameterModel::getById(['id' => 'loginpage_message', 'select' => ['param_value_string']]);

        $encryptKey = CoreConfigModel::getEncryptKey();

        $loggingMethod = CoreConfigModel::getLoggingMethod();
        $authUri = null;
        if ($loggingMethod['id'] == 'cas') {
            $casConfiguration = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/cas_config.xml']);
            $hostname         = (string)$casConfiguration->WEB_CAS_URL;
            $port             = (string)$casConfiguration->WEB_CAS_PORT;
            $uri              = (string)$casConfiguration->WEB_CAS_CONTEXT;
            $authUri          = "https://{$hostname}:{$port}{$uri}/login?service=" . UrlController::getCoreUrl() . 'dist/index.html#/login';
        } elseif ($loggingMethod['id'] == 'keycloak') {
            $keycloakConfig = CoreConfigModel::getKeycloakConfiguration();
            $provider       = new Keycloak($keycloakConfig);
            $authUri        = $provider->getAuthorizationUrl(['scope' => $keycloakConfig['scope']]);
            $keycloakState  = $provider->getState();
        } elseif ($loggingMethod['id'] == 'sso') {
            $ssoConfiguration = ConfigurationModel::getByPrivilege(['privilege' => 'admin_sso', 'select' => ['value']]);
            $ssoConfiguration = !empty($ssoConfiguration['value']) ? json_decode($ssoConfiguration['value'], true) : null;
            $authUri          = $ssoConfiguration['url'] ?? null;
        } elseif ($loggingMethod['id'] == 'openam') {
            $configuration  = CoreConfigModel::getJsonLoaded(['path' => 'apps/maarch_entreprise/xml/openAM.json']);
            $authUri        = $configuration['connectionUrl'] ?? null;
        }

        $return = [
            'instanceId'        => $hashedPath,
            'applicationName'   => $appName,
            'loginMessage'      => $parameter['param_value_string'] ?? null,
            'changeKey'         => $encryptKey == 'Security Key Maarch Courrier #2008',
            'authMode'          => $loggingMethod['id'],
            'authUri'           => $authUri,
            'lang'              => CoreConfigModel::getLanguage()
        ];

        if (!empty($keycloakState)) {
            $return['keycloakState'] = $keycloakState;
        }

        return $response->withJson($return);
    }

    public function getValidUrl(Request $request, Response $response)
    {
        if (!is_file('custom/custom.json')) {
            return $response->withJson(['message' => 'No custom file', 'lang' => 'noConfiguration']);
        }

        $jsonFile = file_get_contents('custom/custom.json');
        $jsonFile = json_decode($jsonFile, true);
        if (count($jsonFile) == 0) {
            return $response->withJson(['message' => 'No custom', 'lang' => 'noConfiguration']);
        } elseif (count($jsonFile) > 1) {
            return $response->withJson(['message' => 'There is more than 1 custom', 'lang' => 'moreOneCustom']);
        }

        $url = null;
        if (!empty($jsonFile[0]['path'])) {
            $coreUrl = UrlController::getCoreUrl();
            $url = $coreUrl . $jsonFile[0]['path'] . "/dist/index.html";
        } elseif (!empty($jsonFile[0]['uri'])) {
            $url = $jsonFile[0]['uri'] . "/dist/index.html";
        }

        return $response->withJson(['url' => $url]);
    }

    public static function authentication($authorizationHeaders = [])
    {
        $userId = null;

        $canBasicAuth = true;
        $loginMethod = CoreConfigModel::getLoggingMethod();
        if ($loginMethod['id'] != 'standard' && !empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
            $rawUser = UserModel::getByLogin(['select' => ['mode'], 'login' => $_SERVER['PHP_AUTH_USER']]);
            if (!empty($rawUser) && $rawUser['mode'] != 'rest') {
                $canBasicAuth = false;
            }
        }

        if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW']) && $canBasicAuth) {
            if (AuthenticationModel::authentication(['login' => $_SERVER['PHP_AUTH_USER'], 'password' => $_SERVER['PHP_AUTH_PW']])) {
                $user = UserModel::getByLogin(['select' => ['id', 'mode'], 'login' => $_SERVER['PHP_AUTH_USER']]);
                $userId = $user['id'];
            }
        } else {
            if (!empty($authorizationHeaders)) {
                $token = null;
                foreach ($authorizationHeaders as $authorizationHeader) {
                    if (strpos($authorizationHeader, 'Bearer') === 0) {
                        $token = str_replace('Bearer ', '', $authorizationHeader);
                    }
                }
                if (!empty($token)) {
                    try {
                        $jwt = (array)JWT::decode($token, CoreConfigModel::getEncryptKey(), ['HS256']);
                    } catch (\Exception $e) {
                        return null;
                    }
                    $jwt['user'] = (array)$jwt['user'];
                    if (!empty($jwt) && !empty($jwt['user']['id'])) {
                        $userId = $jwt['user']['id'];
                    }
                }
            }
        }

        if (!empty($userId)) {
            UserModel::update([
                'set'   => ['reset_token' => null],
                'where' => ['id = ?'],
                'data'  => [$userId]
            ]);
        }

        return $userId;
    }

    public static function isRouteAvailable(array $args)
    {
        ValidatorModel::notEmpty($args, ['userId', 'currentRoute', 'currentMethod']);
        ValidatorModel::intVal($args, ['userId']);
        ValidatorModel::stringType($args, ['currentRoute', 'currentMethod']);

        $user = UserModel::getById(['select' => ['status', 'password_modification_date', 'mode', 'authorized_api'], 'id' => $args['userId']]);

        if ($user['mode'] == 'rest') {
            $authorizedApi = json_decode($user['authorized_api'], true);
            if (!empty($authorizedApi) && !in_array($args['currentMethod'].$args['currentRoute'], $authorizedApi)) {
                return ['isRouteAvailable' => false, 'errors' => 'This route is not authorized for this user'];
            }
            return ['isRouteAvailable' => true];
        } elseif ($user['status'] == 'ABS' && !in_array($args['currentRoute'], ['/users/{id}/status', '/currentUser/profile', '/header', '/passwordRules', '/users/{id}/password'])) {
            return ['isRouteAvailable' => false, 'errors' => 'User is ABS and must be activated'];
        }

        if (!in_array($args['currentRoute'], ['/passwordRules', '/users/{id}/password'])) {
            $loggingMethod = CoreConfigModel::getLoggingMethod();

            if (!in_array($loggingMethod['id'], ['sso', 'cas', 'ldap', 'keycloak', 'shibboleth'])) {
                $passwordRules = PasswordModel::getEnabledRules();
                if (!empty($passwordRules['renewal'])) {
                    $currentDate = new \DateTime();
                    $lastModificationDate = new \DateTime($user['password_modification_date']);
                    $lastModificationDate->add(new \DateInterval("P{$passwordRules['renewal']}D"));

                    if ($currentDate > $lastModificationDate) {
                        return ['isRouteAvailable' => false, 'errors' => 'User must change his password'];
                    }
                }
            }
        }

        return ['isRouteAvailable' => true];
    }

    public static function handleFailedAuthentication(array $args)
    {
        ValidatorModel::notEmpty($args, ['userId']);
        ValidatorModel::intVal($args, ['userId']);

        $passwordRules = PasswordModel::getEnabledRules();

        if (!empty($passwordRules['lockAttempts'])) {
            $user = UserModel::getById(['select' => ['failed_authentication', 'locked_until'], 'id' => $args['userId']]);
            $set = [];
            if (!empty($user['locked_until'])) {
                $currentDate = new \DateTime();
                $lockedUntil = new \DateTime($user['locked_until']);
                if ($lockedUntil < $currentDate) {
                    $set['locked_until'] = null;
                    $user['failed_authentication'] = 0;
                } else {
                    return ['accountLocked' => true, 'lockedDate' => $user['locked_until']];
                }
            }

            $set['failed_authentication'] = $user['failed_authentication'] + 1;
            UserModel::update([
                'set'       => $set,
                'where'     => ['id = ?'],
                'data'      => [$args['userId']]
            ]);

            if (!empty($user['failed_authentication']) && ($user['failed_authentication'] + 1) >= $passwordRules['lockAttempts'] && !empty($passwordRules['lockTime'])) {
                $lockedUntil = time() + 60 * $passwordRules['lockTime'];
                UserModel::update([
                    'set'       => ['locked_until'  => date('Y-m-d H:i:s', $lockedUntil)],
                    'where'     => ['id = ?'],
                    'data'      => [$args['userId']]
                ]);
                return ['accountLocked' => true, 'lockedDate' => date('Y-m-d H:i:s', $lockedUntil)];
            }
        }

        return true;
    }

    public function authenticate(Request $request, Response $response)
    {
        $body = $request->getParsedBody();

        $loggingMethod = CoreConfigModel::getLoggingMethod();
        if (in_array($loggingMethod['id'], ['standard', 'ldap'])) {
            if (!Validator::stringType()->notEmpty()->validate($body['login']) || !Validator::stringType()->notEmpty()->validate($body['password'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Bad Request']);
            }
        }

        if ($loggingMethod['id'] == 'standard') {
            $login = strtolower($body['login']);
            if (!AuthenticationController::isUserAuthorized(['login' => $login])) {
                return $response->withStatus(403)->withJson(['errors' => 'Authentication Failed']);
            }
            $authenticated = AuthenticationController::standardConnection(['login' => $login, 'password' => $body['password']]);
            if (!empty($authenticated['date'])) {
                return $response->withStatus(401)->withJson(['errors' => $authenticated['errors'], 'date' => $authenticated['date']]);
            } elseif (!empty($authenticated['errors'])) {
                return $response->withStatus(401)->withJson(['errors' => $authenticated['errors']]);
            }
        } elseif ($loggingMethod['id'] == 'ldap') {
            $login = $body['login'];
            if (!AuthenticationController::isUserAuthorized(['login' => $login])) {
                return $response->withStatus(403)->withJson(['errors' => 'Authentication Failed']);
            }
            $authenticated = AuthenticationController::ldapConnection(['login' => $login, 'password' => $body['password']]);
            if (!empty($authenticated['errors'])) {
                return $response->withStatus(401)->withJson(['errors' => $authenticated['errors']]);
            }
        } elseif ($loggingMethod['id'] == 'cas') {
            $authenticated = AuthenticationController::casConnection();
            if (!empty($authenticated['errors'])) {
                return $response->withStatus(401)->withJson(['errors' => $authenticated['errors']]);
            }
            $login = strtolower($authenticated['login']);
            if (!AuthenticationController::isUserAuthorized(['login' => $login])) {
                return $response->withStatus(403)->withJson(['errors' => 'Authentication Failed']);
            }
        } elseif ($loggingMethod['id'] == 'keycloak') {
            $queryParams = $request->getQueryParams();
            $authenticated = AuthenticationController::keycloakConnection(['code' => $queryParams['code']]);
            if (!empty($authenticated['errors'])) {
                return $response->withStatus(401)->withJson(['errors' => $authenticated['errors']]);
            }
            $login = $authenticated['login'];
            if (!AuthenticationController::isUserAuthorized(['login' => $login])) {
                return $response->withStatus(403)->withJson(['errors' => 'Authentication unauthorized']);
            }
        } elseif ($loggingMethod['id'] == 'sso') {
            $authenticated = AuthenticationController::ssoConnection();
            if (!empty($authenticated['errors'])) {
                return $response->withStatus(401)->withJson(['errors' => $authenticated['errors']]);
            }
            $login = strtolower($authenticated['login']);
            if (!AuthenticationController::isUserAuthorized(['login' => $login])) {
                return $response->withStatus(403)->withJson(['errors' => 'Authentication unauthorized']);
            }
        } elseif ($loggingMethod['id'] == 'openam') {
            $authenticated = AuthenticationController::openAMConnection();
            if (!empty($authenticated['errors'])) {
                return $response->withStatus(401)->withJson(['errors' => $authenticated['errors']]);
            }
            $login = strtolower($authenticated['login']);
            if (!AuthenticationController::isUserAuthorized(['login' => $login])) {
                return $response->withStatus(403)->withJson(['errors' => 'Authentication unauthorized']);
            }
        } else {
            return $response->withStatus(403)->withJson(['errors' => 'Logging method unauthorized']);
        }

        $user = UserModel::getByLowerLogin(['login' => $login, 'select' => ['id', 'refresh_token', 'user_id']]);

        $GLOBALS['id'] = $user['id'];
        $GLOBALS['login'] = $user['user_id'];

        $user['refresh_token'] = json_decode($user['refresh_token'], true);
        foreach ($user['refresh_token'] as $key => $refreshToken) {
            try {
                JWT::decode($refreshToken, CoreConfigModel::getEncryptKey(), ['HS256']);
            } catch (\Exception $e) {
                unset($user['refresh_token'][$key]);
            }
        }
        $user['refresh_token'] = array_values($user['refresh_token']);
        if (count($user['refresh_token']) > 10) {
            array_shift($user['refresh_token']);
        }

        $refreshToken = AuthenticationController::getRefreshJWT();
        $user['refresh_token'][] = $refreshToken;
        UserModel::update([
            'set'   => ['reset_token' => null, 'refresh_token' => json_encode($user['refresh_token']), 'failed_authentication' => 0, 'locked_until' => null],
            'where' => ['id = ?'],
            'data'  => [$user['id']]
        ]);

        $response = $response->withHeader('Token', AuthenticationController::getJWT());
        $response = $response->withHeader('Refresh-Token', $refreshToken);

        HistoryController::add([
            'tableName' => 'users',
            'recordId'  => $user['id'],
            'eventType' => 'LOGIN',
            'info'      => _LOGIN . ' : ' . $login,
            'moduleId'  => 'authentication',
            'eventId'   => 'login'
        ]);

        return $response->withStatus(204);
    }

    public function logout(Request $request, Response $response)
    {
        $loggingMethod = CoreConfigModel::getLoggingMethod();

        $logoutUrl = null;
        if ($loggingMethod['id'] == 'cas') {
            $disconnection = AuthenticationController::casDisconnection();
            $logoutUrl = $disconnection['logoutUrl'];
        } elseif ($loggingMethod['id'] == 'keycloak') {
            $disconnection = AuthenticationController::keycloakDisconnection();
            $logoutUrl = $disconnection['logoutUrl'];
        }

        return $response->withJson(['logoutUrl' => $logoutUrl]);
    }

    private static function standardConnection(array $args)
    {
        $login = $args['login'];
        $password = $args['password'];

        $authenticated = AuthenticationModel::authentication(['login' => $login, 'password' => $password]);
        if (empty($authenticated)) {
            $user = UserModel::getByLowerLogin(['login' => $login, 'select' => ['id']]);
            $handle = AuthenticationController::handleFailedAuthentication(['userId' => $user['id']]);
            if (!empty($handle['accountLocked'])) {
                return ['errors' => 'Account Locked', 'date' => $handle['lockedDate']];
            }
            return ['errors' => 'Authentication Failed'];
        }

        return true;
    }

    private static function ldapConnection(array $args)
    {
        $login = $args['login'];
        $password = $args['password'];

        $ldapConfigurations = CoreConfigModel::getXmlLoaded(['path' => 'modules/ldap/xml/config.xml']);
        if (empty($ldapConfigurations) || empty($ldapConfigurations->config->ldap)) {
            return ['errors' => 'No ldap configurations'];
        }

        foreach ($ldapConfigurations->config->ldap as $ldapConfiguration) {
            $ssl = (string)$ldapConfiguration->ssl;
            $domain = (string)$ldapConfiguration->domain;
            $prefix = (string)$ldapConfiguration->prefix_login;
            $suffix = (string)$ldapConfiguration->suffix_login;
            $standardConnect = (string)$ldapConfiguration->standardConnect;

            $uri = ($ssl == 'true' ? "LDAPS://{$domain}" : $domain);

            $ldap = @ldap_connect($uri);
            if ($ldap === false) {
                $error = 'Ldap connect failed : uri is maybe wrong';
                continue;
            }
            ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
            ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
            ldap_set_option($ldap, LDAP_OPT_NETWORK_TIMEOUT, 10);
            $ldapLogin = (!empty($prefix) ? $prefix . '\\' . $login : $login);
            $ldapLogin = (!empty($suffix) ? $ldapLogin . $suffix : $ldapLogin);
            if (!empty((string)$ldapConfiguration->baseDN)) { //OpenLDAP
                $search = @ldap_search($ldap, (string)$ldapConfiguration->baseDN, "(uid={$ldapLogin})", ['dn']);
                if ($search === false) {
                    $error = 'Ldap search failed : baseDN is maybe wrong => ' . ldap_error($ldap);
                    continue;
                }
                $entries = ldap_get_entries($ldap, $search);
                $ldapLogin = $entries[0]['dn'];
            }
            $authenticated = @ldap_bind($ldap, $ldapLogin, $password);
            if ($authenticated) {
                break;
            }
            $error = ldap_error($ldap);
        }

        if (!empty($standardConnect) && $standardConnect == 'true') {
            if (empty($authenticated)) {
                $authenticated = AuthenticationModel::authentication(['login' => $login, 'password' => $password]);
            } else {
                $user = UserModel::getByLowerLogin(['login' => $login, 'select' => ['id']]);
                UserModel::updatePassword(['id' => $user['id'], 'password' => $password]);
            }
        }

        if (empty($authenticated) && !empty($error) && $error != 'Invalid credentials') {
            return ['errors' => $error];
        } elseif (empty($authenticated)) {
            return ['errors' => 'Authentication Failed'];
        }

        return true;
    }

    private static function casConnection()
    {
        $casConfiguration = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/cas_config.xml']);

        $version = (string)$casConfiguration->CAS_VERSION;
        $hostname = (string)$casConfiguration->WEB_CAS_URL;
        $port = (string)$casConfiguration->WEB_CAS_PORT;
        $uri = (string)$casConfiguration->WEB_CAS_CONTEXT;
        $certificate = (string)$casConfiguration->PATH_CERTIFICATE;
        $separator = (string)$casConfiguration->ID_SEPARATOR;

        if (!in_array($version, ['CAS_VERSION_2_0', 'CAS_VERSION_3_0'])) {
            return ['errors' => 'Cas version not supported'];
        }

        \phpCAS::setDebug();
        \phpCAS::setVerbose(true);
        \phpCAS::client(constant($version), $hostname, (int)$port, $uri, $version != 'CAS_VERSION_3_0');

        if (!empty($certificate)) {
            \phpCAS::setCasServerCACert($certificate);
        } else {
            \phpCAS::setNoCasServerValidation();
        }
        \phpCAS::setFixedServiceURL(UrlController::getCoreUrl() . 'dist/index.html');
        \phpCAS::setNoClearTicketsFromUrl();
        if (!\phpCAS::isAuthenticated()) {
            return ['errors' => 'Cas authentication failed'];
        }

        $casId = \phpCAS::getUser();
        if (!empty($separator)) {
            $login = explode($separator, $casId)[0];
        } else {
            $login = $casId;
        }

        return ['login' => $login];
    }

    private static function casDisconnection()
    {
        $casConfiguration = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/cas_config.xml']);

        $version = (string)$casConfiguration->CAS_VERSION;
        $hostname = (string)$casConfiguration->WEB_CAS_URL;
        $port = (string)$casConfiguration->WEB_CAS_PORT;
        $uri = (string)$casConfiguration->WEB_CAS_CONTEXT;
        $certificate = (string)$casConfiguration->PATH_CERTIFICATE;

        \phpCAS::setDebug();
        \phpCAS::setVerbose(true);
        \phpCAS::client(constant($version), $hostname, (int)$port, $uri, $version != 'CAS_VERSION_3_0');

        if (!empty($certificate)) {
            \phpCAS::setCasServerCACert($certificate);
        } else {
            \phpCAS::setNoCasServerValidation();
        }
        \phpCAS::setFixedServiceURL(UrlController::getCoreUrl() . 'dist/index.html');
        \phpCAS::setNoClearTicketsFromUrl();
        $logoutUrl = \phpCAS::getServerLogoutURL();

        return ['logoutUrl' => $logoutUrl];
    }

    private static function ssoConnection()
    {
        $ssoConfiguration = ConfigurationModel::getByPrivilege(['privilege' => 'admin_sso', 'select' => ['value']]);
        if (empty($ssoConfiguration['value'])) {
            return ['errors' => 'Sso configuration missing'];
        }

        $ssoConfiguration = json_decode($ssoConfiguration['value'], true);
        $mapping = array_column($ssoConfiguration['mapping'], 'ssoId', 'maarchId');
        if (empty($mapping['login'])) {
            return ['errors' => 'Sso configuration missing : no login mapping'];
        }

        if (in_array(strtoupper($mapping['login']), ['REMOTE_USER', 'PHP_AUTH_USER'])) {
            $login = $_SERVER[strtoupper($mapping['login'])] ?? null;
        } else {
            $login = $_SERVER['HTTP_' . strtoupper($mapping['login'])] ?? null;
        }
        if (empty($login)) {
            return ['errors' => 'Authentication Failed : login not present in header'];
        }

        return ['login' => $login];
    }

    private static function keycloakConnection(array $args)
    {
        $keycloakConfig = CoreConfigModel::getKeycloakConfiguration();

        if (empty($keycloakConfig) || empty($keycloakConfig['authServerUrl']) || empty($keycloakConfig['realm']) || empty($keycloakConfig['clientId']) || empty($keycloakConfig['clientSecret']) || empty($keycloakConfig['redirectUri'])) {
            return ['errors' => 'Keycloak not configured'];
        }

        $provider = new Keycloak($keycloakConfig);

        try {
            $token = $provider->getAccessToken('authorization_code', ['code' => $args['code']]);
        } catch (\Exception $e) {
            return ['errors' => 'Authentication Failed'];
        }

        try {
            $user = $provider->getResourceOwner($token);

            $login = $user->getId();
            $keycloakAccessToken = $token->getToken();

            $userMaarch = UserModel::getByLogin(['login' => $login, 'select' => ['id', 'external_id']]);

            if (empty($userMaarch)) {
                return ['errors' => 'Authentication Failed'];
            }

            $userMaarch['external_id'] = json_decode($userMaarch['external_id'], true);
            $userMaarch['external_id']['keycloakAccessToken'] = $keycloakAccessToken;
            $userMaarch['external_id'] = json_encode($userMaarch['external_id']);

            UserModel::updateExternalId(['id' => $userMaarch['id'], 'externalId' => $userMaarch['external_id']]);

            return ['login' => $login];
        } catch (\Exception $e) {
            return ['errors' => 'Authentication Failed'];
        }
    }

    private static function keycloakDisconnection()
    {
        $keycloakConfig = CoreConfigModel::getKeycloakConfiguration();

        $provider = new Keycloak($keycloakConfig);

        $externalId = UserModel::getById(['id' => $GLOBALS['id'], 'select' => ['external_id']]);
        $externalId = json_decode($externalId['external_id'], true);
        $accessToken = $externalId['keycloakAccessToken'];
        unset($externalId['keycloakAccessToken']);
        UserModel::update([
            'set'   => ['external_id' => json_encode($externalId)],
            'where' => ['id = ?'],
            'data'  => [$GLOBALS['id']]
        ]);

        $url = $provider->getLogoutUrl(['client_id' => $keycloakConfig['clientId'], 'refresh_token' => $accessToken]);

        return ['logoutUrl' => $url];
    }

    private static function openAMConnection()
    {
        //TODO OpenAM 13
        $configuration = CoreConfigModel::getJsonLoaded(['path' => 'apps/maarch_entreprise/xml/openAM.json']);

        if (empty($configuration['attributeUrl']) || empty($configuration['cookieName']) || empty($configuration['attributeName'])) {
            return ['errors' => 'OpenAM configuration missing'];
        }

        if (empty($_COOKIE[$configuration['cookieName']])) {
            return ['errors' => 'Authentication Failed : User cookie is not set'];
        }
        $curlResponse = CurlModel::execSimple([
            'url'           => "{$configuration['attributeUrl']}?subjectid={$_COOKIE[$configuration['cookieName']]}&attributenames={$configuration['attributeName']}",
            'method'        => 'GET',
        ]);

        $login = $curlResponse['response']['attributes'][0]['values'][0] ?? null;

        if (empty($login)) {
            return ['errors' => 'Authentication Failed : login not present in response'];
        }

        return ['login' => $login];
    }

    public function getRefreshedToken(Request $request, Response $response)
    {
        $queryParams = $request->getQueryParams();

        if (!Validator::stringType()->notEmpty()->validate($queryParams['refreshToken'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Refresh Token is empty']);
        }

        try {
            $jwt = JWT::decode($queryParams['refreshToken'], CoreConfigModel::getEncryptKey(), ['HS256']);
        } catch (\Exception $e) {
            return $response->withStatus(401)->withJson(['errors' => 'Authentication Failed']);
        }

        $user = UserModel::getById(['select' => ['id', 'refresh_token'], 'id' => $jwt->user->id]);
        if (empty($user['refresh_token'])) {
            return $response->withStatus(401)->withJson(['errors' => 'Authentication Failed']);
        }

        $user['refresh_token'] = json_decode($user['refresh_token'], true);
        if (!in_array($queryParams['refreshToken'], $user['refresh_token'])) {
            return $response->withStatus(401)->withJson(['errors' => 'Authentication Failed']);
        }

        $GLOBALS['id'] = $user['id'];

        return $response->withJson(['token' => AuthenticationController::getJWT()]);
    }

    public static function getJWT()
    {
        $sessionTime = AuthenticationController::MAX_DURATION_TOKEN;

        $file = CoreConfigModel::getJsonLoaded(['path' => 'apps/maarch_entreprise/xml/config.json']);
        if ($file) {
            if (!empty($file['config']['cookieTime'])) {
                if ($sessionTime > (int)$file['config']['cookieTime']) {
                    $sessionTime = (int)$file['config']['cookieTime'];
                }
            }
        }

        $user = UserModel::getById(['id' => $GLOBALS['id'], 'select' => ['id', 'firstname', 'lastname', 'status', 'user_id as login']]);

        $token = [
            'exp'   => time() + 60 * $sessionTime,
            'user'  => $user
        ];

        $jwt = JWT::encode($token, CoreConfigModel::getEncryptKey());

        return $jwt;
    }

    public static function getRefreshJWT()
    {
        $sessionTime = AuthenticationController::MAX_DURATION_TOKEN;

        $file = CoreConfigModel::getJsonLoaded(['path' => 'apps/maarch_entreprise/xml/config.json']);
        if ($file) {
            $sessionTime = (int)$file['config']['cookieTime'];
        }

        $token = [
            'exp'   => time() + 60 * $sessionTime,
            'user'  => [
                'id' => $GLOBALS['id']
            ]
        ];

        $jwt = JWT::encode($token, CoreConfigModel::getEncryptKey());

        return $jwt;
    }

    public static function getResetJWT($args = [])
    {
        $token = [
            'exp'   => time() + $args['expirationTime'],
            'user'  => [
                'id' => $args['id']
            ]
        ];

        $jwt = JWT::encode($token, CoreConfigModel::getEncryptKey());

        return $jwt;
    }

    public static function sendAccountActivationNotification(array $args)
    {
        $resetToken = AuthenticationController::getResetJWT(['id' => $args['userId'], 'expirationTime' => 1209600]); // 14 days
        UserModel::update(['set' => ['reset_token' => $resetToken], 'where' => ['id = ?'], 'data' => [$args['userId']]]);

        $url = UrlController::getCoreUrl() . 'dist/index.html#/reset-password?token=' . $resetToken;

        $configuration = ConfigurationModel::getByPrivilege(['privilege' => 'admin_email_server', 'select' => ['value']]);
        $configuration = json_decode($configuration['value'], true);
        if (!empty($configuration['from'])) {
            $sender = $configuration['from'];
        } else {
            $sender = $args['userEmail'];
        }
        EmailController::createEmail([
            'userId'    => $args['userId'],
            'data'      => [
                'sender'        => ['email' => $sender],
                'recipients'    => [$args['userEmail']],
                'object'        => _NOTIFICATIONS_USER_CREATION_SUBJECT,
                'body'          => _NOTIFICATIONS_USER_CREATION_BODY . '<a href="' . $url . '">'._CLICK_HERE.'</a>' . _NOTIFICATIONS_USER_CREATION_FOOTER,
                'isHtml'        => true,
                'status'        => 'WAITING'
            ]
        ]);

        return true;
    }

    private static function isUserAuthorized(array $args)
    {
        $user = UserModel::getByLowerLogin(['login' => $args['login'], 'select' => ['mode', 'status']]);
        if (empty($user) || $user['mode'] == 'rest' || $user['status'] == 'SPD') {
            return false;
        }

        return true;
    }

    public static function canAccessInstallerWhitoutAuthentication(array $args)
    {
        $installerRoutes = [
            'GET/installer/prerequisites', 'GET/installer/databaseConnection', 'GET/installer/sqlDataFiles', 'GET/installer/docservers', 'GET/installer/custom',
            'GET/installer/customs', 'POST/installer/custom', 'POST/installer/database', 'POST/installer/docservers', 'POST/installer/customization',
            'PUT/installer/administrator', 'DELETE/installer/lock'
        ];

        if (!in_array($args['route'], $installerRoutes)) {
            return false;
        } elseif (is_file("custom/custom.json")) {
            $customs = scandir('custom');
            if (count($customs) > 4) {
                return false;
            }
            foreach ($customs as $custom) {
                if (in_array($custom, ['custom.json', '.', '..'])) {
                    continue;
                }
                if (!is_file("custom/{$custom}/initializing.lck")) {
                    return false;
                }
            }
        }

        return true;
    }
}