Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/6/federation/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,20 @@ See [the Apollo documentation on federated directives](https://www.apollographql
```graphql
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
url: "https://specs.apollo.dev/federation/v2.9"
import: [
"@authenticated"
"@composeDirective"
"@extends"
"@external"
"@inaccessible"
"@interfaceObject"
"@key"
"@override"
"@policy"
"@provides"
"@requires"
"@requiresScopes"
"@shareable"
"@tag"
]
Expand Down
32 changes: 32 additions & 0 deletions src/Federation/Directives/AuthenticatedDirective.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types=1);

namespace Nuwave\Lighthouse\Federation\Directives;

use Nuwave\Lighthouse\Schema\Directives\BaseDirective;

class AuthenticatedDirective extends BaseDirective
{
public const NAME = 'authenticated';

public static function definition(): string
{
return /* @lang GraphQL */ <<<'GRAPHQL'
"""
Indicates to composition that the target element is accessible only to authenticated users.

```graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@authenticated"])

type User @key(fields: "id") @authenticated {
id: ID!
email: String!
}
```

https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/reference/directives#authenticated
"""
directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
GRAPHQL;
}
}
38 changes: 38 additions & 0 deletions src/Federation/Directives/PolicyDirective.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);

namespace Nuwave\Lighthouse\Federation\Directives;

use Nuwave\Lighthouse\Schema\Directives\BaseDirective;

class PolicyDirective extends BaseDirective
{
public const NAME = 'policy';

public static function definition(): string
{
return /* @lang GraphQL */ <<<'GRAPHQL'
"""
Indicates to composition that the target element is restricted based on authorization policies.

```graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@policy"])

type User @key(fields: "id")
@policy(policies: [["read:user"], ["admin"]]) {
id: ID!
email: String!
}
```

https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/reference/directives#policy
"""
directive @policy(policies: [[federation__Policy!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM

"""
An authorization policy value required by `@policy`.
"""
scalar federation__Policy
GRAPHQL;
}
}
39 changes: 39 additions & 0 deletions src/Federation/Directives/RequiresScopesDirective.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php declare(strict_types=1);

namespace Nuwave\Lighthouse\Federation\Directives;

use Nuwave\Lighthouse\Schema\Directives\BaseDirective;

class RequiresScopesDirective extends BaseDirective
{
public const NAME = 'requiresScopes';

public static function definition(): string
{
return /* @lang GraphQL */ <<<'GRAPHQL'
"""
Indicates to composition that the target element is accessible only to authenticated users
with the appropriate JWT scopes.

```graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@requiresScopes"])

type User @key(fields: "id")
@requiresScopes(scopes: [["profile.read"], ["admin"]]) {
id: ID!
email: String!
}
```

https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/reference/directives#requiresscopes
"""
directive @requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM

"""
A JWT scope required by `@requiresScopes`.
"""
scalar federation__Scope
GRAPHQL;
}
}
6 changes: 6 additions & 0 deletions src/Federation/FederationPrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
use Nuwave\Lighthouse\Federation\Directives\ComposeDirectiveDirective;
use Nuwave\Lighthouse\Federation\Directives\ExtendsDirective;
use Nuwave\Lighthouse\Federation\Directives\ExternalDirective;
use Nuwave\Lighthouse\Federation\Directives\AuthenticatedDirective;
use Nuwave\Lighthouse\Federation\Directives\InaccessibleDirective;
use Nuwave\Lighthouse\Federation\Directives\InterfaceObjectDirective;
use Nuwave\Lighthouse\Federation\Directives\KeyDirective;
use Nuwave\Lighthouse\Federation\Directives\LinkDirective;
use Nuwave\Lighthouse\Federation\Directives\OverrideDirective;
use Nuwave\Lighthouse\Federation\Directives\PolicyDirective;
use Nuwave\Lighthouse\Federation\Directives\ProvidesDirective;
use Nuwave\Lighthouse\Federation\Directives\RequiresDirective;
use Nuwave\Lighthouse\Federation\Directives\RequiresScopesDirective;
use Nuwave\Lighthouse\Federation\Directives\ShareableDirective;
use Nuwave\Lighthouse\Federation\Directives\TagDirective;
use Nuwave\Lighthouse\Schema\RootType;
Expand Down Expand Up @@ -59,6 +62,9 @@ class FederationPrinter
TagDirective::NAME,
LinkDirective::NAME,
ComposeDirectiveDirective::NAME,
AuthenticatedDirective::NAME,
RequiresScopesDirective::NAME,
PolicyDirective::NAME,
];

public static function print(Schema $schema): string
Expand Down
22 changes: 22 additions & 0 deletions tests/Integration/Federation/FederationSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,28 @@ public function testPaginationTypesAreMarkedAsSharableWhenUsingFederationV2(): v
$this->assertStringContainsString('type SimplePaginatorInfo @shareable {', $sdl);
}

public function testServiceQueryShouldReturnFederationV2AccessControlDirectives(): void
{
$schemaExtension = /** @lang GraphQL */ <<<'GRAPHQL'
extend schema @link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@key", "@authenticated", "@requiresScopes", "@policy"])
GRAPHQL;

$typeUser = /** @lang GraphQL */ <<<'GRAPHQL'
type User @key(fields: "id") @authenticated @requiresScopes(scopes: [["profile.read"], ["admin"]]) @policy(policies: [["read:user"], ["admin"]]) {
id: ID!
email: String! @authenticated @requiresScopes(scopes: [["email.read"]]) @policy(policies: [["email_policy"]])
}
GRAPHQL;

$this->schema = "{$schemaExtension} {$typeUser}";

$sdl = $this->_serviceSdl();

$this->assertSdlContainsString($schemaExtension, $sdl);
$this->assertStringContainsString('type User @key(fields: "id") @authenticated @requiresScopes(scopes: [["profile.read"], ["admin"]]) @policy(policies: [["read:user"], ["admin"]]) {', $sdl);
$this->assertStringContainsString('email: String! @authenticated @requiresScopes(scopes: [["email.read"]]) @policy(policies: [["email_policy"]])', $sdl);
}

private function _serviceSdl(): string
{
$response = $this->graphQL(/** @lang GraphQL */ <<<'GRAPHQL'
Expand Down
Loading