file_system_access_permission_context.cc 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. // Copyright (c) 2024 Microsoft, GmbH
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/file_system_access/file_system_access_permission_context.h"
  5. #include <string>
  6. #include <utility>
  7. #include "base/base_paths.h"
  8. #include "base/files/file_path.h"
  9. #include "base/json/values_util.h"
  10. #include "base/path_service.h"
  11. #include "base/task/thread_pool.h"
  12. #include "base/time/time.h"
  13. #include "base/values.h"
  14. #include "chrome/browser/browser_process.h"
  15. #include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h" // nogncheck
  16. #include "chrome/browser/file_system_access/file_system_access_features.h"
  17. #include "chrome/common/chrome_paths.h"
  18. #include "chrome/grit/generated_resources.h"
  19. #include "content/public/browser/browser_context.h"
  20. #include "content/public/browser/browser_thread.h"
  21. #include "content/public/browser/disallow_activation_reason.h"
  22. #include "content/public/browser/render_frame_host.h"
  23. #include "content/public/browser/render_process_host.h"
  24. #include "content/public/browser/web_contents.h"
  25. #include "shell/browser/electron_permission_manager.h"
  26. #include "shell/browser/web_contents_permission_helper.h"
  27. #include "shell/common/gin_converters/file_path_converter.h"
  28. #include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom.h"
  29. #include "ui/base/l10n/l10n_util.h"
  30. #include "url/origin.h"
  31. namespace {
  32. using BlockType = ChromeFileSystemAccessPermissionContext::BlockType;
  33. using HandleType = content::FileSystemAccessPermissionContext::HandleType;
  34. using GrantType = electron::FileSystemAccessPermissionContext::GrantType;
  35. using blink::mojom::PermissionStatus;
  36. // Dictionary keys for the FILE_SYSTEM_LAST_PICKED_DIRECTORY website setting.
  37. // Schema (per origin):
  38. // {
  39. // ...
  40. // {
  41. // "default-id" : { "path" : <path> , "path-type" : <type>}
  42. // "custom-id-fruit" : { "path" : <path> , "path-type" : <type> }
  43. // "custom-id-flower" : { "path" : <path> , "path-type" : <type> }
  44. // ...
  45. // }
  46. // ...
  47. // }
  48. const char kDefaultLastPickedDirectoryKey[] = "default-id";
  49. const char kCustomLastPickedDirectoryKey[] = "custom-id";
  50. const char kPathKey[] = "path";
  51. const char kPathTypeKey[] = "path-type";
  52. const char kTimestampKey[] = "timestamp";
  53. #if BUILDFLAG(IS_WIN)
  54. [[nodiscard]] constexpr bool ContainsInvalidDNSCharacter(
  55. base::FilePath::StringType hostname) {
  56. return !base::ranges::all_of(hostname, [](base::FilePath::CharType c) {
  57. return (c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z') ||
  58. (c >= L'0' && c <= L'9') || (c == L'.') || (c == L'-');
  59. });
  60. }
  61. bool MaybeIsLocalUNCPath(const base::FilePath& path) {
  62. if (!path.IsNetwork()) {
  63. return false;
  64. }
  65. const std::vector<base::FilePath::StringType> components =
  66. path.GetComponents();
  67. // Check for server name that could represent a local system. We only
  68. // check for a very short list, as it is impossible to cover all different
  69. // variants on Windows.
  70. if (components.size() >= 2 &&
  71. (base::FilePath::CompareEqualIgnoreCase(components[1],
  72. FILE_PATH_LITERAL("localhost")) ||
  73. components[1] == FILE_PATH_LITERAL("127.0.0.1") ||
  74. components[1] == FILE_PATH_LITERAL(".") ||
  75. components[1] == FILE_PATH_LITERAL("?") ||
  76. ContainsInvalidDNSCharacter(components[1]))) {
  77. return true;
  78. }
  79. // In case we missed the server name check above, we also check for shares
  80. // ending with '$' as they represent pre-defined shares, including the local
  81. // drives.
  82. for (size_t i = 2; i < components.size(); ++i) {
  83. if (components[i].back() == L'$') {
  84. return true;
  85. }
  86. }
  87. return false;
  88. }
  89. #endif // BUILDFLAG(IS_WIN)
  90. // Describes a rule for blocking a directory, which can be constructed
  91. // dynamically (based on state) or statically (from kBlockedPaths).
  92. struct BlockPathRule {
  93. base::FilePath path;
  94. BlockType type;
  95. };
  96. bool ShouldBlockAccessToPath(const base::FilePath& path,
  97. HandleType handle_type,
  98. std::vector<BlockPathRule> rules) {
  99. DCHECK(!path.empty());
  100. DCHECK(path.IsAbsolute());
  101. #if BUILDFLAG(IS_WIN)
  102. // On Windows, local UNC paths are rejected, as UNC path can be written in a
  103. // way that can bypass the blocklist.
  104. if (base::FeatureList::IsEnabled(
  105. features::kFileSystemAccessLocalUNCPathBlock) &&
  106. MaybeIsLocalUNCPath(path)) {
  107. return true;
  108. }
  109. #endif // BUILDFLAG(IS_WIN)
  110. // Add the hard-coded rules to the dynamic rules.
  111. for (auto const& [key, rule_path, type] :
  112. ChromeFileSystemAccessPermissionContext::kBlockedPaths) {
  113. if (key == ChromeFileSystemAccessPermissionContext::kNoBasePathKey) {
  114. rules.emplace_back(base::FilePath{rule_path}, type);
  115. } else if (base::FilePath path; base::PathService::Get(key, &path)) {
  116. rules.emplace_back(rule_path ? path.Append(rule_path) : path, type);
  117. }
  118. }
  119. base::FilePath nearest_ancestor;
  120. BlockType nearest_ancestor_block_type = BlockType::kDontBlockChildren;
  121. for (const auto& block : rules) {
  122. if (path == block.path || path.IsParent(block.path)) {
  123. DLOG(INFO) << "Blocking access to " << path
  124. << " because it is a parent of " << block.path;
  125. return true;
  126. }
  127. if (block.path.IsParent(path) &&
  128. (nearest_ancestor.empty() || nearest_ancestor.IsParent(block.path))) {
  129. nearest_ancestor = block.path;
  130. nearest_ancestor_block_type = block.type;
  131. }
  132. }
  133. // The path we're checking is not in a potentially blocked directory, or the
  134. // nearest ancestor does not block access to its children. Grant access.
  135. if (nearest_ancestor.empty() ||
  136. nearest_ancestor_block_type == BlockType::kDontBlockChildren) {
  137. return false;
  138. }
  139. // The path we're checking is a file, and the nearest ancestor only blocks
  140. // access to directories. Grant access.
  141. if (handle_type == HandleType::kFile &&
  142. nearest_ancestor_block_type == BlockType::kBlockNestedDirectories) {
  143. return false;
  144. }
  145. // The nearest ancestor blocks access to its children, so block access.
  146. DLOG(INFO) << "Blocking access to " << path << " because it is inside "
  147. << nearest_ancestor;
  148. return true;
  149. }
  150. std::string GenerateLastPickedDirectoryKey(const std::string& id) {
  151. return id.empty() ? kDefaultLastPickedDirectoryKey
  152. : base::StrCat({kCustomLastPickedDirectoryKey, "-", id});
  153. }
  154. } // namespace
  155. namespace electron {
  156. class FileSystemAccessPermissionContext::PermissionGrantImpl
  157. : public content::FileSystemAccessPermissionGrant {
  158. public:
  159. PermissionGrantImpl(base::WeakPtr<FileSystemAccessPermissionContext> context,
  160. const url::Origin& origin,
  161. const base::FilePath& path,
  162. HandleType handle_type,
  163. GrantType type,
  164. UserAction user_action)
  165. : context_{std::move(context)},
  166. origin_{origin},
  167. handle_type_{handle_type},
  168. type_{type},
  169. path_{path} {}
  170. // FileSystemAccessPermissionGrant:
  171. PermissionStatus GetStatus() override {
  172. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  173. return status_;
  174. }
  175. base::FilePath GetPath() override {
  176. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  177. return path_;
  178. }
  179. void RequestPermission(
  180. content::GlobalRenderFrameHostId frame_id,
  181. UserActivationState user_activation_state,
  182. base::OnceCallback<void(PermissionRequestOutcome)> callback) override {
  183. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  184. // Check if a permission request has already been processed previously. This
  185. // check is done first because we don't want to reset the status of a
  186. // permission if it has already been granted.
  187. if (GetStatus() != PermissionStatus::ASK || !context_) {
  188. if (GetStatus() == PermissionStatus::GRANTED) {
  189. SetStatus(PermissionStatus::GRANTED);
  190. }
  191. std::move(callback).Run(PermissionRequestOutcome::kRequestAborted);
  192. return;
  193. }
  194. content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(frame_id);
  195. if (!rfh) {
  196. // Requested from a no longer valid RenderFrameHost.
  197. std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
  198. return;
  199. }
  200. // Don't request permission for an inactive RenderFrameHost as the
  201. // page might not distinguish properly between user denying the permission
  202. // and automatic rejection.
  203. if (rfh->IsInactiveAndDisallowActivation(
  204. content::DisallowActivationReasonId::
  205. kFileSystemAccessPermissionRequest)) {
  206. std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
  207. return;
  208. }
  209. // We don't allow file system access from fenced frames.
  210. if (rfh->IsNestedWithinFencedFrame()) {
  211. std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
  212. return;
  213. }
  214. if (user_activation_state == UserActivationState::kRequired &&
  215. !rfh->HasTransientUserActivation()) {
  216. // No permission prompts without user activation.
  217. std::move(callback).Run(PermissionRequestOutcome::kNoUserActivation);
  218. return;
  219. }
  220. if (content::WebContents::FromRenderFrameHost(rfh) == nullptr) {
  221. std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
  222. return;
  223. }
  224. auto origin = rfh->GetLastCommittedOrigin().GetURL();
  225. if (url::Origin::Create(origin) != origin_) {
  226. // Third party iframes are not allowed to request more permissions.
  227. std::move(callback).Run(PermissionRequestOutcome::kThirdPartyContext);
  228. return;
  229. }
  230. auto* permission_manager =
  231. static_cast<electron::ElectronPermissionManager*>(
  232. context_->browser_context()->GetPermissionControllerDelegate());
  233. if (!permission_manager) {
  234. std::move(callback).Run(PermissionRequestOutcome::kRequestAborted);
  235. return;
  236. }
  237. blink::PermissionType type = static_cast<blink::PermissionType>(
  238. electron::WebContentsPermissionHelper::PermissionType::FILE_SYSTEM);
  239. base::Value::Dict details;
  240. details.Set("filePath", base::FilePathToValue(path_));
  241. details.Set("isDirectory", handle_type_ == HandleType::kDirectory);
  242. details.Set("fileAccessType",
  243. type_ == GrantType::kWrite ? "writable" : "readable");
  244. permission_manager->RequestPermissionWithDetails(
  245. type, rfh, origin, false, std::move(details),
  246. base::BindOnce(&PermissionGrantImpl::OnPermissionRequestResult, this,
  247. std::move(callback)));
  248. }
  249. const url::Origin& origin() const {
  250. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  251. return origin_;
  252. }
  253. HandleType handle_type() const {
  254. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  255. return handle_type_;
  256. }
  257. GrantType type() const {
  258. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  259. return type_;
  260. }
  261. void SetStatus(PermissionStatus new_status) {
  262. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  263. auto permission_changed = status_ != new_status;
  264. status_ = new_status;
  265. if (permission_changed) {
  266. NotifyPermissionStatusChanged();
  267. }
  268. }
  269. static void UpdateGrantPath(
  270. std::map<base::FilePath, PermissionGrantImpl*>& grants,
  271. const base::FilePath& old_path,
  272. const base::FilePath& new_path) {
  273. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  274. auto entry_it = base::ranges::find_if(
  275. grants,
  276. [&old_path](const auto& entry) { return entry.first == old_path; });
  277. if (entry_it == grants.end()) {
  278. // There must be an entry for an ancestor of this entry. Nothing to do
  279. // here.
  280. return;
  281. }
  282. DCHECK_EQ(entry_it->second->GetStatus(), PermissionStatus::GRANTED);
  283. auto* const grant_impl = entry_it->second;
  284. grant_impl->SetPath(new_path);
  285. // Update the permission grant's key in the map of active permissions.
  286. grants.erase(entry_it);
  287. grants.emplace(new_path, grant_impl);
  288. }
  289. protected:
  290. ~PermissionGrantImpl() override {
  291. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  292. if (context_) {
  293. context_->PermissionGrantDestroyed(this);
  294. }
  295. }
  296. private:
  297. void OnPermissionRequestResult(
  298. base::OnceCallback<void(PermissionRequestOutcome)> callback,
  299. blink::mojom::PermissionStatus status) {
  300. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  301. if (status == blink::mojom::PermissionStatus::GRANTED) {
  302. SetStatus(PermissionStatus::GRANTED);
  303. std::move(callback).Run(PermissionRequestOutcome::kUserGranted);
  304. } else {
  305. SetStatus(PermissionStatus::DENIED);
  306. std::move(callback).Run(PermissionRequestOutcome::kUserDenied);
  307. }
  308. }
  309. void SetPath(const base::FilePath& new_path) {
  310. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  311. if (path_ == new_path)
  312. return;
  313. path_ = new_path;
  314. NotifyPermissionStatusChanged();
  315. }
  316. SEQUENCE_CHECKER(sequence_checker_);
  317. base::WeakPtr<FileSystemAccessPermissionContext> const context_;
  318. const url::Origin origin_;
  319. const HandleType handle_type_;
  320. const GrantType type_;
  321. base::FilePath path_;
  322. // This member should only be updated via SetStatus().
  323. PermissionStatus status_ = PermissionStatus::ASK;
  324. };
  325. struct FileSystemAccessPermissionContext::OriginState {
  326. // Raw pointers, owned collectively by all the handles that reference this
  327. // grant. When last reference goes away this state is cleared as well by
  328. // PermissionGrantDestroyed().
  329. std::map<base::FilePath, PermissionGrantImpl*> read_grants;
  330. std::map<base::FilePath, PermissionGrantImpl*> write_grants;
  331. };
  332. FileSystemAccessPermissionContext::FileSystemAccessPermissionContext(
  333. content::BrowserContext* browser_context,
  334. const base::Clock* clock)
  335. : browser_context_(browser_context), clock_(clock) {
  336. DETACH_FROM_SEQUENCE(sequence_checker_);
  337. }
  338. FileSystemAccessPermissionContext::~FileSystemAccessPermissionContext() =
  339. default;
  340. scoped_refptr<content::FileSystemAccessPermissionGrant>
  341. FileSystemAccessPermissionContext::GetReadPermissionGrant(
  342. const url::Origin& origin,
  343. const base::FilePath& path,
  344. HandleType handle_type,
  345. UserAction user_action) {
  346. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  347. // operator[] might insert a new OriginState in |active_permissions_map_|,
  348. // but that is exactly what we want.
  349. auto& origin_state = active_permissions_map_[origin];
  350. auto*& existing_grant = origin_state.read_grants[path];
  351. scoped_refptr<PermissionGrantImpl> new_grant;
  352. if (existing_grant && existing_grant->handle_type() != handle_type) {
  353. // |path| changed from being a directory to being a file or vice versa,
  354. // don't just re-use the existing grant but revoke the old grant before
  355. // creating a new grant.
  356. existing_grant->SetStatus(PermissionStatus::DENIED);
  357. existing_grant = nullptr;
  358. }
  359. if (!existing_grant) {
  360. new_grant = base::MakeRefCounted<PermissionGrantImpl>(
  361. weak_factory_.GetWeakPtr(), origin, path, handle_type, GrantType::kRead,
  362. user_action);
  363. existing_grant = new_grant.get();
  364. }
  365. // If a parent directory is already readable this new grant should also be
  366. // readable.
  367. if (new_grant &&
  368. AncestorHasActivePermission(origin, path, GrantType::kRead)) {
  369. existing_grant->SetStatus(PermissionStatus::GRANTED);
  370. } else {
  371. switch (user_action) {
  372. case UserAction::kOpen:
  373. case UserAction::kSave:
  374. // Open and Save dialog only grant read access for individual files.
  375. if (handle_type == HandleType::kDirectory) {
  376. break;
  377. }
  378. [[fallthrough]];
  379. case UserAction::kDragAndDrop:
  380. // Drag&drop grants read access for all handles.
  381. existing_grant->SetStatus(PermissionStatus::GRANTED);
  382. break;
  383. case UserAction::kLoadFromStorage:
  384. case UserAction::kNone:
  385. break;
  386. }
  387. }
  388. return existing_grant;
  389. }
  390. scoped_refptr<content::FileSystemAccessPermissionGrant>
  391. FileSystemAccessPermissionContext::GetWritePermissionGrant(
  392. const url::Origin& origin,
  393. const base::FilePath& path,
  394. HandleType handle_type,
  395. UserAction user_action) {
  396. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  397. // operator[] might insert a new OriginState in |active_permissions_map_|,
  398. // but that is exactly what we want.
  399. auto& origin_state = active_permissions_map_[origin];
  400. auto*& existing_grant = origin_state.write_grants[path];
  401. scoped_refptr<PermissionGrantImpl> new_grant;
  402. if (existing_grant && existing_grant->handle_type() != handle_type) {
  403. // |path| changed from being a directory to being a file or vice versa,
  404. // don't just re-use the existing grant but revoke the old grant before
  405. // creating a new grant.
  406. existing_grant->SetStatus(PermissionStatus::DENIED);
  407. existing_grant = nullptr;
  408. }
  409. if (!existing_grant) {
  410. new_grant = base::MakeRefCounted<PermissionGrantImpl>(
  411. weak_factory_.GetWeakPtr(), origin, path, handle_type,
  412. GrantType::kWrite, user_action);
  413. existing_grant = new_grant.get();
  414. }
  415. // If a parent directory is already writable this new grant should also be
  416. // writable.
  417. if (new_grant &&
  418. AncestorHasActivePermission(origin, path, GrantType::kWrite)) {
  419. existing_grant->SetStatus(PermissionStatus::GRANTED);
  420. } else {
  421. switch (user_action) {
  422. case UserAction::kSave:
  423. // Only automatically grant write access for save dialogs.
  424. existing_grant->SetStatus(PermissionStatus::GRANTED);
  425. break;
  426. case UserAction::kOpen:
  427. case UserAction::kDragAndDrop:
  428. case UserAction::kLoadFromStorage:
  429. case UserAction::kNone:
  430. break;
  431. }
  432. }
  433. return existing_grant;
  434. }
  435. bool FileSystemAccessPermissionContext::CanObtainReadPermission(
  436. const url::Origin& origin) {
  437. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  438. return true;
  439. }
  440. bool FileSystemAccessPermissionContext::CanObtainWritePermission(
  441. const url::Origin& origin) {
  442. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  443. return true;
  444. }
  445. void FileSystemAccessPermissionContext::ConfirmSensitiveEntryAccess(
  446. const url::Origin& origin,
  447. PathType path_type,
  448. const base::FilePath& path,
  449. HandleType handle_type,
  450. UserAction user_action,
  451. content::GlobalRenderFrameHostId frame_id,
  452. base::OnceCallback<void(SensitiveEntryResult)> callback) {
  453. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  454. auto after_blocklist_check_callback = base::BindOnce(
  455. &FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist,
  456. GetWeakPtr(), origin, path, handle_type, user_action, frame_id,
  457. std::move(callback));
  458. CheckPathAgainstBlocklist(path_type, path, handle_type,
  459. std::move(after_blocklist_check_callback));
  460. }
  461. void FileSystemAccessPermissionContext::CheckPathAgainstBlocklist(
  462. PathType path_type,
  463. const base::FilePath& path,
  464. HandleType handle_type,
  465. base::OnceCallback<void(bool)> callback) {
  466. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  467. // TODO(https://crbug.com/1009970): Figure out what external paths should be
  468. // blocked. We could resolve the external path to a local path, and check for
  469. // blocked directories based on that, but that doesn't work well. Instead we
  470. // should have a separate Chrome OS only code path to block for example the
  471. // root of certain external file systems.
  472. if (path_type == PathType::kExternal) {
  473. std::move(callback).Run(/*should_block=*/false);
  474. return;
  475. }
  476. std::vector<BlockPathRule> extra_rules;
  477. extra_rules.emplace_back(browser_context_->GetPath().DirName(),
  478. BlockType::kBlockAllChildren);
  479. base::ThreadPool::PostTaskAndReplyWithResult(
  480. FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
  481. base::BindOnce(&ShouldBlockAccessToPath, path, handle_type, extra_rules),
  482. std::move(callback));
  483. }
  484. void FileSystemAccessPermissionContext::PerformAfterWriteChecks(
  485. std::unique_ptr<content::FileSystemAccessWriteItem> item,
  486. content::GlobalRenderFrameHostId frame_id,
  487. base::OnceCallback<void(AfterWriteCheckResult)> callback) {
  488. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  489. std::move(callback).Run(AfterWriteCheckResult::kAllow);
  490. }
  491. void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
  492. const url::Origin& origin,
  493. const base::FilePath& path,
  494. HandleType handle_type,
  495. UserAction user_action,
  496. content::GlobalRenderFrameHostId frame_id,
  497. base::OnceCallback<void(SensitiveEntryResult)> callback,
  498. bool should_block) {
  499. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  500. if (user_action == UserAction::kNone) {
  501. std::move(callback).Run(should_block ? SensitiveEntryResult::kAbort
  502. : SensitiveEntryResult::kAllowed);
  503. return;
  504. }
  505. // Chromium opens a dialog here, but in Electron's case we log and abort.
  506. if (should_block) {
  507. LOG(INFO) << path.value()
  508. << " is blocked by the blocklis and cannot be accessed";
  509. std::move(callback).Run(SensitiveEntryResult::kAbort);
  510. return;
  511. }
  512. std::move(callback).Run(SensitiveEntryResult::kAllowed);
  513. }
  514. void FileSystemAccessPermissionContext::MaybeEvictEntries(
  515. base::Value::Dict& dict) {
  516. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  517. std::vector<std::pair<base::Time, std::string>> entries;
  518. entries.reserve(dict.size());
  519. for (auto entry : dict) {
  520. // Don't evict the default ID.
  521. if (entry.first == kDefaultLastPickedDirectoryKey) {
  522. continue;
  523. }
  524. // If the data is corrupted and `entry.second` is for some reason not a
  525. // dict, it should be first in line for eviction.
  526. auto timestamp = base::Time::Min();
  527. if (entry.second.is_dict()) {
  528. timestamp = base::ValueToTime(entry.second.GetDict().Find(kTimestampKey))
  529. .value_or(base::Time::Min());
  530. }
  531. entries.emplace_back(timestamp, entry.first);
  532. }
  533. if (entries.size() <= max_ids_per_origin_) {
  534. return;
  535. }
  536. base::ranges::sort(entries);
  537. size_t entries_to_remove = entries.size() - max_ids_per_origin_;
  538. for (size_t i = 0; i < entries_to_remove; ++i) {
  539. bool did_remove_entry = dict.Remove(entries[i].second);
  540. DCHECK(did_remove_entry);
  541. }
  542. }
  543. void FileSystemAccessPermissionContext::SetLastPickedDirectory(
  544. const url::Origin& origin,
  545. const std::string& id,
  546. const base::FilePath& path,
  547. const PathType type) {
  548. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  549. // Create an entry into the nested dictionary.
  550. base::Value::Dict entry;
  551. entry.Set(kPathKey, base::FilePathToValue(path));
  552. entry.Set(kPathTypeKey, static_cast<int>(type));
  553. entry.Set(kTimestampKey, base::TimeToValue(clock_->Now()));
  554. auto it = id_pathinfo_map_.find(origin);
  555. if (it != id_pathinfo_map_.end()) {
  556. base::Value::Dict& dict = it->second;
  557. dict.Set(GenerateLastPickedDirectoryKey(id), std::move(entry));
  558. MaybeEvictEntries(dict);
  559. } else {
  560. base::Value::Dict dict;
  561. dict.Set(GenerateLastPickedDirectoryKey(id), std::move(entry));
  562. MaybeEvictEntries(dict);
  563. id_pathinfo_map_.insert(std::make_pair(origin, std::move(dict)));
  564. }
  565. }
  566. FileSystemAccessPermissionContext::PathInfo
  567. FileSystemAccessPermissionContext::GetLastPickedDirectory(
  568. const url::Origin& origin,
  569. const std::string& id) {
  570. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  571. auto it = id_pathinfo_map_.find(origin);
  572. PathInfo path_info;
  573. if (it == id_pathinfo_map_.end()) {
  574. return path_info;
  575. }
  576. auto* entry = it->second.FindDict(GenerateLastPickedDirectoryKey(id));
  577. if (!entry) {
  578. return path_info;
  579. }
  580. auto type_int =
  581. entry->FindInt(kPathTypeKey).value_or(static_cast<int>(PathType::kLocal));
  582. path_info.type = type_int == static_cast<int>(PathType::kExternal)
  583. ? PathType::kExternal
  584. : PathType::kLocal;
  585. path_info.path =
  586. base::ValueToFilePath(entry->Find(kPathKey)).value_or(base::FilePath());
  587. return path_info;
  588. }
  589. base::FilePath FileSystemAccessPermissionContext::GetWellKnownDirectoryPath(
  590. blink::mojom::WellKnownDirectory directory,
  591. const url::Origin& origin) {
  592. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  593. int key = base::PATH_START;
  594. switch (directory) {
  595. case blink::mojom::WellKnownDirectory::kDirDesktop:
  596. key = base::DIR_USER_DESKTOP;
  597. break;
  598. case blink::mojom::WellKnownDirectory::kDirDocuments:
  599. key = chrome::DIR_USER_DOCUMENTS;
  600. break;
  601. case blink::mojom::WellKnownDirectory::kDirDownloads:
  602. key = chrome::DIR_DEFAULT_DOWNLOADS;
  603. break;
  604. case blink::mojom::WellKnownDirectory::kDirMusic:
  605. key = chrome::DIR_USER_MUSIC;
  606. break;
  607. case blink::mojom::WellKnownDirectory::kDirPictures:
  608. key = chrome::DIR_USER_PICTURES;
  609. break;
  610. case blink::mojom::WellKnownDirectory::kDirVideos:
  611. key = chrome::DIR_USER_VIDEOS;
  612. break;
  613. }
  614. base::FilePath directory_path;
  615. base::PathService::Get(key, &directory_path);
  616. return directory_path;
  617. }
  618. std::u16string FileSystemAccessPermissionContext::GetPickerTitle(
  619. const blink::mojom::FilePickerOptionsPtr& options) {
  620. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  621. // TODO(asully): Consider adding custom strings for invocations of the file
  622. // picker, as well. Returning the empty string will fall back to the platform
  623. // default for the given picker type.
  624. std::u16string title;
  625. switch (options->type_specific_options->which()) {
  626. case blink::mojom::TypeSpecificFilePickerOptionsUnion::Tag::
  627. kDirectoryPickerOptions:
  628. title = l10n_util::GetStringUTF16(
  629. options->type_specific_options->get_directory_picker_options()
  630. ->request_writable
  631. ? IDS_FILE_SYSTEM_ACCESS_CHOOSER_OPEN_WRITABLE_DIRECTORY_TITLE
  632. : IDS_FILE_SYSTEM_ACCESS_CHOOSER_OPEN_READABLE_DIRECTORY_TITLE);
  633. break;
  634. case blink::mojom::TypeSpecificFilePickerOptionsUnion::Tag::
  635. kSaveFilePickerOptions:
  636. title = l10n_util::GetStringUTF16(
  637. IDS_FILE_SYSTEM_ACCESS_CHOOSER_OPEN_SAVE_FILE_TITLE);
  638. break;
  639. case blink::mojom::TypeSpecificFilePickerOptionsUnion::Tag::
  640. kOpenFilePickerOptions:
  641. break;
  642. }
  643. return title;
  644. }
  645. void FileSystemAccessPermissionContext::NotifyEntryMoved(
  646. const url::Origin& origin,
  647. const base::FilePath& old_path,
  648. const base::FilePath& new_path) {
  649. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  650. if (old_path == new_path) {
  651. return;
  652. }
  653. auto it = active_permissions_map_.find(origin);
  654. if (it != active_permissions_map_.end()) {
  655. PermissionGrantImpl::UpdateGrantPath(it->second.write_grants, old_path,
  656. new_path);
  657. PermissionGrantImpl::UpdateGrantPath(it->second.read_grants, old_path,
  658. new_path);
  659. }
  660. }
  661. void FileSystemAccessPermissionContext::OnFileCreatedFromShowSaveFilePicker(
  662. const GURL& file_picker_binding_context,
  663. const storage::FileSystemURL& url) {}
  664. void FileSystemAccessPermissionContext::CheckPathsAgainstEnterprisePolicy(
  665. std::vector<PathInfo> entries,
  666. content::GlobalRenderFrameHostId frame_id,
  667. EntriesAllowedByEnterprisePolicyCallback callback) {
  668. std::move(callback).Run(std::move(entries));
  669. }
  670. void FileSystemAccessPermissionContext::RevokeGrant(
  671. const url::Origin& origin,
  672. const base::FilePath& file_path) {
  673. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  674. auto origin_it = active_permissions_map_.find(origin);
  675. if (origin_it != active_permissions_map_.end()) {
  676. OriginState& origin_state = origin_it->second;
  677. for (auto& grant : origin_state.read_grants) {
  678. if (file_path.empty() || grant.first == file_path) {
  679. grant.second->SetStatus(PermissionStatus::ASK);
  680. }
  681. }
  682. for (auto& grant : origin_state.write_grants) {
  683. if (file_path.empty() || grant.first == file_path) {
  684. grant.second->SetStatus(PermissionStatus::ASK);
  685. }
  686. }
  687. }
  688. }
  689. bool FileSystemAccessPermissionContext::OriginHasReadAccess(
  690. const url::Origin& origin) {
  691. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  692. auto it = active_permissions_map_.find(origin);
  693. if (it != active_permissions_map_.end()) {
  694. return base::ranges::any_of(it->second.read_grants, [&](const auto& grant) {
  695. return grant.second->GetStatus() == PermissionStatus::GRANTED;
  696. });
  697. }
  698. return false;
  699. }
  700. bool FileSystemAccessPermissionContext::OriginHasWriteAccess(
  701. const url::Origin& origin) {
  702. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  703. auto it = active_permissions_map_.find(origin);
  704. if (it != active_permissions_map_.end()) {
  705. return base::ranges::any_of(
  706. it->second.write_grants, [&](const auto& grant) {
  707. return grant.second->GetStatus() == PermissionStatus::GRANTED;
  708. });
  709. }
  710. return false;
  711. }
  712. void FileSystemAccessPermissionContext::CleanupPermissions(
  713. const url::Origin& origin) {
  714. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  715. RevokeGrant(origin);
  716. }
  717. bool FileSystemAccessPermissionContext::AncestorHasActivePermission(
  718. const url::Origin& origin,
  719. const base::FilePath& path,
  720. GrantType grant_type) const {
  721. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  722. auto it = active_permissions_map_.find(origin);
  723. if (it == active_permissions_map_.end()) {
  724. return false;
  725. }
  726. const auto& relevant_grants = grant_type == GrantType::kWrite
  727. ? it->second.write_grants
  728. : it->second.read_grants;
  729. if (relevant_grants.empty()) {
  730. return false;
  731. }
  732. // Permissions are inherited from the closest ancestor.
  733. for (base::FilePath parent = path.DirName(); parent != parent.DirName();
  734. parent = parent.DirName()) {
  735. auto i = relevant_grants.find(parent);
  736. if (i != relevant_grants.end() && i->second &&
  737. i->second->GetStatus() == PermissionStatus::GRANTED) {
  738. return true;
  739. }
  740. }
  741. return false;
  742. }
  743. void FileSystemAccessPermissionContext::PermissionGrantDestroyed(
  744. PermissionGrantImpl* grant) {
  745. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  746. auto it = active_permissions_map_.find(grant->origin());
  747. if (it == active_permissions_map_.end()) {
  748. return;
  749. }
  750. auto& grants = grant->type() == GrantType::kRead ? it->second.read_grants
  751. : it->second.write_grants;
  752. auto grant_it = grants.find(grant->GetPath());
  753. // Any non-denied permission grants should have still been in our grants
  754. // list. If this invariant is violated we would have permissions that might
  755. // be granted but won't be visible in any UI because the permission context
  756. // isn't tracking them anymore.
  757. if (grant_it == grants.end()) {
  758. DCHECK_EQ(PermissionStatus::DENIED, grant->GetStatus());
  759. return;
  760. }
  761. // The grant in |grants| for this path might have been replaced with a
  762. // different grant. Only erase if it actually matches the grant that was
  763. // destroyed.
  764. if (grant_it->second == grant) {
  765. grants.erase(grant_it);
  766. }
  767. }
  768. base::WeakPtr<FileSystemAccessPermissionContext>
  769. FileSystemAccessPermissionContext::GetWeakPtr() {
  770. return weak_factory_.GetWeakPtr();
  771. }
  772. } // namespace electron