11/*
2- * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
2+ * This file is part of CoCalc: Copyright © 2020 - 2025 Sagemath, Inc.
33 * License: MS-RSL – see LICENSE.md for details
44 */
55
@@ -8,6 +8,10 @@ import { is_array, is_valid_uuid_string } from "@cocalc/util/misc";
88import { callback2 } from "@cocalc/util/async-utils" ;
99import isSandbox from "@cocalc/server/projects/is-sandbox" ;
1010import idleSandboxUsers from "@cocalc/server/projects/idle-sandbox-users" ;
11+ import {
12+ ensureCanManageCollaborators ,
13+ ensureCanRemoveUser ,
14+ } from "./ownership-checks" ;
1115
1216const GROUPS = [ "owner" , "collaborator" ] as const ;
1317
@@ -40,6 +44,18 @@ export async function add_collaborators_to_projects(
4044 Also, the input is uuid's, which typescript can't check. */
4145 verify_types ( account_id , accounts , projects ) ;
4246
47+ // Check strict_collaborator_management setting before database changes
48+ // Only check if not using tokens (tokens have their own permission system)
49+ if ( ! tokens ) {
50+ for ( const project_id of new Set ( projects ) ) {
51+ if ( ! project_id ) continue ; // skip empty strings
52+ await ensureCanManageCollaborators ( {
53+ project_id,
54+ account_id,
55+ } ) ;
56+ }
57+ }
58+
4359 // We now know that account_id is allowed to add users to all of the projects,
4460 // *OR* at that there are valid tokens to permit adding users.
4561
@@ -71,7 +87,7 @@ export async function remove_collaborators_from_projects(
7187 db : PostgreSQL ,
7288 account_id : string ,
7389 accounts : string [ ] ,
74- projects : string [ ] , // can be empty strings if tokens specified (since they determine project_id)
90+ projects : string [ ] ,
7591) : Promise < void > {
7692 try {
7793 // Ensure user is allowed to modify project(s)
@@ -92,6 +108,31 @@ export async function remove_collaborators_from_projects(
92108 Also, the input is uuid's, which typescript can't check. */
93109 verify_types ( account_id , accounts , projects ) ;
94110
111+ // Check strict_collaborator_management setting before database changes
112+ // Skip check if the user is only removing themselves
113+ const is_self_remove_only =
114+ accounts . length === 1 && accounts [ 0 ] === account_id ;
115+ if ( ! is_self_remove_only ) {
116+ for ( const project_id of new Set ( projects ) ) {
117+ if ( ! project_id ) continue ; // skip empty strings
118+ await ensureCanManageCollaborators ( {
119+ project_id,
120+ account_id,
121+ } ) ;
122+ }
123+ }
124+
125+ // CRITICAL: Verify that no target users are owners
126+ // Owners must be demoted to collaborator first, then removed
127+ for ( const i in projects ) {
128+ const project_id : string = projects [ i ] ;
129+ const target_account_id : string = accounts [ i ] ;
130+ await ensureCanRemoveUser ( {
131+ project_id,
132+ target_account_id,
133+ } ) ;
134+ }
135+
95136 // Remove users from projects
96137 //
97138 for ( const i in projects ) {
0 commit comments