use crate::{ key, client_management, config, schema::{self, RealmID}, server, token, user_management, UIDCError, group_management, token_management, }; use clap::{Parser, Subcommand}; use microrm::prelude::*; #[derive(Debug, Parser)] #[clap(author, version, about, long_about = None)] struct RootArgs { #[clap(short, long, 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), /// 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: microrm::DB, realm_id: RealmID, } impl RootArgs { async fn run(&self) -> Result<(), UIDCError> { if let Command::Init = self.command { return self.init().await; } let db = microrm::DB::new(schema::schema(), &self.db, microrm::CreateMode::MustExist).map_err(|_| UIDCError::Abort("Error accessing database"))?; let realm_id = db.query_interface().get().by(schema::Realm::Shortname, self.realm.as_str()).one()?.ok_or(UIDCError::Abort("no such realm"))?.id(); let ra = RunArgs { db: db, realm_id }; match &self.command { Command::Init => unreachable!(), Command::Config(v) => v.run(ra).await, Command::Client(v) => v.run(ra).await, Command::Group(v) => v.run(ra).await, Command::Key(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 maybedb = microrm::DB::new(schema::schema(), &self.db, microrm::CreateMode::MustExist); if maybedb.is_ok() { return Err(UIDCError::Abort("Database already initialized, not overwriting!")); } log::info!("Initializing!"); let db = microrm::DB::new( schema::schema(), &self.db, microrm::CreateMode::AllowNewDatabase, ) .expect("Unable to initialize database!"); // create primary realm db.query_interface() .add(&schema::Realm { shortname: "primary".to_string(), })?; Ok(()) } } #[derive(Debug, Subcommand)] enum KeyCommand { Inspect, Generate, } #[derive(Debug, Parser)] struct KeyArgs { #[clap(subcommand)] command: KeyCommand, } impl KeyArgs { async fn run(&self, args: RunArgs) -> Result<(), UIDCError> { match &self.command { KeyCommand::Inspect => { key::inspect(&args.db, args.realm_id) } KeyCommand::Generate => { key::generate(&args.db, args.realm_id) } } } } #[derive(Debug, Subcommand)] enum ClientCommand { Create { name: String }, List, Inspect { name: String }, } #[derive(Debug, Parser)] struct ClientArgs { #[clap(subcommand)] command: ClientCommand, } impl ClientArgs { async fn run(&self, args: RunArgs) -> Result<(), UIDCError> { match &self.command { ClientCommand::Create { name } => { client_management::create(&args.db, args.realm_id, name) } ClientCommand::List => { todo!() } ClientCommand::Inspect { name } => { client_management::inspect(&args.db, args.realm_id, 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> { let qi = args.db.query_interface(); match &self.command { ConfigCommand::Dump => { let config = config::Config::build_from(&qi, None); println!("config: {:?}", config); } ConfigCommand::Set { key, value } => { todo!() } ConfigCommand::Load { toml_path } => { let config = config::Config::build_from(&qi, Some(toml_path)); config.save(&qi); } } Ok(()) } } #[derive(Debug, Subcommand)] enum GroupCommand { Create { group_name: String }, Members { group_name: String }, 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> { let qi = args.db.query_interface(); match &self.command { GroupCommand::Create { group_name } => { group_management::create_group(&qi, args.realm_id, group_name)?; }, GroupCommand::Members { group_name } => { group_management::list_members(&qi, args.realm_id, group_name.as_str())?; }, GroupCommand::AttachRole { group_name, role_name } => { group_management::attach_role(&qi, args.realm_id, group_name.as_str(), role_name.as_str())?; }, GroupCommand::DetachRole { group_name, role_name } => { group_management::detach_role(&qi, args.realm_id, group_name.as_str(), role_name.as_str())?; }, GroupCommand::AttachUser { group_name, username } => { group_management::attach_user(&qi, args.realm_id, group_name.as_str(), username.as_str())?; }, GroupCommand::DetachUser { group_name, username } => { group_management::detach_user(&qi, args.realm_id, group_name.as_str(), username.as_str())?; } } Ok(()) } } #[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.query_interface(), 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: String, }, } #[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.query_interface(), None); let qi = args.db.query_interface(); match &self.command { TokenCommand::GenerateAuth { client, username, scopes, } => { let token = token_management::create_auth_token(&qi, &config, args.realm_id, client.as_str(), username.as_str(), scopes.as_str())?; println!("{}", token); Ok(()) } TokenCommand::GenerateRefresh { client, username, scopes, } => { todo!() } TokenCommand::Inspect { token } => { todo!() } } } } #[derive(Debug, Subcommand)] enum RoleCommand { List, Create { name: String }, Delete { name: String }, } #[derive(Debug, Parser)] struct RoleArgs { #[clap(subcommand)] command: RoleCommand, } impl RoleArgs { async fn run(&self, args: RunArgs) -> Result<(), UIDCError> { let qi = args.db.query_interface(); // let config = config::Config::build_from(&qi, None); match &self.command { RoleCommand::List => { todo!() }, RoleCommand::Create { name } => { let add_result = qi.add(&schema::Role { realm: args.realm_id, shortname: name.clone() }); match add_result { Ok(_role_id) => { println!("Added role {}", name); } Err(_) => { println!("Failed to add role {}!", name); }, } }, RoleCommand::Delete { name } => { qi.delete().by(schema::Role::Realm, &args.realm_id).by(schema::Role::Shortname, name.as_str()).exec().unwrap(); }, } Ok(()) } } #[derive(Debug, Subcommand)] enum UserCommand { List, Create { username: String }, Auth { username: String, #[clap(short = 'p', long, parse(from_occurrences))] change_password: usize, }, Inspect { username: String, } } #[derive(Debug, Parser)] struct UserArgs { #[clap(subcommand)] command: UserCommand, } impl UserArgs { async fn run(&self, args: RunArgs) -> Result<(), UIDCError> { let qi = args.db.query_interface(); match &self.command { UserCommand::List => user_management::list(&qi, args.realm_id), UserCommand::Create { username } => { user_management::create(&qi, args.realm_id, username.as_str()) } UserCommand::Auth { username, change_password } => user_management::change_auth( &qi, args.realm_id, username.as_str(), *change_password > 0, ), UserCommand::Inspect { username } => user_management::inspect(&qi, args.realm_id, username.as_str()), } } } pub fn invoked() { let args = RootArgs::parse(); match smol::block_on(args.run()) { Ok(_) => (), Err(e) => { log::error!("Error occured while running command: {}", e); } } }