Skip to content

Commit

Permalink
Merge pull request #6010 from usu/chore/voter-camp-null
Browse files Browse the repository at this point in the history
chore(api): voters deny access when camp is null
  • Loading branch information
pmattmann authored Sep 27, 2024
2 parents 08a4e3c + e525555 commit c3ab369
Show file tree
Hide file tree
Showing 23 changed files with 25 additions and 47 deletions.
2 changes: 1 addition & 1 deletion api/src/Entity/Activity.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
validationContext: ['groups' => ['Default', 'create']],
denormalizationContext: ['groups' => ['write', 'create']],
normalizationContext: self::ITEM_NORMALIZATION_CONTEXT,
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.category === null'
),
],
denormalizationContext: ['groups' => ['write']],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ActivityProgressLabel.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
validationContext: ['groups' => ['Default', 'create']],
denormalizationContext: ['groups' => ['write', 'create']],
normalizationContext: self::ITEM_NORMALIZATION_CONTEXT,
securityPostDenormalize: 'is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MANAGER", object) or object.camp === null'
),
],
denormalizationContext: ['groups' => ['write']],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ActivityResponsible.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
security: 'is_authenticated()'
),
new Post(
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.activity === null'
), ],
denormalizationContext: ['groups' => ['write']],
normalizationContext: ['groups' => ['read']]
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/CampCollaboration.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
denormalizationContext: ['groups' => ['write', 'create']],
normalizationContext: self::ITEM_NORMALIZATION_CONTEXT,
openapi: new OpenApiOperation(description: 'Also sends an invitation email to the inviteEmail address, if specified.'),
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.camp === null'
),
],
denormalizationContext: ['groups' => ['write']],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
processor: CategoryCreateProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
normalizationContext: self::ITEM_NORMALIZATION_CONTEXT,
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.camp === null'
),
new GetCollection(
uriTemplate: self::CAMP_SUBRESOURCE_URI_TEMPLATE,
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/Checklist.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
new Post(
processor: ChecklistCreateProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.camp === null'
),
new GetCollection(
uriTemplate: self::CAMP_SUBRESOURCE_URI_TEMPLATE,
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ChecklistItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
),
new Post(
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.checklist === null'
),
new GetCollection(
uriTemplate: self::CHECKLIST_SUBRESOURCE_URI_TEMPLATE,
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ContentNode/ChecklistNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
new Post(
processor: ChecklistNodePersistProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.parent === null',
validationContext: ['groups' => ['Default', 'create']],
),
],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ContentNode/ColumnLayout.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
new Post(
processor: ContentNodePersistProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.parent === null',
validationContext: ['groups' => ['Default', 'create']]
),
],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ContentNode/MaterialNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
new Post(
processor: ContentNodePersistProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.parent === null',
validationContext: ['groups' => ['Default', 'create']]
),
],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ContentNode/MultiSelect.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
new Post(
processor: MultiSelectCreateProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.parent === null',
validationContext: ['groups' => ['Default', 'create']]
),
],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ContentNode/ResponsiveLayout.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
new Post(
processor: ContentNodePersistProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.parent === null',
validationContext: ['groups' => ['Default', 'create']]
),
],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ContentNode/SingleText.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
new Post(
processor: SingleTextPersistProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.parent === null',
validationContext: ['groups' => ['Default', 'create']]
),
],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ContentNode/Storyboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
new Post(
processor: StoryboardPersistProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.parent === null',
validationContext: ['groups' => ['Default', 'create']]
),
],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/DayResponsible.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
],
),
new Post(
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.day === null'
),
],
denormalizationContext: ['groups' => ['write']],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/MaterialItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
security: 'is_authenticated()'
),
new Post(
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.materialList === null'
),
],
denormalizationContext: ['groups' => ['write']],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/MaterialList.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
),
new Post(
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.camp === null'
),
],
denormalizationContext: ['groups' => ['write']],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/Period.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
new Post(
processor: PeriodPersistProcessor::class,
denormalizationContext: ['groups' => ['write', 'create']],
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.camp === null'
),
],
denormalizationContext: ['groups' => ['write']],
Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ScheduleEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
new Post(
denormalizationContext: ['groups' => ['write', 'create']],
normalizationContext: self::ITEM_NORMALIZATION_CONTEXT,
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
securityPostDenormalize: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object) or object.activity === null',
validationContext: ['groups' => ScheduleEntryPostGroupSequence::class]
),
],
Expand Down
6 changes: 1 addition & 5 deletions api/src/Security/Voter/CampIsPrototypeVoter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use App\Entity\BelongsToCampInterface;
use App\Entity\BelongsToContentNodeTreeInterface;
use App\Entity\Camp;
use App\HttpCache\ResponseTagger;
use App\Util\GetCampFromContentNodeTrait;
use Doctrine\ORM\EntityManagerInterface;
Expand All @@ -31,10 +30,7 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter
$camp = $this->getCampFromInterface($subject, $this->em);

if (null === $camp) {
// Allow access when camp is null.
// In write operations, this should be handled by validation.
// Therefore, in read operations this should never happen.
return true;
return false;
}

if ($camp->isPrototype) {
Expand Down
6 changes: 1 addition & 5 deletions api/src/Security/Voter/CampRoleVoter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use App\Entity\BelongsToCampInterface;
use App\Entity\BelongsToContentNodeTreeInterface;
use App\Entity\Camp;
use App\Entity\CampCollaboration;
use App\Entity\User;
use App\HttpCache\ResponseTagger;
Expand Down Expand Up @@ -45,10 +44,7 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter
$camp = $this->getCampFromInterface($subject, $this->em);

if (null === $camp) {
// Allow access when camp is null.
// In write operations, this should be handled by validation.
// Therefore, in read operations this should never happen.
return true;
return false;
}

$campCollaboration = $camp->collaborations
Expand Down
11 changes: 2 additions & 9 deletions api/tests/Security/Voter/CampIsPrototypeVoterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,7 @@ public function testDoesntVoteWhenSubjectIsNull() {
$this->assertEquals(VoterInterface::ACCESS_ABSTAIN, $result);
}

/**
* When the camp associated with an entity is null, this isn't a security question,
* but rather should have been caught by validation rules.
* If the association with a camp really is optional for some entity, the security
* rules can easily add a check manually:
* is_granted("CAMP_COLLABORATOR", object) and null != object.getCamp().
*/
public function testGrantsAccessWhenGetCampYieldsNull() {
public function testDeniesAccessWhenGetCampYieldsNull() {
// given
$this->token->method('getUser')->willReturn(new User());
$subject = $this->createMock(Period::class);
Expand All @@ -81,7 +74,7 @@ public function testGrantsAccessWhenGetCampYieldsNull() {
$result = $this->voter->vote($this->token, $subject, ['CAMP_IS_PROTOTYPE']);

// then
$this->assertEquals(VoterInterface::ACCESS_GRANTED, $result);
$this->assertEquals(VoterInterface::ACCESS_DENIED, $result);
}

public function testDeniesAccessWhenCampIsntPrototype() {
Expand Down
11 changes: 2 additions & 9 deletions api/tests/Security/Voter/CampRoleVoterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,7 @@ public function testDeniesAccessWhenNotLoggedIn() {
$this->assertEquals(VoterInterface::ACCESS_DENIED, $result);
}

/**
* When the camp associated with an entity is null, this isn't a security question,
* but rather should have been caught by validation rules.
* If the association with a camp really is optional for some entity, the security
* rules can easily add a check manually:
* is_granted("CAMP_COLLABORATOR", object) and null != object.getCamp().
*/
public function testGrantsAccessWhenGetCampYieldsNull() {
public function testDeniesAccessWhenGetCampYieldsNull() {
// given
$this->token->method('getUser')->willReturn(new User());
$subject = $this->createMock(Period::class);
Expand All @@ -93,7 +86,7 @@ public function testGrantsAccessWhenGetCampYieldsNull() {
$result = $this->voter->vote($this->token, $subject, ['CAMP_COLLABORATOR']);

// then
$this->assertEquals(VoterInterface::ACCESS_GRANTED, $result);
$this->assertEquals(VoterInterface::ACCESS_DENIED, $result);
}

public function testDeniesAccessWhenGetCampYieldsNullAndNotLoggedIn() {
Expand Down

0 comments on commit c3ab369

Please sign in to comment.