├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── Bug-Report.yml │ ├── Feature-Request.yml │ ├── Improvement.yml │ └── config.yml ├── ci │ ├── files │ │ └── .env │ └── scripts │ │ └── setup-pimcore-environment.sh └── workflows │ ├── cla-check.yaml │ ├── php-style.yml │ ├── poeditor-export.yml │ ├── stale.yml │ └── static-analysis.yml ├── .gitignore ├── .php-cs-fixer.cache ├── .php-cs-fixer.dist.php ├── LICENSE.md ├── README.md ├── SECURITY.md ├── composer.json ├── doc └── img │ ├── property.jpg │ └── sample.jpg ├── phpstan-baseline.neon ├── phpstan-bootstrap.php ├── phpstan.neon └── src ├── CoreExtensions ├── ClassDefinitions │ ├── DynamicPermissionResource.php │ ├── Helper │ │ └── DataProviderResolver.php │ ├── Interfaces │ │ └── DataProviderInterface.php │ ├── PermissionManyToManyRelation.php │ ├── PermissionManyToOneRelation.php │ └── PermissionResource.php ├── Navigation │ └── Builder.php └── Traits │ └── PermissionResourcesAsRolesTrait.php ├── DependencyInjection └── FrontendPermissionToolkitExtension.php ├── Event └── PermissionsEvent.php ├── EventSubscriber └── CustomLayoutSubscriber.php ├── FrontendPermissionToolkitBundle.php ├── Resources ├── config │ ├── pimcore │ │ └── config.yml │ └── services.yml ├── public │ ├── css │ │ └── backend.css │ └── js │ │ ├── datatypes │ │ ├── classes │ │ │ └── data │ │ │ │ ├── dynamicPermissionResource.js │ │ │ │ ├── permissionManyToManyRelation.js │ │ │ │ ├── permissionManyToOneRelation.js │ │ │ │ └── permissionResource.js │ │ └── tags │ │ │ ├── dynamicPermissionResource.js │ │ │ ├── permissionManyToManyRelation.js │ │ │ ├── permissionManyToOneRelation.js │ │ │ └── permissionResource.js │ │ └── startup.js └── translations │ ├── admin.ca.yml │ ├── admin.cs.yml │ ├── admin.de.yml │ ├── admin.en.yml │ ├── admin.es.yml │ ├── admin.fr.yml │ ├── admin.hu.yml │ ├── admin.it.yml │ ├── admin.nl.yml │ ├── admin.pl.yml │ ├── admin.pt_br.yml │ ├── admin.ro.yml │ ├── admin.sk.yml │ ├── admin.sv.yml │ ├── admin.th.yml │ └── admin.zh_Hans.yml └── Service.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [*.php] 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [*.yml] 17 | indent_size = 4 18 | 19 | [composer.json] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | * -text -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug-Report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: [Bug] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ## Important notice 10 | As an open core project we love to work together with our community to improve and develop our products. 11 | It's also important for us to make clear that **we're not working for you or your company**, 12 | but we enjoy to work together to solve existing bugs. 13 | So we would love to see PRs with bugfixes, discuss them and we are happy to merge them when they are ready. 14 | For details see also our [contributing guidelines](https://github.com/pimcore/pimcore/blob/10.x/CONTRIBUTING.md). 15 | 16 | Bug reports that do not meet the conditions listed below will be closed/deleted without comment. 17 | 18 | - Bug was verified on the latest supported version. 19 | - This is not a security issue -> see [our security policy](https://github.com/pimcore/pimcore/security/policy) instead. 20 | - You are not able to provide a pull request that fixes the issue. 21 | - There's no existing ticket for the same issue. 22 | 23 | - type: textarea 24 | attributes: 25 | label: Expected behavior 26 | validations: 27 | required: true 28 | - type: textarea 29 | attributes: 30 | label: Actual behavior 31 | validations: 32 | required: true 33 | - type: textarea 34 | attributes: 35 | label: Steps to reproduce 36 | validations: 37 | required: true 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature-Request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Request or propose a new feature 3 | title: "[Feature]: " 4 | labels: ["New Feature"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ## Important notice 10 | As an open core project we love to work together with our community to improve and develop our products. 11 | It's also important for us to make clear that **we're not working for you or your company**, 12 | but we enjoy to work together to improve or add new features to the product. 13 | So we are always ready to discuss features and improvements with our community. 14 | Especially for bigger topics, please [start a discussion](https://github.com/pimcore/pimcore/discussions) first to aviod unnecessary efforts. 15 | 16 | As soon as a topic is more specific, feel free to create issues for it or even better provide a corresponding PR as we love to 17 | review and merge contributions. 18 | 19 | Feature requests that do not meet the conditions listed below will be closed/deleted without comment. 20 | - There's no existing ticket for the same topic 21 | - This is already a specific ready-to-work-on feature request 22 | 23 | - type: textarea 24 | attributes: 25 | label: Feature description 26 | validations: 27 | required: true 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Improvement.yml: -------------------------------------------------------------------------------- 1 | name: Improvement 2 | description: Request or propose an improvement 3 | title: "[Improvement]: " 4 | labels: ["Improvement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ## Important notice 10 | As an open core project we love to work together with our community to improve and develop our products. 11 | It's also important for us to make clear that **we're not working for you or your company**, 12 | but we enjoy to work together to improve or add new features to the product. 13 | So we are always ready to discuss features and improvements with our community. 14 | Especially for bigger topics, please [start a discussion](https://github.com/pimcore/pimcore/discussions) first to aviod unnecessary efforts. 15 | 16 | As soon as a topic is more specific, feel free to create issues for it or even better provide a corresponding PR as we love to 17 | review and merge contributions. 18 | 19 | Feature requests that do not meet the conditions listed below will be closed/deleted without comment. 20 | - There's no existing ticket for the same topic 21 | - This is already a specific ready-to-work-on feature request 22 | 23 | - type: textarea 24 | attributes: 25 | label: Improvement description 26 | validations: 27 | required: true 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: We are hiring! 4 | url: https://pimcore.com/en/careers?utm_source=github&utm_medium=issue-template-frontend-permission-toolkit&utm_campaign=careers 5 | about: Enjoy working with Pimcore? Join us on our mission! 6 | - name: Community Support 7 | url: https://github.com/pimcore/pimcore/discussions 8 | about: Please ask and answer questions here. 9 | -------------------------------------------------------------------------------- /.github/ci/files/.env: -------------------------------------------------------------------------------- 1 | APP_ENV=test -------------------------------------------------------------------------------- /.github/ci/scripts/setup-pimcore-environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu -o xtrace 4 | 5 | cp .github/ci/files/.env . -------------------------------------------------------------------------------- /.github/workflows/cla-check.yaml: -------------------------------------------------------------------------------- 1 | name: CLA check 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | pull_request_target: 7 | types: [opened, closed, synchronize] 8 | 9 | jobs: 10 | cla-workflow: 11 | uses: pimcore/workflows-collection-public/.github/workflows/reusable-cla-check.yaml@v1.3.0 12 | if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' 13 | secrets: 14 | CLA_ACTION_ACCESS_TOKEN: ${{ secrets.CLA_ACTION_ACCESS_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/php-style.yml: -------------------------------------------------------------------------------- 1 | name: "PHP-CS-Fixer" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "[0-9]+.[0-9]+" 7 | - "[0-9]+.x" 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | php-style: 14 | uses: pimcore/workflows-collection-public/.github/workflows/reusable-php-cs-fixer.yaml@main 15 | if: github.repository_owner == 'pimcore' 16 | secrets: 17 | PHP_CS_FIXER_GITHUB_TOKEN: ${{ secrets.PHP_CS_FIXER_GITHUB_TOKEN }} 18 | with: 19 | head_ref: ${{ github.event.pull_request.head.ref }} 20 | repository: ${{ github.event.pull_request.head.repo.full_name }} 21 | config_file: .php-cs-fixer.dist.php 22 | -------------------------------------------------------------------------------- /.github/workflows/poeditor-export.yml: -------------------------------------------------------------------------------- 1 | 2 | name: "Trigger POEditor Translations Export" 3 | 4 | on: 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - "[0-9]+.x" 9 | paths: 10 | - 'src/Resources/translations/admin.en.yml' 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | poeditor: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Trigger workflow in pimcore/poeditor-export-action 20 | env: 21 | GH_TOKEN: ${{ secrets.POEDITOR_ACTION_TRIGGER_TOKEN }} 22 | run: | 23 | gh workflow run -R pimcore/poeditor-export-action poeditor-export.yaml 24 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Handle stale issues 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '37 7 * * *' 7 | 8 | jobs: 9 | call-stale-workflow: 10 | uses: pimcore/workflows-collection-public/.github/workflows/stale.yml@v1.1.0 11 | -------------------------------------------------------------------------------- /.github/workflows/static-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Static Analysis" 2 | 3 | on: 4 | schedule: 5 | - cron: '0 4 * * 1,3,5' 6 | pull_request: 7 | branches: 8 | - "[0-9]+.[0-9]+" 9 | - "[0-9]+.x" 10 | push: 11 | branches: 12 | - "[0-9]+.[0-9]+" 13 | - "[0-9]+.x" 14 | - "*_actions" 15 | jobs: 16 | static-analysis-phpstan: 17 | name: "Static Analysis with PHPStan" 18 | runs-on: "ubuntu-latest" 19 | continue-on-error: ${{ matrix.experimental }} 20 | strategy: 21 | matrix: 22 | include: 23 | - { php-version: "8.3", dependencies: "lowest", experimental: false } 24 | - { php-version: "8.4", dependencies: "highest", experimental: false } 25 | - { php-version: "8.4", dependencies: "highest", pimcore_version: "12.x-dev as 12.0.0", experimental: true } 26 | steps: 27 | - name: "Checkout code" 28 | uses: actions/checkout@v2 29 | 30 | - name: "Install PHP" 31 | uses: shivammathur/setup-php@v2 32 | with: 33 | coverage: "none" 34 | php-version: "${{ matrix.php-version }}" 35 | 36 | - name: "Setup Pimcore environment" 37 | run: | 38 | .github/ci/scripts/setup-pimcore-environment.sh 39 | 40 | - name: "Update Pimcore version" 41 | env: 42 | PIMCORE_VERSION: "${{ matrix.pimcore_version }}" 43 | run: | 44 | if [ ! -z "$PIMCORE_VERSION" ]; then 45 | composer require --no-update pimcore/pimcore:"${PIMCORE_VERSION}" 46 | fi 47 | 48 | 49 | - name: "Install dependencies with Composer" 50 | uses: ramsey/composer-install@v2 51 | with: 52 | dependency-versions: "${{ matrix.dependencies }}" 53 | 54 | - name: "Run a static analysis with phpstan/phpstan" 55 | run: vendor/bin/phpstan analyse ${{ matrix.phpstan_args }} -c phpstan.neon --memory-limit=-1 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | *.log 4 | 5 | /phpunit.xml 6 | 7 | # PHP-CS-Fixer 8 | /.php_cs 9 | /.php_cs.cache 10 | 11 | # composer 12 | /composer.lock 13 | !/vendor 14 | /vendor/* 15 | !/vendor/.gitkeep 16 | 17 | # PhpStorm / IDEA 18 | .idea -------------------------------------------------------------------------------- /.php-cs-fixer.cache: -------------------------------------------------------------------------------- 1 | {"php":"8.3.20","version":"3.75.0","indent":" ","lineEnding":"\n","rules":{"encoding":true,"full_opening_tag":true,"blank_line_after_namespace":true,"braces_position":true,"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ignore"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","do","else","elseif","final","for","foreach","function","if","interface","namespace","private","protected","public","static","switch","trait","try","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["method","property"]},"array_syntax":{"syntax":"short"},"header_comment":{"comment_type":"PHPDoc","header":"This source file is available under the terms of the\nPimcore Open Core License (POCL)\nFull copyright and license information is available in\nLICENSE.md which is distributed with this source code.\n\n @copyright Copyright (c) Pimcore GmbH (https:\/\/www.pimcore.com)\n @license Pimcore Open Core License (POCL)"},"blank_line_before_statement":true,"function_typehint_space":true,"single_line_comment_style":true,"lowercase_cast":true,"magic_constant_casing":true,"class_attributes_separation":true,"native_function_casing":true,"no_blank_lines_after_class_opening":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_blank_lines":true,"no_leading_import_slash":true,"no_leading_namespace_whitespace":true,"no_short_bool_cast":true,"no_spaces_around_offset":true,"no_unneeded_control_parentheses":true,"no_unused_imports":true,"no_whitespace_before_comma_in_array":true,"no_whitespace_in_blank_line":true,"object_operator_without_whitespace":true,"ordered_imports":true,"phpdoc_indent":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"return_type_declaration":true,"self_accessor":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_quote":true,"space_after_semicolon":true,"standardize_not_equals":true,"ternary_operator_spaces":true,"whitespace_after_comma_in_array":true},"hashes":{"src\/EventSubscriber\/CustomLayoutSubscriber.php":"82e112e13c2272a40af3e5d8aac28f21","src\/FrontendPermissionToolkitBundle.php":"f9cf2cf49f00f7bbb931501ef0f98be3","src\/DependencyInjection\/FrontendPermissionToolkitExtension.php":"a02553fa3d9c05eb9b6462dc6feba9bf","src\/Service.php":"9c31cf18a5433fa9cb407713cf6c1b35","src\/CoreExtensions\/Traits\/PermissionResourcesAsRolesTrait.php":"7e0fc3954721a209fa1e134308769fcb","src\/CoreExtensions\/Navigation\/Builder.php":"f6126bc29c4b3a9f7a9ec7f602db8d58","src\/CoreExtensions\/ClassDefinitions\/PermissionResource.php":"b07b0d1c0d0d7626b574f07c392937fe","src\/CoreExtensions\/ClassDefinitions\/PermissionManyToOneRelation.php":"f38465e3d753d173873bf361e82670b8","src\/CoreExtensions\/ClassDefinitions\/Helper\/DataProviderResolver.php":"b9e9893a01d80370da3d6387dfeb964c","src\/CoreExtensions\/ClassDefinitions\/Interfaces\/DataProviderInterface.php":"85034c8e1615b0d166d51a3551ce34d2","src\/CoreExtensions\/ClassDefinitions\/DynamicPermissionResource.php":"56decccd2bb7a3f49b682e551dbaada6","src\/CoreExtensions\/ClassDefinitions\/PermissionManyToManyRelation.php":"d5fe4aee4b315f695e675ade3a456fca","src\/Event\/PermissionsEvent.php":"1a7bb9be7ac41ca85c5ac8c6541c17d9"}} -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([__DIR__ . '/src']) 5 | 6 | // do not fix views 7 | ->notName('*.html.php'); 8 | 9 | $config = new PhpCsFixer\Config(); 10 | $config->setRules([ 11 | '@PSR1' => true, 12 | '@PSR2' => true, 13 | 'array_syntax' => ['syntax' => 'short'], 14 | 15 | 'header_comment' => [ 16 | 'comment_type' => 'PHPDoc', 17 | 'header' => 18 | 'This source file is available under the terms of the' . PHP_EOL . 19 | 'Pimcore Open Core License (POCL)' . PHP_EOL . 20 | 'Full copyright and license information is available in' . PHP_EOL . 21 | 'LICENSE.md which is distributed with this source code.' . PHP_EOL . 22 | PHP_EOL . 23 | ' @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)' . PHP_EOL . 24 | ' @license Pimcore Open Core License (POCL)' 25 | ], 26 | 27 | 'blank_line_before_statement' => true, 28 | 'encoding' => true, 29 | 'function_typehint_space' => true, 30 | 'single_line_comment_style' => true, 31 | 'lowercase_cast' => true, 32 | 'magic_constant_casing' => true, 33 | 'method_argument_space' => ['on_multiline' => 'ignore'], 34 | 'class_attributes_separation' => true, 35 | 'native_function_casing' => true, 36 | 'no_blank_lines_after_class_opening' => true, 37 | 'no_blank_lines_after_phpdoc' => true, 38 | 'no_empty_comment' => true, 39 | 'no_empty_phpdoc' => true, 40 | 'no_empty_statement' => true, 41 | 'no_extra_blank_lines' => true, 42 | 'no_leading_import_slash' => true, 43 | 'no_leading_namespace_whitespace' => true, 44 | 'no_short_bool_cast' => true, 45 | 'no_spaces_around_offset' => true, 46 | 'no_unneeded_control_parentheses' => true, 47 | 'no_unused_imports' => true, 48 | 'no_whitespace_before_comma_in_array' => true, 49 | 'no_whitespace_in_blank_line' => true, 50 | 'object_operator_without_whitespace' => true, 51 | 'ordered_imports' => true, 52 | 'phpdoc_indent' => true, 53 | 'phpdoc_no_useless_inheritdoc' => true, 54 | 'phpdoc_scalar' => true, 55 | 'phpdoc_separation' => true, 56 | 'phpdoc_single_line_var_spacing' => true, 57 | 'return_type_declaration' => true, 58 | 'self_accessor' => true, 59 | 'short_scalar_cast' => true, 60 | 'single_blank_line_before_namespace' => true, 61 | 'single_quote' => true, 62 | 'space_after_semicolon' => true, 63 | 'standardize_not_equals' => true, 64 | 'ternary_operator_spaces' => true, 65 | 'whitespace_after_comma_in_array' => true, 66 | ]); 67 | 68 | $config->setFinder($finder); 69 | return $config; 70 | 71 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | Copyright (C) Pimcore GmbH (http://www.pimcore.com) 3 | 4 | This software is available under the terms of the 5 | following Pimcore Open Core License (POCL) 6 | 7 | 8 | **PIMCORE OPEN CORE LICENSE AGREEMENT (POCL)** 9 | 10 | **Last Update: April 2025** 11 | 12 | This Open Core License Agreement ("**Agreement**" or “**POCL**”), effective as of the day of the first installation or use by Customer (the "**Effective Date**"), is by and between Pimcore GmbH, Söllheimer Straße 16, AT-5020 Salzburg, Republic of Austria (hereinafter "**Licensor**" or “**Pimcore**”) and the user of the Software, as defined herein, (hereinafter "**Licensee**" or "**Customer**"). Licensor and Licensee may be referred to herein collectively as the "**Parties**" or individually as a "**Party**." 13 | 14 | **WHEREAS** Licensor desires to license out certain Pimcore Software (“**Software**“). 15 | 16 | **WHEREAS** (a) Software for which the source code is publicly available but which is not licensed out as open source software is "**Open Core Software**" and 17 | (b) Software for which the source code is not publicly available is "**Proprietary Software**", 18 | both covered by this Agreement. 19 | 20 | **WHEREAS** the exact products that are available under this Agreement are defined in the additional contractual documents or by inclusion of, or referral to, this Agreement within the source code or within the source code repositories; if not provided for otherwise, a software element is Proprietary Software. 21 | 22 | **WHEREAS** the Software is protected by copyright world- wide; and 23 | 24 | **WHEREAS** Licensee desires to obtain a license to use the Software for its internal business purposes, subject to the terms and conditions of this Agreement. 25 | 26 | **NOW, THEREFORE**, in consideration of the mutual covenants, terms, and conditions set forth herein, and for other good and valuable consideration, the receipt and sufficiency of which are hereby acknowledged, the Parties agree as follows. 27 | 28 | ### 1. LICENSE 29 | 1.1 PLEASE READ THIS PIMCORE SOFTWARE LICENSE AGREEMENT CAREFULLY AS IT CONSTITUTES A LEGALLY BINDING AGREEMENT. BY INSTALLING OR USING THE SOFTWARE, YOU ACCEPT AND AGREE TO ALL TERMS AND CONDITIONS OF THIS AGREEMENT, AND CONFIRM THAT YOUR STATEMENT – IF APPLICABLE – ON THE RELEVANT GLOBAL REVENUE IS CORRECT AND COMPLETE. IF YOU REPRESENT A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU HAVE FULL LEGAL AUTHORITY TO ENTER INTO THIS AGREEMENT TO BIND THAT LEGAL ENTITY. IF YOU DO NOT AGREE TO THESE TERMS AND CONDITIONS, YOU MAY NOT INSTALL OR USE THE SOFTWARE. 30 | 31 | 1.2 Pimcore grants the Customer a non-exclusive, non-transferable, non-sublicensable, geographically unlimited right, limited in time to the term of the Agreement, to use the Software and to customize, modify or adapt it for its own purposes. Unless if required by Pimcore for compliance with applicable laws or any order of a governmental authority, the Customer is not obliged to share these modifications, adaptations, and customizations (“**Derivatives**”) with Pimcore or anyone else. 32 | 33 | 1.2.1 Solution Development and Production Use (Open Core Software) 34 | 35 | “**Production Use**” means the usage of a software for development of solutions and productions within a business operation. 36 | 37 | a) An organization with total global revenue not exceeding €5 million (€5M) or equivalent amount in other currency annually (“**Threshold**”) may qualify for a free license for Production Use of the Open Core Software, provided such organization is not a part, subsidiary, affiliate, or shell company to another organization, entity, or company group whose total combined revenue exceeds the Threshold. Eligibility must be self-certified by the Customer when starting the use of the Open Core Software and is subject to periodic review and audit by Pimcore. If at any time the Customer’s revenue exceeds the Threshold, a paid commercial license will be required for continued Production Use of the software. The Customer is obliged to inform Pimcore about relevant changes in revenues. Pimcore is entitled to charge license fees retroactively from the date on which Customer exceeded the Threshold. 38 | 39 | b) Non-profit and educational organizations are eligible for a free license for Production Use of the Open Core Software, subject to Pimcore’s non-profit criteria. 40 | 41 | Pimcore shall decide at its own reasonable discretion whether (a) the Threshold is exceeded or (b) the requirements for non-profit or educational usage are met. Legal recourse is excluded with regard to such decision of Pimcore. 42 | 43 | 1.2.2 Non-Production Use and Transition to Production Use (Open Core Software) 44 | 45 | For non-production purposes, such as demonstrations, designing of prototypes, proofs of concept, and sales presentations (such and comparable usages of a software “**Non-Production Use**”), the Pimcore Developer License (PDLA) must be purchased. 46 | 47 | If the Customer or a Partner or any other third person acting on the Customer’s behalf initiates development of a solution with the intention or foreseeable or actual effect of deploying it into production, such use from its beginning shall be deemed Production Use of the Open Core Software for which the Threshold applies from the outset. Individual transition periods to Production Use may be agreed between Pimcore and Customer in writing. 48 | 49 | Pimcore reserves the right to audit, verify and enforce compliance with these terms, including restricting or terminating access to the Open Core Software. 50 | 51 | 1.2.3 The use of Proprietary Software is never free of charge. Sect. 1.2.1 and 1.2.2 do not apply to Proprietary Software. 52 | 53 | 1.3 Restrictions on Use 54 | 55 | 1.3.1 The Customer may not offer the Software as a hosted or managed service by granting third parties access to a significant part of its features or functions. Additionally, the Customer may not fork, modify, or redistribute the Software, or any Derivative, in a manner that results in a competing or functionally comparable product that is offered as a free or commercial alternative to Pimcore’s official offerings. 56 | 57 | 1.3.2 The Customer shall also refrain from incorporating the Software, or any Derivative, into a commercial product or service offering materially deriving its economic value from the Software, even if it is not directly exposed or obvious. 58 | 59 | 1.3.3 The Customer is also prohibited from representing, implying, or otherwise suggesting that its use, distribution, or customization of the Software is endorsed, certified, or supported by Pimcore, unless such authorization has been explicitly granted in writing. 60 | 61 | 1.3.4 The Customer may only use the Software for its own enterprise. The Customer may not use the Software simultaneously in more instances than Customer has acquired usage licences for. The Customer is only permitted to copy the Software to the extent that this is necessary for the intended use, including the correction of errors. The creation of a backup copy is permitted if it is necessary to secure the contractual use. 62 | 63 | 1.3.5 The Customer must not, at any time, (i) rent, lease, lend, sell, license, assign, distribute, publish, transfer, or otherwise make available the Software; (ii) reverse engineer, disassemble, decompile, decode, adapt, or otherwise attempt to derive or gain access to source code of the Proprietary Software, in whole or in part; (iii) use the Software in any manner or for any purpose that infringes, misappropriate, or otherwise wireless any intellectual property ride or other ride of any person, or that violates any applicable law. 64 | 65 | 1.4 If the Customer violates any of the provisions Sect. 1.2 and 1.3, all rights of usage granted under the POCL shall immediately become invalid and shall automatically revert to Pimcore. In this case, the Customer must immediately and completely cease using the Software, delete all copies of the Software installed on its systems and delete any backup copies made or hand them over to Pimcore. In addition, Pimcore reserves the right to take all legal steps. 66 | 67 | 1.5 Sect. 1.4 applies accordingly if a Derivative of the Customer infringe upon patents. 68 | 69 | 1.6 The parties may agree on expanded usage rights, arrangements for enterprise customers, and special OEM provisions separately. 70 | 71 | 1.7 Upon request, the Customer shall enable Pimcore to verify the proper use of the Software, in particular whether the Customer is using the Software as agreed. For this purpose, the Customer shall provide Pimcore with information, grant access to relevant documents and records and enable an audit of the hardware and software environment by Pimcore or an auditing company named by Pimcore and acceptable to the Customer. Pimcore may carry out the audit on the Customer's premises during the Customer's regular business hours or have it carried out by third parties bound to secrecy. Pimcore shall ensure that the Customer's business operations are disturbed as little as possible by the on-site audit. If the inspection reveals a licence violation by the Customer that is not merely minimal, the Customer shall bear the costs of the inspection, otherwise Pimcore shall bear them. Pimcore reserves all other rights. 72 | 73 | 1.8 Licensee acknowledges that, as between Licensee and Licensor, Licensor owns all right, title, and interest, including all intellectual property rights, in and to the Software and, with respect to third-party products, the applicable third-party licensors own all right, title and interest, including all intellectual property rights, in and to the third-party products. 74 | 75 | 1.9 Licensor reserves all rights not expressly granted to Licensee in this Agreement. Except for the limited rights and licenses expressly granted under this Agreement, nothing in this Agreement grants, by implication, waiver, estoppel, or otherwise, to Licensee or any third party any intellectual property rights or other right, title, or interest in or to the Software. 76 | 77 | ### 2. CONTRIBUTIONS OF DERIVATIVES 78 | 2.1 If the Customer wishes to contribute to the Software or to distribute a Derivative, both must be made in accordance with the Pimcore Contributors License Agreement (“PCLA”), available at . The PCLA stipulates the terms under which intellectual contributions are managed, ensuring that all parties' rights are protected. Acceptance of the PCLA is mandatory for all contributors and can be reviewed on the source-code repository. Contributions without adherence to the PCLA will not be accepted. 79 | 80 | 2.2 Any contribution to the Software by a Derivative must be clearly documented, in order to maintain transparency and integrity of the source code. 81 | 82 | 2.3. Any Derivative distributed must prominently be specified as “Derivative”, comply with the terms of the POCL, include copyright notices, and be licensed as a whole under the terms of the POCL, with the proviso that the recipient (licensee) of the out-licensed Derivative gets the role of the “Customer” regarding rights and obligations. Upon distribution of any Derivative, recipient must be provided with a copy of this POCL. 83 | 84 | ### 3. COLLATERAL OBLIGATIONS OF THE CUSTOMER 85 | 86 | 3.1 The Customer shall not manipulate, in particular modify, move, remove, suppress, switch off or circumvent licence keys and technical protection mechanisms in the Software, e. g. directly, or through the use of intermediaries, white-labelling, or segmentation of services designed to avoid licensing obligations. 87 | 88 | 3.2 The Customer shall not alter or obfuscate any of the Pimcore's licensing, copyright, or other proprietary notices within the Software. Any use of Pimcore’s trademarks must comply with applicable laws. 89 | 90 | 3.3 The Customer shall not modify, relocate, disable, or bypass any functionalities associated with the Pimcore Store. 91 | 92 | 3.4 The Customer shall not (a) use GPLv3-licensed Pimcore software alongside POCL licensed Software, and shall not (b) revert from POCL to GPLv3, to protect the Customer’s rights in Derivatives. 93 | 94 | 3.5 The Customer must ensure that the access data to the user accounts is not passed on to unauthorised third parties and is protected against unauthorised access by third parties. The authorised users shall be instructed accordingly. The Customer shall inform Pimcore immediately if there is a suspicion of misuse of the Software. 95 | 96 | 3.6 If Customer infringes upon one of the provisions set up by Sect. 3.1 through 3.5, Sect. 1.4 sentence 1 applies accordingly. 97 | 98 | ### 4. CONFIDENTIALITY 99 | 100 | From time to time during the Term, either Party may disclose or make available to the other Party information about its business affairs, products, confidential intellectual property, trade secrets, third-party confidential information, and other sensitive or proprietary information, whether orally or in written, electronic, or other form or media, and whether or not marked, designated or otherwise identified as "confidential" (collectively, "**Confidential Information**"). Confidential Information does not include information that, at the time of disclosure is: (a) in the public domain; (b) known to the receiving Party at the time of disclosure; (c) rightfully obtained by the receiving Party on a non-confidential basis from a third party; or (d) independently developed by the receiving Party. The receiving Party shall not disclose the disclosing Party's Confidential Information to any person or entity, except to the receiving Party's employees who have a need to know the Confidential Information for the receiving Party to exercise its rights or perform its obligations hereunder. Notwithstanding the foregoing, each Party may disclose Confidential Information to the limited extent required (i) in order to comply with the order of a court or other governmental body, or as otherwise necessary to comply with applicable law, provided that the Party making the disclosure pursuant to the order shall first have given written notice to the other Party and made a reasonable effort to obtain a protective order; or (ii) to establish a Party's rights under this Agreement, including to make required court filings. On the expiration or termination of this Agreement, the receiving Party shall promptly return to the disclosing Party all copies, whether in written, electronic, or other form or media, of the disclosing Party's Confidential Information, or destroy all such copies and certify in writing to the disclosing Party that such Confidential Information has been destroyed. Each Party's obligations of non­disclosure with regard to Confidential Information are effective as of the Effective Date and will expire five years from the date first disclosed to the receiving Party; provided, however, with respect to any Confidential Information that constitutes a trade secret (as determined under applicable law), such obligations of non-disclosure will survive the termination or expiration of this Agreement for as long as such Confidential Information remains subject to trade secret protection under applicable law. 101 | 102 | ### 5. LIMITED WARRANTY AND WARRANTY DISCLAIMER 103 | 104 | Pimcore warrants that, at the time of delivery, the Software does not contain any virus or other malicious code that would cause the Software to become inoperable or incapable of being used in accordance with its documentation. The warranties set forth herein do not apply and become null and void if Licensee breaches any material provision of this Agreement or any instrument related hereto, or if Licensee, or any person provided access to the Software by Licensee whether or not in violation of this Agreement: (i) installs or uses the Software on or in connection with any hardware or software not specified in the documentation or expressly authorized by Licensor in writing; (ii) illicitly modifies or damages the Software; or (iii) misuses the Software, including any use of the Software other than as specified in the documentation or expressly authorized by Licensor in writing. If any Software fails to comply with the warranty set forth hereinbefore, and such failure is not excluded from warranty pursuant to this provision, Licensor shall, subject to Licensee's promptly notifying Licensor in writing of such failure, at its sole option, either: (i) repair or replace the Software, provided that Licensee provides Licensor with all information Licensor reasonably requests to resolve the reported failure, including sufficient information to enable the Licensor to recreate such failure; or (ii) refund the fees paid for such Software, subject to Licensee's ceasing all use of and, if requested by Licensor, returning to Licensor all copies of the Software. If Licensor repairs or replaces the Software, the warranty will continue to run from the Effective Date and not from Licensee's receipt of the repair or replacement. The remedies set forth in this Section 5 are Licensee's sole remedies and Licensor's sole liability under the limited warranty set forth in this Section 5. 105 | 106 | EXCEPT FOR THE LIMITED WARRANTY SET FORTH IN THIS SECTION 5, THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" AND LICENSOR HEREBY DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE. LICENSOR SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT, AND ALL WARRANTIES ARISING FROM COURSE OF DEALING, USAGE, OR TRADE PRACTICE. LICENSOR MAKES NO WARRANTY OF ANY KIND THAT THE SOFTWARE AND DOCUMENTATION, OR ANY PRODUCTS OR RESULTS OF THE USE THEREOF, WILL MEET LICENSEE'S OR ANY OTHER PERSON'S REQUIREMENTS, OPERATE WITHOUT INTERRUPTION, ACHIEVE ANY INTENDED RESULT, BE COMPATIBLE OR WORK WITH ANY SOFTWARE, SYSTEM OR OTHER SERVICES, OR BE SECURE, ACCURATE, COMPLETE, FREE OF HARMFUL CODE, OR ERROR FREE. 107 | 108 | ### 6. DEFECTS 109 | 110 | 6.1 The Customer is obliged to notify Pimcore of any defect or error in the Software immediately after its occurrence. 111 | 112 | 6.2 Before reporting any defect or error, the Customer must carry out an analysis of the system environment as far as possible to ensure that the defect or error is not due to system components that are not covered by this Agreement. 113 | 114 | 6.3 The Customer shall immediately install or carry out updates or other troubleshooting measures provided by Pimcore. 115 | 116 | 6.4 Violations of the obligations to co-operate may result in additional costs for Pimcore. The Customer must reimburse Pimcore for such costs, unless it is not responsible for them. 117 | 118 | ### 7. LIMITATION OF LIABILITY 119 | 120 | IN NO EVENT WILL LICENSOR BE LIABLE UNDER OR IN CONNECTION WITH THIS AGREEMENT UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, AND OTHERWISE, FOR ANY: (a) CONSEQUENTIAL, INCIDENTAL, INDIRECT, EXEMPLARY, SPECIAL, ENHANCED, OR PUNITIVE DAMAGES; (b) INCREASED COSTS, DIMINUTION IN VALUE OR LOST BUSINESS, PRODUCTION, REVENUES, OR PROFITS; (c) LOSS OF GOODWILL OR REPUTATION; (d) USE, INABILITY TO USE, LOSS, INTERRUPTION, DELAY OR RECOVERY OF ANY DATA, OR BREACH OF DATA OR SYSTEM SECURITY; OR (e) COST OF REPLACEMENT GOODS OR SERVICES, IN EACH CASE REGARDLESS OF WHETHER LICENSOR WAS ADVISED OF THE POSSIBILITY OF SUCH LOSSES OR DAMAGES OR SUCH LOSSES OR DAMAGES WERE OTHERWISE FORESEEABLE. 121 | 122 | IN NO EVENT WILL LICENSOR'S AGGREGATE LIABILITY ARISING OUT OF OR RELATED TO THIS AGREEMENT UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, AND OTHERWISE EXCEED THE TOTAL AMOUNTS PAID TO LICENSOR UNDER THIS AGREEMENT IN THE TWELVE (12) MONTH PERIOD PRECEDING THE EVENT GIVING RISE TO THE CLAIM. 123 | 124 | ### 8. INDEMNIFICATION 125 | 126 | The Customer shall indemnify Pimcore and its affiliates, officers, directors, employees, agents, and assigns, from and against all claims, losses, damages, liabilities, costs, and expenses (including reasonable attorney’s fees and costs) against Pimcore arising out of or relating to the Customer’s use of the Software or Derivatives. 127 | 128 | ### 9. TERMINATION 129 | 130 | Term and termination will be regulated separately. If Customer uses the Software in violation of this Agreement or otherwise violates the use rights or prohibitions contained in this Agreement, Customer’s License shall automatically terminate. Upon termination of this Agreement, the Customer shall uninstall the Software, including all copies, and delete any remaining Software residues from its IT system. The Customer must destroy any backup copies made. At Pimcore's request, the Customer must confirm that it has fulfilled these obligations. 131 | 132 | ### 10. REMUNERATION 133 | 134 | The remuneration for the use of the software shall be agreed separately. 135 | 136 | ### 11. MISCELLANEOUS 137 | 138 | 11.1 The Software may automatically collect and transmit non-personal statistical data related to its installation and use, including but not limited to the number of records in the database, installed modules, system configuration, and usage metrics ("Usage Data"). Such data is collected solely for the purposes of product improvement, support, and analytics. Licensee agrees not to interfere with the collection and transmission of Usage Data. 139 | 140 | 11.2 Licensee may not assign or transfer any of its rights or delegate any of its obligations hereunder, in each case whether voluntarily, involuntarily, by operation of law or otherwise, without the prior written consent of Licensor. Any purported assignment, transfer, or delegation in violation of this Section is null and void. No assignment, transfer, or delegation will relieve the assigning or delegating Party of any of its obligations hereunder. This Agreement is binding upon and inures to the benefit of the Parties hereto and their respective permitted successors and assigns. 141 | 142 | 11.3 Each Party acknowledges and agrees that a breach or threatened breach by such Party of any of its contractual obligations may cause the other Party irreparable harm for which monetary damages would not be an adequate remedy and agrees that, in the event of such breach or threatened breach, the other Party will be entitled to equitable relief, including a restraining order, an injunction, specific performance, and any other relief that may be available from any court, without any requirement to post a bond or other security, or to prove actual damages or that monetary damages are not an adequate remedy. Such remedies are not exclusive and are in addition to all other remedies that may be available at law, in equity, or otherwise. 143 | 144 | 11.4 No amendment to or modification of this Agreement is effective unless it is in writing and signed by an authorized representative of each Party. No waiver by any Party of any of the provisions hereof will be effective unless explicitly set forth in writing and signed by the Party so waiving. Except as otherwise set forth in this Agreement, (i) no failure to exercise, or delay in exercising, any rights, remedy, power, or privilege arising from this Agreement will operate or be construed as a waiver thereof, and (ii) no single or partial exercise of any right, remedy, power, or privilege hereunder will preclude any other or further exercise thereof or the exercise of any other right, remedy, power, or privilege. 145 | 146 | 11.5 If any provision of this Agreement is invalid, illegal, or unenforceable in any jurisdiction, such invalidity, illegality, or unenforceability will not affect any other term or provision of this Agreement or invalidate or render unenforceable such term or provision in any other jurisdiction. Upon such determination that any term or other provision is invalid, illegal, or unenforceable, the Parties hereto shall negotiate in good faith to modify this Agreement so as to effect the original intent of the Parties as closely as possible in a mutually acceptable manner in order that the transactions contemplated hereby be consummated as originally contemplated to the greatest extent possible. 147 | 148 | 11.6 In all relevant respects that are not regulated by this Agreement, the following documents shall apply, as far as applicable: 149 | 150 | - Pimcore Terms & Conditions, available at [] 151 | - Pimcore Privacy Statement (PPS) 152 | - Pimcore Data Processing Agreement (PDPA) 153 | - Pimcore PaaS Terms & Conditions 154 | 155 | 11.7 Specifications originating from the Customer regarding the service content and legal elements, such as GTC or contractual clauses, do not apply. 156 | 157 | 11.8 Support, maintenance, and other services remain subject to separate agreements. 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FrontendPermissionToolkit 2 | 3 | Adds some helpers to define permissions for users in websites based on Pimcore objects. 4 | So user permissions for complex systems can be defined directly in Pimcore objects. 5 | 6 | A scenario to set up a role based permission system: 7 | - user represented as objects with a number of permission rights (= Permission Resources) 8 | - each user has relations to user groups (also Pimcore objects) with also a number of permission rights (= Permission Resources) 9 | 10 | ![sample](doc/img/sample.jpg) 11 | 12 | ## Installation instructions 13 | 14 | 1. Inside your pimcore project require the bundle as a dependency: 15 | 16 | ``` 17 | composer require pimcore/frontend-permission-toolkit-bundle 18 | ``` 19 | 2. Enable the bundle using the CLI command: 20 | 21 | ``` 22 | bin/console pimcore:bundle:enable FrontendPermissionToolkitBundle 23 | ``` 24 | 25 | This will enable & install the bundle in your pimcore project as well as run the assets:install command. Alternatively you can log in to your admin area go to _Tools > Extensions_ and enable the bundle from the list by clicking on the appropriate icon. 26 | 27 | ### Functionality overview 28 | - Additional data types for Pimcore objects 29 | - Permission Resource: 30 | - represents one specific user right (e.g. login) 31 | - can have values `allow` `deny` `inherit` 32 | - Dynamic Permission Resource: 33 | - represents a set of specific rights for a user 34 | - each entry can have values `allow` `deny` `inherit` 35 | - actual permission resources are defined by a data provider 36 | - defined in the class definition, either defined by class name or service name with leading `@` 37 | - data provider class needs to implement `DataProviderInterface` and return an array of roles and labels: 38 | ```php 39 | public function getPermissionResources(array $context, \Pimcore\Model\DataObject\ClassDefinition\Data $fieldDefinition): array 40 | { 41 | // static example for explanation 42 | return [ 43 | ['value' => 'testpermission_1', 'label' => 'Permission for test'], 44 | ['value' => 'testpermission_2', 'label' => 'Another Permission for test'], 45 | ]; 46 | } 47 | ``` 48 | - Permission ManyToMany Relation: Wrapper for default data type `objects` for recursive permission calculation. 49 | - Permission ManyToOne Relation: Wrapper for default data type `href` for recursive permission calculation. 50 | 51 | - Service for checking user rights based on a Pimcore object and a permission resource as service class `Service` with 52 | two methods: 53 | - `Service::getPermissions`: 54 | - returns an array of all permissions for the given object, automatically merges all permission resources of objects related to the given object with 'Permission Objects' or 'Permission Href'. 55 | - merging: When permission is set to allow / deny directly in object, this is always used. Otherwise optimistic merging is used -> once one permission is allowed, it stays that way. 56 | - `Service::isAllowed`: checks if given object is allowed for given resource 57 | 58 | 59 | 60 | The Service is registered at the container with the key `bundle.frontendpermissiontoolkit.service`. 61 | 62 | #### Event listener 63 | 64 | The postGetPermissions event listener allows you to manipulate the permissions after they have been collected. Take into account that the getPermissions method can be executed recursively. Therefore, make sure you add an object condition. 65 | 66 | ```php 67 | namespace AppBundle\EventListener; 68 | 69 | use FrontendPermissionToolkitBundle\Event\PermissionsEvent; 70 | use Pimcore\Model\DataObject\User; 71 | 72 | class PermissionsListener 73 | { 74 | public function postGetPermissions(PermissionsEvent $permissionsEvent): void 75 | { 76 | // Object the permissions are retrieved for 77 | $user = $permissionsEvent->getObject(); 78 | if (!$user instanceof User) { 79 | return; 80 | } 81 | 82 | // Access service methods to retrieve additional permissions and merge them 83 | $service = $permissionsEvent->getService(); 84 | 85 | $permissions = $permissionsEvent->getPermissions(); 86 | $mergedPermissions = $permissions; 87 | foreach ($user->getGroups() ?? [] as $userGroup) { 88 | $userGroupPermissions = $service->getPermissions($userGroup); 89 | $mergedPermissions = $service->mergeNestedObjectPermissions($mergedPermissions, $permissions, $userGroupPermissions); 90 | } 91 | 92 | // Update the permissions to return them from the service method 93 | $permissionsEvent->setPermissions($mergedPermissions); 94 | } 95 | } 96 | ``` 97 | 98 | ```yaml 99 | service: 100 | AppBundle\EventListener\PermissionsListener: 101 | tags: 102 | - { 103 | name: kernel.event_listener 104 | event: frontendPermissionsToolkit.service.postGetPermissions 105 | method: postGetPermissions 106 | } 107 | ``` 108 | 109 | 110 | ### Integration with Symfony Security 111 | For how to integrate Pimcore objects with Symfony Security in general have a look at 112 | [Pimcore docs](https://www.pimcore.org/docs/5.0.0/Development_Tools_and_Details/Security_Authentication/Authenticate_Pimcore_Objects.html). 113 | 114 | In order to use Permission Resources in Symfony Security definition, you could export each allowed Permission Resource 115 | of an Pimcore object as role. 116 | 117 | To do so, add the trait `FrontendPermissionToolkitBundle\CoreExtensions\Traits\PermissionResourcesAsRolesTrait` to your 118 | Pimcore user object and make sure there is no other `getRoles` method defined in the object. This method returns all 119 | Permission Resources the user is allowed prefixed with `GROUP_` to as an array. 120 | 121 | As a consequence, you can use Permission Resources in your access control configuration as follows: 122 | 123 | ```yaml 124 | access_control: 125 | - { path: ^/special-offer-page, roles: ROLE_offer } 126 | ``` 127 | 128 | > Note: To apply changes of permissions in the user object, the user has to logout and login again. 129 | 130 | 131 | ### Integration with Pimcore navigation 132 | 133 | To show/hide documents in navigation, you can assign Permission Resources as properties to Pimcore documents. 134 | Just add a property named `permission_resource` with name name of the `permissionResource` as value to the document. 135 | 136 | ![Permission Property](doc/img/property.jpg) 137 | 138 | A special navigation builder shipped by this bundle (`FrontendPermissionToolkitBundle\CoreExtensions\Navigation\Builder`) 139 | then can show/hide documents in navigation based on the permissions of the current user. 140 | 141 | To do so, add following service definition to your application: 142 | 143 | ```yaml 144 | 145 | Pimcore\Navigation\Builder: 146 | class: FrontendPermissionToolkitBundle\CoreExtensions\Navigation\Builder 147 | arguments: ['@pimcore.http.request_helper'] 148 | public: false 149 | calls: 150 | - [setService, ['@bundle.frontendpermissiontoolkit.service']] 151 | - [setCurrentUser, ['@security.token_storage']] 152 | 153 | ``` 154 | 155 | > Make sure that you deactivate the caching of the Pimcore navigation creation! 156 | 157 | 158 | > This only hides the document in navigation. It does not check permissions when the document is called directly via its 159 | > url. Add an additional check into controller or access control to make sure the document cannot be called with missing 160 | > permissions. 161 | 162 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you think that you have found a security issue, 6 | don’t use the bug tracker and don’t publish it publicly. 7 | Instead, all security issues must be reported via a private vulnerability report. 8 | 9 | Please follow the [instructions](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) to submit a private report. 10 | 11 | 12 | ## Resolving Process 13 | Every submitted security issue is handled with top priority by following these steps: 14 | 15 | 1. Confirm the vulnerability 16 | 2. Determine the severity 17 | 3. Contact reporter 18 | 4. Work on a patch 19 | 5. Get a CVE identification number (may be done by the reporter or a security service provider) 20 | 6. Patch reviewing 21 | 7. Tagging a new release for supported versions 22 | 8. Publish security announcement 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pimcore/frontend-permission-toolkit-bundle", 3 | "description": "Provides a way to configure permissions for frontend applications in data objects.", 4 | "license": "proprietary", 5 | "type": "pimcore-bundle", 6 | "autoload": { 7 | "psr-4": { 8 | "FrontendPermissionToolkitBundle\\": "src/" 9 | } 10 | }, 11 | "prefer-stable": true, 12 | "minimum-stability": "dev", 13 | "require": { 14 | "php": "~8.3.0 || ~8.4.0", 15 | "pimcore/pimcore": "^12.0", 16 | "symfony/event-dispatcher-contracts": "^3.0" 17 | }, 18 | "conflict": { 19 | "pimcore/pimcore": "<11.0.0-ALPHA6" 20 | }, 21 | "require-dev": { 22 | "phpstan/phpstan": "^1.10" 23 | }, 24 | "extra": { 25 | "pimcore": { 26 | "bundles": [ 27 | "FrontendPermissionToolkitBundle\\FrontendPermissionToolkitBundle" 28 | ] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /doc/img/property.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimcore/frontend-permission-toolkit/afff40f302b35d39c03b452fe3f5c428d79c7d34/doc/img/property.jpg -------------------------------------------------------------------------------- /doc/img/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimcore/frontend-permission-toolkit/afff40f302b35d39c03b452fe3f5c428d79c7d34/doc/img/sample.jpg -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | -------------------------------------------------------------------------------- /phpstan-bootstrap.php: -------------------------------------------------------------------------------- 1 | dataProvider; 74 | } 75 | 76 | /** 77 | * @param string $dataProvider 78 | */ 79 | public function setDataProvider(string $dataProvider): void 80 | { 81 | $this->dataProvider = $dataProvider; 82 | } 83 | 84 | /** 85 | * @return array 86 | */ 87 | public function getPermissionResources(): array 88 | { 89 | return $this->permissionResources ?: []; 90 | } 91 | 92 | /** 93 | * @param array $permissionResources 94 | */ 95 | public function setPermissionResources($permissionResources): void 96 | { 97 | $this->permissionResources = $permissionResources; 98 | } 99 | 100 | /** 101 | * @return array 102 | */ 103 | public function getPermissionOptions(): array 104 | { 105 | return $this->permissionOptions ?: []; 106 | } 107 | 108 | /** 109 | * @param array $permissionOptions 110 | */ 111 | public function setPermissionOptions($permissionOptions): void 112 | { 113 | $this->permissionOptions = $permissionOptions; 114 | } 115 | 116 | protected function cleanupAndCheckForEmpty($data) 117 | { 118 | $resources = $this->loadPermissionResourcesFromProvider(); 119 | 120 | $data = $data ?? []; 121 | 122 | $cleanData = []; 123 | 124 | foreach ($resources as $resource) { 125 | $originalValue = $data[$resource['value']] ?? null; 126 | $cleanData[$resource['value']] = $originalValue ?: Service::INHERIT; 127 | } 128 | 129 | return $cleanData; 130 | } 131 | 132 | public function getDataForEditmode(mixed $data, ?DataObject\Concrete $object = null, array $params = []): mixed 133 | { 134 | $permissions = $this->getPermissionResources(); 135 | foreach ($permissions as $permission) { 136 | if (!array_key_exists($permission['value'], $data)) { 137 | $data[$permission['value']] = Service::INHERIT; 138 | } 139 | } 140 | 141 | return $data; 142 | } 143 | 144 | public function getDataFromEditmode(mixed $data, ?DataObject\Concrete $object = null, array $params = []): mixed 145 | { 146 | return $data; 147 | } 148 | 149 | public function getDataForQueryResource(mixed $data, ?Concrete $object = null, array $params = []): mixed 150 | { 151 | $data = $this->cleanupAndCheckForEmpty($data); 152 | if (is_array($data)) { 153 | return http_build_query($data, '', ';') . ';'; 154 | } 155 | 156 | return ''; 157 | } 158 | 159 | public function getDataForResource(mixed $data, ?Concrete $object = null, array $params = []): mixed 160 | { 161 | return Serialize::serialize($this->cleanupAndCheckForEmpty($data)); 162 | } 163 | 164 | public function getDataFromResource(mixed $data, ?Concrete $object = null, array $params = []): mixed 165 | { 166 | return $this->cleanupAndCheckForEmpty(Serialize::unserialize($data)); 167 | } 168 | 169 | /** 170 | * Checks if data is valid for current data field 171 | * 172 | * @param mixed $data 173 | * @param bool $omitMandatoryCheck 174 | * 175 | * @throws \Exception 176 | */ 177 | public function checkValidity($data, $omitMandatoryCheck = false, $params = []): void 178 | { 179 | if (!$omitMandatoryCheck && $this->getMandatory() && empty($data)) { 180 | throw new ValidationException('Empty mandatory field [ '.$this->getName().' ]'); 181 | } 182 | 183 | if (!empty($data) && !is_array($data)) { 184 | throw new ValidationException('Invalid table data'); 185 | } 186 | } 187 | 188 | public function getForCsvExport(DataObject\Concrete | DataObject\Localizedfield | DataObject\Objectbrick\Data\AbstractData | DataObject\Fieldcollection\Data\AbstractData $object, array $params = []): string 189 | { 190 | $data = $this->getDataFromObjectParam($object, $params); 191 | if (is_array($data)) { 192 | return base64_encode(Serialize::serialize($data)); 193 | } 194 | 195 | return ''; 196 | } 197 | 198 | /** 199 | * @param string $importValue 200 | * @param null|DataObject\Concrete $object 201 | * @param array $params 202 | * 203 | * @return array|null 204 | */ 205 | public function getFromCsvImport($importValue, $object = null, $params = []) 206 | { 207 | $value = Serialize::unserialize(base64_decode($importValue)); 208 | if (is_array($value)) { 209 | return $value; 210 | } 211 | 212 | return null; 213 | } 214 | 215 | /** 216 | * @param array|null $oldValue 217 | * @param array|null $newValue 218 | * 219 | * @return bool 220 | */ 221 | public function isEqual($oldValue, $newValue): bool 222 | { 223 | return $this->isEqualArray($oldValue, $newValue); 224 | } 225 | 226 | /** Generates a pretty version preview (similar to getVersionPreview) can be either html or 227 | * a image URL. See the https://github.com/pimcore/object-merger bundle documentation for details 228 | * 229 | * @param array|null $data 230 | * @param DataObject\Concrete|null $object 231 | * @param mixed $params 232 | * 233 | * @return array|string 234 | */ 235 | public function getDiffVersionPreview($data, $object = null, $params = []) 236 | { 237 | if ($data) { 238 | $html = ''; 239 | 240 | foreach ($data as $key => $permission) { 241 | $html .= ''; 242 | $html .= ''; 243 | $html .= ''; 244 | $html .= ''; 245 | } 246 | $html .= '
' . htmlentities($key) . '' . htmlentities($permission) . '
'; 247 | 248 | $value = []; 249 | $value['html'] = $html; 250 | $value['type'] = 'html'; 251 | 252 | return $value; 253 | } else { 254 | return ''; 255 | } 256 | } 257 | 258 | public function getVersionPreview(mixed $data, ?DataObject\Concrete $object = null, array $params = []): string 259 | { 260 | $versionPreview = $this->getDiffVersionPreview($data, $object, $params); 261 | if (is_array($versionPreview) && $versionPreview['html']) { 262 | return $versionPreview['html']; 263 | } 264 | 265 | return ''; 266 | } 267 | 268 | protected function loadPermissionResourcesFromProvider(): array 269 | { 270 | $dataProvider = DataProviderResolver::resolveDataProvider($this->getDataProvider()); 271 | 272 | $permissionResources = []; 273 | if ($dataProvider) { 274 | $context = [ 275 | 'fieldname' => $this->getName() 276 | ]; 277 | $permissionResources = $dataProvider->getPermissionResources($context, $this); 278 | } 279 | 280 | return $permissionResources; 281 | } 282 | 283 | public function enrichFieldDefinition(array $context = []): static 284 | { 285 | $this->setPermissionResources($this->loadPermissionResourcesFromProvider()); 286 | 287 | $this->setPermissionOptions([ 288 | ['key' => Service::INHERIT, 'value' => Service::INHERIT], 289 | ['key' => Service::ALLOW, 'value' => Service::ALLOW], 290 | ['key' => Service::DENY, 'value' => Service::DENY] 291 | ]); 292 | 293 | return $this; 294 | } 295 | 296 | public function enrichLayoutDefinition($object, $context = []) 297 | { 298 | $this->enrichFieldDefinition($context); 299 | } 300 | 301 | public function getParameterTypeDeclaration(): ?string 302 | { 303 | return '?array'; 304 | } 305 | 306 | public function getReturnTypeDeclaration(): ?string 307 | { 308 | return '?array'; 309 | } 310 | 311 | public function getPhpdocInputType(): ?string 312 | { 313 | return 'null|array'; 314 | } 315 | 316 | public function getPhpdocReturnType(): ?string 317 | { 318 | return 'null|array'; 319 | } 320 | 321 | public function getQueryColumnType(): array | string 322 | { 323 | return $this->queryColumnType; 324 | } 325 | 326 | public function getColumnType(): array | string 327 | { 328 | return $this->columnType; 329 | } 330 | 331 | public function getFieldType(): string 332 | { 333 | return 'dynamicPermissionResource'; 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/CoreExtensions/ClassDefinitions/Helper/DataProviderResolver.php: -------------------------------------------------------------------------------- 1 | 'permission1', 'label' => 'some nice label' ] 24 | */ 25 | public function getPermissionResources(array $context, Data $fieldDefinition): array; 26 | } 27 | -------------------------------------------------------------------------------- /src/CoreExtensions/ClassDefinitions/PermissionManyToManyRelation.php: -------------------------------------------------------------------------------- 1 | Service::INHERIT, 'value' => Service::INHERIT], 35 | ['key' => Service::ALLOW, 'value' => Service::ALLOW], 36 | ['key' => Service::DENY, 'value' => Service::DENY] 37 | ]; 38 | 39 | $this->setOptions($options); 40 | } 41 | 42 | protected function checkForEmpty($data) 43 | { 44 | if (empty($data)) { 45 | return Service::INHERIT; 46 | } 47 | 48 | return $data; 49 | } 50 | 51 | public function getDataForResource(mixed $data, ?Concrete $object = null, array $params = []): ?string 52 | { 53 | return $this->checkForEmpty($data); 54 | } 55 | 56 | public function getDataFromResource(mixed $data, ?Concrete $object = null, array $params = []): ?string 57 | { 58 | return $this->checkForEmpty($data); 59 | } 60 | 61 | public function getDataForQueryResource(mixed $data, ?Concrete $object = null, array $params = []): ?string 62 | { 63 | return $this->checkForEmpty($data); 64 | } 65 | 66 | public function __wakeup() 67 | { 68 | $this->configureOptions(); 69 | } 70 | 71 | public static function __set_state(/* array */ $data): static 72 | { 73 | $obj = parent::__set_state($data); 74 | $obj->configureOptions(); 75 | 76 | return $obj; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/CoreExtensions/Navigation/Builder.php: -------------------------------------------------------------------------------- 1 | service = $service; 44 | } 45 | 46 | /** 47 | * @param TokenStorageInterface $securityTokenStorage 48 | */ 49 | public function setCurrentUser(TokenStorageInterface $securityTokenStorage) 50 | { 51 | if ($securityToken = $securityTokenStorage->getToken()) { 52 | $user = $securityToken->getUser(); 53 | if ($user instanceof Concrete) { 54 | $this->currentUser = $user; 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * @inheritdoc 61 | */ 62 | protected function getChildren(Document $parentDocument): array 63 | { 64 | $children = $parentDocument->getChildren(); 65 | 66 | $allowedChildren = []; 67 | 68 | foreach ($children as $child) { 69 | $permissionResource = $child->getProperty('permission_resource'); 70 | 71 | if (empty($permissionResource) || $this->currentUser && $this->service->isAllowed($this->currentUser, $child->getProperty('permission_resource'))) { 72 | $allowedChildren[] = $child; 73 | } 74 | } 75 | 76 | return $allowedChildren; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/CoreExtensions/Traits/PermissionResourcesAsRolesTrait.php: -------------------------------------------------------------------------------- 1 | get(Service::class); 30 | $permissions = $service->getPermissions($this); 31 | 32 | $roles = []; 33 | foreach ($permissions as $permission => $allowed) { 34 | if ($allowed === Service::ALLOW) { 35 | $roles[] = 'ROLE_' . $permission; 36 | } 37 | } 38 | 39 | return $roles; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DependencyInjection/FrontendPermissionToolkitExtension.php: -------------------------------------------------------------------------------- 1 | load('services.yml'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Event/PermissionsEvent.php: -------------------------------------------------------------------------------- 1 | permissions = $permissions; 48 | $this->object = $object; 49 | $this->service = $service; 50 | } 51 | 52 | /** 53 | * @return array 54 | */ 55 | public function getPermissions(): array 56 | { 57 | return $this->permissions; 58 | } 59 | 60 | /** 61 | * @param array $permissions 62 | * 63 | * @return $this 64 | */ 65 | public function setPermissions(array $permissions): self 66 | { 67 | $this->permissions = $permissions; 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * @return Concrete 74 | */ 75 | public function getObject(): Concrete 76 | { 77 | return $this->object; 78 | } 79 | 80 | /** 81 | * @return Service 82 | */ 83 | public function getService(): Service 84 | { 85 | return $this->service; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/EventSubscriber/CustomLayoutSubscriber.php: -------------------------------------------------------------------------------- 1 | 'onUpdate', 31 | DataObjectCustomLayoutEvents::PRE_UPDATE => 'onUpdate', 32 | ]; 33 | } 34 | 35 | public function onUpdate(CustomLayoutEvent $event): void 36 | { 37 | $customLayout = $event->getCustomLayout(); 38 | $this->resetPermissionResources($customLayout->getLayoutDefinitions()); 39 | } 40 | 41 | private function resetPermissionResources(ClassDefinition\Data | ClassDefinition\Layout | null $layout): void 42 | { 43 | if ($layout === null) { 44 | return; 45 | } 46 | 47 | if ($layout instanceof DynamicPermissionResource) { 48 | $layout->setPermissionResources([]); 49 | } 50 | if (method_exists($layout, 'getChildren')) { 51 | foreach ($layout->getChildren() ?? [] as $child) { 52 | $this->resetPermissionResources($child); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/FrontendPermissionToolkitBundle.php: -------------------------------------------------------------------------------- 1 | '; 155 | 156 | for(var key in value) { 157 | 158 | var permissionDefinition = field.layout.permissionResources.find(function(key, element) { 159 | return element.value == key; 160 | }.bind(this, key)); 161 | 162 | table += ''; 163 | table += '' + Ext.util.Format.htmlEncode(permissionDefinition.label) + ''; 164 | table += '' + Ext.util.Format.htmlEncode(value[key]) + ''; 165 | table += ''; 166 | } 167 | table += ''; 168 | return table; 169 | } 170 | return ""; 171 | }.bind(this, field) 172 | }; 173 | }, 174 | 175 | getCellEditValue: function () { 176 | return this.getValue(); 177 | }, 178 | 179 | 180 | }); 181 | -------------------------------------------------------------------------------- /src/Resources/public/js/datatypes/tags/permissionManyToManyRelation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This source file is available under the terms of the 3 | * Pimcore Open Core License (POCL) 4 | * Full copyright and license information is available in 5 | * LICENSE.md which is distributed with this source code. 6 | * 7 | * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.com) 8 | * @license Pimcore Open Core License (POCL) 9 | */ 10 | 11 | 12 | pimcore.registerNS("pimcore.object.tags.permissionManyToManyRelation"); 13 | pimcore.object.tags.permissionManyToManyRelation = Class.create(pimcore.object.tags.manyToManyObjectRelation, { 14 | 15 | type: "permissionManyToManyRelation" 16 | 17 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/datatypes/tags/permissionManyToOneRelation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This source file is available under the terms of the 3 | * Pimcore Open Core License (POCL) 4 | * Full copyright and license information is available in 5 | * LICENSE.md which is distributed with this source code. 6 | * 7 | * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.com) 8 | * @license Pimcore Open Core License (POCL) 9 | */ 10 | 11 | 12 | pimcore.registerNS("pimcore.object.tags.permissionManyToOneRelation"); 13 | pimcore.object.tags.permissionManyToOneRelation = Class.create(pimcore.object.tags.manyToOneRelation, { 14 | 15 | type: "permissionManyToOneRelation" 16 | 17 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/datatypes/tags/permissionResource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This source file is available under the terms of the 3 | * Pimcore Open Core License (POCL) 4 | * Full copyright and license information is available in 5 | * LICENSE.md which is distributed with this source code. 6 | * 7 | * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.com) 8 | * @license Pimcore Open Core License (POCL) 9 | */ 10 | 11 | 12 | pimcore.registerNS("pimcore.object.tags.permissionResource"); 13 | pimcore.object.tags.permissionResource = Class.create(pimcore.object.tags.select, { 14 | 15 | type: "permissionResource", 16 | 17 | initialize: function (data, fieldConfig) { 18 | this.data = data; 19 | this.fieldConfig = fieldConfig; 20 | }, 21 | 22 | getLayoutEdit: function () { 23 | 24 | // generate store 25 | var store = []; 26 | var validValues = []; 27 | 28 | for (var i = 0; i < this.fieldConfig.options.length; i++) { 29 | store.push([this.fieldConfig.options[i].value, t(this.fieldConfig.options[i].key)]); 30 | validValues.push(this.fieldConfig.options[i].value); 31 | } 32 | 33 | var options = { 34 | name: this.fieldConfig.name, 35 | triggerAction: "all", 36 | editable: false, 37 | fieldLabel: this.fieldConfig.title, 38 | store: store, 39 | componentCls: this.getWrapperClassNames(), 40 | width: this.fieldConfig.labelWidth + 200 41 | }; 42 | 43 | if (typeof this.data == "string" || typeof this.data == "number") { 44 | if (in_array(this.data, validValues)) { 45 | options.value = this.data; 46 | } else { 47 | options.value = ""; 48 | } 49 | } else { 50 | options.value = ""; 51 | } 52 | 53 | this.component = new Ext.form.ComboBox(options); 54 | 55 | return this.component; 56 | } 57 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/startup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This source file is available under the terms of the 3 | * Pimcore Open Core License (POCL) 4 | * Full copyright and license information is available in 5 | * LICENSE.md which is distributed with this source code. 6 | * 7 | * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.com) 8 | * @license Pimcore Open Core License (POCL) 9 | */ 10 | 11 | 12 | pimcore.registerNS("pimcore.plugin.frontendpermissiontoolkit"); 13 | 14 | pimcore.plugin.frontendpermissiontoolkit = Class.create({ 15 | getClassName: function() { 16 | return "pimcore.plugin.frontendpermissiontoolkit"; 17 | }, 18 | 19 | initialize: function() { 20 | document.addEventListener(pimcore.events.pimcoreReady, this.onPimcoreReady.bind(this)); 21 | }, 22 | 23 | onPimcoreReady: function (e) { 24 | // alert("Example Ready!"); 25 | } 26 | }); 27 | 28 | var frontendpermissiontoolkitPlugin = new pimcore.plugin.frontendpermissiontoolkit(); 29 | 30 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.ca.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.cs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.de.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.en.yml: -------------------------------------------------------------------------------- 1 | permissionResource: Permission Resource 2 | dynamicPermissionResource: Dynamic Permission Resource 3 | permissionManyToManyRelation: Permission Objects 4 | permissionToolkit: Permission Toolkit 5 | permissionManyToOneRelation: Permission ManyToOne Relation 6 | permissionResourceProvider: Permission Resource Provider Service -------------------------------------------------------------------------------- /src/Resources/translations/admin.es.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.fr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.hu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.it.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.nl.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.pl.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.pt_br.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.ro.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.sk.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.sv.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.th.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Resources/translations/admin.zh_Hans.yml: -------------------------------------------------------------------------------- 1 | --- 2 | permissionResource: Permission Resource 3 | dynamicPermissionResource: Dynamic Permission Resource 4 | permissionManyToManyRelation: Permission Objects 5 | permissionToolkit: Permission Toolkit 6 | permissionManyToOneRelation: Permission ManyToOne Relation 7 | permissionResourceProvider: Permission Resource Provider Service 8 | ... 9 | -------------------------------------------------------------------------------- /src/Service.php: -------------------------------------------------------------------------------- 1 | eventDispatcher = $eventDispatcher; 47 | } 48 | 49 | /** 50 | * returns array of all permission resources of given object 51 | * automatically merges all permission resources of objects related to the given object with 'Permission Objects' or 'Permission Href' 52 | * 53 | * merging: 54 | * - when permission is set to allow / deny directly in object, this is always used 55 | * - otherwise optimistic merging is used -> once one permission is allowed, it stays that way 56 | * 57 | * 58 | * @param Concrete $object 59 | * 60 | * @return array 61 | */ 62 | public function getPermissions(Concrete $object, array $visitedIds = []): array 63 | { 64 | if (isset($this->permissionCache[$object->getId()])) { 65 | return $this->permissionCache[$object->getId()]; 66 | } 67 | 68 | if (isset($visitedIds[$object->getId()])) { 69 | return []; 70 | } else { 71 | $visitedIds[$object->getId()] = true; 72 | } 73 | 74 | $fieldDefinitions = $object->getClass()->getFieldDefinitions(); 75 | $permissions = $permissionObjects = []; 76 | 77 | // get permission resources directly in given object 78 | foreach ($fieldDefinitions as $fieldDefinition) { 79 | [$fieldPermissions, $fieldPermissionObjects] = $this->getPermissionsByFieldDefinition($object, $fieldDefinition); 80 | $permissions = array_merge($permissions, $fieldPermissions); 81 | $permissionObjects = array_merge($permissionObjects, $fieldPermissionObjects); 82 | } 83 | 84 | // get permission resources from related objects and merge them with permissions of base object 85 | $mergedPermissions = $permissions; 86 | foreach ($permissionObjects as $permissionObject) { 87 | $objectPermissions = $this->getPermissions($permissionObject, $visitedIds); 88 | $mergedPermissions = $this->mergeNestedObjectPermissions($mergedPermissions, $permissions, $objectPermissions); 89 | } 90 | 91 | $permissionsEvent = new PermissionsEvent($mergedPermissions, $object, $this); 92 | $this->eventDispatcher->dispatch($permissionsEvent, PermissionsEvent::POST_GET); 93 | $mergedPermissions = $permissionsEvent->getPermissions(); 94 | 95 | $this->permissionCache[$object->getId()] = $mergedPermissions; 96 | 97 | return $mergedPermissions; 98 | } 99 | 100 | /** 101 | * Base permissions take precedence when explicitly set to allow or deny. 102 | * Optimistic merging is used for nested permissions. Once allowed a permission stays allowed. 103 | * 104 | * @param array $mergedPermissions Already merged permissions 105 | * @param array $basePermissions Permissions of the base object 106 | * @param array $nestedPermissions Permissions of the nested object 107 | * 108 | * @return array Updated merged permissions 109 | */ 110 | public function mergeNestedObjectPermissions( 111 | array $mergedPermissions, 112 | array $basePermissions, 113 | array $nestedPermissions 114 | ) { 115 | foreach ($nestedPermissions as $key => $value) { 116 | if ( 117 | (($basePermissions[$key] ?? null) === self::INHERIT || !array_key_exists($key, $basePermissions)) 118 | && ($mergedPermissions[$key] ?? null) !== self::ALLOW 119 | ) { 120 | $mergedPermissions[$key] = $value; 121 | } 122 | } 123 | 124 | return $mergedPermissions; 125 | } 126 | 127 | /** 128 | * checks if given object is allowed for given resource 129 | * 130 | * @param Concrete $object 131 | * @param string $resource 132 | * 133 | * @return bool 134 | */ 135 | public function isAllowed(Concrete $object, $resource): bool 136 | { 137 | $permissions = $this->getPermissions($object); 138 | 139 | return ($permissions[$resource] ?? false) == self::ALLOW; 140 | } 141 | 142 | /** 143 | * @param Concrete|Objectbrick\Data\AbstractData $object 144 | * @param ClassDefinition\Data $fieldDefinition 145 | * 146 | * @return array 147 | */ 148 | protected function getPermissionsByFieldDefinition( 149 | AbstractModel $object, 150 | ClassDefinition\Data $fieldDefinition 151 | ): array { 152 | $permissions = $permissionObjects = []; 153 | switch (true) { 154 | case $fieldDefinition instanceof PermissionManyToManyRelation: 155 | $permissionObjects = $object->get($fieldDefinition->getName()); 156 | 157 | break; 158 | 159 | case $fieldDefinition instanceof PermissionManyToOneRelation: 160 | $manyToOneRelation = $object->get($fieldDefinition->getName()); 161 | if ($manyToOneRelation) { 162 | $permissionObjects = [$manyToOneRelation]; 163 | } 164 | 165 | break; 166 | 167 | case $fieldDefinition instanceof PermissionResource: 168 | $permissions = [$fieldDefinition->getName() => $object->get($fieldDefinition->getName())]; 169 | 170 | break; 171 | 172 | case $fieldDefinition instanceof DynamicPermissionResource: 173 | $permissions = $object->get($fieldDefinition->getName()); 174 | 175 | break; 176 | 177 | case $fieldDefinition instanceof ClassDefinition\Data\Objectbricks: 178 | // @var $objectBrick Objectbrick 179 | $objectBrick = $object->get($fieldDefinition->getName()); 180 | foreach ($objectBrick->getBrickGetters() as $brickGetter) { 181 | // @var $brick Objectbrick\Data\AbstractData 182 | $brick = $objectBrick->$brickGetter(); 183 | if (!$brick) { 184 | continue; 185 | } 186 | 187 | $brickFieldDefinitions = $brick->getDefinition()->getFieldDefinitions(); 188 | foreach ($brickFieldDefinitions as $brickFieldDefinition) { 189 | [$brickPermissions, $brickPermissionObjects] = $this->getPermissionsByFieldDefinition( 190 | $brick, 191 | $brickFieldDefinition 192 | ); 193 | $permissions = array_merge($permissions, $brickPermissions); 194 | $permissionObjects = array_merge($permissionObjects, $brickPermissionObjects); 195 | } 196 | } 197 | 198 | break; 199 | } 200 | 201 | return [$permissions, $permissionObjects]; 202 | } 203 | } 204 | --------------------------------------------------------------------------------