3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
14 * Gestion des actions sécurisées
16 * @package SPIP\Core\Actions
19 if (!defined('_ECRIRE_INC_VERSION')) {
24 * Génère ou vérifie une action sécurisée
28 * - au moins un argument: retourne une URL ou un formulaire securisés
29 * - sans argument : vérifie la sécurité et retourne `_request('arg')`, ou exit.
31 * @uses securiser_action_auteur() Pour produire l'URL ou le formulaire
33 * Tester une action reçue et obtenir son argument :
35 * $securiser_action = charger_fonction('securiser_action');
36 * $arg = $securiser_action();
39 * @param string $action
41 * @param string $redirect
42 * @param bool|int|string $mode
43 * - -1 : renvoyer action, arg et hash sous forme de array()
44 * - true ou false : renvoyer une url, avec & (false) ou & (true)
45 * - string : renvoyer un formulaire
46 * @param string|int $att
47 * id_auteur pour lequel generer l'action en mode url ou array()
48 * atributs du formulaire en mode formulaire
50 * @return array|string
52 function inc_securiser_action_dist($action = '', $arg = '', $redirect = "", $mode = false, $att = '', $public = false) {
54 return securiser_action_auteur($action, $arg, $redirect, $mode, $att, $public);
56 $arg = _request('arg');
57 $hash = _request('hash');
58 $action = _request('action') ?
_request('action') : _request('formulaire_action');
59 if ($a = verifier_action_auteur("$action-$arg", $hash)) {
62 include_spip('inc/minipres');
69 * Retourne une URL ou un formulaire sécurisés
72 * Attention: PHP applique urldecode sur $_GET mais pas sur $_POST
73 * cf http://fr.php.net/urldecode#48481
74 * http://code.spip.net/@securiser_action_auteur
76 * @uses calculer_action_auteur()
77 * @uses generer_form_action()
79 * @param string $action
81 * @param string $redirect
82 * @param bool|int|string $mode
83 * - -1 : renvoyer action, arg et hash sous forme de array()
84 * - true ou false : renvoyer une url, avec & (false) ou & (true)
85 * - string : renvoyer un formulaire
86 * @param string|int $att
87 * - id_auteur pour lequel générer l'action en mode URL ou array()
88 * - atributs du formulaire en mode formulaire
90 * @return array|string
91 * - string URL, si $mode = true ou false,
92 * - string code HTML du formulaire, si $mode texte,
93 * - array Tableau (action=>x, arg=>x, hash=>x) si $mode=-1.
95 function securiser_action_auteur($action, $arg, $redirect = "", $mode = false, $att = '', $public = false) {
98 if (!is_string($mode)) {
99 $hash = calculer_action_auteur("$action-$arg", is_numeric($att) ?
$att : null);
101 $r = rawurlencode($redirect);
103 return array('action' => $action, 'arg' => $arg, 'hash' => $hash);
105 return generer_url_action($action, "arg=" . rawurlencode($arg) . "&hash=$hash" . (!$r ?
'' : "&redirect=$r"),
111 $hash = calculer_action_auteur("$action-$arg");
112 $att .= " style='margin: 0px; border: 0px'";
114 $redirect = "\n\t\t<input name='redirect' type='hidden' value='" . str_replace("'", ''', $redirect) . "' />";
116 $mode .= $redirect . "
117 <input name='hash' type='hidden' value='$hash' />
118 <input name='arg' type='hidden' value='$arg' />";
120 return generer_form_action($action, $mode, $att, $public);
124 * Caracteriser un auteur : l'auteur loge si $id_auteur=null
126 * @param int|null $id_auteur
129 function caracteriser_auteur($id_auteur = null) {
130 static $caracterisation = array();
132 if (is_null($id_auteur) and !isset($GLOBALS['visiteur_session']['id_auteur'])) {
133 // si l'auteur courant n'est pas connu alors qu'il peut demander une action
134 // c'est une connexion par php_auth ou 1 instal, on se rabat sur le cookie.
135 // S'il n'avait pas le droit de realiser cette action, le hash sera faux.
136 if (isset($_COOKIE['spip_session'])
137 and (preg_match('/^(\d+)/', $_COOKIE['spip_session'], $r))
139 return array($r[1], '');
140 // Necessaire aux forums anonymes.
141 // Pour le reste, ca echouera.
143 return array('0', '');
146 // Eviter l'acces SQL si le pass est connu de PHP
147 if (is_null($id_auteur)) {
148 $id_auteur = isset($GLOBALS['visiteur_session']['id_auteur']) ?
$GLOBALS['visiteur_session']['id_auteur'] : 0;
149 if (isset($GLOBALS['visiteur_session']['pass']) and $GLOBALS['visiteur_session']['pass']) {
150 return $caracterisation[$id_auteur] = array($id_auteur, $GLOBALS['visiteur_session']['pass']);
154 if (isset($caracterisation[$id_auteur])) {
155 return $caracterisation[$id_auteur];
159 include_spip('base/abstract_sql');
160 $t = sql_fetsel("id_auteur, pass", "spip_auteurs", "id_auteur=$id_auteur");
162 return $caracterisation[$id_auteur] = array($t['id_auteur'], $t['pass']);
164 include_spip('inc/minipres');
167 } // Visiteur anonyme, pour ls forums par exemple
169 return array('0', '');
174 * Calcule une cle securisee pour une action et un auteur donnes
175 * utilisee pour generer des urls personelles pour executer une action qui modifie la base
176 * et verifier la legitimite de l'appel a l'action
178 * @param string $action
179 * @param int $id_auteur
180 * @param string $pass
181 * @param string $alea
184 function _action_auteur($action, $id_auteur, $pass, $alea) {
185 static $sha = array();
186 if (!isset($sha[$id_auteur . $pass . $alea])) {
187 if (!isset($GLOBALS['meta'][$alea]) and _request('exec') !== 'install') {
188 include_spip('inc/acces');
190 if (empty($GLOBALS['meta'][$alea])) {
191 include_spip('inc/minipres');
193 spip_log("$alea indisponible");
197 include_spip('auth/sha256.inc');
198 $sha[$id_auteur . $pass . $alea] = _nano_sha256($id_auteur . $pass . @$GLOBALS['meta'][$alea]);
200 if (function_exists('sha1')) {
201 return sha1($action . $sha[$id_auteur . $pass . $alea]);
203 return md5($action . $sha[$id_auteur . $pass . $alea]);
208 * Calculer le hash qui signe une action pour un auteur
210 * @param string $action
211 * @param int|null $id_auteur
214 function calculer_action_auteur($action, $id_auteur = null) {
215 list($id_auteur, $pass) = caracteriser_auteur($id_auteur);
217 return _action_auteur($action, $id_auteur, $pass, 'alea_ephemere');
222 * Verifier le hash de signature d'une action
223 * toujours exclusivement pour l'auteur en cours
229 function verifier_action_auteur($action, $hash) {
230 list($id_auteur, $pass) = caracteriser_auteur();
231 if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere')) {
234 if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere_ancien')) {
242 // Des fonctions independantes du visiteur, qui permettent de controler
243 // par exemple que l'URL d'un document a la bonne cle de lecture
247 * Renvoyer le secret du site, et le generer si il n'existe pas encore
248 * Le secret du site doit rester aussi secret que possible, et est eternel
249 * On ne doit pas l'exporter
253 function secret_du_site() {
254 if (!isset($GLOBALS['meta']['secret_du_site'])) {
255 include_spip('base/abstract_sql');
256 $GLOBALS['meta']['secret_du_site'] = sql_getfetsel('valeur', 'spip_meta', "nom='secret_du_site'");
258 if (!isset($GLOBALS['meta']['secret_du_site'])
259 or (strlen($GLOBALS['meta']['secret_du_site']) < 64)
261 include_spip('inc/acces');
262 include_spip('auth/sha256.inc');
263 ecrire_meta('secret_du_site',
264 _nano_sha256($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SERVER_SIGNATURE"] . creer_uniqid()), 'non');
265 lire_metas(); // au cas ou ecrire_meta() ne fonctionne pas
268 return $GLOBALS['meta']['secret_du_site'];
272 * Calculer une signature valable pour une action et pour le site
274 * @param string $action
277 function calculer_cle_action($action) {
278 if (function_exists('sha1')) {
279 return sha1($action . secret_du_site());
281 return md5($action . secret_du_site());
286 * Verifier la cle de signature d'une action valable pour le site
288 * @param string $action
292 function verifier_cle_action($action, $cle) {
293 return ($cle == calculer_cle_action($action));
298 * Calculer le token de prévisu
300 * Il permettra de transmettre une URL publique d’un élément non encore publié,
301 * pour qu’une personne tierce le relise. Valable quelques temps.
303 * @see verifier_token_previsu()
304 * @param string $url Url à autoriser en prévisu
305 * @param int|null id_auteur qui génère le token de prévisu. Null utilisera auteur courant.
306 * @param string $alea Nom de l’alea à utiliser
307 * @return string Token, de la forme "{id}*{hash}"
309 function calculer_token_previsu($url, $id_auteur = null, $alea = 'alea_ephemere') {
310 if (is_null($id_auteur)) {
311 if (!empty($GLOBALS['visiteur_session']['id_auteur'])) {
312 $id_auteur = $GLOBALS['visiteur_session']['id_auteur'];
315 if (!$id_auteur = intval($id_auteur)) {
318 // On nettoie l’URL de tous les var_.
319 $url = nettoyer_uri_var($url);
321 $token = _action_auteur('previsualiser-' . $url, $id_auteur, null, $alea);
322 return "$id_auteur-$token";
327 * Vérifie un token de prévisu
329 * Découpe le token pour avoir l’id_auteur,
330 * Retrouve à partir de l’url un objet/id_objet en cours de parcours
331 * Recrée un token pour l’auteur et l’objet trouvé et le compare au token.
333 * @see calculer_token_previsu()
334 * @param string $token Token, de la forme '{id}*{hash}'
335 * @return false|array
336 * - `False` si echec,
337 * + Tableau (id auteur, type d’objet, id_objet) sinon.
339 function verifier_token_previsu($token) {
340 // retrouver auteur / hash
341 $e = explode('-', $token, 2);
342 if (count($e) == 2 and is_numeric(reset($e))) {
343 $id_auteur = intval(reset($e));
348 // calculer le type et id de l’url actuelle
349 include_spip('inc/urls');
350 include_spip('inc/filtres_mini');
351 $url = url_absolue(self());
354 $_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere');
355 if (!$_token or $token !== $_token) {
356 $_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere_ancien');
357 if (!$_token or $token !== $_token) {
363 'id_auteur' => $id_auteur,
368 * Décrire un token de prévisu en session
369 * @uses verifier_token_previsu()
372 function decrire_token_previsu() {
374 if (is_null($desc)) {
375 if ($token = _request('var_previewtoken')) {
376 $desc = verifier_token_previsu($token);