feat: use PHP attribute to mark requests disabled in demo (#1873)

This commit is contained in:
Phan An 2024-11-08 18:40:59 +01:00 committed by GitHub
parent d900c9cb26
commit 22c16b996f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 104 additions and 14 deletions

View file

@ -0,0 +1,14 @@
<?php
namespace App\Attributes;
use Attribute;
use Illuminate\Http\Response;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
class DisabledInDemo
{
public function __construct(public int $code = Response::HTTP_FORBIDDEN)
{
}
}

View file

@ -2,17 +2,17 @@
namespace App\Http\Controllers\API; namespace App\Http\Controllers\API;
use App\Attributes\DisabledInDemo;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\API\ForgotPasswordRequest; use App\Http\Requests\API\ForgotPasswordRequest;
use App\Services\AuthenticationService; use App\Services\AuthenticationService;
use Illuminate\Http\Response; use Illuminate\Http\Response;
#[DisabledInDemo]
class ForgotPasswordController extends Controller class ForgotPasswordController extends Controller
{ {
public function __invoke(ForgotPasswordRequest $request, AuthenticationService $auth) public function __invoke(ForgotPasswordRequest $request, AuthenticationService $auth)
{ {
static::disableInDemo();
return $auth->trySendResetPasswordLink($request->email) return $auth->trySendResetPasswordLink($request->email)
? response()->noContent() ? response()->noContent()
: response('', Response::HTTP_NOT_FOUND); : response('', Response::HTTP_NOT_FOUND);

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers\API\Podcast; namespace App\Http\Controllers\API\Podcast;
use App\Attributes\DisabledInDemo;
use App\Exceptions\UserAlreadySubscribedToPodcast; use App\Exceptions\UserAlreadySubscribedToPodcast;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\API\Podcast\PodcastStoreRequest; use App\Http\Requests\API\Podcast\PodcastStoreRequest;
@ -29,10 +30,9 @@ class PodcastController extends Controller
return PodcastResourceCollection::make($this->podcastRepository->getAllByUser($this->user)); return PodcastResourceCollection::make($this->podcastRepository->getAllByUser($this->user));
} }
#[DisabledInDemo]
public function store(PodcastStoreRequest $request) public function store(PodcastStoreRequest $request)
{ {
self::disableInDemo();
try { try {
return PodcastResource::make($this->podcastService->addPodcast($request->url, $this->user)); return PodcastResource::make($this->podcastService->addPodcast($request->url, $this->user));
} catch (UserAlreadySubscribedToPodcast) { } catch (UserAlreadySubscribedToPodcast) {

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers\API; namespace App\Http\Controllers\API;
use App\Attributes\DisabledInDemo;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\API\ProfileUpdateRequest; use App\Http\Requests\API\ProfileUpdateRequest;
use App\Http\Resources\UserResource; use App\Http\Resources\UserResource;
@ -30,10 +31,9 @@ class ProfileController extends Controller
return UserResource::make($this->user); return UserResource::make($this->user);
} }
#[DisabledInDemo(Response::HTTP_NO_CONTENT)]
public function update(ProfileUpdateRequest $request) public function update(ProfileUpdateRequest $request)
{ {
static::disableInDemo(Response::HTTP_NO_CONTENT);
// If the user is not using SSO, we need to verify their current password. // If the user is not using SSO, we need to verify their current password.
throw_if( throw_if(
!$this->user->is_sso && !$this->hash->check($request->current_password, $this->user->password), !$this->user->is_sso && !$this->hash->check($request->current_password, $this->user->password),

View file

@ -2,17 +2,17 @@
namespace App\Http\Controllers\API; namespace App\Http\Controllers\API;
use App\Attributes\DisabledInDemo;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\API\ResetPasswordRequest; use App\Http\Requests\API\ResetPasswordRequest;
use App\Services\AuthenticationService; use App\Services\AuthenticationService;
use Illuminate\Http\Response; use Illuminate\Http\Response;
#[DisabledInDemo]
class ResetPasswordController extends Controller class ResetPasswordController extends Controller
{ {
public function __invoke(ResetPasswordRequest $request, AuthenticationService $auth) public function __invoke(ResetPasswordRequest $request, AuthenticationService $auth)
{ {
static::disableInDemo();
return $auth->tryResetPasswordUsingBroker($request->email, $request->password, $request->token) return $auth->tryResetPasswordUsingBroker($request->email, $request->password, $request->token)
? response()->noContent() ? response()->noContent()
: response('', Response::HTTP_UNPROCESSABLE_ENTITY); : response('', Response::HTTP_UNPROCESSABLE_ENTITY);

View file

@ -5,7 +5,6 @@ namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller as BaseController; use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController abstract class Controller extends BaseController
@ -13,9 +12,4 @@ abstract class Controller extends BaseController
use AuthorizesRequests; use AuthorizesRequests;
use DispatchesJobs; use DispatchesJobs;
use ValidatesRequests; use ValidatesRequests;
protected static function disableInDemo(int $code = Response::HTTP_FORBIDDEN): void
{
abort_if(config('koel.misc.demo'), $code);
}
} }

View file

@ -6,6 +6,7 @@ use App\Http\Middleware\AudioAuthenticate;
use App\Http\Middleware\Authenticate; use App\Http\Middleware\Authenticate;
use App\Http\Middleware\EncryptCookies; use App\Http\Middleware\EncryptCookies;
use App\Http\Middleware\ForceHttps; use App\Http\Middleware\ForceHttps;
use App\Http\Middleware\HandleDemoMode;
use App\Http\Middleware\ObjectStorageAuthenticate; use App\Http\Middleware\ObjectStorageAuthenticate;
use App\Http\Middleware\ThrottleRequests; use App\Http\Middleware\ThrottleRequests;
use App\Http\Middleware\TrimStrings; use App\Http\Middleware\TrimStrings;
@ -48,10 +49,12 @@ class Kernel extends HttpKernel
StartSession::class, StartSession::class,
VerifyCsrfToken::class, VerifyCsrfToken::class,
SubstituteBindings::class, SubstituteBindings::class,
HandleDemoMode::class,
], ],
'api' => [ 'api' => [
'throttle:60,1', 'throttle:60,1',
SubstituteBindings::class, SubstituteBindings::class,
HandleDemoMode::class,
], ],
]; ];

View file

@ -0,0 +1,44 @@
<?php
namespace App\Http\Middleware;
use App\Attributes\DisabledInDemo;
use Closure;
use Illuminate\Http\Request;
use ReflectionClass;
use ReflectionMethod;
use Symfony\Component\HttpFoundation\Response;
class HandleDemoMode
{
private static function ensureRequestIsAllowedInDemoMode(Request $request): void
{
$route = $request->route();
$class = $route->getControllerClass();
$method = $route->getActionMethod();
$controllerReflection = new ReflectionClass($class);
foreach ($controllerReflection->getAttributes(DisabledInDemo::class) as $attribute) {
abort($attribute->newInstance()->code);
}
$methodReflection = new ReflectionMethod($class, $method);
foreach ($methodReflection->getAttributes(DisabledInDemo::class) as $attribute) {
abort($attribute->newInstance()->code);
}
}
/**
* @param Closure(Request): Response $next
*/
public function handle(Request $request, Closure $next): Response
{
if (config('koel.misc.demo')) {
self::ensureRequestIsAllowedInDemoMode($request);
}
return $next($request);
}
}

View file

@ -69,4 +69,25 @@ class ForgotPasswordTest extends TestCase
self::assertTrue(Hash::check('old-password', $user->refresh()->password)); self::assertTrue(Hash::check('old-password', $user->refresh()->password));
Event::assertNotDispatched(PasswordReset::class); Event::assertNotDispatched(PasswordReset::class);
} }
#[Test]
public function disabledInDemo(): void
{
config(['koel.misc.demo' => true]);
$user = create_user();
$this->post('/api/reset-password', [
'email' => $user->email,
'password' => 'new-password',
'token' => Password::createToken($user),
])->assertForbidden();
}
public function tearDown(): void
{
config(['koel.misc.demo' => false]);
parent::tearDown();
}
} }

View file

@ -101,4 +101,18 @@ class ProfileTest extends TestCase
self::assertNull($user->getRawOriginal('avatar')); self::assertNull($user->getRawOriginal('avatar'));
} }
#[Test]
public function disabledInDemo(): void
{
config(['koel.misc.demo' => true]);
$user = create_user(['password' => Hash::make('secret')]);
$this->putAs('api/me', [
'name' => 'Foo',
'email' => 'bar@baz.com',
'current_password' => 'secret',
], $user)
->assertNoContent();
}
} }