Skip to content

Commit

Permalink
feat: Adds the ability to authorize a request for settings using @can…
Browse files Browse the repository at this point in the history
…Settings
  • Loading branch information
Roboroads committed Jul 5, 2022
1 parent f0f4370 commit 294ba7f
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 26 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,28 @@ type Mutation {
}
```

> Note: You can use @rules, @trin, @can, etc. like you can when updating models.
> Note: Except for @can, you can use @rules, @trim, etc. like you can when updating models.

### Using @canSettings instead of @can

Since Lighthouse really wants to connect @can to an actual Model, you have to use @canSettings to use the ability to authorize the user for settings.

```graphql
type Mutation {
updateGeneralSettings(
name: String!
url: String!
description: String!
): GeneralSettings! @settings @canSettings(ability: "editSettings")
}
```

```php
//Defining a gate
Gate::define('editSettings', function (User $user, string $settingsClass) {
return $user->isAdmin();
});
```

## Optional configuration

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
}
],
"require": {
"php": ">=7.0",
"php": ">=7.4",
"nuwave/lighthouse": ">=5",
"spatie/laravel-settings": ">=2",
"illuminate/support": ">=8"
Expand Down
64 changes: 64 additions & 0 deletions src/Directives/CanSettingsDirective.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Roboroads\LighthouseSettings\Directives;

use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Auth\CanDirective;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
use Roboroads\LighthouseSettings\Helpers\SettingsInstanceHelper;

class CanSettingsDirective extends CanDirective
{
public static function definition(): string
{
return /** @lang GraphQL */ <<<'GRAPHQL'
"""
Alternative for the @can directive so it can be used with settings
"""
directive @canSettings(
"""
The ability to check permissions for.
"""
ability: String!
"""
The settings class, if different from the type name
"""
class: String
"""
Pass along the client given input data as arguments to `Gate::check`.
"""
injectArgs: Boolean = false
"""
Statically defined arguments that are passed to `Gate::check`.
You may pass arbitrary GraphQL literals,
e.g.: [1, 2, 3] or { foo: "bar" }
"""
args: CanArgs
) on FIELD_DEFINITION
GRAPHQL;
}

public function handleField(FieldValue $fieldValue, Closure $next): FieldValue
{
$previousResolver = $fieldValue->getResolver();
$ability = $this->directiveArgValue('ability');

$fieldValue->setResolver(function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($ability, $previousResolver) {
$gate = $this->gate->forUser($context->user());
$checkArguments = $this->buildCheckArguments($args);

$settingsClass = (new SettingsInstanceHelper($this->definitionNode, $this->directiveArgValue('class')))->getSettingsClass();
$this->authorize($gate, $ability, $settingsClass, $checkArguments);

return $previousResolver($root, $args, $context, $resolveInfo);
});

return $next($fieldValue);
}
}
26 changes: 2 additions & 24 deletions src/Directives/SettingsDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Nuwave\Lighthouse\Support\Contracts\FieldResolver;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
use Roboroads\LighthouseSettings\Exceptions\NotInstanceOfSettingsException;
use Roboroads\LighthouseSettings\Helpers\SettingsInstanceHelper;
use Spatie\LaravelSettings\Settings;

class SettingsDirective extends BaseDirective implements FieldResolver
Expand All @@ -35,30 +36,7 @@ class: String
public function resolveField(FieldValue $fieldValue): FieldValue
{
$fieldValue->setResolver(function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Settings {
// Get name of the settings class
if ($this->directiveHasArgument('class')) {
$settingsClass = $this->directiveArgValue('class');
} else {
$settingsClass = ASTHelper::modelName($this->definitionNode);
}

// Find out if we can instanciate the settings class
try {
$settingsInstance = App::make($settingsClass);
} catch (Exception $ex) {
$originalException = $ex;
try {
$settingsInstance = App::make((config('lighthouse-settings.settings-namespace') ?? '\\App\\Settings') . '\\' . $settingsClass);
} catch (Exception $ex) {
throw $originalException;
}
}

// Instance should be subclass of Settings
if(!is_subclass_of($settingsInstance, Settings::class)) {
throw new NotInstanceOfSettingsException($settingsInstance::class);
}
/* @var Settings $settingsInstance */
$settingsInstance = (new SettingsInstanceHelper($this->definitionNode, $this->directiveArgValue('class')))->getSettingsInstance();

// If @setting is used for a mutation, update the settings
if($resolveInfo->operation->operation === "mutation") {
Expand Down
14 changes: 14 additions & 0 deletions src/Exceptions/ClassNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Roboroads\LighthouseSettings\Exceptions;

use Exception;
use Spatie\LaravelSettings\Settings;

class ClassNotFoundException extends Exception
{
public function __construct(string $triedClass = "")
{
parent::__construct("Could not find settingsclass ".$triedClass);
}
}
62 changes: 62 additions & 0 deletions src/Helpers/SettingsInstanceHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Roboroads\LighthouseSettings\Helpers;

use GraphQL\Language\AST\Node;
use Illuminate\Support\Facades\App;
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
use Roboroads\LighthouseSettings\Exceptions\ClassNotFoundException;
use Roboroads\LighthouseSettings\Exceptions\NotInstanceOfSettingsException;
use Spatie\LaravelSettings\Settings;

class SettingsInstanceHelper
{
protected Node $definitionNode;
protected ?string $manualClass;

public function __construct(Node $definitionNode, ?string $manualClass)
{
$this->definitionNode = $definitionNode;
$this->manualClass = $manualClass;
}


public function getSettingsInstance(): Settings
{
return App::make($this->getSettingsClass());
}

public function getSettingsClass(): string
{
// Get name of the settings class
if ($this->manualClass) {
$settingsClass = $this->manualClass;
} else {
$settingsClass = ASTHelper::modelName($this->definitionNode);
}

if ($this->testSettingsClass($settingsClass)) {
return $settingsClass;
}

$guesssedSettingsClass = (config('lighthouse-settings.settings-namespace') ?? '\\App\\Settings').'\\'.$settingsClass;
if ($this->testSettingsClass($guesssedSettingsClass)) {
return $guesssedSettingsClass;
}

throw new ClassNotFoundException($settingsClass);
}

public function testSettingsClass(string $settingsClass): bool
{
if (class_exists($settingsClass)) {
if (!is_subclass_of($settingsClass, Settings::class)) {
throw new NotInstanceOfSettingsException($settingsClass);
}

return true;
}

return false;
}
}

0 comments on commit 294ba7f

Please sign in to comment.