Changeset 84562 in spip-zone


Ignore:
Timestamp:
Sep 10, 2014, 11:18:30 PM (5 years ago)
Author:
marcimat@…
Message:

Proposition de modification du fonctionnement de api_docrestreint (qui décide de l'affichage ou non d'un document pour un visiteur).

Le code est réécrit pour limiter l'indentation (if ... return)
Factorisation de quelques parties appelées fréquemment.

On place dans un objet toutes les infos connues du document à analyser ;
Cet objet traverse un nouveau pipeline 'accesrestreint_pre_vue_document';
si le 'status' dans l'objet est renseigné après ce passage,
il est utilisé pour ce document.

Sinon, c'est le mécanisme précédent qui s'applique,
à savoir : si ce document n'est pas présent dans spip_documents => 404, et s'il est
présent, il faut l'autorisation de le voir, par clé d'action ou par autoriser().

Un autre pipeline est aussi créé (peut être inutile ?) nommé 'accesrestreint_repertoires_toujours_autorises'
permettant de définir des sous répertoires de IMG dont on autorise systématiquement l'accès.

Ces deux pipelines (n'hésitez pas à trouver de meilleurs nommages) devraient permettre
de gérer plus finement l'accès restreint aux documents qui sont hors de la médiathèque.
Actuellement ils étaient systématiquement refusés, hormis ceux du sous répertoire 'nl' (newsletter).

Cela devrait permettre de gérer des documents IMG/truc/muche.tld qui nécessitent par exemple simplement d'être identifié
pour être vus. De même, le plugin newsletter pourrait maintenant indiquer que son répertoire est ouvert, soit avec
le pipeline 'accesrestreint_pre_vue_document', soit avec 'accesrestreint_repertoires_toujours_autorises' si on le garde,
évitant ce petit code en dur dans accès restreint.

Location:
_plugins_/acces_restreint/trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • _plugins_/acces_restreint/trunk/action/api_docrestreint.php

    r84557 r84562  
    2525 * docrestreint.api/id/cle/file
    2626 *
     27 * @pipeline_appel accesrestreint_repertoires_toujours_autorises
     28 * @pipeline_appel accesrestreint_pre_vue_document
     29 *
    2730 * @param null $arg
    2831 */
     
    4346                return;
    4447        }
     48
    4549
    4650        // Séparer les 3 arguments
     
    5054        $f           = implode("/", $arg);
    5155
    52         /**
    53          * URL de test de fonctionnement
    54          * @see accesrestreint_gerer_htaccess()
    55          */
    56         if ($id_document==0 AND $cle==1 AND $f=="test/.test") {
     56        // Objet reprenant toutes les infos connues
     57        $Document = new Accesrestreint_document($f, $id_document, $cle);
     58
     59        // Simple test de fonctionnement ?
     60        if ($Document->est_un_test()) {
    5761                echo "OK";
    5862                return;
    5963        }
    6064
    61         include_spip('inc/documents');
    62 
    63         $file = get_spip_doc($f);
    64         spip_log($file, 'dbg');
    65 
    66         // securite : on refuse tout ../ ou url absolue
    67         if ((strpos($f, '../') !== false) OR (preg_match(',^\w+://,', $f))) {
    68                 accesrestreint_afficher_erreur_document(403);
    69                 return;
    70         }
    71 
    72         // inexistant ou illisible : 404
    73         if (!file_exists($file) OR !is_readable($file)) {
    74                 accesrestreint_afficher_erreur_document(404);
    75                 return;
    76         }
    77 
    78         $status = $doc = false;
    79         $dossiers_a_exclure = array('nl');
    80 
    81         // Si c'est dans un sous-dossier explicitement utilisé pour autre chose que les documents
    82         // (exemple : les newsletters)
    83         // et bien on ne teste pas l'accès
    84         if (preg_match('%^(' . join('|', $dossiers_a_exclure) . ')/%', $f)){
    85                 $status = 200;
    86         }
    87         else {
    88                 $where = "documents.fichier=".sql_quote(set_spip_doc($file))
    89                 . ($id_document ? " AND documents.id_document=".intval($id_document): '');
    90                 spip_log($where,'dbg');
    91 
    92                 $doc = sql_fetsel("documents.id_document, documents.titre, documents.fichier, types.mime_type, types.inclus, documents.extension", "spip_documents AS documents LEFT JOIN spip_types_documents AS types ON documents.extension=types.extension",$where);
    93                 spip_log($doc,'dbg');
    94                 if (!$doc) {
    95                         $status = 404;
    96                 }
    97                 else {
    98 
    99                         // ETag pour gerer le status 304
    100                         $ETag = md5($file . ': '. filemtime($file));
    101                         if (isset($_SERVER['HTTP_IF_NONE_MATCH'])
    102                           AND $_SERVER['HTTP_IF_NONE_MATCH'] == $ETag) {
    103                                 http_status(304); // Not modified
    104                                 exit;
    105                         }
    106                         else {
    107                                 header('ETag: '.$ETag);
    108                         }
    109 
    110                         //
    111                         // Verifier les droits de lecture du document
    112 
    113                         // en controlant la cle passee en argument si elle est dispo
    114                         // (perf issue : toutes les urls ont en principe cette cle fournie dans la page au moment du calcul de la page)
    115                         if ($cle){
    116                                 include_spip('inc/securiser_action');
    117                                 if (!verifier_cle_action($doc['id_document'].','.$f, $cle)) {
    118                                         spip_log("acces interdit $cle erronee");
    119                                         $status = 403;
    120                                 }
    121                         }
    122                         // en verifiant le droit explicitement sinon, plus lent !
    123                         else {
    124                                 if (!function_exists("autoriser"))
    125                                         include_spip("inc/autoriser");
    126                                 if (!autoriser('voir', 'document', $doc['id_document'])) {
    127                                         $status = 403;
    128                                         spip_log("acces interdit $cle erronee");
    129                                 }
    130                         }
    131                 }
    132         }
    133 
    134 
    135         switch($status) {
    136 
    137         case 403:
    138                 accesrestreint_afficher_erreur_document(403);
    139                 break;
    140 
    141         case 404:
    142                 accesrestreint_afficher_erreur_document(404);
    143                 break;
    144 
    145         default:
    146                 header("Content-Type: ". $doc['mime_type']);
    147                 // pour les images ne pas passer en attachment
    148                 // sinon, lorsqu'on pointe directement sur leur adresse,
    149                 // le navigateur les downloade au lieu de les afficher
    150 
    151                 if ($doc['inclus']=='non') {
    152 
    153                         $f = basename($file);
    154                         // ce content-type est necessaire pour eviter des corruptions de zip dans ie6
    155                         header('Content-Type: application/octet-stream');
    156 
    157                         header("Content-Disposition: attachment; filename=\"$f\";");
    158                         header("Content-Transfer-Encoding: binary");
    159 
    160                         // fix for IE catching or PHP bug issue
    161                         header("Pragma: public");
    162                         header("Expires: 0"); // set expiration time
    163                         header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    164 
    165                 }
    166                 else {
    167                         header("Expires: 3600"); // set expiration time
    168                 }
    169 
    170                 if ($cl = filesize($file))
    171                         header("Content-Length: ". $cl);
    172 
    173                 readfile($file);
    174                 break;
    175         }
    176 
     65        // Insécurisé ( chemin ../ ou absolu ) : 403
     66        if ($Document->est_insecurise()) {
     67                $Document->status = 403;
     68                accesrestreint_afficher_document_selon_status($Document);
     69                return;
     70        }
     71
     72        // inexistant ou non lisible : 404
     73        if (!$Document->est_existant() OR !$Document->est_lisible()) {
     74                $Document->status = 404;
     75                accesrestreint_afficher_document_selon_status($Document);
     76                return;
     77        }
     78
     79        /**
     80         * Liste les sous répertoires de IMG qui donnent accès aux fichiers
     81         * systématiquement (sans autorisations particulières).
     82        **/
     83        $repertoires_autorises = pipeline('accesrestreint_repertoires_toujours_autorises', array('nl'));
     84
     85        if ($Document->est_dans_repertoire($repertoires_autorises)) {
     86                $Document->statut = 200;
     87                accesrestreint_afficher_document_selon_status($Document);
     88                return;
     89        }
     90
     91        /**
     92         * Propose à des plugins de modifier le Document
     93         *
     94         * Particulièrement pour donner le status http qui convient pour ce document.
     95         *
     96         * Si ce 'status' est renseigné par un plugin : on affiche le document (ou l'erreur) avec le statut trouvé
     97         *
     98         * Sinon, il sera calculé automatiquement ensuite :
     99         *
     100         * - si le document ne fait pas partie de spip_documents : 404
     101         * - sinon, test d'autorisation de voir ce document (par clé d'action si indiquée, sinon par l'api autoriser)
     102        **/
     103        $Document = pipeline('accesrestreint_pre_vue_document', $Document);
     104
     105        if ($Document->status) {
     106                accesrestreint_afficher_document_selon_status($Document);
     107                return;
     108        }
     109
     110        // Un document sans 'status' doit être analysé
     111        // Est-ce un document SPIP ? Non = 404
     112        $doc = $Document->get_spip_document();
     113
     114        if (!$doc) {
     115                $Document->status = 404;
     116                accesrestreint_afficher_document_selon_status($Document);
     117                return;
     118        }
     119
     120
     121        // ETag pour gerer le status 304
     122        $ETag = $Document->get_Etag();
     123
     124        if (isset($_SERVER['HTTP_IF_NONE_MATCH'])
     125          AND $_SERVER['HTTP_IF_NONE_MATCH'] == $ETag) {
     126                $Document->status = 304; // Not modified
     127                accesrestreint_afficher_document_selon_status($Document);
     128                exit;
     129        }
     130
     131        header('ETag: ' . $ETag);
     132
     133        // Verifier les droits de lecture du document
     134        if (!$Document->est_autorise()) {
     135                $Document->status = 403;
     136        }
     137
     138        // envoyer le document
     139        accesrestreint_afficher_document_selon_status($Document);
    177140}
    178141
     
    196159
    197160/**
     161 * Envoie le document au navigateur, en fonction de son status http
     162 *
     163 * @uses accesrestreint_afficher_erreur_document()
     164 * @uses accesrestreint_afficher_document()
     165 *
     166 * @param Accesrestreint_document $Document
     167 * @return void
     168**/
     169function accesrestreint_afficher_document_selon_status(Accesrestreint_document $Document) {
     170        switch ($Document->status) {
     171
     172                case 304:
     173                case 403:
     174                case 404:
     175                        accesrestreint_afficher_erreur_document($Document->status);
     176                        break;
     177
     178                default:
     179                        accesrestreint_afficher_document($Document);
     180                        break;
     181        }
     182}
     183
     184
     185/**
    198186 * Affiche une page indiquant un document introuvable ou interdit
    199187 *
     
    206194        switch ($status)
    207195        {
     196                case 304:
     197                        // not modified : sortir de suite !
     198                        http_status(304);
     199                        exit;
     200
    208201                case 403:
    209202                        include_spip('inc/minipres');
     
    218211        }
    219212}
     213
     214/**
     215 * Envoie le document au navigateur
     216 *
     217 * Soit en document attaché, soit en direct
     218 *
     219 * @param Accesrestreint_document $Document
     220 * @return void
     221**/
     222function accesrestreint_afficher_document(Accesrestreint_document $Document) {
     223
     224        $chemin_fichier = $Document->get_chemin_complet_fichier();
     225
     226        // document décrit dans la table spip_documents ?
     227        if ($doc = $Document->get_spip_document()) {
     228                if ($doc['mime_type']) {
     229                        header("Content-Type: " . $doc['mime_type']);
     230                }
     231
     232                // pour les images ne pas passer en attachment
     233                // sinon, lorsqu'on pointe directement sur leur adresse,
     234                // le navigateur les downloade au lieu de les afficher
     235                if ($doc['inclus'] == 'non') {
     236                        $f = basename($chemin_fichier);
     237                        // ce content-type est necessaire pour eviter des corruptions de zip dans ie6
     238                        header('Content-Type: application/octet-stream');
     239
     240                        header("Content-Disposition: attachment; filename=\"$f\";");
     241                        header("Content-Transfer-Encoding: binary");
     242
     243                        // fix for IE catching or PHP bug issue
     244                        header("Pragma: public");
     245                        header("Expires: 0"); // set expiration time
     246                        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
     247                } else {
     248                        header("Expires: 3600"); // set expiration time
     249                }
     250        } else {
     251                header("Expires: 3600"); // set expiration time
     252        }
     253
     254
     255        if ($size = filesize($chemin_fichier)) {
     256                header("Content-Length: ". $size);
     257        }
     258
     259        readfile($chemin_fichier);
     260}
     261
     262
     263/**
     264 * Décrit un document qu'un utilisateur cherche à afficher.
     265 *
     266 * En aura t'il le droit ?
     267**/
     268class Accesrestreint_document {
     269
     270        /**
     271         * Identifiant du document demandé, si connu
     272         *
     273         * 0 si document inconnu.
     274         *
     275         * @var int
     276        **/
     277        private $id_document = 0;
     278
     279        /**
     280         * Clé d'action auteur, si connue
     281         *
     282         * 0 si inconnue
     283         *
     284         * @var int|string
     285        **/
     286        private $cle_action = 0;
     287
     288        /**
     289         * Chemin du fichier demandé, depuis IMG/
     290         *
     291         * @var string
     292        **/
     293        private $_fichier = "";
     294
     295        /**
     296         * Status HTTP à utiliser pour ce document
     297         *
     298         * @var string|int
     299        **/
     300        public $status = "";
     301
     302
     303        /**
     304         * Constructeur
     305         *
     306         * @param int $_fichier
     307         *     Chemin du fichier demandé, depuis IMG/
     308         * @param int $id_document
     309         *     Identifiant du document, si connu
     310         * @param int|string $cle_action
     311         *     Clé d'action auteur, si connue
     312         * @return
     313        **/
     314        public function __construct($_fichier, $id_document = 0, $cle_action = 0) {
     315                $this->_fichier = $_fichier;
     316                $this->id_document = $id_document;
     317                $this->cle_action  = $cle_action;
     318        }
     319
     320        /**
     321         * Test si le document demandé vérifie simplement le fonctionnement correct du .htaccess dans IMG posé par Acces Restreint
     322         *
     323         * @see accesrestreint_gerer_htaccess()
     324         *
     325         * return bool
     326         *     True si document de test
     327         */
     328        public function est_un_test() {
     329                return  $this->id_document == 0
     330                        AND $this->cle_action == 1
     331                        AND $this->_fichier == "test/.test";
     332        }
     333
     334
     335        /**
     336         * Test si le document demandé n'a pas un chemin d'accès sécurisé
     337         *
     338         * Par exemple avec ../ ou url absolue http://
     339         *
     340         * @return bool
     341         *     True si le chemin est insécurisé
     342        **/
     343        public function est_insecurise() {
     344                return (strpos($this->_fichier, '../') !== false)
     345                        OR (preg_match(',^\w+://,', $this->_fichier));
     346        }
     347
     348        /**
     349         * Test si le fichier existe
     350         *
     351         * @return bool True si existe
     352        **/
     353        public function est_existant() {
     354                $chemin_fichier = $this->get_chemin_complet_fichier();
     355                return file_exists($chemin_fichier);
     356        }
     357
     358        /**
     359         * Test si le fichier est accessible en lecture
     360         *
     361         * @return bool True si lisible
     362        **/
     363        public function est_lisible() {
     364                $chemin_fichier = $this->get_chemin_complet_fichier();
     365                return is_readable($chemin_fichier);
     366        }
     367
     368
     369        /**
     370         * Test si un document est dans un sous-répertoire à la racine de IMG
     371         *
     372         * @param string|array $repertoires
     373         *     Nom d'un ou plusieurs répertoires
     374         * @return bool
     375         *     True si le document est contenu dans un de ces répertoires
     376        **/
     377        public function est_dans_repertoire($repertoires) {
     378                if (!$repertoires) {
     379                        return false;
     380                }
     381                if (is_string($repertoires)) {
     382                        $repertoires = array($repertoires);
     383                }
     384                $repertoires = array_filter($repertoires);
     385
     386                return preg_match('%^(' . implode('|', $repertoires) . ')/%', $this->_fichier);
     387        }
     388
     389
     390        /**
     391         * Test l'autorisation de voir ce document (spip)
     392         *
     393         * @return bool True si on peut voir le document
     394        **/
     395        public function est_autorise() {
     396                $doc = $this->get_spip_document();
     397                if (!$doc) {
     398                        spip_log("acces interdit, document hors de spip_documents");
     399                        return false;
     400                }
     401
     402                // en controlant la cle passee en argument si elle est dispo
     403                // (perf issue : toutes les urls ont en principe cette cle fournie dans la page au moment du calcul de la page)
     404                if ($this->cle_action){
     405                        include_spip('inc/securiser_action');
     406                        if (!verifier_cle_action($doc['id_document'].','.$this->_fichier, $this->cle_action)) {
     407                                spip_log("acces interdit $this->cle_action erronee");
     408                                return false;
     409                        }
     410                        return true;
     411                }
     412               
     413                // en verifiant le droit explicitement sinon, plus lent !
     414                if (!function_exists("autoriser")) {
     415                        include_spip("inc/autoriser");
     416                }
     417
     418                if (!autoriser('voir', 'document', $doc['id_document'])) {
     419                        spip_log("acces interdit, pas autorise a voir le document " . $doc['id_document']);
     420                        return false;
     421                }
     422
     423                return true;
     424        }
     425
     426
     427        /**
     428         * Retourne la description du document dans la table spip_documents (et spip_types_documents)
     429         *
     430         * Seulement si ce fichier est un document dans SPIP
     431         *
     432         * @return array Description du document dans SPIP
     433        **/
     434        public function get_spip_document() {
     435                static $doc = null;
     436                if (is_null($doc)) {
     437                        include_spip('inc/documents');
     438                        $where = "documents.fichier = ".sql_quote(set_spip_doc($this->get_chemin_complet_fichier()))
     439                                . ($this->id_document ? " AND documents.id_document=".intval($this->id_document): '');
     440                        spip_log($where, 'dbg');
     441
     442                        $doc = sql_fetsel(
     443                                "documents.id_document, documents.titre, documents.fichier, types.mime_type, types.inclus, documents.extension",
     444                                "spip_documents AS documents LEFT JOIN spip_types_documents AS types ON documents.extension=types.extension",
     445                                $where
     446                        );
     447
     448                        spip_log($doc, 'dbg');
     449                        if (!$doc) {
     450                                $doc = array();
     451                        }
     452                }
     453                return $doc;
     454        }
     455
     456        /**
     457         * Calcule et retourne le chemin complet  du fichier (depuis la racine du site)
     458         *
     459         * @return string
     460        **/
     461        public function get_chemin_complet_fichier() {
     462                static $fichier = null;
     463                if (is_null($fichier)) {
     464                        include_spip('inc/documents');
     465                        $fichier = get_spip_doc($this->_fichier);
     466                        spip_log($fichier, 'dbg');
     467                }
     468                return $fichier;
     469        }
     470
     471        /**
     472         * Calcule et retourne le hash Etag du fichier
     473         *
     474         * @return string
     475        **/
     476        public function get_ETag() {
     477                static $ETag = null;
     478                if (is_null($ETag)) {
     479                        $ETag = md5($this->get_chemin_complet_fichier() . ': '. filemtime($this->get_chemin_complet_fichier()));
     480                }
     481                return $ETag;
     482        }
     483
     484}
  • _plugins_/acces_restreint/trunk/paquet.xml

    r84550 r84562  
    22        prefix="accesrestreint"
    33        categorie="auteur"
    4         version="3.9.3"
     4        version="3.10.0"
    55        etat="dev"
    66        compatibilite="[3.0.0;3.0.*]"
     
    3232        <pipeline nom="autoriser" inclure="inc/accesrestreint_autoriser.php" />
    3333
     34        <!-- IMG/xx/... toujours accessibles en lecture -->
     35        <pipeline nom="accesrestreint_repertoires_toujours_autorises" action="" />
     36        <!-- Avant d'analyser si un fichier demandé doit être envoyé au navigateur -->
     37        <pipeline nom="accesrestreint_pre_vue_document" action="" />
     38
    3439        <menu nom="zones" titre="accesrestreint:icone_menu_config" parent="menu_publication" icone="images/zone-16.png" />
    3540
Note: See TracChangeset for help on using the changeset viewer.