123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- use crate::{
- config::Config,
- key::{self, KeyType},
- realm::RealmHelper,
- schema::{self, UIDCDatabase},
- server, UIDCError,
- };
- use clap::{Parser, Subcommand};
- use microrm::cli::Autogenerate;
- use microrm::{prelude::*, schema::Stored};
- mod client;
- mod group;
- mod role;
- mod scope;
- 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_CONFIG", default_value_t = String::from("uidc.toml"))]
- /// Configuration file path
- config_path: String,
- #[clap(short, long, default_value_t = String::from("primary"))]
- /// Which realm to use, for non-serve commands only
- realm: String,
- #[clap(subcommand)]
- command: Command,
- }
- #[derive(Debug, Subcommand)]
- enum Command {
- /// database initialization
- Init,
- /// OAuth2 client management
- Client {
- #[clap(subcommand)]
- cmd: Autogenerate<client::ClientInterface>,
- },
- // /// general configuration
- // Config(ConfigArgs),
- /// permissions grouping management
- Group {
- #[clap(subcommand)]
- cmd: Autogenerate<group::GroupInterface>,
- },
- /// key management
- Key(KeyArgs),
- /// scope management
- Scope {
- #[clap(subcommand)]
- cmd: Autogenerate<scope::ScopeInterface>,
- },
- /// run the actual OIDC server
- Serve(ServerArgs),
- /// manual token generation and inspection
- Token {
- #[clap(subcommand)]
- cmd: TokenCommand,
- },
- /// role management
- Role {
- #[clap(subcommand)]
- cmd: Autogenerate<role::RoleInterface>,
- },
- /// user management
- User {
- #[clap(subcommand)]
- cmd: Autogenerate<user::UserInterface>,
- },
- }
- pub struct RunArgs {
- pub config: Config,
- pub db: UIDCDatabase,
- pub realm: Stored<schema::Realm>,
- }
- impl RootArgs {
- async fn run(self) -> Result<(), UIDCError> {
- let config_contents = std::fs::read_to_string(self.config_path.as_str())
- .expect("couldn't open configuration file");
- let config: Config =
- toml::from_str(config_contents.as_str()).expect("couldn't parse configuration file");
- if let Command::Init = self.command {
- return self.init(config).await;
- }
- let db = UIDCDatabase::open_path(&config.db_path)
- .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 { config, db, realm };
- match self.command {
- Command::Init => unreachable!(),
- // Command::Config(v) => v.run(ra).await,
- Command::Key(v) => v.run(ra).await,
- Command::Client { cmd } => cmd.perform(&ra.realm, &ra.realm.clients),
- Command::Scope { cmd } => cmd.perform(&ra.realm, &ra.realm.scopes),
- Command::Group { cmd } => cmd.perform(&ra.realm, &ra.realm.groups),
- Command::Serve(v) => v.run(ra).await,
- Command::Token { cmd } => cmd.run(ra).await,
- Command::Role { cmd } => cmd.perform(&ra.realm, &ra.realm.roles),
- Command::User { cmd } => cmd.perform(&ra, &ra.realm.users),
- }
- }
- async fn init(&self, config: Config) -> Result<(), UIDCError> {
- // first check to see if the database is already vaguely set up
- let db = UIDCDatabase::open_path(&config.db_path)
- .map_err(|e| UIDCError::AbortString(format!("Error accessing database: {:?}", e)))?;
- log::info!("Initializing!");
- if db.realms.keyed("primary").get()?.is_some() {
- log::warn!("Already initialized with primary realm!");
- return Ok(());
- }
- // create primary realm
- let primary = db.realms.insert_and_return(schema::Realm {
- shortname: "primary".into(),
- ..Default::default()
- })?;
- // add a HMAC key for refresh tokens
- key::generate_in(&primary, KeyType::HMac(key::HMacType::Sha256))?;
- 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)?;
- 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 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, Parser)]
- struct ServerArgs {
- #[clap(short, long)]
- bind: Option<String>,
- #[clap(short, long)]
- port: Option<u16>,
- }
- impl ServerArgs {
- async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
- server::run_server(
- args.db,
- args.config,
- self.bind.as_deref().unwrap_or("127.0.0.1"),
- 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<String>,
- },
- }
- impl TokenCommand {
- async fn run(self, args: RunArgs) -> Result<(), UIDCError> {
- let get_stored = |client_name: &str, user_name: &str| {
- let stored_client = args
- .realm
- .clients
- .with(schema::Client::Shortname, client_name)
- .first()
- .get()?
- .ok_or(UIDCError::Abort("no such client"))?;
- let stored_user = args
- .realm
- .users
- .with(schema::User::Username, user_name)
- .first()
- .get()?
- .ok_or(UIDCError::Abort("no such user"))?;
- Result::<_, UIDCError>::Ok((stored_client, stored_user))
- };
- match self {
- TokenCommand::GenerateAuth {
- client,
- username,
- scopes,
- } => {
- let (stored_client, stored_user) = get_stored(client.as_str(), username.as_str())?;
- let realm = RealmHelper::new(args.config, args.realm);
- let token =
- realm.generate_access_token(&stored_client, &stored_user, scopes.split(' '))?;
- println!("{}", token);
- Ok(())
- }
- TokenCommand::GenerateRefresh {
- client,
- username,
- scopes,
- } => {
- let (stored_client, stored_user) = get_stored(client.as_str(), username.as_str())?;
- let realm = RealmHelper::new(args.config, args.realm);
- let token = realm.generate_refresh_token(
- &stored_client,
- &stored_user,
- scopes.split(' '),
- )?;
- println!("{}", token);
- Ok(())
- }
- TokenCommand::Inspect { token: _ } => {
- todo!()
- // token_management::inspect_token(&config, &args.realm, token.as_ref())
- }
- }
- }
- }
- pub fn invoked() {
- let args = RootArgs::parse();
- match smol::block_on(args.run()) {
- Ok(_) => (),
- Err(e) => {
- log::error!("Error occured while running command: {}", e);
- }
- }
- }
|