Completed
Push — master ( d8d4cc...6b056b )
by cam
07:55
created

securiser_action.php ➔ verifier_token_previsu()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 4
nop 1
dl 0
loc 27
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Systeme de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright (c) 2001-2017                                                *
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)) {
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;
0 ignored issues
show
Coding Style Compatibility introduced by
The function inc_securiser_action_dist() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
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
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...
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);
0 ignored issues
show
Bug introduced by
It seems like $mode defined by parameter $mode on line 95 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...
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 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...
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) {
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...
162
			return $caracterisation[$id_auteur] = array($t['id_auteur'], $t['pass']);
163
		}
164
		include_spip('inc/minipres');
165
		echo minipres();
166
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function caracteriser_auteur() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
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
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...
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;
0 ignored issues
show
Coding Style Compatibility introduced by
The function _action_auteur() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
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
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...
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')) {
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...
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');
0 ignored issues
show
Bug introduced by
It seems like _nano_sha256($_SERVER['D...URE'] . creer_uniqid()) targeting _nano_sha256() can also be of type boolean; however, ecrire_meta() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
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
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...
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
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...
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
}