[SPIP][PLUGINS] v3.0-->v3.2
[lhc/web/www.git] / www / ecrire / inc / securiser_action.php
index 1968538..91eabf1 100644 (file)
@@ -3,45 +3,62 @@
 /***************************************************************************\
  *  SPIP, Systeme de publication pour l'internet                           *
  *                                                                         *
- *  Copyright (c) 2001-2016                                                *
+ *  Copyright (c) 2001-2017                                                *
  *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
  *                                                                         *
  *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
  *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
 \***************************************************************************/
 
-if (!defined('_ECRIRE_INC_VERSION')) return;
+/**
+ * Gestion des actions sécurisées
+ *
+ * @package SPIP\Core\Actions
+ **/
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+       return;
+}
 
 /**
- * interface d'appel:
- * - au moins un argument: retourne une URL ou un formulaire securises
- * - sans argument: verifie la securite et retourne _request('arg'), ou exit.
+ * Génère ou vérifie une action sécurisée
  *
- * http://doc.spip.org/@inc_securiser_action_dist
+ * Interface d'appel:
+ *
+ * - au moins un argument: retourne une URL ou un formulaire securisés
+ * - sans argument : vérifie la sécurité et retourne `_request('arg')`, ou exit.
+ *
+ * @uses securiser_action_auteur() Pour produire l'URL ou le formulaire
+ * @example
+ *     Tester une action reçue et obtenir son argument :
+ *     ```
+ *     $securiser_action = charger_fonction('securiser_action');
+ *     $arg = $securiser_action();
+ *     ```
  *
  * @param string $action
  * @param string $arg
  * @param string $redirect
  * @param bool|int|string $mode
- *   -1 : renvoyer action, arg et hash sous forme de array()
- *   true ou false : renvoyer une url, avec & (false) ou & (true)
- *   string : renvoyer un formulaire
+ *   - -1 : renvoyer action, arg et hash sous forme de array()
+ *   true ou false : renvoyer une url, avec & (false) ou & (true)
+ *   string : renvoyer un formulaire
  * @param string|int $att
  *   id_auteur pour lequel generer l'action en mode url ou array()
  *   atributs du formulaire en mode formulaire
  * @param bool $public
  * @return array|string
  */
-function inc_securiser_action_dist($action='', $arg='', $redirect="", $mode=false, $att='', $public=false)
-{
-       if ($action)
+function inc_securiser_action_dist($action = '', $arg = '', $redirect = "", $mode = false, $att = '', $public = false) {
+       if ($action) {
                return securiser_action_auteur($action, $arg, $redirect, $mode, $att, $public);
-       else {
+       else {
                $arg = _request('arg');
                $hash = _request('hash');
-               $action = _request('action')?_request('action'):_request('formulaire_action');
-               if ($a = verifier_action_auteur("$action-$arg", $hash))
+               $action = _request('action') ? _request('action') : _request('formulaire_action');
+               if ($a = verifier_action_auteur("$action-$arg", $hash)) {
                        return $arg;
+               }
                include_spip('inc/minipres');
                echo minipres();
                exit;
@@ -49,41 +66,53 @@ function inc_securiser_action_dist($action='', $arg='', $redirect="", $mode=fals
 }
 
 /**
- * Attention: PHP applique urldecode sur $_GET mais pas sur $_POST
- * cf http://fr.php.net/urldecode#48481
- * http://doc.spip.org/@securiser_action_auteur
+ * Retourne une URL ou un formulaire sécurisés
+ *
+ * @note
+ *   Attention: PHP applique urldecode sur $_GET mais pas sur $_POST
+ *   cf http://fr.php.net/urldecode#48481
+ *   http://code.spip.net/@securiser_action_auteur
+ *
+ * @uses calculer_action_auteur()
+ * @uses generer_form_action()
  *
  * @param string $action
  * @param string $arg
  * @param string $redirect
  * @param bool|int|string $mode
- *   -1 : renvoyer action, arg et hash sous forme de array()
- *   true ou false : renvoyer une url, avec & (false) ou & (true)
- *   string : renvoyer un formulaire
+ *   - -1 : renvoyer action, arg et hash sous forme de array()
+ *   true ou false : renvoyer une url, avec & (false) ou & (true)
+ *   string : renvoyer un formulaire
  * @param string|int $att
- *   id_auteur pour lequel generer l'action en mode url ou array()
- *   atributs du formulaire en mode formulaire
+ *   - id_auteur pour lequel générer l'action en mode URL ou array()
+ *   atributs du formulaire en mode formulaire
  * @param bool $public
  * @return array|string
+ *    - string URL, si $mode = true ou false,
+ *    - string code HTML du formulaire, si $mode texte,
+ *    - array Tableau (action=>x, arg=>x, hash=>x) si $mode=-1.
  */
-function securiser_action_auteur($action, $arg, $redirect="", $mode=false, $att='', $public=false) {
+function securiser_action_auteur($action, $arg, $redirect = "", $mode = false, $att = '', $public = false) {
 
        // mode URL ou array
-       if (!is_string($mode)){
-               $hash = calculer_action_auteur("$action-$arg",is_numeric($att)?$att:null);
+       if (!is_string($mode)) {
+               $hash = calculer_action_auteur("$action-$arg", is_numeric($att) ? $att : null);
 
                $r = rawurlencode($redirect);
-               if ($mode===-1)
-                       return array('action'=>$action,'arg'=>$arg,'hash'=>$hash);
-               else
-                       return generer_url_action($action, "arg=$arg&hash=$hash" . (!$r ? '' : "&redirect=$r"), $mode, $public);
+               if ($mode === -1) {
+                       return array('action' => $action, 'arg' => $arg, 'hash' => $hash);
+               } else {
+                       return generer_url_action($action, "arg=" . rawurlencode($arg) . "&hash=$hash" . (!$r ? '' : "&redirect=$r"),
+                               $mode, $public);
+               }
        }
 
        // mode formulaire
        $hash = calculer_action_auteur("$action-$arg");
        $att .= " style='margin: 0px; border: 0px'";
-       if ($redirect)
-               $redirect = "\n\t\t<input name='redirect' type='hidden' value='". str_replace("'", '&#39;', $redirect) ."' />";
+       if ($redirect) {
+               $redirect = "\n\t\t<input name='redirect' type='hidden' value='" . str_replace("'", '&#39;', $redirect) . "' />";
+       }
        $mode .= $redirect . "
 <input name='hash' type='hidden' value='$hash' />
 <input name='arg' type='hidden' value='$arg' />";
@@ -94,46 +123,50 @@ function securiser_action_auteur($action, $arg, $redirect="", $mode=false, $att=
 /**
  * Caracteriser un auteur : l'auteur loge si $id_auteur=null
  *
- * http://doc.spip.org/@caracteriser_auteur
- *
  * @param int|null $id_auteur
  * @return array
  */
-function caracteriser_auteur($id_auteur=null) {
+function caracteriser_auteur($id_auteur = null) {
        static $caracterisation = array();
 
-       if (is_null($id_auteur) AND !isset($GLOBALS['visiteur_session']['id_auteur'])) {
-       // si l'auteur courant n'est pas connu alors qu'il peut demander une action
-       // c'est une connexion par php_auth ou 1 instal, on se rabat sur le cookie.
-       // S'il n'avait pas le droit de realiser cette action, le hash sera faux.
+       if (is_null($id_auteur) and !isset($GLOBALS['visiteur_session']['id_auteur'])) {
+               // si l'auteur courant n'est pas connu alors qu'il peut demander une action
+               // c'est une connexion par php_auth ou 1 instal, on se rabat sur le cookie.
+               // S'il n'avait pas le droit de realiser cette action, le hash sera faux.
                if (isset($_COOKIE['spip_session'])
-               AND (preg_match('/^(\d+)/',$_COOKIE['spip_session'],$r))) {
-                         return array($r[1], '');
-                         // Necessaire aux forums anonymes.
-                         // Pour le reste, ca echouera.
-               } else return array('0','');
+                       and (preg_match('/^(\d+)/', $_COOKIE['spip_session'], $r))
+               ) {
+                       return array($r[1], '');
+                       // Necessaire aux forums anonymes.
+                       // Pour le reste, ca echouera.
+               } else {
+                       return array('0', '');
+               }
        }
        // Eviter l'acces SQL si le pass est connu de PHP
-       if (is_null($id_auteur)){
-               $id_auteur = isset($GLOBALS['visiteur_session']['id_auteur'])?$GLOBALS['visiteur_session']['id_auteur']:0;
-               if (isset($GLOBALS['visiteur_session']['pass']) AND $GLOBALS['visiteur_session']['pass'])
+       if (is_null($id_auteur)) {
+               $id_auteur = isset($GLOBALS['visiteur_session']['id_auteur']) ? $GLOBALS['visiteur_session']['id_auteur'] : 0;
+               if (isset($GLOBALS['visiteur_session']['pass']) and $GLOBALS['visiteur_session']['pass']) {
                        return $caracterisation[$id_auteur] = array($id_auteur, $GLOBALS['visiteur_session']['pass']);
+               }
        }
 
-       if (isset($caracterisation[$id_auteur])) return $caracterisation[$id_auteur];
+       if (isset($caracterisation[$id_auteur])) {
+               return $caracterisation[$id_auteur];
+       }
 
        if ($id_auteur) {
                include_spip('base/abstract_sql');
                $t = sql_fetsel("id_auteur, pass", "spip_auteurs", "id_auteur=$id_auteur");
-               if ($t)
+               if ($t) {
                        return $caracterisation[$id_auteur] = array($t['id_auteur'], $t['pass']);
+               }
                include_spip('inc/minipres');
                echo minipres();
                exit;
-       }
-       // Visiteur anonyme, pour ls forums par exemple
+       } // Visiteur anonyme, pour ls forums par exemple
        else {
-               return array('0','');
+               return array('0', '');
        }
 }
 
@@ -142,8 +175,6 @@ function caracteriser_auteur($id_auteur=null) {
  * utilisee pour generer des urls personelles pour executer une action qui modifie la base
  * et verifier la legitimite de l'appel a l'action
  *
- * http://doc.spip.org/@_action_auteur
- *
  * @param string $action
  * @param int $id_auteur
  * @param string $pass
@@ -152,11 +183,11 @@ function caracteriser_auteur($id_auteur=null) {
  */
 function _action_auteur($action, $id_auteur, $pass, $alea) {
        static $sha = array();
-       if (!isset($sha[$id_auteur.$pass.$alea])){
-               if (!isset($GLOBALS['meta'][$alea]) AND _request('exec')!=='install') {
-                       include_spip('base/abstract_sql');
-                       $GLOBALS['meta'][$alea] = sql_getfetsel('valeur', 'spip_meta', "nom=" . sql_quote($alea));
-                       if (!($GLOBALS['meta'][$alea])) {
+       if (!isset($sha[$id_auteur . $pass . $alea])) {
+               if (!isset($GLOBALS['meta'][$alea]) and _request('exec') !== 'install') {
+                       include_spip('inc/acces');
+                       charger_aleas();
+                       if (empty($GLOBALS['meta'][$alea])) {
                                include_spip('inc/minipres');
                                echo minipres();
                                spip_log("$alea indisponible");
@@ -164,24 +195,25 @@ function _action_auteur($action, $id_auteur, $pass, $alea) {
                        }
                }
                include_spip('auth/sha256.inc');
-               $sha[$id_auteur.$pass.$alea] = _nano_sha256($id_auteur.$pass.@$GLOBALS['meta'][$alea]);
+               $sha[$id_auteur . $pass . $alea] = _nano_sha256($id_auteur . $pass . @$GLOBALS['meta'][$alea]);
+       }
+       if (function_exists('sha1')) {
+               return sha1($action . $sha[$id_auteur . $pass . $alea]);
+       } else {
+               return md5($action . $sha[$id_auteur . $pass . $alea]);
        }
-       if (function_exists('sha1'))
-               return sha1($action.$sha[$id_auteur.$pass.$alea]);
-       else
-               return md5($action.$sha[$id_auteur.$pass.$alea]);
 }
 
 /**
  * Calculer le hash qui signe une action pour un auteur
- * http://doc.spip.org/@calculer_action_auteur
  *
  * @param string $action
  * @param int|null $id_auteur
  * @return string
  */
-function calculer_action_auteur($action, $id_auteur=null) {
+function calculer_action_auteur($action, $id_auteur = null) {
        list($id_auteur, $pass) = caracteriser_auteur($id_auteur);
+
        return _action_auteur($action, $id_auteur, $pass, 'alea_ephemere');
 }
 
@@ -189,7 +221,6 @@ function calculer_action_auteur($action, $id_auteur=null) {
 /**
  * Verifier le hash de signature d'une action
  * toujours exclusivement pour l'auteur en cours
- * http://doc.spip.org/@verifier_action_auteur
  *
  * @param $action
  * @param $hash
@@ -197,10 +228,13 @@ function calculer_action_auteur($action, $id_auteur=null) {
  */
 function verifier_action_auteur($action, $hash) {
        list($id_auteur, $pass) = caracteriser_auteur();
-       if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere'))
+       if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere')) {
                return true;
-       if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere_ancien'))
+       }
+       if ($hash == _action_auteur($action, $id_auteur, $pass, 'alea_ephemere_ancien')) {
                return true;
+       }
+
        return false;
 }
 
@@ -214,42 +248,42 @@ function verifier_action_auteur($action, $hash) {
  * Le secret du site doit rester aussi secret que possible, et est eternel
  * On ne doit pas l'exporter
  *
- * http://doc.spip.org/@secret_du_site
- *
  * @return string
  */
 function secret_du_site() {
-       if (!isset($GLOBALS['meta']['secret_du_site'])){
+       if (!isset($GLOBALS['meta']['secret_du_site'])) {
                include_spip('base/abstract_sql');
                $GLOBALS['meta']['secret_du_site'] = sql_getfetsel('valeur', 'spip_meta', "nom='secret_du_site'");
        }
        if (!isset($GLOBALS['meta']['secret_du_site'])
-         OR (strlen($GLOBALS['meta']['secret_du_site'])<64)) {
+               or (strlen($GLOBALS['meta']['secret_du_site']) < 64)
+       ) {
                include_spip('inc/acces');
                include_spip('auth/sha256.inc');
-               ecrire_meta('secret_du_site', _nano_sha256($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SERVER_SIGNATURE"] . creer_uniqid()), 'non');
+               ecrire_meta('secret_du_site',
+                       _nano_sha256($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SERVER_SIGNATURE"] . creer_uniqid()), 'non');
                lire_metas(); // au cas ou ecrire_meta() ne fonctionne pas
        }
+
        return $GLOBALS['meta']['secret_du_site'];
 }
 
 /**
  * Calculer une signature valable pour une action et pour le site
- * http://doc.spip.org/@calculer_cle_action
  *
  * @param string $action
  * @return string
  */
 function calculer_cle_action($action) {
-       if (function_exists('sha1'))
+       if (function_exists('sha1')) {
                return sha1($action . secret_du_site());
-       else
+       } else {
                return md5($action . secret_du_site());
+       }
 }
 
 /**
  * Verifier la cle de signature d'une action valable pour le site
- * http://doc.spip.org/@verifier_cle_action
  *
  * @param string $action
  * @param string $cle
@@ -259,4 +293,90 @@ function verifier_cle_action($action, $cle) {
        return ($cle == calculer_cle_action($action));
 }
 
-?>
+
+/**
+ * Calculer le token de prévisu
+ *
+ * Il permettra de transmettre une URL publique d’un élément non encore publié,
+ * pour qu’une personne tierce le relise. Valable quelques temps.
+ *
+ * @see verifier_token_previsu()
+ * @param string $url Url à autoriser en prévisu
+ * @param int|null id_auteur qui génère le token de prévisu. Null utilisera auteur courant.
+ * @param string $alea Nom de l’alea à utiliser
+ * @return string Token, de la forme "{id}*{hash}"
+ */
+function calculer_token_previsu($url, $id_auteur = null, $alea = 'alea_ephemere') {
+       if (is_null($id_auteur)) {
+               if (!empty($GLOBALS['visiteur_session']['id_auteur'])) {
+                       $id_auteur = $GLOBALS['visiteur_session']['id_auteur'];
+               }
+       }
+       if (!$id_auteur = intval($id_auteur)) {
+               return "";
+       }
+       // On nettoie l’URL de tous les var_.
+       $url = nettoyer_uri_var($url);
+
+       $token = _action_auteur('previsualiser-' . $url, $id_auteur, null, $alea);
+       return "$id_auteur-$token";
+}
+
+
+/**
+ * Vérifie un token de prévisu
+ *
+ * Découpe le token pour avoir l’id_auteur,
+ * Retrouve à partir de l’url un objet/id_objet en cours de parcours
+ * Recrée un token pour l’auteur et l’objet trouvé et le compare au token.
+ *
+ * @see calculer_token_previsu()
+ * @param string $token Token, de la forme '{id}*{hash}'
+ * @return false|array
+ *     - `False` si echec,
+ *     + Tableau (id auteur, type d’objet, id_objet) sinon.
+ */
+function verifier_token_previsu($token) {
+       // retrouver auteur / hash
+       $e = explode('-', $token, 2);
+       if (count($e) == 2 and is_numeric(reset($e))) {
+               $id_auteur = intval(reset($e));
+       } else {
+               return false;
+       }
+
+       // calculer le type et id de l’url actuelle
+       include_spip('inc/urls');
+       include_spip('inc/filtres_mini');
+       $url = url_absolue(self());
+
+       // verifier le token
+       $_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere');
+       if (!$_token or $token !== $_token) {
+               $_token = calculer_token_previsu($url, $id_auteur, 'alea_ephemere_ancien');
+               if (!$_token or $token !== $_token) {
+                       return false;
+               }
+       }
+
+       return array(
+               'id_auteur' => $id_auteur,
+       );
+}
+
+/**
+ * Décrire un token de prévisu en session
+ * @uses verifier_token_previsu()
+ * @return bool|array
+ */
+function decrire_token_previsu() {
+       static $desc = null;
+       if (is_null($desc)) {
+               if ($token = _request('var_previewtoken')) {
+                       $desc = verifier_token_previsu($token);
+               } else {
+                       $desc = false;
+               }
+       }
+       return $desc;
+}
\ No newline at end of file