use std::collections::BTreeSet;
use std::fmt;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
use biscuit_auth::builder::{Check, Fact, Term, fact, string};
use crate::{AccessRequest, Command, Error, Mode, Name, Network};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Right {
pub server: Name,
pub user: Name,
}
impl Right {
pub fn to_fact(&self) -> Fact {
fact("right", &[string(self.server.as_str()), string(self.user.as_str())])
}
}
impl FromStr for Right {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let (server, user) = s
.split_once(':')
.ok_or(Error::invalid("right", "expected server:user"))?;
Ok(Self {
server: server.parse()?,
user: user.parse()?,
})
}
}
impl fmt::Display for Right {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.server, self.user)
}
}
#[derive(Debug, Clone)]
pub enum Restriction {
ExpiresAt(SystemTime),
SourceNetworks(Vec<Network>),
Servers(Vec<Name>),
Users(Vec<Name>),
Commands(Vec<Command>),
PublicKeys(Vec<String>),
Mode(Mode),
}
impl Restriction {
pub fn to_check(&self) -> Result<Check, Error> {
let (source, value) = match self {
Self::ExpiresAt(t) => ("check if time($t), $t <= {v}", Term::Date(unix_seconds(*t)?)),
Self::SourceNetworks(v) => ("check if source_net($n), {v}.contains($n)", set(v)?),
Self::Servers(v) => ("check if server($s), {v}.contains($s)", set(v)?),
Self::Users(v) => ("check if user($u), {v}.contains($u)", set(v)?),
Self::Commands(v) => (
"check if command($c), {v}.contains($c)",
set(v.iter().map(Command::as_str))?,
),
Self::PublicKeys(v) => ("check if public_key($k), {v}.contains($k)", set(v)?),
Self::Mode(m) => ("check if mode({v})", string(m.as_str())),
};
let mut check = Check::try_from(source).map_err(Error::Datalog)?;
check.set("v", value).map_err(Error::Datalog)?;
Ok(check)
}
}
fn set<T: ToString>(values: impl IntoIterator<Item = T>) -> Result<Term, Error> {
let set: BTreeSet<Term> = values.into_iter().map(|v| string(&v.to_string())).collect();
if set.is_empty() {
return Err(Error::invalid("restriction", "needs at least one value"));
}
Ok(Term::Set(set))
}
pub(crate) fn unix_seconds(time: SystemTime) -> Result<u64, Error> {
time.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.map_err(|_| Error::invalid("time", "before the Unix epoch"))
}
pub(crate) fn time_fact(seconds: u64) -> Fact {
fact("time", &[Term::Date(seconds)])
}
pub(crate) fn request_facts(request: &AccessRequest) -> Vec<Fact> {
let mut facts = vec![
fact("source_ip", &[string(&request.source_ip.to_canonical().to_string())]),
fact("server", &[string(request.server.as_str())]),
fact("user", &[string(request.user.as_str())]),
fact("public_key", &[string(&request.public_key)]),
fact("mode", &[string(request.mode.as_str())]),
];
facts.extend(Network::enclosing(request.source_ip).map(|n| fact("source_net", &[string(&n.to_string())])));
if let Some(command) = &request.command {
facts.push(fact("command", &[string(command.as_str())]));
}
facts
}