Added option for admins to impersonate other users

Inspired by: https://github.com/nextcloud/impersonate
This commit is contained in:
Julian Prieber 2023-07-13 18:22:50 +02:00
parent 9933713d91
commit 3f62c84406
8 changed files with 231 additions and 2 deletions

View File

@ -665,4 +665,54 @@ public function SendTestMail(Request $request)
{ {
return view('/panel/theme'); return view('/panel/theme');
} }
//Removes impersonation if authenticated
public function authAs(request $request)
{
$userID = $request->id;
$token = $request->token;
$user = User::find($userID);
if($user->remember_token == $token){
$user->auth_as = null;
$user->remember_token = null;
$user->save();
setcookie("display_auth_nav", "", time() - 3600, "/");
Auth::loginUsingId($userID);
return redirect('/admin/users/all');
} else {
return redirect('');
}
}
//Removes impersonation if authenticated
public function authAsID(request $request)
{
$adminUser = User::whereNotNull('auth_as')->where('role', 'admin')->first();
if (!$adminUser) {
$userID = $request->id;
$id = Auth::user()->id;
$user = User::find($id);
$user->auth_as = $userID;
$user->save();
return redirect('dashboard');
} else {
return redirect('admin/users/all');
}
}
} }

View File

@ -65,5 +65,6 @@ class Kernel extends HttpKernel
'admin' => \App\Http\Middleware\admin::class, 'admin' => \App\Http\Middleware\admin::class,
'blocked' => \App\Http\Middleware\CheckBlockedUser::class, 'blocked' => \App\Http\Middleware\CheckBlockedUser::class,
'max.users' => \App\Http\Middleware\MaxUsers::class, 'max.users' => \App\Http\Middleware\MaxUsers::class,
'impersonate' => \App\Http\Middleware\Impersonate::class,
]; ];
} }

View File

@ -0,0 +1,155 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use App\Models\User;
use Closure;
class Impersonate
{
public function handle($request, Closure $next)
{
$adminUser = User::whereNotNull('auth_as')->where('role', 'admin')->first();
if ($adminUser) {
$originalUser = $adminUser->id;
$id = is_numeric($adminUser->auth_as) ? $adminUser->auth_as : $adminUser->id;
$user = User::find($id);
$name = $user->name;
if(Auth::user()->id === $originalUser) {
// Generate unique token
$token = Str::random(60);
if(\Route::currentRouteName() !== 'authAs'){
$adminUser->remember_token = $token;
$adminUser->save();
echo "<script>window.location.href = '".url('studio/links')."';</script>";
}
Auth::loginUsingId($id);
setcookie("display_auth_nav", "true", time() + (10 * 365 * 24 * 60 * 60), "/");
}
if(isset($_COOKIE['display_auth_nav'])) {
if (file_exists(base_path(findAvatar($id)))) {
$img = '<img alt="avatar" class="iimg irounded" src="' . url(findAvatar($id)) . '">';
} elseif (file_exists(base_path("assets/linkstack/images/").findFile('avatar'))) {
$img = '<img alt="avatar" class="iimg irounded" src="' . url("assets/linkstack/images/") . "/" . findFile('avatar') . '">';
} else {
$img = '<img alt="avatar" class="iimg" src="' . asset('assets/linkstack/images/logo.svg') . '">';
}
$dashboard = url('dashboard');
$URL = url('/auth-as');
$csrf = csrf_token();
$remember_token = User::find($originalUser);
$token = $remember_token->remember_token;
$customHtml =
<<<EOD
<style>
.ibar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 67px;
background-color: #4d4c51;
z-index: 911;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.itext1 {
color: white;
font-family: "Inter", sans-serif;
font-size: 18px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 17px 16px;
}
.itext1 span a {
display: flex;
align-items: center;
justify-content: space-between;
}
.itext1 a {
color: white;
text-decoration: none;
}
.itext1 svg {
width: 32px;
height: 32px;
fill: currentColor;
margin-left: 8px;
margin-bottom: 4px;
}
.iimg {
width: 32px;
height: 32px;
margin-right: 8px;
margin-bottom: 3px;
}
.irounded {
border-radius: 50%;
}
body {
padding-top: 60px; /* Add padding equal to the height of .ibar */
}
</style>
<div class="ibar">
<p class="itext1">
<span>
<a href="$dashboard">$img $name</a>
</span>
<a style="cursor:pointer" onclick="document.getElementById('submitForm').submit(); return false;">
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-x" viewBox="0 0 16 16">
<path
d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"
/>
</svg>
</a>
</p>
</div>
<form id="submitForm" action="$URL" method="POST" style="display: none;">
<input type="hidden" name="_token" value="$csrf">
<input type="hidden" name="token" value="$token">
<input type="hidden" name="id" value="$originalUser">
</form>
<script>
function submitForm() {
document.getElementById('submitForm').submit();
}
</script>
EOD;;
} else {$customHtml = "";}
$response = $next($request);
$content = $response->getContent();
$modifiedContent = preg_replace('/<body([^>]*)>/', "<body$1>{$customHtml}", $content);
$response->setContent($modifiedContent);
return $response;
} else {
if(isset($_COOKIE['display_auth_nav'])) {
setcookie("display_auth_nav", "", time() - 3600, "/");
Auth::logout();
}
return $next($request);
}
}
}

View File

@ -26,6 +26,7 @@ class CreateUsersTable extends Migration
$table->rememberToken(); $table->rememberToken();
$table->timestamps(); $table->timestamps();
$table->string('theme')->nullable(); $table->string('theme')->nullable();
$table->unsignedBigInteger('auth_as')->nullable();
}); });
} }

View File

@ -521,6 +521,7 @@ return [
# Tooltips # Tooltips
'tt.Delete' => 'Delete', 'tt.Delete' => 'Delete',
'tt.Impersonate' => 'Impersonate',
'tt.Edit' => 'Edit', 'tt.Edit' => 'Edit',
'tt.All links' => 'All links', 'tt.All links' => 'All links',

View File

@ -154,6 +154,14 @@ use App\Models\Page;
} catch (exception $e) {} } catch (exception $e) {}
Schema::enableForeignKeyConstraints(); Schema::enableForeignKeyConstraints();
// Adds new column to the users table
try {
if (!Schema::hasColumn('users', 'auth_as')) {
Schema::table('users', function (Blueprint $table) {
$table->unsignedBigInteger('auth_as')->nullable();
});
}} catch (exception $e) {}
try { try {
DB::table('link_types')->updateOrInsert([ DB::table('link_types')->updateOrInsert([
'typename' => 'text', 'typename' => 'text',

View File

@ -1,3 +1,5 @@
<?php use App\Models\User; ?>
@extends('layouts.sidebar') @extends('layouts.sidebar')
@section('content') @section('content')
@ -117,6 +119,15 @@
</svg> </svg>
</span> </span>
</a> </a>
@php $adminUser = User::whereNotNull('auth_as')->where('role', 'admin')->first(); @endphp
<a class="btn btn-sm btn-icon btn-primary" style="@if(!$adminUser) background:#3a57e8;border-color:#3a57e8; @else background:#6c757d;border-color:#6c757d; @endif" data-bs-toggle="tooltip" data-bs-placement="top" data-original-title="{{__('messages.tt.Impersonate')}}" @if(!$adminUser) href="{{ route('authAsID', $user->id ) }}" @endif aria-label="Impersonate" data-bs-original-title="Impersonate">
<span class="btn-inner">
<svg class="icon-20" width="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.59151 15.2068C13.2805 15.2068 16.4335 15.7658 16.4335 17.9988C16.4335 20.2318 13.3015 20.8068 9.59151 20.8068C5.90151 20.8068 2.74951 20.2528 2.74951 18.0188C2.74951 15.7848 5.88051 15.2068 9.59151 15.2068Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.59157 12.0198C7.16957 12.0198 5.20557 10.0568 5.20557 7.63476C5.20557 5.21276 7.16957 3.24976 9.59157 3.24976C12.0126 3.24976 13.9766 5.21276 13.9766 7.63476C13.9856 10.0478 12.0356 12.0108 9.62257 12.0198H9.59157Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M16.4829 10.8815C18.0839 10.6565 19.3169 9.28253 19.3199 7.61953C19.3199 5.98053 18.1249 4.62053 16.5579 4.36353" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M18.5952 14.7322C20.1462 14.9632 21.2292 15.5072 21.2292 16.6272C21.2292 17.3982 20.7192 17.8982 19.8952 18.2112" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</span>
</a>
<a class="btn btn-sm btn-icon btn-danger confirmation" data-bs-toggle="tooltip" data-bs-placement="top" data-original-title="{{__('messages.tt.Delete')}}" href="{{ route('deleteUser', ['id' => $user->id] ) }}" aria-label="Delete" data-bs-original-title="Delete"> <a class="btn btn-sm btn-icon btn-danger confirmation" data-bs-toggle="tooltip" data-bs-placement="top" data-original-title="{{__('messages.tt.Delete')}}" href="{{ route('deleteUser', ['id' => $user->id] ) }}" aria-label="Delete" data-bs-original-title="Delete">
<span class="btn-inner"> <span class="btn-inner">
<svg class="icon-20" width="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor"> <svg class="icon-20" width="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">

View File

@ -96,7 +96,7 @@ Route::get('/vcard/{id?}', [UserController::class, 'vcard'])->name('vcard');
Route::get('/demo-page', [App\Http\Controllers\HomeController::class, 'demo'])->name('demo'); Route::get('/demo-page', [App\Http\Controllers\HomeController::class, 'demo'])->name('demo');
Route::middleware(['auth', 'blocked'])->group(function () { Route::middleware(['auth', 'blocked', 'impersonate'])->group(function () {
//User route //User route
Route::group([ Route::group([
'middleware' => env('REGISTER_AUTH'), 'middleware' => env('REGISTER_AUTH'),
@ -128,6 +128,7 @@ Route::post('/edit-icons', [UserController::class, 'editIcons'])->name('editIcon
Route::get('/clearIcon/{id}', [UserController::class, 'clearIcon'])->name('clearIcon'); Route::get('/clearIcon/{id}', [UserController::class, 'clearIcon'])->name('clearIcon');
Route::get('/studio/page/delprofilepicture', [UserController::class, 'delProfilePicture'])->name('delProfilePicture'); Route::get('/studio/page/delprofilepicture', [UserController::class, 'delProfilePicture'])->name('delProfilePicture');
Route::get('/studio/delete-user/{id}', [UserController::class, 'deleteUser'])->name('deleteUser')->middleware('verified'); Route::get('/studio/delete-user/{id}', [UserController::class, 'deleteUser'])->name('deleteUser')->middleware('verified');
Route::post('/auth-as', [AdminController::class, 'authAs'])->name('authAs');
if(env('ALLOW_USER_EXPORT') != false){ if(env('ALLOW_USER_EXPORT') != false){
Route::get('/export-links', [UserController::class, 'exportLinks'])->name('exportLinks'); Route::get('/export-links', [UserController::class, 'exportLinks'])->name('exportLinks');
Route::get('/export-all', [UserController::class, 'exportAll'])->name('exportAll'); Route::get('/export-all', [UserController::class, 'exportAll'])->name('exportAll');
@ -144,7 +145,7 @@ Route::get('/studio/linkparamform_part/{typeid}/{linkid}', [LinkTypeViewControll
Route::get('/social-auth/{provider}/callback', [SocialLoginController::class, 'providerCallback']); Route::get('/social-auth/{provider}/callback', [SocialLoginController::class, 'providerCallback']);
Route::get('/social-auth/{provider}', [SocialLoginController::class, 'redirectToProvider'])->name('social.redirect'); Route::get('/social-auth/{provider}', [SocialLoginController::class, 'redirectToProvider'])->name('social.redirect');
Route::middleware(['auth', 'blocked'])->group(function () { Route::middleware(['auth', 'blocked', 'impersonate'])->group(function () {
//Admin route //Admin route
Route::group([ Route::group([
'middleware' => 'admin', 'middleware' => 'admin',
@ -179,6 +180,7 @@ Route::group([
Route::get('/admin/config', [AdminController::class, 'showConfig'])->name('showConfig'); Route::get('/admin/config', [AdminController::class, 'showConfig'])->name('showConfig');
Route::post('/admin/config', [AdminController::class, 'editConfig'])->name('editConfig'); Route::post('/admin/config', [AdminController::class, 'editConfig'])->name('editConfig');
Route::get('/send-test-email', [AdminController::class, 'SendTestMail'])->name('SendTestMail'); Route::get('/send-test-email', [AdminController::class, 'SendTestMail'])->name('SendTestMail');
Route::get('/auth-as/{id}', [AdminController::class, 'authAsID'])->name('authAsID');
Route::get('/theme-updater', function () {return view('studio/theme-updater', []);}); Route::get('/theme-updater', function () {return view('studio/theme-updater', []);});
Route::get('/update', function () {return view('update', []);}); Route::get('/update', function () {return view('update', []);});
Route::get('/backup', function () {return view('backup', []);}); Route::get('/backup', function () {return view('backup', []);});