use crate::{ client_management, config, key::{self, KeyType}, schema::{self, UIDCDatabase}, scope_management, server, token_management, UIDCError, }; use clap::{Parser, Subcommand}; use microrm::{prelude::*, schema::Stored}; use microrm::cli::Autogenerate; mod role; mod user; impl microrm::cli::CLIError for UIDCError { fn no_such_entity(ename: &'static str, keys: String) -> Self { UIDCError::AbortString(format!("no such {ename} matching {keys}")) } } #[derive(Debug, Parser)] #[clap(author, version, about, long_about = None)] struct RootArgs { #[clap(short, long, env = "UIDC_DB", default_value_t = String::from("uidc.db"))] /// Database path db: String, #[clap(short, long, default_value_t = String::from("primary"))] /// Which realm to use, for non-server only realm: String, #[clap(subcommand)] command: Command, } #[derive(Debug, Subcommand)] enum Command { /// database initialization Init, /// OAuth2 client management Client(ClientArgs), /// general configuration Config(ConfigArgs), /// permissions grouping management Group(GroupArgs), /// key management Key(KeyArgs), /// scope management Scope(ScopeArgs), /// run the actual OIDC server Server(ServerArgs), /// manual token generation and inspection Token(TokenArgs), /// role management Role(RoleArgs), /// user management User(UserArgs), } struct RunArgs { db: UIDCDatabase, realm: Stored, } impl RootArgs { async fn run(self) -> Result<(), UIDCError> { if let Command::Init = self.command { return self.init().await; } let db = UIDCDatabase::open_path(&self.db) .map_err(|e| UIDCError::AbortString(format!("Error accessing database: {:?}", e)))?; let realm = db .realms .keyed(&self.realm) .get()? .ok_or(UIDCError::Abort("no such realm"))?; let ra = RunArgs { db, realm, }; match self.command { Command::Init => unreachable!(), Command::Config(v) => v.run(ra).await, Command::Key(v) => v.run(ra).await, Command::Client(v) => v.run(ra).await, Command::Scope(v) => v.run(ra).await, Command::Group(v) => v.run(ra).await, Command::Server(v) => v.run(ra).await, Command::Token(v) => v.run(ra).await, Command::Role(v) => v.run(ra).await, Command::User(v) => v.run(ra).await, } } async fn init(&self) -> Result<(), UIDCError> { // first check to see if the database is already vaguely set up let db = UIDCDatabase::open_path(&self.db) .map_err(|e| UIDCError::AbortString(format!("Error accessing database: {:?}", e)))?; log::info!("Initializing!"); let primary_realm = "primary".to_string(); if db.realms.keyed(&primary_realm).get()?.is_some() { log::warn!("Already initialized with primary realm!"); return Ok(()); } // create primary realm db.realms.insert(schema::Realm { shortname: primary_realm, ..Default::default() })?; Ok(()) } } #[derive(Debug, Subcommand)] enum KeyCommand { /// Print details of all keys List, /// Generate a new key; see types subcommand for valid options. Generate { key_type: KeyType }, /// List what keytypes are supported Types, /// Remove a key from use Remove { key_id: String }, } #[derive(Debug, Parser)] struct KeyArgs { #[clap(subcommand)] command: KeyCommand, } impl KeyArgs { async fn run(self, args: RunArgs) -> Result<(), UIDCError> { match &self.command { KeyCommand::List => key::list(&args.realm), KeyCommand::Generate { key_type } => { key::generate_in(&args.realm, *key_type)?; return Ok(()); } KeyCommand::Types => { for (spec, _kty) in key::KEY_TYPE_NAMES { println!("- {}", spec); } Ok(()) } KeyCommand::Remove { key_id } => key::remove(&args.realm, key_id), } } } #[derive(Debug, Subcommand)] enum ClientCommand { Create { /// Name for the new client name: String, /// Signing key type to use for this client. Default is ed25519. key_type: Option, }, List, Inspect { name: String, }, } #[derive(Debug, Parser)] struct ClientArgs { #[clap(subcommand)] command: ClientCommand, } impl ClientArgs { async fn run(&self, args: RunArgs) -> Result<(), UIDCError> { todo!() /* match self.command { ClientCommand::Create { name, key_type } => { client_management::create(&args.realm, name, key_type.unwrap_or(KeyType::Ed25519)) } ClientCommand::List => { todo!() } ClientCommand::Inspect { name } => client_management::inspect(&args.realm, name), } */ } } #[derive(Debug, Subcommand)] enum ConfigCommand { Dump, Load { toml_path: String }, Set { key: String, value: String }, } #[derive(Debug, Parser)] struct ConfigArgs { #[clap(subcommand)] command: ConfigCommand, } impl ConfigArgs { async fn run(self, args: RunArgs) -> Result<(), UIDCError> { match &self.command { ConfigCommand::Dump => { let config = config::Config::build_from(&args.db, None); println!("{:?}", config); } ConfigCommand::Set { key, value } => { args.db.persistent_config.keyed(key).delete()?; args.db.persistent_config.insert(schema::PersistentConfig { key: key.clone(), value: value.clone(), })?; } ConfigCommand::Load { toml_path } => { let config = config::Config::build_from(&args.db, Some(toml_path)); config.save(&args.db); } } Ok(()) } } #[derive(Debug, Subcommand)] enum GroupCommand { Create { group_name: String, }, Members { group_name: String, }, Roles { group_name: String, }, List, AttachRole { group_name: String, role_name: String, }, DetachRole { group_name: String, role_name: String, }, AttachUser { group_name: String, username: String, }, DetachUser { group_name: String, username: String, }, } #[derive(Debug, Parser)] struct GroupArgs { #[clap(subcommand)] command: GroupCommand, } impl GroupArgs { async fn run(self, args: RunArgs) -> Result<(), UIDCError> { todo!() /*match &self.command { GroupCommand::Create { group_name } => { group_management::create_group(&args.realm, group_name)?; } GroupCommand::Members { group_name } => { group_management::list_members(&args.realm, group_name)?; } GroupCommand::Roles { group_name } => { group_management::list_roles(&args.realm, group_name)?; } GroupCommand::List => { group_management::list_groups(&args.realm)?; } GroupCommand::AttachRole { group_name, role_name, } => { group_management::attach_role(&args.realm, group_name, role_name)?; } GroupCommand::DetachRole { group_name, role_name, } => { group_management::detach_role(&args.realm, group_name, role_name)?; } GroupCommand::AttachUser { group_name, username, } => { group_management::attach_user(&args.realm, group_name, username)?; } GroupCommand::DetachUser { group_name, username, } => { group_management::detach_user(&args.realm, group_name, username)?; } }*/ // Ok(()) } } #[derive(Debug, Subcommand)] enum ScopeCommand { AttachRole { scope_name: String, role_name: String, }, Create { scope_name: String, }, DetachRole { scope_name: String, role_name: String, }, Inspect { scope_name: String, }, List, } #[derive(Debug, Parser)] struct ScopeArgs { #[clap(subcommand)] command: ScopeCommand, } impl ScopeArgs { async fn run(self, args: RunArgs) -> Result<(), UIDCError> { match &self.command { ScopeCommand::AttachRole { scope_name, role_name, } => scope_management::attach_role(&args.realm, scope_name, role_name), ScopeCommand::Create { scope_name } => { scope_management::create_scope(&args.realm, scope_name) } ScopeCommand::DetachRole { scope_name, role_name, } => scope_management::detach_role(&args.realm, scope_name, role_name), ScopeCommand::Inspect { scope_name } => { scope_management::inspect_scope(&args.realm, scope_name) } ScopeCommand::List => scope_management::list_scopes(&args.realm), } } } #[derive(Debug, Parser)] struct ServerArgs { #[clap(short, long)] port: Option, } impl ServerArgs { async fn run(self, args: RunArgs) -> Result<(), UIDCError> { let config = config::Config::build_from(&args.db, None); server::run_server(args.db, config, self.port.unwrap_or(2114)).await } } #[derive(Debug, Subcommand)] enum TokenCommand { GenerateAuth { #[clap(short, long)] client: String, #[clap(short, long)] username: String, #[clap(short, long)] scopes: String, }, GenerateRefresh { #[clap(short, long)] client: String, #[clap(short, long)] username: String, #[clap(short, long)] scopes: String, }, Inspect { token: Option, }, } #[derive(Debug, Parser)] struct TokenArgs { #[clap(subcommand)] command: TokenCommand, } impl TokenArgs { async fn run(self, args: RunArgs) -> Result<(), UIDCError> { let config = config::Config::build_from(&args.db, None); match &self.command { TokenCommand::GenerateAuth { client, username, scopes, } => { let token = token_management::create_auth_token( &args.realm, &config, client, username, scopes, )?; println!("{}", token); Ok(()) } TokenCommand::GenerateRefresh { client, username, scopes, } => { let token = token_management::create_refresh_token( &args.realm, &config, client, username, scopes, )?; println!("{}", token); Ok(()) } TokenCommand::Inspect { token } => { token_management::inspect_token(&config, &args.realm, token.as_ref()) } } } } #[derive(Debug, Subcommand)] enum RoleCommand { List, Create { name: String }, Delete { name: String }, } #[derive(Debug, Parser)] struct RoleArgs { #[clap(subcommand)] command: Autogenerate, } impl RoleArgs { async fn run(self, args: RunArgs) -> Result<(), UIDCError> { self.command.perform(&args.realm, &args.realm.roles, &args.realm.roles) } } #[derive(Debug, Subcommand)] enum UserCommand { Auth { username: String, #[clap(short = 'p', long, action = clap::ArgAction::Count)] change_password: usize, #[clap(short = 't', long, action = clap::ArgAction::Count)] change_totp: usize, }, } #[derive(Debug, Parser)] struct UserArgs { #[clap(subcommand)] command: Autogenerate, } impl UserArgs { async fn run(self, args: RunArgs) -> Result<(), UIDCError> { self.command.perform(&args.realm, &args.realm.users, &args.realm.users) } } pub fn invoked() { let args = RootArgs::parse(); match smol::block_on(args.run()) { Ok(_) => (), Err(e) => { log::error!("Error occured while running command: {}", e); } } }