// Copyright © 2022 Kim Altintop // SPDX-License-Identifier: GPL-2.0-only WITH openvpn-openssl-exception use std::{ collections::BTreeSet, path::PathBuf, }; use anyhow::{ anyhow, ensure, }; use clap::ValueHint; use either::{ Either, Left, Right, }; use url::Url; use crate::{ cfg, cmd::{ self, args::Refname, }, git, metadata::{ self, git::META_FILE_ID, IdentityId, }, paths, }; mod edit; pub use edit::{ edit, Edit, }; mod init; pub use init::{ init, Init, }; mod show; pub use show::{ show, Show, }; mod sign; pub use sign::{ sign, Sign, }; #[derive(Debug, clap::Subcommand)] #[allow(clippy::large_enum_variant)] pub enum Cmd { /// Initialise a fresh identity Init(Init), /// Display the identity docment Show(Show), /// Edit the identity document Edit(Edit), /// Sign a proposed identity document Sign(Sign), } impl Cmd { pub fn run(self) -> cmd::Result { match self { Self::Init(args) => init(args).map(cmd::IntoOutput::into_output), Self::Show(args) => show(args).map(cmd::IntoOutput::into_output), Self::Edit(args) => edit(args).map(cmd::IntoOutput::into_output), Self::Sign(args) => sign(args).map(cmd::IntoOutput::into_output), } } } #[derive(Clone, Debug, clap::Args)] pub struct Common { /// Path to the 'keyring' repository // nb. not using from_global here -- current_dir doesn't make sense here as // the default #[clap( long, value_parser, value_name = "DIR", env = "GIT_DIR", default_value_os_t = paths::ids(), value_hint = ValueHint::DirPath, )] git_dir: PathBuf, /// Identity to operate on /// /// If not set as an option nor in the environment, the value of `it.id` in /// the git config is tried. #[clap(short = 'I', long = "identity", value_name = "ID", env = "IT_ID")] id: Option, } impl Common { pub fn resolve(&self) -> cmd::Result<(git2::Repository, Refname)> { let repo = git::repo::open(&self.git_dir)?; let refname = identity_ref( match self.id { Some(id) => Left(id), None => Right(repo.config()?), } .as_ref(), )?; Ok((repo, refname)) } } pub fn identity_ref(id: Either<&IdentityId, &git2::Config>) -> cmd::Result { let id = id.either( |iid| Ok(iid.to_string()), |cfg| { cfg::git::identity(cfg)? .ok_or_else(|| anyhow!("'{}' not set", cfg::git::IT_ID)) .map(|iid| iid.to_string()) }, )?; Ok(Refname::try_from(format!("refs/heads/it/ids/{id}"))?) } #[derive(serde::Serialize, serde::Deserialize)] struct Editable { keys: metadata::KeySet<'static>, #[serde(flatten)] roles: metadata::identity::Roles, mirrors: BTreeSet, expires: Option, custom: metadata::Custom, } impl From for Editable { fn from( metadata::Identity { keys, roles, mirrors, expires, custom, .. }: metadata::Identity, ) -> Self { Self { keys, roles, mirrors, expires, custom, } } } impl TryFrom for metadata::Identity { type Error = crate::Error; fn try_from( Editable { keys, roles, mirrors, expires, custom, }: Editable, ) -> Result { ensure!(!keys.is_empty(), "keys cannot be empty"); ensure!( !roles.is_threshold(), "flat threshold is deprecated, please specify the root keys explicity" ); Ok(Self { fmt_version: Default::default(), prev: None, keys, roles, mirrors, expires, custom, }) } }