-
Notifications
You must be signed in to change notification settings - Fork 4
feat(integration-registry): 5 built-in IntegrationProvider implementations (umbrella PR 3/N) #1469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [BLOCKER] AuditTrailProvider::list() has no user-scope filter — exposes all objects' audit trail
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [CONCERN] AuditTrailProvider::list() uses method_exists() duck-typing instead of a stable mapper interface The provider checks |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * AuditTrailProvider — exposes audit-trail entries for an OR object | ||
| * via the IntegrationProvider contract. | ||
| * | ||
| * Storage strategy is `query-time` (AD-22): audit-trail entries are | ||
| * OR's own data; the provider always reads live from `AuditTrailMapper` | ||
| * rather than maintaining a parallel link table. Mutation methods | ||
| * throw NotImplementedException — audit-trail entries are immutable | ||
| * by construction. | ||
| * | ||
| * Always available — no required NC app — so `requiredApp` returns | ||
| * null and `isEnabled()` is hardcoded true. | ||
| * | ||
| * @category Service | ||
| * @package OCA\OpenRegister\Service\Integration\BuiltinProviders | ||
| * | ||
| * @author Conduction Development Team <info@conduction.nl> | ||
| * @copyright 2026 Conduction B.V. | ||
| * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 | ||
| * | ||
| * SPDX-License-Identifier: EUPL-1.2 | ||
| * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> | ||
| * | ||
| * @link https://conduction.nl | ||
| * | ||
| * @spec openspec/changes/pluggable-integration-registry/tasks.md#task-16 | ||
| */ | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace OCA\OpenRegister\Service\Integration\BuiltinProviders; | ||
|
|
||
| use OCA\OpenRegister\Db\AuditTrailMapper; | ||
| use OCA\OpenRegister\Service\Integration\AbstractIntegrationProvider; | ||
| use OCP\IL10N; | ||
|
|
||
| /** | ||
| * Audit-trail integration provider (read-only, query-time). | ||
| */ | ||
| class AuditTrailProvider extends AbstractIntegrationProvider | ||
| { | ||
|
|
||
| /** | ||
| * Constructor. | ||
| * | ||
| * @param AuditTrailMapper $mapper Audit-trail mapper. | ||
| * @param IL10N $l10n Localisation. | ||
| * | ||
| * @return void | ||
| */ | ||
| public function __construct( | ||
| private AuditTrailMapper $mapper, | ||
| private IL10N $l10n, | ||
| ) { | ||
| }//end __construct() | ||
|
|
||
| public function getId(): string | ||
| { | ||
| return 'audit-trail'; | ||
| }//end getId() | ||
|
|
||
| public function getLabel(): string | ||
| { | ||
| return $this->l10n->t('Audit trail'); | ||
| }//end getLabel() | ||
|
|
||
| public function getIcon(): string | ||
| { | ||
| return 'History'; | ||
| }//end getIcon() | ||
|
|
||
| public function getGroup(): ?string | ||
| { | ||
| return 'core'; | ||
| }//end getGroup() | ||
|
|
||
| public function getRequiredApp(): ?string | ||
| { | ||
| return null; | ||
| }//end getRequiredApp() | ||
|
|
||
| public function getStorageStrategy(): string | ||
| { | ||
| return 'query-time'; | ||
| }//end getStorageStrategy() | ||
|
|
||
| public function isEnabled(): bool | ||
| { | ||
| return true; | ||
| }//end isEnabled() | ||
|
|
||
| /** | ||
| * List audit-trail entries for an OR object. | ||
| * | ||
| * Best-effort delegation to `AuditTrailMapper::findAllByObject` / | ||
| * `findAll` depending on the mapper's exposed API. Returns an | ||
| * empty list rather than 500 when the mapper signature doesn't | ||
| * match — the umbrella's controller refactor (tasks 18-22) will | ||
| * tighten this. | ||
| * | ||
| * @param string $register Register slug or numeric id. | ||
| * @param string $schema Schema slug or numeric id. | ||
| * @param string $objectId Object uuid. | ||
| * @param array<string,mixed> $filters Reserved. | ||
| * | ||
| * @return array<int,array<string,mixed>> | ||
| */ | ||
| public function list(string $register, string $schema, string $objectId, array $filters = []): array | ||
| { | ||
| try { | ||
| if (method_exists($this->mapper, 'findAllByObject') === true) { | ||
| $entries = $this->mapper->findAllByObject($objectId); | ||
| return $this->normalize($entries); | ||
| } | ||
|
|
||
| if (method_exists($this->mapper, 'findAll') === true) { | ||
| $entries = $this->mapper->findAll(filters: ['object' => $objectId]); | ||
| return $this->normalize($entries); | ||
| } | ||
| } catch (\Throwable $e) { | ||
| // AuditTrail history is a soft surface — never block the | ||
| // detail page on a stale or missing audit row. | ||
| } | ||
|
|
||
| return []; | ||
| }//end list() | ||
|
|
||
| /** | ||
| * Convert mapper output (Entity[] or array[]) into the shape | ||
| * IntegrationProvider::list() promises. | ||
| * | ||
| * @param mixed $entries Mapper output. | ||
| * | ||
| * @return array<int, array<string, mixed>> | ||
| */ | ||
| private function normalize($entries): array | ||
| { | ||
| if (is_array($entries) === false) { | ||
| return []; | ||
| } | ||
|
|
||
| $rows = []; | ||
| foreach ($entries as $entry) { | ||
| if (is_object($entry) === true && method_exists($entry, 'jsonSerialize') === true) { | ||
| $serialised = $entry->jsonSerialize(); | ||
| $rows[] = is_array($serialised) === true ? $serialised : ['value' => $serialised]; | ||
| continue; | ||
| } | ||
|
|
||
| if (is_array($entry) === true) { | ||
| $rows[] = $entry; | ||
| } | ||
| } | ||
|
|
||
| return $rows; | ||
| }//end normalize() | ||
|
|
||
| }//end class | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[BLOCKER] Bare catch(\Throwable $e){} silently swallows provider construction failures (Gate 7)
The inner loop catches every
\Throwablewith an empty body and no logging. A misconfigured or maliciously broken provider will fail invisibly, making debugging impossible and masking security-relevant injection errors. At minimum log atwarninglevel with$e->getMessage()and the provider class name. The outer catch at line 123 has the same issue.