Skip to content

Commit 9215aa6

Browse files
authored
refactor(node/npm): separate out permission check from npm resolvers (#27511)
Decouples permissions from the npm resolvers (towards moving the resolvers out of the cli crate)
1 parent 79c0b2c commit 9215aa6

File tree

12 files changed

+185
-191
lines changed

12 files changed

+185
-191
lines changed

cli/factory.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ use crate::npm::CliNpmResolver;
7171
use crate::npm::CliNpmResolverCreateOptions;
7272
use crate::npm::CliNpmResolverManagedSnapshotOption;
7373
use crate::npm::CreateInNpmPkgCheckerOptions;
74+
use crate::npm::NpmRegistryReadPermissionChecker;
75+
use crate::npm::NpmRegistryReadPermissionCheckerMode;
7476
use crate::resolver::CjsTracker;
7577
use crate::resolver::CliDenoResolver;
7678
use crate::resolver::CliNpmReqResolver;
@@ -941,6 +943,19 @@ impl CliFactory {
941943
let cjs_tracker = self.cjs_tracker()?.clone();
942944
let pkg_json_resolver = self.pkg_json_resolver().clone();
943945
let npm_req_resolver = self.npm_req_resolver().await?;
946+
let npm_registry_permission_checker = {
947+
let mode = if cli_options.use_byonm() {
948+
NpmRegistryReadPermissionCheckerMode::Byonm
949+
} else if let Some(node_modules_dir) = cli_options.node_modules_dir_path()
950+
{
951+
NpmRegistryReadPermissionCheckerMode::Local(node_modules_dir.clone())
952+
} else {
953+
NpmRegistryReadPermissionCheckerMode::Global(
954+
self.npm_cache_dir()?.root_dir().to_path_buf(),
955+
)
956+
};
957+
Arc::new(NpmRegistryReadPermissionChecker::new(self.sys(), mode))
958+
};
944959

945960
Ok(CliMainWorkerFactory::new(
946961
self.blob_store().clone(),
@@ -968,13 +983,14 @@ impl CliFactory {
968983
self.module_load_preparer().await?.clone(),
969984
node_code_translator.clone(),
970985
node_resolver.clone(),
971-
npm_req_resolver.clone(),
972-
cli_npm_resolver.clone(),
973986
NpmModuleLoader::new(
974987
self.cjs_tracker()?.clone(),
975988
fs.clone(),
976989
node_code_translator.clone(),
977990
),
991+
npm_registry_permission_checker,
992+
npm_req_resolver.clone(),
993+
cli_npm_resolver.clone(),
978994
self.parsed_source_cache().clone(),
979995
self.resolver().await?.clone(),
980996
self.sys(),

cli/module_loader.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ use crate::graph_util::ModuleGraphBuilder;
6666
use crate::node::CliNodeCodeTranslator;
6767
use crate::node::CliNodeResolver;
6868
use crate::npm::CliNpmResolver;
69+
use crate::npm::NpmRegistryReadPermissionChecker;
6970
use crate::resolver::CjsTracker;
7071
use crate::resolver::CliNpmReqResolver;
7172
use crate::resolver::CliResolver;
@@ -221,9 +222,10 @@ struct SharedCliModuleLoaderState {
221222
module_load_preparer: Arc<ModuleLoadPreparer>,
222223
node_code_translator: Arc<CliNodeCodeTranslator>,
223224
node_resolver: Arc<CliNodeResolver>,
225+
npm_module_loader: NpmModuleLoader,
226+
npm_registry_permission_checker: Arc<NpmRegistryReadPermissionChecker>,
224227
npm_req_resolver: Arc<CliNpmReqResolver>,
225228
npm_resolver: Arc<dyn CliNpmResolver>,
226-
npm_module_loader: NpmModuleLoader,
227229
parsed_source_cache: Arc<ParsedSourceCache>,
228230
resolver: Arc<CliResolver>,
229231
sys: CliSys,
@@ -281,9 +283,10 @@ impl CliModuleLoaderFactory {
281283
module_load_preparer: Arc<ModuleLoadPreparer>,
282284
node_code_translator: Arc<CliNodeCodeTranslator>,
283285
node_resolver: Arc<CliNodeResolver>,
286+
npm_module_loader: NpmModuleLoader,
287+
npm_registry_permission_checker: Arc<NpmRegistryReadPermissionChecker>,
284288
npm_req_resolver: Arc<CliNpmReqResolver>,
285289
npm_resolver: Arc<dyn CliNpmResolver>,
286-
npm_module_loader: NpmModuleLoader,
287290
parsed_source_cache: Arc<ParsedSourceCache>,
288291
resolver: Arc<CliResolver>,
289292
sys: CliSys,
@@ -307,9 +310,10 @@ impl CliModuleLoaderFactory {
307310
module_load_preparer,
308311
node_code_translator,
309312
node_resolver,
313+
npm_module_loader,
314+
npm_registry_permission_checker,
310315
npm_req_resolver,
311316
npm_resolver,
312-
npm_module_loader,
313317
parsed_source_cache,
314318
resolver,
315319
sys,
@@ -348,7 +352,10 @@ impl CliModuleLoaderFactory {
348352
sys: self.shared.sys.clone(),
349353
graph_container,
350354
in_npm_pkg_checker: self.shared.in_npm_pkg_checker.clone(),
351-
npm_resolver: self.shared.npm_resolver.clone(),
355+
npm_registry_permission_checker: self
356+
.shared
357+
.npm_registry_permission_checker
358+
.clone(),
352359
});
353360
CreateModuleLoaderResult {
354361
module_loader,
@@ -1095,7 +1102,7 @@ struct CliNodeRequireLoader<TGraphContainer: ModuleGraphContainer> {
10951102
sys: CliSys,
10961103
graph_container: TGraphContainer,
10971104
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
1098-
npm_resolver: Arc<dyn CliNpmResolver>,
1105+
npm_registry_permission_checker: Arc<NpmRegistryReadPermissionChecker>,
10991106
}
11001107

11011108
impl<TGraphContainer: ModuleGraphContainer> NodeRequireLoader
@@ -1112,7 +1119,9 @@ impl<TGraphContainer: ModuleGraphContainer> NodeRequireLoader
11121119
return Ok(std::borrow::Cow::Borrowed(path));
11131120
}
11141121
}
1115-
self.npm_resolver.ensure_read_permission(permissions, path)
1122+
self
1123+
.npm_registry_permission_checker
1124+
.ensure_read_permission(permissions, path)
11161125
}
11171126

11181127
fn load_text_file_lossy(

cli/npm/byonm.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
// Copyright 2018-2025 the Deno authors. MIT license.
22

3-
use std::borrow::Cow;
43
use std::path::Path;
54
use std::sync::Arc;
65

7-
use deno_core::error::AnyError;
86
use deno_core::serde_json;
97
use deno_resolver::npm::ByonmNpmResolver;
108
use deno_resolver::npm::ByonmNpmResolverCreateOptions;
119
use deno_resolver::npm::CliNpmReqResolver;
12-
use deno_runtime::deno_node::NodePermissions;
1310
use deno_runtime::ops::process::NpmProcessStateProvider;
1411
use node_resolver::NpmPackageFolderResolver;
1512

@@ -73,21 +70,6 @@ impl CliNpmResolver for CliByonmNpmResolver {
7370
self.root_node_modules_dir()
7471
}
7572

76-
fn ensure_read_permission<'a>(
77-
&self,
78-
permissions: &mut dyn NodePermissions,
79-
path: &'a Path,
80-
) -> Result<Cow<'a, Path>, AnyError> {
81-
if !path
82-
.components()
83-
.any(|c| c.as_os_str().to_ascii_lowercase() == "node_modules")
84-
{
85-
permissions.check_read_path(path).map_err(Into::into)
86-
} else {
87-
Ok(Cow::Borrowed(path))
88-
}
89-
}
90-
9173
fn check_state_hash(&self) -> Option<u64> {
9274
// it is very difficult to determine the check state hash for byonm
9375
// so we just return None to signify check caching is not supported

cli/npm/managed/mod.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use deno_npm_cache::NpmCacheSetting;
2424
use deno_path_util::fs::canonicalize_path_maybe_not_exists;
2525
use deno_resolver::npm::CliNpmReqResolver;
2626
use deno_runtime::colors;
27-
use deno_runtime::deno_node::NodePermissions;
2827
use deno_runtime::ops::process::NpmProcessStateProvider;
2928
use deno_semver::package::PackageNv;
3029
use deno_semver::package::PackageReq;
@@ -167,6 +166,7 @@ fn create_inner(
167166
sys.clone(),
168167
npm_rc.clone(),
169168
));
169+
170170
let fs_resolver = create_npm_fs_resolver(
171171
npm_cache.clone(),
172172
&npm_install_deps_provider,
@@ -754,14 +754,6 @@ impl CliNpmResolver for ManagedCliNpmResolver {
754754
self.fs_resolver.node_modules_path()
755755
}
756756

757-
fn ensure_read_permission<'a>(
758-
&self,
759-
permissions: &mut dyn NodePermissions,
760-
path: &'a Path,
761-
) -> Result<Cow<'a, Path>, AnyError> {
762-
self.fs_resolver.ensure_read_permission(permissions, path)
763-
}
764-
765757
fn check_state_hash(&self) -> Option<u64> {
766758
// We could go further and check all the individual
767759
// npm packages, but that's probably overkill.

cli/npm/managed/resolvers/common.rs

Lines changed: 0 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,17 @@
33
pub mod bin_entries;
44
pub mod lifecycle_scripts;
55

6-
use std::borrow::Cow;
7-
use std::collections::HashMap;
8-
use std::io::ErrorKind;
96
use std::path::Path;
107
use std::path::PathBuf;
11-
use std::sync::Arc;
12-
use std::sync::Mutex;
138

149
use async_trait::async_trait;
1510
use deno_ast::ModuleSpecifier;
16-
use deno_core::anyhow::Context;
1711
use deno_core::error::AnyError;
18-
use deno_core::futures;
19-
use deno_core::futures::StreamExt;
2012
use deno_npm::NpmPackageCacheFolderId;
2113
use deno_npm::NpmPackageId;
22-
use deno_npm::NpmResolutionPackage;
23-
use deno_runtime::deno_node::NodePermissions;
2414
use node_resolver::errors::PackageFolderResolveError;
25-
use sys_traits::FsCanonicalize;
2615

2716
use super::super::PackageCaching;
28-
use crate::npm::CliNpmTarballCache;
29-
use crate::sys::CliSys;
3017

3118
/// Part of the resolution that interacts with the file system.
3219
#[async_trait(?Send)]
@@ -63,101 +50,4 @@ pub trait NpmPackageFsResolver: Send + Sync {
6350
&self,
6451
caching: PackageCaching<'a>,
6552
) -> Result<(), AnyError>;
66-
67-
#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
68-
fn ensure_read_permission<'a>(
69-
&self,
70-
permissions: &mut dyn NodePermissions,
71-
path: &'a Path,
72-
) -> Result<Cow<'a, Path>, AnyError>;
73-
}
74-
75-
#[derive(Debug)]
76-
pub struct RegistryReadPermissionChecker {
77-
sys: CliSys,
78-
cache: Mutex<HashMap<PathBuf, PathBuf>>,
79-
registry_path: PathBuf,
80-
}
81-
82-
impl RegistryReadPermissionChecker {
83-
pub fn new(sys: CliSys, registry_path: PathBuf) -> Self {
84-
Self {
85-
sys,
86-
registry_path,
87-
cache: Default::default(),
88-
}
89-
}
90-
91-
pub fn ensure_registry_read_permission<'a>(
92-
&self,
93-
permissions: &mut dyn NodePermissions,
94-
path: &'a Path,
95-
) -> Result<Cow<'a, Path>, AnyError> {
96-
if permissions.query_read_all() {
97-
return Ok(Cow::Borrowed(path)); // skip permissions checks below
98-
}
99-
100-
// allow reading if it's in the node_modules
101-
let is_path_in_node_modules = path.starts_with(&self.registry_path)
102-
&& path
103-
.components()
104-
.all(|c| !matches!(c, std::path::Component::ParentDir));
105-
106-
if is_path_in_node_modules {
107-
let mut cache = self.cache.lock().unwrap();
108-
let mut canonicalize =
109-
|path: &Path| -> Result<Option<PathBuf>, AnyError> {
110-
match cache.get(path) {
111-
Some(canon) => Ok(Some(canon.clone())),
112-
None => match self.sys.fs_canonicalize(path) {
113-
Ok(canon) => {
114-
cache.insert(path.to_path_buf(), canon.clone());
115-
Ok(Some(canon))
116-
}
117-
Err(e) => {
118-
if e.kind() == ErrorKind::NotFound {
119-
return Ok(None);
120-
}
121-
Err(AnyError::from(e)).with_context(|| {
122-
format!("failed canonicalizing '{}'", path.display())
123-
})
124-
}
125-
},
126-
}
127-
};
128-
if let Some(registry_path_canon) = canonicalize(&self.registry_path)? {
129-
if let Some(path_canon) = canonicalize(path)? {
130-
if path_canon.starts_with(registry_path_canon) {
131-
return Ok(Cow::Owned(path_canon));
132-
}
133-
} else if path.starts_with(registry_path_canon)
134-
|| path.starts_with(&self.registry_path)
135-
{
136-
return Ok(Cow::Borrowed(path));
137-
}
138-
}
139-
}
140-
141-
permissions.check_read_path(path).map_err(Into::into)
142-
}
143-
}
144-
145-
/// Caches all the packages in parallel.
146-
pub async fn cache_packages(
147-
packages: &[NpmResolutionPackage],
148-
tarball_cache: &Arc<CliNpmTarballCache>,
149-
) -> Result<(), AnyError> {
150-
let mut futures_unordered = futures::stream::FuturesUnordered::new();
151-
for package in packages {
152-
futures_unordered.push(async move {
153-
tarball_cache
154-
.ensure_package(&package.id.nv, &package.dist)
155-
.await
156-
});
157-
}
158-
while let Some(result) = futures_unordered.next().await {
159-
// surface the first error
160-
result?;
161-
}
162-
Ok(())
16353
}

0 commit comments

Comments
 (0)