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])) {
188 if (!$exec = _request('exec') or !autoriser_sans_cookie($exec)){
189 include_spip('inc/acces');
191 if (empty($GLOBALS['meta'][$alea])){
192 include_spip('inc/minipres');
194 spip_log("$alea indisponible");
199 include_spip('auth/sha256.inc');
200 $sha[$id_auteur . $pass . $alea] = _nano_sha256($id_auteur . $pass . @$GLOBALS['meta'][$alea]);
202 if (function_exists('sha1')) {
203 return sha1($action . $sha[$id_auteur . $pass . $alea]);
205 return md5($action . $sha[$id_auteur . $pass . $alea]);
210 * Calculer le hash qui signe une action pour un auteur
212 * @param string $action
213 * @param int|null $id_auteur
216 function calculer_action_auteur($action, $id_auteur = null) {
217 list($id_auteur, $pass) = caracteriser_auteur($id_auteur);
219 return _action_auteur($action, $id_auteur, $pass, 'alea_ephemere');
224 * Verifier le hash de signature d'une action
225 * toujours exclusivement pour l'auteur en cours
231 function verifier_action_auteur($action, $hash) {
232 list($id_auteur, $pass) = caracteriser_auteur();
233 if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere')) {
236 if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere_ancien')) {
244 // Des fonctions independantes du visiteur, qui permettent de controler
245 // par exemple que l'URL d'un document a la bonne cle de lecture
249 * Renvoyer le secret du site, et le generer si il n'existe pas encore
250 * Le secret du site doit rester aussi secret que possible, et est eternel
251 * On ne doit pas l'exporter
255 function secret_du_site() {
256 if (!isset($GLOBALS['meta']['secret_du_site'])) {
257 include_spip('base/abstract_sql');
258 $GLOBALS['meta']['secret_du_site'] = sql_getfetsel('valeur', 'spip_meta', "nom='secret_du_site'");
260 if (!isset($GLOBALS['meta']['secret_du_site'])
261 or (strlen($GLOBALS['meta']['secret_du_site']) < 64)
263 include_spip('inc/acces');
264 include_spip('auth/sha256.inc');
265 ecrire_meta('secret_du_site',
266 _nano_sha256($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SERVER_SIGNATURE"] . creer_uniqid()), 'non');
267 lire_metas(); // au cas ou ecrire_meta() ne fonctionne pas
270 return $GLOBALS['meta']['secret_du_site'];
274 * Calculer une signature valable pour une action et pour le site
276 * @param string $action
279 function calculer_cle_action($action) {
280 if (function_exists('sha1')) {
281 return sha1($action . secret_du_site());
283 return md5($action . secret_du_site());
288 * Verifier la cle de signature d'une action valable pour le site
290 * @param string $action
294 function verifier_cle_action($action, $cle) {
295 return ($cle == calculer_cle_action($action));
300 * Calculer le token de prévisu
302 * Il permettra de transmettre une URL publique d’un élément non encore publié,
303 * pour qu’une personne tierce le relise. Valable quelques temps.
305 * @see verifier_token_previsu()
306 * @param string $url Url à autoriser en prévisu
307 * @param int|null id_auteur qui génère le token de prévisu. Null utilisera auteur courant.
308 * @param string $alea Nom de l’alea à utiliser
309 * @return string Token, de la forme "{id}*{hash}"
311 function calculer_token_previsu($url, $id_auteur = null, $alea = 'alea_ephemere') {
312 if (is_null($id_auteur)) {
313 if (!empty($GLOBALS['visiteur_session']['id_auteur'])) {
314 $id_auteur = $GLOBALS['visiteur_session']['id_auteur'];
317 if (!$id_auteur = intval($id_auteur)) {
320 // On nettoie l’URL de tous les var_.
321 $url = nettoyer_uri_var($url);
323 $token = _action_auteur('previsualiser-' . $url, $id_auteur, null, $alea);
324 return "$id_auteur-$token";
329 * Vérifie un token de prévisu
331 * Découpe le token pour avoir l’id_auteur,
332 * Retrouve à partir de l’url un objet/id_objet en cours de parcours
333 * Recrée un token pour l’auteur et l’objet trouvé et le compare au token.
335 * @see calculer_token_previsu()
336 * @param string $token Token, de la forme '{id}*{hash}'
337 * @return false|array
338 * - `False` si echec,
339 * + Tableau (id auteur, type d’objet, id_objet) sinon.
341 function verifier_token_previsu($token) {
342 // retrouver auteur / hash
343 $e = explode('-', $token, 2);
344 if (count($e) == 2 and is_numeric(reset($e))) {
345 $id_auteur = intval(reset($e));
350 // calculer le type et id de l’url actuelle
351 include_spip('inc/urls');
352 include_spip('inc/filtres_mini');
353 $url = url_absolue(self());
356 $_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere');
357 if (!$_token or $token !== $_token) {
358 $_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere_ancien');
359 if (!$_token or $token !== $_token) {
365 'id_auteur' => $id_auteur,
370 * Décrire un token de prévisu en session
371 * @uses verifier_token_previsu()
374 function decrire_token_previsu() {
376 if (is_null($desc)) {
377 if ($token = _request('var_previewtoken')) {
378 $desc = verifier_token_previsu($token);