Skip to content

Commit

Permalink
Merge pull request #66 from kiy0taka/dev/scope
Browse files Browse the repository at this point in the history
Scopeをチェックする
  • Loading branch information
okazy committed Jul 30, 2020
2 parents b623d84 + 271af9a commit c76446c
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 2 deletions.
15 changes: 13 additions & 2 deletions Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
use Eccube\Controller\AbstractController;
use GraphQL\Error\DebugFlag;
use GraphQL\GraphQL;
use GraphQL\Validator\DocumentValidator;
use Plugin\Api\GraphQL\Schema;
use Plugin\Api\GraphQL\ScopeValidationRule;
use Plugin\Api\GraphQL\Types;
use RuntimeException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
Expand All @@ -35,24 +37,32 @@ class ApiController extends AbstractController
* @var KernelInterface
*/
private $kernel;

/**
* @var Schema
*/
private $schema;

/**
* @var ScopeValidationRule
*/
private $scopeValidationRule;

public function __construct(
Types $types,
KernelInterface $kernel,
Schema $schema
Schema $schema,
ScopeValidationRule $scopeValidationRule
) {
$this->types = $types;
$this->kernel = $kernel;
$this->schema = $schema;
$this->scopeValidationRule = $scopeValidationRule;
}

/**
* @Route("/api", name="api", methods={"GET", "POST"})
* @Security("has_role('ROLE_OAUTH2_READ')")
* @Security("has_role('ROLE_OAUTH2_READ') or has_role('ROLE_OAUTH2_WRITE')")
*/
public function index(Request $request)
{
Expand All @@ -70,6 +80,7 @@ public function index(Request $request)
throw new RuntimeException();
}

DocumentValidator::addRule($this->scopeValidationRule);
$result = GraphQL::executeQuery($this->schema, $query, null, null, $variableValues);

if ($this->kernel->isDebug()) {
Expand Down
50 changes: 50 additions & 0 deletions GraphQL/ScopeValidationRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* http://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Plugin\Api\GraphQL;

use GraphQL\Error\Error;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Validator\Rules\ValidationRule;
use GraphQL\Validator\ValidationContext;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

class ScopeValidationRule extends ValidationRule
{
/**
* @var AuthorizationCheckerInterface
*/
private $authorizationChecker;

/**
* ScopeValidationRule constructor.
*/
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}

public function getVisitor(ValidationContext $context)
{
return [
NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $def) use ($context) {
if ($def->operation === 'query' && !$this->authorizationChecker->isGranted('ROLE_OAUTH2_READ')) {
$context->reportError(new Error('Insufficient permission. (read)'));
} elseif ($def->operation === 'mutation' && !$this->authorizationChecker->isGranted(['ROLE_OAUTH2_READ', 'ROLE_OAUTH2_WRITE'])) {
$context->reportError(new Error('Insufficient permission. (read,write)'));
}
},
];
}
}
115 changes: 115 additions & 0 deletions Tests/Web/ApiControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* http://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Plugin\Api\Tests\Web;

use DateTime;
use Eccube\Tests\Web\AbstractWebTestCase;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use Trikoder\Bundle\OAuth2Bundle\League\Entity\AccessToken;
use Trikoder\Bundle\OAuth2Bundle\League\Entity\Scope;
use Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine\ClientManager;
use Trikoder\Bundle\OAuth2Bundle\Model\Client;

class ApiControllerTest extends AbstractWebTestCase
{
/** @var ClientManager */
private $clientManager;

/** @var ClientRepositoryInterface */
private $clientRepository;

/** @var AccessTokenRepositoryInterface */
private $accessTokenRepository;

/** @var AuthorizationServer */
private $authorizationServer;

public function setUp()
{
parent::setUp();
$this->clientManager = $this->container->get(ClientManager::class);
$this->clientRepository = $this->container->get(ClientRepositoryInterface::class);
$this->accessTokenRepository = $this->container->get(AccessTokenRepositoryInterface::class);
$this->authorizationServer = $this->container->get(AuthorizationServer::class);
}

/**
* @dataProvider permissionProvider
*/
public function testPermission($scopes, $query, $expectedErrorMessage = null)
{
$token = $this->newAccessToken($scopes);
$this->client->request('POST', $this->generateUrl('api'), [], [], [
'HTTP_AUTHORIZATION' => 'Bearer '.$token,
], json_encode(['query' => $query]));

self::assertEquals(200, $this->client->getResponse()->getStatusCode());

$payload = json_decode($this->client->getResponse()->getContent(), true);

if ($expectedErrorMessage) {
self::assertEquals($expectedErrorMessage, $payload['errors'][0]['message']);
} else {
self::assertFalse(isset($payload['errors']));
}
}

public function permissionProvider()
{
$query = '{ product(id:1) { id, name } }';
$mutation = 'mutation { updateProductStock(code: "sand-01", stock: 10, stock_unlimited:false) { id } }';

return [
[['read'], $query],
[['write'], $query, 'Insufficient permission. (read)'],
[['read', 'write'], $query],
[['read'], $mutation, 'Insufficient permission. (read,write)'],
[['write'], $mutation, 'Insufficient permission. (read,write)'],
[['read', 'write'], $mutation],
];
}

private function newAccessToken($scopes)
{
$identifier = hash('md5', random_bytes(16));
$secret = hash('sha512', random_bytes(32));

$client = new Client($identifier, $secret);
$client->setScopes(...array_map(function ($s) {
return new \Trikoder\Bundle\OAuth2Bundle\Model\Scope($s);
}, $scopes));
$this->clientManager->save($client);
$clientEntity = $this->clientRepository->getClientEntity($identifier, 'authorization_code', $secret);

$accessTokenEntity = new AccessToken();
$accessTokenEntity->setIdentifier($identifier);
$accessTokenEntity->setClient($clientEntity);
$accessTokenEntity->setExpiryDateTime(new DateTime('+1 hour'));
$accessTokenEntity->setUserIdentifier('admin');
array_walk($scopes, function ($s) use ($accessTokenEntity) {
$scope = new Scope();
$scope->setIdentifier($s);
$accessTokenEntity->addScope($scope);
});
$this->accessTokenRepository->persistNewAccessToken($accessTokenEntity);

$rc = new \ReflectionClass($this->authorizationServer);
$property = $rc->getProperty('privateKey');
$property->setAccessible(true);

return $accessTokenEntity->convertToJWT($property->getValue($this->authorizationServer));
}
}

0 comments on commit c76446c

Please sign in to comment.