InvitationPolicy::class, User::class => UserPolicy::class, Department::class => DepartmentPolicy::class, Project::class => ProjectPolicy::class, Checklist::class => ChecklistPolicy::class, Sector::class => SectorPolicy::class, Category::class => CategoryPolicy::class, Genre::class => GenrePolicy::class, Comment::class => CommentPolicy::class, ChecklistTemplate::class => ChecklistTemplatePolicy::class, TaskTemplate::class => TaskTemplatePolicy::class, Area::class => AreaPolicy::class, Contract::class => ContractPolicy::class, ContractModule::class => ContractModulePolicy::class, MoneySource::class => MoneySourcePolicy::class, MoneySourceTask::class => MoneySourceTaskPolicy::class, Room::class => RoomPolicy::class, InternalIssue::class => MaterialIssuePolicy::class, ExternalIssue::class => MaterialIssuePolicy::class, Freelancer::class => FreelancerPolicy::class, ServiceProviderModel::class => ServiceProviderPolicy::class, GeneralSettings::class => GeneralSettingsPolicy::class, Token::class => TokenPolicy::class, WebhookEndpoint::class => WebhookEndpointPolicy::class, ShiftQualification::class => ShiftQualificationPolicy::class, ShiftPlanRequest::class => ShiftPlanRequestPolicy::class, SageApiSettings::class => SageApiSettingsPolicy::class, SageAssignedDataComment::class => SageAssignedDataCommentPolicy::class, SageNotAssignedData::class => SageNotAssignedDataPolicy::class, BudgetColumnSetting::class => BudgetColumnSettingPolicy::class, BudgetManagementAccount::class => BudgetManagementAccountPolicy::class, BudgetManagementCostUnit::class => BudgetManagementCostUnitPolicy::class, Event::class => EventPolicy::class, ModuleSettings::class => ModuleSettingsPolicy::class, ExternalAccess::class => ExternalAccessPolicy::class, ExternalUserSource::class => ExternalUserSourcePolicy::class, \Artwork\Modules\Chat\Models\Chat::class => \Artwork\Modules\Chat\Policies\ChatPolicy::class, \Artwork\Modules\Vacation\Models\Vacation::class => \Artwork\Modules\Vacation\Policies\VacationPolicy::class, \Artwork\Modules\IndividualTimes\Models\IndividualTime::class => \Artwork\Modules\IndividualTimes\Policies\IndividualTimePolicy::class, \Artwork\Modules\IndividualTimes\Models\IndividualTimeSeries::class => \App\Policies\IndividualTimeSeriesPolicy::class, \Artwork\Modules\Availability\Models\Availability::class => \Artwork\Modules\Availability\Policies\AvailabilityPolicy::class, \Artwork\Modules\WorkTime\Models\WorkTimeChangeRequest::class => \Artwork\Modules\WorkTime\Policies\WorkTimeChangeRequestPolicy::class, Accommodation::class => AccommodationPolicy::class, Artist::class => ArtistPolicy::class, Contact::class => ContactPolicy::class, ]; public function register(): void { parent::register(); // Ersetzt Passports ResourceServer-Binding nur, um den ExtendedTokenValidator einzuhängen: // Er prüft zusätzlich oauth_access_tokens.expires_at (in der Oberfläche gesetztes Ablaufdatum) // und trägt die bewusste Ausnahme für einzelne Tokens (config passport.extended_tokens). $this->app->singleton(ResourceServer::class, function ($container) { // Spiegelt PassportServiceProvider::makeCryptKey('public') (protected, daher nachgebaut). // Bei Passport-Upgrades gegen vendor/laravel/passport/src/PassportServiceProvider.php abgleichen. $key = str_replace('\\n', "\n", config('passport.public_key') ?? ''); if (!$key) { $key = 'file://' . Passport::keyPath('oauth-public.key'); } return new ResourceServer( $container->make(AccessTokenRepository::class), new CryptKey($key, null, Passport::$validateKeyPermissions), new ExtendedTokenValidator( $container->make(AccessTokenRepository::class), config('passport.extended_tokens', []), ), ); }); } public function boot(): void { $this->registerPolicies(); // Eloquent-Provider für User: deaktivierte Accounts melden sich auf keinem Guard an. Überschreibt den // Treiber "eloquent" statt einen eigenen Namen zu nutzen, weil Passport (HasApiTokens::getProviderName) // nur Provider mit driver "eloquent" findet; andere Models (z. B. externals) bleiben unverändert. Auth::provider('eloquent', static function ($app, array $config): EloquentUserProvider { return is_a($config['model'], User::class, true) ? new ActiveUserProvider($app['hash'], $config['model']) : new EloquentUserProvider($app['hash'], $config['model']); }); Passport::$clientUuids = false; // Scopes der Maschinen-API und der App. Ein Scope, der hier fehlt, lässt sich nicht // vergeben — die ScopeRepository lehnt ihn beim Anlegen mit invalid_scope ab. // // Wichtig: Scopes stehen im signierten JWT, nicht in der Datenbank. Tokens, die vor der // Einführung dieser Liste ausgegeben wurden, tragen dauerhaft eine leere Scope-Menge und // können nachträglich keine Rechte erhalten — sie müssen neu erstellt werden. Passport::tokensCan([ 'app' => 'Access the artwork app API', 'inventory:read' => 'Read inventory categories and articles', 'calendar:read' => 'Read rooms, areas and events (room occupancy)', 'ticketing:read' => 'Read released events, price categories and branding', 'ticketing:write' => 'Report sales, bookings and check-ins back to artwork', ]); // Passport ≥12 registriert keine Default-View mehr – ohne dieses Binding // wirft /oauth/authorize eine BindingResolutionException, sobald ein // Client eine Consent-Abfrage braucht. Passport::authorizationView('oauth.authorize'); // Implicitly grant "admin" role all permissions // This works in the app by using gate-related functions like auth()->user->can() and @can() Gate::before(function ($user) { // Type-check so that ExternalAccess (or any non-User identity) does not slip into // the admin bypass — external identities run normally through policies. if (!$user instanceof User) { return null; } return $user->hasRole(RoleEnum::ARTWORK_ADMIN->value) ? true : null; }); // Genehmiger-Seite des Dienstplan-Festschreibungs-Workflows: wer als // ShiftCommitWorkflowUser hinterlegt ist, darf Anfragen prüfen, genehmigen, // ablehnen und Änderungen nach Festschreibung bearbeiten (Admins via Gate::before). Gate::define('approve-shift-plan-requests', function (User $user): bool { return ShiftCommitWorkflowUser::where('user_id', $user->id)->exists(); }); // Kontakt-Lookups werden auch außerhalb des CRM genutzt (Projektteam, Dokumentanfragen, Verträge, // Künstler-Verknüpfung, externe Einladungen): erlaubt mit CRM-Sicht oder einem der Rechte dieser // Features; der Tooltip zusätzlich für Kontakte im Team eines sichtbaren Projekts. Gate::define( 'crm.contacts.lookup', function (User $user, ?CrmContact $crmContact = null): bool { if ( $user->canAny([ PermissionEnum::CRM_VIEW->value, PermissionEnum::CRM_MANAGER->value, PermissionEnum::TEAM_UPDATE->value, PermissionEnum::WRITE_PROJECTS->value, PermissionEnum::ADD_EDIT_OWN_PROJECT->value, PermissionEnum::DOCUMENT_REQUEST_CREATE->value, PermissionEnum::DOCUMENT_REQUEST_EDIT->value, PermissionEnum::CONTRACT_EDIT_UPLOAD->value, PermissionEnum::INVITE_EXTERNAL->value, ]) ) { return true; } if ($user->projects()->wherePivot('can_write', true)->exists()) { return true; } if ($crmContact === null) { return false; } if ( $crmContact->teamProjects() ->get() ->contains(static fn ($project): bool => $user->can('view', $project)) ) { return true; } // Projektkünstler*innen (Komponente „Projektkünstler*in“ oder Sonderrolle in einer Kontaktliste): // Infokarte für alle, die das Projekt sehen (Kalender, Dienstplan, Übersicht) $artistProjectIds = $crmContact->projects()->pluck('projects.id') ->merge(ProjectComponentCrmContact::query() ->where('crm_contact_id', $crmContact->id) ->where('is_artist', true) ->pluck('project_id')) ->unique(); return Project::query() ->whereIn('id', $artistProjectIds) ->get() ->contains(static fn ($project): bool => $user->can('view', $project)); } ); // Horizon zeigt Job-Payloads (u. a. Magic-Link-Tokens). Ohne eigenes Gate wäre das Dashboard // bei APP_ENV=local anonym offen und sonst für niemanden; daher nur für artwork-Admins. Gate::define('viewHorizon', static function (?User $user = null): bool { return $user?->hasRole(RoleEnum::ARTWORK_ADMIN->value) ?? false; }); Horizon::auth(static function ($request): bool { $user = $request->user(); return $user instanceof User && Gate::forUser($user)->allows('viewHorizon'); }); // Dienstplaner-Berechtigung als benanntes Gate — die eine Definition // für Web und App (Admins via Gate::before). Gate::define('plan-shifts', static function (User $user): bool { return $user->can(PermissionEnum::SHIFT_PLANNER->value); }); // Inventarübersicht (Web und App): "Inventar sehen" oder ein Recht, das es im Katalog enthält // (Anlegen/Bearbeiten, Löschen, Disposition). Artikelplanung und Materialausgaben bleiben an der // Disposition; der Modul-Schalter wird separat geprüft. Admins via Gate::before. Gate::define('view-inventory', static function (User $user): bool { return $user->canAny([ PermissionEnum::INVENTORY_VIEW->value, PermissionEnum::INVENTORY_CREATE_EDIT->value, PermissionEnum::INVENTORY_DELETE->value, PermissionEnum::INVENTORY_DISPOSITION->value, ]); }); } }