Compare commits

..

No commits in common. "5c0b6baf957ea63810615a0db395b9d8c71da0a0" and "d81ccb81e7a6fb9c4265a386c6a42ced44e8c7eb" have entirely different histories.

9 changed files with 99 additions and 292 deletions

View File

@ -2,7 +2,6 @@
## Versions 13.5.00 ## Versions 13.5.00
**Améliorations :** **Améliorations :**
- Validation de la connexion au site grâce à l'envoi d'un code par email. L'option est activée depuis la configuration du site, onglet connexion. Elle s'active par groupe montant, exemple "éditeur" pour éditeurs et administrateurs.
- Optimisation du chargement des variables de classe. - Optimisation du chargement des variables de classe.
- Suppression de redondance de déclaration des charset. - Suppression de redondance de déclaration des charset.
@ -10,7 +9,6 @@
- Corrige un bug de changement de mot de passe pour les comptes non admin. - Corrige un bug de changement de mot de passe pour les comptes non admin.
- Blog 7.12, corrige un bug d'affichage des articles lorsque le thème Moderne est sélectionné. - Blog 7.12, corrige un bug d'affichage des articles lorsque le thème Moderne est sélectionné.
- Corrige un dysfonctionnement de la fonction de tronquage subword qui perturbait l'affichage des articles de blog. - Corrige un dysfonctionnement de la fonction de tronquage subword qui perturbait l'affichage des articles de blog.
- Activation de la mémorisation de l'onglet actif dans la configuration après validation du formulaire ou visite d'une autre page du site.
## Versions 13.4.00 ## Versions 13.4.00
** Améliorations :** ** Améliorations :**

View File

@ -343,7 +343,7 @@ class helper
public static function checkRewrite() public static function checkRewrite()
{ {
// N'interroge que le serveur Apache // N'interroge que le serveur Apache
if ((helper::checkServerSoftware() === false)) { if (strpos($_SERVER["SERVER_SOFTWARE"], 'Apache') > 0) {
self::$rewriteStatus = false; self::$rewriteStatus = false;
} else { } else {
// Ouvre et scinde le fichier .htaccess // Ouvre et scinde le fichier .htaccess
@ -354,14 +354,6 @@ class helper
return self::$rewriteStatus; return self::$rewriteStatus;
} }
/**
* Retourne vrai ou faux selon que le serveur est comptatible avec htaccess
* @return bool
*/
public static function checkServerSoftware() {
return (stripos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false || stripos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false);
}
/** /**
* Renvoie le numéro de version de Zwii est en ligne * Renvoie le numéro de version de Zwii est en ligne
* @return string * @return string

View File

@ -496,8 +496,7 @@ class config extends common
'autoDisconnect' => $this->getInput('connectAutoDisconnect', helper::FILTER_BOOLEAN), 'autoDisconnect' => $this->getInput('connectAutoDisconnect', helper::FILTER_BOOLEAN),
'captchaType' => $this->getInput('connectCaptchaType'), 'captchaType' => $this->getInput('connectCaptchaType'),
'showPassword' => $this->getInput('connectShowPassword', helper::FILTER_BOOLEAN), 'showPassword' => $this->getInput('connectShowPassword', helper::FILTER_BOOLEAN),
'redirectLogin' => $this->getInput('connectRedirectLogin', helper::FILTER_BOOLEAN), 'redirectLogin' => $this->getInput('connectRedirectLogin', helper::FILTER_BOOLEAN)
'mailAuth' => $this->getInput('connectAuthMail', helper::FILTER_BOOLEAN),
] ]
] ]
]); ]);

View File

@ -3,7 +3,13 @@
<div class="col12"> <div class="col12">
<div class="block"> <div class="block">
<h4><?php echo helper::translate('Sécurité de la connexion'); ?> <h4><?php echo helper::translate('Sécurité de la connexion'); ?>
<!--<span id="specialeHelpButton" class="helpDisplayButton">
<a href="https://doc.zwiicms.fr/connexion" target="_blank" title="Cliquer pour consulter l'aide en ligne">
<?php // echo template::ico('help', ['margin' => 'left']); ?>
</a>
</span>-->
</h4> </h4>
<div class="row"> <div class="row">
<div class="col4"> <div class="col4">
<?php echo template::checkbox('connectShowPassword', true, 'Dévoiler le mot de passe', [ <?php echo template::checkbox('connectShowPassword', true, 'Dévoiler le mot de passe', [
@ -25,36 +31,44 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col4"> <div class="col3">
<?php echo template::select('connectAttempt', $module::$connectAttempt, [ <?php echo template::select('connectAttempt', $module::$connectAttempt, [
'label' => 'Limitation des tentatives', 'label' => 'Limitation des tentatives',
'selected' => $this->getData(['config', 'connect', 'attempt']) 'selected' => $this->getData(['config', 'connect', 'attempt'])
]); ?> ]); ?>
</div> </div>
<div class="col4"> <div class="col3">
<?php echo template::select('connectTimeout', $module::$connectTimeout, [ <?php echo template::select('connectTimeout', $module::$connectTimeout, [
'label' => 'Blocage après échecs', 'label' => 'Blocage après échecs',
'selected' => $this->getData(['config', 'connect', 'timeout']) 'selected' => $this->getData(['config', 'connect', 'timeout'])
]); ?> ]); ?>
</div> </div>
<div class="col4"> <div class="col3 verticalAlignBottom">
<?php echo template::select('connectAuthMail', array_merge([''=>'Aucune'], self::$groupNews), [ <label id="helpBlacklist"><?php echo helper::translate('Liste noire'); ?>
'label' => 'Validation par messagerie', <?php echo template::help(
'selected' => $this->getData(['config', 'connect', 'mailAuth']), 'La liste noire énumère les tentatives de connexion à partir de comptes inexistants. Sont stockés : la date, l\'heure, le nom du compte et l\'IP.
'help' => 'La connexion est confirmée par une clé adressée par messagerie. Depuis le groupe sélectionnée et les groupes supérieurs.' Après le nombre de tentatives autorisées, l\'IP et le compte sont bloqués.'
);
?>
</label>
<?php echo template::button('ConnectBlackListDownload', [
'href' => helper::baseUrl() . 'config/blacklistDownload',
'value' => 'Télécharger la liste',
'ico' => 'download'
]); ?> ]); ?>
</div> </div>
<div class="col3 verticalAlignBottom">
<?php echo template::button('CnnectBlackListReset', [
'class' => 'buttonRed',
'href' => helper::baseUrl() . 'config/blacklistReset',
'value' => 'Réinitialiser la liste',
'ico' => 'trash'
]); ?>
</div> </div>
</div> </div>
</div>
<div class="col12">
<div class="block">
<h4><?php echo helper::translate('Captcha à la connexion'); ?>
</h4>
<div class="row"> <div class="row">
<div class="col3"> <div class="col3">
<?php echo template::checkbox('connectCaptcha', true, 'Activer', [ <?php echo template::checkbox('connectCaptcha', true, 'Captcha à la connexion', [
'checked' => $this->getData(['config', 'connect', 'captcha']) 'checked' => $this->getData(['config', 'connect', 'captcha'])
]); ?> ]); ?>
</div> </div>
@ -78,32 +92,34 @@
<div class="col12"> <div class="col12">
<div class="block"> <div class="block">
<h4><?php echo helper::translate('Journalisation'); ?> <h4><?php echo helper::translate('Journalisation'); ?>
<!--<span id="specialeHelpButton" class="helpDisplayButton">
<a href="https://doc.zwiicms.fr/journalisation" target="_blank" title="Cliquer pour consulter l'aide en ligne">
<?php // echo template::ico('help', ['margin' => 'left']); ?>
</a>
</span>
-->
</h4> </h4>
<div class="row"> <div class="row">
<div class="col6"> <div class="col3">
<div class="row">
<div class="col6">
<?php echo template::checkbox('connectLog', true, 'Activer la journalisation', [ <?php echo template::checkbox('connectLog', true, 'Activer la journalisation', [
'checked' => $this->getData(['config', 'connect', 'log']) 'checked' => $this->getData(['config', 'connect', 'log'])
]); ?> ]); ?>
</div> </div>
<div class="col6"> <div class="col3">
<?php echo template::select('connectAnonymousIp', $module::$anonIP, [ <?php echo template::select('connectAnonymousIp', $module::$anonIP, [
'label' => 'Anonymat des adresses IP', 'label' => 'Anonymat des adresses IP',
'selected' => $this->getData(['config', 'connect', 'anonymousIp']), 'selected' => $this->getData(['config', 'connect', 'anonymousIp']),
'help' => 'La règlementation française impose un anonymat de niveau 2' 'help' => 'La règlementation française impose un anonymat de niveau 2'
]); ?> ]); ?>
</div> </div>
</div> <div class="col3 verticalAlignBottom">
<div class="row">
<div class="col6 ">
<?php echo template::button('ConfigLogDownload', [ <?php echo template::button('ConfigLogDownload', [
'href' => helper::baseUrl() . 'config/logDownload', 'href' => helper::baseUrl() . 'config/logDownload',
'value' => 'Télécharger le journal', 'value' => 'Télécharger le journal',
'ico' => 'download' 'ico' => 'download'
]); ?> ]); ?>
</div> </div>
<div class="col6"> <div class="col3 verticalAlignBottom">
<?php echo template::button('ConnectLogReset', [ <?php echo template::button('ConnectLogReset', [
'class' => 'buttonRed', 'class' => 'buttonRed',
'href' => helper::baseUrl() . 'config/logReset', 'href' => helper::baseUrl() . 'config/logReset',
@ -113,34 +129,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col6 verticalAlignBottom">
<div class="row">
<div class="col6 verticalAlignBottom">
<label id="helpBlacklist"><?php echo helper::translate('Liste noire'); ?>
<?php echo template::help(
'La liste noire énumère les tentatives de connexion à partir de comptes inexistants. Sont stockés : la date, l\'heure, le nom du compte et l\'IP.
Après le nombre de tentatives autorisées, l\'IP et le compte sont bloqués.'
);
?>
</label>
<?php echo template::button('ConnectBlackListDownload', [
'href' => helper::baseUrl() . 'config/blacklistDownload',
'value' => 'Télécharger la liste',
'ico' => 'download'
]); ?>
</div>
<div class="col6 verticalAlignBottom">
<?php echo template::button('CnnectBlackListReset', [
'class' => 'buttonRed',
'href' => helper::baseUrl() . 'config/blacklistReset',
'value' => 'Réinitialiser la liste',
'ico' => 'trash'
]); ?>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,23 +22,23 @@
<?php echo template::button('configSetupButton', [ <?php echo template::button('configSetupButton', [
'value' => 'Configuration', 'value' => 'Configuration',
'class' => 'buttonTab', 'class' => 'buttonTab',
'href' => helper::baseUrl() . 'config/register/setup' //'href' => helper::baseUrl() . 'config/register/setup'
]); ?> ]); ?>
<?php echo template::button('configSocialButton', [ <?php echo template::button('configSocialButton', [
'value' => 'Référencement', 'value' => 'Référencement',
'class' => 'buttonTab', 'class' => 'buttonTab',
'href' => helper::baseUrl() . 'config/register/social' //'href' => helper::baseUrl() . 'config/register/social'
]); ?> ]); ?>
<?php echo template::button('configConnectButton', [ <?php echo template::button('configConnectButton', [
'value' => 'Connexion', 'value' => 'Connexion',
'class' => 'buttonTab', 'class' => 'buttonTab',
'href' => helper::baseUrl() . 'config/register/connect' //'href' => helper::baseUrl() . 'config/register/connect'
]); ?> ]); ?>
<?php echo template::button('configNetworkButton', [ <?php echo template::button('configNetworkButton', [
'value' => 'Réseau', 'value' => 'Réseau',
'class' => 'buttonTab', 'class' => 'buttonTab',
'href' => helper::baseUrl() . 'config/register/network' //'href' => helper::baseUrl() . 'config/register/network'
]); ?> ]); ?>
</div> </div>

View File

@ -44,7 +44,7 @@
<?php echo template::checkbox('configRewrite', true, 'Apache URL intelligentes', [ <?php echo template::checkbox('configRewrite', true, 'Apache URL intelligentes', [
'checked' => helper::checkRewrite(), 'checked' => helper::checkRewrite(),
'help' => 'Supprime le point d\'interrogation dans les URL, l\'option est indisponible avec les autres serveurs Web', 'help' => 'Supprime le point d\'interrogation dans les URL, l\'option est indisponible avec les autres serveurs Web',
'disabled' => helper::checkServerSoftware() === false and $module->isModRewriteEnabled() 'disabled' => stripos($_SERVER["SERVER_SOFTWARE"], 'Apache') === false and $module->isModRewriteEnabled()
]); ?> ]); ?>
</div> </div>
</div> </div>

View File

@ -26,7 +26,6 @@ class user extends common
'logout' => self::GROUP_MEMBER, 'logout' => self::GROUP_MEMBER,
'forgot' => self::GROUP_VISITOR, 'forgot' => self::GROUP_VISITOR,
'login' => self::GROUP_VISITOR, 'login' => self::GROUP_VISITOR,
'auth' => self::GROUP_VISITOR,
'reset' => self::GROUP_VISITOR, 'reset' => self::GROUP_VISITOR,
'profil' => self::GROUP_ADMIN, 'profil' => self::GROUP_ADMIN,
'profilEdit' => self::GROUP_ADMIN, 'profilEdit' => self::GROUP_ADMIN,
@ -67,7 +66,7 @@ class user extends common
public static $groupProfils = [ public static $groupProfils = [
self::GROUP_MEMBER => 'Membre', self::GROUP_MEMBER => 'Membre',
self::GROUP_EDITOR => 'Éditeur', self::GROUP_EDITOR => 'Éditeur'
]; ];
public static $listModules = []; public static $listModules = [];
@ -1044,47 +1043,14 @@ class user extends common
and $this->getData(['user', $userId, 'group']) >= self::GROUP_MEMBER and $this->getData(['user', $userId, 'group']) >= self::GROUP_MEMBER
and $captcha === true and $captcha === true
) { ) {
// RAZ
// RAZ des compteurs de blocage
$this->setData(['user', $userId, 'connectFail', 0], false); $this->setData(['user', $userId, 'connectFail', 0], false);
$this->setData(['user', $userId, 'connectTimeout', 0], false); $this->setData(['user', $userId, 'connectTimeout', 0], false);
// Accès multiples avec le même compte // Clé d'authenfication
$this->setData(['user', $userId, 'accessCsrf', $_SESSION['csrf']], false);
// Valeurs en sortie lorsque le site est en maintenance et que l'utilisateur n'est pas administrateur
if (
$this->getData(['config', 'maintenance'])
and $this->getData(['user', $userId, 'group']) < self::GROUP_ADMIN
) {
$this->addOutput([
'notification' => helper::translate('Seul un administrateur peut se connecter lors d\'une maintenance'),
'redirect' => helper::baseUrl(),
'state' => false
]);
} else {
/**
* Le site n'est pas en maintenance
* Double authentification en cas de saisie correcte
*/
// Clé d'authenfication utlisée pour lié le compte au cookie au lieu de stocke le hash du mot de passe
$authKey = uniqid('', true) . bin2hex(random_bytes(8)); $authKey = uniqid('', true) . bin2hex(random_bytes(8));
if ($this->getData(['config', 'connect', 'mailAuth']) >= $this->getData(['user', $userId, 'group'])) {
$logStatus = 'Envoi du mail d\'authentification';
// Redirection vers la page d'authentification
$authRedirect = 'user/auth/';
// Stocker la clé envoyée par email
$this->setData(['user', $userId, 'authKey', rand(100000, 999999)]);
} else {
$logStatus = 'Connexion réussie';
// La page d'autentification est vide
$authRedirect = '';
$this->setData(['user', $userId, 'authKey', $authKey]); $this->setData(['user', $userId, 'authKey', $authKey]);
}
// Validité du cookie // Validité du cookie
$expire = $this->getInput('userLoginLongTime', helper::FILTER_BOOLEAN) === true ? strtotime("+1 year") : 0; $expire = $this->getInput('userLoginLongTime', helper::FILTER_BOOLEAN) === true ? strtotime("+1 year") : 0;
switch ($this->getInput('userLoginLongTime', helper::FILTER_BOOLEAN)) { switch ($this->getInput('userLoginLongTime', helper::FILTER_BOOLEAN)) {
@ -1106,6 +1072,20 @@ class user extends common
break; break;
} }
// Accès multiples avec le même compte
$this->setData(['user', $userId, 'accessCsrf', $_SESSION['csrf']], false);
// Valeurs en sortie lorsque le site est en maintenance et que l'utilisateur n'est pas administrateur
if (
$this->getData(['config', 'maintenance'])
and $this->getData(['user', $userId, 'group']) < self::GROUP_ADMIN
) {
$this->addOutput([
'notification' => helper::translate('Seul un administrateur peut se connecter lors d\'une maintenance'),
'redirect' => helper::baseUrl(),
'state' => false
]);
} else {
$logStatus = 'Connexion réussie';
$pageId = $this->getUrl(2); $pageId = $this->getUrl(2);
if ( if (
$this->getData(['config', 'page404']) === $pageId $this->getData(['config', 'page404']) === $pageId
@ -1113,7 +1093,7 @@ class user extends common
) { ) {
$pageId = ''; $pageId = '';
} }
$redirect = ($pageId && strpos($pageId, 'user_reset') !== 0) ? helper::baseUrl() . $authRedirect . str_replace('_', '/', str_replace('__', '#', $pageId)) : helper::baseUrl() . $authRedirect; $redirect = ($pageId && strpos($pageId, 'user_reset') !== 0) ? helper::baseUrl() . str_replace('_', '/', str_replace('__', '#', $pageId)) : helper::baseUrl();
// Valeurs en sortie // Valeurs en sortie
$this->addOutput([ $this->addOutput([
'notification' => sprintf(helper::translate('Bienvenue %s %s'), $this->getData(['user', $userId, 'firstname']), $this->getData(['user', $userId, 'lastname'])), 'notification' => sprintf(helper::translate('Bienvenue %s %s'), $this->getData(['user', $userId, 'firstname']), $this->getData(['user', $userId, 'lastname'])),
@ -1162,107 +1142,16 @@ class user extends common
]); ]);
} }
/**
*
* Validation de la connexion par email
* @return void
*/
public function auth()
{
// Soumission du formulaire
if (
$this->isPost()
) {
// Vérifier la clé saisie
$targetKey = $this->getData(['user', $this->getUser('id'), 'authKey']);
$inputKey = $this->getInput('userAuthKey', helper::FILTER_INT);
if (
$targetKey === $inputKey &&
$this->getData(['user', $this->getUser('id'), 'connectTimeout']) + 3600 >= time()
) {
$pageId = $this->getUrl(2);
// La fiche de l'utilisateur contient la clé d'authentification
$this->setData(['user', $this->getUser('id'), 'authKey', $this->getInput('ZWII_AUTH_KEY')]);
$redirect = ($pageId && strpos($pageId, 'user_reset') !== 0) ? helper::baseUrl() . str_replace('_', '/', str_replace('__', '#', $pageId)) : helper::baseUrl();
// Journalisation
$this->saveLog('Connexion réussie');
// Réinitialiser le compteur de temps
$this->setData(['user', $this->getUser('id'), 'connectTimeout', 0]);
// Valeurs en sortie
$this->addOutput([
'redirect' => $redirect,
'notification' => helper::translate('Connexion réussie'),
'state' => true
]);
} else {
// Supprime la clé stockée et le temps limite
$this->deleteData(['user', $this->getUser('id'), 'authKey']);
// Réinitialiser le compteur de temps
$this->setData(['user', $this->getUser('id'), 'connectTimeout', 0]);
// Détruit les cookies d'authenfication
helper::deleteCookie('ZWII_USER_ID');
helper::deleteCookie('ZWII_AUTH_KEY');
// Détruit la session
session_destroy();
// Journalisation
$this->saveLog('Erreur de vérification de la clé envoyée par email ' . $this->getUser('id'));
// Valeurs en sortie
$this->addOutput([
'redirect' => helper::baseUrl(),
'notification' => helper::translate('La clé est incorrecte'),
'state' => false
]);
}
} else {
/**
* Envoi d'un email contenant une clé
* Stockage de la clé dans le compte de l'utilisateur
*/
// La clé est envoyée une seule fois
$sent = false;
if (
$this->getData(['user', $this->getUser('id'), 'authKey'])
&& $this->getData(['user', $this->getUser('id'), 'connectTimeout']) === 0
) {
$sent = $this->sendMail(
$this->getUser('mail'),
'Tentative de connexion à votre',
//'Bonjour <strong>' . $item['prenom'] . ' ' . $item['nom'] . '</strong>,<br><br>' .
'<p>Clé de validation à saisir dans le formulaire :</p>' .
'<h1><center>' . $this->getData(['user', $this->getUser('id'), 'authKey']) . '</center></h1>',
null,
$this->getData(['config', 'smtp', 'from'])
);
// Stocker l'envoi de l'email
$this->setData(['user', $this->getUser('id'), 'connectTimeout', time()]);
}
// Message envoyé sinon la connexion est réalisée pour ne pas bloquer.
if ($sent === false) {
}
// Valeurs en sortie
$this->addOutput([
'title' => helper::translate('Double authentification'),
'view' => 'auth',
'display' => self::DISPLAY_LAYOUT_LIGHT,
]);
}
}
/** /**
* Déconnexion * Déconnexion
*/ */
public function logout() public function logout()
{ {
// Détruit les cookies d'authenfication
helper::deleteCookie('ZWII_USER_ID'); helper::deleteCookie('ZWII_USER_ID');
//helper::deleteCookie('ZWII_USER_PASSWORD');
helper::deleteCookie('ZWII_AUTH_KEY'); helper::deleteCookie('ZWII_AUTH_KEY');
// Détruit la session // Détruit la session
session_destroy(); session_destroy();

View File

@ -1,36 +0,0 @@
/**
* This file is part of Zwii.
*
* For full copyright and license information, please see the LICENSE
* file that was distributed with this source code.
*
* @author Rémi Jean <remi.jean@outlook.com>
* @copyright Copyright (C) 2008-2018, Rémi Jean
* @author Frédéric Tempez <frederic.tempez@outlook.com>
* @copyright Copyright (C) 2018-2024, Frédéric Tempez
* @license CC Attribution-NonCommercial-NoDerivatives 4.0 International
* @link http://zwiicms.fr/
*/
/** @import url("site/data/admin.css"); */
/** NE PAS EFFACER
* admin.css
*/
@media screen and (max-width: 768px) {
#buttonsContainer {
display: grid;
}
#loginContainer {
order: 1;
}
#backContainer{
order: 2;
}
}
#userAuthKey {
text-align: center;
font-size: 1.3rem;
}

View File

@ -1,23 +0,0 @@
<?php echo template::formOpen('userAuthForm'); ?>
<div class="row">
<div class="col6 offset3">
<?php echo template::text('userAuthKey', [
'label' => helper::translate('Clé reçue par couriel')
]); ?>
</div>
</div>
<div class="row" id="buttonsContainer">
<div class="col2" id="backContainer">
<?php echo template::button('userAuthBack', [
'href' => $this->getUrl(2) ? helper::baseUrl() . ' user/login' . str_replace('_', '/', str_replace('__', '#', $this->getUrl(2))) : helper::baseUrl() . ' user/login',
'value' => template::ico('left')
]); ?>
</div>
<div class="col3 offset7" id="loginContainer">
<?php echo template::submit('userLoginSubmit', [
'value' => helper::translate('Authentification'),
'ico' => ''
]); ?>
</div>
</div>
<?php echo template::formClose(); ?>