Ctrl K
ComunidadGuías…

app/Providers/AuthServiceProvider.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php

namespace App\Providers;

use App\Auth\ActiveUserProvider;
use App\Auth\ExtendedTokenValidator;
use App\Policies\AccommodationPolicy;
use App\Policies\ArtistPolicy;
use App\Policies\ContactPolicy;
use Artwork\Modules\Accommodation\Models\Accommodation;
use Artwork\Modules\Area\Models\Area;
use Artwork\Modules\Area\Policies\AreaPolicy;
use Artwork\Modules\ArtistResidency\Models\Artist;
use Artwork\Modules\Contacts\Models\Contact;
use Artwork\Modules\Crm\Models\CrmContact;
use Artwork\Modules\Permission\Enums\PermissionEnum;
use Artwork\Modules\Budget\Models\SageAssignedDataComment;
use Artwork\Modules\Budget\Models\SageNotAssignedData;
use Artwork\Modules\Budget\Policies\SageAssignedDataCommentPolicy;
use Artwork\Modules\Budget\Policies\SageNotAssignedDataPolicy;
use Artwork\Modules\Budget\Models\BudgetColumnSetting;
use Artwork\Modules\Budget\Policies\BudgetColumnSettingPolicy;
use Artwork\Modules\Budget\Models\BudgetManagementAccount;
use Artwork\Modules\Budget\Policies\BudgetManagementAccountPolicy;
use Artwork\Modules\Budget\Models\BudgetManagementCostUnit;
use Artwork\Modules\Budget\Policies\BudgetManagementCostUnitPolicy;
use Artwork\Modules\Category\Models\Category;
use Artwork\Modules\Category\Policies\CategoryPolicy;
use Artwork\Modules\Checklist\Models\Checklist;
use Artwork\Modules\Checklist\Policies\ChecklistPolicy;
use Artwork\Modules\Checklist\Models\ChecklistTemplate;
use Artwork\Modules\Checklist\Policies\ChecklistTemplatePolicy;
use Artwork\Modules\Contract\Models\Contract;
use Artwork\Modules\Contract\Policies\ContractPolicy;
use Artwork\Modules\Contract\Models\ContractModule;
use Artwork\Modules\Contract\Policies\ContractModulePolicy;
use Artwork\Modules\ExternalIssue\Models\ExternalIssue;
use Artwork\Modules\InternalIssue\Models\InternalIssue;
use Artwork\Modules\Inventory\Policies\MaterialIssuePolicy;
use Artwork\Modules\MoneySource\Models\MoneySource;
use Artwork\Modules\MoneySource\Models\MoneySourceTask;
use Artwork\Modules\MoneySource\Policies\MoneySourcePolicy;
use Artwork\Modules\MoneySource\Policies\MoneySourceTaskPolicy;
use Artwork\Modules\Room\Models\Room;
use Artwork\Modules\Room\Policies\RoomPolicy;
use Artwork\Modules\Department\Models\Department;
use Artwork\Modules\Department\Policies\DepartmentPolicy;
use Artwork\Modules\Event\Models\Event;
use Artwork\Modules\Event\Policies\EventPolicy;
use Artwork\Modules\ExternalAccess\Models\ExternalAccess;
use Artwork\Modules\ExternalAccess\Policies\ExternalAccessPolicy;
use Artwork\Modules\ExternalUserManagement\Models\ExternalUserSource;
use Artwork\Modules\ExternalUserManagement\Policies\ExternalUserSourcePolicy;
use Artwork\Modules\Freelancer\Models\Freelancer;
use Artwork\Modules\Freelancer\Policies\FreelancerPolicy;
use Artwork\Modules\GeneralSettings\Models\GeneralSettings;
use Artwork\Modules\GeneralSettings\Policies\GeneralSettingsPolicy;
use Artwork\Modules\Genre\Models\Genre;
use Artwork\Modules\Genre\Policies\GenrePolicy;
use Artwork\Modules\Invitation\Models\Invitation;
use Artwork\Modules\Invitation\Policies\InvitationPolicy;
use Artwork\Modules\ModuleSettings\Http\Policies\ModuleSettingsPolicy;
use Artwork\Modules\ModuleSettings\Models\ModuleSettings;
use Artwork\Modules\Project\Models\Comment;
use Artwork\Modules\Project\Models\Project;
use Artwork\Modules\Project\Models\ProjectComponentCrmContact;
use Artwork\Modules\Project\Policies\CommentPolicy;
use Artwork\Modules\Project\Policies\ProjectPolicy;
use Artwork\Modules\Role\Enums\RoleEnum;
use Artwork\Modules\SageApiSettings\Models\SageApiSettings;
use Artwork\Modules\SageApiSettings\Policies\SageApiSettingsPolicy;
use Artwork\Modules\Sector\Models\Sector;
use Artwork\Modules\Sector\Policies\SectorPolicy;
use Artwork\Modules\ServiceProvider\Models\ServiceProvider as ServiceProviderModel;
use Artwork\Modules\ServiceProvider\Policies\ServiceProviderPolicy;
use Artwork\Modules\Shift\Models\ShiftCommitWorkflowUser;
use Artwork\Modules\Shift\Models\ShiftQualification;
use Artwork\Modules\Shift\Models\ShiftPlanRequest;
use Artwork\Modules\Shift\Policies\ShiftPlanRequestPolicy;
use Artwork\Modules\Shift\Policies\ShiftQualificationPolicy;
use Artwork\Modules\System\ApiManagement\Policies\TokenPolicy;
use Artwork\Modules\TaskTemplate\Models\TaskTemplate;
use Artwork\Modules\TaskTemplate\Policies\TaskTemplatePolicy;
use Artwork\Modules\User\Models\User;
use Artwork\Modules\User\Policies\UserPolicy;
use Artwork\Modules\Webhook\Models\WebhookEndpoint;
use Artwork\Modules\Webhook\Policies\WebhookEndpointPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Laravel\Horizon\Horizon;
use Laravel\Passport\Bridge\AccessTokenRepository;
use Laravel\Passport\Passport;
use Laravel\Passport\Token;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\ResourceServer;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        Invitation::class => 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,
            ]);
        });
    }
}