file_system_access_permission_context.cc 34 KB

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