فهرست منبع

WIP converting modules to use new UIDCError type.

Kestrel 1 سال پیش
والد
کامیت
c8ed581345
12فایلهای تغییر یافته به همراه190 افزوده شده و 119 حذف شده
  1. 2 1
      .gitignore
  2. 1 1
      Cargo.toml
  3. 11 11
      simple-setup.sh
  4. 45 39
      src/cli.rs
  5. 42 0
      src/error.rs
  6. 32 28
      src/key.rs
  7. 5 1
      src/main.rs
  8. 0 0
      src/role_management.rs
  9. 3 3
      src/server.rs
  10. 5 5
      src/server/session.rs
  11. 25 9
      src/user.rs
  12. 19 21
      src/user_management.rs

+ 2 - 1
.gitignore

@@ -1,3 +1,4 @@
 /target
-/uauth.db
+/uidc
+/uidc.db
 .*.sw?

+ 1 - 1
Cargo.toml

@@ -1,5 +1,5 @@
 [package]
-name = "uauth2"
+name = "uidc"
 version = "0.1.0"
 edition = "2021"
 

+ 11 - 11
simple-setup.sh

@@ -1,20 +1,20 @@
 #!/bin/bash
 
 cargo build
-UAUTH=./target/debug/uauth2
+UIDC=./target/debug/uidc
 
-$UAUTH init
-$UAUTH config load /dev/stdin <<EOF
+$UIDC init
+$UIDC config load /dev/stdin <<EOF
 base_url = "http://localhost:2114/"
 EOF
 
-$UAUTH cert generate
-$UAUTH client create testclient
-$UAUTH user create kestrel
+$UIDC cert generate
+$UIDC client create testclient
+$UIDC user create kestrel
 echo "please enter password for user 'kestrel'"
-$UAUTH user auth -p kestrel
+$UIDC user auth -p kestrel
 
-$UAUTH group create testgroup
-$UAUTH role create testrole
-$UAUTH group attach-role testgroup testrole
-$UAUTH group attach-user testgroup kestrel
+$UIDC group create testgroup
+$UIDC role create testrole
+$UIDC group attach-role testgroup testrole
+$UIDC group attach-user testgroup kestrel

+ 45 - 39
src/cli.rs

@@ -1,7 +1,5 @@
 use crate::{
-    cert, client_management, config,
-    schema::{self, schema},
-    server, token, user_management,
+    key, client_management, config, schema::{self, RealmID}, server, token, user_management, UIDCError,
 };
 use clap::{Parser, Subcommand};
 use microrm::prelude::*;
@@ -9,7 +7,7 @@ use microrm::prelude::*;
 #[derive(Debug, Parser)]
 #[clap(author, version, about, long_about = None)]
 struct RootArgs {
-    #[clap(short, long, default_value_t = String::from("uauth.db"))]
+    #[clap(short, long, default_value_t = String::from("uidc.db"))]
     /// Database path
     db: String,
 
@@ -25,14 +23,14 @@ struct RootArgs {
 enum Command {
     /// database initialization
     Init,
-    /// certificate management
-    Cert(CertArgs),
     /// 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
@@ -43,26 +41,25 @@ enum Command {
     User(UserArgs),
 }
 
+struct RunArgs {
+    db: microrm::DB,
+    realm: RealmID,
+}
+
 impl RootArgs {
-    async fn run(&self) {
+    async fn run(&self) -> Result<(), UIDCError> {
         if let Command::Init = self.command {
             return self.init().await;
         }
 
-        let storage = microrm::DB::new(schema::schema(), &self.db, microrm::CreateMode::MustExist);
-
-        if let Err(e) = storage {
-            println!("Error occured while loading database: {}", e);
-            return;
-        }
-        let storage = storage.unwrap();
+        let storage = microrm::DB::new(schema::schema(), &self.db, microrm::CreateMode::MustExist).map_err(|_| UIDCError::Abort("Error accessing database"))?;
 
         match &self.command {
             Command::Init => unreachable!(),
-            Command::Cert(v) => v.run(&self, storage).await,
             Command::Config(v) => v.run(&self, storage).await,
             Command::Client(v) => v.run(&self, storage).await,
             Command::Group(v) => v.run(&self, storage).await,
+            Command::Key(v) => v.run(&self, storage).await,
             Command::Server(v) => v.run(&self, storage).await,
             Command::Token(v) => v.run(&self, storage).await,
             Command::Role(v) => v.run(&self, storage).await,
@@ -70,13 +67,12 @@ impl RootArgs {
         }
     }
 
-    async fn init(&self) {
+    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() {
-            println!("Database already initialized, not overwriting!");
-            return;
+            return Err(UIDCError::Abort("Database already initialized, not overwriting!"));
         }
 
         log::info!("Initializing!");
@@ -92,33 +88,34 @@ impl RootArgs {
         db.query_interface()
             .add(&schema::Realm {
                 shortname: "primary".to_string(),
-            })
-            .expect("couldn't add realm");
+            })?;
+        Ok(())
     }
 }
 
 #[derive(Debug, Subcommand)]
-enum CertCommand {
+enum KeyCommand {
     Inspect,
     Generate,
 }
 
 #[derive(Debug, Parser)]
-struct CertArgs {
+struct KeyArgs {
     #[clap(subcommand)]
-    command: CertCommand,
+    command: KeyCommand,
 }
 
-impl CertArgs {
-    async fn run(&self, root: &RootArgs, db: microrm::DB) {
+impl KeyArgs {
+    async fn run(&self, root: &RootArgs, db: microrm::DB) -> Result<(), UIDCError> {
         match &self.command {
-            CertCommand::Inspect => {
-                cert::inspect(&db, &root.realm);
+            KeyCommand::Inspect => {
+                key::inspect(&db, &root.realm);
             }
-            CertCommand::Generate => {
-                cert::generate(&db, &root.realm);
+            KeyCommand::Generate => {
+                key::generate(&db, &root.realm);
             }
         }
+        Ok(())
     }
 }
 
@@ -136,7 +133,7 @@ struct ClientArgs {
 }
 
 impl ClientArgs {
-    async fn run(&self, root: &RootArgs, db: microrm::DB) {
+    async fn run(&self, root: &RootArgs, db: microrm::DB) -> Result<(), UIDCError> {
         match &self.command {
             ClientCommand::Create { name } => {
                 client_management::create(&db, root.realm.as_str(), name);
@@ -146,6 +143,7 @@ impl ClientArgs {
                 client_management::inspect(&db, name);
             }
         }
+        Ok(())
     }
 }
 
@@ -163,7 +161,7 @@ struct ConfigArgs {
 }
 
 impl ConfigArgs {
-    async fn run(&self, root: &RootArgs, db: microrm::DB) {
+    async fn run(&self, root: &RootArgs, db: microrm::DB) -> Result<(), UIDCError> {
         match &self.command {
             ConfigCommand::Dump => {
                 let qi = db.query_interface();
@@ -185,6 +183,7 @@ impl ConfigArgs {
                 }
             }
         }
+        Ok(())
     }
 }
 
@@ -205,7 +204,7 @@ struct GroupArgs {
 }
 
 impl GroupArgs {
-    async fn run(&self, root: &RootArgs, db: microrm::DB) {
+    async fn run(&self, root: &RootArgs, db: microrm::DB) -> Result<(), UIDCError> {
         let qi = db.query_interface();
         let realm_id = qi.get().by(schema::Realm::Shortname, root.realm.as_str()).one().unwrap().expect("no such realm").id();
         match &self.command {
@@ -285,6 +284,7 @@ impl GroupArgs {
                 
             }
         }
+        Ok(())
     }
 }
 
@@ -295,7 +295,7 @@ struct ServerArgs {
 }
 
 impl ServerArgs {
-    async fn run(&self, root: &RootArgs, db: microrm::DB) {
+    async fn run(&self, root: &RootArgs, db: microrm::DB) -> Result<(), UIDCError> {
         let config = config::Config::build_from(&db.query_interface(), None);
         server::run_server(db, config, self.port.unwrap_or(2114)).await
     }
@@ -331,7 +331,7 @@ struct TokenArgs {
 }
 
 impl TokenArgs {
-    async fn run(&self, root: &RootArgs, db: microrm::DB) {
+    async fn run(&self, root: &RootArgs, db: microrm::DB) -> Result<(), UIDCError> {
         let config = config::Config::build_from(&db.query_interface(), None);
         match &self.command {
             TokenCommand::GenerateAuth {
@@ -383,6 +383,7 @@ impl TokenArgs {
             } => {}
             TokenCommand::Inspect { token } => {}
         }
+        Ok(())
     }
 }
 
@@ -400,7 +401,7 @@ struct RoleArgs {
 }
 
 impl RoleArgs {
-    async fn run(&self, root: &RootArgs, db: microrm::DB) {
+    async fn run(&self, root: &RootArgs, db: microrm::DB) -> Result<(), UIDCError> {
         let config = config::Config::build_from(&db.query_interface(), None);
         match &self.command {
             RoleCommand::List => {
@@ -429,6 +430,7 @@ impl RoleArgs {
                 qi.delete().by(schema::Role::Realm, &realm.id()).by(schema::Role::Shortname, name.as_str()).exec().unwrap();
             },
         }
+        Ok(())
     }
 }
 
@@ -456,7 +458,7 @@ struct UserArgs {
 }
 
 impl UserArgs {
-    async fn run(&self, root: &RootArgs, db: microrm::DB) {
+    async fn run(&self, root: &RootArgs, db: microrm::DB) -> Result<(), UIDCError> {
         match &self.command {
             UserCommand::List => user_management::list(&root.realm, db),
             UserCommand::Create { username } => {
@@ -468,7 +470,7 @@ impl UserArgs {
                 username.as_str(),
                 *change_password > 0,
             ),
-            UserCommand::Inspect { username } => user_management::inspect(&root.realm, db, username.as_str()).expect("database error"),
+            UserCommand::Inspect { username } => user_management::inspect(&root.realm, db, username.as_str()),
         }
     }
 }
@@ -476,6 +478,10 @@ impl UserArgs {
 pub fn invoked() {
     let args = RootArgs::parse();
 
-    smol::block_on(args.run());
-    // async_std::task::block_on(args.run());
+    match smol::block_on(args.run()) {
+        Ok(_) => (),
+        Err(e) => {
+            log::error!("Error occured while running command: {}", e);
+        }
+    }
 }

+ 42 - 0
src/error.rs

@@ -0,0 +1,42 @@
+use crate::{key::KeyError, user::UserError};
+
+#[derive(Debug)]
+pub enum UIDCError {
+    /// aborting operation because of invalid state
+    Abort(&'static str),
+    /// database access issue or violated constraint
+    DatabaseError(microrm::Error),
+
+    /// error with key generation or message signing
+    KeyError(KeyError),
+
+    /// error with user operation
+    UserError(UserError),
+}
+
+impl std::fmt::Display for UIDCError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        <Self as std::fmt::Debug>::fmt(self, f)
+    }
+}
+
+impl std::error::Error for UIDCError {}
+
+impl From<microrm::Error> for UIDCError {
+    fn from(value: microrm::Error) -> Self {
+        Self::DatabaseError(value)
+    }
+}
+
+macro_rules! error_converter {
+    ($toconv:ident) => {
+        impl From<$toconv> for UIDCError {
+            fn from(value: $toconv) -> Self {
+                Self::$toconv(value)
+            }
+        }
+    }
+}
+
+error_converter!(KeyError);
+error_converter!(UserError);

+ 32 - 28
src/cert.rs → src/key.rs

@@ -1,16 +1,21 @@
-use crate::schema;
+use crate::{schema,UIDCError};
 use microrm::prelude::*;
 use ring::signature::{Ed25519KeyPair, KeyPair};
 use sha2::Digest;
 use std::collections::HashMap;
 
-pub struct CertStore<'a> {
+#[derive(Debug)]
+pub enum KeyError {
+    Plain(&'static str),
+}
+
+pub struct KeyStore<'a> {
     db: &'a microrm::DB,
     qi: microrm::QueryInterface<'a>,
     keys: HashMap<String, Ed25519KeyPair>,
 }
 
-impl<'a> CertStore<'a> {
+impl<'a> KeyStore<'a> {
     pub fn new(db: &'a microrm::DB) -> Self {
         Self {
             db,
@@ -19,23 +24,23 @@ impl<'a> CertStore<'a> {
         }
     }
 
-    fn realm_id(&self, realm_name: &str) -> Option<schema::RealmID> {
+    fn realm_id(&self, realm_name: &str) -> Result<schema::RealmID, UIDCError> {
         self.qi
             .get()
             .by(schema::Realm::Shortname, realm_name)
-            .one()
-            .expect("couldn't query db")
-            .map(|x| x.id())
+            .one()?
+            .map(|r| r.id())
+            .ok_or(KeyError::Plain("no such realm").into())
     }
 
-    pub fn generate_in(&self, realm_name: &str) -> Result<String, &'static str> {
+    pub fn generate_in(&self, realm_name: &str) -> Result<String, UIDCError> {
         let mut rng = ring::rand::SystemRandom::new();
         let sign_generated = Ed25519KeyPair::generate_pkcs8(&mut rng);
 
-        let realm_id = self.realm_id(realm_name).ok_or("Failed to find realm")?;
+        let realm_id = self.realm_id(realm_name)?;
 
         if let Err(_) = sign_generated {
-            return Err("Failed to generate key");
+            return Err(KeyError::Plain("Failed to generate key").into())
         }
         let sign_generated = sign_generated.unwrap();
 
@@ -54,43 +59,42 @@ impl<'a> CertStore<'a> {
                 realm: realm_id,
                 key_id: key_id.clone(),
                 keydata,
-            })
-            .expect("Couldn't add key");
+            })?;
 
         Ok(key_id)
     }
 }
 
-pub fn inspect(db: &microrm::DB, realm_name: &str) {
+pub fn inspect(db: &microrm::DB, realm_name: &str) -> Result<(), UIDCError> {
     let qi = db.query_interface();
-    let cs = CertStore::new(db);
-    println!("Certstore loaded.");
+    let cs = KeyStore::new(db);
+    println!("Keystore loaded.");
     let realm = qi
         .get()
         .by(schema::Realm::Shortname, realm_name)
-        .one()
-        .expect("couldn't query db");
-    if realm.is_none() {
-        println!("No such realm {}", realm_name);
-        return;
-    }
-    let realm = realm.unwrap();
+        .one()?
+        .ok_or(UIDCError::Abort("no such realm"))?;
 
     println!("Retrieving keys for {} realm...", realm_name);
     let keys = qi
         .get()
         .by(schema::Key::Realm, &realm.id())
-        .all()
-        .expect("Can get keys");
+        .all()?;
+
     for key in keys {
         println!("- [{:20}]", key.key_id);
     }
+    Ok(())
 }
 
-pub fn generate(db: &microrm::DB, realm_name: &str) {
+pub fn generate(db: &microrm::DB, realm_name: &str) -> Result<(), UIDCError> {
     let qi = db.query_interface();
-    let cs = CertStore::new(db);
-    if let Err(e) = cs.generate_in(realm_name) {
-        println!("Failed to generate key: {}", e);
+    let cs = KeyStore::new(db);
+    match cs.generate_in(realm_name) {
+        Ok(_) => (),
+        Err(e) => {
+            println!("Failed to generate key: {}", e);
+        }
     }
+    Ok(())
 }

+ 5 - 1
src/main.rs

@@ -1,15 +1,19 @@
-mod cert;
 mod cli;
 mod client_management;
 mod config;
+mod error;
 mod jwt;
+mod key;
 mod login;
+mod role_management;
 mod schema;
 mod server;
 mod token;
 mod user;
 mod user_management;
 
+pub use error::UIDCError;
+
 fn main() {
     stderrlog::new()
         .verbosity(5)

+ 0 - 0
src/role_management.rs


+ 3 - 3
src/server.rs

@@ -1,4 +1,4 @@
-use crate::{config, schema};
+use crate::{config, schema, UIDCError};
 use microrm::prelude::*;
 
 mod oidc;
@@ -50,7 +50,7 @@ async fn index(req: tide::Request<ServerStateWrapper>) -> tide::Result<tide::Res
     Ok(response)
 }
 
-pub async fn run_server(db: microrm::DB, config: config::Config, port: u16) {
+pub async fn run_server(db: microrm::DB, config: config::Config, port: u16) -> Result<(), UIDCError> {
     let db_box = Box::new(db);
     let db: &'static mut microrm::DB = Box::leak(db_box);
     let pool = microrm::DBPool::new(db);
@@ -89,5 +89,5 @@ pub async fn run_server(db: microrm::DB, config: config::Config, port: u16) {
     session::session_v1_server(app.at("/:realm/v1/session/"));
     oidc::oidc_server(app.at("/:realm/"));
 
-    app.listen(("127.0.0.1", port)).await.expect("Can listen");
+    app.listen(("127.0.0.1", port)).await.map_err(|_| UIDCError::Abort("couldn't listen on port"))
 }

+ 5 - 5
src/server/session.rs

@@ -11,7 +11,7 @@ pub(super) struct SessionHelper<'l> {
 
 type Request = tide::Request<super::ServerStateWrapper>;
 
-const SESSION_COOKIE_NAME: &'static str = "uauth_session";
+const SESSION_COOKIE_NAME: &'static str = "uidc_session";
 
 impl<'l> SessionHelper<'l> {
     pub fn new(req: &'l Request) -> Self {
@@ -306,7 +306,7 @@ async fn v1_login_post(mut req: Request) -> tide::Result<tide::Response> {
                     let verification = user.verify_challenge(&qi, ct, body.challenge.as_bytes());
 
                     match verification {
-                        Some(true) => {
+                        Ok(true) => {
                             auth.challenges_left.remove(0);
                             qi.update()
                                 .to(auth.as_ref())
@@ -314,12 +314,12 @@ async fn v1_login_post(mut req: Request) -> tide::Result<tide::Response> {
                                 .exec()
                                 .expect("couldn't update auth status?");
                         }
-                        Some(false) => {
+                        Ok(false) => {
                             error = Some("Incorrect response. Please try again".into());
                         }
-                        None => {
+                        Err(_) => {
                             error = Some(
-                                "User no longer exists. Please contact an administrator.".into(),
+                                "Internal error. Please contact an administrator.".into(),
                             );
                         }
                     }

+ 25 - 9
src/user.rs

@@ -1,6 +1,12 @@
-use crate::schema;
+use crate::{schema, UIDCError};
 use microrm::prelude::*;
 
+#[derive(Debug)]
+pub enum UserError {
+    NoSuchUser,
+    NoSuchChallenge,
+}
+
 pub struct User {
     id: schema::UserID,
     model: Option<schema::User>,
@@ -16,20 +22,30 @@ impl User {
         }
     }
 
-    /// returns Some(true) if challenge passed, Some(false) if challenge failed, and None if
-    /// challenge not found
+    pub fn change_username(&self, qi: &microrm::QueryInterface, new_name: &str) -> Result<(), UIDCError> {
+        // check to ensure the new username isn't already in use
+        if qi.get().by(schema::User::Username, new_name).one()?.is_some() {
+            Err(UIDCError::Abort("username already in use"))
+        }
+        else {
+            Ok(qi.update().update(schema::User::Username, new_name).by_id(&self.id).exec()?)
+        }
+    }
+
+    /// returns Ok(true) if challenge passed, Ok(false) if challenge failed, and
+    /// UserError::NoSuchChallenge if challenge not found
     pub fn verify_challenge(
         &self,
         qi: &microrm::QueryInterface,
         which: schema::AuthChallengeType,
         response: &[u8],
-    ) -> Option<bool> {
+    ) -> Result<bool, UIDCError> {
         let challenge = qi
             .get()
             .by(schema::AuthChallenge::User, &self.id)
             .by(schema::AuthChallenge::ChallengeType, &which)
-            .one()
-            .expect("couldn't query db")?;
+            .one()?
+            .ok_or(UserError::NoSuchChallenge)?;
 
         match which {
             schema::AuthChallengeType::Password => {
@@ -80,10 +96,10 @@ impl User {
         &self,
         challenge: schema::AuthChallenge,
         response: &[u8],
-    ) -> Option<bool> {
+    ) -> Result<bool, UIDCError> {
         use ring::pbkdf2;
 
-        Some(
+        Ok(
             pbkdf2::verify(
                 pbkdf2::PBKDF2_HMAC_SHA256,
                 PBKDF2_ROUNDS,
@@ -99,7 +115,7 @@ impl User {
         &self,
         challenge: schema::AuthChallenge,
         response: &[u8],
-    ) -> Option<bool> {
+    ) -> Result<bool, UIDCError> {
         todo!()
     }
 }

+ 19 - 21
src/user_management.rs

@@ -1,23 +1,21 @@
-use crate::{schema, user};
+use crate::{schema, user, UIDCError};
 use microrm::prelude::*;
 
-pub fn list(realm: &str, db: microrm::DB) {
+pub fn list(realm: &str, db: microrm::DB) -> Result<(), UIDCError> {
     // get realm ID
     let qi = db.query_interface();
 
     let realm_id = qi
         .get()
         .by(schema::Realm::Shortname, realm)
-        .one()
-        .expect("couldn't query db")
-        .expect("No such realm")
+        .one()?
+        .ok_or(UIDCError::Abort("no such realm"))?
         .id();
 
     let users = qi
         .get()
         .by(schema::User::Realm, &realm_id)
-        .all()
-        .expect("couldn't query db");
+        .all()?;
 
     println!("User list ({} users):", users.len());
 
@@ -26,23 +24,23 @@ pub fn list(realm: &str, db: microrm::DB) {
         let auth_challenges = qi
             .get()
             .by(schema::AuthChallenge::User, &user.id())
-            .all()
-            .expect("Can't get authentication challenges?");
+            .all()?;
         for ch in &auth_challenges {
             println!("    - Has {:?} authentication challenge", ch.challenge_type);
         }
     }
+
+    Ok(())
 }
 
-pub fn create(realm: &str, db: microrm::DB, username: &str) {
+pub fn create(realm: &str, db: microrm::DB, username: &str) -> Result<(), UIDCError> {
     // get realm ID
     let qi = db.query_interface();
 
     let realm_id = qi
         .get()
         .by(schema::Realm::Shortname, realm)
-        .one()
-        .expect("couldn't query db")
+        .one()?
         .expect("No such realm")
         .id();
 
@@ -51,26 +49,25 @@ pub fn create(realm: &str, db: microrm::DB, username: &str) {
         .get()
         .by(schema::User::Realm, &realm_id)
         .by(schema::User::Username, &username)
-        .one()
-        .expect("couldn't query db");
+        .one()?;
 
     if existing_user.is_some() {
         log::error!(
-            "Can't create user {} in {} realm as a user with that username already exists",
+            "Can't create user {} in {} realm, as a user with that username already exists",
             username,
             realm
         );
-        return;
+        return Ok(());
     }
 
     qi.add(&schema::User {
         realm: realm_id,
         username: username.to_owned(),
-    })
-    .expect("couldn't add user");
+    })?;
+    Ok(())
 }
 
-pub fn change_auth(realm: &str, db: microrm::DB, username: &str, change_password: bool) {
+pub fn change_auth(realm: &str, db: microrm::DB, username: &str, change_password: bool) -> Result<(), UIDCError> {
     // get realm ID
     let qi = db.query_interface();
 
@@ -90,7 +87,7 @@ pub fn change_auth(realm: &str, db: microrm::DB, username: &str, change_password
         .expect("couldn't query db");
     if existing_user.is_none() {
         log::error!("User {} does not exist in the {} realm!", username, realm);
-        return;
+        return Ok(());
     }
 
     let user = user::User::from_model(existing_user.unwrap());
@@ -99,9 +96,10 @@ pub fn change_auth(realm: &str, db: microrm::DB, username: &str, change_password
         let raw_pass = rpassword::prompt_password("Enter new user password: ").unwrap();
         user.set_new_password(&qi, raw_pass.as_bytes());
     }
+    Ok(())
 }
 
-pub fn inspect(realm: &str, db: microrm::DB, username: &str) -> Result<(), microrm::Error> {
+pub fn inspect(realm: &str, db: microrm::DB, username: &str) -> Result<(), UIDCError> {
     let qi = db.query_interface();
 
     let realm = qi.get().by(schema::Realm::Shortname, realm).one()?.expect("no such realm");