171 lines
7.7 KiB
Diff
171 lines
7.7 KiB
Diff
diff --git a/integration-tests/tests/get_token.rs b/integration-tests/tests/get_token.rs
|
|
index 386f97b..102ff4a 100644
|
|
--- a/integration-tests/tests/get_token.rs
|
|
+++ b/integration-tests/tests/get_token.rs
|
|
@@ -353,13 +353,12 @@ async fn remote_token() {
|
|
assert_eq!(response["url"].as_str(), Some(sfu.url()));
|
|
let jwt = response["jwt"].as_str().unwrap_or_default();
|
|
|
|
- // The JWT should grant joining, subscribing and updating the
|
|
- // participant's own metadata, but not publishing, since the homeserver
|
|
- // is remote.
|
|
+ // Remote verified users may publish, but the service must not create a
|
|
+ // room for them. Room creation remains gated on the full-access list.
|
|
let claims = decode_livekit_jwt(jwt);
|
|
assert_eq!(claims["iss"].as_str(), Some(LIVEKIT_KEY));
|
|
assert_eq!(claims["video"]["roomJoin"].as_bool(), Some(true));
|
|
- assert_eq!(claims["video"]["canPublish"].as_bool(), Some(false));
|
|
+ assert_eq!(claims["video"]["canPublish"].as_bool(), Some(true));
|
|
assert_eq!(claims["video"]["canSubscribe"].as_bool(), Some(true));
|
|
assert_eq!(
|
|
claims["video"]["canUpdateOwnMetadata"].as_bool(),
|
|
diff --git a/integration-tests/tests/sfu_get.rs b/integration-tests/tests/sfu_get.rs
|
|
index 5a08445..2cc8672 100644
|
|
--- a/integration-tests/tests/sfu_get.rs
|
|
+++ b/integration-tests/tests/sfu_get.rs
|
|
@@ -323,7 +323,7 @@ async fn remote_token() {
|
|
let claims = decode_livekit_jwt(jwt);
|
|
assert_eq!(claims["iss"].as_str(), Some(LIVEKIT_KEY));
|
|
assert_eq!(claims["video"]["roomJoin"].as_bool(), Some(true));
|
|
- assert_eq!(claims["video"]["canPublish"].as_bool(), Some(false));
|
|
+ assert_eq!(claims["video"]["canPublish"].as_bool(), Some(true));
|
|
assert_eq!(claims["video"]["canSubscribe"].as_bool(), Some(true));
|
|
assert_eq!(
|
|
claims["sub"].as_str(),
|
|
diff --git a/src/handler.rs b/src/handler.rs
|
|
index 2797194..7910e1e 100644
|
|
--- a/src/handler.rs
|
|
+++ b/src/handler.rs
|
|
@@ -978,7 +978,11 @@ impl Handler {
|
|
&lk_room_alias,
|
|
&lk_identity,
|
|
&matrix_id,
|
|
- is_full_access_user,
|
|
+ // Publication is an authenticated participation permission, not a
|
|
+ // room-creation privilege. OpenID and (when configured) membership
|
|
+ // checks above must still succeed. Keep creation/delegation gated
|
|
+ // below and LiveKit room.auto_create=false on the SFU.
|
|
+ true,
|
|
)?;
|
|
|
|
if is_full_access_user {
|
|
@@ -1058,7 +1062,11 @@ impl Handler {
|
|
&lk_room_alias,
|
|
&lk_identity,
|
|
&matrix_id,
|
|
- is_full_access_user,
|
|
+ // Publication is an authenticated participation permission, not a
|
|
+ // room-creation privilege. OpenID and (when configured) membership
|
|
+ // checks above must still succeed. Keep creation/delegation gated
|
|
+ // below and LiveKit room.auto_create=false on the SFU.
|
|
+ true,
|
|
)?;
|
|
|
|
if is_full_access_user {
|
|
diff --git a/src/handler_tests.rs b/src/handler_tests.rs
|
|
index 54d1650..e6c943c 100644
|
|
--- a/src/handler_tests.rs
|
|
+++ b/src/handler_tests.rs
|
|
@@ -5403,3 +5403,87 @@ async fn test_membership_skipped_unless_hs_is_the_only_full_access_homeserver()
|
|
handler.close().await;
|
|
}
|
|
}
|
|
+
|
|
+// iscg standalone publication policy regression tests. The full-access list
|
|
+// governs room creation/delegation, never whether a verified participant speaks.
|
|
+#[tokio::test]
|
|
+async fn test_iscg_standalone_publication_does_not_grant_creation() {
|
|
+ for path in ["/sfu/get", "/get_token"] {
|
|
+ for server in ["example.com", "astr.to", "unrelated.example"] {
|
|
+ let sub = format!("@user:{server}");
|
|
+ let created = Arc::new(AtomicUsize::new(0));
|
|
+ let counter = created.clone();
|
|
+ let deps = HandlerTestDeps {
|
|
+ exchange_openid_userinfo_fn: exchange_openid_userinfo_ok(&sub),
|
|
+ create_livekit_room_fn: Some(Box::new(move |_, _, _| {
|
|
+ counter.fetch_add(1, Ordering::SeqCst);
|
|
+ Ok(())
|
|
+ })),
|
|
+ ..Default::default()
|
|
+ };
|
|
+ let handler = new_get_token_handler(deps);
|
|
+ let mut req = valid_sfu_request();
|
|
+ req.openid_token.matrix_server_name = server.into();
|
|
+ req.member.claimed_user_id = sub.clone();
|
|
+ let body = if path == "/get_token" {
|
|
+ serde_json::to_string(&req).unwrap()
|
|
+ } else {
|
|
+ serde_json::to_string(&LegacySfuRequest {
|
|
+ room: req.room_id.clone(),
|
|
+ openid_token: req.openid_token.clone(),
|
|
+ device_id: req.member.claimed_device_id.clone(),
|
|
+ ..Default::default()
|
|
+ }).unwrap()
|
|
+ };
|
|
+ let resp = send_request(&handler, post_json(path, body)).await;
|
|
+ assert_eq!(resp.status(), StatusCode::OK, "{path} {server}");
|
|
+ let result: SfuResponse = serde_json::from_slice(&body_bytes(resp).await).unwrap();
|
|
+ let claims = parse_jwt_claims(&result.jwt, &handler.livekit_auth.secret);
|
|
+ assert_eq!(claims["video"]["canPublish"], true, "{path} {server}");
|
|
+ assert_eq!(claims["video"]["canSubscribe"], true);
|
|
+ assert_eq!(claims["video"]["roomJoin"], true);
|
|
+ for privilege in ["roomCreate", "roomAdmin", "roomList", "roomRecord", "ingressAdmin"] {
|
|
+ assert_ne!(claims["video"][privilege], true, "{path}: {privilege}");
|
|
+ }
|
|
+ let want_room = livekit_room_alias_for(&req.room_id, &req.slot_id);
|
|
+ assert_eq!(claims["video"]["room"], want_room.0);
|
|
+ assert_eq!(created.load(Ordering::SeqCst), usize::from(server == "example.com"));
|
|
+ assert_eq!(handler.is_full_access_user(server), server == "example.com");
|
|
+ handler.close().await;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+#[tokio::test]
|
|
+async fn test_iscg_publication_still_requires_valid_openid() {
|
|
+ for path in ["/sfu/get", "/get_token"] {
|
|
+ for forged_subject in [false, true] {
|
|
+ let deps = HandlerTestDeps {
|
|
+ exchange_openid_userinfo_fn: if forged_subject {
|
|
+ exchange_openid_userinfo_ok("@victim:example.com")
|
|
+ } else {
|
|
+ Some(Box::new(|_| Err("invalid or expired token".into())))
|
|
+ },
|
|
+ // An accidental room creation or membership request panics.
|
|
+ ..Default::default()
|
|
+ };
|
|
+ let handler = new_get_token_handler(deps);
|
|
+ let mut req = valid_sfu_request();
|
|
+ req.openid_token.matrix_server_name = "astr.to".into();
|
|
+ req.member.claimed_user_id = "@user:astr.to".into();
|
|
+ let body = if path == "/get_token" {
|
|
+ serde_json::to_string(&req).unwrap()
|
|
+ } else {
|
|
+ serde_json::to_string(&LegacySfuRequest {
|
|
+ room: req.room_id,
|
|
+ openid_token: req.openid_token,
|
|
+ device_id: "DEVICE".into(),
|
|
+ ..Default::default()
|
|
+ }).unwrap()
|
|
+ };
|
|
+ let resp = send_request(&handler, post_json(path, body)).await;
|
|
+ assert_eq!(resp.status(), StatusCode::UNAUTHORIZED, "{path} forged={forged_subject}");
|
|
+ handler.close().await;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main.rs b/src/main.rs
|
|
index 2a057d6..7e7ebfd 100644
|
|
--- a/src/main.rs
|
|
+++ b/src/main.rs
|
|
@@ -49,6 +49,10 @@ async fn main() {
|
|
}
|
|
}
|
|
|
|
+ info!(
|
|
+ "iscg standalone policy v1: verified users may publish; room creation and delayed-leave delegation remain full-access only"
|
|
+ );
|
|
+
|
|
let config = match parse_config() {
|
|
Ok(config) => config,
|
|
Err(err) => {
|