cli.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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 pool: microrm::ConnectionPool,
  78. pub realm: Stored<schema::Realm>,
  79. }
  80. impl RootArgs {
  81. async fn run(self) -> Result<(), UIDCError> {
  82. let config_contents = std::fs::read_to_string(self.config_path.as_str())
  83. .expect("couldn't open configuration file");
  84. let config: Config =
  85. toml::from_str(config_contents.as_str()).expect("couldn't parse configuration file");
  86. let (pool, db) = microrm::ConnectionPool::open::<UIDCDatabase>(&config.db_path)
  87. .map_err(|e| UIDCError::AbortString(format!("Error accessing database: {e:?}")))?;
  88. let mut lease = pool.acquire()?;
  89. if let Command::Init = self.command {
  90. return self.init(db, &mut lease, config).await;
  91. }
  92. // let db = UIDCDatabase::open_path(&config.db_path)
  93. // .map_err(|e| UIDCError::AbortString(format!("Error accessing database: {:?}", e)))?;
  94. let realm = db
  95. .realms
  96. .keyed(&self.realm)
  97. .get(&mut lease)?
  98. .ok_or(UIDCError::Abort("no such realm"))?;
  99. match self.command {
  100. Command::Init => unreachable!(),
  101. // Command::Config(v) => v.run(ra).await,
  102. Command::Key(v) => {
  103. drop(lease);
  104. let ra = RunArgs {
  105. config,
  106. db,
  107. pool,
  108. realm,
  109. };
  110. v.run(ra).await
  111. }
  112. Command::Serve(v) => {
  113. drop(lease);
  114. let ra = RunArgs {
  115. config,
  116. db,
  117. pool,
  118. realm,
  119. };
  120. v.run(ra).await
  121. }
  122. Command::Token { cmd } => {
  123. drop(lease);
  124. let ra = RunArgs {
  125. config,
  126. db,
  127. pool,
  128. realm,
  129. };
  130. cmd.run(ra).await
  131. }
  132. Command::Client { cmd } => cmd.perform(&realm, &mut lease, &realm.clients),
  133. Command::Scope { cmd } => cmd.perform(&realm, &mut lease, &realm.scopes),
  134. Command::Group { cmd } => cmd.perform(&realm, &mut lease, &realm.groups),
  135. Command::Role { cmd } => cmd.perform(&realm, &mut lease, &realm.roles),
  136. Command::User { cmd } => {
  137. cmd.perform(&(realm.clone(), config), &mut lease, &realm.users)
  138. }
  139. }
  140. }
  141. async fn init<'l>(
  142. &self,
  143. db: UIDCDatabase,
  144. lease: &mut microrm::ConnectionLease<'l>,
  145. config: Config,
  146. ) -> Result<(), UIDCError> {
  147. // first check to see if the database is already vaguely set up
  148. /*let db = UIDCDatabase::open_path(&config.db_path)
  149. .map_err(|e| UIDCError::AbortString(format!("Error accessing database: {:?}", e)))?;*/
  150. log::info!("Initializing!");
  151. if db.realms.keyed("primary").get(lease)?.is_some() {
  152. log::warn!("Already initialized with primary realm!");
  153. return Ok(());
  154. }
  155. // create primary realm
  156. let primary = db.realms.insert_and_return(
  157. lease,
  158. schema::Realm {
  159. shortname: "primary".into(),
  160. ..Default::default()
  161. },
  162. )?;
  163. // add a HMAC key for refresh tokens
  164. key::generate_in(lease, &primary, KeyType::HMac(key::HMacType::Sha256))?;
  165. Ok(())
  166. }
  167. }
  168. #[derive(Debug, Subcommand)]
  169. enum KeyCommand {
  170. /// Print details of all keys
  171. List,
  172. /// Generate a new key; see types subcommand for valid options.
  173. Generate { key_type: KeyType },
  174. /// List what keytypes are supported
  175. Types,
  176. /// Remove a key from use
  177. Remove { key_id: String },
  178. }
  179. #[derive(Debug, Parser)]
  180. struct KeyArgs {
  181. #[clap(subcommand)]
  182. command: KeyCommand,
  183. }
  184. impl KeyArgs {
  185. async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
  186. let mut lease = args.pool.acquire()?;
  187. match &self.command {
  188. KeyCommand::List => key::list(&mut lease, &args.realm),
  189. KeyCommand::Generate { key_type } => {
  190. key::generate_in(&mut lease, &args.realm, *key_type)?;
  191. Ok(())
  192. }
  193. KeyCommand::Types => {
  194. for (spec, _kty) in key::KEY_TYPE_NAMES {
  195. println!("- {}", spec);
  196. }
  197. Ok(())
  198. }
  199. KeyCommand::Remove { key_id } => key::remove(&mut lease, &args.realm, key_id),
  200. }
  201. }
  202. }
  203. /*
  204. #[derive(Debug, Subcommand)]
  205. enum ConfigCommand {
  206. Dump,
  207. Load { toml_path: String },
  208. Set { key: String, value: String },
  209. }
  210. #[derive(Debug, Parser)]
  211. struct ConfigArgs {
  212. #[clap(subcommand)]
  213. command: ConfigCommand,
  214. }
  215. impl ConfigArgs {
  216. async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
  217. match &self.command {
  218. ConfigCommand::Dump => {
  219. let config = config::Config::build_from(&args.db, None);
  220. println!("{:#?}", config);
  221. }
  222. ConfigCommand::Set { key, value } => {
  223. args.db.persistent_config.keyed(key).delete()?;
  224. args.db.persistent_config.insert(schema::PersistentConfig {
  225. key: key.clone(),
  226. value: value.clone(),
  227. })?;
  228. }
  229. ConfigCommand::Load { toml_path } => {
  230. let config = config::Config::build_from(&args.db, Some(toml_path));
  231. config.save(&args.db);
  232. }
  233. }
  234. Ok(())
  235. }
  236. }
  237. */
  238. #[derive(Debug, Parser)]
  239. struct ServerArgs {
  240. #[clap(short, long)]
  241. bind: Option<String>,
  242. #[clap(short, long)]
  243. port: Option<u16>,
  244. }
  245. impl ServerArgs {
  246. async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
  247. server::run_server(
  248. args.pool,
  249. args.db,
  250. args.config,
  251. self.bind.as_deref().unwrap_or("127.0.0.1"),
  252. self.port.unwrap_or(2114),
  253. )
  254. .await
  255. }
  256. }
  257. #[derive(Debug, Subcommand)]
  258. enum TokenCommand {
  259. GenerateAuth {
  260. #[clap(short, long)]
  261. client: String,
  262. #[clap(short, long)]
  263. username: String,
  264. #[clap(short, long)]
  265. scopes: String,
  266. },
  267. GenerateRefresh {
  268. #[clap(short, long)]
  269. client: String,
  270. #[clap(short, long)]
  271. username: String,
  272. #[clap(short, long)]
  273. scopes: String,
  274. },
  275. Inspect {
  276. token: Option<String>,
  277. },
  278. }
  279. impl TokenCommand {
  280. async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
  281. let mut lease = args.pool.acquire()?;
  282. let mut get_stored = |client_name: &str, user_name: &str| {
  283. let stored_client = args
  284. .realm
  285. .clients
  286. .with(schema::Client::Shortname, client_name)
  287. .first()
  288. .get(&mut lease)?
  289. .ok_or(UIDCError::Abort("no such client"))?;
  290. let stored_user = args
  291. .realm
  292. .users
  293. .with(schema::User::Username, user_name)
  294. .first()
  295. .get(&mut lease)?
  296. .ok_or(UIDCError::Abort("no such user"))?;
  297. Result::<_, UIDCError>::Ok((stored_client, stored_user))
  298. };
  299. match self {
  300. TokenCommand::GenerateAuth {
  301. client,
  302. username,
  303. scopes,
  304. } => {
  305. let (stored_client, stored_user) = get_stored(client.as_str(), username.as_str())?;
  306. let realm = RealmHelper::new(args.config, args.realm);
  307. let token = realm.generate_access_token(
  308. &mut lease,
  309. &stored_client,
  310. &stored_user,
  311. scopes.split(' '),
  312. )?;
  313. println!("{}", token);
  314. Ok(())
  315. }
  316. TokenCommand::GenerateRefresh {
  317. client,
  318. username,
  319. scopes,
  320. } => {
  321. let (stored_client, stored_user) = get_stored(client.as_str(), username.as_str())?;
  322. let realm = RealmHelper::new(args.config, args.realm);
  323. let token = realm.generate_refresh_token(
  324. &mut lease,
  325. &stored_client,
  326. &stored_user,
  327. scopes.split(' '),
  328. )?;
  329. println!("{}", token);
  330. Ok(())
  331. }
  332. TokenCommand::Inspect { token: _ } => {
  333. todo!()
  334. // token_management::inspect_token(&config, &args.realm, token.as_ref())
  335. }
  336. }
  337. }
  338. }
  339. pub fn invoked() {
  340. let args = RootArgs::parse();
  341. match smol::block_on(args.run()) {
  342. Ok(_) => (),
  343. Err(e) => {
  344. log::error!("Error occured while running command: {}", e);
  345. }
  346. }
  347. }