Conversation
5fbb9f6 to
0b493aa
Compare
|
| <error>Before you continue:</error> | ||
| - Make sure to back up your database. | ||
| - Take the installation offline. The database should not be modified while the script is being executed. | ||
| - Run this command in production environment using <info>--env=prod</info> | ||
| EOT; |
| self::DEFAULT_REPOSITORY_USER | ||
| ) | ||
| ->setHelp( | ||
| <<<EOT |
| ) { | ||
| $output->writeln('<comment>Nothing was changed. Use --force to skip this question.</comment>'); | ||
|
|
||
| return 0; |
There was a problem hiding this comment.
| return 0; | |
| return Command::SUCCESS; |
| $output->writeln(sprintf('<info>Role "%s" (%d) updated.</info>', $role->identifier, $roleId)); | ||
| } | ||
|
|
||
| return $failed ? 1 : 0; |
There was a problem hiding this comment.
| return $failed ? 1 : 0; | |
| return $failed ? Command::FAILURE : Command::SUCCESS; |
| private const MISSING_ENTITY_MESSAGE = "limitationValues[%key%] => '%value%' does not exist in the backend"; | ||
|
|
||
| /** @var \Ibexa\Contracts\Core\Repository\RoleService&\PHPUnit\Framework\MockObject\MockObject */ | ||
| private $roleService; |
There was a problem hiding this comment.
| private $roleService; | |
| private RoleService $roleService; |
| */ | ||
| private function buildRepository(): MockObject | ||
| { | ||
| $userService = $this->createMock(UserService::class); |
There was a problem hiding this comment.
Shouldn't this be a Stub instead? See https://github.com/ibexa/internal-ai/pull/3.
| /** | ||
| * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception | ||
| */ | ||
| public function testCopyRoleWithLimitationValueOfDeletedContentType(): void |
There was a problem hiding this comment.
I presume this one fails before introducing your changes, right?
| $roleCopyStruct->addPolicy($policyCreateStruct); | ||
|
|
||
| try { | ||
| $roleService->copyRole( |
There was a problem hiding this comment.
Wouldn't it be more readable to have ->expectException here?
| /** | ||
| * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception | ||
| */ | ||
| private function createContentType(): int |
There was a problem hiding this comment.
Don't we have a helper in the testing framework somewhere doing exactly that?
| /** | ||
| * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception | ||
| */ | ||
| private function createRoleWithContentTypeLimitation(int $contentTypeId): Role |
|
|
||
| $danglingValues = []; | ||
|
|
||
| if ($this->isListOfValues($limitationValues)) { |
There was a problem hiding this comment.
Could we only auto-fix Limitation types that have an explicit repair rule? Limitation types can be added by extensions. This command currently decides how to remove values only from the array shape and the validation message. It does not know what an empty list means for a custom Limitation type. For example, an empty list may mean "allow everything" for one type, but "allow nothing" for another. Running --fix could therefore change permissions unexpectedly. Could we report unknown or custom types and leave them for manual repair instead?
| { | ||
| // Emptying "roles" would make UserPermissionsLimitationType::evaluate() grant every Role, | ||
| // and storage cannot tell an emptied value list from one that was never restricted. | ||
| $limitation = new ContentTypeLimitation(['limitationValues' => ['roles' => [15], 'user_groups' => [11]]]); |
There was a problem hiding this comment.
Could we cover this with the real UserPermissionsLimitation and its Limitation Type? This test puts UserPermissions-shaped values into ContentTypeLimitation and uses a generic mock, so it does not verify the real acceptance and permission semantics that make --fix security-sensitive.
| * | ||
| * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException | ||
| */ | ||
| private function applyToDraft(RoleDraft $roleDraft, array $findingsByPolicyId, Role $role, int $roleId): void |
There was a problem hiding this comment.
$roleId here duplicates $role->id. $role is loaded from that same $roleId right above, in line 241, so they're always equal. Could we drop the $roleId parameter from applyToDraft() and use $role->id at its one use site? Same redundancy shows up in fix() itself (lines 252, 262, 267, 287, 295), where $roleId (the loop key) and $role->id are used interchangeably for the same value. Picking one consistently would remove the duplication.
| * | ||
| * @return \Ibexa\Contracts\Core\Repository\Values\User\Limitation[] | ||
| */ | ||
| private function pruneLimitations(Policy $policyDraft, array $findings): array |
There was a problem hiding this comment.
pruneLimitations() types its parameter as Policy $policyDraft, but the only caller always passes an actual PolicyDraft. Could we tighten the type to PolicyDraft so the signature matches what it's actually called with, instead of the wider parent type paired with a "draft" name?
| ); | ||
| } | ||
|
|
||
| protected function execute(InputInterface $input, OutputInterface $output): int |
There was a problem hiding this comment.
execute() does five different things inline: scan all roles, print skipped items, print "nothing found" short-circuit, render the findings table, and drive the confirm and fix flow. Could we extract the scanning loop building $findings and $skipped into a private scan(): array returning both lists? This would shorten execute() to orchestration only and make the scan logic independently testable and readable, without the CLI plumbing around it.
| /** | ||
| * @internal | ||
| */ | ||
| final class DanglingLimitationValues |
There was a problem hiding this comment.
Every call site names variables of this type $finding or $findings, never $danglingLimitationValues. This is really a per-Limitation finding not just a values holder. Also, the file already uses two different words for the same idea: "dangling" in class and method names, but "missing" in MISSING_ENTITY_SUFFIX and "no longer resolve" in the command's own description. Could we settle on one, e.g. UnresolvedLimitationFinding (matches the command's own wording), instead of adding "dangling" as a third term?
| @@ -49,8 +49,12 @@ public function acceptValue(APILimitationValue $limitationValue): void | |||
| } | |||
|
|
|||
| foreach ($limitationValue->limitationValues as $key => $id) { | |||
There was a problem hiding this comment.
This block is duplicated almost verbatim in RoleLimitationType::acceptValue() (differs only in the regex allowing a leading -). Both classes already extend AbstractPersistenceLimitationType. Could we move this into a shared protected helper there, e.g. normalizeIntId($id, string $key, bool $allowNegative = false), and call it from both acceptValue() methods?



Description:
RoleService::copyRole()rebuilds aRoleCreateStructfrom the existing Role's persistedPolicies and runs create-time validation over it, so a Role the Repository loads without complaint
cannot be copied once a Limitation value names a deleted entity.
deleteContentType()leaves suchvalues behind.
Node,Subtree,Section,ObjectState,RoleandMemberOflimitations dangle thesame way.
copyRole()already documents validation of "a policy limitation in the
$roleCopyStruct" — the caller'sstruct, not the source Role. Moved the validation above the loop that appends the copied Role's
Policies. Values that no longer resolve are copied rather than pruned: an empty Limitation array
becomes
'*'inRoleDomainMapper::buildPersistencePolicyObject(), so dropping the last value ofa Limitation would widen the Policy instead of narrowing it.
PR also contains a
ibexa:roles:cleanup-limitation-valuescommand that repairs existing installations:--fixtries to fix (remove) dangling valuesUserPermissionslimitations with two-dimensional lists are covered tooleave "roles" [], which grants every Role, while removing the Policy would take away the
grant "user_groups" [11] still carries
stands
Two unrelated defects are fixed alongside,
getLimitationErrors()returnednullon 4.6 (fixed on 5.0 by 72cf197, never backported)MemberOfLimitationTypeandRoleLimitationTyperejected the string values storage hands them.For QA:
See ticket for how to reproduce.
The command reports the same values, and is the quickest way to find a Role to test against:
Documentation:
New maintenance command
ibexa:roles:cleanup-limitation-values— reports Policy Limitation valuesthat no longer resolve to an existing entity, and removes them under
--fix(--forceskips theconfirmation). Worth documenting that a Policy left with no Limitation values is removed rather than
emptied, and that a
UserPermissionsLimitation is left to a human whenever repairing it wouldchange what the Policy grants, since that limitation reads an empty list as no restriction on that
dimension.