Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ProcessMaker/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Kernel extends HttpKernel
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
Middleware\SessionStarted::class,
Middleware\AuthenticateSession::class,
Middleware\EnsureAccountAllowsAccess::class,
Middleware\SessionControlKill::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
//\ProcessMaker\Http\Middleware\VerifyCsrfToken::class,
Expand Down
76 changes: 76 additions & 0 deletions ProcessMaker/Http/Middleware/EnsureAccountAllowsAccess.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace ProcessMaker\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use ProcessMaker\Models\User;
use Symfony\Component\HttpFoundation\Response;

class EnsureAccountAllowsAccess
{
/**
* Status values that cannot use the application while authenticated (aligned with LoginController).
*/
private const DENIED_STATUSES = ['BLOCKED', 'INACTIVE'];

/**
* If any active guard has a blocked/inactive user, log out and return the appropriate response.
* Used by the web middleware group and by ProcessMakerAuthenticate (covers all auth:api routes, including packages).
*/
public static function blockingResponseForRequest(Request $request): ?Response
{
foreach (['web', 'api'] as $guard) {
if (!Auth::guard($guard)->check()) {
continue;
}

/** @var User $user */
$user = Auth::guard($guard)->user();
if (!$user instanceof User || !in_array($user->status, self::DENIED_STATUSES, true)) {
continue;
}

return self::denyAccess($request, $guard, $user);
}

return null;
}

public function handle(Request $request, Closure $next): Response
{
$blocked = self::blockingResponseForRequest($request);
if ($blocked !== null) {
return $blocked;
}

return $next($request);
}

public static function denyAccess(Request $request, string $guard, User $user): Response
{
Auth::guard($guard)->logout();

if ($request->hasSession()) {
$request->session()->invalidate();
$request->session()->regenerateToken();
}

if ($guard === 'api' || $request->expectsJson()) {
$message = $user->status === 'BLOCKED'
? __('Account locked after too many failed attempts. Contact administrator.')
: __('Unauthorized');

return response()->json(['message' => $message], 401);
}

return redirect()
->guest(route('login'))
->withErrors([
'username' => $user->status === 'BLOCKED'
? __('Account locked after too many failed attempts. Contact administrator.')
: __('These credentials do not match our records.'),
]);
}
}
18 changes: 18 additions & 0 deletions ProcessMaker/Http/Middleware/ProcessMakerAuthenticate.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,29 @@

namespace ProcessMaker\Http\Middleware;

use Closure;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Support\Str;

class ProcessMakerAuthenticate extends Authenticate
{
/**
* {@inheritdoc}
*
* After a successful authenticate(), reject BLOCKED/INACTIVE users so every auth:api route
* (core and packages) is covered without listing middleware per route file.
*/
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($request, $guards);

if ($blocked = EnsureAccountAllowsAccess::blockingResponseForRequest($request)) {
return $blocked;
}

return $next($request);
}

protected function authenticate($request, array $guards)
{
$this->addAcceptJsonHeaderIfApiCall($request, $guards);
Expand Down
Loading