Skip to content

Commit e58b84d

Browse files
committed
breaking change(cargo-credential)
Changes the JSON format for cache:expires
1 parent b8099be commit e58b84d

File tree

8 files changed

+179
-27
lines changed

8 files changed

+179
-27
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ anyhow = "1.0.75"
2020
base64 = "0.21.3"
2121
bytesize = "1.3"
2222
cargo = { path = "" }
23-
cargo-credential = { version = "0.3.0", path = "credential/cargo-credential" }
23+
cargo-credential = { version = "0.4.0", path = "credential/cargo-credential" }
2424
cargo-credential-libsecret = { version = "0.3.1", path = "credential/cargo-credential-libsecret" }
2525
cargo-credential-wincred = { version = "0.3.0", path = "credential/cargo-credential-wincred" }
2626
cargo-credential-macos-keychain = { version = "0.3.0", path = "credential/cargo-credential-macos-keychain" }

credential/cargo-credential/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cargo-credential"
3-
version = "0.3.1"
3+
version = "0.4.0"
44
edition.workspace = true
55
license.workspace = true
66
repository = "https://github.com/rust-lang/cargo"

credential/cargo-credential/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Create a Cargo project with this as a dependency:
1818
# Add this to your Cargo.toml:
1919

2020
[dependencies]
21-
cargo-credential = "0.3"
21+
cargo-credential = "0.4"
2222
```
2323

2424
And then include a `main.rs` binary which implements the `Credential` trait, and calls

credential/cargo-credential/src/lib.rs

Lines changed: 165 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub use secret::Secret;
5050
use stdio::stdin_stdout_to_console;
5151

5252
/// Message sent by the credential helper on startup
53-
#[derive(Serialize, Deserialize, Clone, Debug)]
53+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
5454
pub struct CredentialHello {
5555
// Protocol versions supported by the credential process.
5656
pub v: Vec<u32>,
@@ -70,7 +70,7 @@ impl Credential for UnsupportedCredential {
7070
}
7171

7272
/// Message sent by Cargo to the credential helper after the hello
73-
#[derive(Serialize, Deserialize, Clone, Debug)]
73+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
7474
#[serde(rename_all = "kebab-case")]
7575
pub struct CredentialRequest<'a> {
7676
// Cargo will respond with the highest common protocol supported by both.
@@ -84,7 +84,7 @@ pub struct CredentialRequest<'a> {
8484
pub args: Vec<&'a str>,
8585
}
8686

87-
#[derive(Serialize, Deserialize, Clone, Debug)]
87+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
8888
#[serde(rename_all = "kebab-case")]
8989
pub struct RegistryInfo<'a> {
9090
/// Registry index url
@@ -98,7 +98,7 @@ pub struct RegistryInfo<'a> {
9898
pub headers: Vec<String>,
9999
}
100100

101-
#[derive(Serialize, Deserialize, Clone, Debug)]
101+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
102102
#[non_exhaustive]
103103
#[serde(tag = "kind", rename_all = "kebab-case")]
104104
pub enum Action<'a> {
@@ -121,7 +121,7 @@ impl<'a> Display for Action<'a> {
121121
}
122122
}
123123

124-
#[derive(Serialize, Deserialize, Clone, Debug)]
124+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
125125
#[serde(rename_all = "kebab-case")]
126126
pub struct LoginOptions<'a> {
127127
/// Token passed on the command line via --token or from stdin
@@ -133,7 +133,7 @@ pub struct LoginOptions<'a> {
133133
}
134134

135135
/// A record of what kind of operation is happening that we should generate a token for.
136-
#[derive(Serialize, Deserialize, Clone, Debug)]
136+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
137137
#[non_exhaustive]
138138
#[serde(tag = "operation", rename_all = "kebab-case")]
139139
pub enum Operation<'a> {
@@ -172,12 +172,13 @@ pub enum Operation<'a> {
172172
}
173173

174174
/// Message sent by the credential helper
175-
#[derive(Serialize, Deserialize, Clone, Debug)]
175+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
176176
#[serde(tag = "kind", rename_all = "kebab-case")]
177177
#[non_exhaustive]
178178
pub enum CredentialResponse {
179179
Get {
180180
token: Secret<String>,
181+
#[serde(flatten)]
181182
cache: CacheControl,
182183
operation_independent: bool,
183184
},
@@ -187,14 +188,17 @@ pub enum CredentialResponse {
187188
Unknown,
188189
}
189190

190-
#[derive(Serialize, Deserialize, Clone, Debug)]
191-
#[serde(rename_all = "kebab-case")]
191+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
192+
#[serde(tag = "cache", rename_all = "kebab-case")]
192193
#[non_exhaustive]
193194
pub enum CacheControl {
194195
/// Do not cache this result.
195196
Never,
196197
/// Cache this result and use it for subsequent requests in the current Cargo invocation until the specified time.
197-
Expires(#[serde(with = "time::serde::timestamp")] OffsetDateTime),
198+
Expires {
199+
#[serde(with = "time::serde::timestamp")]
200+
expiration: OffsetDateTime,
201+
},
198202
/// Cache this result and use it for all subsequent requests in the current Cargo invocation.
199203
Session,
200204
#[serde(other)]
@@ -242,11 +246,7 @@ fn doit(
242246
if len == 0 {
243247
return Ok(());
244248
}
245-
let request: CredentialRequest = serde_json::from_str(&buffer)?;
246-
if request.v != PROTOCOL_VERSION_1 {
247-
return Err(format!("unsupported protocol version {}", request.v).into());
248-
}
249-
249+
let request = deserialize_request(&buffer)?;
250250
let response = stdin_stdout_to_console(|| {
251251
credential.perform(&request.registry, &request.action, &request.args)
252252
})?;
@@ -256,6 +256,17 @@ fn doit(
256256
}
257257
}
258258

259+
/// Deserialize a request from Cargo.
260+
fn deserialize_request(
261+
value: &str,
262+
) -> Result<CredentialRequest<'_>, Box<dyn std::error::Error + Send + Sync>> {
263+
let request: CredentialRequest = serde_json::from_str(&value)?;
264+
if request.v != PROTOCOL_VERSION_1 {
265+
return Err(format!("unsupported protocol version {}", request.v).into());
266+
}
267+
Ok(request)
268+
}
269+
259270
/// Read a line of text from stdin.
260271
pub fn read_line() -> Result<String, io::Error> {
261272
let mut buf = String::new();
@@ -282,3 +293,142 @@ pub fn read_token(
282293

283294
Ok(Secret::from(read_line().map_err(Box::new)?))
284295
}
296+
297+
#[cfg(test)]
298+
mod tests {
299+
use super::*;
300+
301+
#[test]
302+
fn unsupported_version() {
303+
// This shouldn't ever happen in practice, since the credential provider signals to Cargo which
304+
// protocol versions it supports, and Cargo should only attempt to use one of those.
305+
let msg = r#"{"v":999, "registry": {"index-url":""}, "args":[], "kind": "unexpected"}"#;
306+
assert_eq!(
307+
"unsupported protocol version 999",
308+
deserialize_request(msg).unwrap_err().to_string()
309+
);
310+
}
311+
312+
#[test]
313+
fn cache_control() {
314+
let cc = CacheControl::Expires {
315+
expiration: OffsetDateTime::from_unix_timestamp(1693928537).unwrap(),
316+
};
317+
let json = serde_json::to_string(&cc).unwrap();
318+
assert_eq!(json, r#"{"cache":"expires","expiration":1693928537}"#);
319+
320+
let cc = CacheControl::Session;
321+
let json = serde_json::to_string(&cc).unwrap();
322+
assert_eq!(json, r#"{"cache":"session"}"#);
323+
324+
let cc: CacheControl = serde_json::from_str(r#"{"cache":"unknown-kind"}"#).unwrap();
325+
assert_eq!(cc, CacheControl::Unknown);
326+
327+
assert_eq!(
328+
"missing field `expiration`",
329+
serde_json::from_str::<CacheControl>(r#"{"cache":"expires"}"#)
330+
.unwrap_err()
331+
.to_string()
332+
);
333+
}
334+
335+
#[test]
336+
fn credential_response() {
337+
let cr = CredentialResponse::Get {
338+
cache: CacheControl::Never,
339+
operation_independent: true,
340+
token: Secret::from("value".to_string()),
341+
};
342+
let json = serde_json::to_string(&cr).unwrap();
343+
assert_eq!(
344+
json,
345+
r#"{"kind":"get","token":"value","cache":"never","operation_independent":true}"#
346+
);
347+
348+
let cr = CredentialResponse::Login;
349+
let json = serde_json::to_string(&cr).unwrap();
350+
assert_eq!(json, r#"{"kind":"login"}"#);
351+
352+
let cr: CredentialResponse =
353+
serde_json::from_str(r#"{"kind":"unknown-kind","extra-data":true}"#).unwrap();
354+
assert_eq!(cr, CredentialResponse::Unknown);
355+
356+
let cr: CredentialResponse =
357+
serde_json::from_str(r#"{"kind":"login","extra-data":true}"#).unwrap();
358+
assert_eq!(cr, CredentialResponse::Login);
359+
360+
let cr: CredentialResponse = serde_json::from_str(r#"{"kind":"get","token":"value","cache":"never","operation_independent":true,"extra-field-ignored":123}"#).unwrap();
361+
assert_eq!(
362+
cr,
363+
CredentialResponse::Get {
364+
cache: CacheControl::Never,
365+
operation_independent: true,
366+
token: Secret::from("value".to_string())
367+
}
368+
);
369+
}
370+
371+
#[test]
372+
fn credential_request() {
373+
let get_oweners = CredentialRequest {
374+
v: PROTOCOL_VERSION_1,
375+
args: vec![],
376+
registry: RegistryInfo {
377+
index_url: "url",
378+
name: None,
379+
headers: vec![],
380+
},
381+
action: Action::Get(Operation::Owners { name: "pkg" }),
382+
};
383+
384+
let json = serde_json::to_string(&get_oweners).unwrap();
385+
assert_eq!(
386+
json,
387+
r#"{"v":1,"registry":{"index-url":"url"},"kind":"get","operation":"owners","name":"pkg"}"#
388+
);
389+
390+
let cr: CredentialRequest =
391+
serde_json::from_str(r#"{"extra-1":true,"v":1,"registry":{"index-url":"url","extra-2":true},"kind":"get","operation":"owners","name":"pkg","args":[]}"#).unwrap();
392+
assert_eq!(cr, get_oweners);
393+
}
394+
395+
#[test]
396+
fn credential_request_logout() {
397+
let unknown = CredentialRequest {
398+
v: PROTOCOL_VERSION_1,
399+
args: vec![],
400+
registry: RegistryInfo {
401+
index_url: "url",
402+
name: None,
403+
headers: vec![],
404+
},
405+
action: Action::Logout,
406+
};
407+
408+
let cr: CredentialRequest = serde_json::from_str(
409+
r#"{"v":1,"registry":{"index-url":"url"},"kind":"logout","extra-1":true,"args":[]}"#,
410+
)
411+
.unwrap();
412+
assert_eq!(cr, unknown);
413+
}
414+
415+
#[test]
416+
fn credential_request_unknown() {
417+
let unknown = CredentialRequest {
418+
v: PROTOCOL_VERSION_1,
419+
args: vec![],
420+
registry: RegistryInfo {
421+
index_url: "",
422+
name: None,
423+
headers: vec![],
424+
},
425+
action: Action::Unknown,
426+
};
427+
428+
let cr: CredentialRequest = serde_json::from_str(
429+
r#"{"v":1,"registry":{"index-url":""},"kind":"unexpected-1","extra-1":true,"args":[]}"#,
430+
)
431+
.unwrap();
432+
assert_eq!(cr, unknown);
433+
}
434+
}

src/cargo/util/auth/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ fn auth_token_optional(
563563
let token = Secret::from(token);
564564
tracing::trace!("found token");
565565
let expiration = match cache_control {
566-
CacheControl::Expires(expiration) => Some(expiration),
566+
CacheControl::Expires { expiration } => Some(expiration),
567567
CacheControl::Session => None,
568568
CacheControl::Never | _ => return Ok(Some(token)),
569569
};

src/doc/src/reference/unstable.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ Actual messages must not contain newlines.
11541154
"operation":"read",
11551155
// Registry information
11561156
"registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"},
1157-
// Additional command-line args
1157+
// Additional command-line args (optional)
11581158
"args":[]
11591159
}
11601160
```
@@ -1178,7 +1178,7 @@ Actual messages must not contain newlines.
11781178
"cksum":"...",
11791179
// Registry information
11801180
"registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"},
1181-
// Additional command-line args
1181+
// Additional command-line args (optional)
11821182
"args":[]
11831183
}
11841184
```
@@ -1195,8 +1195,10 @@ Actual messages must not contain newlines.
11951195
// Cache control. Can be one of the following:
11961196
// * "never"
11971197
// * "session"
1198-
// * { "expires": UNIX timestamp }
1199-
"cache":{"expires":1684251794},
1198+
// * "expires"
1199+
"cache":"expires",
1200+
// Unix timestamp (only for "cache": "expires")
1201+
"expiration":1693942857,
12001202
// Is the token operation independent?
12011203
"operation_independent":true
12021204
}}

tests/testsuite/credential_process.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -520,13 +520,13 @@ fn token_caching() {
520520

521521
// Token should not be re-used if it is expired
522522
let expired_provider = build_provider(
523-
"test-cred",
524-
r#"{"Ok":{"kind":"get","token":"sekrit","cache":{"expires":0},"operation_independent":true}}"#,
523+
"expired_provider",
524+
r#"{"Ok":{"kind":"get","token":"sekrit","cache":"expires","expiration":0,"operation_independent":true}}"#,
525525
);
526526

527527
// Token should not be re-used for a different operation if it is not operation_independent
528528
let non_independent_provider = build_provider(
529-
"test-cred",
529+
"non_independent_provider",
530530
r#"{"Ok":{"kind":"get","token":"sekrit","cache":"session","operation_independent":false}}"#,
531531
);
532532

0 commit comments

Comments
 (0)