spip_nursit/ecrire/inc/securiser_action.php
2023-06-01 17:30:12 +02:00

384 lines
No EOL
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/***************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2019 *
* Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
* Pour plus de details 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 &amp; (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;
}
}
/**
* 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
* http://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 &amp; (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 array('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("'", '&#39;', $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 = array();
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 array($r[1], '');
// Necessaire aux forums anonymes.
// Pour le reste, ca echouera.
} else {
return array('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] = array($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] = array($t['id_auteur'], $t['pass']);
}
include_spip('inc/minipres');
echo minipres();
exit;
} // Visiteur anonyme, pour ls forums par exemple
else {
return array('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 = array();
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] = _nano_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',
_nano_sha256($_SERVER["DOCUMENT_ROOT"] . $_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 dun élément non encore publié,
* pour quune 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 lalea à 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 lURL 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 lid_auteur,
* Retrouve à partir de lurl un objet/id_objet en cours de parcours
* Recrée un token pour lauteur et lobjet 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 dobjet, 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 lurl 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 array(
'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;
}