Skip to content

Commit

Permalink
fix(graphql): enable graphql policies
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Oct 4, 2024
1 parent 69c0cd2 commit 37c34db
Show file tree
Hide file tree
Showing 18 changed files with 423 additions and 140 deletions.
38 changes: 30 additions & 8 deletions src/GraphQl/Type/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
use ApiPlatform\GraphQl\Exception\InvalidTypeException;
use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface;
use ApiPlatform\GraphQl\Type\Definition\TypeInterface;
use ApiPlatform\Metadata\FilterInterface;
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Operation;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\Subscription;
use ApiPlatform\Metadata\InflectorInterface;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
Expand Down Expand Up @@ -296,20 +298,40 @@ public function resolveResourceArgs(array $args, Operation $operation): array
continue;
}

$filter = $this->filterLocator->get($filterId);
$parsedKey = explode('[:property]', $key);
$flattenFields = [];
foreach ($this->filterLocator->get($filterId)->getDescription($operation->getClass()) as $key => $value) {
$values = [];
parse_str($key, $values);
if (isset($values[$parsedKey[0]])) {
$values = $values[$parsedKey[0]];

if ($filter instanceof FilterInterface) {
foreach ($filter->getDescription($operation->getClass()) as $name => $value) {
$values = [];
parse_str($name, $values);
if (isset($values[$parsedKey[0]])) {
$values = $values[$parsedKey[0]];
}

$name = key($values);
$flattenFields[] = ['name' => $name, 'required' => $value['required'] ?? null, 'description' => $value['description'] ?? null, 'leafs' => $values[$name], 'type' => $value['type'] ?? 'string'];
}

$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0]);
}

if ($filter instanceof OpenApiParameterFilterInterface) {
foreach ($filter->getOpenApiParameters($parameter) as $value) {
$values = [];
parse_str($value->getName(), $values);
if (isset($values[$parsedKey[0]])) {
$values = $values[$parsedKey[0]];
}

$name = key($values);
$flattenFields[] = ['name' => $name, 'required' => $value->getRequired(), 'description' => $value->getDescription(), 'leafs' => $values[$name], 'type' => $value->getSchema()['type'] ?? 'string'];
}

$name = key($values);
$flattenFields[] = ['name' => $name, 'required' => $value['required'] ?? null, 'description' => $value['description'] ?? null, 'leafs' => $values[$name], 'type' => $value['type'] ?? 'string'];
$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0].$operation->getShortName().$operation->getName());
}

$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0]);
continue;
}

Expand Down
178 changes: 106 additions & 72 deletions src/Laravel/ApiPlatformProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,10 @@ public function register(): void
);
});

if (interface_exists(FieldsBuilderEnumInterface::class)) {
$this->registerGraphQl($this->app);
}

$this->app->singleton(JsonApiObjectNormalizer::class, function (Application $app) {
return new JsonApiObjectNormalizer(
$app->make(ObjectNormalizer::class),
Expand All @@ -936,51 +940,6 @@ public function register(): void
);
});

if ($this->app['config']->get('api-platform.graphql.enabled')) {
$this->app->singleton(GraphQlItemNormalizer::class, function (Application $app) {
return new GraphQlItemNormalizer(
$app->make(PropertyNameCollectionFactoryInterface::class),
$app->make(PropertyMetadataFactoryInterface::class),
$app->make(IriConverterInterface::class),
$app->make(IdentifiersExtractorInterface::class),
$app->make(ResourceClassResolverInterface::class),
$app->make(PropertyAccessorInterface::class),
$app->make(NameConverterInterface::class),
$app->make(SerializerClassMetadataFactory::class),
null,
$app->make(ResourceMetadataCollectionFactoryInterface::class),
$app->make(ResourceAccessCheckerInterface::class)
);
});

$this->app->singleton(GraphQlObjectNormalizer::class, function (Application $app) {
return new GraphQlObjectNormalizer(
$app->make(ObjectNormalizer::class),
$app->make(IriConverterInterface::class),
$app->make(IdentifiersExtractorInterface::class),
);
});
}

$this->app->singleton(GraphQlErrorNormalizer::class, function () {
return new GraphQlErrorNormalizer();
});

$this->app->singleton(GraphQlValidationExceptionNormalizer::class, function (Application $app) {
/** @var ConfigRepository */
$config = $app['config'];

return new GraphQlValidationExceptionNormalizer($config->get('api-platform.exception_to_status'));
});

$this->app->singleton(GraphQlHttpExceptionNormalizer::class, function () {
return new GraphQlHttpExceptionNormalizer();
});

$this->app->singleton(GraphQlRuntimeExceptionNormalizer::class, function () {
return new GraphQlHttpExceptionNormalizer();
});

$this->app->bind(SerializerInterface::class, Serializer::class);
$this->app->bind(NormalizerInterface::class, Serializer::class);
$this->app->singleton(Serializer::class, function (Application $app) {
Expand Down Expand Up @@ -1009,7 +968,7 @@ public function register(): void
$list->insert($app->make(JsonApiItemNormalizer::class), -890);
$list->insert($app->make(JsonApiObjectNormalizer::class), -995);

if ($config->get('api-platform.graphql.enabled')) {
if (interface_exists(FieldsBuilderEnumInterface::class)) {
$list->insert($app->make(GraphQlItemNormalizer::class), -890);
$list->insert($app->make(GraphQlObjectNormalizer::class), -995);
$list->insert($app->make(GraphQlErrorNormalizer::class), -790);
Expand All @@ -1033,7 +992,8 @@ public function register(): void
new JsonEncoder('jsonapi'),
new JsonEncoder('jsonhal'),
new CsvEncoder(),
]);
]
);
});

$this->app->singleton(JsonLdItemNormalizer::class, function (Application $app) {
Expand Down Expand Up @@ -1078,17 +1038,56 @@ function (Application $app) {
return new Inflector();
});

if ($this->app['config']->get('api-platform.graphql.enabled')) {
$this->registerGraphQl($this->app);
}

if ($this->app->runningInConsole()) {
$this->commands([Console\InstallCommand::class]);
}
}

private function registerGraphQl(Application $app): void
{
$this->app->singleton(GraphQlItemNormalizer::class, function (Application $app) {
return new GraphQlItemNormalizer(
$app->make(PropertyNameCollectionFactoryInterface::class),
$app->make(PropertyMetadataFactoryInterface::class),
$app->make(IriConverterInterface::class),
$app->make(IdentifiersExtractorInterface::class),
$app->make(ResourceClassResolverInterface::class),
$app->make(PropertyAccessorInterface::class),
$app->make(NameConverterInterface::class),
$app->make(SerializerClassMetadataFactory::class),
null,
$app->make(ResourceMetadataCollectionFactoryInterface::class),
$app->make(ResourceAccessCheckerInterface::class)
);
});

$this->app->singleton(GraphQlObjectNormalizer::class, function (Application $app) {
return new GraphQlObjectNormalizer(
$app->make(ObjectNormalizer::class),
$app->make(IriConverterInterface::class),
$app->make(IdentifiersExtractorInterface::class),
);
});

$this->app->singleton(GraphQlErrorNormalizer::class, function () {
return new GraphQlErrorNormalizer();
});

$this->app->singleton(GraphQlValidationExceptionNormalizer::class, function (Application $app) {
/** @var ConfigRepository */
$config = $app['config'];

return new GraphQlValidationExceptionNormalizer($config->get('api-platform.exception_to_status'));
});

$this->app->singleton(GraphQlHttpExceptionNormalizer::class, function () {
return new GraphQlHttpExceptionNormalizer();
});

$this->app->singleton(GraphQlRuntimeExceptionNormalizer::class, function () {
return new GraphQlHttpExceptionNormalizer();
});

$app->singleton('api_platform.graphql.type_locator', function (Application $app) {
$tagged = iterator_to_array($app->tagged('api_platform.graphql.type'));

Expand Down Expand Up @@ -1130,44 +1129,78 @@ private function registerGraphQl(Application $app): void
return new GraphQlSerializerContextBuilder($app->make(NameConverterInterface::class));
});

$app->singleton('api_platform.graphql.state_provider', function (Application $app) {
$app->singleton(GraphQlReadProvider::class, function (Application $app) {
/** @var ConfigRepository */
$config = $app['config'];
$tagged = iterator_to_array($app->tagged(ParameterProviderInterface::class));
$resolvers = iterator_to_array($app->tagged('api_platform.graphql.resolver'));

return new GraphQlReadProvider(
new GraphQlDenormalizeProvider(
new ResolverProvider(
new ParameterProvider(
$app->make(CallableProvider::class),
new ServiceLocator($tagged)
),
new ServiceLocator($resolvers),
),
$app->make(SerializerInterface::class),
$app->make(GraphQlSerializerContextBuilder::class)
),
$this->app->make(CallableProvider::class),
$app->make(IriConverterInterface::class),
$app->make(GraphQlSerializerContextBuilder::class),
$config->get('api-platform.graphql.nesting_separator') ?? '__'
);
});
$app->alias(GraphQlReadProvider::class, 'api_platform.graphql.state_provider.read');

$app->singleton(ResolverProvider::class, function (Application $app) {
$resolvers = iterator_to_array($app->tagged('api_platform.graphql.resolver'));

return new ResolverProvider(
$app->make(GraphQlReadProvider::class),
new ServiceLocator($resolvers),
);
});

$app->alias(ResolverProvider::class, 'api_platform.graphql.state_provider.resolver');

$app->singleton(GraphQlDenormalizeProvider::class, function (Application $app) {
return new GraphQlDenormalizeProvider(
$this->app->make(ResolverProvider::class),
$app->make(SerializerInterface::class),
$app->make(GraphQlSerializerContextBuilder::class)
);
});

$app->alias(GraphQlDenormalizeProvider::class, 'api_platform.graphql.state_provider.denormalize');

$app->singleton('api_platform.graphql.state_provider.parameter', function (Application $app) {
$tagged = iterator_to_array($app->tagged(ParameterProviderInterface::class));
$tagged['api_platform.serializer.filter_parameter_provider'] = $app->make(SerializerFilterParameterProvider::class);

return new ParameterProvider(
new ParameterValidatorProvider(
new SecurityParameterProvider(
$app->make(GraphQlDenormalizeProvider::class),
$app->make(ResourceAccessCheckerInterface::class)
),
),
new ServiceLocator($tagged)
);
});

$app->singleton('api_platform.graphql.state_provider.access_checker', function (Application $app) {
return new AccessCheckerProvider($app->make('api_platform.graphql.state_provider.parameter'), $app->make(ResourceAccessCheckerInterface::class));
});

$app->singleton(NormalizeProcessor::class, function (Application $app) {
return new NormalizeProcessor(
$app->make(SerializerInterface::class),
$app->make(GraphQlSerializerContextBuilder::class),
$app->make(Pagination::class)
);
});
$app->alias(NormalizeProcessor::class, 'api_platform.graphql.state_processor.normalize');

$app->singleton('api_platform.graphql.state_processor', function (Application $app) {
return new WriteProcessor(
new NormalizeProcessor(
$app->make(SerializerInterface::class),
$app->make(GraphQlSerializerContextBuilder::class),
$app->make(Pagination::class)
),
$app->make('api_platform.graphql.state_processor.normalize'),
$app->make(CallableProcessor::class),
);
});

$app->singleton(ResolverFactoryInterface::class, function (Application $app) {
return new ResolverFactory(
$app->make('api_platform.graphql.state_provider'),
$app->make('api_platform.graphql.state_provider.access_checker'),
$app->make('api_platform.graphql.state_processor')
);
});
Expand Down Expand Up @@ -1227,7 +1260,8 @@ private function registerGraphQl(Application $app): void
$app->make(SerializerInterface::class),
$app->make(ErrorHandlerInterface::class),
debug: $config->get('app.debug'),
negotiator: $app->make(Negotiator::class)
negotiator: $app->make(Negotiator::class),
formats: $config->get('api-platform.formats')
);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\GraphQl\DeleteMutation;
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Subscription;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
Expand All @@ -39,6 +44,12 @@ final class EloquentResourceCollectionMetadataFactory implements ResourceMetadat
GetCollection::class => 'viewAny',
Delete::class => 'delete',
Patch::class => 'update',

Query::class => 'view',
QueryCollection::class => 'viewAny',
Mutation::class => 'update',
DeleteMutation::class => 'delete',
Subscription::class => 'viewAny',
];

public function __construct(
Expand Down Expand Up @@ -94,6 +105,12 @@ public function create(string $resourceClass): ResourceMetadataCollection
$graphQlOperations = $resourceMetadata->getGraphQlOperations();

foreach ($graphQlOperations ?? [] as $operationName => $graphQlOperation) {
if (!$graphQlOperation->getPolicy() && ($policy = Gate::getPolicyFor($model))) {
if (($policyMethod = self::POLICY_METHODS[$graphQlOperation::class] ?? null) && method_exists($policy, $policyMethod)) {
$graphQlOperation = $graphQlOperation->withPolicy($policyMethod);
}
}

if (!$graphQlOperation->getProvider()) {
$graphQlOperation = $graphQlOperation->withProvider($graphQlOperation instanceof CollectionOperationInterface ? CollectionProvider::class : ItemProvider::class);
}
Expand Down
Loading

0 comments on commit 37c34db

Please sign in to comment.