[SPIP] v3.2.1-->v3.2.2
[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]) and _request('exec') !== 'install') {
188 include_spip('inc/acces');
189 charger_aleas();
190 if (empty($GLOBALS['meta'][$alea])) {
191 include_spip('inc/minipres');
192 echo minipres();
193 spip_log("$alea indisponible");
194 exit;
195 }
196 }
197 include_spip('auth/sha256.inc');
198 $sha[$id_auteur . $pass . $alea] = _nano_sha256($id_auteur . $pass . @$GLOBALS['meta'][$alea]);
199 }
200 if (function_exists('sha1')) {
201 return sha1($action . $sha[$id_auteur . $pass . $alea]);
202 } else {
203 return md5($action . $sha[$id_auteur . $pass . $alea]);
204 }
205 }
206
207 /**
208 * Calculer le hash qui signe une action pour un auteur
209 *
210 * @param string $action
211 * @param int|null $id_auteur
212 * @return string
213 */
214 function calculer_action_auteur($action, $id_auteur = null) {
215 list($id_auteur, $pass) = caracteriser_auteur($id_auteur);
216
217 return _action_auteur($action, $id_auteur, $pass, 'alea_ephemere');
218 }
219
220
221 /**
222 * Verifier le hash de signature d'une action
223 * toujours exclusivement pour l'auteur en cours
224 *
225 * @param $action
226 * @param $hash
227 * @return bool
228 */
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')) {
232 return true;
233 }
234 if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere_ancien')) {
235 return true;
236 }
237
238 return false;
239 }
240
241 //
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
244 //
245
246 /**
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
250 *
251 * @return string
252 */
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'");
257 }
258 if (!isset($GLOBALS['meta']['secret_du_site'])
259 or (strlen($GLOBALS['meta']['secret_du_site']) < 64)
260 ) {
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
266 }
267
268 return $GLOBALS['meta']['secret_du_site'];
269 }
270
271 /**
272 * Calculer une signature valable pour une action et pour le site
273 *
274 * @param string $action
275 * @return string
276 */
277 function calculer_cle_action($action) {
278 if (function_exists('sha1')) {
279 return sha1($action . secret_du_site());
280 } else {
281 return md5($action . secret_du_site());
282 }
283 }
284
285 /**
286 * Verifier la cle de signature d'une action valable pour le site
287 *
288 * @param string $action
289 * @param string $cle
290 * @return bool
291 */
292 function verifier_cle_action($action, $cle) {
293 return ($cle == calculer_cle_action($action));
294 }
295
296
297 /**
298 * Calculer le token de prévisu
299 *
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.
302 *
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}"
308 */
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'];
313 }
314 }
315 if (!$id_auteur = intval($id_auteur)) {
316 return "";
317 }
318 // On nettoie l’URL de tous les var_.
319 $url = nettoyer_uri_var($url);
320
321 $token = _action_auteur('previsualiser-' . $url, $id_auteur, null, $alea);
322 return "$id_auteur-$token";
323 }
324
325
326 /**
327 * Vérifie un token de prévisu
328 *
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.
332 *
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.
338 */
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));
344 } else {
345 return false;
346 }
347
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());
352
353 // verifier le token
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) {
358 return false;
359 }
360 }
361
362 return array(
363 'id_auteur' => $id_auteur,
364 );
365 }
366
367 /**
368 * Décrire un token de prévisu en session
369 * @uses verifier_token_previsu()
370 * @return bool|array
371 */
372 function decrire_token_previsu() {
373 static $desc = null;
374 if (is_null($desc)) {
375 if ($token = _request('var_previewtoken')) {
376 $desc = verifier_token_previsu($token);
377 } else {
378 $desc = false;
379 }
380 }
381 return $desc;
382 }