cli.rs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. use crate::{
  2. config::Config,
  3. key::{self, KeyType},
  4. realm::RealmHelper,
  5. schema::{self, UIDCDatabase},
  6. server, UIDCError,
  7. };
  8. use clap::{Parser, Subcommand};
  9. use microrm::cli::Autogenerate;
  10. use microrm::{prelude::*, schema::Stored};
  11. mod client;
  12. mod group;
  13. mod role;
  14. mod scope;
  15. mod user;
  16. impl microrm::cli::CLIError for UIDCError {
  17. fn no_such_entity(ename: &'static str, keys: String) -> Self {
  18. UIDCError::AbortString(format!("no such {ename} matching {keys}"))
  19. }
  20. }
  21. #[derive(Debug, Parser)]
  22. #[clap(author, version, about, long_about = None)]
  23. struct RootArgs {
  24. #[clap(short, long, env = "UIDC_CONFIG", default_value_t = String::from("uidc.toml"))]
  25. /// Configuration file path
  26. config_path: String,
  27. #[clap(short, long, default_value_t = String::from("primary"))]
  28. /// Which realm to use, for non-serve commands only
  29. realm: String,
  30. #[clap(subcommand)]
  31. command: Command,
  32. }
  33. #[derive(Debug, Subcommand)]
  34. enum Command {
  35. /// database initialization
  36. Init,
  37. /// OAuth2 client management
  38. Client {
  39. #[clap(subcommand)]
  40. cmd: Autogenerate<client::ClientInterface>,
  41. },
  42. // /// general configuration
  43. // Config(ConfigArgs),
  44. /// permissions grouping management
  45. Group {
  46. #[clap(subcommand)]
  47. cmd: Autogenerate<group::GroupInterface>,
  48. },
  49. /// key management
  50. Key(KeyArgs),
  51. /// scope management
  52. Scope {
  53. #[clap(subcommand)]
  54. cmd: Autogenerate<scope::ScopeInterface>,
  55. },
  56. /// run the actual OIDC server
  57. Serve(ServerArgs),
  58. /// manual token generation and inspection
  59. Token {
  60. #[clap(subcommand)]
  61. cmd: TokenCommand,
  62. },
  63. /// role management
  64. Role {
  65. #[clap(subcommand)]
  66. cmd: Autogenerate<role::RoleInterface>,
  67. },
  68. /// user management
  69. User {
  70. #[clap(subcommand)]
  71. cmd: Autogenerate<user::UserInterface>,
  72. },
  73. }
  74. pub struct RunArgs {
  75. pub config: Config,
  76. pub db: UIDCDatabase,
  77. pub realm: Stored<schema::Realm>,
  78. }
  79. impl RootArgs {
  80. async fn run(self) -> Result<(), UIDCError> {
  81. let config_contents = std::fs::read_to_string(self.config_path.as_str())
  82. .expect("couldn't open configuration file");
  83. let config: Config =
  84. toml::from_str(config_contents.as_str()).expect("couldn't parse configuration file");
  85. if let Command::Init = self.command {
  86. return self.init(config).await;
  87. }
  88. let db = UIDCDatabase::open_path(&config.db_path)
  89. .map_err(|e| UIDCError::AbortString(format!("Error accessing database: {:?}", e)))?;
  90. let realm = db
  91. .realms
  92. .keyed(&self.realm)
  93. .get()?
  94. .ok_or(UIDCError::Abort("no such realm"))?;
  95. let ra = RunArgs { config, db, realm };
  96. match self.command {
  97. Command::Init => unreachable!(),
  98. // Command::Config(v) => v.run(ra).await,
  99. Command::Key(v) => v.run(ra).await,
  100. Command::Client { cmd } => cmd.perform(&ra.realm, &ra.realm.clients),
  101. Command::Scope { cmd } => cmd.perform(&ra.realm, &ra.realm.scopes),
  102. Command::Group { cmd } => cmd.perform(&ra.realm, &ra.realm.groups),
  103. Command::Serve(v) => v.run(ra).await,
  104. Command::Token { cmd } => cmd.run(ra).await,
  105. Command::Role { cmd } => cmd.perform(&ra.realm, &ra.realm.roles),
  106. Command::User { cmd } => cmd.perform(&ra, &ra.realm.users),
  107. }
  108. }
  109. async fn init(&self, config: Config) -> Result<(), UIDCError> {
  110. // first check to see if the database is already vaguely set up
  111. let db = UIDCDatabase::open_path(&config.db_path)
  112. .map_err(|e| UIDCError::AbortString(format!("Error accessing database: {:?}", e)))?;
  113. log::info!("Initializing!");
  114. if db.realms.keyed("primary").get()?.is_some() {
  115. log::warn!("Already initialized with primary realm!");
  116. return Ok(());
  117. }
  118. // create primary realm
  119. let primary = db.realms.insert_and_return(schema::Realm {
  120. shortname: "primary".into(),
  121. ..Default::default()
  122. })?;
  123. // add a HMAC key for refresh tokens
  124. key::generate_in(&primary, KeyType::HMac(key::HMacType::Sha256))?;
  125. Ok(())
  126. }
  127. }
  128. #[derive(Debug, Subcommand)]
  129. enum KeyCommand {
  130. /// Print details of all keys
  131. List,
  132. /// Generate a new key; see types subcommand for valid options.
  133. Generate { key_type: KeyType },
  134. /// List what keytypes are supported
  135. Types,
  136. /// Remove a key from use
  137. Remove { key_id: String },
  138. }
  139. #[derive(Debug, Parser)]
  140. struct KeyArgs {
  141. #[clap(subcommand)]
  142. command: KeyCommand,
  143. }
  144. impl KeyArgs {
  145. async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
  146. match &self.command {
  147. KeyCommand::List => key::list(&args.realm),
  148. KeyCommand::Generate { key_type } => {
  149. key::generate_in(&args.realm, *key_type)?;
  150. Ok(())
  151. }
  152. KeyCommand::Types => {
  153. for (spec, _kty) in key::KEY_TYPE_NAMES {
  154. println!("- {}", spec);
  155. }
  156. Ok(())
  157. }
  158. KeyCommand::Remove { key_id } => key::remove(&args.realm, key_id),
  159. }
  160. }
  161. }
  162. /*
  163. #[derive(Debug, Subcommand)]
  164. enum ConfigCommand {
  165. Dump,
  166. Load { toml_path: String },
  167. Set { key: String, value: String },
  168. }
  169. #[derive(Debug, Parser)]
  170. struct ConfigArgs {
  171. #[clap(subcommand)]
  172. command: ConfigCommand,
  173. }
  174. impl ConfigArgs {
  175. async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
  176. match &self.command {
  177. ConfigCommand::Dump => {
  178. let config = config::Config::build_from(&args.db, None);
  179. println!("{:#?}", config);
  180. }
  181. ConfigCommand::Set { key, value } => {
  182. args.db.persistent_config.keyed(key).delete()?;
  183. args.db.persistent_config.insert(schema::PersistentConfig {
  184. key: key.clone(),
  185. value: value.clone(),
  186. })?;
  187. }
  188. ConfigCommand::Load { toml_path } => {
  189. let config = config::Config::build_from(&args.db, Some(toml_path));
  190. config.save(&args.db);
  191. }
  192. }
  193. Ok(())
  194. }
  195. }
  196. */
  197. #[derive(Debug, Parser)]
  198. struct ServerArgs {
  199. #[clap(short, long)]
  200. bind: Option<String>,
  201. #[clap(short, long)]
  202. port: Option<u16>,
  203. }
  204. impl ServerArgs {
  205. async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
  206. server::run_server(
  207. args.db,
  208. args.config,
  209. self.bind.as_deref().unwrap_or("127.0.0.1"),
  210. self.port.unwrap_or(2114),
  211. )
  212. .await
  213. }
  214. }
  215. #[derive(Debug, Subcommand)]
  216. enum TokenCommand {
  217. GenerateAuth {
  218. #[clap(short, long)]
  219. client: String,
  220. #[clap(short, long)]
  221. username: String,
  222. #[clap(short, long)]
  223. scopes: String,
  224. },
  225. GenerateRefresh {
  226. #[clap(short, long)]
  227. client: String,
  228. #[clap(short, long)]
  229. username: String,
  230. #[clap(short, long)]
  231. scopes: String,
  232. },
  233. Inspect {
  234. token: Option<String>,
  235. },
  236. }
  237. impl TokenCommand {
  238. async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
  239. let get_stored = |client_name: &str, user_name: &str| {
  240. let stored_client = args
  241. .realm
  242. .clients
  243. .with(schema::Client::Shortname, client_name)
  244. .first()
  245. .get()?
  246. .ok_or(UIDCError::Abort("no such client"))?;
  247. let stored_user = args
  248. .realm
  249. .users
  250. .with(schema::User::Username, user_name)
  251. .first()
  252. .get()?
  253. .ok_or(UIDCError::Abort("no such user"))?;
  254. Result::<_, UIDCError>::Ok((stored_client, stored_user))
  255. };
  256. match self {
  257. TokenCommand::GenerateAuth {
  258. client,
  259. username,
  260. scopes,
  261. } => {
  262. let (stored_client, stored_user) = get_stored(client.as_str(), username.as_str())?;
  263. let realm = RealmHelper::new(args.config, args.realm);
  264. let token =
  265. realm.generate_access_token(&stored_client, &stored_user, scopes.split(' '))?;
  266. println!("{}", token);
  267. Ok(())
  268. }
  269. TokenCommand::GenerateRefresh {
  270. client,
  271. username,
  272. scopes,
  273. } => {
  274. let (stored_client, stored_user) = get_stored(client.as_str(), username.as_str())?;
  275. let realm = RealmHelper::new(args.config, args.realm);
  276. let token = realm.generate_refresh_token(
  277. &stored_client,
  278. &stored_user,
  279. scopes.split(' '),
  280. )?;
  281. println!("{}", token);
  282. Ok(())
  283. }
  284. TokenCommand::Inspect { token: _ } => {
  285. todo!()
  286. // token_management::inspect_token(&config, &args.realm, token.as_ref())
  287. }
  288. }
  289. }
  290. }
  291. pub fn invoked() {
  292. let args = RootArgs::parse();
  293. match smol::block_on(args.run()) {
  294. Ok(_) => (),
  295. Err(e) => {
  296. log::error!("Error occured while running command: {}", e);
  297. }
  298. }
  299. }