Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 46 additions & 15 deletions tests/Unit/Settings/CmdbPersonDataVisibilityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
* The import links the application owner as a `contactPerson` through
* `usage.businessOwner`. Anonymous visitors (OpenCatalogi search, Portaliq,
* the OpenRegister objects API) must not see that person. This test pins the
* register configuration that guarantees it: neither `usage` nor
* `contactPerson` has a public read rule in the merged register (base plus
* every register.d fragment), and a published `module` only refers to them
* by relation, so a public search hit carries at most ids. The rig check of
* register configuration that guarantees it, in the merged register (base plus
* every register.d fragment): `contactPerson` has no public read rule, a
* `usage` is public only once its publicationDate has passed and its person
* properties never are, and a published `module` only refers to them by
* relation, so a public search hit carries at most ids. The rig check of
* the running stack is the API test in
* tests/e2e/spec-coverage/cmdb-import.spec.ts.
*
Expand Down Expand Up @@ -73,22 +74,52 @@ private static function isPublic(mixed $rule): bool {
}//end isPublic()

/**
* Neither usage nor contactPerson can be read anonymously.
* A contactPerson can never be read anonymously.
*
* @return void
*/
public function testUsageAndContactPersonHaveNoPublicReadRule(): void {
$schemas = $this->mergedRegister()['components']['schemas'];

foreach (['usage', 'contactPerson'] as $schema) {
$read = ($schemas[$schema]['authorization']['read'] ?? null);
$this->assertIsArray($read, $schema . ' must have an explicit read rule; without one OpenRegister does not restrict reads');
$this->assertNotEmpty($read, $schema);
foreach ($read as $rule) {
$this->assertFalse(self::isPublic(rule: $rule), $schema . ' has a public read rule: imported owners would be readable anonymously');
public function testContactPersonHasNoPublicReadRule(): void {
$read = ($this->mergedRegister()['components']['schemas']['contactPerson']['authorization']['read'] ?? null);
$this->assertIsArray($read, 'contactPerson must have an explicit read rule; without one OpenRegister does not restrict reads');
$this->assertNotEmpty($read, 'contactPerson');
foreach ($read as $rule) {
$this->assertFalse(self::isPublic(rule: $rule), 'contactPerson has a public read rule: imported owners would be readable anonymously');
}
}//end testContactPersonHasNoPublicReadRule()

/**
* A usage is public only once published, and never with its owners.
*
* Publication-field-rules (stackiq #1206) makes a usage readable to the public
* group once its publicationDate has passed, so OpenCatalogi can show which
* applications an organisation uses. The import never sets a usage's
* publicationDate, and the properties that name a person carry their own read
* rule without the public group, which OpenRegister enforces on the body,
* relations, `@self` and facets alike.
*
* @return void
*/
public function testAUsageIsPublicOnlyWhenPublishedAndNeverWithItsOwners(): void {
$usage = $this->mergedRegister()['components']['schemas']['usage'];
$read = ($usage['authorization']['read'] ?? null);
$this->assertIsArray($read, 'usage must have an explicit read rule; without one OpenRegister does not restrict reads');
$this->assertNotEmpty($read, 'usage');
foreach ($read as $rule) {
if (self::isPublic(rule: $rule) === true) {
$this->assertIsArray($rule, 'a bare public read rule on usage would publish every usage');
$this->assertArrayHasKey('publicationDate', ($rule['match'] ?? []), 'the public read rule on usage must be conditional on publicationDate');
}
}

foreach (['businessOwner', 'technicalOwner', 'contactPerson'] as $property) {
$propertyRead = ($usage['properties'][$property]['authorization']['read'] ?? null);
$this->assertIsArray($propertyRead, 'usage.' . $property . ' names a person and needs its own read rule');
$this->assertNotEmpty($propertyRead, 'usage.' . $property);
foreach ($propertyRead as $rule) {
$this->assertFalse(self::isPublic(rule: $rule), 'usage.' . $property . ' is publicly readable: imported owners would be visible anonymously');
}
}
}//end testUsageAndContactPersonHaveNoPublicReadRule()
}//end testAUsageIsPublicOnlyWhenPublishedAndNeverWithItsOwners()

/**
* A published module refers to its contact person and usages by relation only, and holds no person field.
Expand Down
2 changes: 1 addition & 1 deletion tests/bootstrap-unit.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
'OCA\\OpenRegister\\Db\\' => __DIR__ . '/Stubs/Db/',
'OCA\\OpenRegister\\Service\\' => __DIR__ . '/Stubs/Service/',
// A copy of OpenRegister's ObjectCreatedEvent, so listener tests construct the real shape.
'OCA\\OpenRegister\\Event\\' => __DIR__ . '/Stubs/Event/',
'OCA\\OpenRegister\\Event\\' => __DIR__ . '/Stubs/OpenRegister/Event/',
];

foreach ($prefixMap as $prefix => $dir) {
Expand Down
14 changes: 14 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,17 @@ function stackiq_nc_root_is_installed(string $ncRoot): bool
}

unset($integriqStubEvent);

// OpenRegister's ObjectCreatedEvent, deferred for the same reason. It extends
// OCP\EventDispatcher\Event, and nextcloud/ocp ships no autoload section, so
// OCP only becomes resolvable once lib/base.php has run above. Loading it in
// the early tests/Stubs glob killed every CI PHPUnit leg in this bootstrap
// with `Class "OCP\EventDispatcher\Event" not found` before a single test ran.
// It sits two directories deep so that glob cannot reach it. When the real
// OpenRegister is enabled, class_exists() loads the real event and the stub
// stays out of the way.
if (class_exists('\\OCP\\EventDispatcher\\Event') === true
&& class_exists('\\OCA\\OpenRegister\\Event\\ObjectCreatedEvent') === false
) {
require_once __DIR__ . '/Stubs/OpenRegister/Event/ObjectCreatedEvent.php';
}
Loading