├── .github
└── workflows
│ └── php.yml
├── .gitignore
├── .scrutinizer.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app
├── composer_autoloader.php
└── slim_rbac.php
├── bin
└── slim_rbac
├── codeception.yml
├── composer.json
├── composer.lock
├── config
└── sr_config.example.yaml
├── docker-compose.yml
├── migrations
├── 20171209082325_create_role.php
├── 20171210103530_create_permission.php
├── 20171210104049_create_role_permission.php
├── 20171210104724_create_user_role.php
├── 20171210195252_create_role_hierarchy.php
├── 20171224111307_add_description_field_for_role.php
└── 20171224111730_add_description_field_for_permission.php
├── src
├── Component
│ ├── ComponentsFactory.php
│ ├── Config
│ │ ├── RbacConfig.php
│ │ ├── RbacConfigLoader.php
│ │ └── RbacConfigStructure.php
│ ├── PermissionNameExtractor
│ │ ├── PermissionNameExtractor.php
│ │ └── UriPathPermissionNameExtractor.php
│ ├── RbacAccessChecker.php
│ ├── RbacContainer.php
│ ├── RbacManager.php
│ ├── RbacMiddleware.php
│ ├── UserIdExtractor
│ │ ├── AttributeUserIdExtractor.php
│ │ ├── BaseUserIdExtractor.php
│ │ ├── CookieUserIdExtractor.php
│ │ ├── HeaderUserIdExtractor.php
│ │ └── UserIdExtractor.php
│ └── services.yaml
├── Console
│ ├── Command
│ │ ├── BaseDatabaseCommand.php
│ │ ├── CreateConfigCommand.php
│ │ ├── MigrateDatabaseCommand.php
│ │ └── RollbackDatabaseCommand.php
│ └── SlimRbacConsoleApplication.php
├── Exception
│ ├── BaseException.php
│ ├── ConfigNotFoundException.php
│ ├── CyclicException.php
│ ├── DatabaseException.php
│ ├── InvalidArgumentException.php
│ ├── NotSupportedDatabaseException.php
│ ├── NotUniqueException.php
│ └── PermissionNotFoundException.php
└── Models
│ ├── Entity
│ ├── Permission.php
│ ├── Role.php
│ ├── RoleHierarchy.php
│ ├── RolePermission.php
│ └── UserRole.php
│ ├── Repository
│ ├── PermissionRepository.php
│ ├── RoleHierarchyRepository.php
│ ├── RolePermissionRepository.php
│ ├── RoleRepository.php
│ └── UserRoleRepository.php
│ └── RepositoryRegistry.php
└── tests
├── _data
└── .gitkeep
├── _output
└── .gitignore
├── _support
├── AcceptanceTester.php
├── FunctionalTester.php
├── Helper
│ ├── Acceptance.php
│ ├── Functional.php
│ └── Unit.php
├── UnitTester.php
└── _generated
│ └── .gitignore
├── unit.suite.yml
└── unit
├── BaseTestCase.php
├── RbacManagerTest.php
└── RbacMiddlewareTest.php
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: Build and Testing
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | services:
15 | mysql:
16 | image: mysql:5.7
17 | env:
18 | MYSQL_ALLOW_EMPTY_PASSWORD: false
19 | MYSQL_ROOT_PASSWORD: password
20 | MYSQL_DATABASE: db
21 | ports:
22 | - 3306/tcp
23 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
24 |
25 | steps:
26 | - uses: actions/checkout@v2
27 |
28 | - name: Setup PHP with PECL extension
29 | uses: shivammathur/setup-php@v2
30 | with:
31 | php-version: '7.3'
32 | coverage: xdebug2
33 |
34 | - name: Validate composer.json and composer.lock
35 | run: composer validate --strict
36 |
37 | - name: Get composer cache directory
38 | id: composer-cache
39 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
40 |
41 | - name: Cache dependencies
42 | uses: actions/cache@v3
43 | with:
44 | path: ${{ steps.composer-cache.outputs.dir }}
45 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
46 | restore-keys: ${{ runner.os }}-composer-
47 |
48 | - name: Install dependencies
49 | run: composer install --prefer-dist --no-progress
50 |
51 | - name: Create config file
52 | run: sed -e "s/3306/${{ job.services.mysql.ports['3306'] }}/" ./config/sr_config.example.yaml > ./config/sr_config.yaml
53 |
54 | - name: Run migrations
55 | run: ./bin/slim_rbac migrate
56 |
57 | - name: Run tests
58 | run: |
59 | vendor/bin/codecept run --coverage --coverage-xml
60 | cp tests/_output/coverage.xml ./coverage.xml
61 | cp tests/_output/coverage.serialized ./coverage.serialized
62 |
63 | - name: Upload coverage to Codecov
64 | uses: codecov/codecov-action@v3
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Exclude vendor packages
2 | vendor/*
3 |
4 | # Exclude PhpStorm files
5 | .idea
6 |
7 | # Exclude Phinx settings
8 | phinx.yml
9 |
10 | # Exclude configurations file
11 | config/sr_config.yaml
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | checks:
2 | php: true
3 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at potievdev@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | ## Pull Requests
4 |
5 | 1. Fork the RBAC Middleware repository
6 | 2. Create a new branch for each feature or improvement
7 | 3. Send a pull request from each feature branch against the version branch for which your fix is intended.
8 |
9 | It is very important to separate new features or improvements into separate feature branches, and to send a
10 | pull request for each branch. This allows each feature or improvement to be reviewed and merged individually.
11 |
12 | ## Style Guide
13 |
14 | All pull requests must adhere to the [PSR-2 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md).
15 |
16 | ## Unit Testing
17 |
18 | All pull requests must be accompanied by passing unit tests and complete code coverage. The Slim Framework uses phpunit for testing.
19 |
20 | [Learn about PHPUnit](https://github.com/sebastianbergmann/phpunit/)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Slim4 RBAC Middleware
3 |
4 |
5 | [](https://app.travis-ci.com/potievdev/slim-rbac)
6 | [](https://codecov.io/gh/potievdev/slim-rbac)
7 | [](https://app.fossa.io/projects/git%2Bgithub.com%2Fpotievdev%2Fslim-rbac?ref=badge_shield)
8 | [](https://scrutinizer-ci.com/g/potievdev/slim-rbac/?branch=master)
9 | [](https://scrutinizer-ci.com/g/potievdev/slim-rbac/build-status/master)
10 | [](https://packagist.org/packages/potievdev/slim-rbac)
11 |
12 | This package helps you to release access control logic via [RBAC](https://en.wikipedia.org/wiki/Role-based_access_control) (Role Based Access Control) technology. The example app you can see [https://github.com/potievdev/slim-rbac-app](https://github.com/potievdev/slim-rbac-app)
13 |
14 | ## :clipboard: Requirements
15 |
16 | - The minimum required PHP version is PHP 7.3.
17 | - Supported database engines
18 | * MySQL
19 | * PostgreSQL
20 | * MariaDB
21 |
22 | ## :wrench: Installation
23 |
24 | ### First step
25 |
26 | ```sh
27 | $ composer require potievdev/slim_rbac "^2.0"
28 | ```
29 |
30 | ## :crossed_flags: Contribution
31 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
32 |
33 | ## :memo: Licence
34 | MIT License
35 |
--------------------------------------------------------------------------------
/app/composer_autoloader.php:
--------------------------------------------------------------------------------
1 | run();
6 |
--------------------------------------------------------------------------------
/codeception.yml:
--------------------------------------------------------------------------------
1 | paths:
2 | tests: tests
3 | output: tests/_output
4 | data: tests/_data
5 | support: tests/_support
6 | envs: tests/_envs
7 | actor_suffix: Tester
8 | extensions:
9 | enabled:
10 | - Codeception\Extension\RunFailed
11 | coverage:
12 | enabled: true
13 | include:
14 | - src/Component/*
15 | - src/Models/*
16 | exclude:
17 | - src/Models/Entity/*
18 | - src/Models/Repository/*
19 | - src/Component/services.yaml
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "potievdev/slim-rbac",
3 | "description": "Role Based Access Control middleware for Slim 3",
4 | "keywords": [
5 | "slim framework",
6 | "slim 3",
7 | "middleware",
8 | "authentication",
9 | "authorization",
10 | "rbac",
11 | "potievdev"
12 | ],
13 | "homepage": "https://github.com/potievdev/slim-rbac",
14 | "license": "MIT",
15 | "authors": [
16 | {
17 | "name": "Abdulmalik Abdulpotiev",
18 | "email": "potievdev@gmail.com",
19 | "homepage": "https://potievdev.com/",
20 | "role": "Developer"
21 | }
22 | ],
23 | "require": {
24 | "php" : "~7.3",
25 | "robmorgan/phinx": "~0.9.2",
26 | "doctrine/orm": "~2.10.5",
27 | "psr/http-message": "~1.0",
28 | "symfony/cache": "~5.4.21",
29 | "symfony/dependency-injection": "^4.4",
30 | "doctrine/annotations": "^1.14"
31 | },
32 | "require-dev": {
33 | "codeception/codeception": "3.1.3"
34 | },
35 | "autoload": {
36 | "psr-4": {
37 | "Potievdev\\SlimRbac\\": "src"
38 | }
39 | },
40 | "autoload-dev": {
41 | "psr-4": {
42 | "Tests\\Unit\\": "tests/unit"
43 | }
44 | },
45 | "bin": [
46 | "bin/slim_rbac"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/config/sr_config.example.yaml:
--------------------------------------------------------------------------------
1 | rbac:
2 | userId:
3 | fieldName: userId
4 | resourceType: attribute
5 | database:
6 | driver: pdo_mysql
7 | host: 127.0.0.1
8 | user: root
9 | password: password
10 | port: 3306
11 | dbname: db
12 | charset: utf8
13 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 | services:
3 | mysql:
4 | image: mysql:5.7
5 | restart: always
6 | environment:
7 | MYSQL_DATABASE: 'db'
8 | MYSQL_ROOT_PASSWORD: 'password'
9 | ports:
10 | - '3306:3306'
11 | expose:
12 | - '3306'
13 | volumes:
14 | - mysql-db:/var/lib/mysql
15 |
16 | postgres:
17 | image: postgres:latest
18 | environment:
19 | POSTGRES_USER: root
20 | POSTGRES_PASSWORD: password
21 | POSTGRES_DB: db
22 | ports:
23 | - "5432:5432"
24 | expose:
25 | - '5432'
26 | volumes:
27 | - postgres-db:/var/lib/postgresql/data/
28 |
29 | volumes:
30 | mysql-db:
31 | postgres-db:
--------------------------------------------------------------------------------
/migrations/20171209082325_create_role.php:
--------------------------------------------------------------------------------
1 | table('role', ['signed' => false]);
17 |
18 | $roleTable->addColumn('name', 'string', ['limit' => 50])
19 | ->addColumn('status', 'boolean', ['default' => true])
20 | ->addColumn('created_at', 'datetime')
21 | ->addColumn('updated_at', 'datetime', ['null' => true])
22 | ->addIndex('name', ['name' => 'idx_role_name', 'unique' => true])
23 | ->create();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/migrations/20171210103530_create_permission.php:
--------------------------------------------------------------------------------
1 | table('permission', ['signed' => false]);
17 |
18 | $permissionTable->addColumn('name', 'string', ['limit' => 100])
19 | ->addColumn('status', 'boolean', ['default' => true])
20 | ->addColumn('created_at', 'datetime')
21 | ->addColumn('updated_at', 'datetime', ['null' => true])
22 | ->addIndex('name', ['name' => 'idx_permission_name' ,'unique' => true])
23 | ->create();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/migrations/20171210104049_create_role_permission.php:
--------------------------------------------------------------------------------
1 | table('role_permission', ['signed' => false]);
17 |
18 | $rolePermissionTable->addColumn('role_id', 'integer', ['signed' => false])
19 | ->addColumn('permission_id', 'integer', ['signed' => false])
20 | ->addColumn('created_at', 'datetime')
21 | ->addIndex(['role_id', 'permission_id'], ['name' => 'idx_role_permission_unique', 'unique' => true])
22 | ->addForeignKey('role_id', 'role', 'id', ['delete' => 'RESTRICT', 'constraint' => 'fk_role_permission_role'])
23 | ->addForeignKey('permission_id', 'permission', 'id', ['delete' => 'RESTRICT', 'constraint' => 'fk_role_permission_permission'])
24 | ->create();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/migrations/20171210104724_create_user_role.php:
--------------------------------------------------------------------------------
1 | table('user_role', ['signed' => false]);
17 |
18 | $userRoleTable->addColumn('user_id', 'integer', ['signed' => false])
19 | ->addColumn('role_id', 'integer', ['signed' => false])
20 | ->addColumn('created_at', 'datetime')
21 | ->addIndex(['user_id', 'role_id'], ['name' => 'idx_user_role_unique', 'unique' => true])
22 | ->addForeignKey('role_id', 'role', 'id', ['delete' => 'RESTRICT', 'constraint' => 'fk_user_role_role'])
23 | ->create();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/migrations/20171210195252_create_role_hierarchy.php:
--------------------------------------------------------------------------------
1 | table('role_hierarchy', ['signed' => false]);
15 |
16 | $userRoleTable->addColumn('parent_role_id', 'integer', ['signed' => false])
17 | ->addColumn('child_role_id', 'integer', ['signed' => false])
18 | ->addColumn('created_at', 'datetime')
19 | ->addIndex(['parent_role_id', 'child_role_id'], ['name' => 'idx_role_hierarchy_unique', 'unique' => true])
20 | ->addForeignKey('parent_role_id', 'role', 'id', ['delete' => 'RESTRICT', 'constraint' => 'fk_role_hierarchy_parent'])
21 | ->addForeignKey('child_role_id', 'role', 'id', ['delete' => 'RESTRICT', 'constraint' => 'fk_role_hierarchy_child'])
22 | ->create();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/migrations/20171224111307_add_description_field_for_role.php:
--------------------------------------------------------------------------------
1 | table('role');
32 |
33 | $roleTable
34 | ->addColumn('description', 'string', ['null' => true, 'comment' => 'description of role'])
35 | ->update();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/migrations/20171224111730_add_description_field_for_permission.php:
--------------------------------------------------------------------------------
1 | table('permission');
32 |
33 | $permissionTable
34 | ->addColumn('description', 'string', ['null' => true, 'comment' => 'description of permission'])
35 | ->update();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Component/ComponentsFactory.php:
--------------------------------------------------------------------------------
1 | $rbacConfig->getDatabaseDriver(),
19 | 'host' => $rbacConfig->getDatabaseHost(),
20 | 'user' => $rbacConfig->getDatabaseUser(),
21 | 'password' => $rbacConfig->getDatabasePassword(),
22 | 'dbname' => $rbacConfig->getDatabaseName(),
23 | 'port' => $rbacConfig->getDatabasePort(),
24 | ];
25 |
26 | $config = Setup::createAnnotationMetadataConfiguration([], false, null, null, false);
27 |
28 | return EntityManager::create($dbParams, $config);
29 | }
30 |
31 | public static function createUserIdExtractor(RbacConfig $rbacConfig): UserIdExtractor
32 | {
33 | $userIdFieldName = $rbacConfig->getUserIdFieldName();
34 | $storageType = $rbacConfig->getUserIdResourceType();
35 |
36 | /** @var integer $userId */
37 | switch ($storageType) {
38 |
39 | case RbacConfig::HEADER_RESOURCE_TYPE:
40 | return new HeaderUserIdExtractor($userIdFieldName);
41 |
42 | case RbacConfig::COOKIE_RESOURCE_TYPE:
43 | return new CookieUserIdExtractor($userIdFieldName);
44 |
45 | case RbacConfig::ATTRIBUTE_RESOURCE_TYPE:
46 | default:
47 | return new AttributeUserIdExtractor($userIdFieldName);
48 | }
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/src/Component/Config/RbacConfig.php:
--------------------------------------------------------------------------------
1 | databaseDriver = $databaseDriver;
63 | $this->databaseHost = $databaseHost;
64 | $this->databaseUser = $databaseUser;
65 | $this->databasePassword = $databasePassword;
66 | $this->databasePort = $databasePort;
67 | $this->databaseName = $databaseName;
68 | $this->databaseCharset = $databaseCharset;
69 | $this->userIdFieldName = $userIdFieldName;
70 | $this->userIdResourceType = $userIdResourceType;
71 | }
72 |
73 | /**
74 | * @throws ConfigNotFoundException
75 | */
76 | public static function createFromConfigFile(): RbacConfig
77 | {
78 | return self::createFromConfigs(RbacConfigLoader::loadConfigs());
79 | }
80 |
81 | public static function createFromConfigs(array $configs): RbacConfig
82 | {
83 | return new self(
84 | $configs['database']['driver'],
85 | $configs['database']['host'],
86 | $configs['database']['user'],
87 | $configs['database']['password'],
88 | $configs['database']['port'],
89 | $configs['database']['dbname'],
90 | $configs['database']['charset'],
91 | $configs['userId']['fieldName'],
92 | $configs['userId']['resourceType'],
93 | );
94 | }
95 |
96 | /**
97 | * @return string
98 | */
99 | public function getDatabaseDriver(): string
100 | {
101 | return $this->databaseDriver;
102 | }
103 |
104 | /**
105 | * @return string
106 | */
107 | public function getDatabaseHost(): string
108 | {
109 | return $this->databaseHost;
110 | }
111 |
112 | /**
113 | * @return string
114 | */
115 | public function getDatabaseUser(): string
116 | {
117 | return $this->databaseUser;
118 | }
119 |
120 | /**
121 | * @return string
122 | */
123 | public function getDatabasePassword(): string
124 | {
125 | return $this->databasePassword;
126 | }
127 |
128 | /**
129 | * @return int
130 | */
131 | public function getDatabasePort(): int
132 | {
133 | return $this->databasePort;
134 | }
135 |
136 | /**
137 | * @return string
138 | */
139 | public function getDatabaseName(): string
140 | {
141 | return $this->databaseName;
142 | }
143 |
144 | /**
145 | * @return string
146 | */
147 | public function getDatabaseCharset(): string
148 | {
149 | return $this->databaseCharset;
150 | }
151 |
152 | /**
153 | * @return string
154 | */
155 | public function getUserIdFieldName(): string
156 | {
157 | return $this->userIdFieldName;
158 | }
159 |
160 | /**
161 | * @return string
162 | */
163 | public function getUserIdResourceType(): string
164 | {
165 | return $this->userIdResourceType;
166 | }
167 |
168 | }
--------------------------------------------------------------------------------
/src/Component/Config/RbacConfigLoader.php:
--------------------------------------------------------------------------------
1 | locate('sr_config.yaml');
25 |
26 | if ($fileName === null) {
27 | throw ConfigNotFoundException::configFileNotFound($configDirectories);
28 | }
29 |
30 | return (new Processor())
31 | ->processConfiguration(new RbacConfigStructure(), Yaml::parseFile($fileName));
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/src/Component/Config/RbacConfigStructure.php:
--------------------------------------------------------------------------------
1 | getRootNode();
17 |
18 | $rootNode
19 | ->children()
20 | ->arrayNode('database')
21 | ->children()
22 | ->enumNode('driver')
23 | ->values(['pdo_mysql', 'pdo_postgres'])
24 | ->isRequired()
25 | ->end()
26 | ->scalarNode('host')
27 | ->cannotBeEmpty()
28 | ->end()
29 | ->scalarNode('user')
30 | ->cannotBeEmpty()
31 | ->end()
32 | ->scalarNode('password')
33 | ->cannotBeEmpty()
34 | ->end()
35 | ->integerNode('port')
36 | ->isRequired()
37 | ->end()
38 | ->scalarNode('dbname')
39 | ->cannotBeEmpty()
40 | ->end()
41 | ->scalarNode('charset')
42 | ->cannotBeEmpty()
43 | ->end()
44 | ->end()
45 | ->end()
46 | ->arrayNode('userId')
47 | ->children()
48 | ->scalarNode('fieldName')
49 | ->cannotBeEmpty()
50 | ->end()
51 | ->enumNode('resourceType')
52 | ->values(['attribute', 'header', 'cookie'])
53 | ->isRequired()
54 | ->end()
55 | ->end()
56 | ->end()
57 | ->end()
58 | ;
59 |
60 | return $treeBuilder;
61 | }
62 | }
--------------------------------------------------------------------------------
/src/Component/PermissionNameExtractor/PermissionNameExtractor.php:
--------------------------------------------------------------------------------
1 | getUri()->getPath();
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Component/RbacAccessChecker.php:
--------------------------------------------------------------------------------
1 | repositoryRegistry = $repositoryRegistry;
19 | }
20 |
21 | /**
22 | * Checks access status.
23 | *
24 | * @throws QueryException
25 | */
26 | public function hasAccess(string $userId, string $permissionName): bool
27 | {
28 | /** @var integer $permissionId */
29 | $permissionId = $this->repositoryRegistry
30 | ->getPermissionRepository()
31 | ->getPermissionIdByName($permissionName);
32 |
33 | if ($permissionId === null) {
34 | return false;
35 | }
36 |
37 | /** @var integer[] $rootRoleIds */
38 | $rootRoleIds = $this->repositoryRegistry
39 | ->getUserRoleRepository()
40 | ->getUserRoleIds($userId);
41 |
42 | if (count($rootRoleIds) == 0) {
43 | return false;
44 | }
45 |
46 | $allRoleIds = $this->repositoryRegistry
47 | ->getRoleHierarchyRepository()
48 | ->getAllRoleIdsHierarchy($rootRoleIds);
49 |
50 | return $this->repositoryRegistry
51 | ->getRolePermissionRepository()
52 | ->isPermissionAssigned($permissionId, $allRoleIds);
53 | }
54 | }
--------------------------------------------------------------------------------
/src/Component/RbacContainer.php:
--------------------------------------------------------------------------------
1 | containerBuilder = new ContainerBuilder();
25 |
26 | if (isset($rbacConfig)) {
27 | $this->containerBuilder->set('rbacConfig', $rbacConfig);
28 | }
29 |
30 | $loader = new YamlFileLoader($this->containerBuilder, new FileLocator(__DIR__));
31 | $loader->load('services.yaml');
32 | }
33 |
34 | public function getRbacMiddleware(): RbacMiddleware
35 | {
36 | return $this->containerBuilder->get('middleware');
37 | }
38 |
39 | public function getRbacManager(): RbacManager
40 | {
41 | return $this->containerBuilder->get('manager');
42 | }
43 |
44 | public function getInnerContainer(): ContainerBuilder
45 | {
46 | return $this->containerBuilder;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Component/RbacManager.php:
--------------------------------------------------------------------------------
1 | entityManager = $entityManager;
40 | $this->repositoryRegistry = $repositoryRegistry;
41 | }
42 |
43 | /**
44 | * Creates permission instance with given name and return it.
45 | * @throws NotUniqueException|DatabaseException|ORMException
46 | */
47 | public function createPermission(string $permissionMane, ?string $description = null): Permission
48 | {
49 | $permission = new Permission();
50 | $permission->setName($permissionMane);
51 |
52 | if (isset($description)) {
53 | $permission->setDescription($description);
54 | }
55 |
56 | try {
57 | $this->saveEntity($permission);
58 | } catch (UniqueConstraintViolationException $e) {
59 | throw NotUniqueException::permissionWithNameAlreadyCreated($permissionMane);
60 | }
61 |
62 | return $permission;
63 | }
64 |
65 | /**
66 | * Creates role instance with given name and return it.
67 | *
68 | * @throws NotUniqueException|DatabaseException|ORMException
69 | */
70 | public function createRole(string $roleName, ?string $description = null): Role
71 | {
72 | $role = new Role();
73 | $role->setName($roleName);
74 |
75 | if (isset($description)) {
76 | $role->setDescription($description);
77 | }
78 |
79 | try {
80 | $this->saveEntity($role);
81 | } catch (UniqueConstraintViolationException $e) {
82 | throw NotUniqueException::notUniqueRole($roleName);
83 | }
84 |
85 | return $role;
86 | }
87 |
88 | /**
89 | * Add permission to role.
90 | *
91 | * @throws DatabaseException
92 | * @throws NotUniqueException|ORMException
93 | */
94 | public function attachPermission(Role $role, Permission $permission)
95 | {
96 | $rolePermission = new RolePermission();
97 |
98 | $rolePermission->setPermission($permission);
99 | $rolePermission->setRole($role);
100 |
101 | try {
102 | $this->saveEntity($rolePermission);
103 | } catch (UniqueConstraintViolationException $e) {
104 | throw NotUniqueException::permissionAlreadyAttachedToRole($permission->getName(), $role->getName());
105 | }
106 | }
107 |
108 | /**
109 | * Add child role to role.
110 | *
111 | * @throws CyclicException
112 | * @throws DatabaseException
113 | * @throws NotUniqueException
114 | * @throws QueryException|ORMException
115 | */
116 | public function attachChildRole(Role $parentRole, Role $childRole)
117 | {
118 | $roleHierarchy = new RoleHierarchy();
119 |
120 | $roleHierarchy->setParentRole($parentRole);
121 | $roleHierarchy->setChildRole($childRole);
122 |
123 | $this->checkForCyclicHierarchy($childRole->getId(), $parentRole->getId());
124 |
125 | try {
126 | $this->saveEntity($roleHierarchy);
127 | } catch (UniqueConstraintViolationException $e) {
128 | throw NotUniqueException::childRoleAlreadyAttachedToGivenParentRole(
129 | $childRole->getName(),
130 | $parentRole->getName()
131 | );
132 | }
133 | }
134 |
135 | /**
136 | * Assign role to user.
137 | *
138 | * @throws NotUniqueException
139 | * @throws DatabaseException|ORMException
140 | */
141 | public function assignRoleToUser(Role $role, int $userId)
142 | {
143 | $userRole = new UserRole();
144 |
145 | $userRole->setUserId($userId);
146 | $userRole->setRole($role);
147 |
148 | try {
149 | $this->saveEntity($userRole);
150 | } catch (UniqueConstraintViolationException $e) {
151 | throw NotUniqueException::roleAlreadyAssignedToUser($role->getName(), $userId);
152 | }
153 | }
154 |
155 | /**
156 | * Checking hierarchy cyclic line.
157 | *
158 | * @throws CyclicException
159 | * @throws QueryException
160 | */
161 | private function checkForCyclicHierarchy(int $parentRoleId, int $childRoleId): void
162 | {
163 | $result = $this->repositoryRegistry
164 | ->getRoleHierarchyRepository()
165 | ->hasChildRoleId($parentRoleId, $childRoleId);
166 |
167 | if ($result === true) {
168 | throw CyclicException::cycleDetected($parentRoleId, $childRoleId);
169 | }
170 | }
171 |
172 | /**
173 | * Insert or update entity.
174 | *
175 | * @throws DatabaseException|ORMException
176 | */
177 | private function saveEntity(object $entity): void
178 | {
179 | try {
180 | $this->entityManager->persist($entity);
181 | $this->entityManager->flush($entity);
182 | } catch (OptimisticLockException $e) {
183 | throw new DatabaseException($e->getMessage());
184 | }
185 | }
186 |
187 | }
188 |
--------------------------------------------------------------------------------
/src/Component/RbacMiddleware.php:
--------------------------------------------------------------------------------
1 | accessChecker = $accessChecker;
41 | $this->userIdExtractor = $userIdExtractor;
42 | $this->permissionNameExtractor = $permissionNameExtractor;
43 | }
44 |
45 | /**
46 | * Check access.
47 | *
48 | * @param ServerRequestInterface $request PSR7 request
49 | * @param ResponseInterface $response PSR7 response
50 | * @param callable $next Next middleware
51 | *
52 | * @return ResponseInterface
53 | * @throws QueryException
54 | * @throws InvalidArgumentException
55 | */
56 | public function __invoke(
57 | ServerRequestInterface $request,
58 | ResponseInterface $response,
59 | callable $next
60 | ): ResponseInterface {
61 | $userId = $this->userIdExtractor->getUserId($request);
62 | $permissionName = $this->permissionNameExtractor->getPermissionName($request);
63 |
64 | if ($this->accessChecker->hasAccess($userId, $permissionName)) {
65 | return $next($request, $response);
66 | }
67 |
68 | return $response->withStatus(self::PERMISSION_DENIED_CODE, self::PERMISSION_DENIED_MESSAGE);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Component/UserIdExtractor/AttributeUserIdExtractor.php:
--------------------------------------------------------------------------------
1 | getAttribute($this->userIdFieldName);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Component/UserIdExtractor/BaseUserIdExtractor.php:
--------------------------------------------------------------------------------
1 | userIdFieldName = $userIdFieldName;
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/src/Component/UserIdExtractor/CookieUserIdExtractor.php:
--------------------------------------------------------------------------------
1 | getCookieParams();
12 |
13 | return $params[$this->userIdFieldName];
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Component/UserIdExtractor/HeaderUserIdExtractor.php:
--------------------------------------------------------------------------------
1 | getHeaderLine($this->userIdFieldName);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Component/UserIdExtractor/UserIdExtractor.php:
--------------------------------------------------------------------------------
1 | [
41 | 'migrations' => self::MIGRATION_PATH
42 | ],
43 | 'environments' => [
44 | 'default_migration_table' => self::DEFAULT_MIGRATION_TABLE,
45 | 'default_database' => self::DEFAULT_ENVIRONMENT_NAME,
46 | self::DEFAULT_ENVIRONMENT_NAME => $this->getAdapterConfigs($rbacConfig)
47 | ]
48 | ];
49 |
50 | $this->config = new Config($configArray);
51 | }
52 |
53 | /**
54 | * @throws NotSupportedDatabaseException
55 | */
56 | private function getAdapterConfigs(RbacConfig $rbacConfig): array
57 | {
58 | $platformName = $rbacConfig->getDatabaseDriver();
59 |
60 | switch ($platformName) {
61 | case 'pdo_mysql':
62 | $adapterName = 'mysql';
63 | break;
64 | case 'pdo_postgres':
65 | $adapterName = 'pgsql';
66 | break;
67 | default:
68 | throw NotSupportedDatabaseException::notSupportedPlatform($platformName);
69 | }
70 |
71 | return [
72 | 'adapter' => $adapterName,
73 | 'name' => $rbacConfig->getDatabaseName(),
74 | 'host' => $rbacConfig->getDatabaseHost(),
75 | 'user' => $rbacConfig->getDatabaseUser(),
76 | 'pass' => $rbacConfig->getDatabasePassword(),
77 | 'port' => $rbacConfig->getDatabasePort(),
78 | 'charset' => $rbacConfig->getDatabaseCharset(),
79 | ];
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Console/Command/CreateConfigCommand.php:
--------------------------------------------------------------------------------
1 | setName('create-config')
19 | ->setDescription('This command creates sr_config.yaml file in working directory');
20 | }
21 |
22 | /**
23 | * @param InputInterface $input
24 | * @param OutputInterface $output
25 | * @return void
26 | */
27 | public function execute(InputInterface $input, OutputInterface $output)
28 | {
29 | $configFile = file_get_contents(__DIR__ . '/../../../config/sr_config.example.yaml');
30 | $currentDir = getcwd();
31 | file_put_contents($currentDir . '/sr_config.yaml', $configFile);
32 | $output->writeln("File sr_config.yaml created in directory: $currentDir");
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Console/Command/MigrateDatabaseCommand.php:
--------------------------------------------------------------------------------
1 | setName('migrate')
21 | ->setDescription('Applies migrations to database');
22 | }
23 |
24 | /**
25 | * @param InputInterface $input
26 | * @param OutputInterface $output
27 | * @return void
28 | * @throws ConfigNotFoundException
29 | * @throws NotSupportedDatabaseException
30 | */
31 | public function execute(InputInterface $input, OutputInterface $output): void
32 | {
33 | parent::execute($input, $output);
34 | $manager = new Manager($this->config, $input, $output);
35 | $manager->migrate($this->config->getDefaultEnvironment());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Console/Command/RollbackDatabaseCommand.php:
--------------------------------------------------------------------------------
1 | setName('rollback')
21 | ->setDescription('Rollback last migration to database');
22 | }
23 |
24 | /**
25 | * @param InputInterface $input
26 | * @param OutputInterface $output
27 | * @return void
28 | * @throws ConfigNotFoundException
29 | * @throws NotSupportedDatabaseException
30 | */
31 | public function execute(InputInterface $input, OutputInterface $output)
32 | {
33 | parent::execute($input, $output);
34 | $manager = new Manager($this->config, $input, $output);
35 | $manager->rollback($this->config->getDefaultEnvironment());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Console/SlimRbacConsoleApplication.php:
--------------------------------------------------------------------------------
1 | addCommands([
23 | new Command\CreateConfigCommand(),
24 | new Command\MigrateDatabaseCommand(),
25 | new Command\RollbackDatabaseCommand(),
26 | ]);
27 | }
28 |
29 | /**
30 | * Runs the current application.
31 | *
32 | * @param InputInterface $input An Input instance
33 | * @param OutputInterface $output An Output instance
34 | * @return integer 0 if everything went fine, or an error code
35 | * @throws \Throwable
36 | */
37 | public function doRun(InputInterface $input, OutputInterface $output): int
38 | {
39 | // always show the version information except when the user invokes the help
40 | // command as that already does it
41 | if (false === $input->hasParameterOption(['--help', '-h']) && null !== $input->getFirstArgument()) {
42 | $output->writeln($this->getLongVersion());
43 | $output->writeln('Slim Role Based Access Control Middleware');
44 | $output->writeln('');
45 | }
46 |
47 | return parent::doRun($input, $output);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Exception/BaseException.php:
--------------------------------------------------------------------------------
1 | additionalParams;
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/src/Exception/ConfigNotFoundException.php:
--------------------------------------------------------------------------------
1 | additionalParams = ['searchedPaths' => $searchedPaths];
16 |
17 | return $e;
18 | }
19 | }
--------------------------------------------------------------------------------
/src/Exception/CyclicException.php:
--------------------------------------------------------------------------------
1 | additionalParams = ['parentId' => $parentId, 'childId' => $childId];
19 |
20 | return $e;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Exception/DatabaseException.php:
--------------------------------------------------------------------------------
1 | additionalParams = ['platformName' => $platformName];
11 |
12 | return $e;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Exception/NotUniqueException.php:
--------------------------------------------------------------------------------
1 | additionalParams = ['permissionName' => $permissionName];
15 |
16 | return $e;
17 | }
18 |
19 | public static function notUniqueRole(string $roleName): self
20 | {
21 | $e = new self("Role with given name already created");
22 | $e->additionalParams = ['roleName' => $roleName];
23 |
24 | return $e;
25 | }
26 |
27 | public static function permissionAlreadyAttachedToRole(string $permissionName, string $roleName): self
28 | {
29 | $e = new self("Permission already attached to role.");
30 | $e->additionalParams = ['permissionName' => $permissionName, 'roleName' => $roleName];
31 |
32 | return $e;
33 | }
34 |
35 | public static function childRoleAlreadyAttachedToGivenParentRole(string $childName, string $parentName): self
36 | {
37 | $e = new self("Child role already attached to parent role.");
38 | $e->additionalParams = ['childRoleName' => $childName, 'parentRoleName' => $parentName];
39 |
40 | return $e;
41 | }
42 |
43 | public static function roleAlreadyAssignedToUser(string $roleName, string $userId): self
44 | {
45 | $e = new self("Role already assigned to user");
46 | $e->additionalParams = ['roleName' => $roleName, 'userId' => $userId];
47 |
48 | return $e;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/Exception/PermissionNotFoundException.php:
--------------------------------------------------------------------------------
1 | id;
67 | }
68 |
69 | public function setId(int $id)
70 | {
71 | $this->id = $id;
72 | }
73 |
74 | public function getName(): string
75 | {
76 | return $this->name;
77 | }
78 |
79 | public function setName(string $name)
80 | {
81 | $this->name = $name;
82 | }
83 |
84 | public function isStatus(): bool
85 | {
86 | return $this->status;
87 | }
88 |
89 | public function setStatus(bool $status)
90 | {
91 | $this->status = $status;
92 | }
93 |
94 | public function getCreatedAt(): DateTime
95 | {
96 | return $this->createdAt;
97 | }
98 |
99 | public function setCreatedAt(DateTime $createdAt)
100 | {
101 | $this->createdAt = $createdAt;
102 | }
103 |
104 | public function getUpdatedAt(): DateTime
105 | {
106 | return $this->updatedAt;
107 | }
108 |
109 | public function setUpdatedAt(DateTime $updatedAt)
110 | {
111 | $this->updatedAt = $updatedAt;
112 | }
113 |
114 | public function getDescription(): string
115 | {
116 | return $this->description;
117 | }
118 |
119 | public function setDescription(string $description)
120 | {
121 | $this->description = $description;
122 | }
123 |
124 | /** @ORM\PrePersist */
125 | public function prePersist()
126 | {
127 | $this->createdAt = new DateTime();
128 | }
129 |
130 | /** @ORM\PreUpdate */
131 | public function preUpdate()
132 | {
133 | $this->updatedAt = new DateTime();
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/Models/Entity/Role.php:
--------------------------------------------------------------------------------
1 | id;
68 | }
69 |
70 | public function setId(int $id)
71 | {
72 | $this->id = $id;
73 | }
74 |
75 | public function getName(): string
76 | {
77 | return $this->name;
78 | }
79 |
80 | public function setName(string $name)
81 | {
82 | $this->name = $name;
83 | }
84 |
85 | public function isStatus(): bool
86 | {
87 | return $this->status;
88 | }
89 |
90 | public function setStatus(bool $status)
91 | {
92 | $this->status = $status;
93 | }
94 |
95 | public function getCreatedAt(): DateTime
96 | {
97 | return $this->createdAt;
98 | }
99 |
100 | public function setCreatedAt(DateTime $createdAt)
101 | {
102 | $this->createdAt = $createdAt;
103 | }
104 |
105 | public function getUpdatedAt(): DateTime
106 | {
107 | return $this->updatedAt;
108 | }
109 |
110 | public function setUpdatedAt(DateTime $updatedAt)
111 | {
112 | $this->updatedAt = $updatedAt;
113 | }
114 |
115 | public function getDescription(): string
116 | {
117 | return $this->description;
118 | }
119 |
120 | public function setDescription(string $description)
121 | {
122 | $this->description = $description;
123 | }
124 |
125 | /** @ORM\PrePersist */
126 | public function prePersist()
127 | {
128 | $this->createdAt = new DateTime();
129 | }
130 |
131 | /** @ORM\PreUpdate */
132 | public function preUpdate()
133 | {
134 | $this->updatedAt = new DateTime();
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/Models/Entity/RoleHierarchy.php:
--------------------------------------------------------------------------------
1 | id;
77 | }
78 |
79 | public function setId(int $id)
80 | {
81 | $this->id = $id;
82 | }
83 |
84 | public function getCreatedAt(): DateTime
85 | {
86 | return $this->createdAt;
87 | }
88 |
89 | public function setCreatedAt(DateTime $createdAt)
90 | {
91 | $this->createdAt = $createdAt;
92 | }
93 |
94 | public function getChildRole(): Role
95 | {
96 | return $this->childRole;
97 | }
98 |
99 | public function setChildRole(Role $childRole)
100 | {
101 | $this->childRole = $childRole;
102 | }
103 |
104 | public function getParentRole(): Role
105 | {
106 | return $this->parentRole;
107 | }
108 |
109 | public function setParentRole(Role $parentRole)
110 | {
111 | $this->parentRole = $parentRole;
112 | }
113 |
114 | /** @ORM\PrePersist */
115 | public function prePersist()
116 | {
117 | $this->createdAt = new DateTime();
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Models/Entity/RolePermission.php:
--------------------------------------------------------------------------------
1 | id;
77 | }
78 |
79 | public function setId(int $id)
80 | {
81 | $this->id = $id;
82 | }
83 |
84 | public function getRoleId(): int
85 | {
86 | return $this->roleId;
87 | }
88 |
89 | public function setRoleId(int $roleId)
90 | {
91 | $this->roleId = $roleId;
92 | }
93 |
94 | public function getPermissionId(): int
95 | {
96 | return $this->permissionId;
97 | }
98 |
99 | public function setPermissionId(int $permissionId)
100 | {
101 | $this->permissionId = $permissionId;
102 | }
103 |
104 | public function getCreatedAt(): DateTime
105 | {
106 | return $this->createdAt;
107 | }
108 |
109 | public function setCreatedAt(DateTime $createdAt)
110 | {
111 | $this->createdAt = $createdAt;
112 | }
113 |
114 | public function getPermission(): Permission
115 | {
116 | return $this->permission;
117 | }
118 |
119 | public function setPermission(Permission $permission)
120 | {
121 | $this->permission = $permission;
122 | }
123 |
124 | public function getRole(): Role
125 | {
126 | return $this->role;
127 | }
128 |
129 | public function setRole(Role $role)
130 | {
131 | $this->role = $role;
132 | }
133 |
134 | /** @ORM\PrePersist */
135 | public function prePersist()
136 | {
137 | $this->createdAt = new DateTime();
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Models/Entity/UserRole.php:
--------------------------------------------------------------------------------
1 | id;
64 | }
65 |
66 | public function setId(int $id)
67 | {
68 | $this->id = $id;
69 | }
70 |
71 | public function getUserId(): string
72 | {
73 | return $this->userId;
74 | }
75 |
76 | public function setUserId(string $userId)
77 | {
78 | $this->userId = $userId;
79 | }
80 |
81 | public function getRoleId(): int
82 | {
83 | return $this->roleId;
84 | }
85 |
86 | public function setRoleId(int $roleId)
87 | {
88 | $this->roleId = $roleId;
89 | }
90 |
91 | public function getCreatedAt(): DateTime
92 | {
93 | return $this->createdAt;
94 | }
95 |
96 | public function setCreatedAt(DateTime $createdAt)
97 | {
98 | $this->createdAt = $createdAt;
99 | }
100 |
101 | public function getRole(): Role
102 | {
103 | return $this->role;
104 | }
105 |
106 | public function setRole(Role $role)
107 | {
108 | $this->role = $role;
109 | }
110 |
111 | /** @ORM\PrePersist */
112 | public function prePersist()
113 | {
114 | $this->createdAt = new DateTime();
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/Models/Repository/PermissionRepository.php:
--------------------------------------------------------------------------------
1 | createQueryBuilder('permission');
21 |
22 | $result = $qb->select('permission.id')
23 | ->where($qb->expr()->eq('permission.name', $qb->expr()->literal($permissionName)))
24 | ->setMaxResults(1)
25 | ->getQuery()
26 | ->getArrayResult();
27 |
28 | if (count($result) > 0) {
29 | return $result[0]['id'];
30 | }
31 |
32 | return null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Models/Repository/RoleHierarchyRepository.php:
--------------------------------------------------------------------------------
1 | getChildIds([$parentRoleId]);
23 |
24 | if (count($childIds) > 0) {
25 |
26 | if (in_array($findingChildId, $childIds)) {
27 | return true;
28 | }
29 |
30 | foreach ($childIds as $childId) {
31 |
32 | if ($this->hasChildRoleId($childId, $findingChildId) == true) {
33 | return true;
34 | }
35 |
36 | }
37 | }
38 |
39 | return false;
40 | }
41 |
42 | /**
43 | * @param integer[] $rootRoleIds
44 | * @return integer[]
45 | * @throws QueryException
46 | */
47 | public function getAllRoleIdsHierarchy(array $rootRoleIds): array
48 | {
49 | $childRoleIds = $this->getAllChildRoleIds($rootRoleIds);
50 |
51 | return array_merge($rootRoleIds, $childRoleIds);
52 | }
53 |
54 | /**
55 | * Returns all hierarchically child role ids for given parent role ids.
56 | *
57 | * @param integer[] $parentIds
58 | * @return integer[]
59 | * @throws QueryException
60 | */
61 | private function getAllChildRoleIds(array $parentIds): array
62 | {
63 | $allChildIds = [];
64 |
65 | while (count($parentIds) > 0) {
66 | $parentIds = $this->getChildIds($parentIds);
67 | $allChildIds = array_merge($allChildIds, $parentIds);
68 | };
69 |
70 | return $allChildIds;
71 | }
72 |
73 | /**
74 | * Returns array of child role ids for given parent role ids.
75 | *
76 | * @param integer[] $parentIds
77 | * @return integer[]
78 | * @throws QueryException
79 | */
80 | private function getChildIds(array $parentIds): array
81 | {
82 | $qb = $this->createQueryBuilder('roleHierarchy');
83 |
84 | $qb->select('roleHierarchy.childRoleId')
85 | ->where($qb->expr()->in( 'roleHierarchy.parentRoleId', $parentIds))
86 | ->indexBy('roleHierarchy', 'roleHierarchy.childRoleId');
87 |
88 | $childRoleIds = $qb->getQuery()->getArrayResult();
89 |
90 | return array_keys($childRoleIds);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Models/Repository/RolePermissionRepository.php:
--------------------------------------------------------------------------------
1 | createQueryBuilder('rolePermission');
23 |
24 | $qb->select('rolePermission.roleId')
25 | ->where($qb->expr()->eq('rolePermission.permissionId', $permission->getId()));
26 |
27 | return $qb->getQuery()->getArrayResult();
28 | }
29 |
30 | /**
31 | * Search RolePermission record. If found return true else false
32 | * @param integer $permissionId
33 | * @param integer[] $roleIds
34 | * @return bool
35 | */
36 | public function isPermissionAssigned(int $permissionId, array $roleIds): bool
37 | {
38 | $qb = $this->createQueryBuilder('rolePermission');
39 |
40 | $result = $qb
41 | ->select('rolePermission.id')
42 | ->where(
43 | $qb->expr()->andX(
44 | $qb->expr()->eq('rolePermission.permissionId', $permissionId),
45 | $qb->expr()->in('rolePermission.roleId', $roleIds)
46 | )
47 | )
48 | ->setMaxResults(1)
49 | ->getQuery()
50 | ->getArrayResult();
51 |
52 | return count($result) > 0;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Models/Repository/RoleRepository.php:
--------------------------------------------------------------------------------
1 | createQueryBuilder('userRole');
24 |
25 | $qb->select('userRole.roleId')
26 | ->where($qb->expr()->eq('userRole.userId', $userId))
27 | ->indexBy('userRole', 'userRole.roleId');
28 |
29 | $roleIds = $qb->getQuery()->getArrayResult();
30 |
31 | return array_keys($roleIds);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Models/RepositoryRegistry.php:
--------------------------------------------------------------------------------
1 | entityManager = $entityManager;
20 | }
21 |
22 | public function getPermissionRepository(): PermissionRepository
23 | {
24 | return $this->entityManager->getRepository('\\Potievdev\\SlimRbac\\Models\\Entity\\Permission');
25 | }
26 |
27 | public function getRoleRepository(): RoleRepository
28 | {
29 | return $this->entityManager->getRepository('\\Potievdev\\SlimRbac\\Models\\Entity\\Role');
30 | }
31 |
32 | public function getUserRoleRepository(): UserRoleRepository
33 | {
34 | return $this->entityManager->getRepository('\\Potievdev\\SlimRbac\\Models\\Entity\\UserRole');
35 | }
36 |
37 | public function getRolePermissionRepository(): RolePermissionRepository
38 | {
39 | return $this->entityManager->getRepository('\\Potievdev\\SlimRbac\\Models\\Entity\\RolePermission');
40 | }
41 |
42 | public function getRoleHierarchyRepository(): RoleHierarchyRepository
43 | {
44 | return $this->entityManager->getRepository('\\Potievdev\\SlimRbac\\Models\\Entity\\RoleHierarchy');
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/tests/_data/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/potievdev/slim-rbac/ce2df17ae736908e2538b924d3e8143b1e48ba75/tests/_data/.gitkeep
--------------------------------------------------------------------------------
/tests/_output/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/tests/_support/AcceptanceTester.php:
--------------------------------------------------------------------------------
1 | rbacContainer = new RbacContainer();
43 | $this->rbacManager = $this->rbacContainer->getRbacManager();
44 | $this->repositoryRegistry = $this->rbacContainer->getInnerContainer()->get('repositoryRegistry');
45 | $this->accessChecker = $this->rbacContainer->getInnerContainer()->get('accessChecker');
46 | $this->clearDatabase();
47 | }
48 |
49 | /**
50 | * @throws DatabaseException
51 | */
52 | private function clearDatabase(): void
53 | {
54 | $pdo = $this->rbacContainer->getInnerContainer()
55 | ->get('entityManager')
56 | ->getConnection()
57 | ->getNativeConnection();
58 |
59 | $pdo->beginTransaction();
60 |
61 | try {
62 | $pdo->exec('DELETE FROM role_permission WHERE 1 > 0');
63 | $pdo->exec('DELETE FROM role_hierarchy WHERE 1 > 0');
64 | $pdo->exec('DELETE FROM permission WHERE 1 > 0');
65 | $pdo->exec('DELETE FROM user_role WHERE 1 > 0');
66 | $pdo->exec('DELETE FROM role WHERE 1 > 0');
67 |
68 | $pdo->commit();
69 |
70 | } catch (Exception $e) {
71 | $pdo->rollBack();
72 | throw new DatabaseException($e->getMessage());
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/unit/RbacManagerTest.php:
--------------------------------------------------------------------------------
1 | rbacManager->createPermission('edit');
30 | $write = $this->rbacManager->createPermission('write');
31 |
32 | $moderator = $this->rbacManager->createRole('moderator');
33 | $admin = $this->rbacManager->createRole('admin');
34 |
35 | $this->rbacManager->attachPermission($moderator, $edit);
36 | $this->rbacManager->attachPermission($admin, $write);
37 |
38 | $this->rbacManager->attachChildRole($admin, $moderator);
39 |
40 | $this->rbacManager->assignRoleToUser($moderator, self::MODERATOR_USER_ID);
41 | $this->rbacManager->assignRoleToUser($admin, self::ADMIN_USER_ID);
42 | }
43 |
44 | public function successCasesProvider(): array
45 | {
46 | return [
47 | 'moderator can edit' => [self::MODERATOR_USER_ID, 'edit'],
48 | 'admin can edit' => [self::ADMIN_USER_ID, 'edit'],
49 | 'admin can write' => [self::ADMIN_USER_ID, 'write'],
50 | ];
51 | }
52 |
53 | /**
54 | * Testing has permission cases.
55 | * @param integer $userId user id
56 | * @param string $roleOrPermission role or permission name
57 | * @throws QueryException
58 | * @dataProvider successCasesProvider
59 | */
60 | public function testCheckAccessSuccessCases(int $userId, string $roleOrPermission): void
61 | {
62 | $this->assertTrue($this->accessChecker->hasAccess($userId, $roleOrPermission));
63 | }
64 |
65 | /**
66 | * @return array
67 | */
68 | public function failCasesProvider(): array
69 | {
70 | return [
71 | 'moderator has no write permission' => [self::MODERATOR_USER_ID, 'write'],
72 | 'not existing permission' => [self::ADMIN_USER_ID, 'none_permission'],
73 | 'not existing user id not has permission' => [self::NOT_USER_ID, 'edit'],
74 | 'not existing user id not has role' => [self::NOT_USER_ID, 'admin']
75 | ];
76 | }
77 |
78 | /**
79 | * Testing not have permission cases
80 | * @param integer $userId user id
81 | * @param string $roleOrPermission role or permission name
82 | * @throws QueryException
83 | * @dataProvider failCasesProvider
84 | */
85 | public function testCheckAccessFailureCases(int $userId, string $roleOrPermission): void
86 | {
87 | $this->assertFalse($this->accessChecker->hasAccess($userId, $roleOrPermission));
88 | }
89 |
90 | /**
91 | * Testing adding not unique permission
92 | *
93 | * @throws DatabaseException
94 | * @throws NotUniqueException|ORMException
95 | */
96 | public function testCheckAddingNotUniquePermission()
97 | {
98 | $this->expectException(NotUniqueException::class);
99 | $this->rbacManager->createPermission('edit');
100 | }
101 |
102 | /**
103 | * Testing adding not unique role
104 | *
105 | * @throws DatabaseException
106 | * @throws NotUniqueException|ORMException
107 | */
108 | public function testCheckAddingNonUniqueRole()
109 | {
110 | $this->expectException(NotUniqueException::class);
111 | $this->rbacManager->createRole('moderator');
112 | }
113 |
114 | /**
115 | *
116 | * @throws CyclicException
117 | * @throws DatabaseException
118 | * @throws NotUniqueException
119 | * @throws QueryException|ORMException
120 | */
121 | public function testCheckCyclicException()
122 | {
123 | $this->expectException(CyclicException::class);
124 | $a = $this->rbacManager->createRole('a');
125 | $b = $this->rbacManager->createRole('b');
126 |
127 | $this->rbacManager->attachChildRole($a, $b);
128 | $this->rbacManager->attachChildRole($b, $a);
129 | }
130 |
131 | /**
132 | * Testing creating permission
133 | */
134 | public function testCheckCreatingPermission()
135 | {
136 | $permission = $this->repositoryRegistry
137 | ->getPermissionRepository()
138 | ->findOneBy(['name' => 'edit']);
139 |
140 | $this->assertTrue($permission instanceof Permission);
141 | }
142 |
143 | /**
144 | * Testing creating role
145 | */
146 | public function testCheckCreatingRole()
147 | {
148 | $role = $this->repositoryRegistry
149 | ->getRoleRepository()
150 | ->findOneBy(['name' => 'admin']);
151 |
152 | $this->assertTrue($role instanceof Role);
153 | }
154 |
155 | /**
156 | * @throws DatabaseException|ORMException
157 | */
158 | public function testCheckDoubleAssigningPermissionToSameRole()
159 | {
160 | $this->expectException(NotUniqueException::class);
161 |
162 | /** @var Role $role */
163 | $role = $this->repositoryRegistry
164 | ->getRoleRepository()
165 | ->findOneBy(['name' => 'admin']);
166 |
167 | /** @var Permission $permission */
168 | $permission = $this->repositoryRegistry
169 | ->getPermissionRepository()
170 | ->findOneBy(['name' => 'write']);
171 |
172 | $this->rbacManager->attachPermission($role, $permission);
173 | }
174 |
175 | /**
176 | * @throws QueryException
177 | * @throws CyclicException
178 | * @throws DatabaseException
179 | * @throws NotUniqueException|ORMException
180 | *
181 | */
182 | public function testCheckAddingSameChildRoleDoubleTime()
183 | {
184 | $this->expectException(NotUniqueException::class);
185 |
186 | /** @var Role $parent */
187 | $parent = $this->repositoryRegistry
188 | ->getRoleRepository()
189 | ->findOneBy(['name' => 'admin']);
190 |
191 | /** @var Role $child */
192 | $child = $this->repositoryRegistry
193 | ->getRoleRepository()
194 | ->findOneBy(['name' => 'moderator']);
195 |
196 | $this->rbacManager->attachChildRole($parent, $child);
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/tests/unit/RbacMiddlewareTest.php:
--------------------------------------------------------------------------------
1 | rbacManager->createPermission('edit', 'Edit permission');
45 | $write = $this->rbacManager->createPermission('write', 'Write permission');
46 |
47 | $moderator = $this->rbacManager->createRole('moderator', 'Moderator role');
48 | $admin = $this->rbacManager->createRole('admin', 'Admin role');
49 |
50 | $this->rbacManager->attachPermission($moderator, $edit);
51 | $this->rbacManager->attachPermission($admin, $write);
52 | $this->rbacManager->attachChildRole($admin, $moderator);
53 |
54 | $this->rbacManager->assignRoleToUser($moderator, self::MODERATOR_USER_ID);
55 | $this->rbacManager->assignRoleToUser($admin, self::ADMIN_USER_ID);
56 |
57 | $this->callable = function (Request $request, Response $response) {
58 | return $response;
59 | };
60 | $this->request = new ServerRequest('GET', 'write');
61 | $this->response = new Response();
62 | }
63 |
64 | /**
65 | * @throws QueryException
66 | * @throws InvalidArgumentException
67 | */
68 | public function testCheckAccessSuccessCase()
69 | {
70 | $middleware = (new RbacContainer())->getRbacMiddleware();
71 | $request = $this->request->withAttribute('userId', self::ADMIN_USER_ID);
72 | $response = $middleware($request, $this->response, $this->callable);
73 | $this->assertEquals(200, $response->getStatusCode());
74 | }
75 |
76 | /**
77 | * @throws QueryException
78 | * @throws InvalidArgumentException
79 | */
80 | public function testCheckAccessDeniedCase()
81 | {
82 | $middleware = (new RbacContainer())->getRbacMiddleware();
83 | $request = $this->request->withAttribute('userId', self::MODERATOR_USER_ID);
84 | $response = $middleware($request, $this->response, $this->callable);
85 | $this->assertEquals(403, $response->getStatusCode());
86 | }
87 |
88 | /**
89 | * @throws QueryException
90 | * @throws InvalidArgumentException
91 | * @throws ConfigNotFoundException
92 | */
93 | public function testCheckReadingUserIdFromHeader()
94 | {
95 | $middleware = (new RbacContainer($this->createRbacConfig(RbacConfig::HEADER_RESOURCE_TYPE)))
96 | ->getRbacMiddleware();
97 | $request = $this->request->withHeader('userId', self::ADMIN_USER_ID);
98 | $response = $middleware($request, $this->response, $this->callable);
99 | $this->assertEquals(200, $response->getStatusCode());
100 | }
101 |
102 | /**
103 | * @throws QueryException
104 | * @throws InvalidArgumentException
105 | * @throws ConfigNotFoundException
106 | */
107 | public function testCheckReadingUserIdFromCookie()
108 | {
109 | $middleware = (new RbacContainer($this->createRbacConfig(RbacConfig::COOKIE_RESOURCE_TYPE)))
110 | ->getRbacMiddleware();
111 | $request = $this->request->withCookieParams(['userId' => self::ADMIN_USER_ID]);
112 | $response = $middleware($request, $this->response, $this->callable);
113 | $this->assertEquals(200, $response->getStatusCode());
114 | }
115 |
116 | /**
117 | * @throws ConfigNotFoundException
118 | */
119 | private function createRbacConfig(?string $resourceTypeId): RbacConfig
120 | {
121 | $rbacConfig = RbacConfig::createFromConfigFile();
122 |
123 | return new RbacConfig(
124 | $rbacConfig->getDatabaseDriver(),
125 | $rbacConfig->getDatabaseHost(),
126 | $rbacConfig->getDatabaseUser(),
127 | $rbacConfig->getDatabasePassword(),
128 | $rbacConfig->getDatabasePort(),
129 | $rbacConfig->getDatabaseName(),
130 | $rbacConfig->getDatabaseCharset(),
131 | $rbacConfig->getUserIdFieldName(),
132 | $resourceTypeId ?? $rbacConfig->getUserIdResourceType()
133 | );
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------