use crate::caveat;
use crate::caveat::CaveatBuilder;
use crate::error::MacaroonError;
use crate::serialization::macaroon_builder::MacaroonBuilder;
use crate::{base64_decode_flexible, ByteString, Macaroon, Result, URL_SAFE_NO_PAD};
use base64::Engine as _;
use serde::{Deserialize, Serialize};
use serde_json;
use std::str;
#[derive(Debug, Default, Deserialize, Serialize)]
struct Caveat {
i: Option<String>,
i64: Option<ByteString>,
l: Option<String>,
l64: Option<String>,
v: Option<String>,
v64: Option<ByteString>,
}
#[derive(Debug, Default, Deserialize, Serialize)]
struct Serialization {
v: u8,
i: Option<String>,
i64: Option<ByteString>,
l: Option<String>,
l64: Option<String>,
c: Vec<Caveat>,
s: Option<Vec<u8>>,
s64: Option<String>,
}
impl Serialization {
fn from_macaroon(macaroon: &Macaroon) -> Result<Serialization> {
let mut serialized: Serialization = Serialization {
v: 2,
i: None,
i64: Some(ByteString(macaroon.identifier().to_vec())),
l: macaroon.location().map(|s| s.to_string()),
l64: None,
c: Vec::new(),
s: None,
s64: Some(URL_SAFE_NO_PAD.encode(macaroon.signature())),
};
for c in macaroon.caveats() {
match c {
caveat::Caveat::FirstParty(fp) => {
let serialized_caveat: Caveat = Caveat {
i: None,
i64: Some(ByteString(fp.predicate().to_vec())),
l: None,
l64: None,
v: None,
v64: None,
};
serialized.c.push(serialized_caveat);
}
caveat::Caveat::ThirdParty(tp) => {
let serialized_caveat: Caveat = Caveat {
i: None,
i64: Some(ByteString(tp.id().to_vec())),
l: Some(tp.location().to_string()),
l64: None,
v: None,
v64: Some(ByteString(tp.verifier_id().to_vec())),
};
serialized.c.push(serialized_caveat);
}
}
}
Ok(serialized)
}
}
fn reject_both<A, B>(a: &Option<A>, b: &Option<B>, pair: &str) -> Result<()> {
if a.is_some() && b.is_some() {
return Err(MacaroonError::DeserializationError(format!(
"Found {} fields",
pair
)));
}
Ok(())
}
impl Macaroon {
fn from_json(ser: Serialization) -> Result<Macaroon> {
if ser.v != 2 {
return Err(MacaroonError::DeserializationError(format!(
"Unsupported V2JSON version field: {} (expected 2)",
ser.v
)));
}
reject_both(&ser.i, &ser.i64, "i and i64")?;
reject_both(&ser.l, &ser.l64, "l and l64")?;
reject_both(&ser.s, &ser.s64, "s and s64")?;
let mut builder: MacaroonBuilder = MacaroonBuilder::new();
builder.set_identifier(match ser.i {
Some(id) => id.into(),
None => match ser.i64 {
Some(id) => id,
None => {
return Err(MacaroonError::DeserializationError(String::from(
"No identifier \
found",
)))
}
},
});
match ser.l {
Some(loc) => builder.set_location(&loc),
None => {
if let Some(loc) = ser.l64 {
builder
.set_location(&String::from_utf8(base64_decode_flexible(loc.as_bytes())?)?)
}
}
};
let raw_sig = match ser.s {
Some(sig) => sig,
None => match ser.s64 {
Some(sig) => base64_decode_flexible(sig.as_bytes())?,
None => {
return Err(MacaroonError::DeserializationError(
"No signature found".into(),
))
}
},
};
if raw_sig.len() != 32 {
return Err(MacaroonError::DeserializationError(
"Illegal signature length".into(),
));
}
builder.set_signature(&raw_sig);
let mut caveat_builder: CaveatBuilder = CaveatBuilder::new();
for c in ser.c {
reject_both(&c.i, &c.i64, "caveat i and i64")?;
reject_both(&c.l, &c.l64, "caveat l and l64")?;
reject_both(&c.v, &c.v64, "caveat v and v64")?;
caveat_builder.add_id(match c.i {
Some(id) => id.into(),
None => match c.i64 {
Some(id64) => id64,
None => {
return Err(MacaroonError::DeserializationError(String::from(
"No caveat ID found",
)))
}
},
});
match c.l {
Some(loc) => caveat_builder.add_location(loc),
None => {
if let Some(loc64) = c.l64 {
caveat_builder.add_location(String::from_utf8(base64_decode_flexible(
loc64.as_bytes(),
)?)?)
}
}
};
match c.v {
Some(vid) => caveat_builder.add_verifier_id(vid.into()),
None => {
if let Some(vid64) = c.v64 {
caveat_builder.add_verifier_id(vid64)
}
}
};
builder.add_caveat(caveat_builder.build()?)?;
caveat_builder = CaveatBuilder::new();
}
builder.build()
}
}
pub fn serialize(macaroon: &Macaroon) -> Result<String> {
let serialized: String = serde_json::to_string(&Serialization::from_macaroon(macaroon)?)?;
Ok(serialized)
}
pub fn deserialize(data: &[u8]) -> Result<Macaroon> {
let v2j: Serialization = serde_json::from_slice(data)?;
Macaroon::from_json(v2j)
}
#[cfg(test)]
mod tests {
use super::super::Format;
use crate::{Caveat, Macaroon, MacaroonKey};
const SERIALIZED_JSON: &str = "{\"v\":2,\"l\":\"http://example.org/\",\"i\":\"keyid\",\
\"c\":[{\"i\":\"account = 3735928559\"},{\"i\":\"user = \
alice\"}],\"s64\":\
\"S-lnzR6gxrJrr2pKlO6bBbFYhtoLqF6MQqk8jQ4SXvw\"}";
const SIGNATURE: [u8; 32] = [
75, 233, 103, 205, 30, 160, 198, 178, 107, 175, 106, 74, 148, 238, 155, 5, 177, 88, 134,
218, 11, 168, 94, 140, 66, 169, 60, 141, 14, 18, 94, 252,
];
#[test]
fn test_deserialize() {
let serialized_json: Vec<u8> = SERIALIZED_JSON.as_bytes().to_vec();
let macaroon = super::deserialize(&serialized_json).unwrap();
assert_eq!("http://example.org/", macaroon.location().unwrap());
assert_eq!(b"keyid", macaroon.identifier());
assert_eq!(2, macaroon.caveats().len());
let predicate = match &macaroon.caveats()[0] {
Caveat::FirstParty(fp) => fp.predicate().to_vec(),
_ => vec![],
};
assert_eq!(b"account = 3735928559".to_vec(), predicate);
let predicate = match &macaroon.caveats()[1] {
Caveat::FirstParty(fp) => fp.predicate().to_vec(),
_ => vec![],
};
assert_eq!(b"user = alice".to_vec(), predicate);
assert_eq!(&MacaroonKey::from(SIGNATURE), macaroon.signature());
}
#[test]
fn test_serialize_deserialize() {
let mut macaroon =
Macaroon::create(Some("http://example.org/"), &SIGNATURE.into(), "keyid").unwrap();
macaroon.add_first_party_caveat("user = alice").unwrap();
macaroon
.add_third_party_caveat(
"https://auth.mybank.com/",
&MacaroonKey::generate(b"my key"),
"keyid",
)
.unwrap();
let serialized = macaroon.serialize(Format::V2JSON).unwrap();
let other = Macaroon::deserialize(&serialized).unwrap();
assert_eq!(macaroon, other);
}
#[test]
fn test_reject_wrong_version() {
let bad =
r#"{"v":1,"i":"keyid","c":[],"s64":"S-lnzR6gxrJrr2pKlO6bBbFYhtoLqF6MQqk8jQ4SXvw"}"#;
let err = Macaroon::deserialize(bad).unwrap_err();
assert!(matches!(err, crate::MacaroonError::DeserializationError(_)));
}
#[test]
fn test_reject_caveat_with_both_i_and_i64() {
let bad = r#"{"v":2,"i":"keyid","c":[{"i":"x","i64":"eA"}],"s64":"S-lnzR6gxrJrr2pKlO6bBbFYhtoLqF6MQqk8jQ4SXvw"}"#;
let err = Macaroon::deserialize(bad).unwrap_err();
match err {
crate::MacaroonError::DeserializationError(s) => {
assert!(s.contains("i and i64"), "unexpected error: {}", s);
}
other => panic!("expected DeserializationError, got {:?}", other),
}
}
}