Added moderator impersonation

This commit is contained in:
Julien Nahum
2024-01-19 14:27:04 +01:00
parent a651c60808
commit 42c65ae06f
12 changed files with 179 additions and 7 deletions

View File

@@ -12,7 +12,7 @@ class ImpersonationController extends Controller
{
public function __construct()
{
$this->middleware('admin');
$this->middleware('moderator');
}
public function impersonate($identifier) {
@@ -29,12 +29,33 @@ class ImpersonationController extends Controller
}
}
if (!$user) return $this->error([
'message'=> 'User not found.'
if (!$user) {
return $this->error([
'message'=> 'User not found.'
]);
} else if ($user->admin) {
return $this->error([
'message' => 'You cannot impersonate an admin.',
]);
}
\Log::warning('Impersonation started',[
'from_id' => auth()->id(),
'from_email' => auth()->user()->email,
'target_id' => $user->id,
'target_email' => $user->id,
]);
// Be this user
$token = auth()->login($user);
if (auth()->user()->moderator) {
$token = auth()->claims([
'impersonating' => true,
'impersonator_id' => auth()->id(),
])->login($user);
} else {
$token = auth()->login($user);
}
return $this->success([
'token' => $token
]);

View File

@@ -5,7 +5,9 @@ namespace App\Http;
use App\Http\Middleware\AcceptsJsonMiddleware;
use App\Http\Middleware\AuthenticateJWT;
use App\Http\Middleware\CustomDomainRestriction;
use App\Http\Middleware\ImpersonationMiddleware;
use App\Http\Middleware\IsAdmin;
use App\Http\Middleware\IsModerator;
use App\Http\Middleware\IsNotSubscribed;
use App\Http\Middleware\IsSubscribed;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
@@ -58,6 +60,7 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
ImpersonationMiddleware::class,
],
];
@@ -72,6 +75,7 @@ class Kernel extends HttpKernel
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'admin' => IsAdmin::class,
'moderator' => IsModerator::class,
'subscribed' => IsSubscribed::class,
'not-subscribed' => IsNotSubscribed::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ImpersonationMiddleware
{
public const ADMIN_LOG_PREFIX = '[admin_action] ';
const LOG_ROUTES = [
'open.forms.store',
'open.forms.update',
'open.forms.duplicate',
'open.forms.regenerate-link',
];
const ALLOWED_ROUTES = [
'logout',
// Forms
'forms.ai.generate',
'forms.ai.show',
'forms.assets.show',
'forms.show',
'forms.answer',
'forms.fetchSubmission',
'forms.users.index',
'open.forms.index-all',
'open.forms.store',
'open.forms.assets.upload',
'open.forms.update',
'open.forms.duplicate',
'open.forms.regenerate-link',
'open.forms.submissions',
'open.forms.submissions.file',
// Workspaces
'open.workspaces.index',
'open.workspaces.create',
'open.workspaces.delete',
'open.workspaces.save-custom-domains',
'open.workspaces.databases.search',
'open.workspaces.databases.show',
'open.workspaces.form.stats',
'open.workspaces.forms.index',
'open.workspaces.users.index',
'templates.index',
'templates.create',
'templates.update',
'templates.show',
'user.current',
'local.temp',
];
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
if (!auth()->check() ||
!auth()->payload()->get('impersonating')) {
return $next($request);
}
// Check that route is allowed
$routeName = $request->route()->getName();
if (!in_array($routeName, self::ALLOWED_ROUTES)) {
return response([
'message' => 'Unauthorized when impersonating',
'route' => $routeName,
'impersonator' => auth()->payload()->get('impersonator_id'),
'impersonated_account' => auth()->id(),
'url' => $request->fullUrl(),
'payload' => $request->all()
], 403);
} else if (in_array($routeName, self::LOG_ROUTES)) {
\Log::warning(self::ADMIN_LOG_PREFIX . 'Impersonator action', [
'route' => $routeName,
'url' => $request->fullUrl(),
'impersonated_account' => auth()->id(),
'impersonator' => auth()->payload()->get('impersonator_id'),
'payload' => $request->all()
]);
}
return $next($request);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class IsModerator
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if ($request->user() && !$request->user()->moderator) {
// This user is not a paying customer...
if ($request->expectsJson()) {
return response([
'message' => 'You are not allowed.',
'type' => 'error',
], 403);
}
return redirect('home');
}
return $next($request);
}
}

View File

@@ -18,6 +18,7 @@ class UserResource extends JsonResource
'is_subscribed' => $this->is_subscribed,
'has_enterprise_subscription' => $this->has_enterprise_subscription,
'admin' => $this->admin,
'moderator' => $this->moderator,
'template_editor' => $this->template_editor,
'has_customer_id' => $this->has_customer_id,
'has_forms' => $this->has_forms,

View File

@@ -102,6 +102,11 @@ class User extends Authenticatable implements JWTSubject
return in_array($this->email, config('opnform.admin_emails'));
}
public function getModeratorAttribute()
{
return in_array($this->email, config('opnform.moderator_emails')) || $this->admin;
}
public function getTemplateEditorAttribute()
{
return $this->admin || in_array($this->email, config('opnform.template_editor_emails'));