diff --git a/src/common/config.rs b/src/common/config.rs index 428ef70c..3b7d1883 100644 --- a/src/common/config.rs +++ b/src/common/config.rs @@ -618,6 +618,9 @@ pub struct FeaturesConfig { pub enable_trash: bool, pub enable_search: bool, pub enable_music: bool, + /// Expose other OxiCloud users as a read-only "system" address book + /// at GET /api/address-books. Set to false to hide the user directory. + pub expose_system_users: bool, } impl Default for FeaturesConfig { @@ -629,6 +632,7 @@ impl Default for FeaturesConfig { enable_trash: true, // Enable trash feature enable_search: true, // Enable search feature enable_music: true, // Enable music feature + expose_system_users: true, // Expose OxiCloud users as address book by default } } } @@ -948,6 +952,12 @@ impl AppConfig { config.features.enable_music = val; } + if let Ok(v) = env::var("OXICLOUD_EXPOSE_SYSTEM_USERS").map(|v| v.parse::()) + && let Ok(val) = v + { + config.features.expose_system_users = val; + } + // Storage limits if let Ok(max_upload) = env::var("OXICLOUD_MAX_UPLOAD_SIZE").map(|v| v.parse::()) && let Ok(val) = max_upload diff --git a/src/interfaces/api/handlers/contacts_handler.rs b/src/interfaces/api/handlers/contacts_handler.rs index 13ce917e..99604da7 100644 --- a/src/interfaces/api/handlers/contacts_handler.rs +++ b/src/interfaces/api/handlers/contacts_handler.rs @@ -30,6 +30,8 @@ const SYSTEM_BOOK_ID: &str = "system"; pub struct ContactsApiState { pub contact_service: Arc, pub auth_service: Option>, + /// When false, the virtual "system" address book (OxiCloud users) is hidden. + pub expose_system_users: bool, } /// Address book entry with `is_readonly` and `is_system` flags. @@ -251,7 +253,7 @@ pub async fn list_address_books( }) .collect(); - if state.auth_service.is_some() { + if state.expose_system_users && state.auth_service.is_some() { let now = Utc::now(); response.push(AddressBookResponse { id: SYSTEM_BOOK_ID.to_string(), @@ -438,6 +440,9 @@ pub async fn list_contacts( Query(params): Query, ) -> impl IntoResponse { if book_id == SYSTEM_BOOK_ID { + if !state.expose_system_users { + return system_book_unavailable(); + } let Some(auth_service) = &state.auth_service else { return system_book_unavailable(); }; @@ -544,6 +549,9 @@ pub async fn get_contact( Path((book_id, contact_id)): Path<(String, String)>, ) -> impl IntoResponse { if book_id == SYSTEM_BOOK_ID { + if !state.expose_system_users { + return system_book_unavailable(); + } let Some(auth_service) = &state.auth_service else { return system_book_unavailable(); }; diff --git a/src/interfaces/api/routes.rs b/src/interfaces/api/routes.rs index 0c2e8b57..b070bf88 100644 --- a/src/interfaces/api/routes.rs +++ b/src/interfaces/api/routes.rs @@ -405,6 +405,7 @@ pub fn create_api_routes(app_state: &Arc) -> Router> { let contacts_state = ContactsApiState { contact_service, auth_service: auth_svc, + expose_system_users: app_state.core.config.features.expose_system_users, }; let contacts_router = Router::new()