Restrict roles available to invitation and member.update, fixes ST-264

This commit is contained in:
Constantin Graf
2024-06-18 18:48:48 +02:00
committed by Gregor Vostrak
parent 2184b3c835
commit 313cee2db0
6 changed files with 112 additions and 8 deletions

View File

@@ -60,7 +60,7 @@ class InvitationController extends Controller
$this->user(), $this->user(),
$organization, $organization,
$request->input('email'), $request->input('email'),
$request->input('role') $request->getRole()->value
); );
return response()->json(null, 204); return response()->json(null, 204);

View File

@@ -68,7 +68,7 @@ class MemberController extends Controller
$member->billable_rate = $request->getBillableRate(); $member->billable_rate = $request->getBillableRate();
} }
if ($request->has('role')) { if ($request->has('role')) {
$member->role = $request->input('role'); $member->role = $request->getRole()->value;
} }
$member->save(); $member->save();

View File

@@ -30,9 +30,14 @@ class InvitationStoreRequest extends FormRequest
'role' => [ 'role' => [
'required', 'required',
'string', 'string',
// TODO: placeholder role should not be allowed Rule::enum(Role::class)
Rule::enum(Role::class), ->except([Role::Owner, Role::Placeholder]),
], ],
]; ];
} }
public function getRole(): Role
{
return Role::from($this->input('role'));
}
} }

View File

@@ -25,8 +25,8 @@ class MemberUpdateRequest extends FormRequest
return [ return [
'role' => [ 'role' => [
'string', 'string',
// TODO: placeholder role should not be allowed Rule::enum(Role::class)
Rule::enum(Role::class), ->except([Role::Owner, Role::Placeholder]),
], ],
'billable_rate' => [ 'billable_rate' => [
'nullable', 'nullable',
@@ -52,4 +52,9 @@ class MemberUpdateRequest extends FormRequest
return $this->has('billable_rate_update_time_entries') && return $this->has('billable_rate_update_time_entries') &&
$this->input('billable_rate_update_time_entries') === 'true'; $this->input('billable_rate_update_time_entries') === 'true';
} }
public function getRole(): Role
{
return Role::from($this->input('role'));
}
} }

View File

@@ -57,8 +57,7 @@ class InvitationEndpointTest extends ApiEndpointTestAbstract
public function test_store_fails_if_user_has_no_permission_to_create_invitations(): void public function test_store_fails_if_user_has_no_permission_to_create_invitations(): void
{ {
// Arrange // Arrange
$data = $this->createUserWithPermission([ $data = $this->createUserWithPermission();
]);
Passport::actingAs($data->user); Passport::actingAs($data->user);
// Act // Act
@@ -71,6 +70,44 @@ class InvitationEndpointTest extends ApiEndpointTestAbstract
$response->assertStatus(403); $response->assertStatus(403);
} }
public function test_store_fails_if_user_invites_with_role_owner(): void
{
// Arrange
$data = $this->createUserWithPermission([
'invitations:create',
]);
Passport::actingAs($data->user);
// Act
$response = $this->postJson(route('api.v1.invitations.store', $data->organization->getKey()), [
'email' => 'test@asdf.at',
'role' => Role::Owner->value,
]);
// Assert
$response->assertStatus(422);
$response->assertJsonPath('message', 'The selected role is invalid.');
}
public function test_store_fails_if_user_invites_with_role_placeholder(): void
{
// Arrange
$data = $this->createUserWithPermission([
'invitations:create',
]);
Passport::actingAs($data->user);
// Act
$response = $this->postJson(route('api.v1.invitations.store', $data->organization->getKey()), [
'email' => 'test@asdf.at',
'role' => Role::Placeholder->value,
]);
// Assert
$response->assertStatus(422);
$response->assertJsonPath('message', 'The selected role is invalid.');
}
public function test_store_invites_user_to_organization(): void public function test_store_invites_user_to_organization(): void
{ {
// Arrange // Arrange

View File

@@ -136,6 +136,63 @@ class MemberEndpointTest extends ApiEndpointTestAbstract
$this->assertSame(10001, $member->billable_rate); $this->assertSame(10001, $member->billable_rate);
} }
public function test_update_member_can_update_role(): void
{
// Arrange
$data = $this->createUserWithPermission([
'members:update',
]);
$otherUser = User::factory()->create();
$otherMember = Member::factory()->forUser($otherUser)->forOrganization($data->organization)->role(Role::Employee)->create();
Passport::actingAs($data->user);
// Act
$response = $this->putJson(route('api.v1.members.update', [$data->organization->getKey(), $otherMember->getKey()]), [
'role' => Role::Admin->value,
]);
// Assert
$response->assertStatus(200);
$otherMember->refresh();
$this->assertSame(Role::Admin->value, $otherMember->role);
}
public function test_update_member_role_fails_if_role_is_owner(): void
{
// Arrange
$data = $this->createUserWithPermission([
'members:update',
]);
Passport::actingAs($data->user);
// Act
$response = $this->putJson(route('api.v1.members.update', [$data->organization->getKey(), $data->member->getKey()]), [
'role' => Role::Owner->value,
]);
// Assert
$response->assertStatus(422);
$response->assertJsonPath('message', 'The selected role is invalid.');
}
public function test_update_member_role_fails_if_role_is_placeholder(): void
{
// Arrange
$data = $this->createUserWithPermission([
'members:update',
]);
Passport::actingAs($data->user);
// Act
$response = $this->putJson(route('api.v1.members.update', [$data->organization->getKey(), $data->member->getKey()]), [
'role' => Role::Placeholder->value,
]);
// Assert
$response->assertStatus(422);
$response->assertJsonPath('message', 'The selected role is invalid.');
}
public function test_invite_placeholder_succeeds_if_data_is_valid(): void public function test_invite_placeholder_succeeds_if_data_is_valid(): void
{ {
$data = $this->createUserWithPermission([ $data = $this->createUserWithPermission([