feat(registry): support granular allowence of specifier types

This commit is contained in:
daimond113 2024-12-11 21:31:00 +01:00
parent 36e6f16ca6
commit 16ab05ec72
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
4 changed files with 79 additions and 57 deletions

View file

@ -19,10 +19,10 @@ To create an index, create a new repository and add a `config.toml` file with
the following content: the following content:
```toml title="config.toml" ```toml title="config.toml"
# The URL of the registry API # the URL of the registry API
api = "https://registry.acme.local/" api = "https://registry.acme.local/"
# Package download URL (optional) # package download URL (optional)
download = "{API_URL}/v0/packages/{PACKAGE}/{PACKAGE_VERSION}/{PACKAGE_TARGET}" download = "{API_URL}/v0/packages/{PACKAGE}/{PACKAGE_VERSION}/{PACKAGE_TARGET}"
# the client ID of the GitHub OAuth app (optional) # the client ID of the GitHub OAuth app (optional)
@ -33,13 +33,16 @@ git_allowed = true
# whether to allow packages which depend on packages from other registries # whether to allow packages which depend on packages from other registries
# (default: false) # (default: false)
other_registries_allowed = true other_registries_allowed = ["https://git.acme.local/index"]
# whether to allow packages with Wally dependencies (default: false) # whether to allow packages with Wally dependencies (default: false)
wally_allowed = false wally_allowed = false
# the maximum size of the archive in bytes (default: 4MB) # the maximum size of the archive in bytes (default: 4MB)
max_archive_size = 4194304 max_archive_size = 4194304
# the scripts packages present in the `init` command selection by default
scripts_packages = ["pesde/scripts_rojo"]
``` ```
- **api**: The URL of the registry API. See below for more information. - **api**: The URL of the registry API. See below for more information.
@ -60,18 +63,24 @@ max_archive_size = 4194304
- **github_oauth_client_id**: This is required if you use GitHub OAuth for - **github_oauth_client_id**: This is required if you use GitHub OAuth for
authentication. See below for more information. authentication. See below for more information.
- **git_allowed**: Whether to allow packages with Git dependencies. This is - **git_allowed**: Whether to allow packages with Git dependencies. This can be
optional and defaults to `false`. either a bool or a list of allowed repository URLs. This is optional and
defaults to `false`.
- **other_registries_allowed**: Whether to allow packages which depend on - **other_registries_allowed**: Whether to allow packages which depend on
packages from other registries. This is optional and defaults to `false`. packages from other registries. This can be either a bool or a list of
allowed index repository URLs. This is optional and defaults to `false`.
- **wally_allowed**: Whether to allow packages with Wally dependencies. This is - **wally_allowed**: Whether to allow packages with Wally dependencies. This can
be either a bool or a list of allowed index repository URLs. This is
optional and defaults to `false`. optional and defaults to `false`.
- **max_archive_size**: The maximum size of the archive in bytes. This is - **max_archive_size**: The maximum size of the archive in bytes. This is
optional and defaults to `4194304` (4MB). optional and defaults to `4194304` (4MB).
- **scripts_packages**: The scripts packages present in the `init` command
selection by default. This is optional and defaults to none.
You should then push this repository to [GitHub](https://github.com/). You should then push this repository to [GitHub](https://github.com/).
## Configuring the registry ## Configuring the registry
@ -88,8 +97,8 @@ has access to the index repository. We recommend using a separate account
for this purpose. for this purpose.
<Aside> <Aside>
For a GitHub account the password **must** be a personal access token. For For a GitHub account the password **must** be a personal access token. For instructions on how to
instructions on how to create a personal access token, see the [GitHub create a personal access token, see the [GitHub
documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens). documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).
The access token must have read and write access to the index repository. The access token must have read and write access to the index repository.
</Aside> </Aside>

View file

@ -304,7 +304,7 @@ pub async fn publish_package(
.filter(|index| match gix::Url::try_from(*index) { .filter(|index| match gix::Url::try_from(*index) {
Ok(url) => config Ok(url) => config
.other_registries_allowed .other_registries_allowed
.is_allowed(source.repo_url().clone(), url), .is_allowed_or_same(source.repo_url().clone(), url),
Err(_) => false, Err(_) => false,
}) })
.is_none() .is_none()
@ -315,16 +315,13 @@ pub async fn publish_package(
} }
} }
DependencySpecifiers::Wally(specifier) => { DependencySpecifiers::Wally(specifier) => {
if !config.wally_allowed {
return Err(Error::InvalidArchive(
"wally dependencies are not allowed".into(),
));
}
if specifier if specifier
.index .index
.as_ref() .as_deref()
.filter(|index| index.parse::<url::Url>().is_ok()) .filter(|index| match gix::Url::try_from(*index) {
Ok(url) => config.wally_allowed.is_allowed(url),
Err(_) => false,
})
.is_none() .is_none()
{ {
return Err(Error::InvalidArchive(format!( return Err(Error::InvalidArchive(format!(
@ -332,15 +329,15 @@ pub async fn publish_package(
))); )));
} }
} }
DependencySpecifiers::Git(_) => { DependencySpecifiers::Git(specifier) => {
if !config.git_allowed { if !config.git_allowed.is_allowed(specifier.repo.clone()) {
return Err(Error::InvalidArchive( return Err(Error::InvalidArchive(
"git dependencies are not allowed".into(), "git dependencies are not allowed".into(),
)); ));
} }
} }
DependencySpecifiers::Workspace(_) => { DependencySpecifiers::Workspace(_) => {
// workspace specifiers are to be transformed into Pesde specifiers by the sender // workspace specifiers are to be transformed into pesde specifiers by the sender
return Err(Error::InvalidArchive( return Err(Error::InvalidArchive(
"non-transformed workspace dependency".into(), "non-transformed workspace dependency".into(),
)); ));

View file

@ -9,6 +9,7 @@ use pesde::{
matching_globs_old_behaviour, matching_globs_old_behaviour,
scripts::ScriptName, scripts::ScriptName,
source::{ source::{
git_index::GitBasedSource,
pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource}, pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource},
specifiers::DependencySpecifiers, specifiers::DependencySpecifiers,
traits::PackageSource, traits::PackageSource,
@ -362,10 +363,6 @@ info: otherwise, the file was deemed unnecessary, if you don't understand why, p
} }
} }
#[cfg(feature = "wally-compat")]
let mut has_wally = false;
let mut has_git = false;
for specifier in manifest for specifier in manifest
.dependencies .dependencies
.values_mut() .values_mut()
@ -389,8 +386,6 @@ info: otherwise, the file was deemed unnecessary, if you don't understand why, p
} }
#[cfg(feature = "wally-compat")] #[cfg(feature = "wally-compat")]
DependencySpecifiers::Wally(specifier) => { DependencySpecifiers::Wally(specifier) => {
has_wally = true;
let index_name = specifier let index_name = specifier
.index .index
.as_deref() .as_deref()
@ -406,9 +401,7 @@ info: otherwise, the file was deemed unnecessary, if you don't understand why, p
.to_string(), .to_string(),
); );
} }
DependencySpecifiers::Git(_) => { DependencySpecifiers::Git(_) => {}
has_git = true;
}
DependencySpecifiers::Workspace(spec) => { DependencySpecifiers::Workspace(spec) => {
let pkg_ref = WorkspacePackageSource let pkg_ref = WorkspacePackageSource
.resolve(spec, project, target_kind, &mut HashSet::new()) .resolve(spec, project, target_kind, &mut HashSet::new())
@ -570,8 +563,7 @@ info: otherwise, the file was deemed unnecessary, if you don't understand why, p
.get(&self.index) .get(&self.index)
.context(format!("missing index {}", self.index))?; .context(format!("missing index {}", self.index))?;
let source = PesdePackageSource::new(index_url.clone()); let source = PesdePackageSource::new(index_url.clone());
source PackageSource::refresh(&source, project)
.refresh(project)
.await .await
.context("failed to refresh source")?; .context("failed to refresh source")?;
let config = source let config = source
@ -587,15 +579,31 @@ info: otherwise, the file was deemed unnecessary, if you don't understand why, p
); );
} }
manifest.all_dependencies().context("dependency conflict")?; let deps = manifest.all_dependencies().context("dependency conflict")?;
if !config.git_allowed && has_git { if let Some((disallowed, _)) = deps.iter().find(|(_, (spec, _))| match spec {
anyhow::bail!("git dependencies are not allowed on this index"); DependencySpecifiers::Pesde(spec) => {
!config.other_registries_allowed.is_allowed_or_same(
source.repo_url().clone(),
manifest
.indices
.get(spec.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME))
.unwrap()
.clone(),
)
} }
DependencySpecifiers::Git(spec) => !config.git_allowed.is_allowed(spec.repo.clone()),
#[cfg(feature = "wally-compat")] #[cfg(feature = "wally-compat")]
if !config.wally_allowed && has_wally { DependencySpecifiers::Wally(spec) => !config.wally_allowed.is_allowed(
anyhow::bail!("wally dependencies are not allowed on this index"); manifest
.wally_indices
.get(spec.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME))
.unwrap()
.clone(),
),
_ => false,
}) {
anyhow::bail!("dependency `{disallowed}` is not allowed on this index");
} }
if self.dry_run { if self.dry_run {

View file

@ -274,22 +274,30 @@ impl Default for AllowedRegistries {
} }
} }
impl AllowedRegistries { // strips .git suffix to allow for more flexible matching
/// Whether the given URL is allowed fn simplify_url(mut url: Url) -> Url {
pub fn is_allowed(&self, mut this: Url, mut external: Url) -> bool { url.path = url.path.strip_suffix(b".git").unwrap_or(&url.path).into();
// strip .git suffix to allow for more flexible matching url
this.path = this.path.strip_suffix(b".git").unwrap_or(&this.path).into(); }
external.path = external
.path
.strip_suffix(b".git")
.unwrap_or(&external.path)
.into();
this == external impl AllowedRegistries {
|| (match self { fn _is_allowed(&self, url: &Url) -> bool {
match self {
Self::All(all) => *all, Self::All(all) => *all,
Self::Specific(urls) => urls.contains(&this) || urls.contains(&external), Self::Specific(urls) => urls.contains(url),
}) }
}
/// Whether the given URL is allowed
pub fn is_allowed(&self, url: Url) -> bool {
self._is_allowed(&simplify_url(url))
}
/// Whether the given URL is allowed, or is the same as the given URL
pub fn is_allowed_or_same(&self, this: Url, external: Url) -> bool {
let this = simplify_url(this);
let external = simplify_url(external);
(this == external) || self._is_allowed(&external) || self._is_allowed(&this)
} }
} }
@ -302,13 +310,13 @@ pub struct IndexConfig {
pub download: Option<String>, pub download: Option<String>,
/// Whether Git is allowed as a source for publishing packages /// Whether Git is allowed as a source for publishing packages
#[serde(default)] #[serde(default)]
pub git_allowed: bool, pub git_allowed: AllowedRegistries,
/// Whether other registries are allowed as a source for publishing packages /// Whether other registries are allowed as a source for publishing packages
#[serde(default)] #[serde(default)]
pub other_registries_allowed: AllowedRegistries, pub other_registries_allowed: AllowedRegistries,
/// Whether Wally is allowed as a source for publishing packages /// Whether Wally is allowed as a source for publishing packages
#[serde(default)] #[serde(default)]
pub wally_allowed: bool, pub wally_allowed: AllowedRegistries,
/// The OAuth client ID for GitHub /// The OAuth client ID for GitHub
#[serde(default)] #[serde(default)]
pub github_oauth_client_id: Option<String>, pub github_oauth_client_id: Option<String>,