file_system_access_permission_context.cc 34 KB

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