@@ -187,8 +187,11 @@ describe('[#20083] plain wildcard coverage', () => {
187187 [ { objects : { '*' : { ...WILD , viewAllRecords : true , modifyAllRecords : true } } } ] ,
188188 source ,
189189 ) ;
190- // Seeded all-false, then folded: the super-user entry shape, not the wildcard's own bits.
191- expect ( Object . keys ( map . crm_account ) ) . toEqual ( [ 'allowCreate' , 'allowRead' , 'allowEdit' , 'allowDelete' , 'apiOperations' ] ) ;
190+ // Seeded all-false, then folded: the super-user entry shape, not the wildcard's own key
191+ // order — [#20134] with `allowTransfer`, which `modifyAllRecords` grants, appended by the
192+ // per-set fold (the wildcard's own `allowTransfer: false` grants nothing and is not copied).
193+ expect ( Object . keys ( map . crm_account ) ) . toEqual ( [ 'allowCreate' , 'allowRead' , 'allowEdit' , 'allowDelete' , 'allowTransfer' , 'apiOperations' ] ) ;
194+ expect ( map . crm_account . allowTransfer ) . toBe ( true ) ;
192195 // …and the private object is the seed's, as before.
193196 expect ( map . crm_secret ) . toMatchObject ( { allowRead : true , allowEdit : true } ) ;
194197 } ) ;
@@ -200,3 +203,83 @@ describe('[#20083] plain wildcard coverage', () => {
200203 expect ( map ) . toEqual ( { '*' : WILD } ) ;
201204 } ) ;
202205} ) ;
206+
207+ /**
208+ * [#20134] A SUPER-USER `'*'` — one carrying `viewAllRecords` or
209+ * `modifyAllRecords` — puts on the map every bit it grants, per set, the way
210+ * `PermissionEvaluator.checkObjectPermission` resolves it: the seed places an
211+ * entry for every registered object the merge left absent, and the per-set
212+ * fold reads each set's wildcard through the spec's `objectPermissionGrants`.
213+ * The map used to hold only the four bits the merged fold pulls, so
214+ * `current_user.can(object, 'transfer')` answered `false` for a platform admin
215+ * the server lets transfer, a super-read wildcard lost its own plain bits, and
216+ * a super-user wildcard carrying `allowExport` left every unrestricted object
217+ * with no entry at all.
218+ *
219+ * The enforcement-side half — every verb, the shipped super-user sets, the
220+ * real `can()` against the real evaluator — is pinned table-driven in
221+ * plugin-security's `get-effective-object-permissions.test.ts`.
222+ */
223+ describe ( '[#20134] super-user wildcard: every bit it grants, per set' , ( ) => {
224+ const SCHEMAS : Record < string , any > = {
225+ crm_account : { name : 'crm_account' } ,
226+ crm_lead : { name : 'crm_lead' , enable : { apiMethods : [ 'get' , 'list' ] } } ,
227+ crm_secret : { name : 'crm_secret' , access : { default : 'private' } } ,
228+ } ;
229+ const source = { allSchemas : ( ) => Object . values ( SCHEMAS ) , schemaOf : ( n : string ) => SCHEMAS [ n ] } ;
230+
231+ it ( 'the write bypass carries `transfer` onto every entry, a private object\'s included' , ( ) => {
232+ const map : any = buildEffectiveObjectPermissions (
233+ [ { objects : { '*' : { allowRead : true , allowCreate : true , modifyAllRecords : true } } } ] ,
234+ source ,
235+ ) ;
236+ for ( const name of Object . keys ( SCHEMAS ) ) {
237+ expect ( map [ name ] , name ) . toMatchObject ( { allowRead : true , allowEdit : true , allowDelete : true , allowTransfer : true } ) ;
238+ }
239+ } ) ;
240+
241+ it ( 'a super-read wildcard keeps its own plain bits — and nothing it does not grant' , ( ) => {
242+ const map : any = buildEffectiveObjectPermissions (
243+ [ { objects : { '*' : { viewAllRecords : true , allowEdit : true } } } ] ,
244+ source ,
245+ ) ;
246+ for ( const name of Object . keys ( SCHEMAS ) ) {
247+ expect ( map [ name ] , name ) . toMatchObject ( { allowRead : true , allowEdit : true , allowCreate : false , allowDelete : false } ) ;
248+ expect ( map [ name ] , name ) . not . toHaveProperty ( 'allowTransfer' ) ;
249+ }
250+ } ) ;
251+
252+ it ( 'a set that names the object contributes its explicit entry, never its own wildcard\'s grants' , ( ) => {
253+ const map : any = buildEffectiveObjectPermissions (
254+ [ { objects : { '*' : { viewAllRecords : true , allowTransfer : true } , crm_account : { allowRead : true } } } ] ,
255+ source ,
256+ ) ;
257+ expect ( map . crm_account ) . not . toHaveProperty ( 'allowTransfer' ) ;
258+ expect ( map . crm_lead ) . toMatchObject ( { allowRead : true , allowTransfer : true } ) ;
259+ } ) ;
260+
261+ it ( 'ANOTHER set\'s super-user wildcard widens a present entry, bit by bit' , ( ) => {
262+ const map : any = buildEffectiveObjectPermissions (
263+ [
264+ { objects : { crm_account : { allowRead : true } } } ,
265+ { objects : { '*' : { viewAllRecords : true , allowTransfer : true , allowExport : true } } } ,
266+ ] ,
267+ source ,
268+ ) ;
269+ // Unrestricted and export-allowed, so annotate has nothing to add to it.
270+ expect ( map . crm_account ) . toEqual ( { allowRead : true , allowTransfer : true , allowExport : true } ) ;
271+ } ) ;
272+
273+ it ( 'a super-user wildcard carrying `allowExport` seeds every registered object — annotate keeps its own skip' , ( ) => {
274+ const map : any = buildEffectiveObjectPermissions (
275+ [ { objects : { '*' : { allowRead : true , allowEdit : true , modifyAllRecords : true , allowExport : true } } } ] ,
276+ source ,
277+ ) ;
278+ expect ( Object . keys ( map ) ) . toEqual ( [ '*' , 'crm_account' , 'crm_lead' , 'crm_secret' ] ) ;
279+ expect ( map . crm_account ) . toMatchObject ( { allowRead : true , allowEdit : true , allowTransfer : true , allowExport : true } ) ;
280+ // An unrestricted object whose export stays allowed: an entry, and no operation set on it.
281+ expect ( map . crm_account ) . not . toHaveProperty ( 'apiOperations' ) ;
282+ // A narrowed one is annotated exactly as before, `export` kept.
283+ expect ( map . crm_lead . apiOperations ) . toEqual ( [ 'get' , 'list' , 'aggregate' , 'search' , 'export' ] ) ;
284+ } ) ;
285+ } ) ;
0 commit comments