diff --git a/lib/Settings/register.d/catalog-ratings.json b/lib/Settings/register.d/catalog-ratings.json index 503be2213..af8d4f975 100644 --- a/lib/Settings/register.d/catalog-ratings.json +++ b/lib/Settings/register.d/catalog-ratings.json @@ -46,7 +46,8 @@ "group": "public", "match": { "status": "approved" - } + }, + "crossOrganisation": true }, "aanbod-beheerder", "ambtenaar", diff --git a/lib/Settings/register.d/publication-field-rules.json b/lib/Settings/register.d/publication-field-rules.json index bfd6ae997..79d600442 100644 --- a/lib/Settings/register.d/publication-field-rules.json +++ b/lib/Settings/register.d/publication-field-rules.json @@ -115,13 +115,15 @@ "modulePublicationDate": { "$lte": "$now" } - } + }, + "crossOrganisation": true }, { "group": "public", "match": { "moduleRegisteredBy": "Supplier" - } + }, + "crossOrganisation": true } ] } @@ -451,7 +453,8 @@ "group": "gebruik-beheerder", "match": { "consumer": "$organisation" - } + }, + "crossOrganisation": true }, { "group": "aanbod-beheerder", @@ -463,7 +466,8 @@ "group": "aanbod-beheerder", "match": { "provider": "$organisation" - } + }, + "crossOrganisation": true }, { "group": "public", @@ -471,7 +475,8 @@ "publicationDate": { "$lte": "$now" } - } + }, + "crossOrganisation": true } ] } diff --git a/lib/Settings/softwarecatalogus_register.json b/lib/Settings/softwarecatalogus_register.json index bb6b24de4..7f1fad256 100644 --- a/lib/Settings/softwarecatalogus_register.json +++ b/lib/Settings/softwarecatalogus_register.json @@ -1091,7 +1091,7 @@ "aanbod-beheerder" ], "read": [ - "public", + {"group": "public", "crossOrganisation": true}, "aanbod-beheerder", "ambtenaar", "functioneel-beheerder", @@ -1270,7 +1270,7 @@ "aanbod-beheerder" ], "read": [ - "public", + {"group": "public", "crossOrganisation": true}, "aanbod-beheerder", "ambtenaar", "functioneel-beheerder", @@ -1559,7 +1559,8 @@ "publicationDate": { "$lte": "$now" } - } + }, + "crossOrganisation": true }, "aanbod-beheerder", "ambtenaar", @@ -1735,7 +1736,7 @@ "aanbod-beheerder" ], "read": [ - "public", + {"group": "public", "crossOrganisation": true}, "aanbod-beheerder", "ambtenaar", "functioneel-beheerder", @@ -2427,7 +2428,8 @@ "publicationDate": { "$lte": "$now" } - } + }, + "crossOrganisation": true }, { "group": "aanbod-beheerder", @@ -2440,21 +2442,24 @@ "match": { "registeredBy": "Supplier", "status": "Active" - } + }, + "crossOrganisation": true }, { "group": "public", "match": { "type": "Municipality", "status": "Active" - } + }, + "crossOrganisation": true }, { "group": "public", "match": { "type": "Collaboration", "status": "Active" - } + }, + "crossOrganisation": true } ], "update": [ @@ -3054,7 +3059,8 @@ "group": "gebruik-beheerder", "match": { "consumer": "$organisation" - } + }, + "crossOrganisation": true }, { "group": "aanbod-beheerder", @@ -3066,7 +3072,8 @@ "group": "aanbod-beheerder", "match": { "provider": "$organisation" - } + }, + "crossOrganisation": true } ], "update": [ @@ -3379,8 +3386,8 @@ "aanbod-beheerder" ], "read": [ - "ambtenaar", - "software-catalog-admins", + {"group": "ambtenaar", "crossOrganisation": true}, + {"group": "software-catalog-admins", "crossOrganisation": true}, {"group": "functioneel-beheerder", "match": {"_organisation": "$organisation"}}, {"group": "gebruik-beheerder", "match": {"_organisation": "$organisation"}}, {"group": "vng-raadpleger", "match": {"_organisation": "$organisation"}}, @@ -3772,7 +3779,8 @@ "publicationDate": { "$lte": "$now" } - } + }, + "crossOrganisation": true }, { "group": "aanbod-beheerder", @@ -3784,7 +3792,8 @@ "group": "aanbod-beheerder", "match": { "provider": "$organisation" - } + }, + "crossOrganisation": true } ], "update": [ @@ -4011,7 +4020,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -5135,7 +5144,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -5458,7 +5467,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -5816,7 +5825,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -5950,7 +5959,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -6353,7 +6362,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -6886,7 +6895,8 @@ "publicationDate": { "$lte": "$now" } - } + }, + "crossOrganisation": true }, { "group": "aanbod-beheerder", @@ -6898,7 +6908,8 @@ "group": "public", "match": { "registeredBy": "Supplier" - } + }, + "crossOrganisation": true } ] }, @@ -7080,7 +7091,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -7210,7 +7221,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -7450,7 +7461,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -7619,7 +7630,7 @@ "groups": null, "authorization": { "read": [ - "public" + {"group": "public", "crossOrganisation": true} ] }, "configuration": { @@ -7848,7 +7859,7 @@ "aanbod-beheerder" ], "read": [ - "software-catalog-admins", + {"group": "software-catalog-admins", "crossOrganisation": true}, { "group": "organisatie-beheerder", "match": { @@ -7883,7 +7894,8 @@ "group": "aanbod-beheerder", "match": { "provider": "$organisation" - } + }, + "crossOrganisation": true } ], "update": [ @@ -8197,7 +8209,7 @@ "aanbod-beheerder" ], "read": [ - "public" + {"group": "public", "crossOrganisation": true} ], "update": [ "software-catalog-admins", diff --git a/tests/Unit/Service/BioComplianceRegisterShapeTest.php b/tests/Unit/Service/BioComplianceRegisterShapeTest.php index 676deda26..c7a950a02 100644 --- a/tests/Unit/Service/BioComplianceRegisterShapeTest.php +++ b/tests/Unit/Service/BioComplianceRegisterShapeTest.php @@ -80,7 +80,7 @@ public function testBioMaatregelSchemaExists(): void { } $this->assertSame(expected: ['BBN1', 'BBN2', 'BBN3'], actual: $props['bbnLevel']['items']['enum'] ?? null); - $this->assertContains(needle: 'public', haystack: $bioMeasure['authorization']['read'] ?? []); + $this->assertContains(needle: ['group' => 'public', 'crossOrganisation' => true], haystack: $bioMeasure['authorization']['read'] ?? []); }//end testBioMaatregelSchemaExists() /** diff --git a/tests/Unit/Service/ReviewModerationOverlayReachesTheSchemaTest.php b/tests/Unit/Service/ReviewModerationOverlayReachesTheSchemaTest.php index ad35a0cdb..598e5d122 100644 --- a/tests/Unit/Service/ReviewModerationOverlayReachesTheSchemaTest.php +++ b/tests/Unit/Service/ReviewModerationOverlayReachesTheSchemaTest.php @@ -140,8 +140,13 @@ public function testTheReviewSchemaPublicReadIsGatedOnApproved(): void { $read, 'a bare public read makes every pending and rejected review world-readable' ); + $this->assertNotContains( + ['group' => 'public', 'crossOrganisation' => true], + $read, + 'an unconditional public read makes every pending and rejected review world-readable' + ); $this->assertContains( - ['group' => 'public', 'match' => ['status' => 'approved']], + ['group' => 'public', 'match' => ['status' => 'approved'], 'crossOrganisation' => true], $read, 'anonymous readers must see approved reviews and only those' ); @@ -165,9 +170,9 @@ public function testTheBaseRegisterIsUngatedWithoutTheFragment(): void { $read = $base['components']['schemas'][ReviewService::REVIEW_TYPE]['authorization']['read']; $this->assertContains( - 'public', + ['group' => 'public', 'crossOrganisation' => true], $read, - 'the base is expected to carry the bare public read that the fragment removes; ' + 'the base is expected to carry the unconditional public read that the fragment removes; ' .'if the base has been gated directly, this test and the fragment are now redundant' ); diff --git a/tests/Unit/Settings/AiSystemFragmentTest.php b/tests/Unit/Settings/AiSystemFragmentTest.php index 3c5844c4c..555b1969d 100644 --- a/tests/Unit/Settings/AiSystemFragmentTest.php +++ b/tests/Unit/Settings/AiSystemFragmentTest.php @@ -155,10 +155,12 @@ public function testReadsAreScopedToTheOrganisation(): void { $read = $this->schema()['authorization']['read']; $this->assertContains(['group' => 'gebruik-beheerder', 'match' => ['_organisation' => '$organisation']], $read); - $this->assertContains(['group' => 'aanbod-beheerder', 'match' => ['provider' => '$organisation']], $read); + $this->assertContains(['group' => 'aanbod-beheerder', 'match' => ['provider' => '$organisation'], 'crossOrganisation' => true], $read); + $this->assertContains(['group' => 'software-catalog-admins', 'crossOrganisation' => true], $read); foreach ($read as $rule) { - if (is_string($rule) === true) { - $this->assertSame('software-catalog-admins', $rule, 'only the catalogue admins read every AI system'); + $this->assertIsArray($rule, 'a bare group rule reads only the own organisation'); + if (($rule['crossOrganisation'] ?? false) === true && isset($rule['match']) === false) { + $this->assertSame('software-catalog-admins', $rule['group'], 'only the catalogue admins read every AI system'); } } diff --git a/tests/Unit/Settings/ContractRbacTest.php b/tests/Unit/Settings/ContractRbacTest.php index d484f9f76..46813fa62 100644 --- a/tests/Unit/Settings/ContractRbacTest.php +++ b/tests/Unit/Settings/ContractRbacTest.php @@ -128,7 +128,7 @@ static function ($entry) { }//end testAanbodBeheerderReadIsOrganisationScoped() /** - * TC-14 (config-shape form): `ambtenaar` retains an unrestricted read + * TC-14 (config-shape form): `ambtenaar` retains a cross-organisation read * grant — admin/ambtenaar MUST retain read access regardless of * counterparty status. * @@ -137,7 +137,8 @@ static function ($entry) { public function testAmbtenaarRetainsUnrestrictedRead(): void { $authorization = $this->loadContractAuthorization(); - $this->assertContains('ambtenaar', $authorization['read']); + $this->assertContains(['group' => 'ambtenaar', 'crossOrganisation' => true], $authorization['read']); + $this->assertNotContains('ambtenaar', $authorization['read'], 'a bare group rule reads only the own organisation'); }//end testAmbtenaarRetainsUnrestrictedRead() @@ -152,7 +153,8 @@ public function testAmbtenaarRetainsUnrestrictedRead(): void { public function testStackiqAdminsRetainsUnrestrictedRead(): void { $authorization = $this->loadContractAuthorization(); - $this->assertContains('software-catalog-admins', $authorization['read']); + $this->assertContains(['group' => 'software-catalog-admins', 'crossOrganisation' => true], $authorization['read']); + $this->assertNotContains('software-catalog-admins', $authorization['read'], 'a bare group rule reads only the own organisation'); }//end testStackiqAdminsRetainsUnrestrictedRead() diff --git a/tests/Unit/Settings/MaintenanceRoadmapFragmentTest.php b/tests/Unit/Settings/MaintenanceRoadmapFragmentTest.php index ec6d971ce..46be5c17d 100644 --- a/tests/Unit/Settings/MaintenanceRoadmapFragmentTest.php +++ b/tests/Unit/Settings/MaintenanceRoadmapFragmentTest.php @@ -145,7 +145,7 @@ public function testOnlyTheSupplierAnnouncesMaintenance(): void { $auth = $this->window()['authorization']; $this->assertSame(['software-catalog-admins', 'aanbod-beheerder'], $auth['create']); - $this->assertSame(['public'], $auth['read']); + $this->assertSame([['group' => 'public', 'crossOrganisation' => true]], $auth['read']); foreach (['update', 'delete'] as $action) { $this->assertSame( ['software-catalog-admins', ['group' => 'aanbod-beheerder', 'match' => ['_organisation' => '$organisation']]], diff --git a/tests/Unit/Settings/PublicationFieldRulesTest.php b/tests/Unit/Settings/PublicationFieldRulesTest.php index b1f9c052d..e2d43deae 100644 --- a/tests/Unit/Settings/PublicationFieldRulesTest.php +++ b/tests/Unit/Settings/PublicationFieldRulesTest.php @@ -132,7 +132,7 @@ public function testAUsageIsPublicFromItsPublicationDateAndOrganisationsKeepRead $this->assertContains($rule, $after, 'an organisation read rule survived: ' . json_encode($rule)); } - $this->assertContains(['group' => 'public', 'match' => ['publicationDate' => ['$lte' => '$now']]], $after); + $this->assertContains(['group' => 'public', 'match' => ['publicationDate' => ['$lte' => '$now']], 'crossOrganisation' => true], $after); $this->assertCount(count($before) + 1, $after); }//end testAUsageIsPublicFromItsPublicationDateAndOrganisationsKeepReading() @@ -146,13 +146,13 @@ public function testAVersionFollowsItsApplication(): void { $module = $schemas['module']['authorization']['read']; $version = $schemas['moduleVersion']['authorization']['read']; - $this->assertContains(['group' => 'public', 'match' => ['publicationDate' => ['$lte' => '$now']]], $module); - $this->assertContains(['group' => 'public', 'match' => ['registeredBy' => 'Supplier']], $module); + $this->assertContains(['group' => 'public', 'match' => ['publicationDate' => ['$lte' => '$now']], 'crossOrganisation' => true], $module); + $this->assertContains(['group' => 'public', 'match' => ['registeredBy' => 'Supplier'], 'crossOrganisation' => true], $module); $this->assertSame( [ 'authenticated', - ['group' => 'public', 'match' => ['modulePublicationDate' => ['$lte' => '$now']]], - ['group' => 'public', 'match' => ['moduleRegisteredBy' => 'Supplier']], + ['group' => 'public', 'match' => ['modulePublicationDate' => ['$lte' => '$now']], 'crossOrganisation' => true], + ['group' => 'public', 'match' => ['moduleRegisteredBy' => 'Supplier'], 'crossOrganisation' => true], ], $version );