Completed
Push — master ( 2e55e3...8b905c )
by cam
04:29
created

securiser_action.php ➔ demander_confirmation_avant_action()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 3
dl 0
loc 26
rs 9.504
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Système de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright © avec tendresse depuis 2001                                 *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
10
 *  Pour plus de détails 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)) {
0 ignored issues
show
Unused Code introduced by
$a is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
60
			return $arg;
61
		}
62
		include_spip('inc/minipres');
63
		echo minipres();
64
		exit;
65
	}
66
}
67
68
/**
69
 * Confirmer avant suppression si on arrive par un bouton action
70
 * a appeler dans la fonction action avant toute action destructrice
71
 *
72
 * demander_confirmation_avant_action("Supprimer l'article xxxx", "Oui je veux le supprimer");
73
 *
74
 * L'action affiche le formulaire de demande de confirmation sans rendre la main au premier appel,
75
 * si l'utilisateur clique, cela relance l'action avec un confirm et quand on repasse ici, la fonction ne fera rien et l'action se finira normalement
76
 *
77
 * @param string $titre
78
 * @param string $titre_bouton
79
 * @param string|null $url_action
80
 * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
81
 */
82
function demander_confirmation_avant_action($titre, $titre_bouton, $url_action=null) {
83
84
	if (!$url_action) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url_action of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
85
		$url_action = self();
86
		$action = _request('action');
87
		$url_action = parametre_url($url_action, 'action', $action, '&');
88
	}
89
	else {
90
		$action = parametre_url($url_action, 'action');
91
	}
92
93
	$arg = parametre_url($url_action, 'arg');
94
	$confirm = md5("$action:$arg:".realpath(__FILE__));
95
	if (_request('confirm_action') === $confirm) {
96
		return true;
97
	}
98
99
	$url_confirm = parametre_url($url_action, "confirm_action", $confirm, '&');
100
	include_spip("inc/filtres");
101
	$bouton_action = bouton_action($titre_bouton, $url_confirm);
0 ignored issues
show
Bug introduced by
It seems like $url_confirm defined by parametre_url($url_actio...action', $confirm, '&') on line 99 can also be of type array<integer,string> or null; however, bouton_action() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
102
	$corps = "<div style='text-align:center;'>$bouton_action</div>";
103
104
	include_spip("inc/minipres");
105
	echo minipres($titre,$corps);
106
	exit;
107
}
108
109
/**
110
 * Retourne une URL ou un formulaire sécurisés
111
 *
112
 * @note
113
 *   Attention: PHP applique urldecode sur $_GET mais pas sur $_POST
114
 *   cf http://fr.php.net/urldecode#48481
115
 *   https://code.spip.net/@securiser_action_auteur
116
 *
117
 * @uses calculer_action_auteur()
118
 * @uses generer_form_action()
119
 *
120
 * @param string $action
121
 * @param string $arg
122
 * @param string $redirect
123
 * @param bool|int|string $mode
124
 *   - -1 : renvoyer action, arg et hash sous forme de array()
125
 *   - true ou false : renvoyer une url, avec &amp; (false) ou & (true)
126
 *   - string : renvoyer un formulaire
127
 * @param string|int $att
128
 *   - id_auteur pour lequel générer l'action en mode URL ou array()
129
 *   - atributs du formulaire en mode formulaire
130
 * @param bool $public
131
 * @return array|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string|null>|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
132
 *    - string URL, si $mode = true ou false,
133
 *    - string code HTML du formulaire, si $mode texte,
134
 *    - array Tableau (action=>x, arg=>x, hash=>x) si $mode=-1.
135
 */
136
function securiser_action_auteur($action, $arg, $redirect = "", $mode = false, $att = '', $public = false) {
137
138
	// mode URL ou array
139
	if (!is_string($mode)) {
140
		$hash = calculer_action_auteur("$action-$arg", is_numeric($att) ? $att : null);
141
142
		$r = rawurlencode($redirect);
143
		if ($mode === -1) {
144
			return array('action' => $action, 'arg' => $arg, 'hash' => $hash);
145
		} else {
146
			return generer_url_action($action, "arg=" . rawurlencode($arg) . "&hash=$hash" . (!$r ? '' : "&redirect=$r"),
147
				$mode, $public);
0 ignored issues
show
Bug introduced by
It seems like $mode defined by parameter $mode on line 136 can also be of type integer; however, generer_url_action() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
148
		}
149
	}
150
151
	// mode formulaire
152
	$hash = calculer_action_auteur("$action-$arg");
153
	$att .= " style='margin: 0px; border: 0px'";
154
	if ($redirect) {
155
		$redirect = "\n\t\t<input name='redirect' type='hidden' value='" . str_replace("'", '&#39;', $redirect) . "' />";
156
	}
157
	$mode .= $redirect . "
158
<input name='hash' type='hidden' value='$hash' />
159
<input name='arg' type='hidden' value='$arg' />";
160
161
	return generer_form_action($action, $mode, $att, $public);
162
}
163
164
/**
165
 * Caracteriser un auteur : l'auteur loge si $id_auteur=null
166
 *
167
 * @param int|null $id_auteur
168
 * @return array
169
 */
170
function caracteriser_auteur($id_auteur = null) {
171
	static $caracterisation = array();
172
173
	if (is_null($id_auteur) and !isset($GLOBALS['visiteur_session']['id_auteur'])) {
174
		// si l'auteur courant n'est pas connu alors qu'il peut demander une action
175
		// c'est une connexion par php_auth ou 1 instal, on se rabat sur le cookie.
176
		// S'il n'avait pas le droit de realiser cette action, le hash sera faux.
177
		if (isset($_COOKIE['spip_session'])
178
			and (preg_match('/^(\d+)/', $_COOKIE['spip_session'], $r))
179
		) {
180
			return array($r[1], '');
181
			// Necessaire aux forums anonymes.
182
			// Pour le reste, ca echouera.
183
		} else {
184
			return array('0', '');
185
		}
186
	}
187
	// Eviter l'acces SQL si le pass est connu de PHP
188
	if (is_null($id_auteur)) {
189
		$id_auteur = isset($GLOBALS['visiteur_session']['id_auteur']) ? $GLOBALS['visiteur_session']['id_auteur'] : 0;
190 View Code Duplication
		if (isset($GLOBALS['visiteur_session']['pass']) and $GLOBALS['visiteur_session']['pass']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
			return $caracterisation[$id_auteur] = array($id_auteur, $GLOBALS['visiteur_session']['pass']);
192
		}
193
	}
194
195
	if (isset($caracterisation[$id_auteur])) {
196
		return $caracterisation[$id_auteur];
197
	}
198
199
	if ($id_auteur) {
200
		include_spip('base/abstract_sql');
201
		$t = sql_fetsel("id_auteur, pass", "spip_auteurs", "id_auteur=$id_auteur");
202
		if ($t) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $t of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
203
			return $caracterisation[$id_auteur] = array($t['id_auteur'], $t['pass']);
204
		}
205
		include_spip('inc/minipres');
206
		echo minipres();
207
		exit;
208
	} // Visiteur anonyme, pour ls forums par exemple
209
	else {
210
		return array('0', '');
211
	}
212
}
213
214
/**
215
 * Calcule une cle securisee pour une action et un auteur donnes
216
 * utilisee pour generer des urls personelles pour executer une action qui modifie la base
217
 * et verifier la legitimite de l'appel a l'action
218
 *
219
 * @param string $action
220
 * @param int $id_auteur
221
 * @param string $pass
222
 * @param string $alea
223
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
224
 */
225
function _action_auteur($action, $id_auteur, $pass, $alea) {
226
	static $sha = array();
227
	if (!isset($sha[$id_auteur . $pass . $alea])) {
228
		if (!isset($GLOBALS['meta'][$alea])) {
229
			if (!$exec = _request('exec') or !autoriser_sans_cookie($exec)){
230
				include_spip('inc/acces');
231
				charger_aleas();
232
				if (empty($GLOBALS['meta'][$alea])){
233
					include_spip('inc/minipres');
234
					echo minipres();
235
					spip_log("$alea indisponible");
236
					exit;
237
				}
238
			}
239
		}
240
		include_spip('auth/sha256.inc');
241
		$sha[$id_auteur . $pass . $alea] = spip_sha256($id_auteur . $pass . @$GLOBALS['meta'][$alea]);
242
	}
243
	if (function_exists('sha1')) {
244
		return sha1($action . $sha[$id_auteur . $pass . $alea]);
245
	} else {
246
		return md5($action . $sha[$id_auteur . $pass . $alea]);
247
	}
248
}
249
250
/**
251
 * Calculer le hash qui signe une action pour un auteur
252
 *
253
 * @param string $action
254
 * @param int|null $id_auteur
255
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
256
 */
257
function calculer_action_auteur($action, $id_auteur = null) {
258
	list($id_auteur, $pass) = caracteriser_auteur($id_auteur);
259
260
	return _action_auteur($action, $id_auteur, $pass, 'alea_ephemere');
261
}
262
263
264
/**
265
 * Verifier le hash de signature d'une action
266
 * toujours exclusivement pour l'auteur en cours
267
 *
268
 * @param $action
269
 * @param $hash
270
 * @return bool
271
 */
272
function verifier_action_auteur($action, $hash) {
273
	list($id_auteur, $pass) = caracteriser_auteur();
274
	if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere')) {
275
		return true;
276
	}
277
	if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere_ancien')) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $hash == _action_...alea_ephemere_ancien');.
Loading history...
278
		return true;
279
	}
280
281
	return false;
282
}
283
284
//
285
// Des fonctions independantes du visiteur, qui permettent de controler
286
// par exemple que l'URL d'un document a la bonne cle de lecture
287
//
288
289
/**
290
 * Renvoyer le secret du site, et le generer si il n'existe pas encore
291
 * Le secret du site doit rester aussi secret que possible, et est eternel
292
 * On ne doit pas l'exporter
293
 *
294
 * @return string
295
 */
296
function secret_du_site() {
297
	if (!isset($GLOBALS['meta']['secret_du_site'])) {
298
		include_spip('base/abstract_sql');
299
		$GLOBALS['meta']['secret_du_site'] = sql_getfetsel('valeur', 'spip_meta', "nom='secret_du_site'");
300
	}
301
	if (!isset($GLOBALS['meta']['secret_du_site'])
302
		or (strlen($GLOBALS['meta']['secret_du_site']) < 64)
303
	) {
304
		include_spip('inc/acces');
305
		include_spip('auth/sha256.inc');
306
		ecrire_meta('secret_du_site',
307
			spip_sha256(
308
				$_SERVER["DOCUMENT_ROOT"] 
309
				. (isset($_SERVER['SERVER_SIGNATURE']) ? $_SERVER["SERVER_SIGNATURE"] : "")
310
				. creer_uniqid()
311
			), 'non');
312
		lire_metas(); // au cas ou ecrire_meta() ne fonctionne pas
313
	}
314
315
	return $GLOBALS['meta']['secret_du_site'];
316
}
317
318
/**
319
 * Calculer une signature valable pour une action et pour le site
320
 *
321
 * @param string $action
322
 * @return string
323
 */
324
function calculer_cle_action($action) {
325
	if (function_exists('sha1')) {
326
		return sha1($action . secret_du_site());
327
	} else {
328
		return md5($action . secret_du_site());
329
	}
330
}
331
332
/**
333
 * Verifier la cle de signature d'une action valable pour le site
334
 *
335
 * @param string $action
336
 * @param string $cle
337
 * @return bool
338
 */
339
function verifier_cle_action($action, $cle) {
340
	return ($cle == calculer_cle_action($action));
341
}
342
343
344
/**
345
 * Calculer le token de prévisu
346
 *
347
 * Il permettra de transmettre une URL publique d’un élément non encore publié,
348
 * pour qu’une personne tierce le relise. Valable quelques temps.
349
 *
350
 * @see verifier_token_previsu()
351
 * @param string $url Url à autoriser en prévisu
352
 * @param int|null id_auteur qui génère le token de prévisu. Null utilisera auteur courant.
353
 * @param string $alea Nom de l’alea à utiliser
354
 * @return string Token, de la forme "{id}*{hash}"
355
 */
356
function calculer_token_previsu($url, $id_auteur = null, $alea = 'alea_ephemere') {
357
	if (is_null($id_auteur)) {
358
		if (!empty($GLOBALS['visiteur_session']['id_auteur'])) {
359
			$id_auteur = $GLOBALS['visiteur_session']['id_auteur'];
360
		}
361
	}
362
	if (!$id_auteur = intval($id_auteur)) {
363
		return "";
364
	}
365
	// On nettoie l’URL de tous les var_.
366
	$url = nettoyer_uri_var($url);
367
368
	$token = _action_auteur('previsualiser-' . $url, $id_auteur, null, $alea);
369
	return "$id_auteur-$token";
370
}
371
372
373
/**
374
 * Vérifie un token de prévisu
375
 *
376
 * Découpe le token pour avoir l’id_auteur,
377
 * Retrouve à partir de l’url un objet/id_objet en cours de parcours
378
 * Recrée un token pour l’auteur et l’objet trouvé et le compare au token.
379
 *
380
 * @see calculer_token_previsu()
381
 * @param string $token Token, de la forme '{id}*{hash}'
382
 * @return false|array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array<string,integer>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
383
 *     - `False` si echec,
384
 *     + Tableau (id auteur, type d’objet, id_objet) sinon.
385
 */
386
function verifier_token_previsu($token) {
387
	// retrouver auteur / hash
388
	$e = explode('-', $token, 2);
389
	if (count($e) == 2 and is_numeric(reset($e))) {
390
		$id_auteur = intval(reset($e));
391
	} else {
392
		return false;
393
	}
394
395
	// calculer le type et id de l’url actuelle
396
	include_spip('inc/urls');
397
	include_spip('inc/filtres_mini');
398
	$url = url_absolue(self());
399
400
	// verifier le token
401
	$_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere');
402
	if (!$_token or $token !== $_token) {
403
		$_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere_ancien');
404
		if (!$_token or $token !== $_token) {
405
			return false;
406
		}
407
	}
408
409
	return array(
410
		'id_auteur' => $id_auteur,
411
	);
412
}
413
414
/**
415
 * Décrire un token de prévisu en session
416
 * @uses verifier_token_previsu()
417
 * @return bool|array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array<string,integer>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
418
 */
419
function decrire_token_previsu() {
420
	static $desc = null;
421
	if (is_null($desc)) {
422
		if ($token = _request('var_previewtoken')) {
423
			$desc = verifier_token_previsu($token);
424
		} else {
425
			$desc = false;
426
		}
427
	}
428
	return $desc;
429
}