Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions crates/uv-pypi-types/src/requirement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ impl Requirement {
url,
} => {
// Redact the repository URL, but allow `git@`.
redact_git_credentials(&mut repository);
redact_credentials(&mut repository);

// Redact the PEP 508 URL.
let mut url = url.to_url();
redact_git_credentials(&mut url);
redact_credentials(&mut url);
let url = VerbatimUrl::from_url(url);

Self {
Expand Down Expand Up @@ -637,7 +637,7 @@ impl From<RequirementSource> for RequirementSourceWire {
let mut url = repository;

// Redact the credentials.
redact_git_credentials(&mut url);
redact_credentials(&mut url);

// Clear out any existing state.
url.set_fragment(None);
Expand Down Expand Up @@ -740,7 +740,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
repository.set_query(None);

// Redact the credentials.
redact_git_credentials(&mut repository);
redact_credentials(&mut repository);

// Create a PEP 508-compatible URL.
let mut url = Url::parse(&format!("git+{repository}"))?;
Expand Down Expand Up @@ -814,9 +814,9 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
}
}

/// Remove the credentials from a Git URL, allowing the generic `git` username (without a password)
/// Remove the credentials from a URL, allowing the generic `git` username (without a password)
/// in SSH URLs, as in, `ssh://git@github.com/...`.
pub fn redact_git_credentials(url: &mut Url) {
pub fn redact_credentials(url: &mut Url) {
// For URLs that use the `git` convention (i.e., `ssh://git@github.com/...`), avoid dropping the
// username.
if url.scheme() == "ssh" && url.username() == "git" && url.password().is_none() {
Expand Down
39 changes: 32 additions & 7 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ use uv_pep440::Version;
use uv_pep508::{split_scheme, MarkerEnvironment, MarkerTree, VerbatimUrl, VerbatimUrlError};
use uv_platform_tags::{TagCompatibility, TagPriority, Tags};
use uv_pypi_types::{
redact_git_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement,
RequirementSource, ResolverMarkerEnvironment,
redact_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement, RequirementSource,
ResolverMarkerEnvironment,
};
use uv_types::{BuildContext, HashStrategy};
use uv_workspace::{InstallTarget, Workspace};
Expand Down Expand Up @@ -3097,7 +3097,7 @@ fn locked_git_url(git_dist: &GitSourceDist) -> Url {
let mut url = git_dist.git.repository().clone();

// Redact the credentials.
redact_git_credentials(&mut url);
redact_credentials(&mut url);

// Clear out any existing state.
url.set_fragment(None);
Expand Down Expand Up @@ -3686,11 +3686,11 @@ fn normalize_requirement(
url,
} => {
// Redact the credentials.
redact_git_credentials(&mut repository);
redact_credentials(&mut repository);

// Redact the PEP 508 URL.
let mut url = url.to_url();
redact_git_credentials(&mut url);
redact_credentials(&mut url);
let url = VerbatimUrl::from_url(url);

Ok(Requirement {
Expand Down Expand Up @@ -3751,11 +3751,36 @@ fn normalize_requirement(
origin: None,
})
}
_ => Ok(Requirement {
RequirementSource::Registry {
specifier,
mut index,
} => {
if let Some(index) = index.as_mut() {
redact_credentials(index);
}
Ok(Requirement {
name: requirement.name,
extras: requirement.extras,
marker: requirement.marker,
source: RequirementSource::Registry { specifier, index },
origin: None,
})
}
RequirementSource::Url {
location,
subdirectory,
ext,
url,
} => Ok(Requirement {
name: requirement.name,
extras: requirement.extras,
marker: requirement.marker,
source: requirement.source,
source: RequirementSource::Url {
location,
subdirectory,
ext,
url,
},
origin: None,
}),
}
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use uv_fs::Simplified;
use uv_git::{GitReference, GIT_STORE};
use uv_normalize::PackageName;
use uv_pep508::{ExtraName, Requirement, UnnamedRequirement, VersionOrUrl};
use uv_pypi_types::{redact_git_credentials, ParsedUrl, RequirementSource, VerbatimParsedUrl};
use uv_pypi_types::{redact_credentials, ParsedUrl, RequirementSource, VerbatimParsedUrl};
use uv_python::{
EnvironmentPreference, Interpreter, PythonDownloads, PythonEnvironment, PythonInstallation,
PythonPreference, PythonRequest, PythonVariant, PythonVersionFile, VersionRequest,
Expand Down Expand Up @@ -448,7 +448,7 @@ pub(crate) async fn add(
GIT_STORE.insert(RepositoryUrl::new(&git), credentials);

// Redact the credentials.
redact_git_credentials(&mut git);
redact_credentials(&mut git);
};
Some(Source::Git {
git,
Expand Down
201 changes: 200 additions & 1 deletion crates/uv/tests/it/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6342,7 +6342,6 @@ fn lock_redact_git_pep508() -> Result<()> {
Ok(())
}

/// However, we don't currently avoid persisting Git credentials in `uv.lock`.
#[test]
fn lock_redact_git_sources() -> Result<()> {
let context = TestContext::new("3.12").with_filtered_link_mode_warning();
Expand Down Expand Up @@ -6439,6 +6438,206 @@ fn lock_redact_git_sources() -> Result<()> {
Ok(())
}

#[test]
fn lock_redact_index_sources() -> Result<()> {
let context = TestContext::new("3.12").with_filtered_link_mode_warning();
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);

let filters: Vec<_> = [(token.as_str(), "***")]
.into_iter()
.chain(context.filters())
.collect();

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "foo"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig>=2"]

[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"

[[tool.uv.index]]
name = "private"
url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/simple"

[tool.uv.sources]
iniconfig = { index = "private" }
"#,
)?;

uv_snapshot!(&filters, context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 2 packages in [TIME]
"###);

let lock = context.read("uv.lock");

insta::with_settings!({
filters => filters.clone(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"

[options]
exclude-newer = "2024-03-25T00:00:00Z"

[[package]]
name = "foo"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "iniconfig" },
]

[package.metadata]
requires-dist = [{ name = "iniconfig", specifier = ">=2", index = "https://public:heron@pypi-proxy.fly.dev/basic-auth/simple" }]

[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi-proxy.fly.dev/basic-auth/simple" }
sdist = { url = "https://pypi-proxy.fly.dev/basic-auth/files/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]
"###
);
});

// Re-run with `--locked`.
uv_snapshot!(&filters, context.lock().arg("--locked"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 2 packages in [TIME]
"###);

// Install from the lockfile.
uv_snapshot!(&filters, context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ foo==0.1.0 (from file://[TEMP_DIR]/)
+ iniconfig==2.0.0
"###);

Ok(())
}

/// We don't currently redact credentials from direct URLs, though.
#[test]
fn lock_redact_url_sources() -> Result<()> {
let context = TestContext::new("3.12").with_filtered_link_mode_warning();
let token = decode_token(common::READ_ONLY_GITHUB_TOKEN);

let filters: Vec<_> = [(token.as_str(), "***")]
.into_iter()
.chain(context.filters())
.collect();

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(r#"
[project]
name = "foo"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig>=2"]

[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"

[tool.uv.sources]
iniconfig = { url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }
"#)?;

uv_snapshot!(&filters, context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 2 packages in [TIME]
"###);

let lock = context.read("uv.lock");

insta::with_settings!({
filters => filters.clone(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"

[options]
exclude-newer = "2024-03-25T00:00:00Z"

[[package]]
name = "foo"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "iniconfig" },
]

[package.metadata]
requires-dist = [{ name = "iniconfig", url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }]

[[package]]
name = "iniconfig"
version = "2.0.0"
source = { url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }
wheels = [
{ url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" },
]
"###
);
});

// Re-run with `--locked`.
uv_snapshot!(&filters, context.lock().arg("--locked"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 2 packages in [TIME]
"###);

// Install from the lockfile.
uv_snapshot!(&filters, context.sync().arg("--frozen").arg("--reinstall").arg("--no-cache"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ foo==0.1.0 (from file://[TEMP_DIR]/)
+ iniconfig==2.0.0 (from https://public:heron@pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
"###);

Ok(())
}

/// Pass credentials for a named index via environment variables.
#[test]
fn lock_env_credentials() -> Result<()> {
Expand Down