Sfoglia il codice sorgente

Implemented group members command, rustfmt pass.

Kestrel 1 anno fa
parent
commit
0f186153c6
16 ha cambiato i file con 339 aggiunte e 181 eliminazioni
  1. 2 2
      Cargo.lock
  2. 1 1
      Cargo.toml
  3. 1 1
      simple-setup.sh
  4. 123 47
      src/cli.rs
  5. 2 0
      src/config/helper.rs
  6. 4 2
      src/error.rs
  7. 68 25
      src/group_management.rs
  8. 1 1
      src/jwt.rs
  9. 15 30
      src/key.rs
  10. 1 0
      src/role_management.rs
  11. 8 2
      src/server.rs
  12. 1 3
      src/server/session.rs
  13. 31 25
      src/token.rs
  14. 17 12
      src/token_management.rs
  15. 24 14
      src/user.rs
  16. 40 16
      src/user_management.rs

+ 2 - 2
Cargo.lock

@@ -1127,9 +1127,9 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
 
 [[package]]
 name = "microrm"
-version = "0.3.10"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f5c3363faceeeffb5d1bf44fc7448e3c3342999dd5f39aa1f27b34b752e4904"
+checksum = "b2aaa7a6fcb8423617eb742a58270fdce45b0002ad640559965a61dfa54ba11b"
 dependencies = [
  "base64 0.13.1",
  "lazy_static",

+ 1 - 1
Cargo.toml

@@ -21,7 +21,7 @@ base64 = { version = "0.13.0" }
 toml = "0.8.2"
 
 # Data storage dependencies
-microrm = { version = "0.3.10" }
+microrm = { version = "0.3.11" }
 serde_bytes = { version = "0.11.6" }
 
 # Public API/server dependencies

+ 1 - 1
simple-setup.sh

@@ -8,7 +8,7 @@ $UIDC config load /dev/stdin <<EOF
 base_url = "http://localhost:2114/"
 EOF
 
-$UIDC cert generate
+$UIDC key generate
 $UIDC client create testclient
 $UIDC user create kestrel
 echo "please enter password for user 'kestrel'"

+ 123 - 47
src/cli.rs

@@ -1,5 +1,7 @@
 use crate::{
-    key, client_management, config, schema::{self, RealmID}, server, token, user_management, UIDCError, group_management, token_management,
+    client_management, config, group_management, key,
+    schema::{self, RealmID},
+    server, token, token_management, user_management, UIDCError,
 };
 use clap::{Parser, Subcommand};
 use microrm::prelude::*;
@@ -52,9 +54,16 @@ impl RootArgs {
             return self.init().await;
         }
 
-        let db = microrm::DB::new(schema::schema(), &self.db, microrm::CreateMode::MustExist).map_err(|_| UIDCError::Abort("Error accessing database"))?;
+        let db = microrm::DB::new(schema::schema(), &self.db, microrm::CreateMode::MustExist)
+            .map_err(|e| UIDCError::AbortString(format!("Error accessing database: {:?}", e)))?;
 
-        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 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 };
 
@@ -76,7 +85,9 @@ impl RootArgs {
         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!"));
+            return Err(UIDCError::Abort(
+                "Database already initialized, not overwriting!",
+            ));
         }
 
         log::info!("Initializing!");
@@ -89,10 +100,9 @@ impl RootArgs {
         .expect("Unable to initialize database!");
 
         // create primary realm
-        db.query_interface()
-            .add(&schema::Realm {
-                shortname: "primary".to_string(),
-            })?;
+        db.query_interface().add(&schema::Realm {
+            shortname: "primary".to_string(),
+        })?;
         Ok(())
     }
 }
@@ -112,12 +122,8 @@ struct KeyArgs {
 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)
-            }
+            KeyCommand::Inspect => key::inspect(&args.db, args.realm_id),
+            KeyCommand::Generate => key::generate(&args.db, args.realm_id),
         }
     }
 }
@@ -141,7 +147,9 @@ impl ClientArgs {
             ClientCommand::Create { name } => {
                 client_management::create(&args.db, args.realm_id, name)
             }
-            ClientCommand::List => { todo!() }
+            ClientCommand::List => {
+                todo!()
+            }
             ClientCommand::Inspect { name } => {
                 client_management::inspect(&args.db, args.realm_id, name)
             }
@@ -185,12 +193,28 @@ impl ConfigArgs {
 
 #[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 },
+    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)]
@@ -205,21 +229,53 @@ impl GroupArgs {
         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())?;
+            }
+            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(())
@@ -278,7 +334,14 @@ impl TokenArgs {
                 username,
                 scopes,
             } => {
-                let token = token_management::create_auth_token(&qi, &config, args.realm_id, client.as_str(), username.as_str(), scopes.as_str())?;
+                let token = token_management::create_auth_token(
+                    &qi,
+                    &config,
+                    args.realm_id,
+                    client.as_str(),
+                    username.as_str(),
+                    scopes.as_str(),
+                )?;
                 println!("{}", token);
                 Ok(())
             }
@@ -286,8 +349,12 @@ impl TokenArgs {
                 client,
                 username,
                 scopes,
-            } => { todo!() }
-            TokenCommand::Inspect { token } => { todo!() }
+            } => {
+                todo!()
+            }
+            TokenCommand::Inspect { token } => {
+                todo!()
+            }
         }
     }
 }
@@ -312,11 +379,11 @@ impl RoleArgs {
         match &self.command {
             RoleCommand::List => {
                 todo!()
-            },
+            }
             RoleCommand::Create { name } => {
                 let add_result = qi.add(&schema::Role {
                     realm: args.realm_id,
-                    shortname: name.clone()
+                    shortname: name.clone(),
                 });
 
                 match add_result {
@@ -325,12 +392,16 @@ impl RoleArgs {
                     }
                     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();
-            },
+                qi.delete()
+                    .by(schema::Role::Realm, &args.realm_id)
+                    .by(schema::Role::Shortname, name.as_str())
+                    .exec()
+                    .unwrap();
+            }
         }
         Ok(())
     }
@@ -340,7 +411,7 @@ impl RoleArgs {
 enum UserCommand {
     List,
     Create {
-        username: String
+        username: String,
     },
     Auth {
         username: String,
@@ -350,7 +421,7 @@ enum UserCommand {
     },
     Inspect {
         username: String,
-    }
+    },
 }
 
 #[derive(Debug, Parser)]
@@ -367,13 +438,18 @@ impl UserArgs {
             UserCommand::Create { username } => {
                 user_management::create(&qi, args.realm_id, username.as_str())
             }
-            UserCommand::Auth { username, change_password } => user_management::change_auth(
+            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()),
+            UserCommand::Inspect { username } => {
+                user_management::inspect(&qi, args.realm_id, username.as_str())
+            }
         }
     }
 }

+ 2 - 0
src/config/helper.rs

@@ -1,3 +1,5 @@
+#![allow(unused_variables)]
+
 use microrm::prelude::*;
 
 use crate::schema;

+ 4 - 2
src/error.rs

@@ -1,9 +1,11 @@
-use crate::{key::KeyError, user::UserError, token::TokenError};
+use crate::{key::KeyError, token::TokenError, user::UserError};
 
 #[derive(Debug)]
 pub enum UIDCError {
     /// aborting operation because of invalid state
     Abort(&'static str),
+    /// aborting operation because of invalid state
+    AbortString(String),
     /// database access issue or violated constraint
     DatabaseError(microrm::Error),
 
@@ -38,7 +40,7 @@ macro_rules! error_converter {
                 Self::$toconv(value)
             }
         }
-    }
+    };
 }
 
 error_converter!(KeyError);

+ 68 - 25
src/group_management.rs

@@ -1,7 +1,11 @@
 use crate::{schema, UIDCError};
 use microrm::prelude::*;
 
-pub fn create_group(qi: &microrm::QueryInterface, realm_id: schema::RealmID, name: &str) -> Result<(), UIDCError> {
+pub fn create_group(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    name: &str,
+) -> Result<(), UIDCError> {
     qi.add(&schema::Group {
         realm: realm_id,
         shortname: name.into(),
@@ -9,56 +13,95 @@ pub fn create_group(qi: &microrm::QueryInterface, realm_id: schema::RealmID, nam
     Ok(())
 }
 
-pub fn list_members(qi: &microrm::QueryInterface, realm_id: schema::RealmID, name: &str) -> Result<(), UIDCError> {
-    todo!()
+pub fn list_members(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    name: &str,
+) -> Result<(), UIDCError> {
+
+    let group_id = qi.get().only_ids().by(schema::Group::Realm, &realm_id).by(schema::Group::Shortname, name).one_id()?.ok_or(UIDCError::Abort("no such group"))?;
+
+    for member in qi.get().by(schema::GroupMembership::Group, &group_id).all()? {
+        let user = qi.get().by_id(&member.user).one()?.ok_or(UIDCError::Abort("no user matching GroupMembership"))?;
+        println!("{}", user.username);
+    }
+    Ok(())
 }
 
-pub fn attach_user(qi: &microrm::QueryInterface, realm_id: schema::RealmID, group_name: &str, username: &str) -> Result<(), UIDCError> {
-    let group = qi.get().by(schema::Group::Realm, &realm_id).by(schema::Group::Shortname, group_name).one()?;
-    let user = qi.get().by(schema::User::Realm, &realm_id).by(schema::User::Username, username).one()?;
+pub fn attach_user(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    group_name: &str,
+    username: &str,
+) -> Result<(), UIDCError> {
+    let group = qi
+        .get()
+        .by(schema::Group::Realm, &realm_id)
+        .by(schema::Group::Shortname, group_name)
+        .one()?;
+    let user = qi
+        .get()
+        .by(schema::User::Realm, &realm_id)
+        .by(schema::User::Username, username)
+        .one()?;
 
     match (group, user) {
-        (None, _) => {
-            Err(UIDCError::Abort("no such group"))
-        },
-        (_, None) => {
-            Err(UIDCError::Abort("no such user"))
-        },
+        (None, _) => Err(UIDCError::Abort("no such group")),
+        (_, None) => Err(UIDCError::Abort("no such user")),
         (Some(group), Some(user)) => {
             qi.add(&schema::GroupMembership {
                 group: group.id(),
                 user: user.id(),
             })?;
             Ok(())
-        },
+        }
     }
 }
 
-pub fn detach_user(qi: &microrm::QueryInterface, realm_id: schema::RealmID, group_name: &str, username: &str) -> Result<(), UIDCError> {
+pub fn detach_user(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    group_name: &str,
+    username: &str,
+) -> Result<(), UIDCError> {
     todo!()
 }
 
-pub fn attach_role(qi: &microrm::QueryInterface, realm_id: schema::RealmID, group_name: &str, role_name: &str) -> Result<(), UIDCError> {
-    let group = qi.get().by(schema::Group::Realm, &realm_id).by(schema::Group::Shortname, group_name).one().unwrap();
-    let role = qi.get().by(schema::Role::Realm, &realm_id).by(schema::Role::Shortname, role_name).one().unwrap();
+pub fn attach_role(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    group_name: &str,
+    role_name: &str,
+) -> Result<(), UIDCError> {
+    let group = qi
+        .get()
+        .by(schema::Group::Realm, &realm_id)
+        .by(schema::Group::Shortname, group_name)
+        .one()?;
+    let role = qi
+        .get()
+        .by(schema::Role::Realm, &realm_id)
+        .by(schema::Role::Shortname, role_name)
+        .one()?;
 
     match (group, role) {
-        (None, _) => {
-            Err(UIDCError::Abort("no such group"))
-        },
-        (_, None) => {
-            Err(UIDCError::Abort("no such role"))
-        },
+        (None, _) => Err(UIDCError::Abort("no such group")),
+        (_, None) => Err(UIDCError::Abort("no such role")),
         (Some(group), Some(role)) => {
             qi.add(&schema::GroupRole {
                 group: group.id(),
                 role: role.id(),
             })?;
             Ok(())
-        },
+        }
     }
 }
 
-pub fn detach_role(qi: &microrm::QueryInterface, realm_id: schema::RealmID, group_name: &str, role: &str) -> Result<(), UIDCError> {
+pub fn detach_role(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    group_name: &str,
+    role: &str,
+) -> Result<(), UIDCError> {
     todo!()
 }

+ 1 - 1
src/jwt.rs

@@ -12,7 +12,7 @@ pub struct JWTData<'l> {
 
 impl<'l> Into<String> for JWTData<'l> {
     fn into(self) -> String {
-        serde_json::to_string(&self).unwrap()
+        serde_json::to_string(&self).expect("JSON serialization failure?")
     }
 }
 

+ 15 - 30
src/key.rs

@@ -1,41 +1,31 @@
-use crate::{schema,UIDCError};
+use crate::{schema, UIDCError};
 use microrm::prelude::*;
 use ring::signature::{Ed25519KeyPair, KeyPair};
 use sha2::Digest;
-use std::collections::HashMap;
 
 #[derive(Debug)]
 pub enum KeyError {
     Plain(&'static str),
 }
 
-pub struct KeyStore<'a> {
-    db: &'a microrm::DB,
-    qi: microrm::QueryInterface<'a>,
-    keys: HashMap<String, Ed25519KeyPair>,
+pub struct KeyStore<'a, 'r> {
+    qi: &'r microrm::QueryInterface<'a>,
 }
 
-impl<'a> KeyStore<'a> {
-    pub fn new(db: &'a microrm::DB) -> Self {
+impl<'a, 'r> KeyStore<'a, 'r> {
+    pub fn new(qi: &'r microrm::QueryInterface<'a>) -> Self {
         Self {
-            db,
-            qi: db.query_interface(),
-            keys: HashMap::new(),
+            qi
         }
     }
 
     pub fn generate_in(&self, realm_id: schema::RealmID) -> Result<String, UIDCError> {
         let mut rng = ring::rand::SystemRandom::new();
-        let sign_generated = Ed25519KeyPair::generate_pkcs8(&mut rng);
-
-        if let Err(_) = sign_generated {
-            return Err(KeyError::Plain("Failed to generate key").into())
-        }
-        let sign_generated = sign_generated.unwrap();
+        let sign_generated = Ed25519KeyPair::generate_pkcs8(&mut rng).map_err(|_| KeyError::Plain("failed to generate key"))?;
 
         let keydata = sign_generated.as_ref().to_owned();
 
-        let loaded_key = Ed25519KeyPair::from_pkcs8(keydata.as_slice()).unwrap();
+        let loaded_key = Ed25519KeyPair::from_pkcs8(keydata.as_slice()).expect("couldn't load just-generated key");
         let pubkey = loaded_key.public_key();
 
         let mut key_hasher = sha2::Sha256::new();
@@ -43,12 +33,11 @@ impl<'a> KeyStore<'a> {
         let mut key_id = base64::encode(key_hasher.finalize());
         key_id.truncate(16);
 
-        self.qi
-            .add(&schema::Key {
-                realm: realm_id,
-                key_id: key_id.clone(),
-                keydata,
-            })?;
+        self.qi.add(&schema::Key {
+            realm: realm_id,
+            key_id: key_id.clone(),
+            keydata,
+        })?;
 
         Ok(key_id)
     }
@@ -56,14 +45,10 @@ impl<'a> KeyStore<'a> {
 
 pub fn inspect(db: &microrm::DB, realm_id: schema::RealmID) -> Result<(), UIDCError> {
     let qi = db.query_interface();
-    let cs = KeyStore::new(db);
     println!("Keystore loaded.");
 
     println!("Retrieving keys realm...");
-    let keys = qi
-        .get()
-        .by(schema::Key::Realm, &realm_id)
-        .all()?;
+    let keys = qi.get().by(schema::Key::Realm, &realm_id).all()?;
 
     for key in keys {
         println!("- [{:20}]", key.key_id);
@@ -73,7 +58,7 @@ pub fn inspect(db: &microrm::DB, realm_id: schema::RealmID) -> Result<(), UIDCEr
 
 pub fn generate(db: &microrm::DB, realm_id: schema::RealmID) -> Result<(), UIDCError> {
     let qi = db.query_interface();
-    let cs = KeyStore::new(db);
+    let cs = KeyStore::new(&qi);
     match cs.generate_in(realm_id) {
         Ok(_) => (),
         Err(e) => {

+ 1 - 0
src/role_management.rs

@@ -0,0 +1 @@
+

+ 8 - 2
src/server.rs

@@ -34,7 +34,11 @@ 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) -> Result<(), UIDCError> {
+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);
@@ -67,5 +71,7 @@ pub async fn run_server(db: microrm::DB, config: config::Config, port: u16) -> R
     session::session_v1_server(app.at("/:realm/v1/session/"));
     oidc::oidc_server(app.at("/:realm/"));
 
-    app.listen(("127.0.0.1", port)).await.map_err(|_| UIDCError::Abort("couldn't listen on port"))
+    app.listen(("127.0.0.1", port))
+        .await
+        .map_err(|_| UIDCError::Abort("couldn't listen on port"))
 }

+ 1 - 3
src/server/session.rs

@@ -318,9 +318,7 @@ async fn v1_login_post(mut req: Request) -> tide::Result<tide::Response> {
                             error = Some("Incorrect response. Please try again".into());
                         }
                         Err(_) => {
-                            error = Some(
-                                "Internal error. Please contact an administrator.".into(),
-                            );
+                            error = Some("Internal error. Please contact an administrator.".into());
                         }
                     }
                 } else {

+ 31 - 25
src/token.rs

@@ -1,4 +1,4 @@
-use crate::{config, jwt, schema};
+use crate::{config, jwt, schema, UIDCError};
 use microrm::{entity::EntityID, prelude::*};
 
 #[derive(Debug)]
@@ -27,10 +27,22 @@ pub fn generate_auth_token<'a>(
     client: schema::ClientID,
     user: schema::UserID,
     scopes: impl Iterator<Item = &'a str>,
-) -> Result<String, TokenError> {
-    let realm = qi.get().by_id(&realm).one()?.ok_or(TokenError::RequestError("no such realm"))?;
-    let client = qi.get().by_id(&client).one()?.ok_or(TokenError::RequestError("no such client"))?;
-    let user = qi.get().by_id(&user).one()?.ok_or(TokenError::RequestError("no such user"))?;
+) -> Result<String, UIDCError> {
+    let realm = qi
+        .get()
+        .by_id(&realm)
+        .one()?
+        .ok_or(TokenError::RequestError("no such realm"))?;
+    let client = qi
+        .get()
+        .by_id(&client)
+        .one()?
+        .ok_or(TokenError::RequestError("no such client"))?;
+    let user = qi
+        .get()
+        .by_id(&user)
+        .one()?
+        .ok_or(TokenError::RequestError("no such user"))?;
 
     let issuer = format!("{}/{}", config.base_url, realm.shortname,);
 
@@ -44,11 +56,7 @@ pub fn generate_auth_token<'a>(
         .by(schema::GroupMembership::User, &user.id())
         .all()?
     {
-        for group_role in qi
-            .get()
-            .by(schema::GroupRole::Group, &group.id())
-            .all()?
-        {
+        for group_role in qi.get().by(schema::GroupRole::Group, &group.id()).all()? {
             user_roles.push(group_role.role);
         }
     }
@@ -57,11 +65,7 @@ pub fn generate_auth_token<'a>(
     let mut requested_roles = vec![];
     for scope in scopes {
         if let Some(scope) = qi.get().by(schema::Scope::Shortname, scope).one()? {
-            for scope_role in qi
-                .get()
-                .by(schema::ScopeRole::Scope, &scope.id())
-                .all()?
-            {
+            for scope_role in qi.get().by(schema::ScopeRole::Scope, &scope.id()).all()? {
                 requested_roles.push(scope_role.role);
             }
         }
@@ -77,15 +81,13 @@ pub fn generate_auth_token<'a>(
         .iter()
         .filter(|req| user_roles.contains(req))
         .map(|role_id| {
-            serde_json::Value::String(
+            Ok(serde_json::Value::String(
                 qi.get()
                     .by_id(role_id)
-                    .one()
-                    .unwrap()
-                    .unwrap()
-                    .shortname
-                    .clone(),
-            )
+                    .one()?
+                    .ok_or(UIDCError::Abort("inconsistent role state"))?
+                    .wrapped().shortname,
+            ))
         });
 
     let token = jwt::JWTData {
@@ -96,13 +98,17 @@ pub fn generate_auth_token<'a>(
         exp: exp.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
         extras: [(
             "roles",
-            serde_json::Value::Array(resulting_roles.collect::<Vec<_>>()),
+            serde_json::Value::Array(resulting_roles.collect::<Result<Vec<_>,UIDCError>>()?),
         )]
         .into(),
     };
 
-    let key = qi.get().by(schema::Key::Realm, &realm.id()).one()?.ok_or(TokenError::InternalError("no signing key for realm"))?;
-    let kpair = ring::signature::Ed25519KeyPair::from_pkcs8(key.keydata.as_slice())?;
+    let key = qi
+        .get()
+        .by(schema::Key::Realm, &realm.id())
+        .one()?
+        .ok_or(TokenError::InternalError("no signing key for realm"))?;
+    let kpair = ring::signature::Ed25519KeyPair::from_pkcs8(key.keydata.as_slice()).map_err(Into::<TokenError>::into)?;
 
     Ok(jwt::JWT::sign(&kpair, token).into_string())
 }

+ 17 - 12
src/token_management.rs

@@ -1,23 +1,28 @@
-use crate::{schema, UIDCError, config::Config, token};
+use crate::{config::Config, schema, token, UIDCError};
 use microrm::prelude::*;
 
-pub fn create_auth_token(qi: &microrm::QueryInterface, config: &Config, realm_id: schema::RealmID, client: &str, username: &str, scopes: &str) -> Result<String, UIDCError> {
+pub fn create_auth_token(
+    qi: &microrm::QueryInterface,
+    config: &Config,
+    realm_id: schema::RealmID,
+    client: &str,
+    username: &str,
+    scopes: &str,
+) -> Result<String, UIDCError> {
     Ok(token::generate_auth_token(
-        &config,
-        &qi,
+        config,
+        qi,
         realm_id,
-        qi.get()
+        qi.get().only_ids()
             .by(schema::Client::Realm, &realm_id)
             .by(schema::Client::Shortname, client)
-            .one()?
-            .expect("no such client")
-            .id(),
-        qi.get()
+            .one_id()?
+            .ok_or(UIDCError::Abort("no such client"))?,
+        qi.get().only_ids()
             .by(schema::User::Realm, &realm_id)
             .by(schema::User::Username, username)
-            .one()?
-            .expect("no such user")
-            .id(),
+            .one_id()?
+            .ok_or(UIDCError::Abort("no such user"))?,
         scopes.split_whitespace(),
     )?)
 }

+ 24 - 14
src/user.rs

@@ -22,13 +22,25 @@ impl User {
         }
     }
 
-    pub fn change_username(&self, qi: &microrm::QueryInterface, new_name: &str) -> Result<(), UIDCError> {
+    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() {
+        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()?)
+        } else {
+            Ok(qi
+                .update()
+                .update(schema::User::Username, new_name)
+                .by_id(&self.id)
+                .exec()?)
         }
     }
 
@@ -99,16 +111,14 @@ impl User {
     ) -> Result<bool, UIDCError> {
         use ring::pbkdf2;
 
-        Ok(
-            pbkdf2::verify(
-                pbkdf2::PBKDF2_HMAC_SHA256,
-                PBKDF2_ROUNDS,
-                challenge.public.as_slice(),
-                response,
-                challenge.secret.as_slice(),
-            )
-            .is_ok(),
+        Ok(pbkdf2::verify(
+            pbkdf2::PBKDF2_HMAC_SHA256,
+            PBKDF2_ROUNDS,
+            challenge.public.as_slice(),
+            response,
+            challenge.secret.as_slice(),
         )
+        .is_ok())
     }
 
     fn verify_totp_challenge(

+ 40 - 16
src/user_management.rs

@@ -2,19 +2,13 @@ use crate::{schema, user, UIDCError};
 use microrm::prelude::*;
 
 pub fn list(qi: &microrm::QueryInterface, realm_id: schema::RealmID) -> Result<(), UIDCError> {
-    let users = qi
-        .get()
-        .by(schema::User::Realm, &realm_id)
-        .all()?;
+    let users = qi.get().by(schema::User::Realm, &realm_id).all()?;
 
     println!("User list ({} users):", users.len());
 
     for user in &users {
         println!("- {:20}", user.username);
-        let auth_challenges = qi
-            .get()
-            .by(schema::AuthChallenge::User, &user.id())
-            .all()?;
+        let auth_challenges = qi.get().by(schema::AuthChallenge::User, &user.id()).all()?;
         for ch in &auth_challenges {
             println!("    - Has {:?} authentication challenge", ch.challenge_type);
         }
@@ -23,7 +17,11 @@ pub fn list(qi: &microrm::QueryInterface, realm_id: schema::RealmID) -> Result<(
     Ok(())
 }
 
-pub fn create(qi: &microrm::QueryInterface, realm_id: schema::RealmID, username: &str) -> Result<(), UIDCError> {
+pub fn create(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    username: &str,
+) -> Result<(), UIDCError> {
     // check that the user doesn't exist already
     let existing_user = qi
         .get()
@@ -46,7 +44,12 @@ pub fn create(qi: &microrm::QueryInterface, realm_id: schema::RealmID, username:
     Ok(())
 }
 
-pub fn change_auth(qi: &microrm::QueryInterface, realm_id: schema::RealmID, username: &str, change_password: bool) -> Result<(), UIDCError> {
+pub fn change_auth(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    username: &str,
+    change_password: bool,
+) -> Result<(), UIDCError> {
     // check that the user exists
     let existing_user = qi
         .get()
@@ -64,18 +67,39 @@ pub fn change_auth(qi: &microrm::QueryInterface, realm_id: schema::RealmID, user
     Ok(())
 }
 
-pub fn inspect(qi: &microrm::QueryInterface, realm_id: schema::RealmID, username: &str) -> Result<(), UIDCError> {
-    let user = qi.get().by(schema::User::Realm, &realm_id).by(schema::User::Username, username).one()?;
+pub fn inspect(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    username: &str,
+) -> Result<(), UIDCError> {
+    let user = qi
+        .get()
+        .by(schema::User::Realm, &realm_id)
+        .by(schema::User::Username, username)
+        .one()?;
 
     if let Some(user) = user {
         println!("User found: {}", username);
         println!("Groups:");
-        for group_membership in qi.get().by(schema::GroupMembership::User, &user.id()).all()? {
-            let group = qi.get().by_id(&group_membership.group).one()?.expect("reference to nonexistent group?");
+        for group_membership in qi
+            .get()
+            .by(schema::GroupMembership::User, &user.id())
+            .all()?
+        {
+            let group = qi
+                .get()
+                .by_id(&group_membership.group)
+                .one()?
+                .ok_or(UIDCError::Abort("reference to nonexistent group"))?;
             println!(" - {}", group.shortname);
         }
-    }
-    else {
+
+        println!("Authentication methods:");
+
+        for challenge in qi.get().by(schema::AuthChallenge::User, &user.id()).all()? {
+            println!(" - {:?}", challenge.challenge_type);
+        }
+    } else {
         println!("No such user {} in realm", username);
     }