aboutsummaryrefslogtreecommitdiffstats
path: root/rust/src
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2022-09-26 14:54:58 -0700
committerBryan Newbold <bnewbold@robocracy.org>2022-09-26 14:55:01 -0700
commit0b69a766d4ecd92608e62b5e6bfed9d7e4797bf0 (patch)
treebd6100f5fb6efd6c8a7d764cc0057b9c5013dd0e /rust/src
parent8bdd5fd92a33cf05424447241033bd529b68af77 (diff)
downloadfatcat-0b69a766d4ecd92608e62b5e6bfed9d7e4797bf0.tar.gz
fatcat-0b69a766d4ecd92608e62b5e6bfed9d7e4797bf0.zip
rust: switch from patched macaroons to upstream v0.2.0
Much fo the changes here are to deal with the 'ByteString' type that is included as part of the macaroons library itself.
Diffstat (limited to 'rust/src')
-rw-r--r--rust/src/auth.rs183
1 files changed, 111 insertions, 72 deletions
diff --git a/rust/src/auth.rs b/rust/src/auth.rs
index b88a72b0..448fb798 100644
--- a/rust/src/auth.rs
+++ b/rust/src/auth.rs
@@ -5,7 +5,7 @@
//! role-based authentication (RBAC).
use data_encoding::BASE64;
-use macaroon::{Format, Macaroon, Verifier};
+use macaroon::{ByteString, Caveat, Format, Macaroon, MacaroonError, MacaroonKey, Verifier};
use std::fmt;
use swagger::auth::{AuthData, Authorization, Scopes};
@@ -23,8 +23,8 @@ use std::collections::HashMap;
use std::env;
use std::str::FromStr;
-// 32 bytes max (!)
-static DUMMY_KEY: &[u8] = b"dummy-key-a-one-two-three-a-la";
+// 32 bytes exactly
+static DUMMY_KEY_BYTES: &[u8; 32] = b"dummy-key-a-one-two-three-a-la!!";
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FatcatRole {
@@ -178,8 +178,22 @@ impl iron::middleware::BeforeMiddleware for MacaroonAuthMiddleware {
pub struct AuthConfectionary {
pub location: String,
pub identifier: String,
- pub key: Vec<u8>,
- pub root_keys: HashMap<String, Vec<u8>>,
+ pub key: MacaroonKey,
+ pub root_keys: HashMap<String, MacaroonKey>,
+}
+
+fn parse_macaroon_key(key_base64: &str) -> Result<MacaroonKey> {
+ // while we created the base64-encoded key from just 32 bytes of binary data, because of
+ // padding the result decodes in to 33 bytes of data. we just drop the last byte
+ let mut key_bytes: [u8; 33] = [0; 33];
+ if key_base64.len() != 44 {
+ bail!("bad base64-padded-encoded key for macaroons");
+ }
+ BASE64
+ .decode_mut(key_base64.as_bytes(), &mut key_bytes)
+ .expect("base64 key decode");
+ let key: MacaroonKey = key_bytes[..32].into();
+ Ok(key)
}
impl AuthConfectionary {
@@ -188,10 +202,10 @@ impl AuthConfectionary {
identifier: String,
key_base64: &str,
) -> Result<AuthConfectionary> {
- macaroon::initialize().unwrap();
- let key = BASE64.decode(key_base64.as_bytes())?;
+ macaroon::initialize().expect("initializing macaroon library");
+ let key = parse_macaroon_key(key_base64)?;
let mut root_keys = HashMap::new();
- root_keys.insert(identifier.clone(), key.clone());
+ root_keys.insert(identifier.clone(), key);
Ok(AuthConfectionary {
location,
identifier,
@@ -204,13 +218,13 @@ impl AuthConfectionary {
AuthConfectionary::new(
"test.fatcat.wiki".to_string(),
"dummy".to_string(),
- &BASE64.encode(DUMMY_KEY),
+ &BASE64.encode(DUMMY_KEY_BYTES),
)
- .unwrap()
+ .expect("creating dummy AuthConfectionary")
}
pub fn add_keypair(&mut self, identifier: String, key_base64: &str) -> Result<()> {
- let key = BASE64.decode(key_base64.as_bytes())?;
+ let key = parse_macaroon_key(key_base64)?;
self.root_keys.insert(identifier, key);
Ok(())
}
@@ -220,23 +234,31 @@ impl AuthConfectionary {
editor_id: FatcatId,
duration: Option<chrono::Duration>,
) -> Result<String> {
- let mut mac = Macaroon::create(&self.location, &self.key, &self.identifier)
- .expect("Macaroon creation");
- mac.add_first_party_caveat(&format!("editor_id = {}", editor_id.to_string()));
+ let mut mac = Macaroon::create(
+ Some(self.location.clone().into()),
+ &self.key,
+ self.identifier.clone().into(),
+ )
+ .expect("Macaroon creation");
+ mac.add_first_party_caveat(format!("editor_id = {}", editor_id.to_string()).into());
let now_utc = Utc::now();
let now = now_utc.to_rfc3339_opts(SecondsFormat::Secs, true);
- mac.add_first_party_caveat(&format!("time > {}", now));
+ mac.add_first_party_caveat(format!("time > {}", now).into());
if let Some(duration) = duration {
let expires = now_utc + duration;
- mac.add_first_party_caveat(&format!(
- "time < {}",
- &expires.to_rfc3339_opts(SecondsFormat::Secs, true)
- ));
+ mac.add_first_party_caveat(
+ format!(
+ "time < {}",
+ &expires.to_rfc3339_opts(SecondsFormat::Secs, true)
+ )
+ .into(),
+ );
};
let raw = mac.serialize(Format::V2).expect("macaroon serialization");
Ok(BASE64.encode(&raw))
}
+ /// Takes a macaroon as a base64-encoded string, deserializes it
pub fn parse_macaroon_token(
&self,
conn: &DbConn,
@@ -255,23 +277,19 @@ impl AuthConfectionary {
.into());
}
};
- let mac = match mac.validate() {
- Ok(m) => m,
- Err(e) => {
- // TODO: should be "chaining" here
- return Err(FatcatError::InvalidCredentials(format!(
- "macaroon validate error: {:?}",
- e
- ))
- .into());
- }
- };
- let mut verifier = Verifier::new();
+ let mut verifier = Verifier::default();
let mut editor_id: Option<FatcatId> = None;
for caveat in mac.first_party_caveats() {
- if caveat.predicate().starts_with("editor_id = ") {
- editor_id = Some(FatcatId::from_str(caveat.predicate().get(12..).unwrap())?);
- break;
+ if let Caveat::FirstParty(fp) = caveat {
+ let predicate_str = String::from_utf8(fp.predicate().as_ref().to_vec())?;
+ if predicate_str.starts_with("editor_id = ") {
+ editor_id = Some(FatcatId::from_str(
+ predicate_str
+ .get(12..)
+ .expect("parsing editor_id from macaroon"),
+ )?);
+ break;
+ }
}
}
let editor_id = match editor_id {
@@ -283,25 +301,28 @@ impl AuthConfectionary {
.into());
}
};
- verifier.satisfy_exact(&format!("editor_id = {}", editor_id.to_string()));
+ verifier.satisfy_exact(format!("editor_id = {}", editor_id.to_string()).into());
if let Some(endpoint) = endpoint {
// API endpoint
- verifier.satisfy_exact(&format!("endpoint = {}", endpoint));
+ verifier.satisfy_exact(format!("endpoint = {}", endpoint).into());
}
let mut created: Option<DateTime<Utc>> = None;
for caveat in mac.first_party_caveats() {
- if caveat.predicate().starts_with("time > ") {
- let ts: chrono::ParseResult<DateTime<Utc>> =
- DateTime::parse_from_rfc3339(caveat.predicate().get(7..).unwrap())
- .map(|x| x.with_timezone(&Utc));
- if let Ok(ts) = ts {
- created = Some(ts);
- break;
- } else {
- info!(
- "couldn't parse macaroon time constraint: {}",
- caveat.predicate()
- );
+ if let Caveat::FirstParty(fp) = caveat {
+ let predicate_str = String::from_utf8(fp.predicate().as_ref().to_vec())?;
+ if predicate_str.starts_with("time > ") {
+ let ts: chrono::ParseResult<DateTime<Utc>> = DateTime::parse_from_rfc3339(
+ predicate_str
+ .get(7..)
+ .expect("parsing timestamp from macaroon"),
+ )
+ .map(|x| x.with_timezone(&Utc));
+ if let Ok(ts) = ts {
+ created = Some(ts);
+ break;
+ } else {
+ info!("couldn't parse macaroon time constraint: {}", predicate_str);
+ }
}
}
}
@@ -314,10 +335,13 @@ impl AuthConfectionary {
.into());
}
};
- verifier.satisfy_exact(&format!(
- "time > {}",
- created.to_rfc3339_opts(SecondsFormat::Secs, true)
- ));
+ verifier.satisfy_exact(
+ format!(
+ "time > {}",
+ created.to_rfc3339_opts(SecondsFormat::Secs, true)
+ )
+ .into(),
+ );
// not finding the editor_id is an auth issue, not a 404
let editor: EditorRow =
match Editor::db_get(conn, editor_id).map_err(|e| FatcatError::from(e)) {
@@ -340,39 +364,48 @@ impl AuthConfectionary {
)
.into());
}
- verifier.satisfy_general(|p: &str| -> bool {
- // not expired (based on time)
- if p.starts_with("time < ") {
- let expires: chrono::ParseResult<DateTime<Utc>> =
- DateTime::parse_from_rfc3339(p.get(7..).unwrap())
- .map(|x| x.with_timezone(&Utc));
- if let Ok(when) = expires {
- //info!("checking time constraint: {} < {}", Utc::now(), when);
- Utc::now() < when
+ verifier.satisfy_general(|p_bstr: &ByteString| -> bool {
+ if let Ok(p) = String::from_utf8(p_bstr.as_ref().to_vec()) {
+ // not expired (based on time)
+ if p.starts_with("time < ") {
+ let expires: chrono::ParseResult<DateTime<Utc>> = DateTime::parse_from_rfc3339(
+ p.get(7..).expect("parsing datetime from macaroon"),
+ )
+ .map(|x| x.with_timezone(&Utc));
+ if let Ok(when) = expires {
+ //info!("checking time constraint: {} < {}", Utc::now(), when);
+ Utc::now() < when
+ } else {
+ info!("couldn't parse macaroon time constraint: {}", p);
+ false
+ }
} else {
- info!("couldn't parse macaroon time constraint: {}", p);
false
}
} else {
false
}
});
- let verify_key = match self.root_keys.get(mac.identifier()) {
+ let verify_key = match self
+ .root_keys
+ .get(std::str::from_utf8(mac.identifier().as_ref())?)
+ {
Some(key) => key,
None => {
return Err(FatcatError::InvalidCredentials(format!(
"no valid auth signing key for identifier: {}",
- mac.identifier()
+ std::str::from_utf8(mac.identifier().as_ref())?
))
.into());
}
};
- match mac.verify(verify_key, &mut verifier) {
- Ok(true) => (),
- Ok(false) => {
- return Err(FatcatError::InvalidCredentials(
- "auth token (macaroon) not valid (signature and/or caveats failed)".to_string(),
- )
+ match verifier.verify(&mac, verify_key.into(), Default::default()) {
+ Ok(()) => (),
+ Err(MacaroonError::InvalidMacaroon(em)) => {
+ return Err(FatcatError::InvalidCredentials(format!(
+ "auth token (macaroon) not valid (signature and/or caveats failed): {}",
+ em
+ ))
.into());
}
Err(e) => {
@@ -448,9 +481,15 @@ impl AuthConfectionary {
let now = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
println!("current time: {}", now);
println!("domain (location): {:?}", mac.location());
- println!("signing key name (identifier): {}", mac.identifier());
+ println!(
+ "signing key name (identifier): {}",
+ std::str::from_utf8(mac.identifier().as_ref())?
+ );
for caveat in mac.first_party_caveats() {
- println!("caveat: {}", caveat.predicate());
+ if let Caveat::FirstParty(fp) = caveat {
+ let predicate_str = String::from_utf8(fp.predicate().as_ref().to_vec())?;
+ println!("caveat: {}", predicate_str);
+ }
}
println!("verify: {:?}", self.parse_macaroon_token(conn, token, None));
Ok(())