Skip to content
Clone

refactor(repo): drop unused permission helpers and lifecycle authz scaffolding

zhenyi committed
parent 87e7190 commit 3609d73 Browse files

5 files changed

+1 −45

File tree 5
Modified lib/repo/features/lifecycle/usecase/get.rs View lib/repo/features/lifecycle/usecase/get.rs
@@ -19,9 +19,6 @@
1919 let repo = load_repo(svc, channel_id, repo_id).await?;
2020 if repo.is_private {
2121 // Private repos are readable only by actors allowed at least
22 // RepoAccess::Read (channel creator / space member / collaborator
23 // / site admin). Public repos stay readable by anyone, including
24 // the anonymous GraphQL path.
2522 authz::authorize_repo(svc, actor, &repo, RepoAccess::Read).await?;
2623 }
2724 Ok(RepoView::from(repo))
Modified lib/repo/features/lifecycle/usecase/list.rs View lib/repo/features/lifecycle/usecase/list.rs
@@ -30,9 +30,6 @@
3030 query = query.filter(repo_entity::Column::LowerName.contains(search));
3131 }
3232 }
33 // Visibility: repos that are public, repos inside channels the actor
34 // can reach, or repos where the actor holds a direct collaborator
35 // grant. Prevents repository enumeration for repositories the caller
3633 // is not allowed to read (CWE-200 / CWE-639).
3734 let (visible_channels, collab_repos) = authz::visible_repo_scope(svc, actor).await?;
3835 let mut visible = Condition::any().add(repo_entity::Column::IsPrivate.eq(false));
Modified lib/repo/features/authz.rs View lib/repo/features/authz.rs
@@ -16,10 +16,6 @@
1616use crate::features::RepoService;
1717use crate::permission;
1818
19/// Resolve an authenticated actor id into an `ActorContext` with an empty
20/// scope set (so `permission::effective_access` reaches the role/channel
21/// branches; an actor built with `for_user()` short-circuits them to `Read`).
22/// `is_site_admin` comes from the live `users` row, not the JWT snapshot.
2319pub async fn resolve_actor(svc: &RepoService, actor_id: Uuid) -> RepoResult<ActorContext> {
2420 if actor_id.is_nil() {
2521 return Err(RepoError::Unauthorized("authentication required".into()));
@@ -42,9 +38,6 @@
4238 })
4339}
4440
45/// Gate a repo operation at `required` access. The repo's channel is resolved
46/// from `repo.channel_id` and ownership is derived from the channel creator
47/// plus the space role and any direct collaborator grant.
4841pub async fn authorize_repo(
4942 svc: &RepoService,
5043 actor_id: Uuid,
@@ -86,8 +79,6 @@
8679 .map_err(map_permission_error)
8780}
8881
89/// Load a repo strictly by id, without the channel-containment check that the
90/// lifecycle helpers enforce (for features that receive only a `repo_id`).
9182pub async fn load_repo_by_id(svc: &RepoService, repo_id: Uuid) -> RepoResult<repo_entity::Model> {
9283 let db = svc.db.reader();
9384 repo_entity::Entity::find_by_id(repo_id)
@@ -96,10 +87,6 @@
9687 .ok_or_else(|| RepoError::NotFound(format!("repo {repo_id}")))
9788}
9889
99/// Gate creating a resource (fork/generate target) inside `channel_id`: the
100/// actor must be the channel creator, a member of the channel's space, or the
101/// space owner. Blocks forking into an arbitrary channel the caller cannot
102/// host repositories in.
10390pub async fn authorize_channel_resource(
10491 svc: &RepoService,
10592 actor_id: Uuid,
@@ -125,7 +112,6 @@
125112 .await?
126113 .ok_or_else(|| RepoError::NotFound(format!("space {space_id}")))?;
127114 match permission::space_role_for(&db, &space, actor_id).await? {
128 // Owner / Admin / Member of the space may host a repo in its channels.
129115 Some(_) => Ok(()),
130116 None => Err(RepoError::Forbidden(
131117 "not a member of the channel's space".into(),
@@ -147,10 +133,6 @@
147133 }
148134}
149135
150/// Scope a repo listing to what `actor_id` may read. Returns the visible
151/// channel ids (channels created by the actor or belonging to spaces the actor
152/// belongs to) plus repo ids where the actor holds a direct collaborator
153/// grant. An empty result for a nil actor means "public repos only".
154136pub async fn visible_repo_scope(
155137 svc: &RepoService,
156138 actor_id: Uuid,
@@ -160,8 +142,6 @@
160142 return Ok((Vec::new(), Vec::new()));
161143 }
162144
163 // Spaces the actor can reach: owned spaces, spaces where they hold an
164 // active membership, or spaces whose role grants `manage_members`.
165145 let mut space_ids: Vec<Uuid> = Vec::new();
166146 {
167147 let owned: Vec<Uuid> = space_entity::Entity::find()
@@ -215,7 +195,6 @@
215195 space_ids.sort_unstable();
216196 space_ids.dedup();
217197
218 // Channels the actor can reach: created by them, or bound to visible spaces.
219198 let mut channel_ids: Vec<Uuid> = Vec::new();
220199 {
221200 let created: Vec<Uuid> = channel_entity::Entity::find()
@@ -242,7 +221,6 @@
242221 channel_ids.sort_unstable();
243222 channel_ids.dedup();
244223
245 // Direct collaborator grants on specific repos.
246224 let mut collab_repo_ids: Vec<Uuid> = Vec::new();
247225 {
248226 let rows: Vec<Uuid> = collab_entity::Entity::find()
Modified lib/repo/actor.rs View lib/repo/actor.rs
@@ -46,9 +46,7 @@
4646 pub scopes: HashSet<TokenScope>,
4747 pub is_site_admin: bool,
4848
49 /// Ceiling this credential may grant (e.g. a `read`-scoped access key).
50 /// `None` means the credential itself imposes no limit.
51 pub credential_max: Option<RepoAccess>,
49 pub credential_max: Option<RepoAccess>,
5250
5351 pub channel_role: Option<ChannelRole>,
5452
Modified lib/repo/permission.rs View lib/repo/permission.rs
@@ -5,8 +5,6 @@
55
66use crate::actor::{ActorContext, ChannelRole, RepoAccess, SpaceRole, TokenScope};
77
8/// A space member holding a role that grants `manage_members` is treated as a
9/// space admin by `space_role_for` (mirrors `lib/space/features/authz`).
108pub const SPACE_ADMIN_PERMISSION: &str = "manage_members";
119
1210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -36,10 +34,6 @@
3634 }
3735
3836 // NOTE: an actor with any Repo* scope short-circuits to the scope-based
39 // ladder, so the role/channel branches below are only reachable by actors
40 // with an EMPTY scope set (the pattern dvcs/gitauth uses). HTTP-side
41 // callers must not build the actor with `for_user()` (which grants
42 // RepoRead) or the role branches are bypassed.
4337 if actor.has_scope(TokenScope::RepoAdmin) {
4438 return RepoAccess::Admin;
4539 }
@@ -80,9 +74,6 @@
8074 RepoAccess::None
8175}
8276
83/// Resolve the actor's role within a space, if any. `None` for non-members;
84/// `Some(Owner)` when the user owns the space; otherwise `Member`/`Admin`
85/// based on active membership + role permissions. Only DB errors are produced.
8677pub async fn space_role_for(
8778 db: &impl ConnectionTrait,
8879 space: &spaces::Model,
@@ -124,8 +115,6 @@
124115 }))
125116}
126117
127/// Direct per-user grant on a repo via `repo_collaborations`. `None` when the
128/// user has no collaboration row. Only DB errors are produced.
129118pub async fn collaborator_access(
130119 db: &impl ConnectionTrait,
131120 repo_id: Uuid,
@@ -213,10 +202,7 @@
213202 require(actor, RepoAccess::Read, None, None, None, is_public)
214203 }
215204
216 // C3 regression: the SSH clone gate (`require(Read)` on a private repo)
217205 // must deny a plain authenticated outsider. Before the C3 fix the SSH
218 // actor carried an unconditional RepoRead scope that short-circuited
219 // `effective_access` to Read, so this returned Ok.
220206 #[test]
221207 fn ssh_plain_user_cannot_clone_private_repo() {
222208 let a = actor(Some(Uuid::now_v7()), false);