Add support for impersonation
This commit is contained in:
parent
dec10cee4e
commit
10484739c9
|
@ -62,11 +62,32 @@ class AuthController extends Controller
|
|||
|
||||
public function me(Request $request)
|
||||
{
|
||||
$impersonateManager = app('impersonate');
|
||||
return [
|
||||
...$request->user()->toArray(),
|
||||
"permissions" => array_map(function($p) {
|
||||
return $p["name"];
|
||||
}, $request->user()->allPermissions()->toArray()),
|
||||
"impersonating_user" => $impersonateManager->isImpersonating(),
|
||||
"impersonator_id" => $impersonateManager->getImpersonatorId()
|
||||
];
|
||||
}
|
||||
|
||||
public function impersonate(Request $request, $user)
|
||||
{
|
||||
$impersonatedUser = User::find($user);
|
||||
$request->user()->impersonate($impersonatedUser);
|
||||
$token = $impersonatedUser->createToken('auth_token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'access_token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
]);
|
||||
}
|
||||
|
||||
public function stopImpersonating(Request $request)
|
||||
{
|
||||
$request->user()->leaveImpersonation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,12 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
|
|||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Laratrust\Traits\LaratrustUserTrait;
|
||||
use Lab404\Impersonate\Models\Impersonate;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use LaratrustUserTrait;
|
||||
use Impersonate;
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
|
@ -55,4 +57,20 @@ class User extends Authenticatable
|
|||
'email_verified_at' => 'datetime',
|
||||
'last_access' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function canImpersonate()
|
||||
{
|
||||
return $this->hasPermission("users-impersonate");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeImpersonated()
|
||||
{
|
||||
return !$this->hasPermission("users-impersonate");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,18 @@ class EventServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
Event::listen(
|
||||
\Lab404\Impersonate\Events\TakeImpersonation::class,
|
||||
function (\Lab404\Impersonate\Events\TakeImpersonation $event) {
|
||||
session()->put('password_hash_sanctum', $event->impersonated->getAuthPassword());
|
||||
}
|
||||
);
|
||||
Event::listen(
|
||||
\Lab404\Impersonate\Events\LeaveImpersonation::class,
|
||||
function (\Lab404\Impersonate\Events\LeaveImpersonation $event) {
|
||||
session()->put('password_hash_sanctum', $event->impersonator->getAuthPassword());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"require": {
|
||||
"php": "^8.1",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"lab404/laravel-impersonate": "^1.7",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/sanctum": "^3.2",
|
||||
"laravel/tinker": "^2.8",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b5d89cfe094ee5746cee5265ecab952d",
|
||||
"content-hash": "c1b31b310ab296e893ef90608cd91a72",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
|
@ -780,16 +780,16 @@
|
|||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "2.4.3",
|
||||
"version": "2.4.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "67c26b443f348a51926030c83481b85718457d3d"
|
||||
"reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d",
|
||||
"reference": "67c26b443f348a51926030c83481b85718457d3d",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/0454e12ef0cd597ccd2adb036f7bda4e7fface66",
|
||||
"reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -815,9 +815,6 @@
|
|||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -879,7 +876,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/psr7/issues",
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.4.3"
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.4.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -895,7 +892,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-10-26T14:07:24+00:00"
|
||||
"time": "2023-04-17T16:00:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/uri-template",
|
||||
|
@ -1031,6 +1028,74 @@
|
|||
},
|
||||
"time": "2022-03-02T17:32:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "lab404/laravel-impersonate",
|
||||
"version": "1.7.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/404labfr/laravel-impersonate.git",
|
||||
"reference": "d8ab69f05daab4117b313e11ca007fbf3199a1ab"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/404labfr/laravel-impersonate/zipball/d8ab69f05daab4117b313e11ca007fbf3199a1ab",
|
||||
"reference": "d8ab69f05daab4117b313e11ca007fbf3199a1ab",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0",
|
||||
"php": "^7.2 | ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.3.3",
|
||||
"orchestra/database": "^4.0 | ^5.0 | ^6.0 | ^7.0 | ^8.0",
|
||||
"orchestra/testbench": "^4.0 | ^5.0 | ^6.0 | ^7.0 | ^8.0",
|
||||
"phpunit/phpunit": "^7.5 | ^8.0 | ^9.0 | ^10.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Lab404\\Impersonate\\ImpersonateServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Lab404\\Impersonate\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Marceau Casals",
|
||||
"email": "marceau@casals.fr"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Impersonate is a plugin that allows to you to authenticate as your users.",
|
||||
"keywords": [
|
||||
"auth",
|
||||
"impersonate",
|
||||
"impersonation",
|
||||
"laravel",
|
||||
"laravel-package",
|
||||
"laravel-plugin",
|
||||
"package",
|
||||
"plugin",
|
||||
"user"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/404labfr/laravel-impersonate/issues",
|
||||
"source": "https://github.com/404labfr/laravel-impersonate/tree/1.7.4"
|
||||
},
|
||||
"time": "2023-01-25T16:56:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v10.0.3",
|
||||
|
|
|
@ -185,6 +185,7 @@ return [
|
|||
/*
|
||||
* Package Service Providers...
|
||||
*/
|
||||
Lab404\Impersonate\ImpersonateServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
|
|
|
@ -13,7 +13,7 @@ return [
|
|||
|
||||
'roles_structure' => [
|
||||
'superadmin' => [
|
||||
'users' => 'c,r,u,d',
|
||||
'users' => 'c,r,u,d,i',
|
||||
],
|
||||
'admin' => [
|
||||
'users' => 'c,r,u'
|
||||
|
@ -31,6 +31,7 @@ return [
|
|||
'lr' => 'limitedRead',
|
||||
'r' => 'read',
|
||||
'u' => 'update',
|
||||
'd' => 'delete'
|
||||
'd' => 'delete',
|
||||
'i' => 'impersonate'
|
||||
]
|
||||
];
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
* The session key used to store the original user id.
|
||||
*/
|
||||
'session_key' => 'impersonated_by',
|
||||
|
||||
/**
|
||||
* The session key used to stored the original user guard.
|
||||
*/
|
||||
'session_guard' => 'impersonator_guard',
|
||||
|
||||
/**
|
||||
* The session key used to stored what guard is impersonator using.
|
||||
*/
|
||||
'session_guard_using' => 'impersonator_guard_using',
|
||||
|
||||
/**
|
||||
* The default impersonator guard used.
|
||||
*/
|
||||
'default_impersonator_guard' => 'web',
|
||||
|
||||
/**
|
||||
* The URI to redirect after taking an impersonation.
|
||||
*
|
||||
* Only used in the built-in controller.
|
||||
* * Use 'back' to redirect to the previous page
|
||||
*/
|
||||
'take_redirect_to' => '/',
|
||||
|
||||
/**
|
||||
* The URI to redirect after leaving an impersonation.
|
||||
*
|
||||
* Only used in the built-in controller.
|
||||
* Use 'back' to redirect to the previous page
|
||||
*/
|
||||
'leave_redirect_to' => '/',
|
||||
|
||||
];
|
|
@ -22,10 +22,13 @@ use Illuminate\Support\Facades\Artisan;
|
|||
Route::post('/register', [AuthController::class, 'register']);
|
||||
Route::post('/login', [AuthController::class, 'login']);
|
||||
|
||||
Route::middleware('auth:sanctum')->group( function () {
|
||||
Route::middleware('auth:web')->group( function () {
|
||||
Route::get('/me', [AuthController::class, 'me']);
|
||||
Route::post('/me', [AuthController::class, 'me']);
|
||||
|
||||
Route::post('/impersonate/{user}', [AuthController::class, 'impersonate']);
|
||||
Route::post('/stop_impersonating', [AuthController::class, 'stopImpersonating']);
|
||||
|
||||
Route::get('/list', [UserController::class, 'index']);
|
||||
|
||||
Route::get('/schedules', [ScheduleSlotsController::class, 'index']);
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
<tbody id="table_body">
|
||||
<tr *ngFor="let row of data">
|
||||
<td>
|
||||
<!-- TODO: implement user impersonation -->
|
||||
<i *ngIf="false && auth.profile.can('users-read') && row.id !== auth.profile.auth_user_id" class="fa fa-user me-2" (click)="onUserImpersonate(row.id)"></i>
|
||||
<i *ngIf="auth.profile.can('users-impersonate') && row.id !== auth.profile.id" class="fa fa-user me-2" (click)="onUserImpersonate(row.id)"></i>
|
||||
<img alt="red helmet" src="./assets/icons/red_helmet.png" width="20px" *ngIf="row.chief">
|
||||
<img alt="red helmet" src="./assets/icons/black_helmet.png" width="20px" *ngIf="!row.chief">
|
||||
<ng-container *ngIf="(getTime() - row.last_access) < 30"><u>{{ row.name }}</u></ng-container>
|
||||
|
|
|
@ -74,10 +74,10 @@ export class TableComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
onUserImpersonate(user: number) {
|
||||
if(this.auth.profile.hasRole('SUPER_ADMIN')) {
|
||||
this.auth.impersonate(user).then((user_id) => {
|
||||
if(this.auth.profile.can('users-impersonate')) {
|
||||
this.auth.impersonate(user).then(() => {
|
||||
this.loadTableData();
|
||||
this.userImpersonate.emit(user_id);
|
||||
this.userImpersonate.emit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ export class AuthorizeGuard implements CanActivate {
|
|||
state: RouterStateSnapshot
|
||||
): boolean {
|
||||
console.log(this.authService, route, state);
|
||||
if(this.authService.profile === undefined) {
|
||||
if(this.authService.profile.id === undefined) {
|
||||
console.log("not logged in");
|
||||
this.router.navigate(['login', state.url.replace('/', '')]);
|
||||
return false;
|
||||
|
|
|
@ -43,13 +43,13 @@ export class ListComponent implements OnInit, OnDestroy {
|
|||
|
||||
changeAvailibility(available: 0|1, id?: number|undefined) {
|
||||
if(typeof id === 'undefined') {
|
||||
id = this.auth.profile.auth_user_id;
|
||||
id = this.auth.profile.id;
|
||||
}
|
||||
this.api.post("availability", {
|
||||
id: id,
|
||||
available: available
|
||||
}).then((response) => {
|
||||
let changed_user_msg = parseInt(response.updated_user) === parseInt(this.auth.profile.auth_user_id) ? "La tua disponibilità" : `La disponibilità di ${response.updated_user_name}`;
|
||||
let changed_user_msg = parseInt(response.updated_user_id) === parseInt(this.auth.profile.id) ? "La tua disponibilità" : `La disponibilità di ${response.updated_user_name}`;
|
||||
let msg = available === 1 ? `${changed_user_msg} è stata impostata con successo.` : `${changed_user_msg} è stata rimossa con successo.`;
|
||||
this.toastr.success(msg);
|
||||
this.loadAvailability();
|
||||
|
|
|
@ -12,9 +12,12 @@ export interface LoginResponse {
|
|||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
public profile: any = {
|
||||
private defaultPlaceholderProfile: any = {
|
||||
id: undefined,
|
||||
impersonating: false,
|
||||
can: (permission: string) => false
|
||||
};
|
||||
public profile: any = this.defaultPlaceholderProfile;
|
||||
public authChanged = new Subject<void>();
|
||||
public authLoaded = false;
|
||||
|
||||
|
@ -31,7 +34,7 @@ export class AuthService {
|
|||
resolve();
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
this.profile = undefined;
|
||||
this.profile = this.defaultPlaceholderProfile;
|
||||
reject();
|
||||
}).finally(() => {
|
||||
this.authChanged.next();
|
||||
|
@ -50,7 +53,7 @@ export class AuthService {
|
|||
}
|
||||
|
||||
public isAuthenticated() {
|
||||
return this.profile !== undefined;
|
||||
return this.profile.id !== undefined;
|
||||
}
|
||||
|
||||
public login(username: string, password: string) {
|
||||
|
@ -94,31 +97,48 @@ export class AuthService {
|
|||
})
|
||||
}
|
||||
|
||||
public impersonate(user_id: number): Promise<number> {
|
||||
public impersonate(user_id: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(0);
|
||||
this.api.post(`impersonate/${user_id}`).then(() => {
|
||||
this.loadProfile().then(() => {
|
||||
resolve();
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
this.logout();
|
||||
this.profile.impersonating_user = false;
|
||||
this.logout();
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public stop_impersonating(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.api.post("stop_impersonating").then(() => {
|
||||
resolve();
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public logout(routerDestination?: string[] | undefined) {
|
||||
this.api.post("logout").then((data: any) => {
|
||||
this.profile = undefined;
|
||||
if(routerDestination === undefined) {
|
||||
routerDestination = ["login", "list"];
|
||||
}
|
||||
this.router.navigate(routerDestination);
|
||||
});
|
||||
/*
|
||||
if(this.profile.impersonating_user) {
|
||||
this.stop_impersonating().then((user_id) => {
|
||||
this.stop_impersonating().then(() => {
|
||||
this.loadProfile();
|
||||
});
|
||||
} else {
|
||||
this.profile = undefined;
|
||||
if(routerDestination === undefined) {
|
||||
routerDestination = ["login", "list"];
|
||||
}
|
||||
this.router.navigate(routerDestination);
|
||||
this.api.post("logout").then((data: any) => {
|
||||
this.profile = this.defaultPlaceholderProfile;
|
||||
if(routerDestination === undefined) {
|
||||
routerDestination = ["login", "list"];
|
||||
}
|
||||
this.router.navigate(routerDestination);
|
||||
});
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<div [className]="menuButtonClicked ? 'topnav responsive' : 'topnav'" id="topNavBar" *ngIf="auth.profile !== undefined">
|
||||
<div [className]="menuButtonClicked ? 'topnav responsive' : 'topnav'" id="topNavBar" *ngIf="auth.profile.id !== undefined">
|
||||
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/list" translate>menu.list</a>
|
||||
<a *ngIf="false" routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/services" translate>menu.services</a>
|
||||
<a *ngIf="false" routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/trainings" translate>menu.trainings</a>
|
||||
<a *ngIf="false" routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/logs" translate>menu.logs</a>
|
||||
<a style="float: right;" id="logout">{{ 'menu.hi'|translate|titlecase }}, {{ auth.profile.name }}. <b id="logout-text" (click)="auth.logout()" translate>menu.logout</b></a>
|
||||
<a style="float: right;" id="logout">{{ 'menu.hi'|translate|titlecase }}, {{ auth.profile.name }}. <b id="logout-text" (click)="auth.logout()" translate *ngIf="!auth.profile.impersonating_user">menu.logout</b><b id="logout-text" (click)="auth.logout()" translate *ngIf="auth.profile.impersonating_user">menu.stop_impersonating</b></a>
|
||||
<a class="icon" id="menuButton" (click)="menuButtonClicked = !menuButtonClicked">☰</a>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"trainings": "Trainings",
|
||||
"logs": "Logs",
|
||||
"logout": "Logout",
|
||||
"stop_impersonating": "Stop impersonating",
|
||||
"hi": "hi"
|
||||
},
|
||||
"table": {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"trainings": "Esercitazioni",
|
||||
"logs": "Logs",
|
||||
"logout": "Logout",
|
||||
"stop_impersonating": "Torna al vero account",
|
||||
"hi": "Ciao"
|
||||
},
|
||||
"table": {
|
||||
|
|
Loading…
Reference in New Issue