epubli4 / permission-bundle
简化权限使用的包
Requires
- php: >=7.2
- ext-json: *
- api-platform/api-pack: ^1.2
- doctrine/orm: ^2.6
- guzzlehttp/guzzle: ^6.5
- symfony/framework-bundle: ^4.4
Requires (Dev)
- l0wskilled/api-platform-test: ^0.1.21
- l0wskilled/api-platform-traits: ^0.1.8
- symfony/phpunit-bridge: ^5.1
- dev-master
- v0.1.34
- v0.1.33
- v0.1.32
- v0.1.31
- v0.1.30
- v0.1.29
- v0.1.28
- v0.1.27
- v0.1.26
- v0.1.25
- v0.1.24
- v0.1.23
- v0.1.22
- v0.1.21
- v0.1.20
- v0.1.19
- v0.1.18
- v0.1.17
- v0.1.16
- v0.1.15
- v0.1.14
- v0.1.13
- v0.1.12
- v0.1.11
- v0.1.10
- 0.1.9
- v0.1.8
- v0.1.7
- v0.1.6
- v0.1.5
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- dev-AD-2923-add-creater-updator-trait
This package is auto-updated.
Last update: 2024-09-07 12:02:06 UTC
README
为e4中的微服务简化使用api-platform的权限的包
安装
请确保已全局安装Composer,如Composer文档中的安装章节所述。
使用Symfony Flex的应用程序
打开命令行,进入您的项目目录,并执行
$ composer require epubli4/permission-bundle
推荐用于单元测试
$ composer require l0wskilled/api-platform-test >=0.1.21
不使用Symfony Flex的应用程序
步骤1:下载Bundle
打开命令行,进入您的项目目录,并执行以下命令以下载此bundle的最新稳定版本
$ composer require epubli4/permission-bundle
推荐用于单元测试
$ composer require l0wskilled/api-platform-test >=0.1.21
步骤2:启用Bundle
然后,通过将其添加到项目中config/bundles.php
文件中已注册的bundle列表中来启用该bundle。
// config/bundles.php return [ // ... Epubli\PermissionBundle\EpubliPermissionBundle::class => ['all' => true], ];
配置
请确保在config/packages/epubli_permission.yaml
(如果尚不存在,请创建此文件)中插入您的微服务名称。示例
// config/packages/epubli_permission.yaml epubli_permission: microservice_name: CHANGE_ME_TO_THE_NAME_OF_YOUR_MICROSERVICE # where the permissions of this microservice should be send to permission_export_route: base_uri: http://user path: /api/permissions/import permission: user.permission.create_permissions # where to get all permissions for a specific user aggregated_permissions_route: base_uri: http://user # {user_id} will be dynamically replaced path: /api/users/{user_id}/aggregated-permissions permission: user.user.user_get_aggregated_permissions
如果尚不存在,请创建此文件config/packages/test/epubli_permission.yaml
// config/packages/test/epubli_permission.yaml epubli_permission: is_test_environment: true
在config/packages/doctrine.yaml
中激活doctrine过滤器
// config/packages/doctrine.yaml doctrine: orm: filters: epubli_permission_bundle_self_permission_filter: class: Epubli\PermissionBundle\Filter\SelfPermissionFilter
使用方法
通常
您需要指定security
键以为此端点启用此bundle。
use ApiPlatform\Core\Annotation\ApiResource; /** * @ApiResource( * collectionOperations={ * "get"={ * "security"="is_granted(null, _api_resource_class)", * }, * "post"={ * "security_post_denormalize"="is_granted(null, object)", * }, * }, * itemOperations={ * "get"={ * "security"="is_granted(null, object)", * }, * "delete"={ * "security"="is_granted(null, object)", * }, * "put"={ * "security"="is_granted(null, object)", * }, * "patch"={ * "security"="is_granted(null, object)", * }, * } * ) */ class ExampleEntity { }
如果您希望此bundle能够区分拥有此类实体或未拥有的用户,则需要实现SelfPermissionInterface
。
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\EntityManagerInterface; use Epubli\PermissionBundle\Interfaces\SelfPermissionInterface; class ExampleEntity implements SelfPermissionInterface { /** * @ORM\Column(type="integer") */ private $user_id; public function getUserId(): ?int { return $this->user_id; } /** * @inheritDoc */ public function getUserIdForPermissionBundle(): ?int { return $this->getUserId(); } /** * @inheritDoc */ public function getFieldNameOfUserIdForPermissionBundle(): string { return 'user_id'; } /** * @inheritDoc */ public function hasUserIdProperty(): bool { return true; } /** * @inheritDoc */ public function getPrimaryIdsWhichBelongToUser(EntityManagerInterface $entityManager, int $userId): array { return []; } }
或使用SelfPermissionTrait
来实现SelfPermissionInterface
的默认实现
use Doctrine\ORM\Mapping as ORM; use Epubli\PermissionBundle\Interfaces\SelfPermissionInterface; use Epubli\PermissionBundle\Traits\SelfPermissionTrait; class ExampleEntity implements SelfPermissionInterface { use SelfPermissionTrait; /** * @ORM\Column(type="integer") */ private $user_id; public function getUserId(): ?int { return $this->user_id; } }
如果您有一个没有userId但与具有userId的另一个实体有关联的实体,则需要自行实现SelfPermissionInterface
的方法。
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query; use Epubli\PermissionBundle\Interfaces\SelfPermissionInterface; class ExampleEntity implements SelfPermissionInterface { /** * @ORM\OneToOne(targetEntity=OtherEntity::class, inversedBy="exampleEntity", cascade={"persist", "remove"}) * @ORM\JoinColumn(nullable=false) */ private $otherEntity; public function getOtherEntity(): ?OtherEntity { return $this->otherEntity; } public function getPrimaryIdsWhichBelongToUser(EntityManagerInterface $entityManager, int $userId): array { /** @var Query $query */ $query = $entityManager->getRepository(__CLASS__) ->createQueryBuilder('c') ->select('c.id') ->join('c.otherEntity', 'u') ->where('u.userId = :userId') ->setParameter('userId', $userId) ->getQuery(); return array_column($query->getArrayResult(), 'id'); } public function getUserIdForPermissionBundle(): ?int { return $this->getOtherEntity()->getUserId(); } public function getFieldNameOfUserIdForPermissionBundle(): string { return ''; } public function hasUserIdProperty(): bool { return false; } }
访问令牌
您可以将它用作服务。它支持自动装配。这使您能够访问用户的访问令牌属性。
namespace App\Controller; use Epubli\PermissionBundle\Service\AccessToken; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class TestAction extends AbstractController { public function __invoke(AccessToken $accessToken) { var_dump('Is the token present and valid: ' . $accessToken->exists()); var_dump('This is the unique json token identifier: ' . $accessToken->getJTI()); var_dump('The id of the user: ' . $accessToken->getUserId()); var_dump('Checking for permissions: ' . $accessToken->hasPermissionKey('user.user.delete')); } }
自定义权限
为了使自定义权限正常工作,您需要将注释添加到使用它的方法中。
示例
namespace App\Controller; use Epubli\PermissionBundle\Annotation\Permission; use Epubli\PermissionBundle\Service\AccessToken; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; class TestController extends AbstractController { /** * @Permission( * key="customPermission1", * description="This is a description" * ) * @Permission( * key="customPermission2", * description="This is a description" * ) */ public function postTest(AccessToken $accessToken) { if (!$accessToken->exists()){ throw new UnauthorizedHttpException('Bearer', 'Access-Token is invalid.'); } if (!$accessToken->hasPermissionKey('test.customPermission1')){ throw new AccessDeniedHttpException('Missing permission key: test.customPermission1'); } //User is now authenticated and authorized for customPermission1 if (!$accessToken->hasPermissionKey('test.customPermission2')){ throw new AccessDeniedHttpException('Missing permission key: test.customPermission2'); } //User is now authenticated and authorized for customPermission2 } }
您的微服务名称将自动添加到权限键的前面。
测试
为了使用此bundle测试您的应用程序,您需要一种向其发送JsonWebTokens的方式,否则测试端点将是不可能的,您的请求将被拒绝。
您需要至少v0.1.21版本的https://github.com/epubli/api-platform-test。
最简单的方法是只需将以下内容包含到您的测试用例中。这样,每个请求都将有权访问每个端点。
use Epubli\ApiPlatform\TestBundle\OrmApiPlatformTestCase; use Epubli\PermissionBundle\Traits\JWTMockTrait; class JsonWebTokenTest extends OrmApiPlatformTestCase { use JWTMockTrait; public static function setUpBeforeClass(): void { self::setUpJsonWebTokenMockCreator(); } public function setUp(): void { parent::setUp(); self::$kernelBrowser->getCookieJar()->set(self::$cachedCookie); } }
如果您想有更多的控制权,并且不想让每个请求都有令牌
use Epubli\ApiPlatform\TestBundle\OrmApiPlatformTestCase; use Epubli\PermissionBundle\Traits\JWTMockTrait; class JsonWebTokenTest extends OrmApiPlatformTestCase { use JWTMockTrait; public static function setUpBeforeClass(): void { self::setUpJsonWebTokenMockCreator(); } public function testRetrieveTheResourceList(): void { self::$kernelBrowser->getCookieJar()->set(self::$cachedCookie); $this->request( '/api/json_web_tokens', 'GET' ); } }
存在UnitTestTrait
特质来帮助您为常见用例编写单元测试。此特质有一个配置(self::$unitTestConfig
),在其中您描述实体。此特质为您执行/生成单元测试。它要求您实现返回单元测试中使用的数据的方法。以下是如何用于支持任何操作的实体的示例。
use Epubli\ApiPlatform\TestBundle\OrmApiPlatformTestCase; use Epubli\PermissionBundle\Traits\JWTMockTrait; use Epubli\PermissionBundle\Traits\UnitTestTrait; use Epubli\PermissionBundle\UnitTestHelpers\UnitTestConfig; use Epubli\PermissionBundle\UnitTestHelpers\UnitTestDeleteData; use Epubli\PermissionBundle\UnitTestHelpers\UnitTestGetCollectionData; use Epubli\PermissionBundle\UnitTestHelpers\UnitTestGetItemData; use Epubli\PermissionBundle\UnitTestHelpers\UnitTestPostData; use Epubli\PermissionBundle\UnitTestHelpers\UnitTestUpdateData; class CompanyDataTest extends OrmApiPlatformTestCase { use JWTMockTrait; use UnitTestTrait; public const RESOURCE_URI = '/api/company_datas/'; public static function setUpBeforeClass(): void { self::setUpJsonWebTokenMockCreator(); self::$unitTestConfig = new UnitTestConfig(); } public function setUp(): void { parent::setUp(); self::$kernelBrowser->getCookieJar()->set(self::$cachedCookie); } protected function getDemoEntity(): CompanyData { $userProfileTestDummy = (new UserProfileTest())->getDemoEntity(); $this->persistAndFlush($userProfileTestDummy); $companyData = new CompanyData(); $companyData->setCompanyName(self::$faker->company); $companyData->setValueAddedTaxNumber((string)self::$faker->randomNumber()); $companyData->setUserProfile($userProfileTestDummy); $companyData->setCreatedAt(self::$faker->dateTimeBetween('-200 days', 'now')); $companyData->setUpdatedAt(self::$faker->dateTimeBetween($companyData->getCreatedAt(), 'now')); return $companyData; } public function getDeleteDataForPermissionBundle(): ?UnitTestDeleteData { /** @var CompanyData $companyData */ $companyData = $this->findOne(CompanyData::class); $userId = $companyData->getUserProfile()->getUserId(); return new UnitTestDeleteData( self::RESOURCE_URI . $companyData->getId(), 'user-profile.company_data.delete', $userId ); } public function getUpdateDataForPermissionBundle(): ?UnitTestUpdateData { /** @var CompanyData $companyData */ $companyData = $this->findOne(CompanyData::class); $userId = $companyData->getUserProfile()->getUserId(); return new UnitTestUpdateData( self::RESOURCE_URI . $companyData->getId(), 'user-profile.company_data.update.companyName', $userId, json_encode( [ 'companyName' => 'new Company Name', ] ), 'companyName', 'new Company Name' ); } public function getPostDataForPermissionBundle(): ?UnitTestPostData { $companyData = $this->getDemoEntity(); $userId = $companyData->getUserProfile()->getUserId(); return new UnitTestPostData( self::RESOURCE_URI, 'user-profile.company_data.create', $userId, json_encode( [ 'companyName' => $companyData->getCompanyName(), 'valueAddedTaxNumber' => $companyData->getValueAddedTaxNumber(), 'userProfile' => '/api/user_profiles/' . $companyData->getUserProfile()->getId(), ] ) ); } public function getGetItemDataForPermissionBundle(): ?UnitTestGetItemData { /** @var CompanyData $companyData */ $companyData = $this->findOne(CompanyData::class); $userId = $companyData->getUserProfile()->getUserId(); return new UnitTestGetItemData( self::RESOURCE_URI . $companyData->getId(), 'user-profile.company_data.read', $userId ); } public function getGetCollectionDataForPermissionBundle(): ?UnitTestGetCollectionData { /** @var CompanyData $companyData */ $companyData = $this->findOne(CompanyData::class); $userId = $companyData->getUserProfile()->getUserId(); return new UnitTestGetCollectionData( self::RESOURCE_URI, 'user-profile.company_data.read', $userId, 1 ); } }
如果您的实体不支持每个操作,则需要调整配置
use Epubli\ApiPlatform\TestBundle\OrmApiPlatformTestCase; use Epubli\PermissionBundle\Traits\UnitTestTrait; use Epubli\PermissionBundle\UnitTestHelpers\UnitTestConfig; class ExampleTest extends OrmApiPlatformTestCase { use UnitTestTrait; public static function setUpBeforeClass(): void { self::$unitTestConfig = new UnitTestConfig(); // If you implemented the SelfPermissionInterface in your entity // then set this to true (defaults to true): self::$unitTestConfig->implementsSelfPermissionInterface = true; // If you do not have a DELETE route for your entity // then set this to false (defaults to true): self::$unitTestConfig->hasDeleteRoute = true; // If your DELETE route requires no acccess control // then set this to false (defaults to true): self::$unitTestConfig->hasSecurityOnDeleteRoute = true; //The config has booleans for every standard HTTP operation } }
导出命令
要将您的微服务的权限导出到用户微服务,您需要在 Docker 容器中执行以下操作:
$ php bin/console epubli:export-permissions
测试
执行以下操作
$ make unit_test
或者
$ ./vendor/bin/simple-phpunit
如何更改/添加此捆绑包中的代码
进一步开发此捆绑包的最简单方法是将其 src
文件夹复制到另一个项目(例如,用户微服务)。
在项目内创建一个名为 permission-bundle
的文件夹,并将 src
文件夹复制到其中。
然后在 composer.json
中查找以下内容
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
并将其替换为
"autoload": {
"psr-4": {
"App\\": "src/",
"Epubli\\PermissionBundle\\": "permission-bundle/src"
}
},
删除 vendor
文件夹中的原始 permission-bundle
。
执行
$ composer dump-autoload
您可能需要在 var/cache/dev
中删除一些内容。
问题
当通过 GET 请求请求多个实体时,使用 SelfPermissionInterface
时 hydra:totalItems
可能会不正确。
因为分页器在查询应用任何过滤器之前被调用,因此项目/实体数量将不正确。 hydra:totalItems
不等于返回的项目/实体数量。
此线程中的解决方案不起作用:[api-platform/core#1185](https://github.com/api-platform/core/issues/1185)
需要完成的事情
- ApiPlatform 子资源
- 如果不存在令牌,则需要应用匿名角色的权限。