[SPIP] ~v3.2.4-->v3.2.5
[lhc/web/www.git] / www / ecrire / inc / securiser_action.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
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 \***************************************************************************/
12
13 /**
14 * Gestion des actions sécurisées
15 *
16 * @package SPIP\Core\Actions
17 **/
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22
23 /**
24 * Génère ou vérifie une action sécurisée
25 *
26 * Interface d'appel:
27 *
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.
30 *
31 * @uses securiser_action_auteur() Pour produire l'URL ou le formulaire
32 * @example
33 * Tester une action reçue et obtenir son argument :
34 * ```
35 * $securiser_action = charger_fonction('securiser_action');
36 * $arg = $securiser_action();
37 * ```
38 *
39 * @param string $action
40 * @param string $arg
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 &amp; (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
49 * @param bool $public
50 * @return array|string
51 */
52 function inc_securiser_action_dist($action = '', $arg = '', $redirect = "", $mode = false, $att = '', $public = false) {
53 if ($action) {
54 return securiser_action_auteur($action, $arg, $redirect, $mode, $att, $public);
55 } else {
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)) {
60 return $arg;
61 }
62 include_spip('inc/minipres');
63 echo minipres();
64 exit;
65 }
66 }
67
68 /**
69 * Retourne une URL ou un formulaire sécurisés
70 *
71 * @note
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
75 *
76 * @uses calculer_action_auteur()
77 * @uses generer_form_action()
78 *
79 * @param string $action
80 * @param string $arg
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 &amp; (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
89 * @param bool $public
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.
94 */
95 function securiser_action_auteur($action, $arg, $redirect = "", $mode = false, $att = '', $public = false) {
96
97 // mode URL ou array
98 if (!is_string($mode)) {
99 $hash = calculer_action_auteur("$action-$arg", is_numeric($att) ? $att : null);
100
101 $r = rawurlencode($redirect);
102 if ($mode === -1) {
103 return array('action' => $action, 'arg' => $arg, 'hash' => $hash);
104 } else {
105 return generer_url_action($action, "arg=" . rawurlencode($arg) . "&hash=$hash" . (!$r ? '' : "&redirect=$r"),
106 $mode, $public);
107 }
108 }
109
110 // mode formulaire
111 $hash = calculer_action_auteur("$action-$arg");
112 $att .= " style='margin: 0px; border: 0px'";
113 if ($redirect) {
114 $redirect = "\n\t\t<input name='redirect' type='hidden' value='" . str_replace("'", '&#39;', $redirect) . "' />";
115 }
116 $mode .= $redirect . "
117 <input name='hash' type='hidden' value='$hash' />
118 <input name='arg' type='hidden' value='$arg' />";
119
120 return generer_form_action($action, $mode, $att, $public);
121 }
122
123 /**
124 * Caracteriser un auteur : l'auteur loge si $id_auteur=null
125 *
126 * @param int|null $id_auteur
127 * @return array
128 */
129 function caracteriser_auteur($id_auteur = null) {
130 static $caracterisation = array();
131
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))
138 ) {
139 return array($r[1], '');
140 // Necessaire aux forums anonymes.
141 // Pour le reste, ca echouera.
142 } else {
143 return array('0', '');
144 }
145 }
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']);
151 }
152 }
153
154 if (isset($caracterisation[$id_auteur])) {
155 return $caracterisation[$id_auteur];
156 }
157
158 if ($id_auteur) {
159 include_spip('base/abstract_sql');
160 $t = sql_fetsel("id_auteur, pass", "spip_auteurs", "id_auteur=$id_auteur");
161 if ($t) {
162 return $caracterisation[$id_auteur] = array($t['id_auteur'], $t['pass']);
163 }
164 include_spip('inc/minipres');
165 echo minipres();
166 exit;
167 } // Visiteur anonyme, pour ls forums par exemple
168 else {
169 return array('0', '');
170 }
171 }
172
173 /**
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
177 *
178 * @param string $action
179 * @param int $id_auteur
180 * @param string $pass
181 * @param string $alea
182 * @return string
183 */
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');
190 charger_aleas();
191 if (empty($GLOBALS['meta'][$alea])){
192 include_spip('inc/minipres');
193 echo minipres();
194 spip_log("$alea indisponible");
195 exit;
196 }
197 }
198 }
199 include_spip('auth/sha256.inc');
200 $sha[$id_auteur . $pass . $alea] = _nano_sha256($id_auteur . $pass . @$GLOBALS['meta'][$alea]);
201 }
202 if (function_exists('sha1')) {
203 return sha1($action . $sha[$id_auteur . $pass . $alea]);
204 } else {
205 return md5($action . $sha[$id_auteur . $pass . $alea]);
206 }
207 }
208
209 /**
210 * Calculer le hash qui signe une action pour un auteur
211 *
212 * @param string $action
213 * @param int|null $id_auteur
214 * @return string
215 */
216 function calculer_action_auteur($action, $id_auteur = null) {
217 list($id_auteur, $pass) = caracteriser_auteur($id_auteur);
218
219 return _action_auteur($action, $id_auteur, $pass, 'alea_ephemere');
220 }
221
222
223 /**
224 * Verifier le hash de signature d'une action
225 * toujours exclusivement pour l'auteur en cours
226 *
227 * @param $action
228 * @param $hash
229 * @return bool
230 */
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')) {
234 return true;
235 }
236 if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere_ancien')) {
237 return true;
238 }
239
240 return false;
241 }
242
243 //
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
246 //
247
248 /**
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
252 *
253 * @return string
254 */
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'");
259 }
260 if (!isset($GLOBALS['meta']['secret_du_site'])
261 or (strlen($GLOBALS['meta']['secret_du_site']) < 64)
262 ) {
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
268 }
269
270 return $GLOBALS['meta']['secret_du_site'];
271 }
272
273 /**
274 * Calculer une signature valable pour une action et pour le site
275 *
276 * @param string $action
277 * @return string
278 */
279 function calculer_cle_action($action) {
280 if (function_exists('sha1')) {
281 return sha1($action . secret_du_site());
282 } else {
283 return md5($action . secret_du_site());
284 }
285 }
286
287 /**
288 * Verifier la cle de signature d'une action valable pour le site
289 *
290 * @param string $action
291 * @param string $cle
292 * @return bool
293 */
294 function verifier_cle_action($action, $cle) {
295 return ($cle == calculer_cle_action($action));
296 }
297
298
299 /**
300 * Calculer le token de prévisu
301 *
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.
304 *
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}"
310 */
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'];
315 }
316 }
317 if (!$id_auteur = intval($id_auteur)) {
318 return "";
319 }
320 // On nettoie l’URL de tous les var_.
321 $url = nettoyer_uri_var($url);
322
323 $token = _action_auteur('previsualiser-' . $url, $id_auteur, null, $alea);
324 return "$id_auteur-$token";
325 }
326
327
328 /**
329 * Vérifie un token de prévisu
330 *
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.
334 *
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.
340 */
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));
346 } else {
347 return false;
348 }
349
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());
354
355 // verifier le token
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) {
360 return false;
361 }
362 }
363
364 return array(
365 'id_auteur' => $id_auteur,
366 );
367 }
368
369 /**
370 * Décrire un token de prévisu en session
371 * @uses verifier_token_previsu()
372 * @return bool|array
373 */
374 function decrire_token_previsu() {
375 static $desc = null;
376 if (is_null($desc)) {
377 if ($token = _request('var_previewtoken')) {
378 $desc = verifier_token_previsu($token);
379 } else {
380 $desc = false;
381 }
382 }
383 return $desc;
384 }