diff options
author | metamuffin <metamuffin@disroot.org> | 2023-10-04 20:41:59 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-10-04 20:41:59 +0200 |
commit | 347274afb36e926b328e799ca8004fc874ffe4cb (patch) | |
tree | 8c7ec38938e3673ce5752bffa9442daa9f589f3d | |
parent | 4095a8804c17c3ec12706f00d3694f564afc0b95 (diff) | |
download | jellything-347274afb36e926b328e799ca8004fc874ffe4cb.tar jellything-347274afb36e926b328e799ca8004fc874ffe4cb.tar.bz2 jellything-347274afb36e926b328e799ca8004fc874ffe4cb.tar.zst |
more permission stuff
-rw-r--r-- | base/src/permission.rs | 46 | ||||
-rw-r--r-- | common/src/lib.rs | 3 | ||||
-rw-r--r-- | common/src/user.rs | 2 | ||||
-rw-r--r-- | import/src/main.rs | 2 | ||||
-rw-r--r-- | server/src/import.rs | 49 | ||||
-rw-r--r-- | server/src/routes/api/mod.rs | 7 | ||||
-rw-r--r-- | server/src/routes/stream.rs | 5 | ||||
-rw-r--r-- | server/src/routes/ui/admin/user.rs | 16 | ||||
-rw-r--r-- | server/src/routes/ui/assets.rs | 7 | ||||
-rw-r--r-- | server/src/routes/ui/node.rs | 5 | ||||
-rw-r--r-- | web/script/player/track.ts | 2 |
11 files changed, 94 insertions, 50 deletions
diff --git a/base/src/permission.rs b/base/src/permission.rs index 382a16e..cc0e32c 100644 --- a/base/src/permission.rs +++ b/base/src/permission.rs @@ -1,19 +1,24 @@ use crate::CONF; use anyhow::anyhow; -use jellycommon::user::{PermissionSet, UserPermission}; +use jellycommon::{ + user::{PermissionSet, UserPermission}, + Node, +}; pub trait PermissionSetExt { - fn check(&self, perm: &UserPermission) -> bool; + fn check_explicit(&self, perm: &UserPermission) -> Option<bool>; + fn check(&self, perm: &UserPermission) -> bool { + self.check_explicit(perm).unwrap_or(perm.default_value()) + } fn assert(&self, perm: &UserPermission) -> Result<(), anyhow::Error>; } impl PermissionSetExt for PermissionSet { - fn check(&self, perm: &UserPermission) -> bool { - *self - .0 + fn check_explicit(&self, perm: &UserPermission) -> Option<bool> { + self.0 .get(&perm) .or(CONF.default_permission_set.0.get(&perm)) - .unwrap_or(&perm.default_value()) + .map(|v| *v) } fn assert(&self, perm: &UserPermission) -> Result<(), anyhow::Error> { if self.check(perm) { @@ -25,3 +30,32 @@ impl PermissionSetExt for PermissionSet { } } } + +pub trait NodePermissionExt { + fn only_if_permitted(self, perms: &PermissionSet) -> Self; +} +impl NodePermissionExt for Option<Node> { + fn only_if_permitted(self, perms: &PermissionSet) -> Self { + self.and_then(|node| { + if check_node_permission(perms, &node) { + Some(node) + } else { + None + } + }) + } +} +fn check_node_permission(perms: &PermissionSet, node: &Node) -> bool { + if let Some(v) = + perms.check_explicit(&UserPermission::AccessNode(node.public.id.clone().unwrap())) + { + v + } else { + for com in node.public.path.clone().into_iter().rev() { + if let Some(v) = perms.check_explicit(&UserPermission::AccessNode(com.to_owned())) { + return v; + } + } + return false; + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index c425c21..2bde0b9 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -37,7 +37,8 @@ pub struct NodePrivate { pub struct NodePublic { pub kind: NodeKind, pub title: String, - #[serde(default)] pub parent: Option<String>, + #[serde(default)] pub id: Option<String>, + #[serde(default)] pub path: Vec<String>, #[serde(default)] pub children: Vec<String>, #[serde(default)] pub tagline: Option<String>, #[serde(default)] pub description: Option<String>, diff --git a/common/src/user.rs b/common/src/user.rs index 8640dc2..b049346 100644 --- a/common/src/user.rs +++ b/common/src/user.rs @@ -20,6 +20,7 @@ pub enum UserPermission { OriginalStream, Transcode, ManageUsers, + FederatedContent, GenerateInvite, AccessNode(String), } @@ -38,6 +39,7 @@ impl UserPermission { impl Display for UserPermission { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&match self { + UserPermission::FederatedContent => format!("access to federated content"), UserPermission::Admin => format!("administrative rights"), UserPermission::OriginalStream => format!("download of the original media"), UserPermission::Transcode => format!("transcoding"), diff --git a/import/src/main.rs b/import/src/main.rs index 3affabd..57e6b99 100644 --- a/import/src/main.rs +++ b/import/src/main.rs @@ -333,7 +333,6 @@ fn main() -> anyhow::Result<()> { }), }, public: NodePublic { - parent: None, federated: None, ratings, description: file_meta @@ -355,6 +354,7 @@ fn main() -> anyhow::Result<()> { duration: m.duration, tracks: m.tracks.clone(), }), + ..Default::default() }, }; diff --git a/server/src/import.rs b/server/src/import.rs index d3919b5..ad1d5e9 100644 --- a/server/src/import.rs +++ b/server/src/import.rs @@ -27,7 +27,7 @@ pub async fn import(db: &Database, fed: &Federation) -> anyhow::Result<()> { let permit = IMPORT_SEM.try_acquire()?; db.node.clear()?; info!("importing..."); - let (_, errs) = import_path(CONF.library_path.clone(), db, fed, None) + let (_, errs) = import_path(CONF.library_path.clone(), db, fed, vec![]) .await .context("indexing")?; info!("import completed"); @@ -46,7 +46,7 @@ pub async fn import_path( path: PathBuf, db: &Database, fed: &Federation, - parent: Option<String>, + mut node_path: Vec<String>, ) -> anyhow::Result<(Vec<String>, usize)> { if path.is_dir() { let mpath = path.join("directory.json"); @@ -62,14 +62,16 @@ pub async fn import_path( let identifier = if mpath.exists() { path.file_name().unwrap().to_str().unwrap().to_string() } else { - parent - .clone() + node_path + .last() + .cloned() .ok_or(anyhow!("non-root node requires parent"))? }; + node_path.push(identifier.clone()); let mut all: FuturesUnordered<_> = children_paths .into_iter() - .map(|p| import_path(p.clone(), db, fed, Some(identifier.clone())).map_err(|e| (p, e))) + .map(|p| import_path(p.clone(), db, fed, node_path.clone()).map_err(|e| (p, e))) .collect(); let mut children_ids = Vec::new(); @@ -87,13 +89,14 @@ pub async fn import_path( } } if mpath.exists() { - let mut data: Node = + let mut node: Node = serde_json::from_reader(File::open(mpath).context("metadata missing")?)?; - data.public.children = children_ids; - data.public.parent = parent; + node.public.children = children_ids; + node.public.path = node_path; + node.public.id = Some(identifier.to_owned()); info!("adding {identifier}"); - db.node.insert(&identifier, &data)?; + db.node.insert(&identifier, &node)?; Ok((vec![identifier], errs)) } else { Ok((children_ids, errs)) @@ -101,8 +104,8 @@ pub async fn import_path( } else if path.is_file() { info!("loading {path:?}"); let datafile = File::open(path.clone()).context("cant load metadata")?; - let mut data: Node = serde_json::from_reader(datafile).context("invalid metadata")?; - let identifier = data.private.id.clone().unwrap_or_else(|| { + let mut node: Node = serde_json::from_reader(datafile).context("invalid metadata")?; + let identifier = node.private.id.clone().unwrap_or_else(|| { path.file_name() .unwrap() .to_str() @@ -112,19 +115,20 @@ pub async fn import_path( .to_string() }); - let idents = if let Some(io) = data.private.import.take() { + let idents = if let Some(io) = node.private.import.take() { let session = fed .get_session(&io.host) .await .context("creating session")?; - import_remote(io, db, &session, identifier.clone(), parent) + import_remote(io, db, &session, identifier.clone(), node_path) .await .context("federated import")? } else { debug!("adding {identifier}"); - data.public.parent = parent; - db.node.insert(&identifier, &data)?; + node.public.path = node_path; + node.public.id = Some(identifier.to_owned()); + db.node.insert(&identifier, &node)?; vec![identifier] }; Ok((idents, 0)) @@ -141,7 +145,7 @@ async fn import_remote( db: &Database, session: &Arc<Session>, identifier: String, - parent: Option<String>, + mut node_path: Vec<String>, ) -> anyhow::Result<Vec<String>> { let _permit = SEM_REMOTE_IMPORT.acquire().await.unwrap(); info!("loading federated node {identifier:?}"); @@ -163,9 +167,7 @@ async fn import_remote( drop(_permit); - let child_parent = if flatten { - parent - } else { + if !flatten { let mut node = Node { public: node.clone(), private: NodePrivate { @@ -179,7 +181,7 @@ async fn import_remote( }), }, }; - node.public.parent = parent; + node.public.path = node_path.clone(); node.public.federated = Some(opts.host.clone()); node.public .children @@ -187,9 +189,10 @@ async fn import_remote( .for_each(|c| *c = format!("{}{c}", opts.prefix.clone().unwrap_or(String::new()))); debug!("adding {identifier}"); + node.public.id = Some(identifier.to_owned()); db.node.insert(&identifier, &node)?; - Some(opts.id.clone()) - }; + node_path.push(opts.id.clone()); + } let mut children: FuturesUnordered<_> = node .children @@ -204,7 +207,7 @@ async fn import_remote( db, session, prefixed, - child_parent.clone(), + node_path.clone(), ) }) .collect(); diff --git a/server/src/routes/api/mod.rs b/server/src/routes/api/mod.rs index 23f313f..615c836 100644 --- a/server/src/routes/api/mod.rs +++ b/server/src/routes/api/mod.rs @@ -41,13 +41,14 @@ pub fn r_api_account_login(database: &State<Database>, data: Json<LoginForm>) -> #[get("/api/node_raw/<id>")] pub fn r_api_node_raw( - _admin: AdminSession, + admin: AdminSession, database: &State<Database>, - id: String, + id: &str, ) -> MyResult<Json<Node>> { + drop(admin); let node = database .node - .get(&id) + .get(&id.to_string()) .context("retrieving library node")? .ok_or(anyhow!("node does not exist"))?; Ok(Json(node)) diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs index 21575b6..b1248ba 100644 --- a/server/src/routes/stream.rs +++ b/server/src/routes/stream.rs @@ -6,7 +6,7 @@ use super::ui::{account::session::Session, error::MyError}; use crate::{database::Database, federation::Federation}; use anyhow::{anyhow, Result}; -use jellybase::CONF; +use jellybase::{permission::NodePermissionExt, CONF}; use jellycommon::{stream::StreamSpec, MediaSource}; use log::{info, warn}; use rocket::{ @@ -36,7 +36,7 @@ pub async fn r_stream_head( #[get("/n/<id>/stream?<spec..>")] pub async fn r_stream( - _sess: Session, + session: Session, federation: &State<Federation>, db: &State<Database>, id: &str, @@ -46,6 +46,7 @@ pub async fn r_stream( let node = db .node .get(&id.to_string())? + .only_if_permitted(&session.user.permissions) .ok_or(anyhow!("node does not exist"))?; let source = node .private diff --git a/server/src/routes/ui/admin/user.rs b/server/src/routes/ui/admin/user.rs index e61ec45..42bcfa7 100644 --- a/server/src/routes/ui/admin/user.rs +++ b/server/src/routes/ui/admin/user.rs @@ -66,15 +66,15 @@ fn manage_single_user<'a>( Ok(LayoutPage { title: "User management".to_string(), content: markup::new! { - h1 { "Manage User" } + h1 { @format!("{:?}", user.display_name) " (" @user.name ")" } + a[href=uri!(r_admin_users())] "Back to the User List" @FlashDisplay { flash: flash.clone() } - h2 { @format!("{:?}", user.display_name) " (" @user.name ")" } form[method="POST", action=uri!(r_admin_remove_user())] { input[type="text", name="name", value=&user.name, hidden]; - input[type="submit", value="Remove(!)"]; + input[type="submit", value="Remove user(!)"]; } - h3 { "Permissions" } + h2 { "Permissions" } @PermissionDisplay { perms: &user.permissions } form[method="POST", action=uri!(r_admin_user_permission())] { @@ -90,9 +90,9 @@ fn manage_single_user<'a>( } fieldset.perms { legend { "Permission" } - label { input[type="radio", name="action", value="unset"]; "Unset" } - label { input[type="radio", name="action", value="grant"]; "Grant" } - label { input[type="radio", name="action", value="revoke"]; "Revoke" } + label { input[type="radio", name="action", value="unset"]; "Unset" } br; + label { input[type="radio", name="action", value="grant"]; "Grant" } br; + label { input[type="radio", name="action", value="revoke"]; "Revoke" } br; } input[type="submit", value="Update"]; } @@ -132,7 +132,7 @@ pub enum GrantState { Unset, } -#[post("/admin/update_user_permission", data = "<form>")] +#[post("/admin/q", data = "<form>")] pub fn r_admin_user_permission( session: AdminSession, database: &State<Database>, diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs index f88faa4..5789685 100644 --- a/server/src/routes/ui/assets.rs +++ b/server/src/routes/ui/assets.rs @@ -8,7 +8,7 @@ use crate::{ routes::ui::{account::session::Session, error::MyError, CacheControlFile}, }; use anyhow::{anyhow, Context}; -use jellybase::AssetLocationExt; +use jellybase::{AssetLocationExt, permission::NodePermissionExt}; use jellycommon::AssetLocation; use log::info; use rocket::{get, http::ContentType, FromFormField, State, UriDisplayQuery}; @@ -25,7 +25,7 @@ pub enum AssetRole { #[get("/n/<id>/asset?<role>&<width>")] pub async fn r_item_assets( - _sess: Session, + session: Session, db: &State<Database>, id: &str, role: AssetRole, @@ -34,13 +34,14 @@ pub async fn r_item_assets( let node = db .node .get(&id.to_string())? + .only_if_permitted(&session.user.permissions) .ok_or(anyhow!("node does not exist"))?; let mut asset = match role { AssetRole::Backdrop => node.private.backdrop, AssetRole::Poster => node.private.poster, }; if let None = asset { - if let Some(parent) = &node.public.parent { + if let Some(parent) = &node.public.path.last() { let parent = db.node.get(parent)?.ok_or(anyhow!("node does not exist"))?; asset = match role { AssetRole::Backdrop => parent.private.backdrop, diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs index 1a906f1..b72ec11 100644 --- a/server/src/routes/ui/node.rs +++ b/server/src/routes/ui/node.rs @@ -22,6 +22,7 @@ use crate::{ uri, }; use anyhow::{anyhow, Context}; +use jellybase::permission::NodePermissionExt; use jellycommon::{MediaInfo, NodeKind, NodePublic, Rating, SourceTrackKind}; use rocket::{get, serde::json::Json, Either, State}; @@ -39,11 +40,11 @@ pub async fn r_library_node_filter<'a>( aj: AcceptJson, filter: NodeFilterSort, ) -> Result<Either<DynLayoutPage<'a>, Json<NodePublic>>, MyError> { - drop(session); let node = db .node .get(&id.to_string()) .context("retrieving library node")? + .only_if_permitted(&session.user.permissions) .ok_or(anyhow!("node does not exist"))? .public; @@ -124,7 +125,7 @@ markup::define! { } @if matches!(node.kind, NodeKind::Collection | NodeKind::Channel) { @if matches!(node.kind, NodeKind::Collection) { - @if let Some(parent) = &node.parent { + @if let Some(parent) = &node.path.last().cloned() { a.dirup[href=uri!(r_library_node(parent))] { "Go up" } } } diff --git a/web/script/player/track.ts b/web/script/player/track.ts index 890176c..e2d9d85 100644 --- a/web/script/player/track.ts +++ b/web/script/player/track.ts @@ -3,7 +3,7 @@ import { JhlsTrack, TimeRange } from "./jhls.d.ts"; import { BufferRange, Player } from "./player.ts"; import { EncodingProfileExt } from "./profiles.ts"; -const TARGET_BUFFER_DURATION = 5 +const TARGET_BUFFER_DURATION = 10 const MIN_BUFFER_DURATION = 1 export interface AppendRange extends TimeRange { buf: ArrayBuffer, index: number, cb: () => void } |