app/Providers/AuthServiceProvider.php
Rama main · réplica de lectura del repositorio original.
<?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,
]);
});
}
}