├── .gitignore ├── README.md ├── composer.json ├── config └── filament-impersonate.php ├── resources ├── lang │ ├── ar │ │ ├── action.php │ │ └── banner.php │ ├── de │ │ ├── action.php │ │ └── banner.php │ ├── en │ │ ├── action.php │ │ └── banner.php │ ├── es │ │ ├── action.php │ │ └── banner.php │ ├── fa │ │ ├── action.php │ │ └── banner.php │ ├── fr │ │ ├── action.php │ │ └── banner.php │ ├── he │ │ ├── action.php │ │ └── banner.php │ ├── hu │ │ ├── action.php │ │ └── banner.php │ ├── id │ │ ├── action.php │ │ └── banner.php │ ├── it │ │ ├── action.php │ │ └── banner.php │ ├── ko │ │ ├── action.php │ │ └── banner.php │ ├── nl │ │ ├── action.php │ │ └── banner.php │ ├── pt_BR │ │ ├── action.php │ │ └── banner.php │ ├── pt_PT │ │ ├── action.php │ │ └── banner.php │ ├── tr │ │ ├── action.php │ │ └── banner.php │ └── uk │ │ ├── action.php │ │ └── banner.php └── views │ ├── components │ └── banner.blade.php │ └── icons │ └── icon.svg ├── routes └── web.php └── src ├── Concerns └── Impersonates.php ├── FilamentImpersonateServiceProvider.php ├── Pages └── Actions │ └── Impersonate.php └── Tables └── Actions └── Impersonate.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | docs 3 | vendor 4 | coverage 5 | .idea 6 | .phpunit* 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Filament Impersonate 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/stechstudio/filament-impersonate.svg?style=flat-square)](https://packagist.org/packages/stechstudio/filament-impersonate) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 5 | 6 | This is a plugin for [Filament](https://filamentadmin.com/) that makes it easy to impersonate your users. 7 | 8 | ### Credit 9 | 10 | This package uses [https://github.com/404labfr/laravel-impersonate](https://github.com/404labfr/laravel-impersonate) under the hood, and borrows heavily from [https://github.com/KABBOUCHI/nova-impersonate](https://github.com/KABBOUCHI/nova-impersonate). 11 | 12 | ## Installation 13 | 14 | You know the drill: 15 | 16 | ```bash 17 | composer require stechstudio/filament-impersonate 18 | ``` 19 | 20 | ## Quickstart 21 | 22 | ### 1. Add table action 23 | 24 | First open the resource where you want the impersonate action to appear. This is generally going to be your `UserResource` class. 25 | 26 | Go down to the `table` method. After defining the table columns, you want to add `Impersonate` as a new action for the table via `actions` method. Your class should look like this: 27 | 28 | ```php 29 | namespace App\Filament\Resources; 30 | 31 | use Filament\Resources\Resource; 32 | use STS\FilamentImpersonate\Tables\Actions\Impersonate; 33 | 34 | class UserResource extends Resource { 35 | public static function table(Table $table) 36 | { 37 | return $table 38 | ->columns([ 39 | // ... 40 | ]) 41 | ->actions([ 42 | Impersonate::make(), // <--- 43 | ]); 44 | } 45 | ``` 46 | 47 | You can also define a `guard` and `redirectTo` for the action: 48 | 49 | ```php 50 | Impersonate::make('impersonate') 51 | ->guard('another-guard') 52 | ->redirectTo(route('some.other.route')); 53 | ``` 54 | 55 | ### 2. Add the page action 56 | 57 | Now open the page where you would want the button to appear, this will commonly be `EditUser`; 58 | 59 | Go to the `getActions` method and add the `Impersonate` page action here. 60 | 61 | ```php 62 | record($this->getRecord()) // <-- 77 | ]; 78 | } 79 | } 80 | ``` 81 | 82 | Note: you must pass the record in as seen in this example! 83 | 84 | ### 3. Add the banner to your blade layout 85 | 86 | The only other step is to display a notice in your app whenever you are impersonating another user. Open up your master layout file and add `` before the closing `` tag. 87 | 88 | ### 4. Profit! 89 | 90 | That's it. You should now see an action icon next to each user in your Filament `UserResource` list: 91 | 92 | CleanShot 2022-01-03 at 14 10 36@2x 93 | 94 | When you click on the impersonate icon you will be logged in as that user, and redirected to your main app. You will see the impersonation banner at the top of the page, with a button to leave and return to Filament: 95 | 96 | ![banner](https://user-images.githubusercontent.com/203749/112773267-5331b400-9003-11eb-85ae-b54c458fb5aa.png) 97 | 98 | 99 | ## Configuration 100 | 101 | All configuration can be managed with ENV variables, no need to publish and edit the config directly. Just check out the [config file](/config/filament-impersonate.php). 102 | 103 | ## Authorization 104 | 105 | By default, only Filament admins can impersonate other users. You can control this by adding a `canImpersonate` method to your `FilamentUser` class: 106 | 107 | ```php 108 | class User implements FilamentUser { 109 | 110 | public function canImpersonate() 111 | { 112 | return true; 113 | } 114 | 115 | } 116 | ``` 117 | 118 | You can also control which targets can *be* impersonated. Just add a `canBeImpersonated` method to the user class with whatever logic you need: 119 | 120 | ```php 121 | class User { 122 | 123 | public function canBeImpersonated() 124 | { 125 | // Let's prevent impersonating other users at our own company 126 | return !Str::endsWith($this->email, '@mycorp.com'); 127 | } 128 | 129 | } 130 | ``` 131 | 132 | ## Customizing the banner 133 | 134 | The blade component has a few options you can customize. 135 | 136 | ### Style 137 | 138 | The banner is dark by default, you can set this to light, or auto. 139 | 140 | ```html 141 | 142 | ``` 143 | 144 | ### Display name 145 | 146 | The banner will show the name of the impersonated user, assuming there is a `name` attribute. You can customize this if needed: 147 | 148 | ```html 149 | 150 | ``` 151 | 152 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stechstudio/filament-impersonate", 3 | "description": "A Filament package to impersonate your users.", 4 | "type": "library", 5 | "minimum-stability": "dev", 6 | "license": "MIT", 7 | "require": { 8 | "lab404/laravel-impersonate": "^1.7", 9 | "filament/filament": "^3.0" 10 | }, 11 | "autoload": { 12 | "psr-4": { 13 | "STS\\FilamentImpersonate\\": "src/" 14 | } 15 | }, 16 | "extra": { 17 | "laravel": { 18 | "providers": [ 19 | "STS\\FilamentImpersonate\\FilamentImpersonateServiceProvider" 20 | ] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config/filament-impersonate.php: -------------------------------------------------------------------------------- 1 | env('FILAMENT_IMPERSONATE_GUARD', 'web'), 5 | 6 | // After impersonating this is where we'll redirect you to. 7 | 'redirect_to' => env('FILAMENT_IMPERSONATE_REDIRECT', '/'), 8 | 9 | // We wire up a route for the "leave" button. You can change the middleware stack here if needed. 10 | 'leave_middleware' => env('FILAMENT_IMPERSONATE_LEAVE_MIDDLEWARE', 'web'), 11 | 12 | 'banner' => [ 13 | // Available hooks: https://filamentphp.com/docs/3.x/support/render-hooks#available-render-hooks 14 | 'render_hook' => env('FILAMENT_IMPERSONATE_BANNER_RENDER_HOOK', 'panels::body.start'), 15 | 16 | // Currently supports 'dark', 'light' and 'auto'. 17 | 'style' => env('FILAMENT_IMPERSONATE_BANNER_STYLE', 'dark'), 18 | 19 | // Turn this off if you want `absolute` positioning, so the banner scrolls out of view 20 | 'fixed' => env('FILAMENT_IMPERSONATE_BANNER_FIXED', true), 21 | 22 | // Currently supports 'top' and 'bottom'. 23 | 'position' => env('FILAMENT_IMPERSONATE_BANNER_POSITION', 'top'), 24 | 25 | 'styles' => [ 26 | 'light' => [ 27 | 'text' => '#1f2937', 28 | 'background' => '#f3f4f6', 29 | 'border' => '#e8eaec', 30 | ], 31 | 'dark' => [ 32 | 'text' => '#f3f4f6', 33 | 'background' => '#1f2937', 34 | 'border' => '#374151', 35 | ], 36 | ] 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/ar/action.php: -------------------------------------------------------------------------------- 1 | 'تقليد', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/ar/banner.php: -------------------------------------------------------------------------------- 1 | 'المستخدم المقلّد', 5 | 'leave' => 'مغادرة', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/de/action.php: -------------------------------------------------------------------------------- 1 | 'Imitieren', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/de/banner.php: -------------------------------------------------------------------------------- 1 | 'Imitiere Benutzer', 5 | 'leave' => 'Verlassen', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/en/action.php: -------------------------------------------------------------------------------- 1 | 'Impersonate', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/en/banner.php: -------------------------------------------------------------------------------- 1 | 'Impersonating user', 5 | 'leave' => 'Leave', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/es/action.php: -------------------------------------------------------------------------------- 1 | 'Suplantar', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/es/banner.php: -------------------------------------------------------------------------------- 1 | 'Haciéndose pasar por usuario', 5 | 'leave' => 'Salir', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/fa/action.php: -------------------------------------------------------------------------------- 1 | 'جعل هویت', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/fa/banner.php: -------------------------------------------------------------------------------- 1 | 'جعل هویت کاربر', 5 | 'leave' => 'خروج', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/fr/action.php: -------------------------------------------------------------------------------- 1 | 'Se faire passer pour', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/fr/banner.php: -------------------------------------------------------------------------------- 1 | 'Vous êtes en train de vous faire passer pour', 5 | 'leave' => 'Quitter', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/he/action.php: -------------------------------------------------------------------------------- 1 | 'להתחזות', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/he/banner.php: -------------------------------------------------------------------------------- 1 | 'מתחזה למשתמש', 5 | 'leave' => 'להתנתק', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/hu/action.php: -------------------------------------------------------------------------------- 1 | 'Átjelentkezés', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/hu/banner.php: -------------------------------------------------------------------------------- 1 | 'Belépve mint:', 5 | 'leave' => 'Visszajelentkezés', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/id/action.php: -------------------------------------------------------------------------------- 1 | 'Peniruan', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/id/banner.php: -------------------------------------------------------------------------------- 1 | 'Anda sedang berpura-pura menjadi', 5 | 'leave' => 'Tinggalkan', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/it/action.php: -------------------------------------------------------------------------------- 1 | 'Impersona', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/it/banner.php: -------------------------------------------------------------------------------- 1 | 'Stai impersonando l\'utente', 5 | 'leave' => 'Abbandona', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/ko/action.php: -------------------------------------------------------------------------------- 1 | '사용자 가장하기', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/ko/banner.php: -------------------------------------------------------------------------------- 1 | '사용자를 표시 중입니다.', 5 | 'leave' => '원래 사용자로 돌아가기', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/nl/action.php: -------------------------------------------------------------------------------- 1 | 'Inloggen als...', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/nl/banner.php: -------------------------------------------------------------------------------- 1 | 'Ingelogd als gebruiker', 5 | 'leave' => 'Terug naar eigen account', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/pt_BR/action.php: -------------------------------------------------------------------------------- 1 | 'Personificar', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/pt_BR/banner.php: -------------------------------------------------------------------------------- 1 | 'personificado como o usuário', 5 | 'leave' => 'Sair', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/pt_PT/action.php: -------------------------------------------------------------------------------- 1 | 'Personificar', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/pt_PT/banner.php: -------------------------------------------------------------------------------- 1 | 'Actualmente a personificar o utilizador: ', 5 | 'leave' => 'Voltar à minha conta', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/tr/action.php: -------------------------------------------------------------------------------- 1 | 'Kullanıcı olarak görüntüle', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/tr/banner.php: -------------------------------------------------------------------------------- 1 | 'Kullanıcı olarak görüntüleniyor', 5 | 'leave' => 'Ayrıl', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/uk/action.php: -------------------------------------------------------------------------------- 1 | 'Імітувати', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/uk/banner.php: -------------------------------------------------------------------------------- 1 | 'Імітація користувача', 5 | 'leave' => 'Вийти', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/views/components/banner.blade.php: -------------------------------------------------------------------------------- 1 | @props(['style', 'display', 'fixed', 'position']) 2 | 3 | @if(app('impersonate')->isImpersonating()) 4 | 5 | @php 6 | $display = $display ?? Filament\Facades\Filament::getUserName(Filament\Facades\Filament::auth()->user()); 7 | $fixed = $fixed ?? config('filament-impersonate.banner.fixed'); 8 | $position = $position ?? config('filament-impersonate.banner.position'); 9 | $borderPosition = $position === 'top' ? 'bottom' : 'top'; 10 | 11 | $style = $style ?? config('filament-impersonate.banner.style'); 12 | $styles = config('filament-impersonate.banner.styles'); 13 | $default = $style === 'auto' ? 'light' : $style; 14 | $flipped = $default === 'dark' ? 'light' : 'dark'; 15 | @endphp 16 | 17 | 116 | 117 |
118 |
119 | {{ __('filament-impersonate::banner.impersonating') }} {{ $display }} 120 |
121 | 122 | {{ __('filament-impersonate::banner.leave') }} 123 |
124 | @endIf 125 | -------------------------------------------------------------------------------- /resources/views/icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | isImpersonating()) { 8 | return redirect('/'); 9 | } 10 | 11 | app(ImpersonateManager::class)->leave(); 12 | 13 | return redirect( 14 | session()->pull('impersonate.back_to') 15 | ); 16 | })->name('filament-impersonate.leave')->middleware(config('filament-impersonate.leave_middleware')); 17 | -------------------------------------------------------------------------------- /src/Concerns/Impersonates.php: -------------------------------------------------------------------------------- 1 | guard = $guard; 27 | 28 | return $this; 29 | } 30 | 31 | public function redirectTo(Closure|string $redirectTo): self 32 | { 33 | $this->redirectTo = $redirectTo; 34 | 35 | return $this; 36 | } 37 | 38 | public function backTo(Closure|string $backTo): self 39 | { 40 | $this->backTo = $backTo; 41 | 42 | return $this; 43 | } 44 | 45 | public function getGuard(): string 46 | { 47 | return $this->evaluate($this->guard) ?? Filament::getCurrentPanel()->getAuthGuard(); 48 | } 49 | 50 | public function getRedirectTo(): string 51 | { 52 | return $this->evaluate($this->redirectTo) ?? config('filament-impersonate.redirect_to'); 53 | } 54 | 55 | public function getBackTo(): ?string 56 | { 57 | return $this->evaluate($this->backTo); 58 | } 59 | 60 | protected function canBeImpersonated($target): bool 61 | { 62 | $current = Filament::auth()->user(); 63 | 64 | return $current->isNot($target) 65 | && !app(ImpersonateManager::class)->isImpersonating() 66 | && (!method_exists($current, 'canImpersonate') || $current->canImpersonate()) 67 | && (!method_exists($target, 'canBeImpersonated') || $target->canBeImpersonated()); 68 | } 69 | 70 | public function impersonate($record): bool|Redirector|RedirectResponse 71 | { 72 | if (!$this->canBeImpersonated($record)) { 73 | return false; 74 | } 75 | 76 | session()->put([ 77 | 'impersonate.back_to' => $this->getBackTo() ?? request('fingerprint.path', request()->header('referer')) ?? Filament::getCurrentPanel()->getUrl(), 78 | 'impersonate.guard' => $this->getGuard() 79 | ]); 80 | 81 | app(ImpersonateManager::class)->take( 82 | Filament::auth()->user(), 83 | $record, 84 | $this->getGuard() 85 | ); 86 | 87 | return redirect($this->getRedirectTo()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/FilamentImpersonateServiceProvider.php: -------------------------------------------------------------------------------- 1 | name(static::$name) 24 | ->hasRoute('web') 25 | ->hasConfigFile() 26 | ->hasTranslations() 27 | ->hasViews(); 28 | } 29 | 30 | public function registeringPackage(): void 31 | { 32 | Event::listen(TakeImpersonation::class, fn () => $this->clearAuthHashes()); 33 | Event::listen(LeaveImpersonation::class, fn () => $this->clearAuthHashes()); 34 | 35 | $this->registerIcon(); 36 | } 37 | 38 | public function bootingPackage(): void 39 | { 40 | FilamentView::registerRenderHook( 41 | config('filament-impersonate.banner.render_hook', 'panels::body.start'), 42 | static fn (): string => Blade::render("") 43 | ); 44 | 45 | // For backwards compatibility we're going to load our views into the namespace we used to use as well. 46 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'impersonate'); 47 | 48 | // Alias our table action for backwards compatibility. 49 | // STS\FilamentImpersonate\Impersonate is where that class used to exist, and I don't 50 | // want a breaking release yet. 51 | if (!class_exists(\STS\FilamentImpersonate\Impersonate::class)) { 52 | class_alias(Impersonate::class, \STS\FilamentImpersonate\Impersonate::class); 53 | } 54 | } 55 | 56 | protected function clearAuthHashes(): void 57 | { 58 | session()->forget(array_unique([ 59 | 'password_hash_' . session('impersonate.guard'), 60 | 'password_hash_' . Filament::getCurrentPanel()->getAuthGuard(), 61 | 'password_hash_' . Filament::getPanel(session()->get('impersonate.back_to_panel'))->getAuthGuard(), 62 | 'password_hash_' . auth()->getDefaultDriver(), 63 | 'password_hash_sanctum' 64 | ])); 65 | } 66 | 67 | protected function registerIcon(): void 68 | { 69 | $this->callAfterResolving(Factory::class, function (Factory $factory) { 70 | $factory->add('impersonate', [ 71 | 'path' => __DIR__.'/../resources/views/icons', 72 | 'prefix' => 'impersonate', 73 | ]); 74 | }); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Pages/Actions/Impersonate.php: -------------------------------------------------------------------------------- 1 | label(__('filament-impersonate::action.label')) 18 | ->icon('impersonate-icon') 19 | ->action(fn ($record) => $this->impersonate($record)) 20 | ->hidden(fn ($record) => !$this->canBeImpersonated($record)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Tables/Actions/Impersonate.php: -------------------------------------------------------------------------------- 1 | label(__('filament-impersonate::action.label')) 18 | ->iconButton() 19 | ->icon('impersonate-icon') 20 | ->action(fn ($record) => $this->impersonate($record)) 21 | ->hidden(fn ($record) => !$this->canBeImpersonated($record)); 22 | } 23 | } 24 | --------------------------------------------------------------------------------