fix: set and unset acting usr based on actor

This commit is contained in:
Orion Kindel 2023-07-21 13:21:09 -05:00
parent dc27262328
commit 7e89edc30f
Signed by untrusted user who does not match committer: orion
GPG Key ID: 6D4165AE4C928719
6 changed files with 76 additions and 46 deletions

View File

@ -161,7 +161,9 @@ fn handle_request<A>(app: &A, req: Addrd<Req<ToadT>>) -> Addrd<Message>
let actor = dbg!(actor); let actor = dbg!(actor);
let not_found = || { let not_found = || {
log::info!("{:?} {} not found", req.data().msg().code, req.data().msg().path_string().unwrap()); log::info!("{:?} {} not found",
req.data().msg().code,
req.data().msg().path_string().unwrap());
Message::builder(Type::Ack, toad::resp::code::NOT_FOUND).token(req.data().msg().token) Message::builder(Type::Ack, toad::resp::code::NOT_FOUND).token(req.data().msg().token)
.build() .build()
}; };

View File

@ -99,20 +99,20 @@ impl<Db> GroupRepo<DbError<Db>> for GroupRepoImpl<Db> where Db: Postgres + 'stat
static QUERY: &'static str = "select g.tag :: text, g.uid :: text from public.usr_groups(($1 :: text) :: human_uuid.huid) as g"; static QUERY: &'static str = "select g.tag :: text, g.uid :: text from public.usr_groups(($1 :: text) :: human_uuid.huid) as g";
let grps = self.0 let grps = self.0
.with_client(|c| c.query(QUERY, &[&id.as_ref()])) .with_client(actor, |c| c.query(QUERY, &[&id.as_ref()]))
.and_then(|rows| { .and_then(|rows| {
rows.iter() rows.iter()
.map(Group::unmarshal) .map(Group::unmarshal)
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
}) })
.map_err(GroupRepoError::Other)?; .map_err(GroupRepoError::Other)?;
let paths = grps.iter().map(|g| Path::parse(format!("/groups/{}/members", g.uid))).collect::<Vec<_>>(); let paths = grps.iter()
.map(|g| Path::parse(format!("/groups/{}/members", g.uid)))
.collect::<Vec<_>>();
if !self.0 if !self.0
.authorized_all(&paths, .authorized_all(&paths, Mode::Read, actor)
Mode::Read,
actor)
.map_err(GroupRepoError::Other)? .map_err(GroupRepoError::Other)?
{ {
return Err(GroupRepoError::Unauthorized); return Err(GroupRepoError::Unauthorized);
@ -134,7 +134,7 @@ impl<Db> GroupRepo<DbError<Db>> for GroupRepoImpl<Db> where Db: Postgres + 'stat
} }
self.0 self.0
.with_client(|c| c.query(QUERY, &[&id.as_ref()])) .with_client(actor, |c| c.query(QUERY, &[&id.as_ref()]))
.and_then(|rows| { .and_then(|rows| {
rows.into_iter() rows.into_iter()
.map(|row| row.try_get::<_, String>("u.uid").map(UserId::from)) .map(|row| row.try_get::<_, String>("u.uid").map(UserId::from))
@ -167,7 +167,7 @@ impl<Db> ReadOne for GroupRepoImpl<Db> where Db: Postgres + 'static
} }
self.0 self.0
.with_client(|c| c.query_opt(QUERY, &[&id.as_ref()])) .with_client(actor, |c| c.query_opt(QUERY, &[&id.as_ref()]))
.and_then(|opt| { .and_then(|opt| {
opt.as_ref() opt.as_ref()
.map(Group::unmarshal) .map(Group::unmarshal)
@ -204,7 +204,7 @@ impl<Db> ReadMany for GroupRepoImpl<Db> where Db: Postgres + 'static
let grps: Vec<Group> = let grps: Vec<Group> =
self.0 self.0
.with_client(|c| match page.after { .with_client(actor, |c| match page.after {
| Some(after) => { | Some(after) => {
c.query(&QUERY_AFTER_LINES.iter() c.query(&QUERY_AFTER_LINES.iter()
.fold(String::new(), |b, a| format!("{b}{a}\n")), .fold(String::new(), |b, a| format!("{b}{a}\n")),
@ -252,9 +252,9 @@ impl<Db> Patch for GroupRepoImpl<Db> where Db: Postgres + 'static
let paths = let paths =
vec![Some(format!("/groups/{id}/name")).filter(|_| state.name.is_some())].into_iter() vec![Some(format!("/groups/{id}/name")).filter(|_| state.name.is_some())].into_iter()
.filter_map(identity) .filter_map(identity)
.map(Path::parse) .map(Path::parse)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !self.0 if !self.0
.authorized_all(&paths, Mode::Write, actor) .authorized_all(&paths, Mode::Write, actor)
.map_err(GroupRepoError::Other)? .map_err(GroupRepoError::Other)?
@ -263,7 +263,7 @@ impl<Db> Patch for GroupRepoImpl<Db> where Db: Postgres + 'static
} }
self.0 self.0
.with_client(|c| { .with_client(actor, |c| {
c.execute(&QUERY_LINES.iter() c.execute(&QUERY_LINES.iter()
.fold(String::new(), |b, a| format!("{b}{a}\n")), .fold(String::new(), |b, a| format!("{b}{a}\n")),
&[&id.as_ref(), &state.name.as_ref().map(|n| n.as_ref())]) &[&id.as_ref(), &state.name.as_ref().map(|n| n.as_ref())])
@ -292,7 +292,7 @@ impl<Db> Insert for GroupRepoImpl<Db> where Db: Postgres + 'static
} }
self.0 self.0
.with_client(|c| { .with_client(actor, |c| {
c.query_one(&QUERY_LINES.iter() c.query_one(&QUERY_LINES.iter()
.fold(String::new(), |b, a| format!("{b}{a}\n")), .fold(String::new(), |b, a| format!("{b}{a}\n")),
&[&insert.name.as_ref()]) &[&insert.name.as_ref()])
@ -318,7 +318,7 @@ impl<Db> Del for GroupRepoImpl<Db> where Db: Postgres + 'static
} }
self.0 self.0
.with_client(|c| c.execute(QUERY, &[&id.as_ref()])) .with_client(actor, |c| c.execute(QUERY, &[&id.as_ref()]))
.map(|n| n == 1) .map(|n| n == 1)
.map_err(GroupRepoError::Other) .map_err(GroupRepoError::Other)
} }

View File

@ -1,5 +1,6 @@
use postgres::{GenericClient, GenericRow}; use postgres::{GenericClient, GenericRow};
use super::Actor;
use crate::newtype; use crate::newtype;
use crate::postgres::{DbError, Postgres}; use crate::postgres::{DbError, Postgres};
use crate::repo::Ext; use crate::repo::Ext;
@ -10,7 +11,11 @@ newtype!(
); );
pub trait HashedTextExt: Ext { pub trait HashedTextExt: Ext {
fn matches<S: AsRef<str>>(&self, this: &HashedText, other: S) -> Result<bool, Self::Error>; fn matches<S: AsRef<str>>(&self,
actor: &Actor,
this: &HashedText,
other: S)
-> Result<bool, Self::Error>;
} }
pub struct HashedTextExtImpl<Db: Postgres>(pub &'static Db); pub struct HashedTextExtImpl<Db: Postgres>(pub &'static Db);
@ -22,11 +27,17 @@ impl<Db> Ext for HashedTextExtImpl<Db> where Db: Postgres
impl<Db> HashedTextExt for HashedTextExtImpl<Db> where Db: Postgres impl<Db> HashedTextExt for HashedTextExtImpl<Db> where Db: Postgres
{ {
fn matches<S: AsRef<str>>(&self, this: &HashedText, other: S) -> Result<bool, DbError<Db>> { fn matches<S: AsRef<str>>(&self,
actor: &Actor,
this: &HashedText,
other: S)
-> Result<bool, DbError<Db>> {
static QUERY: &str = "select public.hashed_text_matches($1, public.hashed_text_of_string($2))"; static QUERY: &str = "select public.hashed_text_matches($1, public.hashed_text_of_string($2))";
self.0 self.0
.with_client(|client| client.query_one(QUERY, &[&other.as_ref(), &this.0.as_str()])) .with_client(actor, |client| {
client.query_one(QUERY, &[&other.as_ref(), &this.0.as_str()])
})
.and_then(|row| row.try_get(0)) .and_then(|row| row.try_get(0))
} }
} }
@ -36,13 +47,18 @@ mod test {
use postgres::types::Type; use postgres::types::Type;
use super::{HashedText, HashedTextExt, HashedTextExtImpl}; use super::{HashedText, HashedTextExt, HashedTextExtImpl};
use crate::model::Actor;
use crate::postgres::test::{from_sql_owned, Client, Row}; use crate::postgres::test::{from_sql_owned, Client, Row};
use crate::postgres::{Postgres, PostgresImpl}; use crate::postgres::{Postgres, PostgresImpl};
#[test] #[test]
fn hashed_text_matches_fn_call() { fn hashed_text_matches_fn_call() {
let client = || Client { query_one: Box::new(|_, q, ps| { let client = || Client { query_one: Box::new(|_, q, ps| {
assert_eq!(q.unwrap_str(), "select public.hashed_text_matches($1, public.hashed_text_of_string($2))"); let q = q.unwrap_str();
if q.contains("set_acting_usr") {
return Ok(Row::new(vec![]));
}
assert_eq!(q, "select public.hashed_text_matches($1, public.hashed_text_of_string($2))");
assert_eq!(from_sql_owned::<String>(ps[1]), String::from("XXX")); assert_eq!(from_sql_owned::<String>(ps[1]), String::from("XXX"));
Ok(Row::new(vec![("", Ok(Row::new(vec![("",
@ -57,9 +73,9 @@ mod test {
std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&pg) std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&pg)
}); });
assert!(htext.matches(&HashedText(String::from("XXX")), "foo") assert!(htext.matches(&Actor::default(), &HashedText(String::from("XXX")), "foo")
.unwrap()); .unwrap());
assert!(!htext.matches(&HashedText(String::from("XXX")), "foob") assert!(!htext.matches(&Actor::default(), &HashedText(String::from("XXX")), "foob")
.unwrap()); .unwrap());
} }
} }

View File

@ -128,7 +128,7 @@ impl<Db> ReadOne for UserRepoImpl<Db> where Db: Postgres + 'static
} }
self.0 self.0
.with_client(|c| { .with_client(actor, |c| {
c.query_opt(&QUERY_LINES.iter() c.query_opt(&QUERY_LINES.iter()
.fold(String::new(), |b, a| format!("{b}{a}\n")), .fold(String::new(), |b, a| format!("{b}{a}\n")),
&[&id.as_ref()]) &[&id.as_ref()])
@ -177,7 +177,7 @@ impl<Db> ReadMany for UserRepoImpl<Db> where Db: Postgres + 'static
let usrs: Vec<User> = let usrs: Vec<User> =
self.0 self.0
.with_client(|c| match page.after { .with_client(actor, |c| match page.after {
| Some(after) => { | Some(after) => {
c.query(&QUERY_AFTER_LINES.iter() c.query(&QUERY_AFTER_LINES.iter()
.fold(String::new(), |b, a| format!("{b}{a}\n")), .fold(String::new(), |b, a| format!("{b}{a}\n")),
@ -238,7 +238,7 @@ impl<Db> Patch for UserRepoImpl<Db> where Db: Postgres + 'static
} }
self.0 self.0
.with_client(|c| { .with_client(actor, |c| {
c.execute(&QUERY_LINES.iter() c.execute(&QUERY_LINES.iter()
.fold(String::new(), |b, a| format!("{b}{a}\n")), .fold(String::new(), |b, a| format!("{b}{a}\n")),
&[&id.as_ref(), &[&id.as_ref(),
@ -272,7 +272,7 @@ impl<Db> Insert for UserRepoImpl<Db> where Db: Postgres + 'static
} }
self.0 self.0
.with_client(|c| { .with_client(actor, |c| {
c.query_one(&QUERY_LINES.iter() c.query_one(&QUERY_LINES.iter()
.fold(String::new(), |b, a| format!("{b}{a}\n")), .fold(String::new(), |b, a| format!("{b}{a}\n")),
&[&insert.tag, &[&insert.tag,
@ -302,7 +302,7 @@ impl<Db> Del for UserRepoImpl<Db> where Db: Postgres + 'static
} }
self.0 self.0
.with_client(|c| c.execute(QUERY, &[&id.as_ref()])) .with_client(actor, |c| c.execute(QUERY, &[&id.as_ref()]))
.map(|n| n == 1) .map(|n| n == 1)
.map_err(Other) .map_err(Other)
} }

View File

@ -77,7 +77,7 @@ impl<Db, G> UserSessionExt for UserSessionExtImpl<Db, G>
let expr = "public.usr_session_touch(public.usr_session_key_of_string($1))"; let expr = "public.usr_session_touch(public.usr_session_key_of_string($1))";
let query = format!("select {cols} from {expr} as u"); let query = format!("select {cols} from {expr} as u");
self.0 self.0
.with_client(|c| { .with_client(&Actor::default(), |c| {
c.query_one(&query, &[&session.as_ref()]) c.query_one(&query, &[&session.as_ref()])
.and_then(|r| User::unmarshal(&r)) .and_then(|r| User::unmarshal(&r))
}) })
@ -118,7 +118,7 @@ impl<Db, G> UserSessionExt for UserSessionExtImpl<Db, G>
" ) s"]; " ) s"];
self.0 self.0
.with_client(|c| { .with_client(&Actor::default(), |c| {
c.query_one(&QUERY_LINES.iter() c.query_one(&QUERY_LINES.iter()
.fold(String::new(), |b, a| format!("{b}{a}\n")), .fold(String::new(), |b, a| format!("{b}{a}\n")),
&[&tag_or_email.as_str(), &[&tag_or_email.as_str(),

View File

@ -6,7 +6,7 @@ use postgres::types::FromSql;
use postgres::{GenericClient, GenericRow}; use postgres::{GenericClient, GenericRow};
use rand::Rng; use rand::Rng;
use crate::model::{Actor, Mode, Path, Perm}; use crate::model::{Actor, Mode, Path, Perm, UserId};
pub type DbError<Pg> = <<Pg as Postgres>::Client as postgres::GenericClient>::Error; pub type DbError<Pg> = <<Pg as Postgres>::Client as postgres::GenericClient>::Error;
@ -76,7 +76,7 @@ pub trait Postgres
fn try_new<F>(connect: F, pool_size: usize) -> Result<Self, DbError<Self>> fn try_new<F>(connect: F, pool_size: usize) -> Result<Self, DbError<Self>>
where F: Fn() -> Result<Self::Client, DbError<Self>>; where F: Fn() -> Result<Self::Client, DbError<Self>>;
fn with_client<F, R>(&self, f: F) -> Result<R, DbError<Self>> fn with_client<F, R>(&self, a: &Actor, f: F) -> Result<R, DbError<Self>>
where F: FnOnce(&mut Self::Client) -> Result<R, DbError<Self>>; where F: FnOnce(&mut Self::Client) -> Result<R, DbError<Self>>;
fn authorized(&self, path: &Path, wants_to: Mode, actor: &Actor) -> Result<bool, DbError<Self>> { fn authorized(&self, path: &Path, wants_to: Mode, actor: &Actor) -> Result<bool, DbError<Self>> {
@ -91,7 +91,7 @@ pub trait Postgres
"inner join public.usr ou on ou.id = p.owner_user", "inner join public.usr ou on ou.id = p.owner_user",
"where path = $1 :: text",].into_iter() "where path = $1 :: text",].into_iter()
.fold(String::new(), |b, a| format!("{b}\n{a}")); .fold(String::new(), |b, a| format!("{b}\n{a}"));
let got_perm = self.with_client(|c| c.query_opt(&q, &[&path.to_string()]))?; let got_perm = self.with_client(actor, |c| c.query_opt(&q, &[&path.to_string()]))?;
Ok(match got_perm { Ok(match got_perm {
| None => { | None => {
log::debug!("no such perm: {}", path); log::debug!("no such perm: {}", path);
@ -133,9 +133,11 @@ pub trait Postgres
"inner join public.grp og on og.id = p.owner_group", "inner join public.grp og on og.id = p.owner_group",
"inner join public.usr ou on ou.id = p.owner_user", "inner join public.usr ou on ou.id = p.owner_user",
format!("where path in ({set})").as_str(),].into_iter() format!("where path in ({set})").as_str(),].into_iter()
.fold(String::new(), |b, a| format!("{b}\n{a}")); .fold(String::new(), |b, a| {
format!("{b}\n{a}")
});
let pass = self.with_client(|c| c.query(&q, &[]))? let pass = self.with_client(actor, |c| c.query(&q, &[]))?
.iter() .iter()
.map(Perm::unmarshal) .map(Perm::unmarshal)
.collect::<Result<Vec<Perm>, _>>()? .collect::<Result<Vec<Perm>, _>>()?
@ -189,13 +191,22 @@ impl<C> Postgres for PostgresImpl<C>
pool: Box::pin(pool) }) pool: Box::pin(pool) })
} }
fn with_client<F, R>(&self, f: F) -> Result<R, C::Error> fn with_client<F, R>(&self, a: &Actor, f: F) -> Result<R, C::Error>
where F: FnOnce(&mut Self::Client) -> Result<R, C::Error> where F: FnOnce(&mut Self::Client) -> Result<R, C::Error>
{ {
match self.unused_lock() { let mut c = match self.unused_lock() {
| Some(mut lock) => f(lock.deref_mut()), | Some(lock) => lock,
| None => f(self.block_for_next().deref_mut()), | None => self.block_for_next(),
} };
c.query_one("select public.set_acting_usr($1)",
&[&a.uid.as_ref().map(UserId::as_ref)])?;
let r = f(c.deref_mut())?;
c.query_one("select public.unset_acting_usr()", &[])?;
Ok(r)
} }
} }
@ -364,7 +375,7 @@ pub mod test {
perms: vec![] }) perms: vec![] })
} }
fn with_client<F, R>(&self, f: F) -> Result<R, super::DbError<Self>> fn with_client<F, R>(&self, a: &Actor, f: F) -> Result<R, super::DbError<Self>>
where F: FnOnce(&mut Self::Client) -> Result<R, super::DbError<Self>> where F: FnOnce(&mut Self::Client) -> Result<R, super::DbError<Self>>
{ {
f(self.client.lock().unwrap().deref_mut()) f(self.client.lock().unwrap().deref_mut())
@ -598,7 +609,8 @@ pub mod test {
assert!(pg.unused_lock().is_some()); assert!(pg.unused_lock().is_some());
// with_client does not block when there are available clients // with_client does not block when there are available clients
let row = pg.with_client(|c| c.query_one("", &[])).unwrap(); let row = pg.with_client(&Actor::default(), |c| c.query_one("", &[]))
.unwrap();
assert_eq!(row.get::<_, String>("foo"), String::from("bar")); assert_eq!(row.get::<_, String>("foo"), String::from("bar"));
} }
} }