auth: automatische Firmenzuordnung beim Wegfall der E-Mail-Grundlage entfernen statt senken (Block 7.13, U01, E7)
- Login-Sync löscht auto-Pivots ohne aktuellen Treffer; member wäre die Teamrolle mit Schreibrecht auf alle Firmen-PMs, keine Leserolle - hochgestufte manuelle Mitgliedschaften tragen granted_via=auto-upgrade und fallen auf member zurück; manual und owner bleiben unberührt - company-memberships:audit --classify stuft nur manual-Zeilen um - Plan: E2 revidiert, E7 entschieden, 1.5 und 7.13 nachgezogen Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
3ea9688c9a
commit
b8c6af5f10
4 changed files with 92 additions and 26 deletions
|
|
@ -1,11 +1,15 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Portal;
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Mail\MagicLoginLink;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\User;
|
||||
use App\Services\Auth\ContactAccessService;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Livewire\Volt\Volt;
|
||||
|
|
@ -334,7 +338,7 @@ test('the membership sync ignores unverified and deactivated users regardless of
|
|||
expect($inactive->fresh()->accessibleCompanyIds())->not->toContain($company->id);
|
||||
});
|
||||
|
||||
test('an auto-granted responsible role is lowered to member on the next login once the email basis is gone (Audit F10)', function () {
|
||||
test('an auto-granted responsible role is removed on the next login once the email basis is gone (Audit F10/U01, E7)', function () {
|
||||
/** @var TestCase $this */
|
||||
$user = User::factory()->create(['email' => 'wechsel@example.test', 'is_active' => true]);
|
||||
$user->assignRole('customer');
|
||||
|
|
@ -355,10 +359,62 @@ test('an auto-granted responsible role is lowered to member on the next login on
|
|||
Volt::test('auth.login')->set('email', 'wechsel@example.test')->set('password', 'password')->call('login')->assertHasNoErrors();
|
||||
|
||||
$fresh = User::query()->findOrFail($user->id);
|
||||
$pivot = $fresh->companies()->withoutGlobalScopes()->whereKey($company->id)->first()->pivot;
|
||||
expect($pivot->role)->toBe('member')
|
||||
expect($fresh->companies()->withoutGlobalScopes()->whereKey($company->id)->exists())->toBeFalse()
|
||||
->and($fresh->can('update', $company))->toBeFalse()
|
||||
->and($fresh->can('view', $company))->toBeTrue();
|
||||
->and($fresh->can('view', $company))->toBeFalse();
|
||||
});
|
||||
|
||||
test('after the email basis is gone a non-author can no longer edit or correct the company press releases (Audit U01, E7)', function () {
|
||||
/** @var TestCase $this */
|
||||
$author = User::factory()->create(['is_active' => true]);
|
||||
$colleague = User::factory()->create(['email' => 'kollege@example.test', 'is_active' => true]);
|
||||
$colleague->assignRole('customer');
|
||||
|
||||
$company = Company::factory()->presseecho()->create(['email' => 'kollege@example.test']);
|
||||
$draft = PressRelease::factory()->forPortal(Portal::Presseecho)->for($company)->create(['user_id' => $author->id, 'status' => PressReleaseStatus::Draft]);
|
||||
$published = PressRelease::factory()->published()->forPortal(Portal::Presseecho)->for($company)->create(['user_id' => $author->id]);
|
||||
$own = PressRelease::factory()->forPortal(Portal::Presseecho)->for($company)->create(['user_id' => $colleague->id, 'status' => PressReleaseStatus::Draft]);
|
||||
|
||||
Volt::test('auth.login')->set('email', 'kollege@example.test')->set('password', 'password')->call('login')->assertHasNoErrors();
|
||||
|
||||
$linked = User::query()->findOrFail($colleague->id);
|
||||
expect($linked->can('update', $draft))->toBeTrue()
|
||||
->and($linked->can('view', $published))->toBeTrue();
|
||||
|
||||
// Mitarbeiterwechsel: die Firma trägt eine andere Adresse.
|
||||
$company->forceFill(['email' => 'nachfolger@example.test'])->save();
|
||||
auth()->logout();
|
||||
|
||||
Volt::test('auth.login')->set('email', 'kollege@example.test')->set('password', 'password')->call('login')->assertHasNoErrors();
|
||||
|
||||
$gone = User::query()->findOrFail($colleague->id);
|
||||
expect($gone->can('update', $draft))->toBeFalse()
|
||||
->and($gone->can('submitForReview', $draft))->toBeFalse()
|
||||
->and($gone->can('delete', $draft))->toBeFalse()
|
||||
->and($gone->can('view', $published))->toBeFalse()
|
||||
->and($gone->can('update', $own))->toBeTrue();
|
||||
|
||||
// Die Korrektur-Action der Kundenseite hängt an `view` und findet die PM nicht mehr.
|
||||
$this->actingAs($gone);
|
||||
expect(fn () => Volt::test('customer.press-releases.show', ['id' => $published->id]))
|
||||
->toThrow(ModelNotFoundException::class);
|
||||
});
|
||||
|
||||
test('an automatically upgraded manual membership falls back to member instead of being removed (E7)', function () {
|
||||
/** @var TestCase $this */
|
||||
$user = User::factory()->create(['email' => 'team@example.test', 'is_active' => true]);
|
||||
$company = Company::factory()->presseecho()->create(['email' => 'team@example.test']);
|
||||
$user->companies()->attach($company->id, ['role' => 'member']);
|
||||
|
||||
app(ContactAccessService::class)->syncMembershipsForEmail($user);
|
||||
$pivot = $user->companies()->withoutGlobalScopes()->whereKey($company->id)->first()->pivot;
|
||||
expect($pivot->role)->toBe('responsible')->and($pivot->granted_via)->toBe('auto-upgrade');
|
||||
|
||||
$company->forceFill(['email' => 'andere@example.test'])->save();
|
||||
app(ContactAccessService::class)->syncMembershipsForEmail($user->fresh());
|
||||
|
||||
$pivot = $user->companies()->withoutGlobalScopes()->whereKey($company->id)->first()->pivot;
|
||||
expect($pivot->role)->toBe('member')->and($pivot->granted_via)->toBe('manual');
|
||||
});
|
||||
|
||||
test('a manually granted responsible role survives the login sync without an email match (Audit F10)', function () {
|
||||
|
|
@ -423,7 +479,7 @@ test('a confirmed email change re-syncs company rights immediately (Audit 7.1)',
|
|||
|
||||
$fresh = User::query()->findOrFail($user->id);
|
||||
expect($fresh->email)->toBe('neu@example.test')
|
||||
->and($fresh->companies()->withoutGlobalScopes()->whereKey($oldCompany->id)->first()->pivot->role)->toBe('member')
|
||||
->and($fresh->companies()->withoutGlobalScopes()->whereKey($oldCompany->id)->exists())->toBeFalse()
|
||||
->and($fresh->companies()->withoutGlobalScopes()->whereKey($newCompany->id)->first()?->pivot->role)->toBe('responsible')
|
||||
->and($fresh->can('update', $oldCompany))->toBeFalse()
|
||||
->and($fresh->can('update', $newCompany))->toBeTrue();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue