Overview
Comment:Fix restore backup where user does not exist anymore: force login as first admin account
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 70bfaa52e7181a5b1b5af7f3a55fca399056aab1f07680cce1f0850425101121
User & Date: bohwaz on 2022-02-09 21:36:55
Other Links: manifest | tags
Context
2022-02-09
23:17
Fix more PHP 8.1 depreciations check-in: 75e0d8d864 user: bohwaz tags: trunk
21:36
Fix restore backup where user does not exist anymore: force login as first admin account check-in: 70bfaa52e7 user: bohwaz tags: trunk
2022-02-08
00:08
Fix PHP 8.1 errors check-in: 4389c3dcc4 user: bohwaz tags: trunk
Changes

Modified src/include/lib/Garradin/Membres/Session.php from [f6b4098759] to [5ca1fff613].

156
157
158
159
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189











190



191
192
193
194
195
196
197
	}

	protected function deleteAllRememberMeSelectors($user_id)
	{
		return $this->db->delete('membres_sessions', $this->db->where('id_membre', $user_id));
	}

	// Ajout de la gestion de LOCAL_LOGIN
	public function isLogged(bool $disable_local_login = false)
	{
		$logged = parent::isLogged();


		if (!$disable_local_login && defined('\Garradin\LOCAL_LOGIN'))
		{
			$login_id = \Garradin\LOCAL_LOGIN;

			// On va chercher le premier membre avec le droit de gérer la config
			if (-1 === $login_id) {
				$login_id = $this->db->firstColumn('SELECT id FROM membres
					WHERE id_category IN (SELECT id FROM users_categories WHERE perm_config = ?)
					LIMIT 1', self::ACCESS_ADMIN);
			}

			if ($login_id > 0 && (!$logged || ($logged && $this->user->id != $login_id)))
			{
				$logged = $this->create($login_id);
			}
		}

		return $logged;
	}

	public function forceLogin(int $id)
	{











		return $this->create($id);



	}

	// Ici checkOTP utilise NTP en second recours
	public function checkOTP($secret, $code)
	{
		if (Security_OTP::TOTP($secret, $code))
		{







<




>
|
<
|
<
<
<
<
<
<
<
<
<
<
<
<







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







156
157
158
159
160
161
162

163
164
165
166
167
168

169












170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
	}

	protected function deleteAllRememberMeSelectors($user_id)
	{
		return $this->db->delete('membres_sessions', $this->db->where('id_membre', $user_id));
	}


	public function isLogged(bool $disable_local_login = false)
	{
		$logged = parent::isLogged();

		// Ajout de la gestion de LOCAL_LOGIN
		if (!$disable_local_login && defined('\Garradin\LOCAL_LOGIN')) {

			$logged = $this->forceLogin(\Garradin\LOCAL_LOGIN);












		}

		return $logged;
	}

	public function forceLogin(int $id)
	{
		// On va chercher le premier membre avec le droit de gérer la config
		if (-1 === $id) {
			$id = $this->db->firstColumn('SELECT id FROM membres
				WHERE id_category IN (SELECT id FROM users_categories WHERE perm_config = ?)
				LIMIT 1', self::ACCESS_ADMIN);
		}

		$logged = parent::isLogged();

		// Only login if required
		if ($id > 0 && (!$logged || ($logged && $this->user->id != $id))) {
			return $this->create($id);
		}

		return $logged;
	}

	// Ici checkOTP utilise NTP en second recours
	public function checkOTP($secret, $code)
	{
		if (Security_OTP::TOTP($secret, $code))
		{

Modified src/include/lib/Garradin/Sauvegarde.php from [dae05764ac] to [fb842afc09].

8
9
10
11
12
13
14

15
16
17
18
19
20
21

use KD2\ZipWriter;

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







>







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

use KD2\ZipWriter;

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

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

	/**
	 * Renvoie la liste des fichiers SQLite sauvegardés
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

		return $this->restoreDB(DATA_ROOT . '/' . $file, false, false);
	}

	/**
	 * Restaure une copie distante (fichier envoyé)
	 * @param  array   $file    Tableau provenant de $_FILES
	 * @param  integer $user_id ID du membre actuellement connecté, utilisé pour 
	 * vérifier qu'il est toujours administrateur dans la sauvegarde
	 * @param  boolean $check_integrity Vérifier l'intégrité de la sauvegarde avant de restaurer
	 * @return boolean true
	 */
	public function restoreFromUpload($file, $user_id, $check_integrity = true)
	{
		if (empty($file['size']) || empty($file['tmp_name']) || !empty($file['error']))
		{
			throw new UserException('Le fichier n\'a pas été correctement envoyé. Essayer de le renvoyer à nouveau.');
		}

		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, true);

		if ($r)
		{
			Utils::safe_unlink($file['tmp_name']);
		}

		return $r;







<
<



|




















|







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

		return $this->restoreDB(DATA_ROOT . '/' . $file, false, false);
	}

	/**
	 * Restaure une copie distante (fichier envoyé)
	 * @param  array   $file    Tableau provenant de $_FILES


	 * @param  boolean $check_integrity Vérifier l'intégrité de la sauvegarde avant de restaurer
	 * @return boolean true
	 */
	public function restoreFromUpload($file, $check_integrity = true)
	{
		if (empty($file['size']) || empty($file['tmp_name']) || !empty($file['error']))
		{
			throw new UserException('Le fichier n\'a pas été correctement envoyé. Essayer de le renvoyer à nouveau.');
		}

		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'], true);

		if ($r)
		{
			Utils::safe_unlink($file['tmp_name']);
		}

		return $r;
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423

	/**
	 * 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, $check_foreign_keys = false)
	{
		$return = 1;

		// Essayons déjà d'ouvrir la base de données à restaurer en lecture
		try {
			$db = new \SQLite3($file, \SQLITE3_OPEN_READONLY);
		}







|







408
409
410
411
412
413
414
415
416
417
418
419
420
421
422

	/**
	 * 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, $check_foreign_keys = false)
	{
		$return = 1;

		// Essayons déjà d'ouvrir la base de données à restaurer en lecture
		try {
			$db = new \SQLite3($file, \SQLITE3_OPEN_READONLY);
		}
477
478
479
480
481
482
483
484


485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
		// Vérification de l'AppID
		$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);
		}



		// Empêchons l'admin de se tirer une balle dans le pied
		if ($user_id)
		{
			if (version_compare($version, '1.1', '<')) {
				$sql = 'SELECT 1 FROM membres_categories WHERE id = (SELECT id_categorie FROM membres WHERE id = %d) AND droit_connexion >= %d AND droit_config >= %d';
			}
			else {
				$sql = 'SELECT 1 FROM users_categories WHERE id = (SELECT id_category FROM membres WHERE id = %d) AND perm_connect >= %d AND perm_config >= %d';
			}

			$sql = sprintf($sql, $user_id, Session::ACCESS_READ, Session::ACCESS_ADMIN);
			$is_still_admin = $db->querySingle($sql);

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








>
>

|

|






|







476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
		// Vérification de l'AppID
		$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);
		}

		$session = Session::getInstance();

		// Empêchons l'admin de se tirer une balle dans le pied
		if ($session->isLogged())
		{
			if (version_compare($version, '1.1', '<')) { // FIXME remove in 1.2
				$sql = 'SELECT 1 FROM membres_categories WHERE id = (SELECT id_categorie FROM membres WHERE id = %d) AND droit_connexion >= %d AND droit_config >= %d';
			}
			else {
				$sql = 'SELECT 1 FROM users_categories WHERE id = (SELECT id_category FROM membres WHERE id = %d) AND perm_connect >= %d AND perm_config >= %d';
			}

			$sql = sprintf($sql, $session->getUser()->id, Session::ACCESS_READ, Session::ACCESS_ADMIN);
			$is_still_admin = $db->querySingle($sql);

			if (!$is_still_admin)
			{
				$return |= self::NOT_AN_ADMIN;
			}
		}
526
527
528
529
530
531
532





533
534
535
536
537
538
539
			// Forcer toutes les catégories à pouvoir gérer les droits
			$db = DB::getInstance();
			$db->update('users_categories', [
				'perm_users' => Session::ACCESS_ADMIN,
				'perm_connect' => Session::ACCESS_READ
			]);
		}






		if ($version != garradin_version())
		{
			$return |= self::NEED_UPGRADE;
		}
		else {
			// Force l'installation de plugin système si non existant dans la sauvegarde existante







>
>
>
>
>







527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
			// Forcer toutes les catégories à pouvoir gérer les droits
			$db = DB::getInstance();
			$db->update('users_categories', [
				'perm_users' => Session::ACCESS_ADMIN,
				'perm_connect' => Session::ACCESS_READ
			]);
		}

		if (!$session->refresh()) {
			$session->forceLogin(-1);
			$return |= self::CHANGED_USER;
		}

		if ($version != garradin_version())
		{
			$return |= self::NEED_UPGRADE;
		}
		else {
			// Force l'installation de plugin système si non existant dans la sauvegarde existante

Modified src/templates/admin/config/backup/restore.tpl from [fe1e0e7549] to [0ec7bed822].

15
16
17
18
19
20
21





22
23
24
25
26
27
28
	<p class="block confirm">
		{if $ok == 'restore'}La restauration a bien été effectuée.
			{if $ok_code & Sauvegarde::NOT_AN_ADMIN}
			</p>
			<p class="block 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}









>
>
>
>
>







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
	<p class="block confirm">
		{if $ok == 'restore'}La restauration a bien été effectuée.
			{if $ok_code & Sauvegarde::NOT_AN_ADMIN}
			</p>
			<p class="block 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.
			{elseif $ok_code & Sauvegarde::CHANGED_USER}
			</p>
			<p class="block alert">
				<strong>Votre compte membre n'existait pas dans la sauvegarde qui a été restaurée, vous êtes désormais connecté avec le premier compte administrateur.</strong>
			</p>
			{/if}
		{elseif $ok == 'remove'}La sauvegarde a été supprimée.
		{/if}
	</p>
{/if}


Modified src/www/admin/config/backup/restore.php from [1f7464bcbf] to [02133a1ff3].

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

44
45
46
47
48
49
50
51
52
53
54
55
		throw new UserException('Aucune sauvegarde sélectionnée');
	}

	$s->remove(f('selected'));
}, 'backup_manage', Utils::getSelfURI(['ok' => 'remove']));


$form->runIf('restore_file', function () use ($s, &$code, $session) {
	// Ignorer la vérification d'intégrité si autorisé et demandé
	$check = (ALLOW_MODIFIED_IMPORT && f('force_import')) ? false : true;

	try {
		$r = $s->restoreFromUpload($_FILES['file'], $session->getUser()->id, $check);
		Utils::redirect(Utils::getSelfURI(['ok' => 'restore', 'code' => (int)$r]));
	} catch (UserException $e) {
		$code = $e->getCode();
		if ($code == 0) {
			throw $e;
		}

	}
}, 'backup_restore');


$ok_code = qg('code'); // return code
$ok = qg('ok'); // return message

$list = $s->getList();

$tpl->assign(compact('code', 'list', 'ok', 'ok_code'));

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







|




|






>


<









25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

47
48
49
50
51
52
53
54
55
		throw new UserException('Aucune sauvegarde sélectionnée');
	}

	$s->remove(f('selected'));
}, 'backup_manage', Utils::getSelfURI(['ok' => 'remove']));


$form->runIf('restore_file', function () use ($s, &$code, $session, $form) {
	// Ignorer la vérification d'intégrité si autorisé et demandé
	$check = (ALLOW_MODIFIED_IMPORT && f('force_import')) ? false : true;

	try {
		$r = $s->restoreFromUpload($_FILES['file'], $check);
		Utils::redirect(Utils::getSelfURI(['ok' => 'restore', 'code' => (int)$r]));
	} catch (UserException $e) {
		$code = $e->getCode();
		if ($code == 0) {
			throw $e;
		}
		$form->addError($e->getMessage());
	}
}, 'backup_restore');


$ok_code = qg('code'); // return code
$ok = qg('ok'); // return message

$list = $s->getList();

$tpl->assign(compact('code', 'list', 'ok', 'ok_code'));

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