Dépôt officiel du core SPIP
Les plugins-dist faisant partie de la distribution SPIP sont présents dans https://git.spip.net/spip/[nom du plugin dist]
https://www.spip.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
438 lines
13 KiB
438 lines
13 KiB
<?php |
|
|
|
/***************************************************************************\ |
|
* SPIP, Système de publication pour l'internet * |
|
* * |
|
* Copyright © avec tendresse depuis 2001 * |
|
* Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James * |
|
* * |
|
* Ce programme est un logiciel libre distribué sous licence GNU/GPL. * |
|
* Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne. * |
|
\***************************************************************************/ |
|
|
|
/** |
|
* Gestion des actions sécurisées |
|
* |
|
* @package SPIP\Core\Actions |
|
**/ |
|
|
|
if (!defined('_ECRIRE_INC_VERSION')) { |
|
return; |
|
} |
|
|
|
/** |
|
* Génère ou vérifie une action sécurisée |
|
* |
|
* Interface d'appel: |
|
* |
|
* - au moins un argument: retourne une URL ou un formulaire securisés |
|
* - sans argument : vérifie la sécurité et retourne `_request('arg')`, ou exit. |
|
* |
|
* @uses securiser_action_auteur() Pour produire l'URL ou le formulaire |
|
* @example |
|
* Tester une action reçue et obtenir son argument : |
|
* ``` |
|
* $securiser_action = charger_fonction('securiser_action'); |
|
* $arg = $securiser_action(); |
|
* ``` |
|
* |
|
* @param string $action |
|
* @param string $arg |
|
* @param string $redirect |
|
* @param bool|int|string $mode |
|
* - -1 : renvoyer action, arg et hash sous forme de array() |
|
* - true ou false : renvoyer une url, avec & (false) ou & (true) |
|
* - string : renvoyer un formulaire |
|
* @param string|int $att |
|
* id_auteur pour lequel generer l'action en mode url ou array() |
|
* atributs du formulaire en mode formulaire |
|
* @param bool $public |
|
* @return array|string |
|
*/ |
|
function inc_securiser_action_dist($action = '', $arg = '', $redirect = '', $mode = false, $att = '', $public = false) { |
|
if ($action) { |
|
return securiser_action_auteur($action, $arg, $redirect, $mode, $att, $public); |
|
} else { |
|
$arg = _request('arg'); |
|
$hash = _request('hash'); |
|
$action = _request('action') ? _request('action') : _request('formulaire_action'); |
|
if ($a = verifier_action_auteur("$action-$arg", $hash)) { |
|
return $arg; |
|
} |
|
include_spip('inc/minipres'); |
|
echo minipres(); |
|
exit; |
|
} |
|
} |
|
|
|
/** |
|
* Confirmer avant suppression si on arrive par un bouton action |
|
* a appeler dans la fonction action avant toute action destructrice |
|
* |
|
* demander_confirmation_avant_action("Supprimer l'article xxxx", "Oui je veux le supprimer"); |
|
* |
|
* L'action affiche le formulaire de demande de confirmation sans rendre la main au premier appel, |
|
* si l'utilisateur clique, cela relance l'action avec un confirm et quand on repasse ici, la fonction ne fera rien et l'action se finira normalement |
|
* |
|
* @param string $titre |
|
* @param string $titre_bouton |
|
* @param string|null $url_action |
|
* @return bool |
|
*/ |
|
function demander_confirmation_avant_action($titre, $titre_bouton, $url_action = null) { |
|
|
|
if (!$url_action) { |
|
$url_action = self(); |
|
$action = _request('action'); |
|
$url_action = parametre_url($url_action, 'action', $action, '&'); |
|
} |
|
else { |
|
$action = parametre_url($url_action, 'action'); |
|
} |
|
|
|
$arg = parametre_url($url_action, 'arg'); |
|
$confirm = md5("$action:$arg:" . realpath(__FILE__)); |
|
if (_request('confirm_action') === $confirm) { |
|
return true; |
|
} |
|
|
|
$url_confirm = parametre_url($url_action, 'confirm_action', $confirm, '&'); |
|
include_spip('inc/filtres'); |
|
$bouton_action = bouton_action($titre_bouton, $url_confirm); |
|
$corps = "<div style='text-align:center;'>$bouton_action</div>"; |
|
|
|
include_spip('inc/minipres'); |
|
echo minipres($titre, $corps); |
|
exit; |
|
} |
|
|
|
/** |
|
* Retourne une URL ou un formulaire sécurisés |
|
* |
|
* @note |
|
* Attention: PHP applique urldecode sur $_GET mais pas sur $_POST |
|
* cf http://fr.php.net/urldecode#48481 |
|
* https://code.spip.net/@securiser_action_auteur |
|
* |
|
* @uses calculer_action_auteur() |
|
* @uses generer_form_action() |
|
* |
|
* @param string $action |
|
* @param string $arg |
|
* @param string $redirect |
|
* @param bool|int|string $mode |
|
* - -1 : renvoyer action, arg et hash sous forme de array() |
|
* - true ou false : renvoyer une url, avec & (false) ou & (true) |
|
* - string : renvoyer un formulaire |
|
* @param string|int $att |
|
* - id_auteur pour lequel générer l'action en mode URL ou array() |
|
* - atributs du formulaire en mode formulaire |
|
* @param bool $public |
|
* @return array|string |
|
* - string URL, si $mode = true ou false, |
|
* - string code HTML du formulaire, si $mode texte, |
|
* - array Tableau (action=>x, arg=>x, hash=>x) si $mode=-1. |
|
*/ |
|
function securiser_action_auteur($action, $arg, $redirect = '', $mode = false, $att = '', $public = false) { |
|
|
|
// mode URL ou array |
|
if (!is_string($mode)) { |
|
$hash = calculer_action_auteur("$action-$arg", is_numeric($att) ? $att : null); |
|
|
|
$r = rawurlencode($redirect); |
|
if ($mode === -1) { |
|
return ['action' => $action, 'arg' => $arg, 'hash' => $hash]; |
|
} else { |
|
return generer_url_action( |
|
$action, |
|
'arg=' . rawurlencode($arg) . "&hash=$hash" . (!$r ? '' : "&redirect=$r"), |
|
$mode, |
|
$public |
|
); |
|
} |
|
} |
|
|
|
// mode formulaire |
|
$hash = calculer_action_auteur("$action-$arg"); |
|
$att .= " style='margin: 0px; border: 0px'"; |
|
if ($redirect) { |
|
$redirect = "\n\t\t<input name='redirect' type='hidden' value='" . str_replace("'", ''', $redirect) . "' />"; |
|
} |
|
$mode .= $redirect . " |
|
<input name='hash' type='hidden' value='$hash' /> |
|
<input name='arg' type='hidden' value='$arg' />"; |
|
|
|
return generer_form_action($action, $mode, $att, $public); |
|
} |
|
|
|
/** |
|
* Caracteriser un auteur : l'auteur loge si $id_auteur=null |
|
* |
|
* @param int|null $id_auteur |
|
* @return array |
|
*/ |
|
function caracteriser_auteur($id_auteur = null) { |
|
static $caracterisation = []; |
|
|
|
if (is_null($id_auteur) and !isset($GLOBALS['visiteur_session']['id_auteur'])) { |
|
// si l'auteur courant n'est pas connu alors qu'il peut demander une action |
|
// c'est une connexion par php_auth ou 1 instal, on se rabat sur le cookie. |
|
// S'il n'avait pas le droit de realiser cette action, le hash sera faux. |
|
if ( |
|
isset($_COOKIE['spip_session']) |
|
and (preg_match('/^(\d+)/', $_COOKIE['spip_session'], $r)) |
|
) { |
|
return [$r[1], '']; |
|
// Necessaire aux forums anonymes. |
|
// Pour le reste, ca echouera. |
|
} else { |
|
return ['0', '']; |
|
} |
|
} |
|
// Eviter l'acces SQL si le pass est connu de PHP |
|
if (is_null($id_auteur)) { |
|
$id_auteur = isset($GLOBALS['visiteur_session']['id_auteur']) ? $GLOBALS['visiteur_session']['id_auteur'] : 0; |
|
if (isset($GLOBALS['visiteur_session']['pass']) and $GLOBALS['visiteur_session']['pass']) { |
|
return $caracterisation[$id_auteur] = [$id_auteur, $GLOBALS['visiteur_session']['pass']]; |
|
} |
|
} |
|
|
|
if (isset($caracterisation[$id_auteur])) { |
|
return $caracterisation[$id_auteur]; |
|
} |
|
|
|
if ($id_auteur) { |
|
include_spip('base/abstract_sql'); |
|
$t = sql_fetsel('id_auteur, pass', 'spip_auteurs', "id_auteur=$id_auteur"); |
|
if ($t) { |
|
return $caracterisation[$id_auteur] = [$t['id_auteur'], $t['pass']]; |
|
} |
|
include_spip('inc/minipres'); |
|
echo minipres(); |
|
exit; |
|
} // Visiteur anonyme, pour ls forums par exemple |
|
else { |
|
return ['0', '']; |
|
} |
|
} |
|
|
|
/** |
|
* Calcule une cle securisee pour une action et un auteur donnes |
|
* utilisee pour generer des urls personelles pour executer une action qui modifie la base |
|
* et verifier la legitimite de l'appel a l'action |
|
* |
|
* @param string $action |
|
* @param int $id_auteur |
|
* @param string $pass |
|
* @param string $alea |
|
* @return string |
|
*/ |
|
function _action_auteur($action, $id_auteur, $pass, $alea) { |
|
static $sha = []; |
|
if (!isset($sha[$id_auteur . $pass . $alea])) { |
|
if (!isset($GLOBALS['meta'][$alea])) { |
|
if (!$exec = _request('exec') or !autoriser_sans_cookie($exec)) { |
|
include_spip('inc/acces'); |
|
charger_aleas(); |
|
if (empty($GLOBALS['meta'][$alea])) { |
|
include_spip('inc/minipres'); |
|
echo minipres(); |
|
spip_log("$alea indisponible"); |
|
exit; |
|
} |
|
} |
|
} |
|
include_spip('auth/sha256.inc'); |
|
$sha[$id_auteur . $pass . $alea] = spip_sha256($id_auteur . $pass . @$GLOBALS['meta'][$alea]); |
|
} |
|
if (function_exists('sha1')) { |
|
return sha1($action . $sha[$id_auteur . $pass . $alea]); |
|
} else { |
|
return md5($action . $sha[$id_auteur . $pass . $alea]); |
|
} |
|
} |
|
|
|
/** |
|
* Calculer le hash qui signe une action pour un auteur |
|
* |
|
* @param string $action |
|
* @param int|null $id_auteur |
|
* @return string |
|
*/ |
|
function calculer_action_auteur($action, $id_auteur = null) { |
|
list($id_auteur, $pass) = caracteriser_auteur($id_auteur); |
|
|
|
return _action_auteur($action, $id_auteur, $pass, 'alea_ephemere'); |
|
} |
|
|
|
|
|
/** |
|
* Verifier le hash de signature d'une action |
|
* toujours exclusivement pour l'auteur en cours |
|
* |
|
* @param $action |
|
* @param $hash |
|
* @return bool |
|
*/ |
|
function verifier_action_auteur($action, $hash) { |
|
list($id_auteur, $pass) = caracteriser_auteur(); |
|
if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere')) { |
|
return true; |
|
} |
|
if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere_ancien')) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// |
|
// Des fonctions independantes du visiteur, qui permettent de controler |
|
// par exemple que l'URL d'un document a la bonne cle de lecture |
|
// |
|
|
|
/** |
|
* Renvoyer le secret du site, et le generer si il n'existe pas encore |
|
* Le secret du site doit rester aussi secret que possible, et est eternel |
|
* On ne doit pas l'exporter |
|
* |
|
* @return string |
|
*/ |
|
function secret_du_site() { |
|
if (!isset($GLOBALS['meta']['secret_du_site'])) { |
|
include_spip('base/abstract_sql'); |
|
$GLOBALS['meta']['secret_du_site'] = sql_getfetsel('valeur', 'spip_meta', "nom='secret_du_site'"); |
|
} |
|
if ( |
|
!isset($GLOBALS['meta']['secret_du_site']) |
|
or (strlen($GLOBALS['meta']['secret_du_site']) < 64) |
|
) { |
|
include_spip('inc/acces'); |
|
include_spip('auth/sha256.inc'); |
|
ecrire_meta( |
|
'secret_du_site', |
|
spip_sha256( |
|
$_SERVER['DOCUMENT_ROOT'] |
|
. (isset($_SERVER['SERVER_SIGNATURE']) ? $_SERVER['SERVER_SIGNATURE'] : '') |
|
. creer_uniqid() |
|
), |
|
'non' |
|
); |
|
lire_metas(); // au cas ou ecrire_meta() ne fonctionne pas |
|
} |
|
|
|
return $GLOBALS['meta']['secret_du_site']; |
|
} |
|
|
|
/** |
|
* Calculer une signature valable pour une action et pour le site |
|
* |
|
* @param string $action |
|
* @return string |
|
*/ |
|
function calculer_cle_action($action) { |
|
if (function_exists('sha1')) { |
|
return sha1($action . secret_du_site()); |
|
} else { |
|
return md5($action . secret_du_site()); |
|
} |
|
} |
|
|
|
/** |
|
* Verifier la cle de signature d'une action valable pour le site |
|
* |
|
* @param string $action |
|
* @param string $cle |
|
* @return bool |
|
*/ |
|
function verifier_cle_action($action, $cle) { |
|
return ($cle == calculer_cle_action($action)); |
|
} |
|
|
|
|
|
/** |
|
* Calculer le token de prévisu |
|
* |
|
* Il permettra de transmettre une URL publique d’un élément non encore publié, |
|
* pour qu’une personne tierce le relise. Valable quelques temps. |
|
* |
|
* @see verifier_token_previsu() |
|
* @param string $url Url à autoriser en prévisu |
|
* @param int|null id_auteur qui génère le token de prévisu. Null utilisera auteur courant. |
|
* @param string $alea Nom de l’alea à utiliser |
|
* @return string Token, de la forme "{id}*{hash}" |
|
*/ |
|
function calculer_token_previsu($url, $id_auteur = null, $alea = 'alea_ephemere') { |
|
if (is_null($id_auteur)) { |
|
if (!empty($GLOBALS['visiteur_session']['id_auteur'])) { |
|
$id_auteur = $GLOBALS['visiteur_session']['id_auteur']; |
|
} |
|
} |
|
if (!$id_auteur = intval($id_auteur)) { |
|
return ''; |
|
} |
|
// On nettoie l’URL de tous les var_. |
|
$url = nettoyer_uri_var($url); |
|
|
|
$token = _action_auteur('previsualiser-' . $url, $id_auteur, null, $alea); |
|
return "$id_auteur-$token"; |
|
} |
|
|
|
|
|
/** |
|
* Vérifie un token de prévisu |
|
* |
|
* Découpe le token pour avoir l’id_auteur, |
|
* Retrouve à partir de l’url un objet/id_objet en cours de parcours |
|
* Recrée un token pour l’auteur et l’objet trouvé et le compare au token. |
|
* |
|
* @see calculer_token_previsu() |
|
* @param string $token Token, de la forme '{id}*{hash}' |
|
* @return false|array |
|
* - `False` si echec, |
|
* + Tableau (id auteur, type d’objet, id_objet) sinon. |
|
*/ |
|
function verifier_token_previsu($token) { |
|
// retrouver auteur / hash |
|
$e = explode('-', $token, 2); |
|
if (count($e) == 2 and is_numeric(reset($e))) { |
|
$id_auteur = intval(reset($e)); |
|
} else { |
|
return false; |
|
} |
|
|
|
// calculer le type et id de l’url actuelle |
|
include_spip('inc/urls'); |
|
include_spip('inc/filtres_mini'); |
|
$url = url_absolue(self()); |
|
|
|
// verifier le token |
|
$_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere'); |
|
if (!$_token or $token !== $_token) { |
|
$_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere_ancien'); |
|
if (!$_token or $token !== $_token) { |
|
return false; |
|
} |
|
} |
|
|
|
return [ |
|
'id_auteur' => $id_auteur, |
|
]; |
|
} |
|
|
|
/** |
|
* Décrire un token de prévisu en session |
|
* @uses verifier_token_previsu() |
|
* @return bool|array |
|
*/ |
|
function decrire_token_previsu() { |
|
static $desc = null; |
|
if (is_null($desc)) { |
|
if ($token = _request('var_previewtoken')) { |
|
$desc = verifier_token_previsu($token); |
|
} else { |
|
$desc = false; |
|
} |
|
} |
|
return $desc; |
|
}
|
|
|