test: permissions
This commit is contained in:
parent
3722a9ce2f
commit
50f416d402
@ -3,7 +3,7 @@ use postgres::GenericClient;
|
||||
|
||||
use crate::newtype;
|
||||
use crate::perm::Actor;
|
||||
use crate::postgres::{DbError, Postgres, UnmarshalRow};
|
||||
use crate::postgres::{try_get, DbError, Postgres, UnmarshalRow};
|
||||
use crate::repo::Repo;
|
||||
use crate::user::{User, UserId};
|
||||
|
||||
@ -29,9 +29,9 @@ impl UnmarshalRow for Group {
|
||||
S: AsRef<str>
|
||||
{
|
||||
let p = p.as_ref().map(|p| p.as_ref());
|
||||
Self::try_get(p, "uid", row).zip(|_: &_| Self::try_get(p, "name", row))
|
||||
.map(|(uid, name)| Group { uid: GroupId(uid),
|
||||
name: GroupName(name) })
|
||||
try_get!(p, "uid", row).zip(|_: &_| try_get!(p, "name", row))
|
||||
.map(|(uid, name)| Group { uid: GroupId(uid),
|
||||
name: GroupName(name) })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,10 @@ mod test {
|
||||
assert_eq!(q.unwrap_str(), "select public.hashed_text_matches($1, public.hashed_text_of_string($2))");
|
||||
assert_eq!(from_sql_owned::<String>(ps[1]), String::from("XXX"));
|
||||
|
||||
Ok(Row::new(vec![("", Type::BOOL)]).value(Type::BOOL, from_sql_owned::<String>(ps[0]) == *"foo"))
|
||||
Ok(Row::new(vec![("",
|
||||
Type::BOOL,
|
||||
Box::new(from_sql_owned::<String>(ps[0])
|
||||
== *"foo"))]))
|
||||
}),
|
||||
..Client::default() };
|
||||
|
||||
|
86
src/perm.rs
86
src/perm.rs
@ -3,7 +3,7 @@ use std::str::FromStr;
|
||||
use naan::prelude::*;
|
||||
|
||||
use crate::group::{Group, GroupId};
|
||||
use crate::postgres::UnmarshalRow;
|
||||
use crate::postgres::{try_get, UnmarshalRow};
|
||||
use crate::user::UserId;
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Eq, Debug)]
|
||||
@ -133,19 +133,20 @@ impl UnmarshalRow for Mode {
|
||||
S: AsRef<str>
|
||||
{
|
||||
let p = p.as_ref().map(|p| p.as_ref());
|
||||
Self::try_get(p, "read", row).zip(|_: &_| Self::try_get(p, "write", row))
|
||||
.map(|(r, w)| {
|
||||
if w {
|
||||
Mode::Write
|
||||
} else if r {
|
||||
Mode::Read
|
||||
} else {
|
||||
Mode::None
|
||||
}
|
||||
})
|
||||
try_get!(p, "read", row).zip(|_: &_| try_get!(p, "write", row))
|
||||
.map(|(r, w)| {
|
||||
if w {
|
||||
Mode::Write
|
||||
} else if r {
|
||||
Mode::Read
|
||||
} else {
|
||||
Mode::None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Perm {
|
||||
pub path: Path,
|
||||
pub owner: (UserId, Mode),
|
||||
@ -167,9 +168,9 @@ impl UnmarshalRow for Perm {
|
||||
Mode::unmarshal_prefixed("owner_mode", row)
|
||||
.zip(|_: &_| Mode::unmarshal_prefixed("group_mode", row))
|
||||
.zip(|_: &_| Mode::unmarshal_prefixed("everyone_mode", row))
|
||||
.zip(|_: &_| Self::try_get::<String, _, _, _>(Some("perm"), "owner", row))
|
||||
.zip(|_: &_| Self::try_get::<String, _, _, _>(Some("perm"), "group", row))
|
||||
.zip(|_: &_| Self::try_get::<String, _, _, _>(Some("perm"), "path", row))
|
||||
.zip(|_: &_| try_get!(<String> Some("perm"), "owner", row))
|
||||
.zip(|_: &_| try_get!(<String> Some("perm"), "group", row))
|
||||
.zip(|_: &_| try_get!(<String> Some("perm"), "path", row))
|
||||
.map(|(((((owner_mode, group_mode), everyone_mode), owner), group), path)| Perm {
|
||||
path: Path::parse(path),
|
||||
owner: (UserId::from(owner), owner_mode),
|
||||
@ -181,15 +182,19 @@ impl UnmarshalRow for Perm {
|
||||
|
||||
impl Perm {
|
||||
pub fn actor_can(&self, actor: &Actor, mode: Mode) -> bool {
|
||||
actor.uid.as_ref() == Some(&self.owner.0)
|
||||
|| actor.groups.iter().any(|g| g.uid == self.group.0)
|
||||
|| mode.covered_by(&self.everyone)
|
||||
mode.covered_by(&self.everyone)
|
||||
|| actor.uid.as_ref() == Some(&self.owner.0) && mode.covered_by(&self.owner.1)
|
||||
|| mode.covered_by(&self.group.1) && actor.groups.iter().any(|g| g.uid == self.group.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use postgres::types::Type;
|
||||
|
||||
use super::*;
|
||||
use crate::group::GroupName;
|
||||
use crate::postgres::test::Row;
|
||||
|
||||
#[test]
|
||||
fn path_parse() {
|
||||
@ -208,4 +213,51 @@ mod tests {
|
||||
assert_eq!(Path::parse("/groups/1/members"),
|
||||
Path::Group(Dir::Within(GroupId::from("1"), GroupPath::Members)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn perm_unmarshal() {
|
||||
let row = Row::<()>::new(vec![("owner_mode.read", Type::BOOL, Box::new(true)),
|
||||
("owner_mode.write", Type::BOOL, Box::new(true)),
|
||||
("group_mode.read", Type::BOOL, Box::new(true)),
|
||||
("group_mode.write", Type::BOOL, Box::new(true)),
|
||||
("everyone_mode.read", Type::BOOL, Box::new(true)),
|
||||
("everyone_mode.write", Type::BOOL, Box::new(false)),
|
||||
("perm.owner", Type::TEXT, Box::new("u1")),
|
||||
("perm.group", Type::TEXT, Box::new("g1")),
|
||||
("perm.path", Type::TEXT, Box::new("/groups/")),]);
|
||||
|
||||
let perm = Perm::unmarshal(&row).unwrap();
|
||||
assert_eq!(perm,
|
||||
Perm { everyone: Mode::Read,
|
||||
group: (GroupId::from("g1"), Mode::Write),
|
||||
owner: (UserId::from("u1"), Mode::Write),
|
||||
path: Path::Group(Dir::Dir) });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn actor_can() {
|
||||
let all_groups = Perm { everyone: Mode::Write,
|
||||
group: (GroupId::from("g1"), Mode::Write),
|
||||
owner: (UserId::from("u1"), Mode::Write),
|
||||
path: Path::Group(Dir::Dir) };
|
||||
|
||||
let group_admin_members = Perm { everyone: Mode::Read,
|
||||
group: (GroupId::from("g1"), Mode::Write),
|
||||
owner: (UserId::from("u1"), Mode::Write),
|
||||
path: Path::Group(Dir::Within(GroupId::from("g1"),
|
||||
GroupPath::Members)) };
|
||||
|
||||
let admin = Actor { uid: Some(UserId::from("u1")),
|
||||
groups: vec![Group { uid: GroupId::from("g1"),
|
||||
name: GroupName::from("admins") }] };
|
||||
|
||||
let nonadmin = Actor { uid: Some(UserId::from("u2")),
|
||||
groups: vec![Group { uid: GroupId::from("g2"),
|
||||
name: GroupName::from("foo") }] };
|
||||
|
||||
assert!(all_groups.actor_can(&admin, Mode::Write));
|
||||
assert!(all_groups.actor_can(&nonadmin, Mode::Write));
|
||||
assert!(group_admin_members.actor_can(&admin, Mode::Write));
|
||||
assert!(!group_admin_members.actor_can(&nonadmin, Mode::Write));
|
||||
}
|
||||
}
|
||||
|
@ -10,19 +10,20 @@ use crate::perm::Actor;
|
||||
|
||||
pub type DbError<Pg> = <<Pg as Postgres>::Client as postgres::GenericClient>::Error;
|
||||
|
||||
pub trait UnmarshalRow: Sized + Send + Sync {
|
||||
fn try_get<'a, T, R, S1, S2>(prefix: Option<S1>, col: S2, row: &'a R) -> Result<T, R::Error>
|
||||
where R: GenericRow,
|
||||
S1: AsRef<str>,
|
||||
S2: AsRef<str>,
|
||||
T: FromSql<'a>
|
||||
{
|
||||
let col = prefix.filter(|s| !s.as_ref().is_empty())
|
||||
.map(|s| format!("{}.{}", s.as_ref(), col.as_ref()))
|
||||
.unwrap_or(col.as_ref().into());
|
||||
row.try_get::<_, T>(col.as_str())
|
||||
}
|
||||
macro_rules! try_get {
|
||||
(<$t:ty> $p:expr, $col:expr, $row:expr) => {{
|
||||
let col = $p.filter(|s| !str::is_empty(s.as_ref()))
|
||||
.map(|s| format!("{}.{}", s, $col))
|
||||
.unwrap_or($col.to_string());
|
||||
$row.try_get::<_, $t>(col.as_str())
|
||||
}};
|
||||
($p:expr, $col:expr, $row:expr) => {{
|
||||
try_get!(<_> $p, $col, $row)
|
||||
}};
|
||||
}
|
||||
pub(crate) use try_get;
|
||||
|
||||
pub trait UnmarshalRow: Sized + Send + Sync {
|
||||
fn unmarshal_maybe_prefixed<R, S>(col_prefix: Option<S>, row: &R) -> Result<Self, R::Error>
|
||||
where R: GenericRow,
|
||||
S: AsRef<str>;
|
||||
@ -210,22 +211,19 @@ pub mod test {
|
||||
}
|
||||
|
||||
impl<E> Row<E> {
|
||||
pub fn new(cols: Vec<(&'static str, Type)>) -> Self {
|
||||
Self { columns: cols.into_iter()
|
||||
.map(|(name, ty)| Column::new(name.to_string(), ty))
|
||||
pub fn new(cols: Vec<(&'static str, Type, Box<dyn ToSql>)>) -> Self {
|
||||
Self { columns: cols.iter()
|
||||
.map(|(name, ty, _)| Column::new(name.to_string(), ty.clone()))
|
||||
.collect(),
|
||||
values: vec![],
|
||||
values: cols.into_iter()
|
||||
.map(|(_, ty, val)| {
|
||||
let mut bs = BytesMut::with_capacity(128);
|
||||
val.to_sql(&ty, &mut bs).unwrap();
|
||||
bs
|
||||
})
|
||||
.collect(),
|
||||
__phantom: PhantomData }
|
||||
}
|
||||
|
||||
pub fn value<V>(mut self, ty: Type, val: V) -> Self
|
||||
where V: ToSql
|
||||
{
|
||||
let mut bs = BytesMut::with_capacity(128);
|
||||
val.to_sql(&ty, &mut bs).unwrap();
|
||||
self.values.push(bs);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> GenericRow for Row<E> where E: core::fmt::Debug
|
||||
@ -451,7 +449,7 @@ pub mod test {
|
||||
#[test]
|
||||
fn postgres_impl_pooling() {
|
||||
let client = || Client { query_one: Box::new(|_, _, _| {
|
||||
Ok(Row::new(vec![("foo", Type::TEXT)]).value(Type::TEXT, "bar"))
|
||||
Ok(Row::new(vec![("foo", Type::TEXT, Box::new("bar"))]))
|
||||
}),
|
||||
..Client::default() };
|
||||
|
||||
|
58
src/user.rs
58
src/user.rs
@ -3,7 +3,7 @@ use postgres::{GenericClient, GenericRow};
|
||||
|
||||
use crate::hashed_text::HashedText;
|
||||
use crate::perm::Actor;
|
||||
use crate::postgres::{DbError, Postgres, UnmarshalRow};
|
||||
use crate::postgres::{try_get, DbError, Postgres, UnmarshalRow};
|
||||
use crate::repo::Repo;
|
||||
use crate::{newtype, Email};
|
||||
|
||||
@ -31,15 +31,15 @@ impl UnmarshalRow for User {
|
||||
S: AsRef<str>
|
||||
{
|
||||
let p = p.as_ref().map(|p| p.as_ref());
|
||||
Self::try_get(p, "uid", row).zip(|_: &_| Self::try_get(p, "tag", row))
|
||||
.zip(|_: &_| Self::try_get::<String, _, _, _>(p, "password", row))
|
||||
.zip(|_: &_| Self::try_get(p, "email", row))
|
||||
.map(|(((uid, tag), password), email)| {
|
||||
User { uid: UserId(uid),
|
||||
tag: UserTag(tag),
|
||||
password: HashedText::from(password),
|
||||
email: Email(email) }
|
||||
})
|
||||
try_get!(p, "uid", row).zip(|_: &_| try_get!(p, "tag", row))
|
||||
.zip(|_: &_| try_get!(<String> p, "password", row))
|
||||
.zip(|_: &_| try_get!(p, "email", row))
|
||||
.map(|(((uid, tag), password), email)| {
|
||||
User { uid: UserId(uid),
|
||||
tag: UserTag(tag),
|
||||
password: HashedText::from(password),
|
||||
email: Email(email) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,13 +154,10 @@ mod tests {
|
||||
use crate::Email;
|
||||
|
||||
fn usr_row(usr: User) -> Row<()> {
|
||||
Row::new(vec![("uid", Type::UUID),
|
||||
("email", Type::TEXT),
|
||||
("password", Type::TEXT),
|
||||
("tag", Type::TEXT)]).value(Type::UUID, usr.uid.to_string())
|
||||
.value(Type::TEXT, usr.email.to_string())
|
||||
.value(Type::TEXT, usr.password.to_string())
|
||||
.value(Type::TEXT, usr.tag.to_string())
|
||||
Row::new(vec![("uid", Type::UUID, Box::new(usr.uid.to_string())),
|
||||
("email", Type::TEXT, Box::new(usr.email.to_string())),
|
||||
("password", Type::TEXT, Box::new(usr.password.to_string())),
|
||||
("tag", Type::TEXT, Box::new(usr.tag.to_string()))])
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -230,22 +227,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn user_repo_insert() {
|
||||
let client = || Client::<()> { state: Box::new(Vec::<UserTag>::new()),
|
||||
query_one: Box::new(|c, q, ps| {
|
||||
let tags = c.state_mut::<Vec<UserTag>>();
|
||||
let client =
|
||||
|| Client::<()> { state: Box::new(Vec::<UserTag>::new()),
|
||||
query_one: Box::new(|c, q, ps| {
|
||||
let tags = c.state_mut::<Vec<UserTag>>();
|
||||
|
||||
let tag = UserTag::from(from_sql_owned::<String>(ps[0]));
|
||||
let tag = UserTag::from(from_sql_owned::<String>(ps[0]));
|
||||
|
||||
if tags.contains(&tag) {
|
||||
Err(())
|
||||
} else {
|
||||
tags.push(tag);
|
||||
Ok(Row::new(vec![("", Type::TEXT)]).value(Type::TEXT,
|
||||
tags.len()
|
||||
.to_string()))
|
||||
}
|
||||
}),
|
||||
..Client::default() };
|
||||
if tags.contains(&tag) {
|
||||
Err(())
|
||||
} else {
|
||||
tags.push(tag);
|
||||
Ok(Row::new(vec![("", Type::TEXT, Box::new(tags.len().to_string()))]))
|
||||
}
|
||||
}),
|
||||
..Client::default() };
|
||||
|
||||
let db = PostgresImpl::try_new(|| Ok(client()), 1).unwrap();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user