From 02c5dc5324c9bbb0e816cbe2d6c4d95488af88c0 Mon Sep 17 00:00:00 2001 From: Lennart Carstens-Behrens Date: Thu, 14 Oct 2021 15:14:59 +0200 Subject: [PATCH] [8.x] Add gate policy callback (#39185) * wip * wip * Apply fixes from StyleCI * formatting * wip * Apply fixes from StyleCI Co-authored-by: Taylor Otwell --- src/Illuminate/Auth/Access/Gate.php | 38 ++++++++++++++++++ .../Auth/Access/AuthorizesRequests.php | 4 ++ tests/Auth/AuthAccessGateTest.php | 40 +++++++++++++++++++ .../FoundationAuthorizesRequestsTraitTest.php | 12 ++++++ 4 files changed, 94 insertions(+) diff --git a/src/Illuminate/Auth/Access/Gate.php b/src/Illuminate/Auth/Access/Gate.php index 75eba1dcea8f..1746a2ec6f98 100644 --- a/src/Illuminate/Auth/Access/Gate.php +++ b/src/Illuminate/Auth/Access/Gate.php @@ -279,6 +279,10 @@ public function denies($ability, $arguments = []) */ public function check($abilities, $arguments = []) { + if (is_array($abilities) && class_exists($abilities[0])) { + $abilities = [$abilities]; + } + return collect($abilities)->every(function ($ability) use ($arguments) { return $this->inspect($ability, $arguments)->allowed(); }); @@ -293,6 +297,13 @@ public function check($abilities, $arguments = []) */ public function any($abilities, $arguments = []) { + // Gate::any([Policy::class, ['view', 'create']], $post)... + if (isset($abilities[1]) && is_array($abilities[1])) { + $abilities = collect($abilities[1])->map(function ($ability) use ($abilities) { + return [$abilities[0], $ability]; + })->all(); + } + return collect($abilities)->contains(function ($ability) use ($arguments) { return $this->check($ability, $arguments); }); @@ -556,6 +567,19 @@ protected function resolveAuthCallback($user, $ability, array $arguments) return $callback; } + if (is_array($ability)) { + [$class, $method] = $ability; + + if ($this->canBeCalledWithUser($user, $class, $method)) { + return $this->getCallableFromClassAndMethod($class, $method); + } + } + + if (class_exists($ability) && + $this->canBeCalledWithUser($user, $ability, '__invoke')) { + return $this->getCallableFromClassAndMethod($ability); + } + if (isset($this->stringCallbacks[$ability])) { [$class, $method] = Str::parseCallback($this->stringCallbacks[$ability]); @@ -781,6 +805,20 @@ protected function resolveUser() return call_user_func($this->userResolver); } + /** + * Get a callable from a class and method. + * + * @param string $class + * @param string $method + * @return \Closure + */ + protected function getCallableFromClassAndMethod($class, $method = '__invoke') + { + return function (...$params) use ($class, $method) { + return $this->container->make($class)->{$method}(...$params); + }; + } + /** * Get all of the defined abilities. * diff --git a/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php b/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php index 85a9596f9c57..76b58b30ebaf 100644 --- a/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php +++ b/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php @@ -53,6 +53,10 @@ protected function parseAbilityAndArguments($ability, $arguments) return [$ability, $arguments]; } + if (is_array($ability)) { + return [$ability, $arguments]; + } + $method = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['function']; return [$this->normalizeGuessedAbilityName($method), $ability]; diff --git a/tests/Auth/AuthAccessGateTest.php b/tests/Auth/AuthAccessGateTest.php index c9c0d7d08ef2..8660b465127a 100644 --- a/tests/Auth/AuthAccessGateTest.php +++ b/tests/Auth/AuthAccessGateTest.php @@ -759,6 +759,46 @@ public function testEveryAbilityCheckFailsIfNonePass() $this->assertFalse($gate->check(['edit', 'update'], new AccessGateTestDummy)); } + public function testInspectPolicyCallback() + { + $gate = $this->getBasicGate(); + + $response = $gate->inspect([AccessGateTestPolicyWithAllPermissions::class, 'edit'], new AccessGateTestDummy); + + $this->assertFalse($response->denied()); + $this->assertTrue($response->allowed()); + } + + public function testInspectPolicyCallbackInvoke() + { + $gate = $this->getBasicGate(); + + $response = $gate->inspect(AccessGateTestGuestInvokableClass::class, new AccessGateTestDummy); + + $this->assertFalse($response->denied()); + $this->assertTrue($response->allowed()); + } + + public function testCheckUsingPolicyCallback() + { + $gate = $this->getBasicGate(); + + $this->assertTrue($gate->check( + [AccessGateTestPolicyWithAllPermissions::class, 'edit'], + new AccessGateTestDummy) + ); + } + + public function testAnyUsingPolicyCallback() + { + $gate = $this->getBasicGate(); + + $this->assertTrue($gate->any( + [AccessGateTestPolicyWithAllPermissions::class, ['edit', 'update']], + new AccessGateTestDummy) + ); + } + /** * @dataProvider hasAbilitiesTestDataProvider * diff --git a/tests/Foundation/FoundationAuthorizesRequestsTraitTest.php b/tests/Foundation/FoundationAuthorizesRequestsTraitTest.php index 1005103aaf66..57c829df111f 100644 --- a/tests/Foundation/FoundationAuthorizesRequestsTraitTest.php +++ b/tests/Foundation/FoundationAuthorizesRequestsTraitTest.php @@ -17,6 +17,18 @@ protected function tearDown(): void Container::setInstance(null); } + public function testCallableGateCheck() + { + unset($_SERVER['_test.authorizes.trait']); + + $gate = $this->getBasicGate(); + + $response = (new FoundationTestAuthorizeTraitClass)->authorize([FoundationAuthorizesRequestTestPolicy::class, 'create']); + + $this->assertInstanceOf(Response::class, $response); + $this->assertTrue($_SERVER['_test.authorizes.trait.policy']); + } + public function testBasicGateCheck() { unset($_SERVER['_test.authorizes.trait']);