Skip to content

Commit

Permalink
runes: Introduce Check for request context
Browse files Browse the repository at this point in the history
We introduce a Context struct that holds data relevant to a signing
request and that we want to check against a rune. The struct currently
checks the following informations:
-method: the rpc method
-pubkey: the pubkey that was used to sign the request
-time: the timestamp that was part of the rune. (this way we COULD
create a rune per command that times out after a certain time)

Signed-off-by: Peter Neuroth <[email protected]>
  • Loading branch information
nepet committed Nov 21, 2023
1 parent 5c966dd commit 81767fa
Showing 1 changed file with 192 additions and 4 deletions.
196 changes: 192 additions & 4 deletions libs/gl-client/src/runes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use futhark::{Alternative, Check, Condition, ConditionChecker, Restriction, Rune, RuneError};
use std::fmt::Display;

use futhark::{Alternative, Condition, Restriction, Rune, RuneError};
use std::time::{SystemTime, UNIX_EPOCH};

/// Represents an entity that can provide restrictions.
///
Expand Down Expand Up @@ -151,11 +151,55 @@ fn alternative(field: &str, cond: Condition, value: &str) -> Result<Alternative,
Alternative::new(field.to_string(), cond, value.to_string(), false)
}

/// A context struct that holds information relevant to check a command against
/// a rune.
#[derive(Clone)]
pub struct Context {
// The rpc method associated with the request.
pub method: String,
// The public key associated with the request.
pub pubkey: String,
// The timestamp associated with the request.
pub time: SystemTime,
// Todo (nepet): Add param field that uses enum or serde to store the params of a call.
}

/// Implementation of the `Check` trait for the `Context` struct, allowing it to
/// perform checks on rune alternatives.
impl Check for Context {
/// Performs a check on an alternative based on the context's fields.
///
/// # Arguments
///
/// * `alt` - The alternative to check against the context.
///
/// # Returns
///
/// * `Ok(())` if the check is successful, an `Err` containing a `RuneError` otherwise.
fn check_alternative(&self, alt: &Alternative) -> anyhow::Result<(), RuneError> {
let value = match alt.get_field().as_str() {
"method" => self.method.clone(),
"pubkey" => self.pubkey.clone(),
"time" => self
.time
.duration_since(UNIX_EPOCH)
.map_err(|e| {
RuneError::Unknown(format!("Can not extract seconds from timestamp {:?}", e))
})?
.as_secs()
.to_string(),
_ => String::new(), // If we don't know the field we can not set it!
};
ConditionChecker { value }.check_alternative(alt)
}
}

#[cfg(test)]
mod tests {
use super::{DefRules, RuneFactory};
use super::{Context, DefRules, RuneFactory};
use base64::{engine::general_purpose, Engine as _};
use futhark::Rune;
use futhark::{Alternative, Condition, Restriction, Rune};
use std::time::SystemTime;

#[test]
fn test_carve_readonly_rune() {
Expand Down Expand Up @@ -200,4 +244,148 @@ mod tests {
let r = DefRules::Add(&[DefRules::Pay, DefRules::ReadOnly]);
assert_eq!(format!("{}", r), "pay|readonly");
}

#[test]
fn test_context_check() {
let seedsecret = &[0; 32];
let mr = Rune::new_master_rune(seedsecret, vec![], None, None).unwrap();

// r1 restrictions: "pubkey=020000000000000000"
let r1 = Rune::new(
mr.authcode(),
vec![Restriction::new(vec![Alternative::new(
String::from("pubkey"),
Condition::Equal,
String::from("020000000000000000"),
false,
)
.unwrap()])
.unwrap()],
None,
None,
)
.unwrap();

// r2 restrictions: "method=GetInfo"
let r2 = Rune::new(
mr.authcode(),
vec![Restriction::new(vec![Alternative::new(
String::from("method"),
Condition::Equal,
String::from("GetInfo"),
false,
)
.unwrap()])
.unwrap()],
None,
None,
)
.unwrap();

// r3 restrictions: "pubkey!"
let r3 = Rune::new(
mr.authcode(),
vec![Restriction::new(vec![Alternative::new(
String::from("pubkey"),
Condition::Missing,
String::new(),
false,
)
.unwrap()])
.unwrap()],
None,
None,
)
.unwrap();

// r4 restriction: "method!"
let r4 = Rune::new(
mr.authcode(),
vec![Restriction::new(vec![Alternative::new(
String::from("method"),
Condition::Missing,
String::new(),
false,
)
.unwrap()])
.unwrap()],
None,
None,
)
.unwrap();

// These should succeed.
// Check with method="", pubkey=020000000000000000
let ctx = Context {
method: String::new(),
pubkey: String::from("020000000000000000"),
time: SystemTime::now(),
};
assert!(r1.are_restrictions_met(ctx).is_ok());
// Check with method="ListFunds", pubkey=020000000000000000
let ctx = Context {
method: String::from("ListFunds"),
pubkey: String::from("020000000000000000"),
time: SystemTime::now(),
};
assert!(r1.are_restrictions_met(ctx).is_ok());
// Check with method="GetInfo", pubkey=""
let ctx = Context {
method: String::from("GetInfo"),
pubkey: String::new(),
time: SystemTime::now(),
};
assert!(r2.are_restrictions_met(ctx).is_ok());
// Check with method="GetInfo", pubkey="020000000000000000"
let ctx = Context {
method: String::from("GetInfo"),
pubkey: String::from("020000000000000000"),
time: SystemTime::now(),
};
assert!(r2.are_restrictions_met(ctx).is_ok());
// Check with method="GetInfo", pubkey=""
let ctx = Context {
method: String::from("GetInfo"),
pubkey: String::new(),
time: SystemTime::now(),
};
assert!(r3.are_restrictions_met(ctx).is_ok());
// Check with method="", pubkey="020000"
let ctx = Context {
method: String::new(),
pubkey: String::from("020000000000000000"),
time: SystemTime::now(),
};
assert!(r4.are_restrictions_met(ctx).is_ok());

// These should fail.
// Check with method="ListFunds", pubkey=030000, wrong pubkey.
let ctx = Context {
method: String::from("ListFunds"),
pubkey: String::from("030000"),
time: SystemTime::now(),
};
assert!(r1.are_restrictions_met(ctx).is_err());
// Check with method="ListFunds", pubkey=030000, wrong method.
let ctx = Context {
method: String::from("ListFunds"),
pubkey: String::from("030000"),
time: SystemTime::now(),
};
assert!(r2.are_restrictions_met(ctx).is_err());
// Check with pubkey=030000, pubkey present.
let ctx = Context {
method: String::new(),
pubkey: String::from("030000"),
time: SystemTime::now(),
};
assert!(r3.are_restrictions_met(ctx).is_err());
// Check with method="GetInfo", method present.
let ctx = Context {
method: String::from("GetInfo"),
pubkey: String::new(),
time: SystemTime::now(),
};
assert!(r4.are_restrictions_met(ctx).is_err());
}
}

0 comments on commit 81767fa

Please sign in to comment.