aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/src/stream.rs2
-rw-r--r--common/src/user.rs21
-rw-r--r--server/src/routes/stream.rs14
-rw-r--r--stream/src/jhls.rs10
-rw-r--r--stream/src/lib.rs16
-rw-r--r--stream/src/segment.rs10
6 files changed, 57 insertions, 16 deletions
diff --git a/common/src/stream.rs b/common/src/stream.rs
index 8a7ff36..6c93294 100644
--- a/common/src/stream.rs
+++ b/common/src/stream.rs
@@ -18,7 +18,7 @@ pub struct StreamSpec {
}
#[rustfmt::skip]
-#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "rocket", derive(FromFormField, UriDisplayQuery))]
pub enum StreamFormat {
diff --git a/common/src/user.rs b/common/src/user.rs
index b049346..3c1c5b0 100644
--- a/common/src/user.rs
+++ b/common/src/user.rs
@@ -1,3 +1,4 @@
+use crate::{stream::StreamFormat, user};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt::Display};
@@ -17,22 +18,26 @@ pub struct PermissionSet(pub HashMap<UserPermission, bool>);
#[serde(rename_all = "snake_case")]
pub enum UserPermission {
Admin,
- OriginalStream,
Transcode,
ManageUsers,
FederatedContent,
GenerateInvite,
+ StreamFormat(StreamFormat),
AccessNode(String),
}
impl UserPermission {
pub const ALL_ENUMERABLE: &'static [UserPermission] = {
use UserPermission::*;
- &[Admin, OriginalStream, Transcode]
+ &[Admin, Transcode, StreamFormat(user::StreamFormat::Original)]
};
pub fn default_value(&self) -> bool {
+ use user::StreamFormat::*;
use UserPermission::*;
- matches!(self, Transcode)
+ matches!(
+ self,
+ Transcode | StreamFormat(Jhls | HlsMaster | HlsVariant | Matroska | Segment)
+ )
}
}
@@ -41,7 +46,15 @@ impl Display for UserPermission {
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::StreamFormat(StreamFormat::Original) => {
+ format!("downloading the original media")
+ }
+ UserPermission::StreamFormat(StreamFormat::Matroska) => {
+ format!("downloading a remuxed WebM/Matroska version of the media ")
+ }
+ UserPermission::StreamFormat(x) => {
+ format!("downloading media via {x:?}")
+ }
UserPermission::Transcode => format!("transcoding"),
UserPermission::ManageUsers => format!("management of all users"),
UserPermission::GenerateInvite => format!("inviting new users"),
diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs
index b1248ba..0569903 100644
--- a/server/src/routes/stream.rs
+++ b/server/src/routes/stream.rs
@@ -6,8 +6,11 @@
use super::ui::{account::session::Session, error::MyError};
use crate::{database::Database, federation::Federation};
use anyhow::{anyhow, Result};
-use jellybase::{permission::NodePermissionExt, CONF};
-use jellycommon::{stream::StreamSpec, MediaSource};
+use jellybase::{
+ permission::{NodePermissionExt, PermissionSetExt},
+ CONF,
+};
+use jellycommon::{stream::StreamSpec, user::UserPermission, MediaSource};
use log::{info, warn};
use rocket::{
get, head,
@@ -55,6 +58,11 @@ pub async fn r_stream(
.ok_or(anyhow!("item does not contain media"))?;
if let MediaSource::Remote { host, remote_id } = source {
+ session
+ .user
+ .permissions
+ .assert(&UserPermission::FederatedContent)?;
+
let (username, password, _) = CONF
.remote_credentials
.get(host)
@@ -91,7 +99,7 @@ pub async fn r_stream(
let head = jellystream::stream_head(&spec);
- match jellystream::stream(node, spec, urange).await {
+ match jellystream::stream(node, spec, urange, &session.user.permissions).await {
Ok(stream) => Ok(Either::Left(StreamResponse {
stream,
range,
diff --git a/stream/src/jhls.rs b/stream/src/jhls.rs
index 5183a26..850cc32 100644
--- a/stream/src/jhls.rs
+++ b/stream/src/jhls.rs
@@ -1,8 +1,9 @@
use anyhow::Result;
-use jellybase::CONF;
+use jellybase::{permission::PermissionSetExt, CONF};
use jellycommon::{
jhls::{JhlsMetadata, JhlsTrack},
stream::StreamSpec,
+ user::{PermissionSet, UserPermission},
LocalTrack, Node,
};
use tokio::io::{AsyncWriteExt, DuplexStream};
@@ -12,6 +13,7 @@ pub async fn jhls_stream(
track_sources: Vec<LocalTrack>,
_spec: StreamSpec,
mut b: DuplexStream,
+ perms: &PermissionSet,
) -> Result<()> {
let media = node.public.media.clone().unwrap();
let tracks = tokio::task::spawn_blocking(move || {
@@ -42,7 +44,11 @@ pub async fn jhls_stream(
let out = serde_json::to_string(&JhlsMetadata {
tracks,
- extra_profiles: CONF.transcoding_profiles.clone(),
+ extra_profiles: if perms.check(&UserPermission::Transcode) {
+ CONF.transcoding_profiles.clone()
+ } else {
+ vec![]
+ },
duration: media.duration,
})?;
tokio::spawn(async move { b.write_all(out.as_bytes()).await });
diff --git a/stream/src/lib.rs b/stream/src/lib.rs
index 85ff382..e9b9a4b 100644
--- a/stream/src/lib.rs
+++ b/stream/src/lib.rs
@@ -10,9 +10,10 @@ pub mod segment;
use anyhow::{anyhow, bail, Context, Result};
use hls::{hls_master_stream, hls_variant_stream};
-use jellybase::CONF;
+use jellybase::{permission::PermissionSetExt, CONF};
use jellycommon::{
stream::{StreamFormat, StreamSpec},
+ user::{PermissionSet, UserPermission},
LocalTrack, MediaSource, Node,
};
use jhls::jhls_stream;
@@ -41,7 +42,14 @@ pub fn stream_head(spec: &StreamSpec) -> StreamHead {
}
}
-pub async fn stream(node: Node, spec: StreamSpec, range: Range<usize>) -> Result<DuplexStream> {
+pub async fn stream(
+ node: Node,
+ spec: StreamSpec,
+ range: Range<usize>,
+ perms: &PermissionSet,
+) -> Result<DuplexStream> {
+ perms.assert(&UserPermission::StreamFormat(spec.format))?;
+
let (a, b) = duplex(4096);
let track_sources = match node
@@ -59,8 +67,8 @@ pub async fn stream(node: Node, spec: StreamSpec, range: Range<usize>) -> Result
StreamFormat::Matroska => remux_stream(node, track_sources, spec, range, b).await?,
StreamFormat::HlsMaster => hls_master_stream(node, track_sources, spec, b).await?,
StreamFormat::HlsVariant => hls_variant_stream(node, track_sources, spec, b).await?,
- StreamFormat::Jhls => jhls_stream(node, track_sources, spec, b).await?,
- StreamFormat::Segment => segment_stream(node, track_sources, spec, b).await?,
+ StreamFormat::Jhls => jhls_stream(node, track_sources, spec, b, perms).await?,
+ StreamFormat::Segment => segment_stream(node, track_sources, spec, b, perms).await?,
}
Ok(a)
diff --git a/stream/src/segment.rs b/stream/src/segment.rs
index ce3f8e1..309da1d 100644
--- a/stream/src/segment.rs
+++ b/stream/src/segment.rs
@@ -4,8 +4,12 @@
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
use anyhow::{anyhow, bail, Result};
-use jellybase::{AssetLocationExt, CONF};
-use jellycommon::{stream::StreamSpec, LocalTrack, Node};
+use jellybase::{permission::PermissionSetExt, AssetLocationExt, CONF};
+use jellycommon::{
+ stream::StreamSpec,
+ user::{PermissionSet, UserPermission},
+ LocalTrack, Node,
+};
use jellytranscoder::snippet::transcode;
use log::warn;
use tokio::{fs::File, io::DuplexStream};
@@ -16,6 +20,7 @@ pub async fn segment_stream(
track_sources: Vec<LocalTrack>,
spec: StreamSpec,
mut b: DuplexStream,
+ perms: &PermissionSet,
) -> Result<()> {
if spec.tracks.len() != 1 {
bail!("unsupported number of tracks for segment, must be exactly one");
@@ -24,6 +29,7 @@ pub async fn segment_stream(
let n = spec.index.ok_or(anyhow!("segment index missing"))?;
if let Some(profile) = spec.profile {
+ perms.assert(&UserPermission::Transcode)?;
let location = transcode(
&format!("{track} {n} {:?}", node.private.source), // TODO maybe not use the entire source
CONF.transcoding_profiles