Kestrel 1 жил өмнө
parent
commit
0e324bffb2

+ 1 - 1
simple-setup.sh

@@ -5,7 +5,7 @@ UIDC=./target/debug/uidc
 
 $UIDC init
 $UIDC config load /dev/stdin <<EOF
-base_url = "http://localhost:2114/"
+base_url = "http://localhost:2114"
 EOF
 
 $UIDC key generate

+ 56 - 7
src/cli.rs

@@ -1,7 +1,7 @@
 use crate::{
     client_management, config, group_management, key,
     schema::{self, RealmID},
-    server, token, token_management, user_management, UIDCError,
+    server, token, token_management, user_management, UIDCError, scope_management,
 };
 use clap::{Parser, Subcommand};
 use microrm::prelude::*;
@@ -34,6 +34,8 @@ enum Command {
     /// key management
     Key(KeyArgs),
     /// run the actual OIDC server
+    Scope(ScopeArgs),
+    /// run the actual OIDC server
     Server(ServerArgs),
     /// manual token generation and inspection
     Token(TokenArgs),
@@ -73,6 +75,7 @@ impl RootArgs {
             Command::Client(v) => v.run(ra).await,
             Command::Group(v) => v.run(ra).await,
             Command::Key(v) => v.run(ra).await,
+            Command::Scope(v) => v.run(ra).await,
             Command::Server(v) => v.run(ra).await,
             Command::Token(v) => v.run(ra).await,
             Command::Role(v) => v.run(ra).await,
@@ -199,6 +202,10 @@ enum GroupCommand {
     Members {
         group_name: String,
     },
+    Roles {
+        group_name: String,
+    },
+    List,
     AttachRole {
         group_name: String,
         role_name: String,
@@ -233,6 +240,12 @@ impl GroupArgs {
             GroupCommand::Members { group_name } => {
                 group_management::list_members(&qi, args.realm_id, group_name.as_str())?;
             }
+            GroupCommand::Roles { group_name } => {
+                group_management::list_roles(&qi, args.realm_id, group_name.as_str())?;
+            }
+            GroupCommand::List => {
+                group_management::list_groups(&qi, args.realm_id)?;
+            }
             GroupCommand::AttachRole {
                 group_name,
                 role_name,
@@ -282,6 +295,35 @@ impl GroupArgs {
     }
 }
 
+
+#[derive(Debug, Subcommand)]
+enum ScopeCommand {
+    AttachRole { scope_name: String, role_name: String },
+    Create { scope_name: String },
+    DetachRole { scope_name: String, role_name: String },
+    Inspect { scope_name: String },
+    List,
+}
+
+#[derive(Debug, Parser)]
+struct ScopeArgs {
+    #[clap(subcommand)]
+    command: ScopeCommand,
+}
+
+impl ScopeArgs {
+    async fn run(&self, args: RunArgs) -> Result<(), UIDCError> {
+        let qi = args.db.query_interface();
+        match &self.command {
+            ScopeCommand::AttachRole { scope_name, role_name } => scope_management::attach_role(&qi, args.realm_id, scope_name.as_str(), role_name.as_str()),
+            ScopeCommand::Create { scope_name } => scope_management::create_scope(&qi, args.realm_id, scope_name.as_str()),
+            ScopeCommand::DetachRole { scope_name, role_name } => todo!(),
+            ScopeCommand::Inspect { scope_name } => scope_management::inspect_scope(&qi, args.realm_id, scope_name.as_str()),
+            ScopeCommand::List => scope_management::list_scopes(&qi, args.realm_id),
+        }
+    }
+}
+
 #[derive(Debug, Parser)]
 struct ServerArgs {
     #[clap(short, long)]
@@ -314,7 +356,7 @@ enum TokenCommand {
         scopes: String,
     },
     Inspect {
-        token: String,
+        token: Option<String>,
     },
 }
 
@@ -350,10 +392,19 @@ impl TokenArgs {
                 username,
                 scopes,
             } => {
-                todo!()
+                let token = token_management::create_refresh_token(
+                    &qi,
+                    &config,
+                    args.realm_id,
+                    client.as_str(),
+                    username.as_str(),
+                    scopes.as_str(),
+                )?;
+                println!("{}", token);
+                Ok(())
             }
             TokenCommand::Inspect { token } => {
-                todo!()
+                token_management::inspect_token(&qi, &config, args.realm_id, token.as_ref().map(|s| s.as_str()))
             }
         }
     }
@@ -375,7 +426,6 @@ struct RoleArgs {
 impl RoleArgs {
     async fn run(&self, args: RunArgs) -> Result<(), UIDCError> {
         let qi = args.db.query_interface();
-        // let config = config::Config::build_from(&qi, None);
         match &self.command {
             RoleCommand::List => {
                 todo!()
@@ -399,8 +449,7 @@ impl RoleArgs {
                 qi.delete()
                     .by(schema::Role::Realm, &args.realm_id)
                     .by(schema::Role::Shortname, name.as_str())
-                    .exec()
-                    .unwrap();
+                    .exec()?;
             }
         }
         Ok(())

+ 26 - 0
src/group_management.rs

@@ -13,6 +13,16 @@ pub fn create_group(
     Ok(())
 }
 
+pub fn list_groups(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID) -> Result<(), UIDCError> {
+    
+    for group in qi.get().by(schema::Group::Realm, &realm_id).all()? {
+        println!("{}", group.shortname);
+    }
+    Ok(())
+}
+
 pub fn list_members(
     qi: &microrm::QueryInterface,
     realm_id: schema::RealmID,
@@ -28,6 +38,22 @@ pub fn list_members(
     Ok(())
 }
 
+pub fn list_roles(
+    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::GroupRole::Group, &group_id).all()? {
+        let role = qi.get().by_id(&member.role).one()?.ok_or(UIDCError::Abort("no role matching GroupRole"))?;
+        println!("{}", role.shortname);
+    }
+    Ok(())
+}
+
+
 pub fn attach_user(
     qi: &microrm::QueryInterface,
     realm_id: schema::RealmID,

+ 13 - 5
src/jwt.rs

@@ -20,7 +20,8 @@ pub const DEFAULT_HEADER: &'static str = r#"{"alg": "EdDSA", "typ": "JWT"}"#;
 
 pub struct JWT {
     pub header: String,
-    pub data: String,
+    pub unencoded_data: Vec<u8>,
+    pub encoded_data: String,
     pub signature: String,
 }
 
@@ -48,15 +49,21 @@ impl JWT {
         // if we got this far, the verification passed
         Some(Self {
             header: header.into(),
-            data: data.into(),
+            unencoded_data: base64::decode_config(data.as_bytes(), base64::URL_SAFE_NO_PAD).ok()?,
+            encoded_data: data.to_string(),
             signature: signature.into(),
         })
     }
 
+    pub fn claims(&self) -> Option<JWTData> {
+        serde_json::from_slice(self.unencoded_data.as_slice()).ok()
+    }
+
     pub fn sign(with: &ring::signature::Ed25519KeyPair, data: JWTData) -> Self {
         let header = base64::encode_config(DEFAULT_HEADER, base64::URL_SAFE_NO_PAD);
+        let unencoded_data = Into::<String>::into(data);
         let data = base64::encode_config(
-            <JWTData as Into<String>>::into(data),
+            unencoded_data.as_bytes(),
             base64::URL_SAFE_NO_PAD,
         );
 
@@ -69,13 +76,14 @@ impl JWT {
 
         Self {
             header,
-            data,
+            unencoded_data: unencoded_data.as_bytes().into(),
+            encoded_data: data,
             signature,
         }
     }
 
     pub fn into_string(self) -> String {
-        self.header + "." + self.data.as_str() + "." + self.signature.as_str()
+        self.header + "." + self.encoded_data.as_str() + "." + self.signature.as_str()
     }
 }
 

+ 1 - 0
src/main.rs

@@ -7,6 +7,7 @@ mod jwt;
 mod key;
 mod role_management;
 mod schema;
+mod scope_management;
 mod server;
 mod token;
 mod token_management;

+ 73 - 0
src/scope_management.rs

@@ -0,0 +1,73 @@
+use crate::{schema, UIDCError};
+use microrm::prelude::*;
+
+pub fn create_scope(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    name: &str,
+) -> Result<(), UIDCError> {
+    qi.add(&schema::Scope {
+        realm: realm_id,
+        shortname: name.into(),
+    })?;
+    Ok(())
+}
+
+pub fn list_scopes(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID) -> Result<(), UIDCError> {
+    
+    for scope in qi.get().by(schema::Scope::Realm, &realm_id).all()? {
+        println!("{}", scope.shortname);
+    }
+    Ok(())
+}
+
+pub fn inspect_scope(qi: &microrm::QueryInterface, realm_id: schema::RealmID, scope_name: &str) -> Result<(), UIDCError> {
+    let scope = qi
+        .get()
+        .by(schema::Scope::Realm, &realm_id)
+        .by(schema::Scope::Shortname, scope_name)
+        .one()?
+        .ok_or(UIDCError::Abort("no such scope"))?;
+
+    println!("scope name: {}", scope.shortname);
+
+    println!("attached roles:");
+    for scope_role in qi.get().by(schema::ScopeRole::Scope, &scope.id()).all()? {
+        let role = qi.get().by_id(&scope_role.role).one()?.ok_or(UIDCError::Abort("role referenced that no longer exists?"))?;
+        println!(" - {}", role.shortname);
+    }
+
+    Ok(())
+}
+
+pub fn attach_role(
+    qi: &microrm::QueryInterface,
+    realm_id: schema::RealmID,
+    scope_name: &str,
+    role_name: &str,
+) -> Result<(), UIDCError> {
+    let scope = qi
+        .get()
+        .by(schema::Scope::Realm, &realm_id)
+        .by(schema::Scope::Shortname, scope_name)
+        .one()?;
+    let role = qi
+        .get()
+        .by(schema::Role::Realm, &realm_id)
+        .by(schema::Role::Shortname, role_name)
+        .one()?;
+
+    match (scope, role) {
+        (None, _) => Err(UIDCError::Abort("no such scope")),
+        (_, None) => Err(UIDCError::Abort("no such role")),
+        (Some(scope), Some(role)) => {
+            qi.add(&schema::ScopeRole {
+                scope: scope.id(),
+                role: role.id(),
+            })?;
+            Ok(())
+        }
+    }
+}

+ 11 - 0
src/token.rs

@@ -112,3 +112,14 @@ pub fn generate_auth_token<'a>(
 
     Ok(jwt::JWT::sign(&kpair, token).into_string())
 }
+
+pub fn generate_refresh_token<'a>(
+    config: &config::Config,
+    qi: &microrm::QueryInterface,
+    realm: schema::RealmID,
+    client: schema::ClientID,
+    user: schema::UserID,
+    scopes: impl Iterator<Item = &'a str>,
+) -> Result<String, UIDCError> {
+    todo!()
+}

+ 61 - 3
src/token_management.rs

@@ -1,5 +1,6 @@
-use crate::{config::Config, schema, token, UIDCError};
+use crate::{config::Config, schema, token, UIDCError, jwt};
 use microrm::prelude::*;
+use ring::signature::KeyPair;
 
 pub fn create_auth_token(
     qi: &microrm::QueryInterface,
@@ -9,7 +10,7 @@ pub fn create_auth_token(
     username: &str,
     scopes: &str,
 ) -> Result<String, UIDCError> {
-    Ok(token::generate_auth_token(
+    token::generate_auth_token(
         config,
         qi,
         realm_id,
@@ -24,5 +25,62 @@ pub fn create_auth_token(
             .one_id()?
             .ok_or(UIDCError::Abort("no such user"))?,
         scopes.split_whitespace(),
-    )?)
+    )
+}
+
+pub fn create_refresh_token(
+    qi: &microrm::QueryInterface,
+    config: &Config,
+    realm_id: schema::RealmID,
+    client: &str,
+    username: &str,
+    scopes: &str,
+) -> Result<String, UIDCError> {
+    token::generate_refresh_token(
+        config,
+        qi,
+        realm_id,
+        qi.get().only_ids()
+            .by(schema::Client::Realm, &realm_id)
+            .by(schema::Client::Shortname, client)
+            .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_id()?
+            .ok_or(UIDCError::Abort("no such user"))?,
+        scopes.split_whitespace(),
+    )
+}
+
+pub fn inspect_token(qi: &microrm::QueryInterface, config: &Config, realm_id: schema::RealmID, token: Option<&str>) -> Result<(), UIDCError> {
+    let key = qi.get().by(schema::Key::Realm, &realm_id).one()?.ok_or(UIDCError::Abort("no key for realm"))?;
+
+    let kpair = ring::signature::Ed25519KeyPair::from_pkcs8(key.keydata.as_slice()).map_err(|_| UIDCError::Abort("could not load key"))?;
+
+    let pubkey = ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, kpair.public_key().as_ref());
+
+    let token = match token {
+        Some(token) => token.to_string(),
+        None => rpassword::prompt_password("Enter token: ").unwrap(),
+    };
+
+    let jwt = jwt::JWT::verify(&pubkey, token.as_str());
+    if let Some(claims) = jwt.as_ref().and_then(jwt::JWT::claims) {
+        println!("Base claims:");
+        println!(" - issuer    : {}", claims.iss);
+        println!(" - audience  : {}", claims.aud);
+        println!(" - subject   : {}", claims.sub);
+        println!(" - issued at : {} [{}]", claims.iat, "");
+        println!(" - expires at: {} [{}]", claims.exp, "");
+        for claim in claims.extras {
+            println!(" - {:10}: {}", claim.0, claim.1);
+
+        }
+    }
+    else {
+        println!("Signature validation against realm key or claim parsing failed!");
+    }
+    Ok(())
 }

+ 1 - 1
src/user_management.rs

@@ -62,7 +62,7 @@ pub fn change_auth(
 
     if change_password {
         let raw_pass = rpassword::prompt_password("Enter new user password: ").unwrap();
-        user.set_new_password(&qi, raw_pass.as_bytes());
+        user.set_new_password(qi, raw_pass.as_bytes());
     }
     Ok(())
 }