Overview
Comment:Implémentation définitive vérification de signature à l'import de fichier, et plutôt que bloquer si le membre n'est plus admin, mettre toutes les catégories en admin.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 623defddc25d9f875c95683b9b4921aad3297a80
User & Date: bohwaz on 2017-05-09 07:03:34
Other Links: branch diff | manifest | tags
Context
2017-05-10
07:02
Modernisation des fiches membres + amélioration UX avec des transitions check-in: 2bcf5d3c6a user: bohwaz tags: dev
2017-05-09
07:03
Implémentation définitive vérification de signature à l'import de fichier, et plutôt que bloquer si le membre n'est plus admin, mettre toutes les catégories en admin. check-in: 623defddc2 user: bohwaz tags: dev
06:59
Simplification script mise à jour de la session check-in: b159a9fb16 user: bohwaz tags: dev
Changes

Modified src/include/init.php from [17ebcf2ead] to [4bc86e819b].

85
86
87
88
89
90
91
92
93
94
95
96

97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
    $host = isset($_SERVER['HTTP_HOST']) 
        ? $_SERVER['HTTP_HOST'] 
        : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
    define('Garradin\WWW_URL', 'http' . (!empty($_SERVER['HTTPS']) ? 's' : '') . '://' . $host . WWW_URI);
}

static $default_config = [
    'CACHE_ROOT'       => DATA_ROOT . '/cache',
    'DB_FILE'          => DATA_ROOT . '/association.sqlite',
    'DB_SCHEMA'        => ROOT . '/include/data/schema.sql',
    'PLUGINS_ROOT'     => DATA_ROOT . '/plugins',
    'PREFER_HTTPS'     => false,

    'PLUGINS_SYSTEM'   => '',
    'SHOW_ERRORS'      => false,
    'MAIL_ERRORS'      => false,
    'USE_CRON'         => false,
    'ENABLE_XSENDFILE' => false,
    'SMTP_HOST'        => false,
    'SMTP_USER'        => null,
    'SMTP_PASSWORD'    => null,
    'SMTP_PORT'        => 587,
    'SMTP_SECURITY'    => 'STARTTLS',
];

foreach ($default_config as $const => $value)
{
    $const = sprintf('Garradin\\%s', $const);

    if (!defined($const))







|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
|







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
    $host = isset($_SERVER['HTTP_HOST']) 
        ? $_SERVER['HTTP_HOST'] 
        : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
    define('Garradin\WWW_URL', 'http' . (!empty($_SERVER['HTTPS']) ? 's' : '') . '://' . $host . WWW_URI);
}

static $default_config = [
    'CACHE_ROOT'            => DATA_ROOT . '/cache',
    'DB_FILE'               => DATA_ROOT . '/association.sqlite',
    'DB_SCHEMA'             => ROOT . '/include/data/schema.sql',
    'PLUGINS_ROOT'          => DATA_ROOT . '/plugins',
    'PREFER_HTTPS'          => false,
    'ALLOW_MODIFIED_IMPORT' => true,
    'PLUGINS_SYSTEM'        => '',
    'SHOW_ERRORS'           => false,
    'MAIL_ERRORS'           => false,
    'USE_CRON'              => false,
    'ENABLE_XSENDFILE'      => false,
    'SMTP_HOST'             => false,
    'SMTP_USER'             => null,
    'SMTP_PASSWORD'         => null,
    'SMTP_PORT'             => 587,
    'SMTP_SECURITY'         => 'STARTTLS',
];

foreach ($default_config as $const => $value)
{
    $const = sprintf('Garradin\\%s', $const);

    if (!defined($const))

Modified src/include/lib/Garradin/Sauvegarde.php from [b0e237675e] to [ace718c666].

1
2
3
4
5
6
7


8


9
10
11
12
13
14
15
<?php

namespace Garradin;

class Sauvegarde
{
	const NEED_UPGRADE = 'nu';


	const INTEGRITY_FAIL = 'if';



	/**
	 * Renvoie la liste des fichiers SQLite sauvegardés
	 * @param  boolean $auto Si true ne renvoie que la liste des sauvegardes automatiques
	 * @return array 		 Liste des fichiers
	 */	
	public function getList($auto = false)






|
>
>
|
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace Garradin;

class Sauvegarde
{
	const NEED_UPGRADE = 0x01 << 2;
	const NOT_AN_ADMIN = 0x01 << 3;

	const INTEGRITY_FAIL = 41;
	const NOT_A_DB = 42;
	const NO_APP_ID = 43;

	/**
	 * Renvoie la liste des fichiers SQLite sauvegardés
	 * @param  boolean $auto Si true ne renvoie que la liste des sauvegardes automatiques
	 * @return array 		 Liste des fichiers
	 */	
	public function getList($auto = false)
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214

		if ($check_integrity)
		{
			$integrity = $this->checkIntegrity($file['tmp_name']);

			if ($integrity === null)
			{
				throw new UserException('Le fichier fourni n\'est pas une base de donnée SQLite3.');
			}
			elseif ($integrity === false)
			{
				return self::INTEGRITY_FAIL;
			}
		}

		$r = $this->restoreDB($file['tmp_name'], $user_id);

		if ($r)
		{







|



|







200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

		if ($check_integrity)
		{
			$integrity = $this->checkIntegrity($file['tmp_name']);

			if ($integrity === null)
			{
				throw new UserException('Le fichier fourni n\'est pas une base de donnée SQLite3.', self::NOT_A_DB);
			}
			elseif ($integrity === false)
			{
				throw new UserException('Le fichier fourni a été modifié par un programme externe.', self::INTEGRITY_FAIL);
			}
		}

		$r = $this->restoreDB($file['tmp_name'], $user_id);

		if ($r)
		{
268
269
270
271
272
273
274


275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342


343
344
345
346
347
348
349
350
351
352
353










354
355
356
357
358
359
360
361
362
363
364
365
366
367
	 * Restauration de base de données, la fonction qui le fait vraiment
	 * @param  string $file Chemin absolu vers la base de données à utiliser
	 * @return mixed 		true si rien ne va plus, ou self::NEED_UPGRADE si la version de la DB
	 * ne correspond pas à la version de Garradin (mise à jour nécessaire).
	 */
	protected function restoreDB($file, $user_id = false)
	{


		// Essayons déjà d'ouvrir la base de données à restaurer en lecture
		try {
			$db = new \SQLite3($file, SQLITE3_OPEN_READONLY);
		}
		catch (\Exception $e)
		{
			throw new UserException('Le fichier fourni n\'est pas une base de données valide. ' .
				'Message d\'erreur de SQLite : ' . $e->getMessage());
		}

		try {
			// Regardons ensuite si la base de données n'est pas corrompue
			$check = $db->firstColumn('PRAGMA integrity_check;');
		}
		catch (\Exception $e)
		{
			// Ici SQLite peut rejeter un message type "file is encrypted or is not a db"
			throw new UserException('Le fichier fourni n\'est pas une base de données valide. ' .
				'Message d\'erreur de SQLite : ' . $e->getMessage());
		}

		if (strtolower(trim($check)) != 'ok')
		{
			throw new UserException('Le fichier fourni est corrompu. SQLite a trouvé ' . $check . ' erreurs.');
		}

		// On ne peut pas faire de vérifications très poussées sur la structure de la base de données,
		// celle-ci pouvant changer d'une version à l'autre et on peut vouloir importer une base
		// un peu vieille, mais on vérifie quand même que ça ressemble un minimum à une base garradin
		$table = $db->firstColumn('SELECT 1 FROM sqlite_master WHERE type=\'table\' AND tbl_name=\'config\';');

		if (!$table)
		{
			throw new UserException('Le fichier fourni ne semble pas contenir de données liées à Garradin.');
		}

		// On récupère la version
		$version = $db->querySingle('SELECT valeur FROM config WHERE cle=\'version\';');

		// Vérification de l'AppID pour les versions récentes
		if (version_compare($version, '0.8.0', '>='))
		{
			$appid = $db->firstColumn('PRAGMA application_id;');

			if ($appid !== DB::APPID)
			{
				throw new UserException('Ce fichier n\'est pas une sauvegarde Garradin (AppID ne correspond pas).');
			}
		}

		if ($user_id)
		{
			// Empêchons l'admin de se tirer une balle dans le pied
			$is_still_admin = $db->querySingle('SELECT 1 FROM membres_categories 
				WHERE id = (SELECT id_categorie FROM membres WHERE id = ' . (int) $user_id . ')
				AND droit_config >= ' . Membres::DROIT_ADMIN . '
				AND droit_connexion >= ' . Membres::DROIT_ACCES);

			if (!$is_still_admin)
			{
				throw new UserException('Vous n\'êtes pas administrateur dans le fichier de sauvegarde fourni.');
			}
		}


		$db->close();

		$backup = str_replace('.sqlite', date('.Y-m-d-His') . '.avant_restauration.sqlite', DB_FILE);


		
		if (!rename(DB_FILE, $backup))
		{
			throw new \RuntimeException('Unable to backup current DB file.');
		}

		if (!copy($file, DB_FILE))
		{
			rename($backup, DB_FILE);
			throw new \RuntimeException('Unable to copy backup DB to main location.');
		}











		if ($version != garradin_version())
		{
			return self::NEED_UPGRADE;
		}

		return true;
	}

	/**
	 * Taille de la base de données actuelle
	 * @return integer Taille en octets du fichier SQLite
	 */
	public function getDBSize()







>
>







|




|





|










|












|



|













|







>
>











>
>
>
>
>
>
>
>
>
>



|


|







272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
	 * Restauration de base de données, la fonction qui le fait vraiment
	 * @param  string $file Chemin absolu vers la base de données à utiliser
	 * @return mixed 		true si rien ne va plus, ou self::NEED_UPGRADE si la version de la DB
	 * ne correspond pas à la version de Garradin (mise à jour nécessaire).
	 */
	protected function restoreDB($file, $user_id = false)
	{
		$return = 1;

		// Essayons déjà d'ouvrir la base de données à restaurer en lecture
		try {
			$db = new \SQLite3($file, SQLITE3_OPEN_READONLY);
		}
		catch (\Exception $e)
		{
			throw new UserException('Le fichier fourni n\'est pas une base de données valide. ' .
				'Message d\'erreur de SQLite : ' . $e->getMessage(), self::NOT_A_DB);
		}

		try {
			// Regardons ensuite si la base de données n'est pas corrompue
			$check = $db->querySingle('PRAGMA integrity_check;', false);
		}
		catch (\Exception $e)
		{
			// Ici SQLite peut rejeter un message type "file is encrypted or is not a db"
			throw new UserException('Le fichier fourni n\'est pas une base de données valide. ' .
				'Message d\'erreur de SQLite : ' . $e->getMessage(), self::NOT_A_DB);
		}

		if (strtolower(trim($check)) != 'ok')
		{
			throw new UserException('Le fichier fourni est corrompu. SQLite a trouvé ' . $check . ' erreurs.');
		}

		// On ne peut pas faire de vérifications très poussées sur la structure de la base de données,
		// celle-ci pouvant changer d'une version à l'autre et on peut vouloir importer une base
		// un peu vieille, mais on vérifie quand même que ça ressemble un minimum à une base garradin
		$table = $db->querySingle('SELECT 1 FROM sqlite_master WHERE type=\'table\' AND tbl_name=\'config\';', false);

		if (!$table)
		{
			throw new UserException('Le fichier fourni ne semble pas contenir de données liées à Garradin.');
		}

		// On récupère la version
		$version = $db->querySingle('SELECT valeur FROM config WHERE cle=\'version\';');

		// Vérification de l'AppID pour les versions récentes
		if (version_compare($version, '0.8.0', '>='))
		{
			$appid = $db->querySingle('PRAGMA application_id;', false);

			if ($appid !== DB::APPID)
			{
				throw new UserException('Ce fichier n\'est pas une sauvegarde Garradin (application_id ne correspond pas).', self::NO_APP_ID);
			}
		}

		if ($user_id)
		{
			// Empêchons l'admin de se tirer une balle dans le pied
			$is_still_admin = $db->querySingle('SELECT 1 FROM membres_categories 
				WHERE id = (SELECT id_categorie FROM membres WHERE id = ' . (int) $user_id . ')
				AND droit_config >= ' . Membres::DROIT_ADMIN . '
				AND droit_connexion >= ' . Membres::DROIT_ACCES);

			if (!$is_still_admin)
			{
				$return |= self::NOT_AN_ADMIN;
			}
		}


		$db->close();

		$backup = str_replace('.sqlite', date('.Y-m-d-His') . '.avant_restauration.sqlite', DB_FILE);

		DB::getInstance()->close();
		
		if (!rename(DB_FILE, $backup))
		{
			throw new \RuntimeException('Unable to backup current DB file.');
		}

		if (!copy($file, DB_FILE))
		{
			rename($backup, DB_FILE);
			throw new \RuntimeException('Unable to copy backup DB to main location.');
		}

		if ($return & self::NOT_AN_ADMIN)
		{
			// Forcer toutes les catégories à pouvoir gérer les droits
			$db = DB::getInstance();
			$db->update('membres_categories', [
				'droit_membres' => Membres::DROIT_ADMIN,
				'droit_connexion' => Membres::DROIT_ACCES
			]);
		}

		if ($version != garradin_version())
		{
			$return |= self::NEED_UPGRADE;
		}

		return $return;
	}

	/**
	 * Taille de la base de données actuelle
	 * @return integer Taille en octets du fichier SQLite
	 */
	public function getDBSize()

Modified src/templates/admin/config/donnees.tpl from [6569f6241f] to [815a847b92].

1
2
3
4
5
6




7
8
9
10
11






12
13
14
15
16
17
18
{include file="admin/_head.tpl" title="Données — Sauvegarde et restauration" current="config"}

{include file="admin/config/_menu.tpl" current="donnees"}

{if $error}
    <p class="error">{$error}</p>




{elseif $ok}
    <p class="confirm">
        {if $ok == 'config'}La configuration a bien été enregistrée.
        {elseif $ok == 'create'}Une nouvelle sauvegarde a été créée.
        {elseif $ok == 'restore'}La restauration a bien été effectuée. Si vous désirez revenir en arrière, vous pouvez utiliser la sauvegarde automatique nommée <em>date-du-jour.avant_restauration.sqlite</em>, sinon vous pouvez l'effacer.






        {elseif $ok == 'remove'}La sauvegarde a été supprimée.
        {/if}
    </p>
{/if}

<form method="post" action="{$self_url}">







>
>
>
>





>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{include file="admin/_head.tpl" title="Données — Sauvegarde et restauration" current="config"}

{include file="admin/config/_menu.tpl" current="donnees"}

{if $error}
    <p class="error">{$error}</p>
    {if $code == Garradin\Sauvegarde::INTEGRITY_FAIL && Garradin\ALLOW_MODIFIED_IMPORT}
        <p class="alert">Pour passer outre, renvoyez le fichier en cochant la case «&nbsp;Ignorer les erreurs&nbsp;».
        Attention, si vous avez effectué des modifications dans la base de données, cela peut créer des bugs&nbsp;!</p>
    {/if}
{elseif $ok}
    <p class="confirm">
        {if $ok == 'config'}La configuration a bien été enregistrée.
        {elseif $ok == 'create'}Une nouvelle sauvegarde a été créée.
        {elseif $ok == 'restore'}La restauration a bien été effectuée. Si vous désirez revenir en arrière, vous pouvez utiliser la sauvegarde automatique nommée <em>date-du-jour.avant_restauration.sqlite</em>, sinon vous pouvez l'effacer.
            {if $ok_code & Garradin\Sauvegarde::NOT_AN_ADMIN}
            </p>
            <p class="alert">
                <strong>Vous n'êtes pas administrateur dans cette sauvegarde.</strong> Garradin a donné les droits d'administration à toutes les catégories afin d'empêcher de ne plus pouvoir se connecter.
                Merci de corriger les droits des catégories maintenant.
            {/if}
        {elseif $ok == 'remove'}La sauvegarde a été supprimée.
        {/if}
    </p>
{/if}

<form method="post" action="{$self_url}">

126
127
128
129
130
131
132





133
134
135
136
137
    <p>
        {csrf_field key="backup_restore"}
        <input type="hidden" name="MAX_FILE_SIZE" value="{$max_file_size}" />
        <input type="file" name="file" id="f_file" required="required" />
        (maximum {$max_file_size|format_bytes})
        <input type="submit" name="restore_file" value="Restaurer depuis le fichier sélectionné &rarr;" />
    </p>





</fieldset>

</form>

{include file="admin/_foot.tpl"}







>
>
>
>
>





136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
    <p>
        {csrf_field key="backup_restore"}
        <input type="hidden" name="MAX_FILE_SIZE" value="{$max_file_size}" />
        <input type="file" name="file" id="f_file" required="required" />
        (maximum {$max_file_size|format_bytes})
        <input type="submit" name="restore_file" value="Restaurer depuis le fichier sélectionné &rarr;" />
    </p>
    {if $error && ($code == Garradin\Sauvegarde::INTEGRITY_FAIL && Garradin\ALLOW_MODIFIED_IMPORT)}
    <p>
        <label><input type="checkbox" name="force_import" value="1" /> Ignorer les erreurs, je sais ce que je fait</label>
    </p>
    {/if}
</fieldset>

</form>

{include file="admin/_foot.tpl"}

Modified src/www/admin/config/donnees.php from [695392fbd6] to [b991b2d410].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$s = new Sauvegarde;
$error = false;

if (Utils::post('config'))
{
    if (!Utils::CSRF_check('backup_config'))
    {
        $error = 'Une erreur est survenue, merci de renvoyer le formulaire.';
    }






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$s = new Sauvegarde;
$code = $error = false;

if (Utils::post('config'))
{
    if (!Utils::CSRF_check('backup_config'))
    {
        $error = 'Une erreur est survenue, merci de renvoyer le formulaire.';
    }
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    if (!Utils::CSRF_check('backup_manage'))
    {
        $error = 'Une erreur est survenue, merci de renvoyer le formulaire.';
    }
    else
    {
        try {
            $s->restoreFromLocal(Utils::post('file'));
            Utils::redirect('/admin/config/donnees.php?ok=restore');
        } catch (UserException $e) {
            $error = $e->getMessage();
        }
    }
}
elseif (Utils::post('remove'))
{







|
|







61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    if (!Utils::CSRF_check('backup_manage'))
    {
        $error = 'Une erreur est survenue, merci de renvoyer le formulaire.';
    }
    else
    {
        try {
            $r = $s->restoreFromLocal(Utils::post('file'));
            Utils::redirect('/admin/config/donnees.php?ok=restore&code=' . (int)$r);
        } catch (UserException $e) {
            $error = $e->getMessage();
        }
    }
}
elseif (Utils::post('remove'))
{
92
93
94
95
96
97
98



99
100
101
102
103

104
105
106
107
108


109
110
111
112
113
114
115
116
{
    if (!Utils::CSRF_check('backup_restore'))
    {
        $error = 'Une erreur est survenue, merci de renvoyer le formulaire.';
    }
    else
    {



        try {
            $s->restoreFromUpload($_FILES['file'], $user->id);
            Utils::redirect('/admin/config/donnees.php?ok=restore');
        } catch (UserException $e) {
            $error = $e->getMessage();

        }
    }
}

$tpl->assign('error', $error);


$tpl->assign('ok', Utils::get('ok'));
$tpl->assign('liste', $s->getList());
$tpl->assign('max_file_size', Utils::getMaxUploadSize());

$tpl->assign('db_size', $s->getDBSize());
$tpl->assign('files_size', $s->getDBFilesSize());

$tpl->display('admin/config/donnees.tpl');







>
>
>

|
|


>





>
>








92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
{
    if (!Utils::CSRF_check('backup_restore'))
    {
        $error = 'Une erreur est survenue, merci de renvoyer le formulaire.';
    }
    else
    {
        // Ignorer la vérification d'intégrité si autorisé et demandé
        $check = (ALLOW_MODIFIED_IMPORT && Utils::post('force_import')) ? false : true;

        try {
            $r = $s->restoreFromUpload($_FILES['file'], $user->id, $check);
            Utils::redirect('/admin/config/donnees.php?ok=restore&code=' . (int)$r);
        } catch (UserException $e) {
            $error = $e->getMessage();
            $code = $e->getCode();
        }
    }
}

$tpl->assign('error', $error);
$tpl->assign('code', $code);
$tpl->assign('ok_code', Utils::get('code'));
$tpl->assign('ok', Utils::get('ok'));
$tpl->assign('liste', $s->getList());
$tpl->assign('max_file_size', Utils::getMaxUploadSize());

$tpl->assign('db_size', $s->getDBSize());
$tpl->assign('files_size', $s->getDBFilesSize());

$tpl->display('admin/config/donnees.tpl');