Overview
Comment: | Use a different session name for WebDAV/NextCloud apps to avoid security issues |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | dev |
Files: | files | file ages | folders |
SHA3-256: |
4ba510f3151a97dd7a8f3d60f3309db8 |
User & Date: | bohwaz on 2022-11-27 04:33:12 |
Other Links: | branch diff | manifest | tags |
Context
2022-11-27
| ||
04:35 | Commit missing class for separate app session check-in: 8dbef69f40 user: bohwaz tags: dev | |
04:33 | Use a different session name for WebDAV/NextCloud apps to avoid security issues check-in: 4ba510f315 user: bohwaz tags: dev | |
2022-11-20
| ||
04:35 | Refactor Skeleton class to manage web dans form skeletons in the same place check-in: 688da32e8c user: bohwaz tags: dev | |
Changes
Modified src/include/lib/Garradin/Entities/Files/File.php from [4f4e17b8ad] to [7f353f0f1d].
︙ | ︙ | |||
701 702 703 704 705 706 707 | throw new \LogicException('Cannot render file of this type'); } else { return Render::render($editor_type, $this, $this->fetch(), $user_prefix); } } | | > > | | | | | | | | | | | | | | | | | | | | 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 | throw new \LogicException('Cannot render file of this type'); } else { return Render::render($editor_type, $this, $this->fetch(), $user_prefix); } } public function canRead(Session $session = null): bool { // Web pages and config files are always public if ($this->isPublic()) { return true; } $session ??= Session::getInstance(); return $session->checkFilePermission($this->path, 'read'); } public function canShare(Session $session = null): bool { $session ??= Session::getInstance(); if (!$session->isLogged()) { return false; } return $session->checkFilePermission($this->path, 'share'); } public function canWrite(Session $session = null): bool { $session ??= Session::getInstance(); if (!$session->isLogged()) { return false; } return $session->checkFilePermission($this->path, 'write'); } public function canDelete(Session $session = null): bool { $session ??= Session::getInstance(); if (!$session->isLogged()) { return false; } return $session->checkFilePermission($this->path, 'delete'); } public function canMoveTo(string $destination, Session $session = null): bool { $session ??= Session::getInstance(); if (!$session->isLogged()) { return false; } return $session->checkFilePermission($this->path, 'move') && $this->canDelete() && self::canCreate($destination); } public function canCopyTo(string $destination, Session $session = null): bool { $session ??= Session::getInstance(); if (!$session->isLogged()) { return false; } return $this->canRead() && self::canCreate($destination); } public function canCreateDirHere(Session $session = null) { if (!$this->isDir()) { return false; } $session ??= Session::getInstance(); if (!$session->isLogged()) { return false; } return $session->checkFilePermission($this->path, 'mkdir'); } static public function canCreateDir(string $path, Session $session = null) { $session ??= Session::getInstance(); if (!$session->isLogged()) { return false; } return $session->checkFilePermission($path, 'mkdir'); } public function canCreateHere(Session $session = null): bool { if (!$this->isDir()) { return false; } $session ??= Session::getInstance(); if (!$session->isLogged()) { return false; } return $session->checkFilePermission($this->path, 'create'); } static public function canCreate(string $path, Session $session = null): bool { $session ??= Session::getInstance(); if (!$session->isLogged()) { return false; } return $session->checkFilePermission($path, 'create'); } |
︙ | ︙ |
Modified src/include/lib/Garradin/Files/WebDAV/NextCloud.php from [24239964bd] to [7e5371f3f3].
1 2 3 4 5 6 7 8 | <?php namespace Garradin\Files\WebDAV; use KD2\WebDAV\NextCloud as WebDAV_NextCloud; use KD2\WebDAV\Exception as WebDAV_Exception; use Garradin\Files\Files; | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php namespace Garradin\Files\WebDAV; use KD2\WebDAV\NextCloud as WebDAV_NextCloud; use KD2\WebDAV\Exception as WebDAV_Exception; use Garradin\Files\Files; use const Garradin\{SECRET_KEY, ADMIN_URL, CACHE_ROOT, WWW_URL}; class NextCloud extends WebDAV_NextCloud { protected string $temporary_chunks_path; protected string $prefix = 'documents/'; |
︙ | ︙ |
Modified src/include/lib/Garradin/Files/WebDAV/Server.php from [07a8dc31c3] to [01bb1e4b21].
1 2 3 4 5 6 | <?php namespace Garradin\Files\WebDAV; use KD2\WebDAV\WOPI; | < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 | <?php namespace Garradin\Files\WebDAV; use KD2\WebDAV\WOPI; use const Garradin\WOPI_DISCOVERY_URL; class Server { static public function route(?string $uri = null): bool { $uri = '/' . ltrim($uri, '/'); |
︙ | ︙ |
Modified src/include/lib/Garradin/Files/WebDAV/Storage.php from [56a1d87348] to [ba171c156f].
1 2 3 4 5 6 7 8 9 10 11 12 | <?php namespace Garradin\Files\WebDAV; use KD2\WebDAV\AbstractStorage; use KD2\WebDAV\WOPI; use KD2\WebDAV\Exception as WebDAV_Exception; use Garradin\DB; use Garradin\Utils; use Garradin\ValidationException; | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php namespace Garradin\Files\WebDAV; use KD2\WebDAV\AbstractStorage; use KD2\WebDAV\WOPI; use KD2\WebDAV\Exception as WebDAV_Exception; use Garradin\DB; use Garradin\Utils; use Garradin\ValidationException; use Garradin\Files\Files; use Garradin\Entities\Files\File; use Garradin\Web\Router; use const Garradin\FILE_STORAGE_BACKEND; class Storage extends AbstractStorage |
︙ | ︙ | |||
107 108 109 110 111 112 113 | $file = $this->load($uri); if (!$file) { throw new WebDAV_Exception('File Not Found', 404); } | | | 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | $file = $this->load($uri); if (!$file) { throw new WebDAV_Exception('File Not Found', 404); } if (!$file->canRead($session)) { throw new WebDAV_Exception('Vous n\'avez pas accès à ce chemin', 403); } $type = $file->type; // Serve files if ($type == File::TYPE_DIRECTORY) { |
︙ | ︙ | |||
191 192 193 194 195 196 197 | return $this->nextcloud->getDirectURL($uri, $session::getUserId()); case Nextcloud::PROP_NC_RICH_WORKSPACE: return ''; case NextCloud::PROP_OC_ID: return NextCloud::getDirectID('', $uri); case NextCloud::PROP_OC_PERMISSIONS: $permissions = [ | | | | | | | | 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | return $this->nextcloud->getDirectURL($uri, $session::getUserId()); case Nextcloud::PROP_NC_RICH_WORKSPACE: return ''; case NextCloud::PROP_OC_ID: return NextCloud::getDirectID('', $uri); case NextCloud::PROP_OC_PERMISSIONS: $permissions = [ NextCloud::PERM_READ => $file->canRead($session), NextCloud::PERM_WRITE => $file->canWrite($session), NextCloud::PERM_DELETE => $file->canDelete($session), NextCloud::PERM_RENAME_MOVE => $file->canDelete($session), NextCloud::PERM_CREATE => $file->canCreateHere($session), ]; $permissions = array_filter($permissions, fn($a) => $a); return implode('', array_keys($permissions)); case 'DAV::quota-available-bytes': return Files::getRemainingQuota(); case 'DAV::quota-used-bytes': return Files::getUsedQuota(); case Nextcloud::PROP_OC_SIZE: return $file->getRecursiveSize(); case WOPI::PROP_USER_NAME: return $session->getUser()->name(); case WOPI::PROP_READ_ONLY: return $file->canWrite($session) ? false : true; case WOPI::PROP_FILE_URL: $id = gzcompress($uri); $id = WOPI::base64_encode_url_safe($id); return WWW_URL . 'wopi/files/' . $id; default: break; } |
︙ | ︙ | |||
275 276 277 278 279 280 281 | if ($target && $target->type === $target::TYPE_DIRECTORY) { throw new WebDAV_Exception('Target is a directory', 409); } $new = !$target ? true : false; $session = Session::getInstance(); | | | | 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | if ($target && $target->type === $target::TYPE_DIRECTORY) { throw new WebDAV_Exception('Target is a directory', 409); } $new = !$target ? true : false; $session = Session::getInstance(); if ($new && !File::canCreate($uri, $session)) { throw new WebDAV_Exception('Vous n\'avez pas l\'autorisation de créer ce fichier', 403); } elseif (!$new && !$target->canWrite($session)) { throw new WebDAV_Exception('Vous n\'avez pas l\'autorisation de modifier ce fichier', 403); } $h = $hash ? hash_init('md5') : null; while (!feof($pointer)) { if ($h) { |
︙ | ︙ | |||
340 341 342 343 344 345 346 | $target = Files::get($uri); if (!$target) { throw new WebDAV_Exception('This file does not exist', 404); } | > > | | | 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 | $target = Files::get($uri); if (!$target) { throw new WebDAV_Exception('This file does not exist', 404); } $session = Session::getInstance(); if (!$target->canDelete($session)) { throw new WebDAV_Exception('Vous n\'avez pas l\'autorisation de supprimer ce fichier', 403); } $target->delete(); } protected function copymove(bool $move, string $uri, string $destination): bool { if (!strpos($uri, '/')) { throw new WebDAV_Exception('Ce répertoire ne peut être modifié', 403); } $source = Files::get($uri); if (!$source) { throw new WebDAV_Exception('File not found', 404); } $session = Session::getInstance(); if (!$source->canMoveTo($destination, $session)) { throw new WebDAV_Exception('Vous n\'avez pas l\'autorisation de déplacer ce fichier', 403); } if (!$move) { if ($source->size > Files::getRemainingQuota(true)) { throw new WebDAV_Exception('Your quota is exhausted', 403); } |
︙ | ︙ |
Modified src/include/lib/Garradin/Users/Session.php from [c079c72d6a] to [58c752f3ef].
︙ | ︙ | |||
53 54 55 56 57 58 59 | protected ?User $_user; protected ?array $_permissions; static protected $_instance = null; static public function getInstance() { | > | | | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | protected ?User $_user; protected ?array $_permissions; static protected $_instance = null; static public function getInstance() { // Use of static is important for Files\WebDAV\Session return static::$_instance ?: static::$_instance = new static; } public function __clone() { throw new \LogicException('Cannot clone'); } public function __construct() { if (static::$_instance !== null) { throw new \LogicException('Wrong call, use getInstance'); } $url = parse_url(ADMIN_URL); parent::__construct(DB::getInstance(), [ 'cookie_domain' => $url['host'], |
︙ | ︙ | |||
154 155 156 157 158 159 160 | } protected function deleteAllRememberMeSelectors($user_id) { return $this->db->delete('users_sessions', $this->db->where('id_user', $user_id)); } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | } protected function deleteAllRememberMeSelectors($user_id) { return $this->db->delete('users_sessions', $this->db->where('id_user', $user_id)); } public function isLogged(bool $disable_local_login = false) { $logged = parent::isLogged(); // Ajout de la gestion de LOCAL_LOGIN if (!$disable_local_login && LOCAL_LOGIN) { $logged = $this->forceLogin(LOCAL_LOGIN); |
︙ | ︙ |
Modified src/www/admin/login.php from [722866c99b] to [fe9fb17109].
1 2 3 4 5 6 7 | <?php namespace Garradin; use KD2\HTTP; use KD2\Security; use Garradin\Users\DynamicFields; | | > > > > | > > > > < | 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?php namespace Garradin; use KD2\HTTP; use KD2\Security; use Garradin\Users\DynamicFields; use Garradin\Users\Session as UserSession; use Garradin\Files\WebDAV\Session as AppSession; use Garradin\UserException; const LOGIN_PROCESS = true; require_once __DIR__ . '/_inc.php'; $app_token = $_GET['app'] ?? null; if ($app_token) { $session = AppSession::getInstance(); } else { $session = UserSession::getInstance(); } // Relance session_start et renvoie une image de 1px transparente if (qg('keepSessionAlive') !== null) { $session->keepAlive(); header('Cache-Control: no-cache, must-revalidate'); header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); header('Content-Type: image/gif'); echo base64_decode("R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="); exit; } $args = $app_token ? '?app=' . rawurlencode($app_token) : ''; $layout = $app_token ? 'public' : null; // L'utilisateur est déjà connecté if ($session->isLogged()) { if ($app_token) { Utils::redirect('!login_app.php' . $args); |
︙ | ︙ | |||
67 68 69 70 71 72 73 | $ok = $session->login(f('id'), f('password'), (bool) f('permanent')); if (!$ok) { throw new UserException(sprintf("Connexion impossible.\nVérifiez votre identifiant (%s) et votre mot de passe.", $id_field_name)); } | | | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | $ok = $session->login(f('id'), f('password'), (bool) f('permanent')); if (!$ok) { throw new UserException(sprintf("Connexion impossible.\nVérifiez votre identifiant (%s) et votre mot de passe.", $id_field_name)); } if ($session::REQUIRE_OTP === $ok) { Utils::redirect('!login_otp.php' . $args); } elseif ($args) { Utils::redirect('!login_app.php' . $args); } }, 'login', ADMIN_URL); |
︙ | ︙ |
Modified src/www/admin/login_app.php from [8b5a33b239] to [38f037c3b1].
1 2 3 | <?php namespace Garradin; | | > > | | | > | | 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 29 30 31 32 | <?php namespace Garradin; use Garradin\Files\WebDAV\Session as AppSession; use Garradin\Entities\Files\File; const LOGIN_PROCESS = true; require_once __DIR__ . '/_inc.php'; $session = AppSession::getInstance(); $session->requireAccess($session::SECTION_DOCUMENTS, $session::ACCESS_READ); $app_token = $_GET['app'] ?? null; if (!$app_token) { die("No app token was supplied."); } $csrf_key = 'app_confirm_' . $app_token; $form->runIf('cancel', function () use ($app_token, $session) { $session->logout(); Utils::redirect('!login.php?app=' . $app_token); }); $form->runIf('confirm', function () use ($app_token, $session) { if ($app_token == 'redirect') { $data = $session->createAppCredentials(); } else { |
︙ | ︙ |
Modified src/www/admin/login_otp.php from [54b9408ded] to [d8c5263355].
1 2 3 4 | <?php namespace Garradin; | | > > > > | > > > > < | 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 29 30 31 32 33 34 | <?php namespace Garradin; use Garradin\Users\Session as UserSession; use Garradin\Files\WebDAV\Session as AppSession; const LOGIN_PROCESS = true; require_once __DIR__ . '/_inc.php'; $app_token = $_GET['app'] ?? null; if ($app_token) { $session = AppSession::getInstance(); } else { $session = UserSession::getInstance(); } if (!$session->isOTPRequired()) { Utils::redirect(ADMIN_URL); } $login = null; $csrf_key = 'login_otp'; $args = $app_token ? '?app=' . rawurlencode($app_token) : ''; $layout = $app_token ? 'public' : null; $form->runIf('login', function () use ($session, $args) { if (!$session->loginOTP(f('code'))) { throw new UserException(sprintf('Code incorrect. Vérifiez que votre téléphone est à l\'heure (heure du serveur : %s).', date('d/m/Y H:i:s'))); } |
︙ | ︙ |