11//! Global admin permission operations.
22//!
3- //! This module provides admin permission checking for the monorepo system.
4- //! All admin permissions are defined in a single `.mega_cedar.json` file
5- //! located in the root directory (`/`).
3+ //! Effective admins are the **union** of:
4+ //! - `monorepo. admin` in mega config (always applied; used at monorepo init and at runtime)
5+ //! - users in the root `/.mega_cedar.json` admin group
66//!
77//! # Design
8- //! - A single global admin list applies to the entire monorepo
9- //! - The admin configuration file is stored at `/.mega_cedar.json`
10- //! - Redis caching is used to avoid repeated file parsing
8+ //! - Config admins are checked on every request (not baked into Redis), so they
9+ //! remain valid even when Cedar/Redis is stale or missing
10+ //! - Cedar-derived admins are Redis-cached (TTL 10 minutes) to avoid re-parsing
11+ //! `.mega_cedar.json`
12+
13+ use std:: collections:: BTreeSet ;
1114
1215use common:: errors:: MegaError ;
1316use git_internal:: internal:: object:: tree:: Tree ;
1417use jupiter:: { redis:: AsyncCommands , utils:: converter:: FromMegaModel } ;
1518
1619use crate :: application:: api_service:: mono:: context:: AdminApplicationService ;
1720
18- /// Cache TTL for admin list (10 minutes).
21+ /// Cache TTL for Cedar admin list (10 minutes).
1922pub const ADMIN_CACHE_TTL : u64 = 600 ;
2023
2124/// The Cedar entity file name in root directory.
2225pub const ADMIN_FILE : & str = ".mega_cedar.json" ;
2326
24- /// Redis cache key suffix for admin list.
27+ /// Redis cache key suffix for Cedar admin list (config admins are merged at read time) .
2528const ADMIN_CACHE_KEY_SUFFIX : & str = "admin:list" ;
2629
2730impl AdminApplicationService {
28- /// Check if a user is an admin.
31+ /// Check if a user is an admin (config `monorepo.admin` or Cedar) .
2932 pub async fn check_is_admin ( & self , username : & str ) -> Result < bool , MegaError > {
33+ let username = username. trim ( ) ;
34+ if username. is_empty ( ) {
35+ return Ok ( false ) ;
36+ }
3037 let admins = self . get_effective_admins ( ) . await ?;
31- Ok ( admins. contains ( & username . to_string ( ) ) )
38+ Ok ( admins. iter ( ) . any ( |a| a == username ) )
3239 }
3340
34- /// Retrieve all admin usernames .
41+ /// Retrieve all effective admin identities (config ∪ Cedar), sorted uniquely .
3542 pub async fn get_all_admins ( & self ) -> Result < Vec < String > , MegaError > {
3643 self . get_effective_admins ( ) . await
3744 }
3845
39- /// Get admins from cache or storage.
40- /// This method first attempts to read from Redis cache. On cache miss,
41- /// it loads the admin list from the `.mega_cedar.json` file and caches
42- /// the result.
46+ /// GitHub logins (or Cedar euids) listed under `[monorepo] admin` in config.
47+ fn config_admins ( & self ) -> Vec < String > {
48+ self . ctx
49+ . storage ( )
50+ . config ( )
51+ . monorepo
52+ . admin
53+ . iter ( )
54+ . map ( |s| s. trim ( ) . to_string ( ) )
55+ . filter ( |s| !s. is_empty ( ) )
56+ . collect ( )
57+ }
58+
59+ /// Merge Cedar admins with config admins (sorted, unique).
60+ fn merge_with_config_admins ( & self , cedar_admins : Vec < String > ) -> Vec < String > {
61+ let mut set: BTreeSet < String > = cedar_admins. into_iter ( ) . collect ( ) ;
62+ for admin in self . config_admins ( ) {
63+ set. insert ( admin) ;
64+ }
65+ set. into_iter ( ) . collect ( )
66+ }
67+
68+ /// Get effective admins: Redis/Cedar list ∪ `monorepo.admin`.
4369 ///
44- /// If `.mega_cedar.json` (or root refs) are missing, returns an empty list
45- /// so callers can fall through to other authz paths (e.g. user approval)
46- /// instead of hard-failing the request .
70+ /// Config admins are always merged after cache/file load so a stale Redis
71+ /// Cedar list cannot drop configured admins. If `.mega_cedar.json` is
72+ /// missing, config admins alone still apply .
4773 async fn get_effective_admins ( & self ) -> Result < Vec < String > , MegaError > {
74+ let cedar_admins = self . get_cedar_admins ( ) . await ?;
75+ Ok ( self . merge_with_config_admins ( cedar_admins) )
76+ }
77+
78+ /// Cedar-only admin list (cached). Does not include config admins.
79+ async fn get_cedar_admins ( & self ) -> Result < Vec < String > , MegaError > {
4880 if let Ok ( admins) = self . get_admins_from_cache ( ) . await {
4981 return Ok ( admins) ;
5082 }
@@ -54,7 +86,7 @@ impl AdminApplicationService {
5486 Err ( e) if is_admin_config_unavailable ( & e) => {
5587 tracing:: warn!(
5688 error = %e,
57- "Admin config unavailable; treating as empty admin list "
89+ "Admin Cedar config unavailable; using monorepo.admin from config only "
5890 ) ;
5991 return Ok ( Vec :: new ( ) ) ;
6092 }
@@ -70,7 +102,7 @@ impl AdminApplicationService {
70102 Ok ( admins)
71103 }
72104
73- /// Invalidate the admin list cache.
105+ /// Invalidate the Cedar admin list cache.
74106 /// This should be called when the `.mega_cedar.json` file is modified.
75107 pub async fn invalidate_admin_cache ( & self ) {
76108 let mut conn = self . ctx . git_object_cache ( ) . connection . clone ( ) ;
0 commit comments