|
| 1 | +//! Example credential provider that stores credentials in a JSON file. |
| 2 | +//! This is not secure |
| 3 | +
|
| 4 | +use cargo_credential::{ |
| 5 | + Action, CacheControl, Credential, CredentialResponse, RegistryInfo, Secret, |
| 6 | +}; |
| 7 | +use std::{collections::HashMap, fs::File, io::ErrorKind}; |
| 8 | +type Error = Box<dyn std::error::Error + Send + Sync + 'static>; |
| 9 | + |
| 10 | +struct FileCredential; |
| 11 | + |
| 12 | +impl Credential for FileCredential { |
| 13 | + fn perform( |
| 14 | + &self, |
| 15 | + registry: &RegistryInfo, |
| 16 | + action: &Action, |
| 17 | + _args: &[&str], |
| 18 | + ) -> Result<CredentialResponse, cargo_credential::Error> { |
| 19 | + if registry.index_url != "https://github.com/rust-lang/crates.io-index" { |
| 20 | + // Restrict this provider to only work for crates.io. Cargo will skip it and attempt |
| 21 | + // another provider for any other registry. |
| 22 | + // |
| 23 | + // If a provider supports any registry, then this check should be omitted. |
| 24 | + return Err(cargo_credential::Error::UrlNotSupported); |
| 25 | + } |
| 26 | + |
| 27 | + // `Error::Other` takes a boxed `std::error::Error` type that causes Cargo to show the error. |
| 28 | + let mut creds = FileCredential::read().map_err(cargo_credential::Error::Other)?; |
| 29 | + |
| 30 | + match action { |
| 31 | + Action::Get(_) => { |
| 32 | + // Cargo requested a token, look it up. |
| 33 | + if let Some(token) = creds.get(registry.index_url) { |
| 34 | + Ok(CredentialResponse::Get { |
| 35 | + token: token.clone(), |
| 36 | + cache: CacheControl::Session, |
| 37 | + operation_independent: true, |
| 38 | + }) |
| 39 | + } else { |
| 40 | + // Credential providers should respond with `NotFound` when a credential can not be |
| 41 | + // found, allowing Cargo to attempt another provider. |
| 42 | + Err(cargo_credential::Error::NotFound) |
| 43 | + } |
| 44 | + } |
| 45 | + Action::Login(login_options) => { |
| 46 | + // The token for `cargo login` can come from the `login_options` parameter or i |
| 47 | + // interactively reading from stdin. |
| 48 | + // |
| 49 | + // `cargo_credential::read_token` automatically handles this. |
| 50 | + let token = cargo_credential::read_token(login_options, registry)?; |
| 51 | + creds.insert(registry.index_url.to_string(), token); |
| 52 | + |
| 53 | + FileCredential::write(&creds).map_err(cargo_credential::Error::Other)?; |
| 54 | + |
| 55 | + // Credentials were successfully stored. |
| 56 | + Ok(CredentialResponse::Login) |
| 57 | + } |
| 58 | + Action::Logout => { |
| 59 | + if creds.remove(registry.index_url).is_none() { |
| 60 | + // If the user attempts to log out from a registry that has no credentials |
| 61 | + // stored, then NotFound is the appropriate error. |
| 62 | + Err(cargo_credential::Error::NotFound) |
| 63 | + } else { |
| 64 | + // Credentials were successfully erased. |
| 65 | + Ok(CredentialResponse::Logout) |
| 66 | + } |
| 67 | + } |
| 68 | + // If a credential provider doesn't support a given operation, it should respond with `OperationNotSupported`. |
| 69 | + _ => Err(cargo_credential::Error::OperationNotSupported), |
| 70 | + } |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +impl FileCredential { |
| 75 | + fn read() -> Result<HashMap<String, Secret<String>>, Error> { |
| 76 | + match File::open("cargo-credentials.json") { |
| 77 | + Ok(f) => Ok(serde_json::from_reader(f)?), |
| 78 | + Err(e) if e.kind() == ErrorKind::NotFound => Ok(HashMap::new()), |
| 79 | + Err(e) => Err(e)?, |
| 80 | + } |
| 81 | + } |
| 82 | + fn write(value: &HashMap<String, Secret<String>>) -> Result<(), Error> { |
| 83 | + let file = File::create("cargo-credentials.json")?; |
| 84 | + Ok(serde_json::to_writer_pretty(file, value)?) |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +fn main() { |
| 89 | + cargo_credential::main(FileCredential); |
| 90 | +} |
0 commit comments