Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/authorization #102

Merged
merged 11 commits into from
May 13, 2021
37 changes: 37 additions & 0 deletions src/Auth/Access/AuthorizesRequests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Radiate\Auth\Access;

use Radiate\Support\Facades\Gate;

trait AuthorizesRequests
{
/**
* Authorize a given action for the current user.
*
* @param string $ability
* @param array|mixed $arguments
* @return \Radiate\Auth\Access\Gate
*
* @throws \Radiate\Foundation\Http\Exceptions\HttpResponseException
*/
public function authorize(string $ability, $arguments = [])
{
return Gate::authorize($ability, $arguments);
}

/**
* Authorize a given action for a user.
*
* @param \Radiate\Database\Models\User|mixed $user
* @param string $ability
* @param array|mixed $arguments
* @return \Radiate\Auth\Access\Gate
*
* @throws \Radiate\Foundation\Http\Exceptions\HttpResponseException
*/
public function authorizeForUser($user, string $ability, $arguments = [])
{
return Gate::forUser($user)->authorize($ability, $arguments);
}
}
154 changes: 152 additions & 2 deletions src/Auth/Access/Gate.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Radiate\Foundation\Http\Exceptions\HttpResponseException;
use Radiate\Support\Arr;
use Radiate\Support\Collection;
use Radiate\Support\Str;

class Gate
{
Expand Down Expand Up @@ -163,13 +164,162 @@ public function raw(string $ability, array $arguments = [])

$result = $user && $this->hasCoreCapability($user, $ability, $arguments);

if (!$callback = $this->abilities[$ability]) {
if (!$this->has($ability) && !$this->hasPolicy($arguments)) {
return $result;
}

return $this->callAuthCallback($user, $ability, $arguments);
}

/**
* Determine if the first argument is a policy
*
* @param array $arguments
* @return boolean
*/
protected function hasPolicy(array $arguments)
{
return isset($arguments[0]) && !is_null($this->getPolicyFor($arguments[0]));
}

/**
* Resolve and call the appropriate authorization callback.
*
* @param \Radiate\Database\Models\User|null $user
* @param string $ability
* @param array $arguments
* @return bool
*/
protected function callAuthCallback(?User $user, string $ability, array $arguments)
{
$callback = $this->resolveAuthCallback($user, $ability, $arguments);

return $callback($user, ...$arguments);
}

/**
* Resolve the callable for the given ability and arguments.
*
* @param \Radiate\Database\Models\User|null $user
* @param string $ability
* @param array $arguments
* @return callable
*/
protected function resolveAuthCallback(?User $user, string $ability, array $arguments)
{
if (
isset($arguments[0]) &&
!is_null($policy = $this->getPolicyFor($arguments[0])) &&
$callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)
) {
return $callback;
}

if ($this->has($ability)) {
return $this->abilities[$ability];
}

return function () {
//
};
}

/**
* Resolve the callback for a policy check.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @param mixed $policy
* @return bool|callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments, $policy)
{
if (!is_callable([$policy, $this->formatAbilityToMethod($ability)])) {
return false;
}

return function () use ($user, $ability, $arguments, $policy) {
$method = $this->formatAbilityToMethod($ability);

return $this->callPolicyMethod($policy, $method, $user, $arguments);
};
}

/**
* Format the policy ability into a method name.
*
* @param string $ability
* @return string
*/
protected function formatAbilityToMethod(string $ability)
{
return Str::camel($ability);
}

/**
* Call the appropriate method on the given policy.
*
* @param mixed $policy
* @param string $method
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $arguments
* @return mixed
*/
protected function callPolicyMethod($policy, $method, $user, array $arguments)
{
// If this first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument array
// because this policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}

if (!is_callable([$policy, $method])) {
return;
}

return $policy->{$method}($user, ...$arguments);
}

/**
* Get a policy instance for a given class.
*
* @param object|string $class
* @return mixed
*/
public function getPolicyFor($class)
{
if (is_object($class)) {
$class = get_class($class);
}

if (!is_string($class)) {
return;
}

if (isset($this->policies[$class])) {
return $this->resolvePolicy($this->policies[$class]);
}

foreach ($this->policies as $expected => $policy) {
if (is_subclass_of($class, $expected)) {
return $this->resolvePolicy($policy);
}
}
}

/**
* Build a policy class instance of the given type.
*
* @param object|string $class
* @return mixed
*/
public function resolvePolicy($class)
{
return $this->app->make($class);
}

/**
* Determine if the user has a core capability
*
Expand Down Expand Up @@ -224,7 +374,7 @@ public function forUser(User $user)
};

return new static(
$this->container,
$this->app,
$callback,
$this->abilities,
$this->policies
Expand Down
4 changes: 3 additions & 1 deletion src/Auth/AuthServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public function register(): void
*/
public function boot()
{
//
$this->commands([
\Radiate\Auth\Console\MakePolicy::class,
]);
}
}
86 changes: 86 additions & 0 deletions src/Auth/Console/MakePolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Theme\Auth\Console;

use LogicException;
use Radiate\Console\GeneratorCommand;

class MakePolicy extends GeneratorCommand
{
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Policy';

/**
* The console command signature.
*
* @var string
*/
protected $signature = 'make:policy {name : The name of the policy}
{--force : Overwrite the policy if it exists}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';

/**
* Replace the class name for the given stub.
*
* @param string $stub
* @param string $name
* @return string
*/
protected function replaceClass(string $stub, string $name): string
{
$stub = parent::replaceClass($stub, $name);

$userModel = $this->userProviderModel();

return str_replace('{{ userModel }}', $userModel, $stub);
}

/**
* Get the model for the guard's user provider.
*
* @return string|null
*/
protected function userProviderModel()
{
$config = $this->app['config'];

$provider = $config->get('auth.default');

if (is_null($config->get('auth.providers.' . $provider))) {
throw new LogicException('The [' . $provider . '] provider is not defined in your "auth" configuration file.');
}

return $config->get('auth.providers.' . $provider . '.model');
}

/**
* Get the stub path.
*
* @return string
*/
protected function getStub()
{
return __DIR__ . '/stubs/policy.stub';
}

/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace(string $rootNamespace)
{
return $rootNamespace . '\\Policies';
}
}
18 changes: 18 additions & 0 deletions src/Auth/Console/stubs/policy.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace {{ namespace }};

use {{ userModel }};

class {{ class }}
{
/**
* Create a new policy instance.
*
* @return void
*/
public function __construct()
{
//
}
}
10 changes: 5 additions & 5 deletions src/Database/Models/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,22 +266,22 @@ public function setUpdatedAtAttribute(DateTimeImmutable $value)
/**
* Get the author id
*
* @return string|null
* @return int
*/
public function getAuthorIdAttribute()
{
return $this->attributes['post_author'];
return (int) $this->attributes['post_author'];
}

/**
* Set the author id
*
* @param string $value
* @param int $value
* @return void
*/
public function setAuthorIdAttribute(string $value): void
public function setAuthorIdAttribute(int $value): void
{
$this->attributes['post_author'] = $value;
$this->attributes['post_author'] = (int) $value;
}

/**
Expand Down
Loading