Merge branch 'permission_rework'

This commit is contained in:
Jan Böhmer 2022-11-27 23:11:03 +01:00
commit c529df89a0
102 changed files with 3656 additions and 3510 deletions

View file

@ -9,6 +9,8 @@
![Docker Build Status](https://github.com/Part-DB/Part-DB-symfony/workflows/Docker%20Image%20Build/badge.svg) ![Docker Build Status](https://github.com/Part-DB/Part-DB-symfony/workflows/Docker%20Image%20Build/badge.svg)
[![Crowdin](https://badges.crowdin.net/e/8325196085d4bee8c04b75f7c915452a/localized.svg)](https://part-db.crowdin.com/part-db) [![Crowdin](https://badges.crowdin.net/e/8325196085d4bee8c04b75f7c915452a/localized.svg)](https://part-db.crowdin.com/part-db)
*When updgrading from a version from before 2022-11-27, please read [this](https://github.com/Part-DB/Part-DB-symfony/discussions/193) before upgrading!*
# Part-DB # Part-DB
Part-DB is an Open-Source inventory managment system for your electronic components. Part-DB is an Open-Source inventory managment system for your electronic components.
It is installed on a web server and so can be accessed with any browser without the need to install additional software. It is installed on a web server and so can be accessed with any browser without the need to install additional software.

View file

@ -871,3 +871,10 @@ table.dataTable tr.selected td.select-checkbox:after, table.dataTable tr.selecte
width: 100%; width: 100%;
} }
/***********************************************
* Permission checkboxes
***********************************************/
.permission-checkbox:checked {
background-color: var(--bs-success);
border-color: var(--bs-success);
}

298
composer.lock generated
View file

@ -1455,16 +1455,16 @@
}, },
{ {
"name": "doctrine/orm", "name": "doctrine/orm",
"version": "2.13.3", "version": "2.13.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/orm.git", "url": "https://github.com/doctrine/orm.git",
"reference": "e750360bd52b080c4cbaaee1b48b80f7dc873b36" "reference": "a5a6cc6630ce497290396d5f206887227820a634"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/orm/zipball/e750360bd52b080c4cbaaee1b48b80f7dc873b36", "url": "https://api.github.com/repos/doctrine/orm/zipball/a5a6cc6630ce497290396d5f206887227820a634",
"reference": "e750360bd52b080c4cbaaee1b48b80f7dc873b36", "reference": "a5a6cc6630ce497290396d5f206887227820a634",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1493,13 +1493,13 @@
"doctrine/annotations": "^1.13", "doctrine/annotations": "^1.13",
"doctrine/coding-standard": "^9.0.2 || ^10.0", "doctrine/coding-standard": "^9.0.2 || ^10.0",
"phpbench/phpbench": "^0.16.10 || ^1.0", "phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.8.5", "phpstan/phpstan": "~1.4.10 || 1.9.2",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psr/log": "^1 || ^2 || ^3", "psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.1", "squizlabs/php_codesniffer": "3.7.1",
"symfony/cache": "^4.4 || ^5.4 || ^6.0", "symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.27.0" "vimeo/psalm": "4.30.0"
}, },
"suggest": { "suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files", "ext-dom": "Provides support for XSD validation for XML mapping files",
@ -1549,22 +1549,22 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/orm/issues", "issues": "https://github.com/doctrine/orm/issues",
"source": "https://github.com/doctrine/orm/tree/2.13.3" "source": "https://github.com/doctrine/orm/tree/2.13.4"
}, },
"time": "2022-10-07T06:37:17+00:00" "time": "2022-11-20T18:53:31+00:00"
}, },
{ {
"name": "doctrine/persistence", "name": "doctrine/persistence",
"version": "3.0.4", "version": "3.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/persistence.git", "url": "https://github.com/doctrine/persistence.git",
"reference": "05612da375f8a3931161f435f91d6704926e6ec5" "reference": "2a9c70a5e21f8968c5a46b79f819ea52f322080b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/persistence/zipball/05612da375f8a3931161f435f91d6704926e6ec5", "url": "https://api.github.com/repos/doctrine/persistence/zipball/2a9c70a5e21f8968c5a46b79f819ea52f322080b",
"reference": "05612da375f8a3931161f435f91d6704926e6ec5", "reference": "2a9c70a5e21f8968c5a46b79f819ea52f322080b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1635,7 +1635,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/persistence/issues", "issues": "https://github.com/doctrine/persistence/issues",
"source": "https://github.com/doctrine/persistence/tree/3.0.4" "source": "https://github.com/doctrine/persistence/tree/3.1.0"
}, },
"funding": [ "funding": [
{ {
@ -1651,7 +1651,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-10-13T07:34:14+00:00" "time": "2022-11-18T14:10:19+00:00"
}, },
{ {
"name": "doctrine/sql-formatter", "name": "doctrine/sql-formatter",
@ -2155,7 +2155,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/florianv/symfony-swap/issues", "issues": "https://github.com/florianv/symfony-swap/issues",
"source": "https://github.com/florianv/symfony-swap/tree/master" "source": "https://github.com/florianv/symfony-swap/tree/v5.4.0"
}, },
"time": "2022-06-13T07:34:25+00:00" "time": "2022-06-13T07:34:25+00:00"
}, },
@ -2366,16 +2366,16 @@
}, },
{ {
"name": "imagine/imagine", "name": "imagine/imagine",
"version": "1.3.2", "version": "1.3.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-imagine/Imagine.git", "url": "https://github.com/php-imagine/Imagine.git",
"reference": "ae864f26afbf8859ebd2e2b9df92d77ee175dc13" "reference": "a6e6da93ea0f76aba33b0e8ed1325523c0413da2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-imagine/Imagine/zipball/ae864f26afbf8859ebd2e2b9df92d77ee175dc13", "url": "https://api.github.com/repos/php-imagine/Imagine/zipball/a6e6da93ea0f76aba33b0e8ed1325523c0413da2",
"reference": "ae864f26afbf8859ebd2e2b9df92d77ee175dc13", "reference": "a6e6da93ea0f76aba33b0e8ed1325523c0413da2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2422,9 +2422,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/php-imagine/Imagine/issues", "issues": "https://github.com/php-imagine/Imagine/issues",
"source": "https://github.com/php-imagine/Imagine/tree/1.3.2" "source": "https://github.com/php-imagine/Imagine/tree/1.3.3"
}, },
"time": "2022-04-01T11:58:30+00:00" "time": "2022-11-16T13:09:11+00:00"
}, },
{ {
"name": "jbtronics/2fa-webauthn", "name": "jbtronics/2fa-webauthn",
@ -2485,16 +2485,16 @@
}, },
{ {
"name": "laminas/laminas-code", "name": "laminas/laminas-code",
"version": "4.7.0", "version": "4.7.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laminas/laminas-code.git", "url": "https://github.com/laminas/laminas-code.git",
"reference": "0337d9265bc2e6376babad8c511500821620cb30" "reference": "91aabc066d5620428120800c0eafc0411e441a62"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-code/zipball/0337d9265bc2e6376babad8c511500821620cb30", "url": "https://api.github.com/repos/laminas/laminas-code/zipball/91aabc066d5620428120800c0eafc0411e441a62",
"reference": "0337d9265bc2e6376babad8c511500821620cb30", "reference": "91aabc066d5620428120800c0eafc0411e441a62",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2547,7 +2547,7 @@
"type": "community_bridge" "type": "community_bridge"
} }
], ],
"time": "2022-09-13T10:33:30+00:00" "time": "2022-11-21T01:32:31+00:00"
}, },
{ {
"name": "lcobucci/clock", "name": "lcobucci/clock",
@ -3344,16 +3344,16 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v4.15.1", "version": "v4.15.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3394,9 +3394,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
}, },
"time": "2022-09-04T07:30:47+00:00" "time": "2022-11-12T15:38:23+00:00"
}, },
{ {
"name": "nikolaposa/version", "name": "nikolaposa/version",
@ -8492,16 +8492,16 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" "reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8516,7 +8516,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -8554,7 +8554,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -8570,20 +8570,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:49:31+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-grapheme", "name": "symfony/polyfill-intl-grapheme",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "433d05519ce6990bf3530fba6957499d327395c2" "reference": "511a08c03c1960e08a883f4cffcacd219b758354"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354",
"reference": "433d05519ce6990bf3530fba6957499d327395c2", "reference": "511a08c03c1960e08a883f4cffcacd219b758354",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8595,7 +8595,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -8635,7 +8635,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -8651,20 +8651,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:49:31+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-icu", "name": "symfony/polyfill-intl-icu",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-icu.git", "url": "https://github.com/symfony/polyfill-intl-icu.git",
"reference": "e407643d610e5f2c8a4b14189150f68934bf5e48" "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/e407643d610e5f2c8a4b14189150f68934bf5e48", "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/a3d9148e2c363588e05abbdd4ee4f971f0a5330c",
"reference": "e407643d610e5f2c8a4b14189150f68934bf5e48", "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8676,7 +8676,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -8722,7 +8722,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -8738,20 +8738,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:49:31+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-idn", "name": "symfony/polyfill-intl-idn",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git", "url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" "reference": "639084e360537a19f9ee352433b84ce831f3d2da"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da",
"reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", "reference": "639084e360537a19f9ee352433b84ce831f3d2da",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8765,7 +8765,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -8809,7 +8809,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -8825,20 +8825,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:49:31+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-normalizer", "name": "symfony/polyfill-intl-normalizer",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "219aa369ceff116e673852dce47c3a41794c14bd" "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
"reference": "219aa369ceff116e673852dce47c3a41794c14bd", "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8850,7 +8850,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -8893,7 +8893,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -8909,20 +8909,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:49:31+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8937,7 +8937,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -8976,7 +8976,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -8992,20 +8992,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:49:31+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/polyfill-php72", "name": "symfony/polyfill-php72",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php72.git", "url": "https://github.com/symfony/polyfill-php72.git",
"reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" "reference": "869329b1e9894268a8a61dabb69153029b7a8c97"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97",
"reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", "reference": "869329b1e9894268a8a61dabb69153029b7a8c97",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9014,7 +9014,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -9052,7 +9052,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -9068,20 +9068,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:49:31+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/polyfill-php73", "name": "symfony/polyfill-php73",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php73.git", "url": "https://github.com/symfony/polyfill-php73.git",
"reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9",
"reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9090,7 +9090,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -9131,7 +9131,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -9147,20 +9147,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:49:31+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/polyfill-php80", "name": "symfony/polyfill-php80",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php80.git", "url": "https://github.com/symfony/polyfill-php80.git",
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9169,7 +9169,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -9214,7 +9214,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -9230,20 +9230,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-10T07:21:04+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/polyfill-php81", "name": "symfony/polyfill-php81",
"version": "v1.26.0", "version": "v1.27.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php81.git", "url": "https://github.com/symfony/polyfill-php81.git",
"reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a",
"reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9252,7 +9252,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.26-dev" "dev-main": "1.27-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -9293,7 +9293,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0"
}, },
"funding": [ "funding": [
{ {
@ -9309,7 +9309,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-24T11:49:31+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
@ -13202,16 +13202,16 @@
}, },
{ {
"name": "composer/pcre", "name": "composer/pcre",
"version": "3.0.2", "version": "3.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/pcre.git", "url": "https://github.com/composer/pcre.git",
"reference": "4482b6409ca6bfc2af043a5711cd21ac3e7a8dfb" "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/4482b6409ca6bfc2af043a5711cd21ac3e7a8dfb", "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2",
"reference": "4482b6409ca6bfc2af043a5711cd21ac3e7a8dfb", "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -13253,7 +13253,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/composer/pcre/issues", "issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.0.2" "source": "https://github.com/composer/pcre/tree/3.1.0"
}, },
"funding": [ "funding": [
{ {
@ -13269,7 +13269,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-03T20:24:16+00:00" "time": "2022-11-17T09:50:14+00:00"
}, },
{ {
"name": "composer/semver", "name": "composer/semver",
@ -14003,16 +14003,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.9.1", "version": "1.9.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f" "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d6fdf01c53978b6429f1393ba4afeca39cc68afa",
"reference": "a59c8b5bfd4a236f27efc8b5ce72c313c2b54b5f", "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -14042,7 +14042,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan/issues", "issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.9.1" "source": "https://github.com/phpstan/phpstan/tree/1.9.2"
}, },
"funding": [ "funding": [
{ {
@ -14058,20 +14058,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-04T13:35:59+00:00" "time": "2022-11-10T09:56:11+00:00"
}, },
{ {
"name": "phpstan/phpstan-doctrine", "name": "phpstan/phpstan-doctrine",
"version": "1.3.22", "version": "1.3.23",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan-doctrine.git", "url": "https://github.com/phpstan/phpstan-doctrine.git",
"reference": "5080276a271a4ef71fbe33f4fb68f181dffeb03d" "reference": "964caf844c89134e5c2f19e97cbf8b5d12193779"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/5080276a271a4ef71fbe33f4fb68f181dffeb03d", "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/964caf844c89134e5c2f19e97cbf8b5d12193779",
"reference": "5080276a271a4ef71fbe33f4fb68f181dffeb03d", "reference": "964caf844c89134e5c2f19e97cbf8b5d12193779",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -14125,9 +14125,9 @@
"description": "Doctrine extensions for PHPStan", "description": "Doctrine extensions for PHPStan",
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan-doctrine/issues", "issues": "https://github.com/phpstan/phpstan-doctrine/issues",
"source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.22" "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.23"
}, },
"time": "2022-11-03T15:13:13+00:00" "time": "2022-11-14T07:46:16+00:00"
}, },
{ {
"name": "phpstan/phpstan-symfony", "name": "phpstan/phpstan-symfony",
@ -14271,12 +14271,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git", "url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "964c5d9ca40d0ec72db203b3dd6382a30abef616" "reference": "0399700d159e09b16645945758b65b921d3491fe"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/964c5d9ca40d0ec72db203b3dd6382a30abef616", "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0399700d159e09b16645945758b65b921d3491fe",
"reference": "964c5d9ca40d0ec72db203b3dd6382a30abef616", "reference": "0399700d159e09b16645945758b65b921d3491fe",
"shasum": "" "shasum": ""
}, },
"conflict": { "conflict": {
@ -14300,6 +14300,7 @@
"asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99",
"awesome-support/awesome-support": "<=6.0.7", "awesome-support/awesome-support": "<=6.0.7",
"aws/aws-sdk-php": ">=3,<3.2.1", "aws/aws-sdk-php": ">=3,<3.2.1",
"backdrop/backdrop": "<=1.23",
"badaso/core": "<2.6.1", "badaso/core": "<2.6.1",
"bagisto/bagisto": "<0.1.5", "bagisto/bagisto": "<0.1.5",
"barrelstrength/sprout-base-email": "<1.2.7", "barrelstrength/sprout-base-email": "<1.2.7",
@ -14333,7 +14334,7 @@
"codeigniter4/shield": "= 1.0.0-beta", "codeigniter4/shield": "= 1.0.0-beta",
"codiad/codiad": "<=2.8.4", "codiad/codiad": "<=2.8.4",
"composer/composer": "<1.10.26|>=2-alpha.1,<2.2.12|>=2.3,<2.3.5", "composer/composer": "<1.10.26|>=2-alpha.1,<2.2.12|>=2.3,<2.3.5",
"concrete5/concrete5": "<9", "concrete5/concrete5": "<9.1.3|>= 9.0.0RC1, < 9.1.3",
"concrete5/core": "<8.5.8|>=9,<9.1", "concrete5/core": "<8.5.8|>=9,<9.1",
"contao-components/mediaelement": ">=2.14.2,<2.21.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1",
"contao/contao": ">=4,<4.4.56|>=4.5,<4.9.18|>=4.10,<4.11.7|>=4.13,<4.13.3", "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.18|>=4.10,<4.11.7|>=4.13,<4.13.3",
@ -14359,7 +14360,7 @@
"doctrine/mongodb-odm": ">=1,<1.0.2", "doctrine/mongodb-odm": ">=1,<1.0.2",
"doctrine/mongodb-odm-bundle": ">=2,<3.0.1", "doctrine/mongodb-odm-bundle": ">=2,<3.0.1",
"doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4",
"dolibarr/dolibarr": "<16|= 12.0.5|>= 3.3.beta1, < 13.0.2", "dolibarr/dolibarr": "<16|>=16.0.1,<16.0.3|= 12.0.5|>= 3.3.beta1, < 13.0.2",
"dompdf/dompdf": "<2.0.1", "dompdf/dompdf": "<2.0.1",
"drupal/core": ">=7,<7.91|>=8,<9.3.19|>=9.4,<9.4.3", "drupal/core": ">=7,<7.91|>=8,<9.3.19|>=9.4,<9.4.3",
"drupal/drupal": ">=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", "drupal/drupal": ">=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4",
@ -14379,16 +14380,17 @@
"ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1", "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1",
"ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1", "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1",
"ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24",
"ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.27", "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26",
"ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1",
"ezsystems/ezplatform-kernel": "<=1.2.5|>=1.3,<1.3.19", "ezsystems/ezplatform-graphql": ">=1-rc.1,<1.0.13|>=2-beta.1,<2.3.12",
"ezsystems/ezplatform-kernel": "<=1.2.5|>=1.3,<1.3.26",
"ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8",
"ezsystems/ezplatform-richtext": ">=2.3,<=2.3.7", "ezsystems/ezplatform-richtext": ">=2.3,<=2.3.7",
"ezsystems/ezplatform-user": ">=1,<1.0.1", "ezsystems/ezplatform-user": ">=1,<1.0.1",
"ezsystems/ezpublish-kernel": "<=6.13.8.1|>=7,<7.5.29", "ezsystems/ezpublish-kernel": "<=6.13.8.1|>=7,<7.5.30",
"ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.3.5.1", "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.3.5.1",
"ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3",
"ezsystems/repository-forms": ">=2.3,<2.3.2.1", "ezsystems/repository-forms": ">=2.3,<2.3.2.1|>=2.5,<2.5.15",
"ezyang/htmlpurifier": "<4.1.1", "ezyang/htmlpurifier": "<4.1.1",
"facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2",
"facturascripts/facturascripts": "<=2022.8", "facturascripts/facturascripts": "<=2022.8",
@ -14397,7 +14399,7 @@
"fenom/fenom": "<=2.12.1", "fenom/fenom": "<=2.12.1",
"filegator/filegator": "<7.8", "filegator/filegator": "<7.8",
"firebase/php-jwt": "<2", "firebase/php-jwt": "<2",
"flarum/core": ">=1,<=1.0.1", "flarum/core": ">=1,<=1.0.1|>=1.5,<1.6.2",
"flarum/sticky": ">=0.1-beta.14,<=0.1-beta.15", "flarum/sticky": ">=0.1-beta.14,<=0.1-beta.15",
"flarum/tags": "<=0.1-beta.13", "flarum/tags": "<=0.1-beta.13",
"fluidtypo3/vhs": "<5.1.1", "fluidtypo3/vhs": "<5.1.1",
@ -14434,7 +14436,9 @@
"hjue/justwriting": "<=1", "hjue/justwriting": "<=1",
"hov/jobfair": "<1.0.13|>=2,<2.0.2", "hov/jobfair": "<1.0.13|>=2,<2.0.2",
"hyn/multi-tenant": ">=5.6,<5.7.2", "hyn/multi-tenant": ">=5.6,<5.7.2",
"ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4", "ibexa/admin-ui": ">=4.2,<4.2.3",
"ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3",
"ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3",
"ibexa/post-install": "<=1.0.4", "ibexa/post-install": "<=1.0.4",
"icecoder/icecoder": "<=8.1", "icecoder/icecoder": "<=8.1",
"idno/known": "<=1.3.1", "idno/known": "<=1.3.1",
@ -14451,6 +14455,7 @@
"ivankristianto/phpwhois": "<=4.3", "ivankristianto/phpwhois": "<=4.3",
"jackalope/jackalope-doctrine-dbal": "<1.7.4", "jackalope/jackalope-doctrine-dbal": "<1.7.4",
"james-heinrich/getid3": "<1.9.21", "james-heinrich/getid3": "<1.9.21",
"jasig/phpcas": "<1.3.3",
"joomla/archive": "<1.1.12|>=2,<2.0.1", "joomla/archive": "<1.1.12|>=2,<2.0.1",
"joomla/filesystem": "<1.6.2|>=2,<2.0.1", "joomla/filesystem": "<1.6.2|>=2,<2.0.1",
"joomla/filter": "<1.4.4|>=2,<2.0.1", "joomla/filter": "<1.4.4|>=2,<2.0.1",
@ -14478,7 +14483,7 @@
"league/commonmark": "<0.18.3", "league/commonmark": "<0.18.3",
"league/flysystem": "<1.1.4|>=2,<2.1.1", "league/flysystem": "<1.1.4|>=2,<2.1.1",
"lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3",
"librenms/librenms": "<=22.8", "librenms/librenms": "<22.10",
"limesurvey/limesurvey": "<3.27.19", "limesurvey/limesurvey": "<3.27.19",
"livehelperchat/livehelperchat": "<=3.91", "livehelperchat/livehelperchat": "<=3.91",
"livewire/livewire": ">2.2.4,<2.2.6", "livewire/livewire": ">2.2.4,<2.2.6",
@ -14503,7 +14508,7 @@
"modx/revolution": "<= 2.8.3-pl|<2.8", "modx/revolution": "<= 2.8.3-pl|<2.8",
"mojo42/jirafeau": "<4.4", "mojo42/jirafeau": "<4.4",
"monolog/monolog": ">=1.8,<1.12", "monolog/monolog": ">=1.8,<1.12",
"moodle/moodle": "<4.0.1", "moodle/moodle": "<4.0.5",
"mustache/mustache": ">=2,<2.14.1", "mustache/mustache": ">=2,<2.14.1",
"namshi/jose": "<2.2", "namshi/jose": "<2.2",
"neoan3-apps/template": "<1.1.1", "neoan3-apps/template": "<1.1.1",
@ -14603,12 +14608,12 @@
"shopware/storefront": "<=6.4.8.1", "shopware/storefront": "<=6.4.8.1",
"shopxo/shopxo": "<2.2.6", "shopxo/shopxo": "<2.2.6",
"showdoc/showdoc": "<2.10.4", "showdoc/showdoc": "<2.10.4",
"silverstripe/admin": ">=1,<1.8.1", "silverstripe/admin": ">=1,<1.11.3",
"silverstripe/assets": ">=1,<1.10.1", "silverstripe/assets": ">=1,<1.11.1",
"silverstripe/cms": "<4.3.6|>=4.4,<4.4.4", "silverstripe/cms": "<4.11.3",
"silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1",
"silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3",
"silverstripe/framework": "<4.10.9", "silverstripe/framework": "<4.11.14",
"silverstripe/graphql": "<3.5.2|>=4-alpha.1,<4-alpha.2|= 4.0.0-alpha1", "silverstripe/graphql": "<3.5.2|>=4-alpha.1,<4-alpha.2|= 4.0.0-alpha1",
"silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1",
"silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1",
@ -14617,6 +14622,7 @@
"silverstripe/subsites": ">=2,<2.1.1", "silverstripe/subsites": ">=2,<2.1.1",
"silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1",
"silverstripe/userforms": "<3", "silverstripe/userforms": "<3",
"silverstripe/versioned-admin": ">=1,<1.11.1",
"simple-updates/phpwhois": "<=1", "simple-updates/phpwhois": "<=1",
"simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4",
"simplesamlphp/simplesamlphp": "<1.18.6", "simplesamlphp/simplesamlphp": "<1.18.6",
@ -14691,7 +14697,7 @@
"topthink/framework": "<=6.0.13", "topthink/framework": "<=6.0.13",
"topthink/think": "<=6.0.9", "topthink/think": "<=6.0.9",
"topthink/thinkphp": "<=3.2.3", "topthink/thinkphp": "<=3.2.3",
"tribalsystems/zenario": "<9.2.55826", "tribalsystems/zenario": "<=9.3.57186",
"truckersmp/phpwhois": "<=4.3.1", "truckersmp/phpwhois": "<=4.3.1",
"twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3",
"typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.29|>=10,<10.4.32|>=11,<11.5.16", "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.29|>=10,<10.4.32|>=11,<11.5.16",
@ -14727,7 +14733,7 @@
"yetiforce/yetiforce-crm": "<=6.4", "yetiforce/yetiforce-crm": "<=6.4",
"yidashi/yii2cmf": "<=2", "yidashi/yii2cmf": "<=2",
"yii2mod/yii2-cms": "<1.9.2", "yii2mod/yii2-cms": "<1.9.2",
"yiisoft/yii": ">=1.1.14,<1.1.15", "yiisoft/yii": "<1.1.27",
"yiisoft/yii2": "<2.0.38", "yiisoft/yii2": "<2.0.38",
"yiisoft/yii2-bootstrap": "<2.0.4", "yiisoft/yii2-bootstrap": "<2.0.4",
"yiisoft/yii2-dev": "<2.0.43", "yiisoft/yii2-dev": "<2.0.43",
@ -14798,7 +14804,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-04T21:04:09+00:00" "time": "2022-11-23T23:04:03+00:00"
}, },
{ {
"name": "sebastian/diff", "name": "sebastian/diff",
@ -15348,16 +15354,16 @@
}, },
{ {
"name": "symplify/easy-coding-standard", "name": "symplify/easy-coding-standard",
"version": "11.1.16", "version": "11.1.17",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symplify/easy-coding-standard.git", "url": "https://github.com/symplify/easy-coding-standard.git",
"reference": "32ebd57f0f47713540df8ea39134adaa9d912fae" "reference": "2a98e5b976a3ab573d8e5604d6eb39d9f5783760"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/32ebd57f0f47713540df8ea39134adaa9d912fae", "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/2a98e5b976a3ab573d8e5604d6eb39d9f5783760",
"reference": "32ebd57f0f47713540df8ea39134adaa9d912fae", "reference": "2a98e5b976a3ab573d8e5604d6eb39d9f5783760",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -15387,7 +15393,7 @@
], ],
"description": "Prefixed scoped version of ECS package", "description": "Prefixed scoped version of ECS package",
"support": { "support": {
"source": "https://github.com/symplify/easy-coding-standard/tree/11.1.16" "source": "https://github.com/symplify/easy-coding-standard/tree/11.1.17"
}, },
"funding": [ "funding": [
{ {
@ -15399,7 +15405,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-10-27T10:48:13+00:00" "time": "2022-11-10T15:20:49+00:00"
}, },
{ {
"name": "vimeo/psalm", "name": "vimeo/psalm",

View file

@ -13,7 +13,7 @@ doctrine:
class: App\Helpers\UTCDateTimeType class: App\Helpers\UTCDateTimeType
big_decimal: big_decimal:
class: App\Helpers\BigDecimalType class: App\Helpers\BigDecimalType
schema_filter: ~^(?!internal)~ schema_filter: ~^(?!internal)~
# Only enable this when needed # Only enable this when needed
profiling_collect_backtrace: false profiling_collect_backtrace: false

View file

@ -2,10 +2,12 @@
# This should be compatible with the legacy Part-DB # This should be compatible with the legacy Part-DB
groups: groups:
parts: #parts:
label: "perm.group.parts" # label: "perm.group.parts"
structures: #structures:
label: "perm.group.structures" # label: "perm.group.structures"
data:
label: "perm.group.data"
system: system:
label: "perm.group.system" label: "perm.group.system"
@ -15,221 +17,52 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
# Part related permissions # Part related permissions
parts: # e.g. this maps to perms_parts in User/Group database parts: # e.g. this maps to perms_parts in User/Group database
group: "parts" group: "data"
label: "perm.parts" label: "perm.parts"
operations: # Here are all possible operations are listed => the op name is mapped to bit value operations: # Here are all possible operations are listed => the op name is mapped to bit value
read: read:
label: "perm.read" label: "perm.read"
bit: 0 # If a part can be read by a user, he can also see all the datastructures (except devices)
alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read',
'currencies.read', 'attachment_types.read', 'measurement_units.read']
edit: edit:
label: "perm.edit" label: "perm.edit"
bit: 2
alsoSet: 'read' alsoSet: 'read'
create: create:
label: "perm.create" label: "perm.create"
bit: 4
alsoSet: ['read', 'edit'] alsoSet: ['read', 'edit']
#move:
# label: "perm.part.move"
# bit: 6
# alsoSet: 'read'
delete: delete:
label: "perm.delete" label: "perm.delete"
bit: 8
alsoSet: ['read', 'edit'] alsoSet: ['read', 'edit']
search:
label: "perm.part.search"
bit: 10
all_parts:
label: "perm.part.all_parts"
bit: 12
no_price_parts:
label: "perm.part.no_price_parts"
bit: 14
obsolete_parts:
label: "perm.part.obsolete_parts"
bit: 16
unknown_instock_parts:
label: "perm.part.unknown_instock_parts"
bit: 18
change_favorite: change_favorite:
label: "perm.part.change_favorite" label: "perm.part.change_favorite"
bit: 20 alsoSet: ['edit']
show_favorite_parts:
label: "perm.part.show_favorite"
bit: 24
show_last_edit_parts:
label: "perm.part.show_last_edit_parts"
bit: 26
show_users:
label: "perm.part.show_users"
bit: 28
show_history: show_history:
label: "perm.part.show_history" label: "perm.part.show_history"
bit: 30 alsoSet: ['read']
revert_element: revert_element:
label: "perm.revert_elements" label: "perm.revert_elements"
bit: 32
alsoSet: ["read", "edit", "create", "delete", "show_history"] alsoSet: ["read", "edit", "create", "delete", "show_history"]
parts_name: &PART_ATTRIBUTE # We define a template here, that we can use for all part attributes.
label: "perm.part.name"
group: "parts"
operations:
read:
label: "perm.read"
bit: 0
edit:
label: "perm.edit"
bit: 2
alsoSet: 'read'
parts_category:
<<: *PART_ATTRIBUTE
label: "perm.part.category"
parts_description:
<<: *PART_ATTRIBUTE
label: "perm.part.description"
parts_minamount:
<<: *PART_ATTRIBUTE
label: "perm.part.minamount"
parts_footprint:
<<: *PART_ATTRIBUTE
label: "perm.part.footprint"
parts_comment:
<<: *PART_ATTRIBUTE
label: "perm.part.comment"
parts_manufacturer:
<<: *PART_ATTRIBUTE
label: "perm.part.manufacturer"
parts_mpn:
<<: *PART_ATTRIBUTE
label: "perm.part.mpn"
parts_status:
<<: *PART_ATTRIBUTE
label: "perm.part.status"
parts_tags:
<<: *PART_ATTRIBUTE
label: "perm.part.tags"
parts_unit:
<<: *PART_ATTRIBUTE
label: "perm.part.unit"
parts_mass:
<<: *PART_ATTRIBUTE
label: "perm.part.mass"
parts_orderdetails: &PART_MULTI_ATTRIBUTE
label: "perm.part.orderdetails"
group: "parts"
operations:
read:
label: "perm.read"
bit: 0
edit:
label: "perm.edit"
bit: 2
alsoSet: 'read'
create:
label: "perm.create"
bit: 4
alsoSet: ['read', 'edit']
delete:
label: "perm.delete"
bit: 6
alsoSet: ['read']
parts_prices:
<<: *PART_MULTI_ATTRIBUTE
label: "perm.part.prices"
parts_parameters:
<<: *PART_MULTI_ATTRIBUTE
label: "perm.part.parameters"
parts_lots:
<<: *PART_MULTI_ATTRIBUTE
label: "perm.part.lots"
parts_attachments:
group: "structures"
label: "perm.part.attachments"
operations:
read:
label: "perm.read"
bit: 0
edit:
label: "perm.edit"
bit: 2
alsoSet: 'read'
create:
label: "perm.create"
bit: 4
alsoSet: ['read', 'edit']
delete:
label: "perm.delete"
bit: 6
alsoSet: ['read']
show_history:
label: "perm.show_history"
bit: 8
revert_element:
label: "perm.revert_elements"
bit: 10
alsoSet: ["read", "edit", "create", "delete", "show_history"]
show_private:
label: "perm.attachment_show_private"
bit: 12
alsoSet: ["read"]
parts_order:
<<: *PART_ATTRIBUTE
label: "perm.part.order"
storelocations: &PART_CONTAINING storelocations: &PART_CONTAINING
label: "perm.storelocations" label: "perm.storelocations"
group: "structures" group: "data"
operations: operations:
read: read:
label: "perm.read" label: "perm.read"
bit: 0
edit: edit:
label: "perm.edit" label: "perm.edit"
bit: 2
alsoSet: 'read' alsoSet: 'read'
create: create:
label: "perm.create" label: "perm.create"
bit: 4
alsoSet: ['read', 'edit'] alsoSet: ['read', 'edit']
move:
label: "perm.move"
bit: 6
delete: delete:
label: "perm.delete" label: "perm.delete"
bit: 8
alsoSet: ['read', 'edit'] alsoSet: ['read', 'edit']
list_parts:
label: "perm.list_parts"
bit: 10
show_users:
label: "perm.show_users"
bit: 12
show_history: show_history:
label: "perm.show_history" label: "perm.show_history"
bit: 14
revert_element: revert_element:
label: "perm.revert_elements" label: "perm.revert_elements"
bit: 16
alsoSet: ["read", "edit", "create", "delete", "show_history"] alsoSet: ["read", "edit", "create", "delete", "show_history"]
footprints: footprints:
@ -267,36 +100,24 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
tools: tools:
label: "perm.part.tools" label: "perm.part.tools"
operations: operations:
import: #import:
label: "perm.tools.import" # label: "perm.tools.import"
bit: 0 #labels:
labels: # label: "perm.tools.labels"
label: "perm.tools.labels"
bit: 2
#calculator: #calculator:
# label: "perm.tools.calculator" # label: "perm.tools.calculator"
# bit: 4
#footprints: #footprints:
# label: "perm.tools.footprints" # label: "perm.tools.footprints"
# bit: 6
#ic_logos: #ic_logos:
# label: "perm.tools.ic_logos" # label: "perm.tools.ic_logos"
# bit: 8
statistics: statistics:
label: "perm.tools.statistics" label: "perm.tools.statistics"
bit: 10
lastActivity: lastActivity:
label: "perm.tools.lastActivity" label: "perm.tools.lastActivity"
bit: 12
timetravel:
label: "perm.tools.timeTravel"
bit: 14
label_scanner: label_scanner:
label: "perm.tools.label_scanner" label: "perm.tools.label_scanner"
bit: 16
reel_calculator: reel_calculator:
label: "perm.tools.reel_calculator" label: "perm.tools.reel_calculator"
bit: 18
groups: groups:
label: "perm.groups" label: "perm.groups"
@ -304,33 +125,23 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
operations: operations:
read: read:
label: "perm.read" label: "perm.read"
bit: 0
edit: edit:
label: "perm.edit" label: "perm.edit"
bit: 2
alsoSet: 'read' alsoSet: 'read'
create: create:
label: "perm.create" label: "perm.create"
bit: 4
alsoSet: ['read', 'edit'] alsoSet: ['read', 'edit']
move:
label: "perm.move"
bit: 6
delete: delete:
label: "perm.delete" label: "perm.delete"
bit: 8
alsoSet: ['read', 'delete'] alsoSet: ['read', 'delete']
edit_permissions: edit_permissions:
label: "perm.edit_permissions" label: "perm.edit_permissions"
alsoSet: ['read', 'edit'] alsoSet: ['read', 'edit']
bit: 10
show_history: show_history:
label: "perm.show_history" label: "perm.show_history"
bit: 12
revert_element: revert_element:
label: "perm.revert_elements" label: "perm.revert_elements"
bit: 14 alsoSet: ["read", "edit", "create", "delete", "edit_permissions", "show_history"]
alsoSet: ["read", "edit", "create", "delete", "move", "edit_permissions", "show_history"]
users: users:
label: "perm.users" label: "perm.users"
@ -338,167 +149,119 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
operations: operations:
read: read:
label: "perm.read" label: "perm.read"
bit: 0
create: create:
label: "perm.create" label: "perm.create"
alsoSet: ['read', 'edit_username', 'edit_infos'] alsoSet: ['read', 'edit_username', 'edit_infos']
bit: 4
delete: delete:
label: "perm.delete" label: "perm.delete"
alsoSet: ['read', 'edit_username', 'edit_infos'] alsoSet: ['read', 'edit_username', 'edit_infos']
bit: 8
edit_username: edit_username:
label: "perm.users.edit_user_name" label: "perm.users.edit_user_name"
alsoSet: ['read'] alsoSet: ['read']
bit: 2
change_group:
label: "perm.users.edit_change_group"
alsoSet: 'read'
bit: 6
edit_infos: edit_infos:
label: "perm.users.edit_infos" label: "perm.users.edit_infos"
alsoSet: 'read' alsoSet: 'read'
bit: 10
edit_permissions: edit_permissions:
label: "perm.users.edit_permissions" label: "perm.users.edit_permissions"
alsoSet: 'read' alsoSet: 'read'
bit: 12
set_password: set_password:
label: "perm.users.set_password" label: "perm.users.set_password"
alsoSet: 'read' alsoSet: 'read'
bit: 14
change_user_settings: change_user_settings:
label: "perm.users.change_user_settings" label: "perm.users.change_user_settings"
bit: 16
show_history: show_history:
label: "perm.show_history" label: "perm.show_history"
bit: 18
revert_element: revert_element:
label: "perm.revert_elements" label: "perm.revert_elements"
bit: 20 alsoSet: ["read", "create", "delete", "edit_permissions", "show_history", "edit_infos", "edit_username"]
alsoSet: ["read", "create", "delete", "edit_permissions", "show_history", "edit_infos", "change_group", "edit_username"]
database: #database:
label: "perm.database" # label: "perm.database"
group: "system" # group: "system"
operations: # operations:
see_status: # see_status:
label: "perm.database.see_status" # label: "perm.database.see_status"
bit: 0 # update_db:
update_db: # label: "perm.database.update_db"
label: "perm.database.update_db" # alsoSet: 'see_status'
bit: 2 # read_db_settings:
alsoSet: 'see_status' # label: "perm.database.read_db_settings"
read_db_settings: # write_db_settings:
label: "perm.database.read_db_settings" # label: "perm.database.write_db_settings"
bit: 4 # alsoSet: ['read_db_settings', 'see_status']
write_db_settings:
label: "perm.database.write_db_settings"
alsoSet: ['read_db_settings', 'see_status']
bit: 2
config: #config:
label: "perm.config" # label: "perm.config"
group: "system" # group: "system"
operations: # operations:
read_config: # read_config:
label: "perm.config.read_config" # label: "perm.config.read_config"
bit: 0 # edit_config:
edit_config: # label: "perm.config.edit_config"
label: "perm.config.edit_config" # alsoSet: 'read_config'
alsoSet: 'read_config' # server_info:
bit: 2 # label: "perm.config.server_info"
server_info:
label: "perm.config.server_info"
bit: 6
system: system:
label: "perm.system" label: "perm.system"
group: "system" group: "system"
operations: operations:
use_debug:
label: "perm.config.use_debug"
bit: 0
show_logs: show_logs:
label: "perm.show_logs" label: "perm.show_logs"
bit: 2
delete_logs: delete_logs:
label: "perm.delete_logs" label: "perm.delete_logs"
alsoSet: 'show_logs' alsoSet: 'show_logs'
bit: 4 server_infos:
label: "perm.server_infos"
devices_parts: attachments:
label: "perm.device_parts" label: "perm.part.attachments"
group: "parts"
operations: operations:
read: show_private:
label: "perm.read" label: "perm.attachments.show_private"
bit: 0 list_attachments:
edit: label: "perm.attachments.list_attachments"
label: "perm.edit" alsoSet: ['attachment_types.read']
alsoSet: 'read'
bit: 2
create:
label: "perm.create"
alsoSet: ['edit', 'read']
bit: 6
delete:
label: "perm.delete"
alsoSet: ['edit', 'read']
bit: 8
self: self:
label: "perm.self" label: "perm.self"
operations: operations:
edit_infos: edit_infos:
label: "perm.self.edit_infos" label: "perm.self.edit_infos"
bit: 0
edit_username: edit_username:
label: "perm.self.edit_username" label: "perm.self.edit_username"
bit: 2
show_permissions: show_permissions:
label: "perm.self.show_permissions" label: "perm.self.show_permissions"
bit: 4
show_logs: show_logs:
label: "perm.self.show_logs" label: "perm.self.show_logs"
bit: 6
labels: labels:
label: "perm.labels" label: "perm.labels"
operations: operations:
create_labels: create_labels:
label: "perm.self.create_labels" label: "perm.self.create_labels"
bit: 0
edit_options: edit_options:
label: "perm.self.edit_options" label: "perm.self.edit_options"
bit: 2
alsoSet: ['create_labels'] alsoSet: ['create_labels']
read_profiles: read_profiles:
label: "perm.self.read_profiles" label: "perm.self.read_profiles"
bit: 10
edit_profiles: edit_profiles:
label: "perm.self.edit_profiles" label: "perm.self.edit_profiles"
bit: 6
alsoSet: ['read_profiles'] alsoSet: ['read_profiles']
create_profiles: create_profiles:
label: "perm.self.create_profiles" label: "perm.self.create_profiles"
bit: 8
alsoSet: ['read_profiles', 'edit_profiles'] alsoSet: ['read_profiles', 'edit_profiles']
delete_profiles: delete_profiles:
label: "perm.self.delete_profiles" label: "perm.self.delete_profiles"
bit: 4
alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles'] alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles']
use_twig: use_twig:
label: "perm.labels.use_twig" label: "perm.labels.use_twig"
bit: 12
alsoSet: ['create_labels', 'edit_options'] alsoSet: ['create_labels', 'edit_options']
show_history: show_history:
label: "perm.show_history" label: "perm.show_history"
bit: 14
alsoSet: ['read_profiles'] alsoSet: ['read_profiles']
revert_element: revert_element:
label: "perm.revert_elements" label: "perm.revert_elements"
bit: 16
alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles'] alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles']

View file

@ -127,10 +127,6 @@ services:
# Security # Security
#################################################################################################################### ####################################################################################################################
App\Security\EntityListeners\ElementPermissionListener:
tags:
- { name: "doctrine.orm.entity_listener" }
#################################################################################################################### ####################################################################################################################
# Cache # Cache
#################################################################################################################### ####################################################################################################################
@ -177,7 +173,7 @@ services:
arguments: arguments:
$allow_email_pw_reset: '%partdb.users.email_pw_reset%' $allow_email_pw_reset: '%partdb.users.email_pw_reset%'
App\Services\TFA\BackupCodeGenerator: App\Services\UserSystem\TFA\BackupCodeGenerator:
arguments: arguments:
$code_length: 8 $code_length: 8
$code_count: 15 $code_count: 15

View file

@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20221114193325 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Update the permission system to the new system. Please note that all permissions will be reset!';
}
private function addDataMigrationAndWarning(): void
{
//Reset the permissions of the predefined groups, when their name was not changed
$this->addSql(<<<'SQL'
UPDATE `groups` SET permissions_data = '{"parts":{"read":true,"edit":true,"create":true,"delete":true,"change_favorite":true,"show_history":true,"revert_element":true},"tools":{"statistics":true,"label_scanner":true,"reel_calculator":true,"lastActivity":true},"attachments":{"list_attachments":true,"show_private":true},"self":{"show_permissions":true,"edit_infos":true},"labels":{"create_labels":true,"edit_options":true,"read_profiles":true,"edit_profiles":true,"create_profiles":true,"delete_profiles":true,"show_history":true,"revert_element":true},"categories":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"storelocations":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"footprints":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"manufacturers":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"attachment_types":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"currencies":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"measurement_units":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"suppliers":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"users":{"read":true,"create":true,"delete":true,"edit_username":true,"edit_infos":true,"edit_permissions":true,"set_password":true,"change_user_settings":true,"show_history":true,"revert_element":true},"groups":{"read":true,"edit":true,"create":true,"delete":true,"edit_permissions":true,"show_history":true,"revert_element":true},"system":{"show_logs":true,"server_infos":true},"devices":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true}}'
WHERE id = 1 AND name = 'admins';
SQL);
$this->addSql(<<<'SQL'
UPDATE `groups` SET permissions_data = '{"parts":{"read":true},"tools":{"statistics":true,"label_scanner":true,"reel_calculator":true},"attachments":{"list_attachments":true},"self":{"show_permissions":true},"labels":{"create_labels":true,"edit_options":true},"storelocations":{"read":true},"footprints":{"read":true},"categories":{"read":true},"suppliers":{"read":true},"manufacturers":{"read":true},"currencies":{"read":true},"attachment_types":{"read":true},"measurement_units":{"read":true},"devices":{"read":true}}'
WHERE id = 2 AND name = 'readonly';
SQL);
$this->addSql(<<<'SQL'
UPDATE `groups` SET permissions_data = '{"parts":{"read":true,"edit":true,"create":true,"delete":true,"change_favorite":true,"show_history":true,"revert_element":true},"tools":{"statistics":true,"label_scanner":true,"reel_calculator":true,"lastActivity":true},"attachments":{"list_attachments":true,"show_private":true},"self":{"show_permissions":true,"edit_infos":true},"labels":{"create_labels":true,"edit_options":true,"read_profiles":true,"edit_profiles":true,"create_profiles":true,"delete_profiles":true,"show_history":true,"revert_element":true},"categories":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"storelocations":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"footprints":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"manufacturers":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"attachment_types":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"currencies":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"measurement_units":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"suppliers":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"devices":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true}}'
WHERE id = 3 AND name = 'users';
SQL);
//Disable login of all users with ID > 2 (meaning all except the anonymous and admin user)
$this->addSql(<<<'SQL'
UPDATE `users` SET disabled = 1
WHERE id > 2;
SQL);
//Reset the permissions of the admin user, to allow admin permissions (like the admins group)
$this->addSql(<<<'SQL'
UPDATE `users` SET permissions_data = '{"parts":{"read":true,"edit":true,"create":true,"delete":true,"change_favorite":true,"show_history":true,"revert_element":true},"tools":{"statistics":true,"label_scanner":true,"reel_calculator":true,"lastActivity":true},"attachments":{"list_attachments":true,"show_private":true},"self":{"show_permissions":true,"edit_infos":true},"labels":{"create_labels":true,"edit_options":true,"read_profiles":true,"edit_profiles":true,"create_profiles":true,"delete_profiles":true,"show_history":true,"revert_element":true},"categories":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"storelocations":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"footprints":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"manufacturers":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"attachment_types":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"currencies":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"measurement_units":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"suppliers":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true},"users":{"read":true,"create":true,"delete":true,"edit_username":true,"edit_infos":true,"edit_permissions":true,"set_password":true,"change_user_settings":true,"show_history":true,"revert_element":true},"groups":{"read":true,"edit":true,"create":true,"delete":true,"edit_permissions":true,"show_history":true,"revert_element":true},"system":{"show_logs":true,"server_infos":true},"devices":{"read":true,"edit":true,"create":true,"delete":true,"show_history":true,"revert_element":true}}'
WHERE id = 2;
SQL);
$this->warnIf(true, "\x1b[1;37;43m\n!!! All permissions were reset! Please change them to the desired state, immediately !!!\x1b[0;39;49m");
$this->warnIf(true, "\x1b[1;37;43m\n!!! For security reasons all users (except the admin user) were disabled. Login with admin user and reenable other users after checking their permissions !!!\x1b[0;39;49m");
$this->warnIf(true, "\x1b[1;37;43m\n!!! For more infos see: https://github.com/Part-DB/Part-DB-symfony/discussions/193 !!!\x1b[0;39;49m");
}
public function mySQLUp(Schema $schema): void
{
$this->addSql('ALTER TABLE groups ADD permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', DROP perms_system, DROP perms_groups, DROP perms_users, DROP perms_self, DROP perms_system_config, DROP perms_system_database, DROP perms_parts, DROP perms_parts_name, DROP perms_parts_description, DROP perms_parts_footprint, DROP perms_parts_manufacturer, DROP perms_parts_comment, DROP perms_parts_order, DROP perms_parts_orderdetails, DROP perms_parts_prices, DROP perms_parts_attachements, DROP perms_devices, DROP perms_devices_parts, DROP perms_storelocations, DROP perms_footprints, DROP perms_categories, DROP perms_suppliers, DROP perms_manufacturers, DROP perms_attachement_types, DROP perms_tools, DROP perms_labels, DROP perms_parts_category, DROP perms_parts_minamount, DROP perms_parts_lots, DROP perms_parts_tags, DROP perms_parts_unit, DROP perms_parts_mass, DROP perms_parts_status, DROP perms_parts_mpn, DROP perms_currencies, DROP perms_measurement_units, DROP perms_parts_parameters');
$this->addSql('ALTER TABLE users ADD permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', DROP perms_system, DROP perms_groups, DROP perms_users, DROP perms_self, DROP perms_system_config, DROP perms_system_database, DROP perms_parts, DROP perms_parts_name, DROP perms_parts_description, DROP perms_parts_footprint, DROP perms_parts_manufacturer, DROP perms_parts_comment, DROP perms_parts_order, DROP perms_parts_orderdetails, DROP perms_parts_prices, DROP perms_parts_attachements, DROP perms_devices, DROP perms_devices_parts, DROP perms_storelocations, DROP perms_footprints, DROP perms_categories, DROP perms_suppliers, DROP perms_manufacturers, DROP perms_attachement_types, DROP perms_tools, DROP perms_labels, DROP perms_parts_category, DROP perms_parts_minamount, DROP perms_parts_lots, DROP perms_parts_tags, DROP perms_parts_unit, DROP perms_parts_mass, DROP perms_parts_status, DROP perms_parts_mpn, DROP perms_currencies, DROP perms_measurement_units, DROP perms_parts_parameters');
$this->addDataMigrationAndWarning();
}
public function mySQLDown(Schema $schema): void
{
$this->addSql('ALTER TABLE `groups` ADD perms_system INT NOT NULL, ADD perms_groups INT NOT NULL, ADD perms_users INT NOT NULL, ADD perms_self INT NOT NULL, ADD perms_system_config INT NOT NULL, ADD perms_system_database INT NOT NULL, ADD perms_parts BIGINT NOT NULL, ADD perms_parts_name SMALLINT NOT NULL, ADD perms_parts_description SMALLINT NOT NULL, ADD perms_parts_footprint SMALLINT NOT NULL, ADD perms_parts_manufacturer SMALLINT NOT NULL, ADD perms_parts_comment SMALLINT NOT NULL, ADD perms_parts_order SMALLINT NOT NULL, ADD perms_parts_orderdetails SMALLINT NOT NULL, ADD perms_parts_prices SMALLINT NOT NULL, ADD perms_parts_attachements SMALLINT NOT NULL, ADD perms_devices INT NOT NULL, ADD perms_devices_parts INT NOT NULL, ADD perms_storelocations INT NOT NULL, ADD perms_footprints INT NOT NULL, ADD perms_categories INT NOT NULL, ADD perms_suppliers INT NOT NULL, ADD perms_manufacturers INT NOT NULL, ADD perms_attachement_types INT NOT NULL, ADD perms_tools INT NOT NULL, ADD perms_labels INT NOT NULL, ADD perms_parts_category SMALLINT NOT NULL, ADD perms_parts_minamount SMALLINT NOT NULL, ADD perms_parts_lots SMALLINT NOT NULL, ADD perms_parts_tags SMALLINT NOT NULL, ADD perms_parts_unit SMALLINT NOT NULL, ADD perms_parts_mass SMALLINT NOT NULL, ADD perms_parts_status SMALLINT NOT NULL, ADD perms_parts_mpn SMALLINT NOT NULL, ADD perms_currencies INT NOT NULL, ADD perms_measurement_units INT NOT NULL, ADD perms_parts_parameters SMALLINT NOT NULL, DROP permissions_data');
$this->addSql('ALTER TABLE `users` ADD perms_system INT NOT NULL, ADD perms_groups INT NOT NULL, ADD perms_users INT NOT NULL, ADD perms_self INT NOT NULL, ADD perms_system_config INT NOT NULL, ADD perms_system_database INT NOT NULL, ADD perms_parts BIGINT NOT NULL, ADD perms_parts_name SMALLINT NOT NULL, ADD perms_parts_description SMALLINT NOT NULL, ADD perms_parts_footprint SMALLINT NOT NULL, ADD perms_parts_manufacturer SMALLINT NOT NULL, ADD perms_parts_comment SMALLINT NOT NULL, ADD perms_parts_order SMALLINT NOT NULL, ADD perms_parts_orderdetails SMALLINT NOT NULL, ADD perms_parts_prices SMALLINT NOT NULL, ADD perms_parts_attachements SMALLINT NOT NULL, ADD perms_devices INT NOT NULL, ADD perms_devices_parts INT NOT NULL, ADD perms_storelocations INT NOT NULL, ADD perms_footprints INT NOT NULL, ADD perms_categories INT NOT NULL, ADD perms_suppliers INT NOT NULL, ADD perms_manufacturers INT NOT NULL, ADD perms_attachement_types INT NOT NULL, ADD perms_tools INT NOT NULL, ADD perms_labels INT NOT NULL, ADD perms_parts_category SMALLINT NOT NULL, ADD perms_parts_minamount SMALLINT NOT NULL, ADD perms_parts_lots SMALLINT NOT NULL, ADD perms_parts_tags SMALLINT NOT NULL, ADD perms_parts_unit SMALLINT NOT NULL, ADD perms_parts_mass SMALLINT NOT NULL, ADD perms_parts_status SMALLINT NOT NULL, ADD perms_parts_mpn SMALLINT NOT NULL, ADD perms_currencies INT NOT NULL, ADD perms_measurement_units INT NOT NULL, ADD perms_parts_parameters SMALLINT NOT NULL, DROP permissions_data');
}
public function sqLiteUp(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachement, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added FROM groups');
$this->addSql('DROP TABLE groups');
$this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --(DC2Type:json)
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D39706DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachement, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachement, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)');
$this->addSql('CREATE INDEX group_idx_name ON groups (name)');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)');
$this->addSql('CREATE INDEX IDX_F06D39706DEDCEC2 ON groups (id_preview_attachement)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachement, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added FROM users');
$this->addSql('DROP TABLE users');
$this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --(DC2Type:json)
, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E96DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachement, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added) SELECT id, group_id, currency_id, id_preview_attachement, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE INDEX user_idx_username ON users (name)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E96DEDCEC2 ON users (id_preview_attachement)');
$this->addDataMigrationAndWarning();
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachement, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added FROM "groups"');
$this->addSql('DROP TABLE "groups"');
$this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, perms_system INTEGER NOT NULL, perms_groups INTEGER NOT NULL, perms_users INTEGER NOT NULL, perms_self INTEGER NOT NULL, perms_system_config INTEGER NOT NULL, perms_system_database INTEGER NOT NULL, perms_parts BIGINT NOT NULL, perms_parts_name SMALLINT NOT NULL, perms_parts_category SMALLINT NOT NULL, perms_parts_description SMALLINT NOT NULL, perms_parts_minamount SMALLINT NOT NULL, perms_parts_footprint SMALLINT NOT NULL, perms_parts_lots SMALLINT NOT NULL, perms_parts_tags SMALLINT NOT NULL, perms_parts_unit SMALLINT NOT NULL, perms_parts_mass SMALLINT NOT NULL, perms_parts_manufacturer SMALLINT NOT NULL, perms_parts_status SMALLINT NOT NULL, perms_parts_mpn SMALLINT NOT NULL, perms_parts_comment SMALLINT NOT NULL, perms_parts_order SMALLINT NOT NULL, perms_parts_orderdetails SMALLINT NOT NULL, perms_parts_prices SMALLINT NOT NULL, perms_parts_parameters SMALLINT NOT NULL, perms_parts_attachements SMALLINT NOT NULL, perms_devices INTEGER NOT NULL, perms_devices_parts INTEGER NOT NULL, perms_storelocations INTEGER NOT NULL, perms_footprints INTEGER NOT NULL, perms_categories INTEGER NOT NULL, perms_suppliers INTEGER NOT NULL, perms_manufacturers INTEGER NOT NULL, perms_attachement_types INTEGER NOT NULL, perms_currencies INTEGER NOT NULL, perms_measurement_units INTEGER NOT NULL, perms_tools INTEGER NOT NULL, perms_labels INTEGER NOT NULL, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D39706DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "groups" (id, parent_id, id_preview_attachement, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachement, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)');
$this->addSql('CREATE INDEX IDX_F06D39706DEDCEC2 ON "groups" (id_preview_attachement)');
$this->addSql('CREATE INDEX group_idx_name ON "groups" (name)');
$this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachement, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added FROM "users"');
$this->addSql('DROP TABLE "users"');
$this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --
(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --
(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, perms_system INTEGER NOT NULL, perms_groups INTEGER NOT NULL, perms_users INTEGER NOT NULL, perms_self INTEGER NOT NULL, perms_system_config INTEGER NOT NULL, perms_system_database INTEGER NOT NULL, perms_parts BIGINT NOT NULL, perms_parts_name SMALLINT NOT NULL, perms_parts_category SMALLINT NOT NULL, perms_parts_description SMALLINT NOT NULL, perms_parts_minamount SMALLINT NOT NULL, perms_parts_footprint SMALLINT NOT NULL, perms_parts_lots SMALLINT NOT NULL, perms_parts_tags SMALLINT NOT NULL, perms_parts_unit SMALLINT NOT NULL, perms_parts_mass SMALLINT NOT NULL, perms_parts_manufacturer SMALLINT NOT NULL, perms_parts_status SMALLINT NOT NULL, perms_parts_mpn SMALLINT NOT NULL, perms_parts_comment SMALLINT NOT NULL, perms_parts_order SMALLINT NOT NULL, perms_parts_orderdetails SMALLINT NOT NULL, perms_parts_prices SMALLINT NOT NULL, perms_parts_parameters SMALLINT NOT NULL, perms_parts_attachements SMALLINT NOT NULL, perms_devices INTEGER NOT NULL, perms_devices_parts INTEGER NOT NULL, perms_storelocations INTEGER NOT NULL, perms_footprints INTEGER NOT NULL, perms_categories INTEGER NOT NULL, perms_suppliers INTEGER NOT NULL, perms_manufacturers INTEGER NOT NULL, perms_attachement_types INTEGER NOT NULL, perms_currencies INTEGER NOT NULL, perms_measurement_units INTEGER NOT NULL, perms_tools INTEGER NOT NULL, perms_labels INTEGER NOT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E96DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "users" (id, group_id, currency_id, id_preview_attachement, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added) SELECT id, group_id, currency_id, id_preview_attachement, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E96DEDCEC2 ON "users" (id_preview_attachement)');
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
}
}

View file

@ -56,7 +56,7 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class SetPasswordCommand extends Command class SetPasswordCommand extends Command
{ {
protected static $defaultName = 'partdb:users:set-password|app:set-password|users:set-password'; protected static $defaultName = 'partdb:users:set-password|app:set-password|users:set-password|partdb:user:set-password';
protected EntityManagerInterface $entityManager; protected EntityManagerInterface $entityManager;
protected UserPasswordHasherInterface $encoder; protected UserPasswordHasherInterface $encoder;
@ -77,7 +77,6 @@ class SetPasswordCommand extends Command
->setDescription('Sets the password of a user') ->setDescription('Sets the password of a user')
->setHelp('This password allows you to set the password of a user, without knowing the old password.') ->setHelp('This password allows you to set the password of a user, without knowing the old password.')
->addArgument('user', InputArgument::REQUIRED, 'The name of the user') ->addArgument('user', InputArgument::REQUIRED, 'The name of the user')
; ;
} }

View file

@ -0,0 +1,92 @@
<?php
namespace App\Command\User;
use App\Entity\UserSystem\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class UserEnableCommand extends Command
{
protected static $defaultName = 'partdb:users:enable|partdb:user:enable';
protected EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager, string $name = null)
{
$this->entityManager = $entityManager;
parent::__construct($name);
}
protected function configure(): void
{
$this
->setDescription('Enables/Disable the login of one or more users')
->setHelp('This allows you to allow or prevent the login of certain user. Use the --disable option to disable the login for the given users')
->addArgument('users', InputArgument::IS_ARRAY, 'The usernames of the users to use')
->addOption('all', 'a', InputOption::VALUE_NONE, 'Enable/Disable all users')
->addOption('disable', 'd', InputOption::VALUE_NONE, 'Disable the login of the given users')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$usernames = $input->getArgument('users');
$all_users = $input->getOption('all');
$disabling = $input->getOption('disable');
if(!$all_users && empty($usernames)) {
$io->error('No users given! You have to pass atleast one username or use the --all option to use all users!');
return self::FAILURE;
}
$repo = $this->entityManager->getRepository(User::class);
$users = [];
if($all_users) { //If we requested to change all users at once, then get all users from repo
$users = $repo->findAll();
} else { //Otherwise, fetch the users from DB
foreach ($usernames as $username) {
$user = $repo->findByEmailOrName($username);
if ($user === null) {
$io->error('No user found with username: '.$username);
return self::FAILURE;
}
$users[] = $user;
}
}
if ($disabling) {
$io->note('The following users will be disabled:');
} else {
$io->note('The following users will be enabled:');
}
$io->table(['Username', 'Enabled/Disabled'],
array_map(function(User $user) {
return [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled'];
}, $users));
if(!$io->confirm('Do you want to continue?')) {
$io->warning('Aborting!');
return self::SUCCESS;
}
foreach ($users as $user) {
$user->setDisabled($disabling);
}
//Save the results
$this->entityManager->flush();
$io->success('Successfully changed the state of the users!');
return self::SUCCESS;
}
}

View file

@ -43,7 +43,7 @@ class UserListCommand extends Command
$io->title('Users:'); $io->title('Users:');
$table = new Table($output); $table = new Table($output);
$table->setHeaders(['ID', 'Username', 'Name', 'Email', 'Group']); $table->setHeaders(['ID', 'Username', 'Name', 'Email', 'Group', 'Login Disabled']);
foreach ($users as $user) { foreach ($users as $user) {
$table->addRow([ $table->addRow([
@ -52,6 +52,7 @@ class UserListCommand extends Command
$user->getFullName(), $user->getFullName(),
$user->getEmail(), $user->getEmail(),
$user->getGroup() !== null ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group', $user->getGroup() !== null ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group',
$user->isDisabled() ? 'Yes' : 'No',
]); ]);
} }

View file

@ -0,0 +1,204 @@
<?php
namespace App\Command\User;
use App\Entity\UserSystem\User;
use App\Repository\UserRepository;
use App\Services\UserSystem\PermissionManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\Translation\TranslatorInterface;
class UsersPermissionsCommand extends Command
{
protected static $defaultName = 'partdb:users:permissions|partdb:user:permissions';
protected static $defaultDescription = 'View and edit the permissions of a given user';
protected EntityManagerInterface $entityManager;
protected UserRepository $userRepository;
protected PermissionManager $permissionResolver;
protected TranslatorInterface $translator;
public function __construct(EntityManagerInterface $entityManager, PermissionManager $permissionResolver, TranslatorInterface $translator)
{
$this->entityManager = $entityManager;
$this->userRepository = $entityManager->getRepository(User::class);
$this->permissionResolver = $permissionResolver;
$this->translator = $translator;
parent::__construct(self::$defaultName);
}
protected function configure(): void
{
$this
->addArgument('user', InputArgument::REQUIRED, 'The username of the user to view')
->addOption('noInherit', null, InputOption::VALUE_NONE, 'Do not inherit permissions from groups')
->addOption('edit', '', InputOption::VALUE_NONE, 'Edit the permissions of the user')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$username = $input->getArgument('user');
$edit_mode = $input->getOption('edit');
$inherit = !$input->getOption('noInherit') && !$edit_mode; //Show the non inherited perms in edit mode
//Find user
$io->note('Finding user with username: ' . $username);
$user = $this->userRepository->findByEmailOrName($username);
if ($user === null) {
$io->error('No user found with username: ' . $username);
return Command::FAILURE;
}
$io->note(sprintf('Found user %s with ID %d', $user->getFullName(true), $user->getId()));
$edit_mapping = $this->renderPermissionTable($output, $user, $inherit);
while($edit_mode) {
$index_to_edit = $io->ask('Which permission do you want to edit? Enter the index (e.g. 2-4) to edit, * for all permissions or "q" to quit', 'q');
if ($index_to_edit === 'q') {
break;
}
if (!isset($edit_mapping[$index_to_edit]) && $index_to_edit !== '*') {
$io->error('Invalid index');
continue;
}
if ($index_to_edit === '*') {
$io->warning('You are about to edit all permissions. This will overwrite all permissions!');
} else {
[$perm_to_edit, $op_to_edit] = $edit_mapping[$index_to_edit];
$io->note('Editing permission ' . $perm_to_edit . ' with operation <options=bold>' . $op_to_edit);
}
$new_value_str = $io->ask('Enter the new value for the permission (A = allow, D = disallow, I = inherit)');
switch (strtolower($new_value_str)) {
case 'a':
case 'allow':
$new_value = true;
break;
case 'd':
case 'disallow':
$new_value = false;
break;
case 'i':
case 'inherit':
$new_value = null;
break;
default:
$io->error('Invalid value');
continue 2;
}
if ($index_to_edit === '*') {
$this->permissionResolver->setAllPermissions($user, $new_value);
$io->success('Permission updated successfully');
$this->entityManager->flush();
break; //Show the new table
}
if (isset($op_to_edit, $perm_to_edit)) {
$this->permissionResolver->setPermission($user, $perm_to_edit, $op_to_edit, $new_value);
} else {
throw new \RuntimeException('Erorr while editing permission');
}
//Ensure that all operations are set accordingly
$this->permissionResolver->ensureCorrectSetOperations($user);
$io->success('Permission updated successfully');
//Save to DB
$this->entityManager->flush();
}
if ($edit_mode) {
$io->note('New permissions:');
$this->renderPermissionTable($output, $user, true);
}
return Command::SUCCESS;
}
protected function renderPermissionTable(OutputInterface $output, User $user, bool $inherit): array
{
//We fill this with index and perm/op combination for later use
$edit_mapping = [];
$table = new Table($output);
$perms = $this->permissionResolver->getPermissionStructure()['perms'];
if ($inherit) {
$table->setHeaderTitle('Inherited Permissions for '.$user->getFullName(true));
} else {
$table->setHeaderTitle('Non Inherited Permissions for '.$user->getFullName(true));
}
$table->setHeaders(['', 'Permission', 'Operation', 'Value']);
$perm_index = '1';
foreach ($perms as $perm_name => $perm_obj) {
$op_index = 1;
foreach ($perm_obj['operations'] as $operation_name => $operation_obj) {
$index = sprintf('%d-%d', $perm_index, $op_index);
$table->addRow([
$index,
$this->translator->trans($perm_obj['label']), //Permission name
$this->translator->trans($operation_obj['label']), //Operation name
$this->getPermissionValue($user, $perm_name, $operation_name, $inherit),
]);
//Save index and perm/op combination for editing later
$edit_mapping[$index] = [
$perm_name, $operation_name,
];
$op_index++;
}
$table->addRow(new TableSeparator());
$perm_index++;
}
$table->render();
return $edit_mapping;
}
protected function getPermissionValue(User $user, string $permission, string $op, bool $inherit = true): string
{
if ($inherit) {
$permission_value = $this->permissionResolver->inherit($user, $permission, $op);
} else {
$permission_value = $this->permissionResolver->dontInherit($user, $permission, $op);
}
if ($permission_value === true) {
return '<fg=green>Allow</>';
} else if ($permission_value === false) {
return '<fg=red>Disallow</>';
} else if ($permission_value === null && !$inherit) {
return '<fg=blue>Inherit</>';
} else if ($permission_value === null && $inherit) {
return '<fg=red>Disallow (Inherited)</>';
}
return '???';
}
}

View file

@ -143,7 +143,6 @@ abstract class BaseAdminController extends AbstractController
protected function revertElementIfNeeded(AbstractDBElement $entity, ?string $timestamp): ?DateTime protected function revertElementIfNeeded(AbstractDBElement $entity, ?string $timestamp): ?DateTime
{ {
if (null !== $timestamp) { if (null !== $timestamp) {
$this->denyAccessUnlessGranted('@tools.timetravel');
$this->denyAccessUnlessGranted('show_history', $entity); $this->denyAccessUnlessGranted('show_history', $entity);
//If the timestamp only contains numbers interpret it as unix timestamp //If the timestamp only contains numbers interpret it as unix timestamp
if (ctype_digit($timestamp)) { if (ctype_digit($timestamp)) {

View file

@ -131,7 +131,7 @@ class AttachmentFileController extends AbstractController
*/ */
public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder) public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder)
{ {
$this->denyAccessUnlessGranted('read', new PartAttachment()); $this->denyAccessUnlessGranted('@attachments.list_attachments');
$formRequest = clone $request; $formRequest = clone $request;
$formRequest->setMethod('GET'); $formRequest->setMethod('GET');

View file

@ -51,6 +51,7 @@ use App\Form\AdminPages\GroupAdminForm;
use App\Services\EntityExporter; use App\Services\EntityExporter;
use App\Services\EntityImporter; use App\Services\EntityImporter;
use App\Services\StructuralElementRecursionHelper; use App\Services\StructuralElementRecursionHelper;
use App\Services\UserSystem\PermissionPresetsHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -73,8 +74,27 @@ class GroupController extends BaseAdminController
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="group_edit") * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="group_edit")
* @Route("/{id}/", requirements={"id"="\d+"}) * @Route("/{id}/", requirements={"id"="\d+"})
*/ */
public function edit(Group $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response public function edit(Group $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, ?string $timestamp = null): Response
{ {
//Handle permissions presets
if ($request->request->has('permission_preset')) {
$this->denyAccessUnlessGranted('edit_permissions', $entity);
if ($this->isCsrfTokenValid('group'.$entity->getId(), $request->request->get('_token'))) {
$preset = $request->request->get('permission_preset');
$permissionPresetsHelper->applyPreset($entity, $preset);
$em->flush();
$this->addFlash('success', 'user.edit.permission_success');
//We need to stop the execution here, or our permissions changes will be overwritten by the form values
return $this->redirectToRoute('group_edit', ['id' => $entity->getID()]);
} else {
$this->addFlash('danger', 'csfr_invalid');
}
}
return $this->_edit($entity, $request, $em, $timestamp); return $this->_edit($entity, $request, $em, $timestamp);
} }

View file

@ -102,7 +102,6 @@ class PartController extends AbstractController
$timeTravel_timestamp = null; $timeTravel_timestamp = null;
if (null !== $timestamp) { if (null !== $timestamp) {
$this->denyAccessUnlessGranted('@tools.timetravel');
$this->denyAccessUnlessGranted('show_history', $part); $this->denyAccessUnlessGranted('show_history', $part);
//If the timestamp only contains numbers interpret it as unix timestamp //If the timestamp only contains numbers interpret it as unix timestamp
if (ctype_digit($timestamp)) { if (ctype_digit($timestamp)) {

View file

@ -175,6 +175,8 @@ class PartListsController extends AbstractController
*/ */
public function showCategory(Category $category, Request $request) public function showCategory(Category $category, Request $request)
{ {
$this->denyAccessUnlessGranted('@categories.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/category_list.html.twig', 'Parts/lists/category_list.html.twig',
function (PartFilter $filter) use ($category) { function (PartFilter $filter) use ($category) {
@ -195,6 +197,8 @@ class PartListsController extends AbstractController
*/ */
public function showFootprint(Footprint $footprint, Request $request) public function showFootprint(Footprint $footprint, Request $request)
{ {
$this->denyAccessUnlessGranted('@footprints.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/footprint_list.html.twig', 'Parts/lists/footprint_list.html.twig',
function (PartFilter $filter) use ($footprint) { function (PartFilter $filter) use ($footprint) {
@ -215,6 +219,8 @@ class PartListsController extends AbstractController
*/ */
public function showManufacturer(Manufacturer $manufacturer, Request $request) public function showManufacturer(Manufacturer $manufacturer, Request $request)
{ {
$this->denyAccessUnlessGranted('@manufacturers.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/manufacturer_list.html.twig', 'Parts/lists/manufacturer_list.html.twig',
function (PartFilter $filter) use ($manufacturer) { function (PartFilter $filter) use ($manufacturer) {
@ -235,6 +241,8 @@ class PartListsController extends AbstractController
*/ */
public function showStorelocation(Storelocation $storelocation, Request $request) public function showStorelocation(Storelocation $storelocation, Request $request)
{ {
$this->denyAccessUnlessGranted('@storelocations.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/store_location_list.html.twig', 'Parts/lists/store_location_list.html.twig',
function (PartFilter $filter) use ($storelocation) { function (PartFilter $filter) use ($storelocation) {
@ -255,6 +263,8 @@ class PartListsController extends AbstractController
*/ */
public function showSupplier(Supplier $supplier, Request $request) public function showSupplier(Supplier $supplier, Request $request)
{ {
$this->denyAccessUnlessGranted('@suppliers.read');
return $this->showListWithFilter($request, return $this->showListWithFilter($request,
'Parts/lists/supplier_list.html.twig', 'Parts/lists/supplier_list.html.twig',
function (PartFilter $filter) use ($supplier) { function (PartFilter $filter) use ($supplier) {

View file

@ -45,7 +45,7 @@ namespace App\Controller;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Events\SecurityEvent; use App\Events\SecurityEvent;
use App\Events\SecurityEvents; use App\Events\SecurityEvents;
use App\Services\PasswordResetManager; use App\Services\UserSystem\PasswordResetManager;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Gregwar\CaptchaBundle\Type\CaptchaType; use Gregwar\CaptchaBundle\Type\CaptchaType;
use RuntimeException; use RuntimeException;

View file

@ -33,6 +33,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* @Route("/select_api") * @Route("/select_api")
*
* This endpoint is used by the select2 library to dynamically load data (used in the multiselect action helper in parts lists)
*/ */
class SelectAPIController extends AbstractController class SelectAPIController extends AbstractController
{ {

View file

@ -2,6 +2,9 @@
namespace App\Controller; namespace App\Controller;
use App\Services\GitVersionInfo;
use App\Services\Misc\DBInfoHelper;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
@ -20,4 +23,47 @@ class ToolsController extends AbstractController
return $this->render('Tools/ReelCalculator/main.html.twig'); return $this->render('Tools/ReelCalculator/main.html.twig');
} }
/**
* @Route("/server_infos", name="tools_server_infos")
*/
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper): Response
{
$this->denyAccessUnlessGranted('@system.server_infos');
return $this->render('Tools/ServerInfos/main.html.twig', [
//Part-DB section
'git_branch' => $versionInfo->getGitBranchName(),
'git_commit' => $versionInfo->getGitCommitHash(),
'default_locale' => $this->getParameter('partdb.locale'),
'default_timezone' => $this->getParameter('partdb.timezone'),
'default_currency' => $this->getParameter('partdb.default_currency'),
'default_theme' => $this->getParameter('partdb.global_theme'),
'enabled_locales' => $this->getParameter('partdb.locale_menu'),
'demo_mode' => $this->getParameter('partdb.demo_mode'),
'gpdr_compliance' => $this->getParameter('partdb.gpdr_compliance'),
'use_gravatar' => $this->getParameter('partdb.users.use_gravatar'),
'email_password_reset' => $this->getParameter('partdb.users.email_pw_reset'),
'enviroment' => $this->getParameter('kernel.environment'),
'is_debug' => $this->getParameter('kernel.debug'),
'email_sender' => $this->getParameter('partdb.mail.sender_email'),
'email_sender_name' => $this->getParameter('partdb.mail.sender_name'),
'allow_attachments_downloads' => $this->getParameter('partdb.attachments.allow_downloads'),
'detailed_error_pages' => $this->getParameter('partdb.error_pages.show_help'),
'error_page_admin_email' => $this->getParameter('partdb.error_pages.admin_email'),
//PHP section
'php_version' => PHP_VERSION,
'php_uname' => php_uname('a'),
'php_sapi' => PHP_SAPI,
'php_extensions' => array_merge(get_loaded_extensions()),
'php_opcache_enabled' => ini_get('opcache.enable'),
'php_upload_max_filesize' => ini_get('upload_max_filesize'),
'php_post_max_size' => ini_get('post_max_size'),
//DB section
'db_type' => $DBInfoHelper->getDatabaseType() ?? 'Unknown',
'db_version' => $DBInfoHelper->getDatabaseVersion() ?? 'Unknown',
]);
}
} }

View file

@ -84,7 +84,11 @@ class TreeController extends AbstractController
*/ */
public function categoryTree(?Category $category = null): JsonResponse public function categoryTree(?Category $category = null): JsonResponse
{ {
$tree = $this->treeGenerator->getTreeView(Category::class, $category, 'list_parts_root'); if ($this->isGranted('@parts.read') && $this->isGranted('@categories.read')) {
$tree = $this->treeGenerator->getTreeView(Category::class, $category, 'list_parts_root');
} else {
return new JsonResponse("Access denied", 403);
}
return new JsonResponse($tree); return new JsonResponse($tree);
} }
@ -95,8 +99,11 @@ class TreeController extends AbstractController
*/ */
public function footprintTree(?Footprint $footprint = null): JsonResponse public function footprintTree(?Footprint $footprint = null): JsonResponse
{ {
$tree = $this->treeGenerator->getTreeView(Footprint::class, $footprint, 'list_parts_root'); if ($this->isGranted('@parts.read') && $this->isGranted('@footprints.read')) {
$tree = $this->treeGenerator->getTreeView(Footprint::class, $footprint, 'list_parts_root');
} else {
return new JsonResponse("Access denied", 403);
}
return new JsonResponse($tree); return new JsonResponse($tree);
} }
@ -106,7 +113,11 @@ class TreeController extends AbstractController
*/ */
public function locationTree(?Storelocation $location = null): JsonResponse public function locationTree(?Storelocation $location = null): JsonResponse
{ {
$tree = $this->treeGenerator->getTreeView(Storelocation::class, $location, 'list_parts_root'); if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) {
$tree = $this->treeGenerator->getTreeView(Storelocation::class, $location, 'list_parts_root');
} else {
return new JsonResponse("Access denied", 403);
}
return new JsonResponse($tree); return new JsonResponse($tree);
} }
@ -117,7 +128,11 @@ class TreeController extends AbstractController
*/ */
public function manufacturerTree(?Manufacturer $manufacturer = null): JsonResponse public function manufacturerTree(?Manufacturer $manufacturer = null): JsonResponse
{ {
$tree = $this->treeGenerator->getTreeView(Manufacturer::class, $manufacturer, 'list_parts_root'); if ($this->isGranted('@parts.read') && $this->isGranted('@manufacturers.read')) {
$tree = $this->treeGenerator->getTreeView(Manufacturer::class, $manufacturer, 'list_parts_root');
} else {
return new JsonResponse("Access denied", 403);
}
return new JsonResponse($tree); return new JsonResponse($tree);
} }
@ -128,7 +143,11 @@ class TreeController extends AbstractController
*/ */
public function supplierTree(?Supplier $supplier = null): JsonResponse public function supplierTree(?Supplier $supplier = null): JsonResponse
{ {
$tree = $this->treeGenerator->getTreeView(Supplier::class, $supplier, 'list_parts_root'); if ($this->isGranted('@parts.read') && $this->isGranted('@suppliers.read')) {
$tree = $this->treeGenerator->getTreeView(Supplier::class, $supplier, 'list_parts_root');
} else {
return new JsonResponse("Access denied", 403);
}
return new JsonResponse($tree); return new JsonResponse($tree);
} }
@ -139,7 +158,11 @@ class TreeController extends AbstractController
*/ */
public function deviceTree(?Device $device = null): JsonResponse public function deviceTree(?Device $device = null): JsonResponse
{ {
$tree = $this->treeGenerator->getTreeView(Device::class, $device, 'devices'); if ($this->isGranted('@devices.read')) {
$tree = $this->treeGenerator->getTreeView(Device::class, $device, 'devices');
} else {
return new JsonResponse("Access denied", 403);
}
return new JsonResponse($tree); return new JsonResponse($tree);
} }

View file

@ -156,6 +156,11 @@ class TypeaheadController extends AbstractController
public function parameters(string $type, EntityManagerInterface $entityManager, string $query = ""): JsonResponse public function parameters(string $type, EntityManagerInterface $entityManager, string $query = ""): JsonResponse
{ {
$class = $this->typeToParameterClass($type); $class = $this->typeToParameterClass($type);
$test_obj = new $class();
//Ensure user has the correct permissions
$this->denyAccessUnlessGranted('read', $test_obj);
/** @var ParameterRepository $repository */ /** @var ParameterRepository $repository */
$repository = $entityManager->getRepository($class); $repository = $entityManager->getRepository($class);
@ -169,6 +174,8 @@ class TypeaheadController extends AbstractController
*/ */
public function tags(string $query, TagFinder $finder): JsonResponse public function tags(string $query, TagFinder $finder): JsonResponse
{ {
$this->denyAccessUnlessGranted('@parts.read');
$array = $finder->searchTags($query); $array = $finder->searchTags($query);
$normalizers = [ $normalizers = [

View file

@ -54,6 +54,7 @@ use App\Form\UserAdminForm;
use App\Services\EntityExporter; use App\Services\EntityExporter;
use App\Services\EntityImporter; use App\Services\EntityImporter;
use App\Services\StructuralElementRecursionHelper; use App\Services\StructuralElementRecursionHelper;
use App\Services\UserSystem\PermissionPresetsHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Exception; use Exception;
use InvalidArgumentException; use InvalidArgumentException;
@ -101,7 +102,7 @@ class UserController extends AdminPages\BaseAdminController
* *
* @throws Exception * @throws Exception
*/ */
public function edit(User $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, ?string $timestamp = null): Response
{ {
//Handle 2FA disabling //Handle 2FA disabling
@ -132,6 +133,25 @@ class UserController extends AdminPages\BaseAdminController
} }
} }
//Handle permissions presets
if ($request->request->has('permission_preset')) {
$this->denyAccessUnlessGranted('edit_permissions', $entity);
if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) {
$preset = $request->request->get('permission_preset');
$permissionPresetsHelper->applyPreset($entity, $preset);
$em->flush();
$this->addFlash('success', 'user.edit.permission_success');
//We need to stop the execution here, or our permissions changes will be overwritten by the form values
return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]);
} else {
$this->addFlash('danger', 'csfr_invalid');
}
}
return $this->_edit($entity, $request, $em, $timestamp); return $this->_edit($entity, $request, $em, $timestamp);
} }

View file

@ -49,7 +49,7 @@ use App\Events\SecurityEvent;
use App\Events\SecurityEvents; use App\Events\SecurityEvents;
use App\Form\TFAGoogleSettingsType; use App\Form\TFAGoogleSettingsType;
use App\Form\UserSettingsType; use App\Form\UserSettingsType;
use App\Services\TFA\BackupCodeManager; use App\Services\UserSystem\TFA\BackupCodeManager;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use RuntimeException; use RuntimeException;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;

View file

@ -18,6 +18,8 @@ class WebauthnKeyRegistrationController extends AbstractController
*/ */
public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper, EntityManagerInterface $em) public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper, EntityManagerInterface $em)
{ {
//When user change its settings, he should be logged in fully.
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
//If form was submitted, check the auth response //If form was submitted, check the auth response
if ($request->getMethod() === 'POST') { if ($request->getMethod() === 'POST') {
@ -51,7 +53,7 @@ class WebauthnKeyRegistrationController extends AbstractController
return $this->render( return $this->render(
'Security/Webauthn/webauthn_register.html.twig', 'security/Webauthn/webauthn_register.html.twig',
[ [
'registrationRequest' => $registrationHelper->generateRegistrationRequestAsJSON(), 'registrationRequest' => $registrationHelper->generateRegistrationRequestAsJSON(),
] ]

View file

@ -43,6 +43,8 @@ declare(strict_types=1);
namespace App\DataFixtures; namespace App\DataFixtures;
use App\Entity\UserSystem\Group; use App\Entity\UserSystem\Group;
use App\Services\UserSystem\PermissionManager;
use App\Services\UserSystem\PermissionPresetsHelper;
use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
@ -52,138 +54,45 @@ class GroupFixtures extends Fixture
public const USERS = 'group-users'; public const USERS = 'group-users';
public const READONLY = 'group-readonly'; public const READONLY = 'group-readonly';
private PermissionPresetsHelper $permission_presets;
private PermissionManager $permissionManager;
public function __construct(PermissionPresetsHelper $permissionPresetsHelper, PermissionManager $permissionManager)
{
$this->permission_presets = $permissionPresetsHelper;
$this->permissionManager = $permissionManager;
}
public function load(ObjectManager $manager): void public function load(ObjectManager $manager): void
{ {
$admins = new Group(); $admins = new Group();
$admins->setName('admins'); $admins->setName('admins');
//Perm values taken from Version 1 //Set permissions using preset
$admins->getPermissions()->setRawPermissionValues([ $this->permission_presets->applyPreset($admins, PermissionPresetsHelper::PRESET_ALL_ALLOW);
'system' => 21, $this->addDevicesPermissions($admins);
'groups' => 1365,
'users' => 87381,
'self' => 85,
'config' => 85,
'database' => 21,
'parts' => 1431655765,
'parts_name' => 5,
'parts_description' => 5,
'parts_footprint' => 5,
'parts_manufacturer' => 5,
'parts_comment' => 5,
'parts_order' => 5,
'parts_orderdetails' => 341,
'parts_prices' => 341,
'parts_attachments' => 341,
'devices' => 5461,
'devices_parts' => 325,
'storelocations' => 5461,
'footprints' => 5461,
'categories' => 5461,
'suppliers' => 5461,
'manufacturers' => 5461,
'attachment_types' => 1365,
'tools' => 349525,
'labels' => 87381,
'parts_category' => 5,
'parts_minamount' => 5,
'parts_lots' => 85,
'parts_tags' => 5,
'parts_unit' => 5,
'parts_mass' => 5,
'parts_status' => 5,
'parts_mpn' => 5,
'currencies' => 5461,
'measurement_units' => 5461,
]);
$this->setReference(self::ADMINS, $admins); $this->setReference(self::ADMINS, $admins);
$manager->persist($admins); $manager->persist($admins);
$readonly = new Group(); $readonly = new Group();
$readonly->setName('readonly'); $readonly->setName('readonly');
$readonly->getPermissions()->setRawPermissionValues([ $this->permission_presets->applyPreset($readonly, PermissionPresetsHelper::PRESET_READ_ONLY);
'system' => 2,
'groups' => 2730,
'users' => 43690,
'self' => 25,
'config' => 170,
'database' => 42,
'parts' => 2778027689,
'parts_name' => 9,
'parts_description' => 9,
'parts_footprint' => 9,
'parts_manufacturer' => 9,
'parts_comment' => 9,
'parts_order' => 9,
'parts_orderdetails' => 681,
'parts_prices' => 681,
'parts_attachments' => 681,
'devices' => 1705,
'devices_parts' => 649,
'storelocations' => 1705,
'footprints' => 1705,
'categories' => 1705,
'suppliers' => 1705,
'manufacturers' => 1705,
'attachment_types' => 681,
'tools' => 87382,
'labels' => 173737,
'parts_category' => 9,
'parts_minamount' => 9,
'parts_lots' => 169,
'parts_tags' => 9,
'parts_unit' => 9,
'parts_mass' => 9,
'parts_status' => 9,
'parts_mpn' => 9,
'currencies' => 9897,
'measurement_units' => 9897,
]);
$this->setReference(self::READONLY, $readonly); $this->setReference(self::READONLY, $readonly);
$manager->persist($readonly); $manager->persist($readonly);
$users = new Group(); $users = new Group();
$users->setName('users'); $users->setName('users');
$users->getPermissions()->setRawPermissionValues([ $this->permission_presets->applyPreset($users, PermissionPresetsHelper::PRESET_EDITOR);
'system' => 42, $this->addDevicesPermissions($users);
'groups' => 2730,
'users' => 43690,
'self' => 89,
'config' => 105,
'database' => 41,
'parts' => 1431655765,
'parts_name' => 5,
'parts_description' => 5,
'parts_footprint' => 5,
'parts_manufacturer' => 5,
'parts_comment' => 5,
'parts_order' => 5,
'parts_orderdetails' => 341,
'parts_prices' => 341,
'parts_attachments' => 341,
'devices' => 5461,
'devices_parts' => 325,
'storelocations' => 5461,
'footprints' => 5461,
'categories' => 5461,
'suppliers' => 5461,
'manufacturers' => 5461,
'attachment_types' => 1365,
'tools' => 87381,
'labels' => 91477,
'parts_category' => 5,
'parts_minamount' => 5,
'parts_lots' => 85,
'parts_tags' => 5,
'parts_unit' => 5,
'parts_mass' => 5,
'parts_status' => 5,
'parts_mpn' => 5,
'currencies' => 5461,
'measurement_units' => 5461,
]);
$this->setReference(self::USERS, $users); $this->setReference(self::USERS, $users);
$manager->persist($users); $manager->persist($users);
$manager->flush(); $manager->flush();
} }
private function addDevicesPermissions(Group $group): void
{
$this->permissionManager->setAllOperationsOfPermission($group, 'devices', true);
}
} }

View file

@ -260,9 +260,7 @@ class LogDataTable implements DataTableTypeInterface
return null; return null;
}, },
'disabled' => function ($value, AbstractLogEntry $context) { 'disabled' => function ($value, AbstractLogEntry $context) {
return return !$this->security->isGranted('show_history', $context->getTargetClass());
!$this->security->isGranted('@tools.timetravel')
|| !$this->security->isGranted('show_history', $context->getTargetClass());
}, },
]); ]);

View file

@ -163,20 +163,29 @@ final class PartsDataTable implements DataTableTypeInterface
]) ])
->add('description', MarkdownColumn::class, [ ->add('description', MarkdownColumn::class, [
'label' => $this->translator->trans('part.table.description'), 'label' => $this->translator->trans('part.table.description'),
]) ]);
->add('category', EntityColumn::class, [
if ($this->security->isGranted('@categories.read')) {
$dataTable->add('category', EntityColumn::class, [
'label' => $this->translator->trans('part.table.category'), 'label' => $this->translator->trans('part.table.category'),
'property' => 'category', 'property' => 'category',
]) ]);
->add('footprint', EntityColumn::class, [ }
if ($this->security->isGranted('@footprints.read')) {
$dataTable->add('footprint', EntityColumn::class, [
'property' => 'footprint', 'property' => 'footprint',
'label' => $this->translator->trans('part.table.footprint'), 'label' => $this->translator->trans('part.table.footprint'),
]) ]);
->add('manufacturer', EntityColumn::class, [ }
if ($this->security->isGranted('@manufacturers.read')) {
$dataTable->add('manufacturer', EntityColumn::class, [
'property' => 'manufacturer', 'property' => 'manufacturer',
'label' => $this->translator->trans('part.table.manufacturer'), 'label' => $this->translator->trans('part.table.manufacturer'),
]) ]);
->add('storelocation', TextColumn::class, [ }
if ($this->security->isGranted('@storelocations.read')) {
$dataTable->add('storelocation', TextColumn::class, [
'label' => $this->translator->trans('part.table.storeLocations'), 'label' => $this->translator->trans('part.table.storeLocations'),
'render' => function ($value, Part $context) { 'render' => function ($value, Part $context) {
$tmp = []; $tmp = [];
@ -194,32 +203,38 @@ final class PartsDataTable implements DataTableTypeInterface
return implode('<br>', $tmp); return implode('<br>', $tmp);
}, },
]) ]);
->add('amount', TextColumn::class, [ }
'label' => $this->translator->trans('part.table.amount'),
'render' => function ($value, Part $context) {
$amount = $context->getAmountSum();
return $this->amountFormatter->format($amount, $context->getPartUnit()); $dataTable->add('amount', TextColumn::class, [
}, 'label' => $this->translator->trans('part.table.amount'),
'orderField' => 'amountSum' 'render' => function ($value, Part $context) {
]) $amount = $context->getAmountSum();
return $this->amountFormatter->format($amount, $context->getPartUnit());
},
'orderField' => 'amountSum'
])
->add('minamount', TextColumn::class, [ ->add('minamount', TextColumn::class, [
'label' => $this->translator->trans('part.table.minamount'), 'label' => $this->translator->trans('part.table.minamount'),
'visible' => false, 'visible' => false,
'render' => function ($value, Part $context) { 'render' => function ($value, Part $context) {
return $this->amountFormatter->format($value, $context->getPartUnit()); return $this->amountFormatter->format($value, $context->getPartUnit());
}, },
]) ]);
->add('partUnit', TextColumn::class, [
if ($this->security->isGranted('@footprints.read')) {
$dataTable->add('partUnit', TextColumn::class, [
'field' => 'partUnit.name', 'field' => 'partUnit.name',
'label' => $this->translator->trans('part.table.partUnit'), 'label' => $this->translator->trans('part.table.partUnit'),
'visible' => false, 'visible' => false,
]) ]);
->add('addedDate', LocaleDateTimeColumn::class, [ }
'label' => $this->translator->trans('part.table.addedDate'),
'visible' => false, $dataTable->add('addedDate', LocaleDateTimeColumn::class, [
]) 'label' => $this->translator->trans('part.table.addedDate'),
'visible' => false,
])
->add('lastModified', LocaleDateTimeColumn::class, [ ->add('lastModified', LocaleDateTimeColumn::class, [
'label' => $this->translator->trans('part.table.lastModified'), 'label' => $this->translator->trans('part.table.lastModified'),
'visible' => false, 'visible' => false,

View file

@ -37,8 +37,6 @@ use Symfony\Component\Serializer\Annotation\Groups;
* *
* @ORM\MappedSuperclass(repositoryClass="App\Repository\DBElementRepository") * @ORM\MappedSuperclass(repositoryClass="App\Repository\DBElementRepository")
* *
* @ORM\EntityListeners({"App\Security\EntityListeners\ElementPermissionListener"})
*
* @DiscriminatorMap(typeProperty="type", mapping={ * @DiscriminatorMap(typeProperty="type", mapping={
* "attachment_type" = "App\Entity\AttachmentType", * "attachment_type" = "App\Entity\AttachmentType",
* "attachment" = "App\Entity\Attachment", * "attachment" = "App\Entity\Attachment",

View file

@ -45,7 +45,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
* *
* @ORM\MappedSuperclass(repositoryClass="App\Repository\StructuralDBElementRepository") * @ORM\MappedSuperclass(repositoryClass="App\Repository\StructuralDBElementRepository")
* *
* @ORM\EntityListeners({"App\Security\EntityListeners\ElementPermissionListener", "App\EntityListeners\TreeCacheInvalidationListener"}) * @ORM\EntityListeners({"App\EntityListeners\TreeCacheInvalidationListener"})
* *
* @UniqueEntity(fields={"name", "parent"}, ignoreNull=false, message="structural.entity.unique_name") * @UniqueEntity(fields={"name", "parent"}, ignoreNull=false, message="structural.entity.unique_name")
*/ */

View file

@ -61,7 +61,6 @@ use App\Entity\Parts\PartTraits\BasicPropertyTrait;
use App\Entity\Parts\PartTraits\InstockTrait; use App\Entity\Parts\PartTraits\InstockTrait;
use App\Entity\Parts\PartTraits\ManufacturerTrait; use App\Entity\Parts\PartTraits\ManufacturerTrait;
use App\Entity\Parts\PartTraits\OrderTrait; use App\Entity\Parts\PartTraits\OrderTrait;
use App\Security\Annotations\ColumnSecurity;
use DateTime; use DateTime;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
@ -103,7 +102,6 @@ class Part extends AttachmentContainingDBElement
protected $parameters; protected $parameters;
/** /**
* @ColumnSecurity(type="datetime")
* @ORM\Column(type="datetime", name="datetime_added", options={"default"="CURRENT_TIMESTAMP"}) * @ORM\Column(type="datetime", name="datetime_added", options={"default"="CURRENT_TIMESTAMP"})
*/ */
protected ?DateTime $addedDate = null; protected ?DateTime $addedDate = null;
@ -116,14 +114,12 @@ class Part extends AttachmentContainingDBElement
/** /**
* @var string The name of this part * @var string The name of this part
* @ORM\Column(type="string") * @ORM\Column(type="string")
* @ColumnSecurity(prefix="name")
*/ */
protected string $name = ''; protected string $name = '';
/** /**
* @var Collection<int, PartAttachment> * @var Collection<int, PartAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\PartAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\PartAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ColumnSecurity(type="collection", prefix="attachments")
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
@ -131,7 +127,6 @@ class Part extends AttachmentContainingDBElement
/** /**
* @var DateTime the date when this element was modified the last time * @var DateTime the date when this element was modified the last time
* @ColumnSecurity(type="datetime")
* @ORM\Column(type="datetime", name="last_modified", options={"default"="CURRENT_TIMESTAMP"}) * @ORM\Column(type="datetime", name="last_modified", options={"default"="CURRENT_TIMESTAMP"})
*/ */
protected ?DateTime $lastModified = null; protected ?DateTime $lastModified = null;

View file

@ -43,7 +43,6 @@ declare(strict_types=1);
namespace App\Entity\Parts\PartTraits; namespace App\Entity\Parts\PartTraits;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Security\Annotations\ColumnSecurity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@ -55,21 +54,18 @@ trait AdvancedPropertyTrait
/** /**
* @var bool Determines if this part entry needs review (for example, because it is work in progress) * @var bool Determines if this part entry needs review (for example, because it is work in progress)
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
* @ColumnSecurity(type="boolean")
*/ */
protected bool $needs_review = false; protected bool $needs_review = false;
/** /**
* @var string a comma separated list of tags, associated with the part * @var string a comma separated list of tags, associated with the part
* @ORM\Column(type="text") * @ORM\Column(type="text")
* @ColumnSecurity(type="string", prefix="tags", placeholder="")
*/ */
protected string $tags = ''; protected string $tags = '';
/** /**
* @var float|null how much a single part unit weighs in grams * @var float|null how much a single part unit weighs in grams
* @ORM\Column(type="float", nullable=true) * @ORM\Column(type="float", nullable=true)
* @ColumnSecurity(type="float", placeholder=null)
* @Assert\PositiveOrZero() * @Assert\PositiveOrZero()
*/ */
protected ?float $mass = null; protected ?float $mass = null;

View file

@ -44,7 +44,6 @@ namespace App\Entity\Parts\PartTraits;
use App\Entity\Parts\Category; use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint; use App\Entity\Parts\Footprint;
use App\Security\Annotations\ColumnSecurity;
use App\Validator\Constraints\Selectable; use App\Validator\Constraints\Selectable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@ -54,14 +53,12 @@ trait BasicPropertyTrait
/** /**
* @var string A text describing what this part does * @var string A text describing what this part does
* @ORM\Column(type="text") * @ORM\Column(type="text")
* @ColumnSecurity(prefix="description")
*/ */
protected string $description = ''; protected string $description = '';
/** /**
* @var string A comment/note related to this part * @var string A comment/note related to this part
* @ORM\Column(type="text") * @ORM\Column(type="text")
* @ColumnSecurity(prefix="comment")
*/ */
protected string $comment = ''; protected string $comment = '';
@ -74,7 +71,6 @@ trait BasicPropertyTrait
/** /**
* @var bool true, if the part is marked as favorite * @var bool true, if the part is marked as favorite
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
* @ColumnSecurity(type="boolean")
*/ */
protected bool $favorite = false; protected bool $favorite = false;
@ -83,7 +79,6 @@ trait BasicPropertyTrait
* Every part must have a category. * Every part must have a category.
* @ORM\ManyToOne(targetEntity="Category") * @ORM\ManyToOne(targetEntity="Category")
* @ORM\JoinColumn(name="id_category", referencedColumnName="id", nullable=false) * @ORM\JoinColumn(name="id_category", referencedColumnName="id", nullable=false)
* @ColumnSecurity(prefix="category", type="App\Entity\Parts\Category")
* @Selectable() * @Selectable()
* @Assert\NotNull(message="validator.select_valid_category") * @Assert\NotNull(message="validator.select_valid_category")
*/ */
@ -93,7 +88,6 @@ trait BasicPropertyTrait
* @var Footprint|null The footprint of this part (e.g. DIP8) * @var Footprint|null The footprint of this part (e.g. DIP8)
* @ORM\ManyToOne(targetEntity="Footprint") * @ORM\ManyToOne(targetEntity="Footprint")
* @ORM\JoinColumn(name="id_footprint", referencedColumnName="id") * @ORM\JoinColumn(name="id_footprint", referencedColumnName="id")
* @ColumnSecurity(prefix="footprint", type="App\Entity\Parts\Footprint")
* @Selectable() * @Selectable()
*/ */
protected ?Footprint $footprint = null; protected ?Footprint $footprint = null;

View file

@ -44,7 +44,6 @@ namespace App\Entity\Parts\PartTraits;
use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\PartLot; use App\Entity\Parts\PartLot;
use App\Security\Annotations\ColumnSecurity;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@ -58,7 +57,6 @@ trait InstockTrait
* @var Collection|PartLot[] A list of part lots where this part is stored * @var Collection|PartLot[] A list of part lots where this part is stored
* @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true)
* @Assert\Valid() * @Assert\Valid()
* @ColumnSecurity(type="collection", prefix="lots")
* @ORM\OrderBy({"amount" = "DESC"}) * @ORM\OrderBy({"amount" = "DESC"})
*/ */
protected $partLots; protected $partLots;
@ -68,7 +66,6 @@ trait InstockTrait
* Given in the partUnit. * Given in the partUnit.
* @ORM\Column(type="float") * @ORM\Column(type="float")
* @Assert\PositiveOrZero() * @Assert\PositiveOrZero()
* @ColumnSecurity(prefix="minamount", type="integer")
*/ */
protected float $minamount = 0; protected float $minamount = 0;
@ -76,7 +73,6 @@ trait InstockTrait
* @var ?MeasurementUnit the unit in which the part's amount is measured * @var ?MeasurementUnit the unit in which the part's amount is measured
* @ORM\ManyToOne(targetEntity="MeasurementUnit") * @ORM\ManyToOne(targetEntity="MeasurementUnit")
* @ORM\JoinColumn(name="id_part_unit", referencedColumnName="id", nullable=true) * @ORM\JoinColumn(name="id_part_unit", referencedColumnName="id", nullable=true)
* @ColumnSecurity(type="object", prefix="unit")
*/ */
protected ?MeasurementUnit $partUnit = null; protected ?MeasurementUnit $partUnit = null;

View file

@ -44,7 +44,6 @@ namespace App\Entity\Parts\PartTraits;
use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Security\Annotations\ColumnSecurity;
use App\Validator\Constraints\Selectable; use App\Validator\Constraints\Selectable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@ -58,7 +57,6 @@ trait ManufacturerTrait
* @var Manufacturer|null The manufacturer of this part * @var Manufacturer|null The manufacturer of this part
* @ORM\ManyToOne(targetEntity="Manufacturer") * @ORM\ManyToOne(targetEntity="Manufacturer")
* @ORM\JoinColumn(name="id_manufacturer", referencedColumnName="id") * @ORM\JoinColumn(name="id_manufacturer", referencedColumnName="id")
* @ColumnSecurity(prefix="manufacturer", type="App\Entity\Parts\Manufacturer")
* @Selectable() * @Selectable()
*/ */
protected ?Manufacturer $manufacturer = null; protected ?Manufacturer $manufacturer = null;
@ -67,14 +65,12 @@ trait ManufacturerTrait
* @var string the url to the part on the manufacturer's homepage * @var string the url to the part on the manufacturer's homepage
* @ORM\Column(type="string") * @ORM\Column(type="string")
* @Assert\Url() * @Assert\Url()
* @ColumnSecurity(prefix="mpn", type="string", placeholder="")
*/ */
protected string $manufacturer_product_url = ''; protected string $manufacturer_product_url = '';
/** /**
* @var string The product number used by the manufacturer. If this is set to "", the name field is used. * @var string The product number used by the manufacturer. If this is set to "", the name field is used.
* @ORM\Column(type="string") * @ORM\Column(type="string")
* @ColumnSecurity(prefix="mpn", type="string", placeholder="")
*/ */
protected string $manufacturer_product_number = ''; protected string $manufacturer_product_number = '';
@ -82,7 +78,6 @@ trait ManufacturerTrait
* @var string The production status of this part. Can be one of the specified ones. * @var string The production status of this part. Can be one of the specified ones.
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @Assert\Choice({"announced", "active", "nrfnd", "eol", "discontinued", ""}) * @Assert\Choice({"announced", "active", "nrfnd", "eol", "discontinued", ""})
* @ColumnSecurity(type="string", prefix="status", placeholder="")
*/ */
protected ?string $manufacturing_status = ''; protected ?string $manufacturing_status = '';

View file

@ -43,7 +43,6 @@ declare(strict_types=1);
namespace App\Entity\Parts\PartTraits; namespace App\Entity\Parts\PartTraits;
use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Orderdetail;
use App\Security\Annotations\ColumnSecurity;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
use function count; use function count;
@ -59,7 +58,6 @@ trait OrderTrait
* @var Orderdetail[]|Collection the details about how and where you can order this part * @var Orderdetail[]|Collection the details about how and where you can order this part
* @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true)
* @Assert\Valid() * @Assert\Valid()
* @ColumnSecurity(prefix="orderdetails", type="collection")
* @ORM\OrderBy({"supplierpartnr" = "ASC"}) * @ORM\OrderBy({"supplierpartnr" = "ASC"})
*/ */
protected $orderdetails; protected $orderdetails;
@ -67,14 +65,12 @@ trait OrderTrait
/** /**
* @var int * @var int
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
* @ColumnSecurity(prefix="order", type="integer")
*/ */
protected int $order_quantity = 0; protected int $order_quantity = 0;
/** /**
* @var bool * @var bool
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
* @ColumnSecurity(prefix="order", type="boolean")
*/ */
protected bool $manual_order = false; protected bool $manual_order = false;
@ -82,8 +78,6 @@ trait OrderTrait
* @var Orderdetail * @var Orderdetail
* @ORM\OneToOne(targetEntity="App\Entity\PriceInformations\Orderdetail") * @ORM\OneToOne(targetEntity="App\Entity\PriceInformations\Orderdetail")
* @ORM\JoinColumn(name="order_orderdetails_id", referencedColumnName="id") * @ORM\JoinColumn(name="order_orderdetails_id", referencedColumnName="id")
*
* @ColumnSecurity(prefix="order", type="object")
*/ */
protected ?Orderdetail $order_orderdetail = null; protected ?Orderdetail $order_orderdetail = null;

View file

@ -95,11 +95,12 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
*/ */
protected $attachments; protected $attachments;
/** @var PermissionsEmbed /**
* @ORM\Embedded(class="PermissionsEmbed", columnPrefix="perms_") * @var PermissionData
* @ValidPermission() * @ValidPermission()
* @ORM\Embedded(class="PermissionData", columnPrefix="permissions_")
*/ */
protected $permissions; protected PermissionData $permissions;
/** @var Collection<int, GroupParameter> /** @var Collection<int, GroupParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\GroupParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\GroupParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
@ -111,7 +112,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->permissions = new PermissionsEmbed(); $this->permissions = new PermissionData();
$this->users = new ArrayCollection(); $this->users = new ArrayCollection();
} }
@ -137,7 +138,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
return $this; return $this;
} }
public function getPermissions(): PermissionsEmbed public function getPermissions(): PermissionData
{ {
return $this->permissions; return $this->permissions;
} }

View file

@ -0,0 +1,140 @@
<?php
namespace App\Entity\UserSystem;
use Doctrine\ORM\Mapping as ORM;
/**
* This class is used to store the permissions of a user.
* This has to be an embeddable or otherwise doctrine could not track the changes of the underlying data array (which is serialized to JSON in the database)
*
* @ORM\Embeddable()
*/
final class PermissionData implements \JsonSerializable
{
/**
* Permission values.
*/
public const INHERIT = null;
public const ALLOW = true;
public const DISALLOW = false;
/**
* @var array This array contains the permission values for each permission
* This array contains the permission values for each permission, in the form of:
* permission => [
* operation => value,
* ]
* @ORM\Column(type="json", name="data", options={"default": "[]"})
*/
protected ?array $data = [];
/**
* Creates a new Permission Data Instance using the given data.
* By default, a empty array is used, meaning
*/
public function __construct(array $data = [])
{
$this->data = $data;
}
/**
* Check if a permission value is set for the given permission and operation (meaning there value is not inherit).
* @param string $permission
* @param string $operation
* @return bool True if the permission value is set, false otherwise
*/
public function isPermissionSet(string $permission, string $operation): bool
{
return isset($this->data[$permission][$operation]);
}
/**
* Returns the permission value for the given permission and operation.
* @param string $permission
* @param string $operation
* @return bool|null True means allow, false means disallow, null means inherit
*/
public function getPermissionValue(string $permission, string $operation): ?bool
{
if ($this->isPermissionSet($permission, $operation)) {
return $this->data[$permission][$operation];
}
//If the value is not set explicitly, return null (meaning inherit)
return null;
}
/**
* Sets the permission value for the given permission and operation.
* @param string $permission
* @param string $operation
* @param bool|null $value
* @return $this
*/
public function setPermissionValue(string $permission, string $operation, ?bool $value): self
{
if ($value === null) {
//If the value is null, unset the permission value (meaning implicit inherit)
unset($this->data[$permission][$operation]);
} else {
//Otherwise, set the pemission value
if(!isset($this->data[$permission])) {
$this->data[$permission] = [];
}
$this->data[$permission][$operation] = $value;
}
return $this;
}
/**
* Resets the saved permissions and set all operations to inherit (which means they are not defined).
* @return $this
*/
public function resetPermissions(): self
{
$this->data = [];
return $this;
}
/**
* Creates a new Permission Data Instance using the given JSON encoded data
* @param string $json
* @return static
* @throws \JsonException
*/
public static function fromJSON(string $json): self
{
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
return new self($data);
}
public function __clone()
{
$this->data = $this->data;
}
/**
* Returns an JSON encodable representation of this object.
* @return array|mixed
*/
public function jsonSerialize()
{
$ret = [];
//Filter out all empty or null values
foreach ($this->data as $permission => $operations) {
$ret[$permission] = array_filter($operations, function ($value) {
return $value !== null;
});
//If the permission has no operations, unset it
if (empty($ret[$permission])) {
unset($ret[$permission]);
}
}
return $ret;
}
}

View file

@ -1,523 +0,0 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Entity\UserSystem;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
use Webmozart\Assert\Assert;
/**
* This entity represents the permission fields a user or group can have.
*
* @ORM\Embeddable()
*/
class PermissionsEmbed
{
/**
* Permission values.
*/
public const INHERIT = 0b00;
public const ALLOW = 0b01;
public const DISALLOW = 0b10;
/**
* Permission strings.
*/
public const STORELOCATIONS = 'storelocations';
public const FOOTRPINTS = 'footprints';
public const CATEGORIES = 'categories';
public const SUPPLIERS = 'suppliers';
public const MANUFACTURERS = 'manufacturers';
public const DEVICES = 'devices';
public const ATTACHMENT_TYPES = 'attachment_types';
public const MEASUREMENT_UNITS = 'measurement_units';
public const CURRENCIES = 'currencies';
public const TOOLS = 'tools';
public const PARTS = 'parts';
public const PARTS_NAME = 'parts_name';
public const PARTS_DESCRIPTION = 'parts_description';
public const PARTS_MINAMOUNT = 'parts_minamount';
public const PARTS_FOOTPRINT = 'parts_footprint';
public const PARTS_MPN = 'parts_mpn';
public const PARTS_STATUS = 'parts_status';
public const PARTS_TAGS = 'parts_tags';
public const PARTS_UNIT = 'parts_unit';
public const PARTS_MASS = 'parts_mass';
public const PARTS_LOTS = 'parts_lots';
public const PARTS_COMMENT = 'parts_comment';
public const PARTS_MANUFACTURER = 'parts_manufacturer';
public const PARTS_ORDERDETAILS = 'parts_orderdetails';
public const PARTS_PRICES = 'parts_prices';
public const PARTS_ATTACHMENTS = 'parts_attachments';
public const PARTS_ORDER = 'parts_order';
public const GROUPS = 'groups';
public const USERS = 'users';
public const DATABASE = 'database';
public const CONFIG = 'config';
public const SYSTEM = 'system';
public const DEVICE_PARTS = 'devices_parts';
public const SELF = 'self';
public const LABELS = 'labels';
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $system = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $groups = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $users = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $self = 0;
/**
* @var int
* @ORM\Column(type="integer", name="system_config")
*/
protected $config = 0;
/**
* @var int
* @ORM\Column(type="integer", name="system_database")
*/
protected $database = 0;
/**
* @var int
* @ORM\Column(type="bigint")
*/
protected $parts = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_name = 0;
/** @var int
* @ORM\Column(type="smallint")
*/
protected $parts_category = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_description = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_minamount = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_footprint = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_lots = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_tags = 0;
/** @var int
* @ORM\Column(type="smallint")
*/
protected $parts_unit = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_mass = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_manufacturer = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_status = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_mpn = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_comment = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_order = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_orderdetails = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_prices = 0;
/**
* @var int
* @ORM\Column(type="smallint")
*/
protected $parts_parameters = 0;
/**
* @var int
* @ORM\Column(type="smallint", name="parts_attachements")
*/
protected $parts_attachments = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $devices = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $devices_parts = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $storelocations = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $footprints = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $categories = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $suppliers = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $manufacturers = 0;
/**
* @var int
* @ORM\Column(type="integer", name="attachement_types")
*/
protected $attachment_types = 0;
/** @var int
* @ORM\Column(type="integer")
*/
protected $currencies = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $measurement_units = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $tools = 0;
/**
* @var int
* @ORM\Column(type="integer")
*/
protected $labels = 0;
/**
* Checks whether a permission with the given name is valid for this object.
*
* @param string $permission_name the name of the permission which should be checked for
*
* @return bool true if the permission is existing on this object
*/
public function isValidPermissionName(string $permission_name): bool
{
return isset($this->{$permission_name});
}
/**
* Returns the bit pair value of the given permission.
*
* @param string $permission_name the name of the permission, for which the bit pair should be returned
* @param int $bit_n the (lower) bit number of the bit pair, which should be read
*
* @return int The value of the bit pair. Compare to the INHERIT, ALLOW, and DISALLOW consts in this class.
*/
public function getBitValue(string $permission_name, int $bit_n): int
{
if (!$this->isValidPermissionName($permission_name)) {
throw new InvalidArgumentException(sprintf('No permission with the name "%s" is existing!', $permission_name));
}
$perm_int = (int) $this->{$permission_name};
return static::readBitPair($perm_int, $bit_n);
}
/**
* Returns the value of the operation for the given permission.
*
* @param string $permission_name the name of the permission, for which the operation should be returned
* @param int $bit_n the (lower) bit number of the bit pair for the operation
*
* @return bool|null The value of the operation. True, if the given operation is allowed, false if disallowed
* and null if it should inherit from parent.
*/
public function getPermissionValue(string $permission_name, int $bit_n): ?bool
{
$value = $this->getBitValue($permission_name, $bit_n);
if (self::ALLOW === $value) {
return true;
}
if (self::DISALLOW === $value) {
return false;
}
return null;
}
/**
* Sets the value of the given permission and operation.
*
* @param string $permission_name the name of the permission, for which the bit pair should be written
* @param int $bit_n the (lower) bit number of the bit pair, which should be written
* @param bool|null $new_value the new value for the operation:
* True, if the given operation is allowed, false if disallowed
* and null if it should inherit from parent
*
* @return PermissionsEmbed the instance itself
*/
public function setPermissionValue(string $permission_name, int $bit_n, ?bool $new_value): self
{
//Determine which bit value the given value is.
if (true === $new_value) {
$bit_value = static::ALLOW;
} elseif (false === $new_value) {
$bit_value = static::DISALLOW;
} else {
$bit_value = static::INHERIT;
}
$this->setBitValue($permission_name, $bit_n, $bit_value);
return $this;
}
/**
* Sets the bit value of the given permission and operation.
*
* @param string $permission_name the name of the permission, for which the bit pair should be written
* @param int $bit_n the (lower) bit number of the bit pair, which should be written
* @param int $new_value the new (bit) value of the bit pair, which should be written
*
* @return PermissionsEmbed the instance itself
*/
public function setBitValue(string $permission_name, int $bit_n, int $new_value): self
{
if (!$this->isValidPermissionName($permission_name)) {
throw new InvalidArgumentException('No permission with the given name is existing!');
}
$this->{$permission_name} = static::writeBitPair((int) $this->{$permission_name}, $bit_n, $new_value);
return $this;
}
/**
* Returns the given permission as raw int (all bit at once).
*
* @param string $permission_name The name of the permission, which should be retrieved.
* If this is not existing an exception is thrown.
*
* @return int the raw permission value
*/
public function getRawPermissionValue(string $permission_name): int
{
if (!$this->isValidPermissionName($permission_name)) {
throw new InvalidArgumentException('No permission with the given name is existing!');
}
return $this->{$permission_name};
}
/**
* Sets the given permission to the value.
*
* @param string $permission_name the name of the permission to that should be set
* @param int $value The new value of the permissions
*
* @return $this
*/
public function setRawPermissionValue(string $permission_name, int $value): self
{
if (!$this->isValidPermissionName($permission_name)) {
throw new InvalidArgumentException(sprintf('No permission with the given name %s is existing!', $permission_name));
}
$this->{$permission_name} = $value;
return $this;
}
/**
* Sets multiple permissions at once.
*
* @param array $values An array in the form ['perm_name' => $value], containing the new data
* @param array|null $values2 if this array is not null, the first array will treated of list of perm names,
* and this array as an array of new values
*
* @return $this
*/
public function setRawPermissionValues(array $values, ?array $values2 = null): self
{
if (!empty($values2)) {
$values = array_combine($values, $values2);
}
foreach ($values as $key => $value) {
$this->setRawPermissionValue($key, $value);
}
return $this;
}
/**
* Reads a bit pair from $data.
*
* @param int|string $data The data from where the bits should be extracted from
* @param int $n The number of the lower bit (of the pair) that should be read. Starting from zero.
*
* @return int the value of the bit pair
*/
final protected static function readBitPair($data, int $n): int
{
//Assert::lessThanEq($n, 31, '$n must be smaller than 32, because only a 32bit int is used! Got %s.');
if (0 !== $n % 2) {
throw new InvalidArgumentException('$n must be dividable by 2, because we address bit pairs here!');
}
$mask = 0b11 << $n; //Create a mask for the data
return ($data & $mask) >> $n; //Apply mask and shift back
}
/**
* Writes a bit pair in the given $data and returns it.
*
* @param int $data The data which should be modified
* @param int $n The number of the lower bit of the pair which should be written
* @param int $new The new value of the pair
*
* @return int the new data with the modified pair
*/
final protected static function writeBitPair(int $data, int $n, int $new): int
{
//Assert::lessThanEq($n, 31, '$n must be smaller than 32, because only a 32bit int is used! Got %s.');
Assert::lessThanEq($new, 3, '$new must be smaller than 3, because a bit pair is written! Got %s.');
Assert::greaterThanEq($new, 0, '$new must not be negative, because a bit pair is written! Got %s.');
if (0 !== $n % 2) {
throw new InvalidArgumentException('$n must be dividable by 2, because we address bit pairs here!');
}
$mask = 0b11 << $n; //Mask all bits that should be written
$newval = $new << $n; //The new value.
return ($data & ~$mask) | ($newval & $mask);
}
}

View file

@ -207,11 +207,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
*/ */
protected bool $need_pw_change = true; protected bool $need_pw_change = true;
/**
* //@ORM\Column(type="json").
*/
//protected $roles = [];
/** /**
* @var string|null The hashed password * @var string|null The hashed password
* @ORM\Column(type="string", nullable=true) * @ORM\Column(type="string", nullable=true)
@ -265,11 +260,12 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
*/ */
protected $currency; protected $currency;
/** @var PermissionsEmbed /**
* @ORM\Embedded(class="PermissionsEmbed", columnPrefix="perms_") * @var PermissionData
* @ValidPermission() * @ValidPermission()
* @ORM\Embedded(class="PermissionData", columnPrefix="permissions_")
*/ */
protected $permissions; protected PermissionData $permissions;
/** /**
* @var DateTime the time until the password reset token is valid * @var DateTime the time until the password reset token is valid
@ -280,7 +276,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->permissions = new PermissionsEmbed(); $this->permissions = new PermissionData();
$this->u2fKeys = new ArrayCollection(); $this->u2fKeys = new ArrayCollection();
$this->webauthn_keys = new ArrayCollection(); $this->webauthn_keys = new ArrayCollection();
} }
@ -427,7 +423,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
return $this; return $this;
} }
public function getPermissions(): PermissionsEmbed public function getPermissions(): PermissionData
{ {
return $this->permissions; return $this->permissions;
} }

View file

@ -64,6 +64,7 @@ class GroupAdminForm extends BaseEntityAdminForm
'mapped' => false, 'mapped' => false,
'data' => $builder->getData(), 'data' => $builder->getData(),
'disabled' => !$this->security->isGranted('edit_permissions', $entity), 'disabled' => !$this->security->isGranted('edit_permissions', $entity),
'show_presets' => $this->security->isGranted('edit_permissions', $entity) && !$is_new,
]); ]);
} }
} }

View file

@ -106,7 +106,7 @@ class AttachmentFormType extends AbstractType
'required' => false, 'required' => false,
'label' => 'attachment.edit.secure_file', 'label' => 'attachment.edit.secure_file',
'mapped' => false, 'mapped' => false,
'disabled' => !$this->security->isGranted('@parts_attachments.show_private'), 'disabled' => !$this->security->isGranted('@attachments.show_private'),
'help' => 'attachment.edit.secure_file.help', 'help' => 'attachment.edit.secure_file.help',
]); ]);

View file

@ -54,7 +54,7 @@ class CollectionTypeExtension extends AbstractTypeExtension
public static function getExtendedTypes(): iterable public static function getExtendedTypes(): iterable
{ {
return [CollectionType::class, WorkaroundCollectionType::class]; return [CollectionType::class];
} }
public function configureOptions(OptionsResolver $resolver): void public function configureOptions(OptionsResolver $resolver): void

View file

@ -50,6 +50,7 @@ use App\Form\Type\StructuralEntityType;
use App\Form\WorkaroundCollectionType; use App\Form\WorkaroundCollectionType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\UrlType; use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -106,16 +107,15 @@ class OrderdetailType extends AbstractType
} }
//Attachment section //Attachment section
$event->getForm()->add('pricedetails', WorkaroundCollectionType::class, [ $event->getForm()->add('pricedetails', CollectionType::class, [
'entry_type' => PricedetailType::class, 'entry_type' => PricedetailType::class,
'allow_add' => $this->security->isGranted('@parts_prices.create'), 'allow_add' => true,
'allow_delete' => $this->security->isGranted('@parts_prices.delete'), 'allow_delete' => true,
'label' => false, 'label' => false,
'reindex_enable' => true, 'reindex_enable' => true,
'prototype_data' => $dummy_pricedetail, 'prototype_data' => $dummy_pricedetail,
'by_reference' => false, 'by_reference' => false,
'entry_options' => [ 'entry_options' => [
'disabled' => !$this->security->isGranted('@parts_prices.edit'),
'measurement_unit' => $options['measurement_unit'], 'measurement_unit' => $options['measurement_unit'],
], ],
]); ]);

View file

@ -103,7 +103,6 @@ class PartBaseType extends AbstractType
'attr' => [ 'attr' => [
'placeholder' => 'part.edit.name.placeholder', 'placeholder' => 'part.edit.name.placeholder',
], ],
'disabled' => !$this->security->isGranted('name.edit', $part),
]) ])
->add('description', RichTextEditorType::class, [ ->add('description', RichTextEditorType::class, [
'required' => false, 'required' => false,
@ -114,7 +113,6 @@ class PartBaseType extends AbstractType
'placeholder' => 'part.edit.description.placeholder', 'placeholder' => 'part.edit.description.placeholder',
'rows' => 2, 'rows' => 2,
], ],
'disabled' => !$this->security->isGranted('description.edit', $part),
]) ])
->add('minAmount', SIUnitType::class, [ ->add('minAmount', SIUnitType::class, [
'attr' => [ 'attr' => [
@ -123,13 +121,11 @@ class PartBaseType extends AbstractType
], ],
'label' => 'part.edit.mininstock', 'label' => 'part.edit.mininstock',
'measurement_unit' => $part->getPartUnit(), 'measurement_unit' => $part->getPartUnit(),
'disabled' => !$this->security->isGranted('minamount.edit', $part),
]) ])
->add('category', StructuralEntityType::class, [ ->add('category', StructuralEntityType::class, [
'class' => Category::class, 'class' => Category::class,
'label' => 'part.edit.category', 'label' => 'part.edit.category',
'disable_not_selectable' => true, 'disable_not_selectable' => true,
'disabled' => !$this->security->isGranted('category.edit', $part),
'constraints' => [ 'constraints' => [
], ],
]) ])
@ -138,7 +134,6 @@ class PartBaseType extends AbstractType
'required' => false, 'required' => false,
'label' => 'part.edit.footprint', 'label' => 'part.edit.footprint',
'disable_not_selectable' => true, 'disable_not_selectable' => true,
'disabled' => !$this->security->isGranted('footprint.edit', $part),
]) ])
->add('tags', TextType::class, [ ->add('tags', TextType::class, [
'required' => false, 'required' => false,
@ -149,7 +144,6 @@ class PartBaseType extends AbstractType
'data-controller' => 'elements--tagsinput', 'data-controller' => 'elements--tagsinput',
'data-autocomplete' => $this->urlGenerator->generate('typeahead_tags', ['query' => '__QUERY__']), 'data-autocomplete' => $this->urlGenerator->generate('typeahead_tags', ['query' => '__QUERY__']),
], ],
'disabled' => !$this->security->isGranted('tags.edit', $part),
]); ]);
//Manufacturer section //Manufacturer section
@ -158,32 +152,27 @@ class PartBaseType extends AbstractType
'required' => false, 'required' => false,
'label' => 'part.edit.manufacturer.label', 'label' => 'part.edit.manufacturer.label',
'disable_not_selectable' => true, 'disable_not_selectable' => true,
'disabled' => !$this->security->isGranted('manufacturer.edit', $part),
]) ])
->add('manufacturer_product_url', UrlType::class, [ ->add('manufacturer_product_url', UrlType::class, [
'required' => false, 'required' => false,
'empty_data' => '', 'empty_data' => '',
'label' => 'part.edit.manufacturer_url.label', 'label' => 'part.edit.manufacturer_url.label',
'disabled' => !$this->security->isGranted('mpn.edit', $part),
]) ])
->add('manufacturer_product_number', TextType::class, [ ->add('manufacturer_product_number', TextType::class, [
'required' => false, 'required' => false,
'empty_data' => '', 'empty_data' => '',
'label' => 'part.edit.mpn', 'label' => 'part.edit.mpn',
'disabled' => !$this->security->isGranted('mpn.edit', $part),
]) ])
->add('manufacturing_status', ChoiceType::class, [ ->add('manufacturing_status', ChoiceType::class, [
'label' => 'part.edit.manufacturing_status', 'label' => 'part.edit.manufacturing_status',
'choices' => $status_choices, 'choices' => $status_choices,
'required' => false, 'required' => false,
'disabled' => !$this->security->isGranted('status.edit', $part),
]); ]);
//Advanced section //Advanced section
$builder->add('needsReview', CheckboxType::class, [ $builder->add('needsReview', CheckboxType::class, [
'required' => false, 'required' => false,
'label' => 'part.edit.needs_review', 'label' => 'part.edit.needs_review',
'disabled' => !$this->security->isGranted('edit', $part),
]) ])
->add('favorite', CheckboxType::class, [ ->add('favorite', CheckboxType::class, [
'required' => false, 'required' => false,
@ -194,14 +183,12 @@ class PartBaseType extends AbstractType
'unit' => 'g', 'unit' => 'g',
'label' => 'part.edit.mass', 'label' => 'part.edit.mass',
'required' => false, 'required' => false,
'disabled' => !$this->security->isGranted('mass.edit', $part),
]) ])
->add('partUnit', StructuralEntityType::class, [ ->add('partUnit', StructuralEntityType::class, [
'class' => MeasurementUnit::class, 'class' => MeasurementUnit::class,
'required' => false, 'required' => false,
'disable_not_selectable' => true, 'disable_not_selectable' => true,
'label' => 'part.edit.partUnit', 'label' => 'part.edit.partUnit',
'disabled' => !$this->security->isGranted('unit.edit', $part),
]); ]);
//Comment section //Comment section
@ -212,20 +199,18 @@ class PartBaseType extends AbstractType
'rows' => 4, 'rows' => 4,
], ],
'mode' => 'markdown-full', 'mode' => 'markdown-full',
'disabled' => !$this->security->isGranted('comment.edit', $part),
'empty_data' => '', 'empty_data' => '',
]); ]);
//Part Lots section //Part Lots section
$builder->add('partLots', CollectionType::class, [ $builder->add('partLots', CollectionType::class, [
'entry_type' => PartLotType::class, 'entry_type' => PartLotType::class,
'allow_add' => $this->security->isGranted('lots.create', $part), 'allow_add' => true,
'allow_delete' => $this->security->isGranted('lots.delete', $part), 'allow_delete' => true,
'reindex_enable' => true, 'reindex_enable' => true,
'label' => false, 'label' => false,
'entry_options' => [ 'entry_options' => [
'measurement_unit' => $part->getPartUnit(), 'measurement_unit' => $part->getPartUnit(),
'disabled' => !$this->security->isGranted('lots.edit', $part),
], ],
'by_reference' => false, 'by_reference' => false,
]); ]);
@ -233,49 +218,45 @@ class PartBaseType extends AbstractType
//Attachment section //Attachment section
$builder->add('attachments', CollectionType::class, [ $builder->add('attachments', CollectionType::class, [
'entry_type' => AttachmentFormType::class, 'entry_type' => AttachmentFormType::class,
'allow_add' => $this->security->isGranted('attachments.create', $part), 'allow_add' => true,
'allow_delete' => $this->security->isGranted('attachments.delete', $part), 'allow_delete' => true,
'reindex_enable' => true, 'reindex_enable' => true,
'label' => false, 'label' => false,
'entry_options' => [ 'entry_options' => [
'data_class' => PartAttachment::class, 'data_class' => PartAttachment::class,
'disabled' => !$this->security->isGranted('attachments.edit', $part),
], ],
'by_reference' => false, 'by_reference' => false,
]); ]);
$builder->add('master_picture_attachment', MasterPictureAttachmentType::class, [ $builder->add('master_picture_attachment', MasterPictureAttachmentType::class, [
'required' => false, 'required' => false,
'disabled' => !$this->security->isGranted('attachments.edit', $part),
'label' => 'part.edit.master_attachment', 'label' => 'part.edit.master_attachment',
'entity' => $part, 'entity' => $part,
]); ]);
//Orderdetails section //Orderdetails section
$builder->add('orderdetails', WorkaroundCollectionType::class, [ $builder->add('orderdetails', CollectionType::class, [
'entry_type' => OrderdetailType::class, 'entry_type' => OrderdetailType::class,
'allow_add' => $this->security->isGranted('orderdetails.create', $part),
'allow_delete' => $this->security->isGranted('orderdetails.delete', $part),
'reindex_enable' => true, 'reindex_enable' => true,
'allow_add' => true,
'allow_delete' => true,
'label' => false, 'label' => false,
'by_reference' => false, 'by_reference' => false,
'prototype_data' => new Orderdetail(), 'prototype_data' => new Orderdetail(),
'entry_options' => [ 'entry_options' => [
'measurement_unit' => $part->getPartUnit(), 'measurement_unit' => $part->getPartUnit(),
'disabled' => !$this->security->isGranted('orderdetails.edit', $part),
], ],
]); ]);
$builder->add('parameters', CollectionType::class, [ $builder->add('parameters', CollectionType::class, [
'entry_type' => ParameterType::class, 'entry_type' => ParameterType::class,
'allow_add' => $this->security->isGranted('parameters.create', $part), 'allow_add' => true,
'allow_delete' => $this->security->isGranted('parameters.delete', $part), 'allow_delete' => true,
'label' => false, 'label' => false,
'reindex_enable' => true, 'reindex_enable' => true,
'by_reference' => false, 'by_reference' => false,
'prototype_data' => new PartParameter(), 'prototype_data' => new PartParameter(),
'entry_options' => [ 'entry_options' => [
'disabled' => !$this->security->isGranted('parameters.edit', $part),
'data_class' => PartParameter::class, 'data_class' => PartParameter::class,
], ],
]); ]);

View file

@ -42,7 +42,7 @@ declare(strict_types=1);
namespace App\Form\Permissions; namespace App\Form\Permissions;
use App\Services\PermissionResolver; use App\Services\UserSystem\PermissionManager;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\Options;
@ -50,10 +50,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class PermissionGroupType extends AbstractType class PermissionGroupType extends AbstractType
{ {
protected PermissionResolver $resolver; protected PermissionManager $resolver;
protected array $perm_structure; protected array $perm_structure;
public function __construct(PermissionResolver $resolver) public function __construct(PermissionManager $resolver)
{ {
$this->resolver = $resolver; $this->resolver = $resolver;
$this->perm_structure = $resolver->getPermissionStructure(); $this->perm_structure = $resolver->getPermissionStructure();

View file

@ -43,7 +43,7 @@ declare(strict_types=1);
namespace App\Form\Permissions; namespace App\Form\Permissions;
use App\Form\Type\TriStateCheckboxType; use App\Form\Type\TriStateCheckboxType;
use App\Services\PermissionResolver; use App\Services\UserSystem\PermissionManager;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
@ -53,10 +53,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class PermissionType extends AbstractType class PermissionType extends AbstractType
{ {
protected PermissionResolver $resolver; protected PermissionManager $resolver;
protected array $perm_structure; protected array $perm_structure;
public function __construct(PermissionResolver $resolver) public function __construct(PermissionManager $resolver)
{ {
$this->resolver = $resolver; $this->resolver = $resolver;
$this->perm_structure = $resolver->getPermissionStructure(); $this->perm_structure = $resolver->getPermissionStructure();
@ -97,6 +97,9 @@ class PermissionType extends AbstractType
'mapped' => false, 'mapped' => false,
'label' => $operation['label'] ?? null, 'label' => $operation['label'] ?? null,
'disabled' => $options['disabled'], 'disabled' => $options['disabled'],
'attr' => [
'class' => 'permission-checkbox tristate',
],
'label_attr' => [ 'label_attr' => [
'class' => 'checkbox-inline opacity-100', 'class' => 'checkbox-inline opacity-100',
], ],

View file

@ -42,7 +42,7 @@ declare(strict_types=1);
namespace App\Form\Permissions; namespace App\Form\Permissions;
use App\Services\PermissionResolver; use App\Services\UserSystem\PermissionManager;
use RuntimeException; use RuntimeException;
use Symfony\Component\Form\DataMapperInterface; use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
@ -54,10 +54,10 @@ use Traversable;
*/ */
final class PermissionsMapper implements DataMapperInterface final class PermissionsMapper implements DataMapperInterface
{ {
private PermissionResolver $resolver; private PermissionManager $resolver;
private bool $inherit; private bool $inherit;
public function __construct(PermissionResolver $resolver, bool $inherit = false) public function __construct(PermissionManager $resolver, bool $inherit = false)
{ {
$this->inherit = $inherit; $this->inherit = $inherit;
$this->resolver = $resolver; $this->resolver = $resolver;

View file

@ -42,7 +42,7 @@ declare(strict_types=1);
namespace App\Form\Permissions; namespace App\Form\Permissions;
use App\Services\PermissionResolver; use App\Services\UserSystem\PermissionManager;
use App\Validator\Constraints\NoLockout; use App\Validator\Constraints\NoLockout;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -53,10 +53,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class PermissionsType extends AbstractType class PermissionsType extends AbstractType
{ {
protected PermissionResolver $resolver; protected PermissionManager $resolver;
protected array $perm_structure; protected array $perm_structure;
public function __construct(PermissionResolver $resolver) public function __construct(PermissionManager $resolver)
{ {
$this->resolver = $resolver; $this->resolver = $resolver;
$this->perm_structure = $resolver->getPermissionStructure(); $this->perm_structure = $resolver->getPermissionStructure();
@ -66,6 +66,7 @@ class PermissionsType extends AbstractType
{ {
$resolver->setDefaults([ $resolver->setDefaults([
'show_legend' => true, 'show_legend' => true,
'show_presets' => false,
'constraints' => static function (Options $options) { 'constraints' => static function (Options $options) {
if (!$options['disabled']) { if (!$options['disabled']) {
return [new NoLockout()]; return [new NoLockout()];
@ -80,6 +81,7 @@ class PermissionsType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options): void public function buildView(FormView $view, FormInterface $form, array $options): void
{ {
$view->vars['show_legend'] = $options['show_legend']; $view->vars['show_legend'] = $options['show_legend'];
$view->vars['show_presets'] = $options['show_presets'];
} }
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void

View file

@ -102,7 +102,7 @@ class UserAdminForm extends AbstractType
'required' => false, 'required' => false,
'label' => 'group.label', 'label' => 'group.label',
'disable_not_selectable' => true, 'disable_not_selectable' => true,
'disabled' => !$this->security->isGranted('change_group', $entity), 'disabled' => !$this->security->isGranted('edit_permissions', $entity),
]) ])
->add('first_name', TextType::class, [ ->add('first_name', TextType::class, [
@ -227,6 +227,7 @@ class UserAdminForm extends AbstractType
'mapped' => false, 'mapped' => false,
'data' => $builder->getData(), 'data' => $builder->getData(),
'disabled' => !$this->security->isGranted('edit_permissions', $entity), 'disabled' => !$this->security->isGranted('edit_permissions', $entity),
'show_presets' => $this->security->isGranted('edit_permissions', $entity) && !$is_new,
]) ])
; ;
/*->add('comment', CKEditorType::class, ['required' => false, /*->add('comment', CKEditorType::class, ['required' => false,

View file

@ -1,23 +0,0 @@
<?php
namespace App\Form;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
/**
* This a workaround for the issue #37024.
*/
class WorkaroundCollectionType extends CollectionType
{
/**
* Use the original implementation for finishView() instead of the one, the one that cause the bug.
*/
public function finishView(FormView $view, FormInterface $form, array $options)
{
if ($view->vars['prototype']->vars['multipart']) {
$view->vars['multipart'] = true;
}
}
}

View file

@ -43,6 +43,7 @@ declare(strict_types=1);
namespace App\Repository; namespace App\Repository;
use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\UserSystem\User;
use App\Helpers\Trees\TreeViewNode; use App\Helpers\Trees\TreeViewNode;
class NamedDBElementRepository extends DBElementRepository class NamedDBElementRepository extends DBElementRepository
@ -63,6 +64,11 @@ class NamedDBElementRepository extends DBElementRepository
$node = new TreeViewNode($entity->getName(), null, null); $node = new TreeViewNode($entity->getName(), null, null);
$node->setId($entity->getID()); $node->setId($entity->getID());
$result[] = $node; $result[] = $node;
if ($entity instanceof User && $entity->isDisabled()) {
//If this is an user, then add a badge when it is disabled
$node->setIcon('fa-fw fa-treeview fa-solid fa-user-lock text-muted');
}
} }
return $result; return $result;

View file

@ -1,148 +0,0 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Security\Annotations;
use App\Entity\Base\AbstractNamedDBElement;
use DateTime;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Collections\ArrayCollection;
use InvalidArgumentException;
use function is_string;
/**
* @Annotation
*
* @Annotation\Target("PROPERTY")
*
* With these annotation you can restrict the access to certain coloumns in entities.
* The entity which should use this class has to use ElementListener as EntityListener.
*/
class ColumnSecurity
{
/**
* @var string The name of the edit permission
*/
public string $edit = 'edit';
/**
* @var string The name of the read permission
*/
public string $read = 'read';
/**
* @var string A prefix for all permission names (e.g..edit, useful for Parts)
*/
public string $prefix = '';
/**
* @var mixed the placeholder that should be used, when the access to the property is denied
*/
public $placeholder = null;
public $subject = null;
/**
* @var string The name of the property. This is used to determine the default placeholder.
* @Annotation\Enum({"integer", "string", "object", "boolean", "datetime", "collection"})
*/
public $type = 'string';
public function getReadOperationName(): string
{
if ('' !== $this->prefix) {
return $this->prefix.'.'.$this->read;
}
return $this->read;
}
public function getEditOperationName(): string
{
if ('' !== $this->prefix) {
return $this->prefix.'.'.$this->edit;
}
return $this->edit;
}
public function getPlaceholder()
{
//Check if a class name was specified
if (class_exists($this->type)) {
$object = new $this->type();
if ($object instanceof AbstractNamedDBElement) {
if (is_string($this->placeholder) && '' !== $this->placeholder) {
$object->setName($this->placeholder);
} else {
$object->setName('???');
}
}
return $object;
}
if (null === $this->placeholder) {
switch ($this->type) {
case 'integer':
case 'int':
return 0;
case 'float':
return 0.0;
case 'string':
return '???';
case 'object':
return null;
case 'collection':
return new ArrayCollection();
case 'boolean':
case 'bool':
return false;
case 'datetime':
return (new DateTime())->setTimestamp(0);
default:
throw new InvalidArgumentException('Unknown type! You have to specify a placeholder!');
}
}
return $this->placeholder;
}
}

View file

@ -1,223 +0,0 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Security\EntityListeners;
use App\Entity\Base\AbstractDBElement;
use App\Entity\UserSystem\User;
use App\Security\Annotations\ColumnSecurity;
use function count;
use Doctrine\Common\Annotations\Reader;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\PostLoad;
use function get_class;
use InvalidArgumentException;
use ReflectionClass;
use Symfony\Component\Security\Core\Security;
/**
* The purpose of this class is to hook into the doctrine entity lifecycle and restrict access to entity informations
* configured by ColoumnSecurity Annotation.
* If the current programm is running from CLI (like a CLI command), the security checks are disabled.
* (Commands should be able to do everything they like).
*
* If a user does not have access to an coloumn, it will be filled, with a placeholder, after doctrine loading is finished.
* The edit process is also catched, so that these placeholders, does not get saved to database.
*/
class ElementPermissionListener
{
protected Security $security;
protected Reader $reader;
protected EntityManagerInterface $em;
protected bool $disabled;
protected array $perm_cache;
public function __construct(Security $security, Reader $reader, EntityManagerInterface $em)
{
$this->security = $security;
$this->reader = $reader;
$this->em = $em;
//Disable security when the current program is running from CLI
$this->disabled = $this->isRunningFromCLI();
$this->perm_cache = [];
}
/**
* @PostLoad
* @ORM\PostUpdate()
* This function is called after doctrine filled, the entity properties with db values.
* We use this, to check if the user is allowed to access these properties, and if not, we write a placeholder
* into the element properties, so that a user only gets non sensitve data.
*
* This function is also called after an entity was updated, so we dont show the original data to user,
* after an update.
*/
public function postLoadHandler(AbstractDBElement $element, LifecycleEventArgs $event): void
{
//Do nothing if security is disabled
if ($this->disabled) {
return;
}
//Read Annotations and properties.
$reflectionClass = new ReflectionClass($element);
$properties = $reflectionClass->getProperties();
foreach ($properties as $property) {
/** @var ColumnSecurity */
$annotation = $this->reader->getPropertyAnnotation(
$property,
ColumnSecurity::class
);
//Check if user is allowed to read info, otherwise apply placeholder
if ((null !== $annotation) && !$this->isGranted('read', $annotation, $element)) {
$property->setAccessible(true);
$value = $annotation->getPlaceholder();
//Detach placeholder entities, so we dont get cascade errors
if ($value instanceof AbstractDBElement) {
$this->em->detach($value);
}
$property->setValue($element, $value);
}
}
}
/**
* @ORM\PreFlush()
* This function is called before flushing. We use it, to remove all placeholders.
* We do it here and not in preupdate, because this is called before calculating the changeset,
* and so we dont get problems with orphan removal.
*/
public function preFlushHandler(AbstractDBElement $element, PreFlushEventArgs $eventArgs): void
{
//Do nothing if security is disabled
if ($this->disabled) {
return;
}
$unitOfWork = $eventArgs->getEntityManager()->getUnitOfWork();
$reflectionClass = new ReflectionClass($element);
$properties = $reflectionClass->getProperties();
$old_data = $unitOfWork->getOriginalEntityData($element);
foreach ($properties as $property) {
$annotation = $this->reader->getPropertyAnnotation(
$property,
ColumnSecurity::class
);
$changed = false;
//Only set the field if it has an annotation
if (null !== $annotation) {
$property->setAccessible(true);
//If the user is not allowed to edit or read this property, reset all values.
//Set value to old value, so that there a no change to this property
if ((!$this->isGranted('read', $annotation, $element)
|| !$this->isGranted('edit', $annotation, $element)) && isset(
$old_data[$property->getName()]
)) {
$property->setValue($element, $old_data[$property->getName()]);
$changed = true;
}
if ($changed) {
//Schedule for update, so the post update method will be called
$unitOfWork->scheduleForUpdate($element);
}
}
}
}
/**
* This function checks if the current script is run from web or from a terminal.
*
* @return bool Returns true if the current programm is running from CLI (terminal)
*/
protected function isRunningFromCLI(): bool
{
return empty($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT']) && count($_SERVER['argv']) > 0;
}
/**
* Checks if access to the property of the given element is granted.
* This function adds an additional cache layer, where the voters are called only once (to improve performance).
*
* @param string $mode What operation should be checked. Must be 'read' or 'edit'
* @param ColumnSecurity $annotation The annotation of the property that should be checked
* @param AbstractDBElement $element The element that should for which should be checked
*
* @return bool True if the user is allowed to read that property
*/
protected function isGranted(string $mode, ColumnSecurity $annotation, AbstractDBElement $element): bool
{
if ('read' === $mode) {
$operation = $annotation->getReadOperationName();
} elseif ('edit' === $mode) {
$operation = $annotation->getEditOperationName();
} else {
throw new InvalidArgumentException('$mode must be either "read" or "edit"!');
}
//Users must always be checked, because its return value can differ if it is the user itself or something else
if ($element instanceof User) {
return $this->security->isGranted($operation, $element);
}
//Check if we have already have saved the permission, otherwise save it to cache
if (!isset($this->perm_cache[$mode][get_class($element)][$operation])) {
$this->perm_cache[$mode][get_class($element)][$operation] = $this->security->isGranted($operation, $element);
}
return $this->perm_cache[$mode][get_class($element)][$operation];
}
}

View file

@ -42,9 +42,9 @@ declare(strict_types=1);
namespace App\Security\Interfaces; namespace App\Security\Interfaces;
use App\Entity\UserSystem\PermissionsEmbed; use App\Entity\UserSystem\PermissionData;
interface HasPermissionsInterface interface HasPermissionsInterface
{ {
public function getPermissions(): PermissionsEmbed; public function getPermissions(): PermissionData;
} }

View file

@ -44,14 +44,19 @@ namespace App\Security;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use Symfony\Component\Security\Core\Exception\AccountStatusException; use Symfony\Component\Security\Core\Exception\AccountStatusException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
use Symfony\Component\Security\Core\Exception\DisabledException; use Symfony\Component\Security\Core\Exception\DisabledException;
use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final class UserChecker implements UserCheckerInterface final class UserChecker implements UserCheckerInterface
{ {
public function __construct() private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
{ {
$this->translator = $translator;
} }
/** /**
@ -77,7 +82,8 @@ final class UserChecker implements UserCheckerInterface
//Check if user is disabled. Then dont allow login //Check if user is disabled. Then dont allow login
if ($user->isDisabled()) { if ($user->isDisabled()) {
throw new DisabledException(); //throw new DisabledException();
throw new CustomUserMessageAccountStatusException($this->translator->trans('user.login_error.user_disabled'));
} }
} }
} }

View file

@ -44,10 +44,22 @@ namespace App\Security\Voter;
use App\Entity\Attachments\Attachment; use App\Entity\Attachments\Attachment;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Services\UserSystem\PermissionManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
use function in_array; use function in_array;
class AttachmentVoter extends ExtendedVoter class AttachmentVoter extends ExtendedVoter
{ {
protected $security;
public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security)
{
parent::__construct($resolver, $entityManager);
$this->security = $security;
}
/** /**
* Similar to voteOnAttribute, but checking for the anonymous user is already done. * Similar to voteOnAttribute, but checking for the anonymous user is already done.
* The current user (or the anonymous user) is passed by $user. * The current user (or the anonymous user) is passed by $user.
@ -56,7 +68,31 @@ class AttachmentVoter extends ExtendedVoter
*/ */
protected function voteOnUser(string $attribute, $subject, User $user): bool protected function voteOnUser(string $attribute, $subject, User $user): bool
{ {
return $this->resolver->inherit($user, 'parts_attachments', $attribute) ?? false; //return $this->resolver->inherit($user, 'attachments', $attribute) ?? false;
//If the attachment has no element (which should not happen), we deny access, as we can not determine if the user is allowed to access the associated element
$target_element = $subject->getElement();
if (! $subject instanceof Attachment || null === $target_element) {
return false;
}
//Depending on the operation delegate either to the attachments element or to the attachment permission
switch ($attribute) {
//We can view the attachment if we can view the element
case 'read':
case 'view':
return $this->security->isGranted('read', $target_element);
//We can edit/create/delete the attachment if we can edit the element
case 'edit':
case 'create':
case 'delete':
return $this->security->isGranted('edit', $target_element);
case 'show_private':
return $this->resolver->inherit($user, 'attachments', 'show_private') ?? false;
}
throw new \RuntimeException('Encountered unknown attribute "'.$attribute.'" in AttachmentVoter!');
} }
/** /**
@ -70,7 +106,8 @@ class AttachmentVoter extends ExtendedVoter
protected function supports(string $attribute, $subject): bool protected function supports(string $attribute, $subject): bool
{ {
if (is_a($subject, Attachment::class, true)) { if (is_a($subject, Attachment::class, true)) {
return in_array($attribute, $this->resolver->listOperationsForPermission('parts_attachments'), false); //These are the allowed attributes
return in_array($attribute, ['read', 'view', 'edit', 'delete', 'create', 'show_private'], true);
} }
//Allow class name as subject //Allow class name as subject

View file

@ -44,7 +44,7 @@ namespace App\Security\Voter;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Repository\UserRepository; use App\Repository\UserRepository;
use App\Services\PermissionResolver; use App\Services\UserSystem\PermissionManager;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Authorization\Voter\Voter;
@ -55,9 +55,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
abstract class ExtendedVoter extends Voter abstract class ExtendedVoter extends Voter
{ {
protected EntityManagerInterface $entityManager; protected EntityManagerInterface $entityManager;
protected PermissionResolver $resolver; protected PermissionManager $resolver;
public function __construct(PermissionResolver $resolver, EntityManagerInterface $entityManager) public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager)
{ {
$this->resolver = $resolver; $this->resolver = $resolver;
$this->entityManager = $entityManager; $this->entityManager = $entityManager;
@ -72,7 +72,7 @@ abstract class ExtendedVoter extends Voter
return false; return false;
} }
// if the user is anonymous, we use the anonymous user. // if the user is anonymous (meaning $user is null), we use the anonymous user.
if (!$user instanceof User) { if (!$user instanceof User) {
/** @var UserRepository $repo */ /** @var UserRepository $repo */
$repo = $this->entityManager->getRepository(User::class); $repo = $this->entityManager->getRepository(User::class);

View file

@ -25,30 +25,60 @@ namespace App\Security\Voter;
use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Orderdetail;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Services\UserSystem\PermissionManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
class OrderdetailVoter extends ExtendedVoter class OrderdetailVoter extends ExtendedVoter
{ {
/** protected Security $security;
* @var string[] When this permsission are encountered, they are checked on part
*/ public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security)
protected const PART_PERMS = ['show_history', 'revert_element']; {
parent::__construct($resolver, $entityManager);
$this->security = $security;
}
protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element'];
protected function voteOnUser(string $attribute, $subject, User $user): bool protected function voteOnUser(string $attribute, $subject, User $user): bool
{ {
if (in_array($attribute, self::PART_PERMS, true)) { if (! is_a($subject, Orderdetail::class, true)) {
return $this->resolver->inherit($user, 'parts', $attribute) ?? false; throw new \RuntimeException('This voter can only handle Orderdetail objects!');
} }
return $this->resolver->inherit($user, 'parts_orderdetails', $attribute) ?? false; switch ($attribute) {
case 'read':
$operation = 'read';
break;
case 'edit': //As long as we can edit, we can also edit orderdetails
case 'create':
case 'delete':
$operation = 'edit';
break;
case 'show_history':
$operation = 'show_history';
break;
case 'revert_element':
$operation = 'revert_element';
break;
default:
throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!');
}
//If we have no part associated use the generic part permission
if (is_string($subject) || $subject->getPart() === null) {
return $this->resolver->inherit($user, 'parts', $operation) ?? false;
}
//Otherwise vote on the part
return $this->security->isGranted($attribute, $subject->getPart());
} }
protected function supports($attribute, $subject): bool protected function supports($attribute, $subject): bool
{ {
if (is_a($subject, Orderdetail::class, true)) { if (is_a($subject, Orderdetail::class, true)) {
return in_array($attribute, array_merge( return in_array($attribute, self::ALLOWED_PERMS, true);
self::PART_PERMS,
$this->resolver->listOperationsForPermission('parts_orderdetails')
), true);
} }
return false; return false;

View file

@ -0,0 +1,113 @@
<?php
namespace App\Security\Voter;
use App\Entity\Parameters\AbstractParameter;
use App\Entity\Parameters\AttachmentTypeParameter;
use App\Entity\Parameters\CategoryParameter;
use App\Entity\Parameters\CurrencyParameter;
use App\Entity\Parameters\DeviceParameter;
use App\Entity\Parameters\FootprintParameter;
use App\Entity\Parameters\GroupParameter;
use App\Entity\Parameters\ManufacturerParameter;
use App\Entity\Parameters\MeasurementUnitParameter;
use App\Entity\Parameters\PartParameter;
use App\Entity\Parameters\StorelocationParameter;
use App\Entity\Parameters\SupplierParameter;
use App\Entity\UserSystem\User;
use App\Services\UserSystem\PermissionManager;
use Doctrine\ORM\EntityManagerInterface;
use RuntimeException;
use Symfony\Component\Security\Core\Security;
class ParameterVoter extends ExtendedVoter
{
protected Security $security;
public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security)
{
$this->security = $security;
parent::__construct($resolver, $entityManager);
}
protected function voteOnUser(string $attribute, $subject, User $user): bool
{
//return $this->resolver->inherit($user, 'attachments', $attribute) ?? false;
if (!$subject instanceof AbstractParameter) {
return false;
}
//If the attachment has no element (which should not happen), we deny access, as we can not determine if the user is allowed to access the associated element
$target_element = $subject->getElement();
if ($target_element !== null) {
//Depending on the operation delegate either to the attachments element or to the attachment permission
switch ($attribute) {
//We can view the attachment if we can view the element
case 'read':
case 'view':
$operation = 'read';
break;
//We can edit/create/delete the attachment if we can edit the element
case 'edit':
case 'create':
case 'delete':
$operation = 'edit';
break;
case 'show_history':
$operation = 'show_history';
break;
case 'revert_element':
$operation = 'revert_element';
break;
default:
throw new RuntimeException('Unknown operation: '.$attribute);
}
return $this->security->isGranted($operation, $target_element);
}
//If we do not have a concrete element, we delegate to the different categories
if ($subject instanceof AttachmentTypeParameter) {
$param = 'attachment_types';
} elseif ($subject instanceof CategoryParameter) {
$param = 'categories';
} elseif ($subject instanceof CurrencyParameter) {
$param = 'currencies';
} elseif ($subject instanceof DeviceParameter) {
$param = 'devices';
} elseif ($subject instanceof FootprintParameter) {
$param = 'footprints';
} elseif ($subject instanceof GroupParameter) {
$param = 'groups';
} elseif ($subject instanceof ManufacturerParameter) {
$param = 'manufacturers';
} elseif ($subject instanceof MeasurementUnitParameter) {
$param = 'measurement_units';
} elseif ($subject instanceof PartParameter) {
$param = 'parts';
} elseif ($subject instanceof StorelocationParameter) {
$param = 'storelocations';
} elseif ($subject instanceof SupplierParameter) {
$param = 'suppliers';
} else {
throw new RuntimeException('Encountered unknown Parameter type: ' . get_class($subject));
}
return $this->resolver->inherit($user, $param, $attribute) ?? false;
}
protected function supports(string $attribute, $subject)
{
if (is_a($subject, AbstractParameter::class, true)) {
//These are the allowed attributes
return in_array($attribute, ['read', 'edit', 'delete', 'create', 'show_history', 'revert_element'], true);
}
//Allow class name as subject
return false;
}
}

View file

@ -25,30 +25,60 @@ namespace App\Security\Voter;
use App\Entity\Parts\PartLot; use App\Entity\Parts\PartLot;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Services\UserSystem\PermissionManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
class PartLotVoter extends ExtendedVoter class PartLotVoter extends ExtendedVoter
{ {
/** protected Security $security;
* @var string[] When this permsission are encountered, they are checked on part
*/ public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security)
protected const PART_PERMS = ['show_history', 'revert_element']; {
parent::__construct($resolver, $entityManager);
$this->security = $security;
}
protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element'];
protected function voteOnUser(string $attribute, $subject, User $user): bool protected function voteOnUser(string $attribute, $subject, User $user): bool
{ {
if (in_array($attribute, self::PART_PERMS, true)) { if (! is_a($subject, PartLot::class, true)) {
return $this->resolver->inherit($user, 'parts', $attribute) ?? false; throw new \RuntimeException('This voter can only handle PartLot objects!');
} }
return $this->resolver->inherit($user, 'parts_lots', $attribute) ?? false; switch ($attribute) {
case 'read':
$operation = 'read';
break;
case 'edit': //As long as we can edit, we can also edit orderdetails
case 'create':
case 'delete':
$operation = 'edit';
break;
case 'show_history':
$operation = 'show_history';
break;
case 'revert_element':
$operation = 'revert_element';
break;
default:
throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!');
}
//If we have no part associated use the generic part permission
if (is_string($subject) || $subject->getPart() === null) {
return $this->resolver->inherit($user, 'parts', $operation) ?? false;
}
//Otherwise vote on the part
return $this->security->isGranted($attribute, $subject->getPart());
} }
protected function supports($attribute, $subject): bool protected function supports($attribute, $subject): bool
{ {
if (is_a($subject, PartLot::class, true)) { if (is_a($subject, PartLot::class, true)) {
return in_array($attribute, array_merge( return in_array($attribute, self::ALLOWED_PERMS, true);
self::PART_PERMS,
$this->resolver->listOperationsForPermission('parts_lots')
), true);
} }
return false; return false;

View file

@ -57,13 +57,6 @@ class PartVoter extends ExtendedVoter
protected function supports($attribute, $subject): bool protected function supports($attribute, $subject): bool
{ {
if (is_a($subject, Part::class, true)) { if (is_a($subject, Part::class, true)) {
//Check if a sub permission should be checked -> $attribute has format name.edit
if (false !== strpos($attribute, '.')) {
[$perm, $op] = explode('.', $attribute);
return $this->resolver->isValidOperation('parts_'.$perm, $op);
}
return $this->resolver->isValidOperation('parts', $attribute); return $this->resolver->isValidOperation('parts', $attribute);
} }
@ -73,13 +66,6 @@ class PartVoter extends ExtendedVoter
protected function voteOnUser(string $attribute, $subject, User $user): bool protected function voteOnUser(string $attribute, $subject, User $user): bool
{ {
//Check for sub permissions
if (false !== strpos($attribute, '.')) {
[$perm, $op] = explode('.', $attribute);
return $this->resolver->inherit($user, 'parts_'.$perm, $op) ?? false;
}
//Null concealing operator means, that no //Null concealing operator means, that no
return $this->resolver->inherit($user, 'parts', $attribute) ?? false; return $this->resolver->inherit($user, 'parts', $attribute) ?? false;
} }

View file

@ -80,7 +80,14 @@ class PermissionVoter extends ExtendedVoter
$attribute = ltrim($attribute, '@'); $attribute = ltrim($attribute, '@');
[$perm, $op] = explode('.', $attribute); [$perm, $op] = explode('.', $attribute);
return $this->resolver->isValidOperation($perm, $op); $valid = $this->resolver->isValidOperation($perm, $op);
//if an invalid operation is encountered, throw an exception so the developer knows it
if(!$valid) {
throw new \RuntimeException('Encountered invalid permission operation "'.$op.'" for permission "'.$perm.'"!');
}
return true;
} }
return false; return false;

View file

@ -25,30 +25,60 @@ namespace App\Security\Voter;
use App\Entity\PriceInformations\Pricedetail; use App\Entity\PriceInformations\Pricedetail;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Services\UserSystem\PermissionManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
class PricedetailVoter extends ExtendedVoter class PricedetailVoter extends ExtendedVoter
{ {
/** protected Security $security;
* @var string[] When this permsission are encountered, they are checked on part
*/ public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security)
protected const PART_PERMS = ['show_history', 'revert_element']; {
parent::__construct($resolver, $entityManager);
$this->security = $security;
}
protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element'];
protected function voteOnUser(string $attribute, $subject, User $user): bool protected function voteOnUser(string $attribute, $subject, User $user): bool
{ {
if (in_array($attribute, self::PART_PERMS, true)) { if (!is_a($subject, Pricedetail::class, true)) {
return $this->resolver->inherit($user, 'parts', $attribute) ?? false; throw new \RuntimeException('This voter can only handle Pricedetails objects!');
} }
return $this->resolver->inherit($user, 'parts_prices', $attribute) ?? false; switch ($attribute) {
case 'read':
$operation = 'read';
break;
case 'edit': //As long as we can edit, we can also edit orderdetails
case 'create':
case 'delete':
$operation = 'edit';
break;
case 'show_history':
$operation = 'show_history';
break;
case 'revert_element':
$operation = 'revert_element';
break;
default:
throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!');
}
//If we have no part associated use the generic part permission
if (is_string($subject) || $subject->getOrderdetail() === null || $subject->getOrderdetail()->getPart() === null) {
return $this->resolver->inherit($user, 'parts', $operation) ?? false;
}
//Otherwise vote on the part
return $this->security->isGranted($attribute, $subject->getOrderdetail()->getPart());
} }
protected function supports($attribute, $subject): bool protected function supports($attribute, $subject): bool
{ {
if (is_a($subject, Pricedetail::class, true)) { if (is_a($subject, Pricedetail::class, true)) {
return in_array($attribute, array_merge( return in_array($attribute, self::ALLOWED_PERMS, true);
self::PART_PERMS,
$this->resolver->listOperationsForPermission('parts_prices')
), true);
} }
return false; return false;

View file

@ -0,0 +1,58 @@
<?php
namespace App\Services\Misc;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\ORM\EntityManagerInterface;
/**
* This service provides db independent information about the database.
*/
class DBInfoHelper
{
protected Connection $connection;
protected EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->connection = $entityManager->getConnection();
}
/**
* Returns the database type of the used database.
* @return string|null Returns 'mysql' for MySQL/MariaDB and 'sqlite' for SQLite. Returns null if unknown type
*/
public function getDatabaseType(): ?string
{
if ($this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
return 'mysql';
}
if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
return 'sqlite';
}
return null;
}
/**
* Returns the database version of the used database.
* @return string|null
* @throws \Doctrine\DBAL\Exception
*/
public function getDatabaseVersion(): ?string
{
if ($this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
return $this->connection->fetchOne('SELECT VERSION()');
}
if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
return $this->connection->fetchOne('SELECT sqlite_version()');
}
return null;
}
}

View file

@ -76,9 +76,11 @@ final class PartsTableActionHandler
switch ($action) { switch ($action) {
case 'favorite': case 'favorite':
$this->denyAccessUnlessGranted('change_favorite', $part);
$part->setFavorite(true); $part->setFavorite(true);
break; break;
case 'unfavorite': case 'unfavorite':
$this->denyAccessUnlessGranted('change_favorite', $part);
$part->setFavorite(false); $part->setFavorite(false);
break; break;
case 'delete': case 'delete':
@ -86,19 +88,19 @@ final class PartsTableActionHandler
$this->entityManager->remove($part); $this->entityManager->remove($part);
break; break;
case 'change_category': case 'change_category':
$this->denyAccessUnlessGranted('category.edit', $part); $this->denyAccessUnlessGranted('@categories.read');
$part->setCategory($this->entityManager->find(Category::class, $target_id)); $part->setCategory($this->entityManager->find(Category::class, $target_id));
break; break;
case 'change_footprint': case 'change_footprint':
$this->denyAccessUnlessGranted('footprint.edit', $part); $this->denyAccessUnlessGranted('@footprints.read');
$part->setFootprint(null === $target_id ? null : $this->entityManager->find(Footprint::class, $target_id)); $part->setFootprint(null === $target_id ? null : $this->entityManager->find(Footprint::class, $target_id));
break; break;
case 'change_manufacturer': case 'change_manufacturer':
$this->denyAccessUnlessGranted('manufacturer.edit', $part); $this->denyAccessUnlessGranted('@manufacturers.read');
$part->setManufacturer(null === $target_id ? null : $this->entityManager->find(Manufacturer::class, $target_id)); $part->setManufacturer(null === $target_id ? null : $this->entityManager->find(Manufacturer::class, $target_id));
break; break;
case 'change_unit': case 'change_unit':
$this->denyAccessUnlessGranted('unit.edit', $part); $this->denyAccessUnlessGranted('@measurement_units.read');
$part->setPartUnit(null === $target_id ? null : $this->entityManager->find(MeasurementUnit::class, $target_id)); $part->setPartUnit(null === $target_id ? null : $this->entityManager->find(MeasurementUnit::class, $target_id));
break; break;

View file

@ -42,7 +42,7 @@ declare(strict_types=1);
namespace App\Services\TranslationExtractor; namespace App\Services\TranslationExtractor;
use App\Services\PermissionResolver; use App\Services\UserSystem\PermissionManager;
use Symfony\Component\Translation\Extractor\ExtractorInterface; use Symfony\Component\Translation\Extractor\ExtractorInterface;
use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\MessageCatalogue;
@ -55,7 +55,7 @@ final class PermissionExtractor implements ExtractorInterface
private array $permission_structure; private array $permission_structure;
private bool $finished = false; private bool $finished = false;
public function __construct(PermissionResolver $resolver) public function __construct(PermissionManager $resolver)
{ {
$this->permission_structure = $resolver->getPermissionStructure(); $this->permission_structure = $resolver->getPermissionStructure();
} }

View file

@ -242,12 +242,15 @@ class ToolsTreeBuilder
protected function getShowNodes(): array protected function getShowNodes(): array
{ {
$show_nodes = []; $show_nodes = [];
$show_nodes[] = (new TreeViewNode(
$this->translator->trans('tree.tools.show.all_parts'),
$this->urlGenerator->generate('parts_show_all')
))->setIcon('fa-fw fa-treeview fa-solid fa-globe');
if ($this->security->isGranted('read', new PartAttachment())) { if ($this->security->isGranted('@parts.read')) {
$show_nodes[] = (new TreeViewNode(
$this->translator->trans('tree.tools.show.all_parts'),
$this->urlGenerator->generate('parts_show_all')
))->setIcon('fa-fw fa-treeview fa-solid fa-globe');
}
if ($this->security->isGranted('@attachments.list_attachments')) {
$show_nodes[] = (new TreeViewNode( $show_nodes[] = (new TreeViewNode(
$this->translator->trans('tree.tools.show.all_attachments'), $this->translator->trans('tree.tools.show.all_attachments'),
$this->urlGenerator->generate('attachment_list') $this->urlGenerator->generate('attachment_list')
@ -291,6 +294,13 @@ class ToolsTreeBuilder
))->setIcon('fa-fw fa-treeview fa-solid fa-binoculars'); ))->setIcon('fa-fw fa-treeview fa-solid fa-binoculars');
} }
if ($this->security->isGranted('@system.server_infos')) {
$nodes[] = (new TreeViewNode(
$this->translator->trans('tools.server_infos.title'),
$this->urlGenerator->generate('tools_server_infos')
))->setIcon('fa-fw fa-treeview fa-solid fa-database');
}
return $nodes; return $nodes;
} }
} }

View file

@ -40,7 +40,7 @@ declare(strict_types=1);
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
namespace App\Services; namespace App\Services\UserSystem;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use DateTime; use DateTime;
@ -50,7 +50,6 @@ use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Address;
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\PasswordHasher\PasswordHasherInterface; use Symfony\Component\PasswordHasher\PasswordHasherInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;

View file

@ -40,7 +40,7 @@ declare(strict_types=1);
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
namespace App\Services; namespace App\Services\UserSystem;
use App\Configuration\PermissionsConfiguration; use App\Configuration\PermissionsConfiguration;
use App\Entity\UserSystem\Group; use App\Entity\UserSystem\Group;
@ -52,7 +52,12 @@ use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
class PermissionResolver /**
* This class manages the permissions of users and groups.
* Permissions are defined in the config/permissions.yaml file, and are parsed and resolved by this class using the
* user and hierachical group PermissionData information.
*/
class PermissionManager
{ {
protected $permission_structure; protected $permission_structure;
@ -81,6 +86,7 @@ class PermissionResolver
* Check if a user/group is allowed to do the specified operation for the permission. * Check if a user/group is allowed to do the specified operation for the permission.
* *
* See permissions.yaml for valid permission operation combinations. * See permissions.yaml for valid permission operation combinations.
* This function does not check, if the permission is valid!
* *
* @param HasPermissionsInterface $user the user/group for which the operation should be checked * @param HasPermissionsInterface $user the user/group for which the operation should be checked
* @param string $permission the name of the permission for which should be checked * @param string $permission the name of the permission for which should be checked
@ -92,18 +98,13 @@ class PermissionResolver
public function dontInherit(HasPermissionsInterface $user, string $permission, string $operation): ?bool public function dontInherit(HasPermissionsInterface $user, string $permission, string $operation): ?bool
{ {
//Get the permissions from the user //Get the permissions from the user
$perm_list = $user->getPermissions(); return $user->getPermissions()->getPermissionValue($permission, $operation);
//Determine bit number using our configuration
$bit = $this->permission_structure['perms'][$permission]['operations'][$operation]['bit'];
return $perm_list->getPermissionValue($permission, $bit);
} }
/** /**
* Checks if a user is allowed to do the specified operation for the permission. * Checks if a user is allowed to do the specified operation for the permission.
* In contrast to dontInherit() it tries to resolve the inherit values, of the user, by going upwards in the * In contrast to dontInherit() it tries to resolve to inherit values, of the user, by going upwards in the
* hierachy (user -> group -> parent group -> so on). But even in this case it is possible, that the inherit value * hierarchy (user -> group -> parent group -> so on). But even in this case it is possible, that to inherit value
* could be resolved, and this function returns null. * could be resolved, and this function returns null.
* *
* In that case the voter should set it manually to false by using ?? false. * In that case the voter should set it manually to false by using ?? false.
@ -153,10 +154,12 @@ class PermissionResolver
//Get the permissions from the user //Get the permissions from the user
$perm_list = $user->getPermissions(); $perm_list = $user->getPermissions();
//Determine bit number using our configuration //Check if the permission/operation combination is valid
$bit = $this->permission_structure['perms'][$permission]['operations'][$operation]['bit']; if (! $this->isValidOperation($permission, $operation)) {
throw new InvalidArgumentException(sprintf('The permission/operation combination "%s.%s" is not valid!', $permission, $operation));
}
$perm_list->setPermissionValue($permission, $bit, $new_val); $perm_list->setPermissionValue($permission, $operation, $new_val);
} }
/** /**
@ -206,13 +209,90 @@ class PermissionResolver
isset($this->permission_structure['perms'][$permission]['operations'][$operation]); isset($this->permission_structure['perms'][$permission]['operations'][$operation]);
} }
/**
* This functions sets all operations mentioned in the alsoSet value of a permission, so that the structure is always valid.
* @param HasPermissionsInterface $user
* @return void
*/
public function ensureCorrectSetOperations(HasPermissionsInterface $user): void
{
//If we have changed anything on the permission structure due to the alsoSet value, this becomes true, so we
//redo the whole process, to ensure that all alsoSet values are set recursively.
$anything_changed = false;
do {
$anything_changed = false; //Reset the variable for the next iteration
//Check for each permission and operation, for an alsoSet attribute
foreach ($this->permission_structure['perms'] as $perm_key => $permission) {
foreach ($permission['operations'] as $op_key => $op) {
if (!empty($op['alsoSet']) &&
true === $this->dontInherit($user, $perm_key, $op_key)) {
//Set every op listed in also Set
foreach ($op['alsoSet'] as $set_also) {
//If the alsoSet value contains a dot then we set the operation of another permission
if (false !== strpos($set_also, '.')) {
[$set_perm, $set_op] = explode('.', $set_also);
} else {
//Else we set the operation of the same permission
[$set_perm, $set_op] = [$perm_key, $set_also];
}
//Check if we change the value of the permission
if ($this->dontInherit($user, $set_perm, $set_op) !== true) {
$this->setPermission($user, $set_perm, $set_op, true);
//Mark the change, so we redo the whole process
$anything_changed = true;
}
}
}
}
}
} while($anything_changed);
}
/**
* Sets all possible operations of all possible permissions of the given entity to the given value.
* @param HasPermissionsInterface $perm_holder
* @param bool|null $new_value
* @return void
*/
public function setAllPermissions(HasPermissionsInterface $perm_holder, ?bool $new_value): void
{
foreach ($this->permission_structure['perms'] as $perm_key => $permission) {
foreach ($permission['operations'] as $op_key => $op) {
$this->setPermission($perm_holder, $perm_key, $op_key, $new_value);
}
}
}
/**
* Sets all operations of the given permissions to the given value.
* Please note that you have to call ensureCorrectSetOperations() after this function, to ensure that all alsoSet values are set.
*
* @param HasPermissionsInterface $perm_holder
* @param string $permission
* @param bool|null $new_value
* @return void
*/
public function setAllOperationsOfPermission(HasPermissionsInterface $perm_holder, string $permission, ?bool $new_value): void
{
if (!$this->isValidPermission($permission)) {
throw new InvalidArgumentException(sprintf('A permission with that name is not existing! Got %s.', $permission));
}
foreach ($this->permission_structure['perms'][$permission]['operations'] as $op_key => $op) {
$this->setPermission($perm_holder, $permission, $op_key, $new_value);
}
}
protected function generatePermissionStructure() protected function generatePermissionStructure()
{ {
$cache = new ConfigCache($this->cache_file, $this->is_debug); $cache = new ConfigCache($this->cache_file, $this->is_debug);
//Check if the cache is fresh, else regenerate it. //Check if the cache is fresh, else regenerate it.
if (!$cache->isFresh()) { if (!$cache->isFresh()) {
$permission_file = __DIR__.'/../../config/permissions.yaml'; $permission_file = __DIR__.'/../../../config/permissions.yaml';
//Read the permission config file... //Read the permission config file...
$config = Yaml::parse( $config = Yaml::parse(

View file

@ -0,0 +1,151 @@
<?php
namespace App\Services\UserSystem;
use App\Entity\UserSystem\PermissionData;
use App\Security\Interfaces\HasPermissionsInterface;
class PermissionPresetsHelper
{
public const PRESET_ALL_INHERIT = 'all_inherit';
public const PRESET_ALL_FORBID = 'all_forbid';
public const PRESET_ALL_ALLOW = 'all_allow';
public const PRESET_READ_ONLY = 'read_only';
public const PRESET_EDITOR = 'editor';
public const PRESET_ADMIN = 'admin';
private PermissionManager $permissionResolver;
public function __construct(PermissionManager $permissionResolver)
{
$this->permissionResolver = $permissionResolver;
}
/**
* Apply the given preset to the permission holding entity (like a user)
* The permission data will be reset during the process and then the preset will be applied.
*
* @param string $preset_name The name of the preset to use
* @return HasPermissionsInterface
*/
public function applyPreset(HasPermissionsInterface $perm_holder, string $preset_name): HasPermissionsInterface
{
//We need to reset the permission data first (afterwards all values are inherit)
$perm_holder->getPermissions()->resetPermissions();
switch($preset_name) {
case self::PRESET_ALL_INHERIT:
//Do nothing, all values are inherit after reset
break;
case self::PRESET_ALL_FORBID:
$this->allForbid($perm_holder);
break;
case self::PRESET_ALL_ALLOW:
$this->allAllow($perm_holder);
break;
case self::PRESET_READ_ONLY:
$this->readOnly($perm_holder);
break;
case self::PRESET_EDITOR:
$this->editor($perm_holder);
break;
case self::PRESET_ADMIN:
$this->admin($perm_holder);
break;
default:
throw new \InvalidArgumentException('Unknown permission preset name: '.$preset_name);
}
//Ensure that permissions are valid (alsoSet values are set), this allows us to use the permission inheritance system to keep the presets short
$this->permissionResolver->ensureCorrectSetOperations($perm_holder);
return $perm_holder;
}
private function admin(HasPermissionsInterface $perm_holder): void
{
//Apply everything from editor permission
$this->editor($perm_holder);
//Allow user and group access
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'users', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'groups', PermissionData::ALLOW);
//Allow access to system log and server infos
$this->permissionResolver->setPermission($perm_holder, 'system', 'show_logs', PermissionData::ALLOW);
$this->permissionResolver->setPermission($perm_holder, 'system', 'server_infos', PermissionData::ALLOW);
}
private function editor(HasPermissionsInterface $permHolder): HasPermissionsInterface
{
//Apply everything from read-only
$this->readOnly($permHolder);
//Set datastructures
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'parts', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'categories', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'storelocations', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'footprints', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'manufacturers', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'attachment_types', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'currencies', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'measurement_units', PermissionData::ALLOW);
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'suppliers', PermissionData::ALLOW);
//Attachments permissions
$this->permissionResolver->setPermission($permHolder, 'attachments', 'show_private', PermissionData::ALLOW);
//Labels permissions (allow all except use twig)
$this->permissionResolver->setAllOperationsOfPermission($permHolder, 'labels', PermissionData::ALLOW);
$this->permissionResolver->setPermission($permHolder,'labels', 'use_twig', PermissionData::INHERIT);
//Self permissions
$this->permissionResolver->setPermission($permHolder, 'self', 'edit_infos', PermissionData::ALLOW);
//Various other permissions
$this->permissionResolver->setPermission($permHolder, 'tools', 'lastActivity', PermissionData::ALLOW);
return $permHolder;
}
private function readOnly(HasPermissionsInterface $perm_holder): HasPermissionsInterface
{
//It is sufficient to only set the read operation to allow, read operations for datastructures are inherited
$this->permissionResolver->setPermission($perm_holder, 'parts', 'read', PermissionData::ALLOW);
//Set tools permissions
$this->permissionResolver->setPermission($perm_holder, 'tools', 'statistics', PermissionData::ALLOW);
$this->permissionResolver->setPermission($perm_holder, 'tools', 'label_scanner', PermissionData::ALLOW);
$this->permissionResolver->setPermission($perm_holder, 'tools', 'reel_calculator', PermissionData::ALLOW);
//Set attachments permissions
$this->permissionResolver->setPermission($perm_holder, 'attachments', 'list_attachments', PermissionData::ALLOW);
//Set user (self) permissions
$this->permissionResolver->setPermission($perm_holder, 'self', 'show_permissions', PermissionData::ALLOW);
//Label permissions
$this->permissionResolver->setPermission($perm_holder, 'labels', 'create_labels', PermissionData::ALLOW);
$this->permissionResolver->setPermission($perm_holder, 'labels', 'edit_options', PermissionData::ALLOW);
$this->permissionResolver->setPermission($perm_holder, 'labels', 'read_profiles', PermissionData::ALLOW);
//Set devices permissions
$this->permissionResolver->setPermission($perm_holder, 'devices', 'read', PermissionData::ALLOW);
return $perm_holder;
}
private function AllForbid(HasPermissionsInterface $perm_holder): HasPermissionsInterface
{
$this->permissionResolver->setAllPermissions($perm_holder, PermissionData::DISALLOW);
return $perm_holder;
}
private function AllAllow(HasPermissionsInterface $perm_holder): HasPermissionsInterface
{
$this->permissionResolver->setAllPermissions($perm_holder, PermissionData::ALLOW);
return $perm_holder;
}
}

View file

@ -40,7 +40,7 @@ declare(strict_types=1);
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
namespace App\Services\TFA; namespace App\Services\UserSystem\TFA;
use Exception; use Exception;
use RuntimeException; use RuntimeException;

View file

@ -40,7 +40,7 @@ declare(strict_types=1);
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
namespace App\Services\TFA; namespace App\Services\UserSystem\TFA;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;

View file

@ -44,7 +44,7 @@ namespace App\Validator\Constraints;
use App\Entity\UserSystem\Group; use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Services\PermissionResolver; use App\Services\UserSystem\PermissionManager;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@ -53,12 +53,12 @@ use Symfony\Component\Validator\ConstraintValidator;
class NoLockoutValidator extends ConstraintValidator class NoLockoutValidator extends ConstraintValidator
{ {
protected PermissionResolver $resolver; protected PermissionManager $resolver;
protected array $perm_structure; protected array $perm_structure;
protected Security $security; protected Security $security;
protected EntityManagerInterface $entityManager; protected EntityManagerInterface $entityManager;
public function __construct(PermissionResolver $resolver, Security $security, EntityManagerInterface $entityManager) public function __construct(PermissionManager $resolver, Security $security, EntityManagerInterface $entityManager)
{ {
$this->resolver = $resolver; $this->resolver = $resolver;
$this->perm_structure = $resolver->getPermissionStructure(); $this->perm_structure = $resolver->getPermissionStructure();

View file

@ -43,20 +43,19 @@ declare(strict_types=1);
namespace App\Validator\Constraints; namespace App\Validator\Constraints;
use App\Security\Interfaces\HasPermissionsInterface; use App\Security\Interfaces\HasPermissionsInterface;
use App\Services\PermissionResolver; use App\Services\UserSystem\PermissionManager;
use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\ConstraintValidator;
class ValidPermissionValidator extends ConstraintValidator class ValidPermissionValidator extends ConstraintValidator
{ {
protected PermissionResolver $resolver; protected PermissionManager $resolver;
protected array $perm_structure; protected array $perm_structure;
public function __construct(PermissionResolver $resolver) public function __construct(PermissionManager $resolver)
{ {
$this->resolver = $resolver; $this->resolver = $resolver;
$this->perm_structure = $resolver->getPermissionStructure();
} }
/** /**
@ -74,17 +73,6 @@ class ValidPermissionValidator extends ConstraintValidator
/** @var HasPermissionsInterface $perm_holder */ /** @var HasPermissionsInterface $perm_holder */
$perm_holder = $this->context->getObject(); $perm_holder = $this->context->getObject();
//Check for each permission and operation, for an alsoSet attribute $this->resolver->ensureCorrectSetOperations($perm_holder);
foreach ($this->perm_structure['perms'] as $perm_key => $permission) {
foreach ($permission['operations'] as $op_key => $op) {
if (!empty($op['alsoSet']) &&
true === $this->resolver->dontInherit($perm_holder, $perm_key, $op_key)) {
//Set every op listed in also Set
foreach ($op['alsoSet'] as $set_also) {
$this->resolver->setPermission($perm_holder, $perm_key, $set_also, true);
}
}
}
}
} }
} }

View file

@ -12,6 +12,7 @@
{% block additional_panes %} {% block additional_panes %}
<div class="tab-pane" id="tab_permissions"> <div class="tab-pane" id="tab_permissions">
<input type="hidden" name="_token" value="{{ csrf_token('group' ~ entity.id) }}">
{{ form_row(form.permissions) }} {{ form_row(form.permissions) }}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -4,7 +4,7 @@
{% if multi_checkbox %} {% if multi_checkbox %}
<div class="form-check"> <div class="form-check">
<input type="checkbox" class="form-check-input tristate permission_multicheckbox" id="mulit_check_{{ form.vars.label }}"> <input type="checkbox" class="form-check-input tristate permission-checkbox permission_multicheckbox" id="mulit_check_{{ form.vars.label }}">
<label class="form-check-label" for="mulit_check_{{ form.vars.label }}"> <label class="form-check-label" for="mulit_check_{{ form.vars.label }}">
<b>{{ form.vars.label | trans }}</b> <b>{{ form.vars.label | trans }}</b>
</label> </label>
@ -48,26 +48,46 @@
{% block permissions_row %} {% block permissions_row %}
{{ form_errors(form) }} {{ form_errors(form) }}
{% if show_legend %} <div class="row mb-2">
<div class="mb-2"> {% if show_legend %}
<label><b>{% trans %}permission.legend.title{% endtrans %}:</b></label> <div class="col">
<div> <label><b>{% trans %}permission.legend.title{% endtrans %}:</b></label>
<div class="form-check form-check-inline"> <div>
<input type="checkbox" class="form-check-input" disabled> <div class="form-check form-check-inline">
<label class="form-check-label opacity-100">{% trans %}permission.legend.disallow{% endtrans %}</label> <input type="checkbox" class="form-check-input permission-checkbox" disabled>
</div> <label class="form-check-label opacity-100">{% trans %}permission.legend.disallow{% endtrans %}</label>
<div class="form-check form-check-inline"> </div>
<input class="form-check-input" type="checkbox" disabled> <div class="form-check form-check-inline">
<label class="form-check-label opacity-100">{% trans %}permission.legend.allow{% endtrans %}</label> <input class="form-check-input permission-checkbox" type="checkbox" checked disabled>
</div> <label class="form-check-label opacity-100">{% trans %}permission.legend.allow{% endtrans %}</label>
<div class="form-check form-check-inline"> </div>
<input type="checkbox" class="tristate form-check-input" indeterminate="indeterminate" disabled> <div class="form-check form-check-inline">
<label class="form-check-label opacity-100">{% trans %}permission.legend.inherit{% endtrans %}</label> <input type="checkbox" class="tristate form-check-input permission-checkbox" indeterminate="indeterminate" value="indeterminate" disabled>
<label class="form-check-label opacity-100">{% trans %}permission.legend.inherit{% endtrans %}</label>
</div>
</div> </div>
</div> </div>
</div> {% endif %}
{% endif %} {% if show_presets %}
<div class="col text-end">
<div class="btn-group">
<button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
{% trans %}permission.preset.button{% endtrans %}
</button>
<ul class="dropdown-menu">
<li><button type="submit" name="permission_preset" value="read_only" class="dropdown-item" >{% trans %}permission.preset.read_only{% endtrans%} <br><small class="text-muted">{% trans %}permission.preset.read_only.desc{% endtrans%}</small></button></li>
<li><button type="submit" name="permission_preset" value="editor" class="dropdown-item" >{% trans %}permission.preset.editor{% endtrans%} <br><small class="text-muted">{% trans %}permission.preset.editor.desc{% endtrans%}</small></button></li>
<li><button type="submit" name="permission_preset" value="admin" class="dropdown-item" >{% trans %}permission.preset.admin{% endtrans%} <br><small class="text-muted">{% trans %}permission.preset.admin.desc{% endtrans%}</small></button></li>
<li><hr class="dropdown-divider"></li>
<li><button type="submit" name="permission_preset" value="all_inherit" class="dropdown-item" >{% trans %}permission.preset.all_inherit{% endtrans%}<br><small class="text-muted">{% trans %}permission.preset.all_inherit.desc{% endtrans%}</small></button></li>
<li><button type="submit" name="permission_preset" value="all_forbid" class="dropdown-item" >{% trans %}permission.preset.all_forbid{% endtrans%}<br><small class="text-muted">{% trans %}permission.preset.all_forbid.desc{% endtrans%}</small></button></li>
<li><button type="submit" name="permission_preset" value="all_allow" class="dropdown-item" >{% trans %}permission.preset.all_allow{% endtrans%}<br><small class="text-muted">{% trans %}permission.preset.all_allow.desc{% endtrans%}</small></button></li>
</ul>
</div>
</div>
{% endif %}
</div>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
{% for group in form %} {% for group in form %}

View file

@ -11,7 +11,7 @@
</table> </table>
<button type="button" class="btn btn-success" {{ collection.create_btn() }} <button type="button" class="btn btn-success" {{ collection.create_btn() }}
{% if not is_granted('lots.create', part) %}disabled{% endif %}> {% if not is_granted('edit', part) %}disabled{% endif %}>
<i class="fas fa-plus-square fa-fw"></i> <i class="fas fa-plus-square fa-fw"></i>
{% trans %}part_lot.create{% endtrans %} {% trans %}part_lot.create{% endtrans %}
</button> </button>

View file

@ -6,12 +6,12 @@
<table class="table table-striped table-sm table-responsive-md" id="orderdetails_table" {{ collection.target() }}> <table class="table table-striped table-sm table-responsive-md" id="orderdetails_table" {{ collection.target() }}>
<tbody> <tbody>
{% for detail in form.orderdetails %} {% for detail in form.orderdetails %}
{{ form_widget(detail, {'disable_delete' : not is_granted('orderdetails.delete', part)}) }} {{ form_widget(detail) }}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<button type="button" class="btn btn-success" {{ collection.create_btn() }} {% if not is_granted('orderdetails.create', part) %}disabled{% endif %}> <button type="button" class="btn btn-success" {{ collection.create_btn() }}>
<i class="fas fa-plus-square fa-fw"></i> <i class="fas fa-plus-square fa-fw"></i>
{% trans %}orderdetail.create{% endtrans %} {% trans %}orderdetail.create{% endtrans %}
</button> </button>

View file

@ -24,7 +24,7 @@
</tbody> </tbody>
</table> </table>
<button type="button" class="btn btn-success" {% if not is_granted('parameters.create', part) %}disabled{% endif %} {{ collection.create_btn() }}> <button type="button" class="btn btn-success" {% if not is_granted('edit', part) %}disabled{% endif %} {{ collection.create_btn() }}>
<i class="fas fa-plus-square fa-fw"></i> <i class="fas fa-plus-square fa-fw"></i>
{% trans %}specification.create{% endtrans %} {% trans %}specification.create{% endtrans %}
</button> </button>

View file

@ -15,7 +15,7 @@
<td>{{ form_widget(form.price_related_quantity, {'attr': {'class': 'form-control-sm'}}) }} {{ form_errors(form.price_related_quantity) }}</td> <td>{{ form_widget(form.price_related_quantity, {'attr': {'class': 'form-control-sm'}}) }} {{ form_errors(form.price_related_quantity) }}</td>
<td> <td>
<button type="button" class="btn btn-danger order_btn_delete btn-sm" title="{% trans %}orderdetail.delete{% endtrans %}" <button type="button" class="btn btn-danger order_btn_delete btn-sm" title="{% trans %}orderdetail.delete{% endtrans %}"
{{ collection.delete_btn() }} {% if not is_granted('@parts_prices.delete') %}disabled{% endif %}> {{ collection.delete_btn() }}>
<i class="fas fa-trash-alt fa-fw"></i> <i class="fas fa-trash-alt fa-fw"></i>
</button> </button>
{{ form_errors(form) }} {{ form_errors(form) }}
@ -50,15 +50,14 @@
</tbody> </tbody>
</table> </table>
<button type="button" class="btn btn-success" {{ collection.create_pricedetail_btn() }} {% if not is_granted('@parts_prices.create') %}disabled{% endif %}> <button type="button" class="btn btn-success" {{ collection.create_pricedetail_btn() }}>
<i class="fas fa-plus-square fa-fw"></i> <i class="fas fa-plus-square fa-fw"></i>
{% trans %}pricedetail.create{% endtrans %} {% trans %}pricedetail.create{% endtrans %}
</button> </button>
</div> </div>
</td> </td>
<td> <td>
<button type="button" class="btn btn-danger order_btn_delete" {{ collection.delete_btn() }} title="{% trans %}orderdetail.delete{% endtrans %}" <button type="button" class="btn btn-danger order_btn_delete" {{ collection.delete_btn() }} title="{% trans %}orderdetail.delete{% endtrans %}">
{% if not is_granted('@parts_orderdetails.delete') %}disabled{% endif %}>
<i class="fas fa-trash-alt fa-fw"></i> <i class="fas fa-trash-alt fa-fw"></i>
</button> </button>
{{ form_errors(form) }} {{ form_errors(form) }}
@ -93,8 +92,7 @@
{{ form_widget(form) }} {{ form_widget(form) }}
</td> </td>
<td> <td>
<button type="button" class="btn btn-danger lot_btn_delete" {{ collection.delete_btn() }} <button type="button" class="btn btn-danger lot_btn_delete" {{ collection.delete_btn() }}>
{% if not is_granted('lots.delete', form.parent.parent.vars.data) %}disabled{% endif %}>
<i class="fas fa-trash-alt fa-fw"></i> <i class="fas fa-trash-alt fa-fw"></i>
{% trans %}part_lot.delete{% endtrans %} {% trans %}part_lot.delete{% endtrans %}
</button> </button>
@ -111,7 +109,7 @@
{{ form_widget(form) }} {{ form_widget(form) }}
</td> </td>
<td> <td>
<button type="button" class="btn btn-danger lot_btn_delete" {{ collection.delete_btn() }} {# {% if not is_granted('attachments.delete', part) %}disabled{% endif %}#}> <button type="button" class="btn btn-danger lot_btn_delete" {{ collection.delete_btn() }}>
<i class="fas fa-trash-alt fa-fw"></i> <i class="fas fa-trash-alt fa-fw"></i>
{% trans %}attachment.delete{% endtrans %} {% trans %}attachment.delete{% endtrans %}
</button> </button>

View file

@ -9,7 +9,7 @@
<tr> <tr>
<td>{% trans %}user.creating_user{% endtrans %}</td> <td>{% trans %}user.creating_user{% endtrans %}</td>
<td>{% if is_granted('show_users', part) %} <td>{% if is_granted('show_history', part) %}
{{ creating_user(part).fullName(true) ?? 'Unknown'|trans }} {{ creating_user(part).fullName(true) ?? 'Unknown'|trans }}
{% else %} {% else %}
{% trans %}accessDenied{% endtrans %} {% trans %}accessDenied{% endtrans %}
@ -24,7 +24,7 @@
<tr> <tr>
<td>{% trans %}user.last_editing_user{% endtrans %}</td> <td>{% trans %}user.last_editing_user{% endtrans %}</td>
<td>{% if is_granted('show_users', part) %} <td>{% if is_granted('show_history', part) %}
{{ last_editing_user(part).fullName(true) ?? 'Unknown'|trans }} {{ last_editing_user(part).fullName(true) ?? 'Unknown'|trans }}
{% else %} {% else %}
{% trans %}accessDenied{% endtrans %} {% trans %}accessDenied{% endtrans %}

View file

@ -0,0 +1,13 @@
{% import "helper.twig" as helper %}
<table class="table table-sm table-striped table-hover table-bordered">
<tbody>
<tr>
<td>Database type</td>
<td>{{ db_type }}</td>
</tr>
<tr>
<td>Database Server Version</td>
<td>{{ db_version }}</td>
</tr>
</tbody>
</table>

View file

@ -0,0 +1,66 @@
{% import "helper.twig" as helper %}
<table class="table table-sm table-striped table-hover table-bordered">
<tbody>
<tr>
<td>Part-DB Version</td>
<td>{{ shivas_app_version }} {% if git_branch is not empty or git_commit is not empty %}({{ git_branch ?? '' }}/{{ git_commit ?? '' }}){% endif %}</td>
</tr>
<tr>
<td>Symfony environment</td>
<td>{{ enviroment }} (Debug: {{ helper.boolean_badge(is_debug) }})</td>
</tr>
<tr>
<td>Part-DB Instance name</td>
<td>{{ partdb_title }}</td>
</tr>
<tr>
<td>Default locale</td>
<td>{{ default_locale | locale_name }} ({{ default_locale }})</td>
</tr>
<tr>
<td>Default timezone</td>
<td>{{ default_timezone }}</td>
</tr>
<tr>
<td>Default Currency</td>
<td>{{ default_currency | currency_name }} ({{ default_currency }}, {{ default_currency | currency_symbol }})</td>
</tr>
<tr>
<td>Default theme</td>
<td>{{ default_theme | default('bootstrap') }}</td>
</tr>
<tr>
<td>Enabled locales</td>
<td>{{ helper.array_to_tags(enabled_locales | map(l => "#{l|locale_name} (#{l})")) }}</td>
</tr>
<tr>
<td>Demo Mode</td>
<td>{{ helper.boolean_badge(demo_mode) }}</td>
</tr>
<tr>
<td>GPDR Compliance Mode</td>
<td>{{ helper.boolean_badge(gpdr_compliance) }}</td>
</tr>
<tr>
<td>Use Gravatar</td>
<td>{{ helper.boolean_badge(use_gravatar) }}</td>
</tr>
<tr>
<td>Password Reset via Email enabled</td>
<td>{{ helper.boolean_badge(email_password_reset) }}</td>
</tr>
<tr>
<td>Configured E-Mail sender</td>
<td>{{ email_sender }} ({{ email_sender_name }})</td>
</tr>
<tr>
<td>Allow server-side download of attachments</td>
<td>{{ helper.boolean_badge(allow_attachments_downloads) }}</td>
</tr>
<tr>
<td>Detailed error pages enabled</td>
<td>{{ helper.boolean_badge(detailed_error_pages) }} (Admin Contact email: {{ error_page_admin_email }})</td>
</tr>
</tbody>
</table>

View file

@ -0,0 +1,25 @@
{% import "helper.twig" as helper %}
<table class="table table-sm table-striped table-hover table-bordered">
<tbody>
<tr>
<td>PHP version</td>
<td>{{ php_version }} (SAPI: {{ php_sapi }})</td>
</tr>
<tr>
<td>Server Operating System</td>
<td>{{ php_uname }}</td>
</tr>
<tr>
<td>Opcache enabled</td>
<td>{{ helper.boolean_badge(php_opcache_enabled) }}</td>
</tr>
<tr>
<td>Maximum upload sizee (upload_max_filesize / post_max_size)</td>
<td>{{ php_upload_max_filesize }} / {{ php_post_max_size }}</td>
</tr>
<tr>
<td>PHP extensions</td>
<td>{{ helper.array_to_tags(php_extensions) }}</td>
</tr>
</tbody>
</table>

View file

@ -0,0 +1,30 @@
{% extends "main_card.html.twig" %}
{% block title %}{% trans %}tools.server_infos.title{% endtrans %}{% endblock %}
{% block card_title %}
<i class="fas fa-database"></i> {% trans %}tools.server_infos.title{% endtrans %}
{% endblock %}
{% block card_content %}
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="server_infos-partdb-tab" data-bs-toggle="tab" data-bs-target="#server_infos-partdb" type="button" role="tab" aria-controls="server_infos-partdb" aria-selected="true"><i class="fa-solid fa-microchip"></i> Part-DB</button>
<button class="nav-link" id="server_infos-php-tab" data-bs-toggle="tab" data-bs-target="#server_infos-php" type="button" role="tab" aria-controls="server_infos-php" aria-selected="false"><i class="fa-brands fa-php"></i> PHP</button>
<button class="nav-link" id="server_infos-database-tab" data-bs-toggle="tab" data-bs-target="#server_infos-db" type="button" role="tab" aria-controls="server_infos-db" aria-selected="false"><i class="fa-solid fa-database"></i> Database</button>
</div>
</nav>
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="server_infos-partdb" role="tabpanel" aria-labelledby="server_infos-partdb-tab">
{% include "Tools/ServerInfos/_partdb.html.twig" %}
</div>
<div class="tab-pane fade" id="server_infos-php" role="tabpanel" aria-labelledby="server_infos-php-tab">
{% include "Tools/ServerInfos/_php.html.twig" %}
</div>
<div class="tab-pane fade" id="server_infos-db" role="tabpanel" aria-labelledby="server_infos-database-tab">
{% include "Tools/ServerInfos/_db.html.twig" %}
</div>
</div>
<p>Run <code>php bin/console partdb:check-requirements</code> in a terminal in your Part-DB folder to check if there are any recommendations for your system configuration.</p>
{% endblock %}

View file

@ -26,8 +26,9 @@
{% endif %} {% endif %}
</ul> </ul>
{% if is_granted('@parts.read') %}
{% include "_navbar_search.html.twig" %} {% include "_navbar_search.html.twig" %}
{% endif %}
<ul class="navbar-nav ms-3" id="login-content"> <ul class="navbar-nav ms-3" id="login-content">

View file

@ -12,7 +12,7 @@
</tbody> </tbody>
</table> </table>
<button type="button" class="btn btn-success" {{ collection.create_btn() }} {% if part_mode and not is_granted('attachments.create', part) %}disabled{% endif %}> <button type="button" class="btn btn-success" {{ collection.create_btn() }} {% if part_mode and not is_granted('edit', part) %}disabled{% endif %}>
<i class="fas fa-plus-square fa-fw"></i> <i class="fas fa-plus-square fa-fw"></i>
{% trans %}attachment.create{% endtrans %} {% trans %}attachment.create{% endtrans %}
</button> </button>

View file

@ -35,15 +35,15 @@
<select class="selectpicker" name="action" data-controller="elements--selectpicker" {{ stimulus_action('elements/datatables/parts', 'updateTargetPicker', 'change') }} <select class="selectpicker" name="action" data-controller="elements--selectpicker" {{ stimulus_action('elements/datatables/parts', 'updateTargetPicker', 'change') }}
title="{% trans %}part_list.action.action.title{% endtrans %}" required> title="{% trans %}part_list.action.action.title{% endtrans %}" required>
<optgroup label="{% trans %}part_list.action.action.group.favorite{% endtrans %}"> <optgroup label="{% trans %}part_list.action.action.group.favorite{% endtrans %}">
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="favorite">{% trans %}part_list.action.action.favorite{% endtrans %}</option> <option {% if not is_granted('@parts.change_favorite') %}disabled{% endif %} value="favorite">{% trans %}part_list.action.action.favorite{% endtrans %}</option>
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="unfavorite">{% trans %}part_list.action.action.unfavorite{% endtrans %}</option> <option {% if not is_granted('@parts.change_favorite') %}disabled{% endif %} value="unfavorite">{% trans %}part_list.action.action.unfavorite{% endtrans %}</option>
</optgroup> </optgroup>
<optgroup label="{% trans %}part_list.action.action.group.change_field{% endtrans %}"> <optgroup label="{% trans %}part_list.action.action.group.change_field{% endtrans %}">
<option {% if not is_granted('@parts_category.edit') %}disabled{% endif %} value="change_category" data-url="{{ path('select_category') }}">{% trans %}part_list.action.action.change_category{% endtrans %}</option> <option {% if not is_granted('@categories.read') %}disabled{% endif %} value="change_category" data-url="{{ path('select_category') }}">{% trans %}part_list.action.action.change_category{% endtrans %}</option>
<option {% if not is_granted('@parts_footprint.edit') %}disabled{% endif %} value="change_footprint" data-url="{{ path('select_footprint') }}">{% trans %}part_list.action.action.change_footprint{% endtrans %}</option> <option {% if not is_granted('@footprints.read') %}disabled{% endif %} value="change_footprint" data-url="{{ path('select_footprint') }}">{% trans %}part_list.action.action.change_footprint{% endtrans %}</option>
<option {% if not is_granted('@parts_manufacturer.edit') %}disabled{% endif %} value="change_manufacturer" data-url="{{ path('select_manufacturer') }}">{% trans %}part_list.action.action.change_manufacturer{% endtrans %}</option> <option {% if not is_granted('@manufacturers.read') %}disabled{% endif %} value="change_manufacturer" data-url="{{ path('select_manufacturer') }}">{% trans %}part_list.action.action.change_manufacturer{% endtrans %}</option>
<option {% if not is_granted('@parts_unit.edit') %}disabled{% endif %} value="change_unit" data-url="{{ path('select_measurement_unit') }}">{% trans %}part_list.action.action.change_unit{% endtrans %}</option> <option {% if not is_granted('@measurement_units.read') %}disabled{% endif %} value="change_unit" data-url="{{ path('select_measurement_unit') }}">{% trans %}part_list.action.action.change_unit{% endtrans %}</option>
</optgroup> </optgroup>
<option {% if not is_granted('@parts.delete') %}disabled{% endif %} value="delete">{% trans %}part_list.action.action.delete{% endtrans %}</option> <option {% if not is_granted('@parts.delete') %}disabled{% endif %} value="delete">{% trans %}part_list.action.action.delete{% endtrans %}</option>
@ -53,7 +53,7 @@
{# This is left empty, as this will be filled by Javascript #} {# This is left empty, as this will be filled by Javascript #}
</select> </select>
<button type="submit" class="btn btn-secondary">{% trans %}part_list.action.submit{% endtrans %}</button> <button type="submit" class="btn btn-secondary" {% if not is_granted('@parts.edit') %}disabled{% endif %}>{% trans %}part_list.action.submit{% endtrans %}</button>
</div> </div>
<div {{ stimulus_target('elements/datatables/parts', 'dt') }}> <div {{ stimulus_target('elements/datatables/parts', 'dt') }}>

View file

@ -1,13 +1,13 @@
{% macro sidebar_dropdown() %} {% macro sidebar_dropdown() %}
{# Format is [mode, route, label] #} {# Format is [mode, route, label, show_condition] #}
{% set data_sources = [ {% set data_sources = [
['categories', path('tree_category_root'), 'category.labelp'], ['categories', path('tree_category_root'), 'category.labelp', is_granted('@categories.read') and is_granted('@parts.read')],
['locations', path('tree_location_root'), 'storelocation.labelp'], ['locations', path('tree_location_root'), 'storelocation.labelp', is_granted('@storelocations.read') and is_granted('@parts.read')],
['footprints', path('tree_footprint_root'), 'footprint.labelp'], ['footprints', path('tree_footprint_root'), 'footprint.labelp', is_granted('@footprints.read') and is_granted('@parts.read')],
['manufacturers', path('tree_manufacturer_root'), 'manufacturer.labelp'], ['manufacturers', path('tree_manufacturer_root'), 'manufacturer.labelp', is_granted('@manufacturers.read') and is_granted('@parts.read')],
['suppliers', path('tree_supplier_root'), 'supplier.labelp'], ['suppliers', path('tree_supplier_root'), 'supplier.labelp', is_granted('@suppliers.read') and is_granted('@parts.read')],
['devices', path('tree_device_root'), 'device.labelp'], ['devices', path('tree_device_root'), 'device.labelp', is_granted('@devices.read')],
['tools', path('tree_tools'), 'tools.label'], ['tools', path('tree_tools'), 'tools.label', true],
] %} ] %}
<li class="dropdown-header">{% trans %}actions{% endtrans %}</li> <li class="dropdown-header">{% trans %}actions{% endtrans %}</li>
@ -17,9 +17,11 @@
<li class="dropdown-header">{% trans %}datasource{% endtrans %}</li> <li class="dropdown-header">{% trans %}datasource{% endtrans %}</li>
{% for source in data_sources %} {% for source in data_sources %}
<li><button class="tree-btns dropdown-item" data-mode="{{ source[0] }}" data-url="{{ source[1] }}" data-text="{{ source[2] | trans }}" {% if source[3] %} {# show_condition #}
{{ stimulus_action('elements/sidebar_tree', 'changeDataSource') }} <li><button class="tree-btns dropdown-item" data-mode="{{ source[0] }}" data-url="{{ source[1] }}" data-text="{{ source[2] | trans }}"
>{{ source[2] | trans }}</button></li> {{ stimulus_action('elements/sidebar_tree', 'changeDataSource') }}
>{{ source[2] | trans }}</button></li>
{% endif %}
{% endfor %} {% endfor %}
{% endmacro %} {% endmacro %}
@ -28,7 +30,7 @@
<div class="input-group input-group-sm mb-2 mt-1"> <div class="input-group input-group-sm mb-2 mt-1">
<button class="btn btn-light dropdown-toggle" type="button" <button class="btn btn-light dropdown-toggle" type="button"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
><span class="sidebar-title" {{ stimulus_target('elements/sidebar_tree', 'sourceText') }}>Loading...</span></button> ><span class="sidebar-title" {{ stimulus_target('elements/sidebar_tree', 'sourceText') }}>Loading... / Access Denied</span></button>
<ul class="dropdown-menu" aria-labelledby="dropdownCat"> <ul class="dropdown-menu" aria-labelledby="dropdownCat">
{{ _self.sidebar_dropdown('tree-categories') }} {{ _self.sidebar_dropdown('tree-categories') }}
</ul> </ul>

View file

@ -6,6 +6,30 @@
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro array_to_tags(tags, class="badge bg-primary") %}
{% for tag in tags %}
<span class="{{ class }}">{{ tag | trim }}</span>
{% endfor %}
{% endmacro %}
{% macro bool_icon(bool) %}
{% if bool %}
<i class="fas fa-check-circle fa-fw" title="{% trans %}Yes{% endtrans %}"></i>
{% else %}
<i class="fas fa-times-circle fa-fw" title="{% trans %}No{% endtrans %}"></i>
{% endif %}
{% endmacro %}
{% macro boolean_badge(value, class="badge") %}
{% if value %}
{% set class = class ~ ' bg-success' %}
{% else %}
{% set class = class ~ ' bg-danger' %}
{% endif %}
<span class="{{ class }}">{{ _self.bool_icon(value) }} {{ _self.boolean(value) }}</span>
{% endmacro %}
{% macro string_to_tags(string, class="badge bg-info") %} {% macro string_to_tags(string, class="badge bg-info") %}
{% for tag in string|split(',') %} {% for tag in string|split(',') %}
<a href="{{ path('part_list_tags', {'tag': tag | trim | url_encode}) }}" class="{{ class }}" >{{ tag | trim }}</a> <a href="{{ path('part_list_tags', {'tag': tag | trim | url_encode}) }}" class="{{ class }}" >{{ tag | trim }}</a>
@ -106,21 +130,13 @@
</nav> </nav>
{% endmacro %} {% endmacro %}
{% macro bool_icon(bool) %}
{% if bool %}
<i class="fas fa-check-circle fa-fw" title="{% trans %}Yes{% endtrans %}"></i>
{% else %}
<i class="fas fa-times-circle fa-fw" title="{% trans %}No{% endtrans %}"></i>
{% endif %}
{% endmacro %}
{% macro date_user_combination(entity, lastModified, datetime_format = "short") %} {% macro date_user_combination(entity, lastModified, datetime_format = "short") %}
{% if lastModified == true %} {% if lastModified == true %}
{{ entity.lastModified | format_datetime(datetime_format) }} {{ entity.lastModified | format_datetime(datetime_format) }}
{% else %} {% else %}
{{ entity.addedDate | format_datetime(datetime_format) }} {{ entity.addedDate | format_datetime(datetime_format) }}
{% endif %} {% endif %}
{% if is_granted('show_users', entity) %} {% if is_granted('show_history', entity) %}
{% if lastModified == true %} {% if lastModified == true %}
{% set user = last_editing_user(entity) %} {% set user = last_editing_user(entity) %}
{% else %} {% else %}
@ -129,9 +145,9 @@
{% if user is not null %} {% if user is not null %}
{% if user.fullName is not empty %} {% if user.fullName is not empty %}
<a href="{{ path('user_info', {"id": user.id}) }}" title="@{{ user.name }}"><i>({{ user.fullName }})</i></a> (<a href="{{ path('user_info', {"id": user.id}) }}" title="@{{ user.name }}">{{ user.fullName }}</a>)
{% else %} {% else %}
<a href="{{ path('user_info', {"id": user.id}) }}" title="@{{ user.name }}"><i>(@{{ user.name }})</i></a> (<a href="{{ path('user_info', {"id": user.id}) }}" title="@{{ user.name }}">@{{ user.name }}</a>)
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}

View file

@ -71,13 +71,13 @@ class ApplicationAvailabilityFunctionalTest extends WebTestCase
$client->request('GET', $url); $client->request('GET', $url);
$this->assertTrue($client->getResponse()->isSuccessful(), 'Request not successful. Status code is '.$client->getResponse()->getStatusCode()); $this->assertTrue($client->getResponse()->isSuccessful(), 'Request not successful. Status code is '.$client->getResponse()->getStatusCode() . ' for URL '.$url);
} }
public function urlProvider(): ?Generator public function urlProvider(): ?Generator
{ {
//Homepage //Homepage
//yield ['/']; yield ['/'];
//User related things //User related things
yield ['/user/settings']; yield ['/user/settings'];
yield ['/user/info']; yield ['/user/info'];
@ -117,9 +117,21 @@ class ApplicationAvailabilityFunctionalTest extends WebTestCase
//Statistics //Statistics
yield ['/statistics']; yield ['/statistics'];
//Event log
yield ['/log/']; //Slash suffix here is important
//Typeahead //Typeahead
yield ['/typeahead/builtInResources/search?query=DIP8']; yield ['/typeahead/builtInResources/search?query=DIP8'];
yield ['/typeahead/tags/search/test']; yield ['/typeahead/tags/search/test'];
yield ['/typeahead/parameters/part/search/NPN'];
yield ['/typeahead/parameters/category/search/NPN'];
//Select API
yield ['/select_api/category'];
yield ['/select_api/footprint'];
yield ['/select_api/manufacturer'];
yield ['/select_api/measurement_unit'];
//Label test //Label test
yield ['/scan']; yield ['/scan'];
@ -130,5 +142,9 @@ class ApplicationAvailabilityFunctionalTest extends WebTestCase
//Tools //Tools
yield ['/tools/reel_calc']; yield ['/tools/reel_calc'];
yield ['/tools/server_infos'];
//Webauthn Register
yield ['/webauthn/register'];
} }
} }

View file

@ -42,7 +42,7 @@ class DatatablesAvailabilityTest extends WebTestCase
]); ]);
$client->catchExceptions(false); $client->catchExceptions(false);
$client->request('GET', $url); $client->request('GET', $url);
$this->assertTrue($client->getResponse()->isSuccessful(), 'Request not successful. Status code is '.$client->getResponse()->getStatusCode()); $this->assertTrue($client->getResponse()->isSuccessful(), 'Request not successful. Status code is '.$client->getResponse()->getStatusCode() . ' for URL '.$url);
static::ensureKernelShutdown(); static::ensureKernelShutdown();
$client = static::createClient([], [ $client = static::createClient([], [
@ -70,5 +70,9 @@ class DatatablesAvailabilityTest extends WebTestCase
yield ['/log/']; yield ['/log/'];
yield ['/attachment/list']; yield ['/attachment/list'];
//Test using filters
yield ['/category/1/parts?part_filter%5Bname%5D%5Boperator%5D=%3D&part_filter%5Bname%5D%5Bvalue%5D=BC547&part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Btags%5D%5Boperator%5D=ANY&part_filter%5Btags%5D%5Bvalue%5D=Test&part_filter%5Bsubmit%5D='];
yield ['/category/1/parts?part_filter%5Bcategory%5D%5Boperator%5D=INCLUDING_CHILDREN&part_filter%5Bstorelocation%5D%5Boperator%5D=%3D&part_filter%5Bstorelocation%5D%5Bvalue%5D=1&part_filter%5BattachmentsCount%5D%5Boperator%5D=%3D&part_filter%5BattachmentsCount%5D%5Bvalue1%5D=3&part_filter%5Bsubmit%5D='];
} }
} }

View file

@ -0,0 +1,131 @@
<?php
namespace App\Tests\Entity\UserSystem;
use App\Entity\UserSystem\PermissionData;
use PHPUnit\Framework\TestCase;
class PermissionDataTest extends TestCase
{
public function testGetSetIs()
{
$perm_data = new PermissionData();
//Empty object should have all permissions set to inherit
$this->assertNull($perm_data->getPermissionValue('not_existing', 'not_existing'));
$this->assertFalse($perm_data->isPermissionSet('not_existing', 'not_existing'));
$this->assertNull($perm_data->getPermissionValue('p1', 'op1'));
$this->assertNull($perm_data->getPermissionValue('p1', 'op2'));
$this->assertNull($perm_data->getPermissionValue('p2', 'op1'));
//Set values
$perm_data->setPermissionValue('p1', 'op1', PermissionData::ALLOW);
$perm_data->setPermissionValue('p1', 'op2', PermissionData::DISALLOW);
$perm_data->setPermissionValue('p2', 'op1', PermissionData::ALLOW);
//Check that values were set
$this->assertTrue($perm_data->isPermissionSet('p1', 'op1'));
$this->assertTrue($perm_data->isPermissionSet('p1', 'op2'));
$this->assertTrue($perm_data->isPermissionSet('p2', 'op1'));
//Check that values are correct
$this->assertTrue($perm_data->getPermissionValue('p1', 'op1'));
$this->assertFalse($perm_data->getPermissionValue('p1', 'op2'));
$this->assertTrue($perm_data->getPermissionValue('p2', 'op1'));
//Set values to null
$perm_data->setPermissionValue('p1', 'op1', null);
$this->assertNull($perm_data->getPermissionValue('p1', 'op1'));
//Values should be unset now
$this->assertFalse($perm_data->isPermissionSet('p1', 'op1'));
}
public function testJSONSerialization()
{
$perm_data = new PermissionData();
$perm_data->setPermissionValue('perm1', 'op1', PermissionData::ALLOW);
$perm_data->setPermissionValue('perm1', 'op2', PermissionData::DISALLOW);
$perm_data->setPermissionValue('perm1', 'op3', PermissionData::ALLOW);
$perm_data->setPermissionValue('perm2', 'op1', PermissionData::ALLOW);
$perm_data->setPermissionValue('perm2', 'op2', PermissionData::DISALLOW);
//Ensure that JSON serialization works
$this->assertJsonStringEqualsJsonString(json_encode([
'perm1' => [
'op1' => true,
'op2' => false,
'op3' => true,
],
'perm2' => [
'op1' => true,
'op2' => false,
],
], JSON_THROW_ON_ERROR), json_encode($perm_data, JSON_THROW_ON_ERROR));
//Set values to inherit to ensure they do not show up in the json
$perm_data->setPermissionValue('perm1', 'op3', null);
$perm_data->setPermissionValue('perm2', 'op1', null);
$perm_data->setPermissionValue('perm2', 'op2', null);
//Ensure that JSON serialization works
$this->assertJsonStringEqualsJsonString(json_encode([
'perm1' => [
'op1' => true,
'op2' => false,
],
], JSON_THROW_ON_ERROR), json_encode($perm_data, JSON_THROW_ON_ERROR));
}
public function testFromJSON()
{
$json = json_encode([
'perm1' => [
'op1' => true,
'op2' => false,
'op3' => true,
],
'perm2' => [
'op1' => true,
'op2' => false,
],
], JSON_THROW_ON_ERROR);
$perm_data = PermissionData::fromJSON($json);
//Ensure that values were set correctly
$this->assertTrue($perm_data->getPermissionValue('perm1', 'op1'));
$this->assertFalse($perm_data->getPermissionValue('perm2', 'op2'));
}
public function testResetPermissions()
{
$data = new PermissionData();
$data->setPermissionValue('perm1', 'op1', PermissionData::ALLOW);
$data->setPermissionValue('perm1', 'op2', PermissionData::DISALLOW);
$data->setPermissionValue('perm1', 'op3', PermissionData::INHERIT);
//Ensure that values were set correctly
$this->assertTrue($data->isPermissionSet('perm1', 'op1'));
$this->assertTrue($data->isPermissionSet('perm1', 'op2'));
$this->assertFalse($data->isPermissionSet('perm1', 'op3'));
//Reset the permissions
$data->resetPermissions();
//Afterwards all values must be set to inherit (null)
$this->assertNull($data->getPermissionValue('perm1', 'op1'));
$this->assertNull($data->getPermissionValue('perm1', 'op2'));
$this->assertNull($data->getPermissionValue('perm1', 'op3'));
//And be undefined
$this->assertFalse($data->isPermissionSet('perm1', 'op1'));
$this->assertFalse($data->isPermissionSet('perm1', 'op2'));
$this->assertFalse($data->isPermissionSet('perm1', 'op3'));
}
}

View file

@ -1,211 +0,0 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Tests\Entity\UserSystem;
use App\Entity\UserSystem\PermissionsEmbed;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
class PermissionsEmbedTest extends TestCase
{
public function testGetPermissionValue(): void
{
$embed = new PermissionsEmbed();
//For newly created embedded, all things should be set to inherit => null
//Test both normal name and constants
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::CONFIG, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::ATTACHMENT_TYPES, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::CATEGORIES, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::DATABASE, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::DEVICE_PARTS, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::DEVICES, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::FOOTRPINTS, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::GROUPS, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::DATABASE, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::LABELS, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::MANUFACTURERS, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS_ATTACHMENTS, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS_COMMENT, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS_DESCRIPTION, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS_FOOTPRINT, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS_MANUFACTURER, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS_MINAMOUNT, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS_NAME, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS_ORDER, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS_ORDERDETAILS, 0));
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::USERS, 0));
//Set a value for testing to the part property
$reflection = new ReflectionClass($embed);
$property = $reflection->getProperty('parts');
$property->setAccessible(true);
$property->setValue($embed, 0b11011000); // 11 01 10 00
//Test if function is working correctly
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS, 0));
$this->assertFalse($embed->getPermissionValue(PermissionsEmbed::PARTS, 2));
$this->assertTrue($embed->getPermissionValue(PermissionsEmbed::PARTS, 4));
// 11 is reserved, but it should be also treat as INHERIT.
$this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS, 6));
}
public function testGetBitValue(): void
{
$embed = new PermissionsEmbed();
//Set a value for testing to the part property
$reflection = new ReflectionClass($embed);
$property = $reflection->getProperty('parts');
$property->setAccessible(true);
$property->setValue($embed, 0b11011000); // 11 01 10 00
//Test if function is working correctly
$this->assertSame(PermissionsEmbed::INHERIT, $embed->getBitValue(PermissionsEmbed::PARTS, 0));
$this->assertSame(PermissionsEmbed::DISALLOW, $embed->getBitValue(PermissionsEmbed::PARTS, 2));
$this->assertSame(PermissionsEmbed::ALLOW, $embed->getBitValue(PermissionsEmbed::PARTS, 4));
// 11 is reserved, but it should be also treat as INHERIT.
$this->assertSame(0b11, $embed->getBitValue(PermissionsEmbed::PARTS, 6));
}
public function testInvalidPermissionName(): void
{
$embed = new PermissionsEmbed();
//When encoutering an unknown permission name the class must throw an exception
$this->expectException(InvalidArgumentException::class);
$embed->getPermissionValue('invalid', 0);
}
public function testInvalidBit1(): void
{
$embed = new PermissionsEmbed();
//When encoutering an negative bit the class must throw an exception
$this->expectException(InvalidArgumentException::class);
$embed->getPermissionValue('parts', -1);
}
public function testInvalidBit2(): void
{
$embed = new PermissionsEmbed();
//When encoutering an odd bit number it must throw an error.
$this->expectException(InvalidArgumentException::class);
$embed->getPermissionValue('parts', 1);
}
public function getStatesBINARY(): array
{
return [
'ALLOW' => [PermissionsEmbed::ALLOW],
'DISALLOW' => [PermissionsEmbed::DISALLOW],
'INHERIT' => [PermissionsEmbed::INHERIT],
'0b11' => [0b11],
];
}
public function getStatesBOOL(): array
{
return [
'ALLOW' => [true],
'DISALLOW' => [false],
'INHERIT' => [null],
'0b11' => [null],
];
}
/**
* @dataProvider getStatesBINARY
*/
public function testTestsetBitValue($value): void
{
$embed = new PermissionsEmbed();
//Check if it returns itself, for chaining.
$this->assertSame($embed, $embed->setBitValue(PermissionsEmbed::PARTS, 0, $value));
$this->assertSame($value, $embed->getBitValue(PermissionsEmbed::PARTS, 0));
}
/**
* @dataProvider getStatesBOOL
*/
public function testSetPermissionValue($value): void
{
$embed = new PermissionsEmbed();
//Check if it returns itself, for chaining.
$this->assertSame($embed, $embed->setPermissionValue(PermissionsEmbed::PARTS, 0, $value));
$this->assertSame($value, $embed->getPermissionValue(PermissionsEmbed::PARTS, 0));
}
public function testSetRawPermissionValue(): void
{
$embed = new PermissionsEmbed();
$embed->setRawPermissionValue(PermissionsEmbed::PARTS, 10);
$this->assertSame(10, $embed->getRawPermissionValue(PermissionsEmbed::PARTS));
}
public function testSetRawPermissionValues(): void
{
$embed = new PermissionsEmbed();
$embed->setRawPermissionValues([
PermissionsEmbed::PARTS => 0,
PermissionsEmbed::USERS => 100,
PermissionsEmbed::CATEGORIES => 1304,
]);
$this->assertSame(0, $embed->getRawPermissionValue(PermissionsEmbed::PARTS));
$this->assertSame(100, $embed->getRawPermissionValue(PermissionsEmbed::USERS));
$this->assertSame(1304, $embed->getRawPermissionValue(PermissionsEmbed::CATEGORIES));
//Test second method to pass perm names and values
$embed->setRawPermissionValues(
[PermissionsEmbed::PARTS, PermissionsEmbed::USERS, PermissionsEmbed::CATEGORIES],
[0, 100, 1304]
);
$this->assertSame(0, $embed->getRawPermissionValue(PermissionsEmbed::PARTS));
$this->assertSame(100, $embed->getRawPermissionValue(PermissionsEmbed::USERS));
$this->assertSame(1304, $embed->getRawPermissionValue(PermissionsEmbed::CATEGORIES));
}
}

View file

@ -1,127 +0,0 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Tests\Security\Annotations;
use App\Entity\Attachments\AttachmentType;
use App\Security\Annotations\ColumnSecurity;
use PHPUnit\Framework\TestCase;
class ColumnSecurityTest extends TestCase
{
public function testGetReadOperation(): void
{
$annotation = new ColumnSecurity();
$this->assertSame('read', $annotation->getReadOperationName(), 'A new annotation must return read');
$annotation->read = 'overwritten';
$this->assertSame('overwritten', $annotation->getReadOperationName());
$annotation->prefix = 'prefix';
$this->assertSame('prefix.overwritten', $annotation->getReadOperationName());
}
public function testGetEditOperation(): void
{
$annotation = new ColumnSecurity();
$this->assertSame('edit', $annotation->getEditOperationName(), 'A new annotation must return read');
$annotation->edit = 'overwritten';
$this->assertSame('overwritten', $annotation->getEditOperationName());
$annotation->prefix = 'prefix';
$this->assertSame('prefix.overwritten', $annotation->getEditOperationName());
}
public function placeholderScalarDataProvider(): array
{
return [
['string', '???'],
['integer', 0],
['int', 0],
['float', 0.0],
['object', null],
['bool', false],
['boolean', false],
//['datetime', (new \DateTime())->setTimestamp(0)]
];
}
/**
* @dataProvider placeholderScalarDataProvider
*
* @param $expected_value
*/
public function testGetPlaceholderScalar(string $type, $expected_value): void
{
$annotation = new ColumnSecurity();
$annotation->type = $type;
$this->assertSame($expected_value, $annotation->getPlaceholder());
}
public function testGetPlaceholderSpecifiedValue(): void
{
$annotation = new ColumnSecurity();
$annotation->placeholder = 3434;
$this->assertSame(3434, $annotation->getPlaceholder());
$annotation->placeholder = [323];
$this->assertCount(1, $annotation->getPlaceholder());
//If a placeholder is specified we allow every type
$annotation->type = 'type2';
$annotation->placeholder = 'invalid';
$this->assertSame('invalid', $annotation->getPlaceholder());
}
public function testGetPlaceholderDBElement(): void
{
$annotation = new ColumnSecurity();
$annotation->type = AttachmentType::class;
/** @var AttachmentType $placeholder */
$placeholder = $annotation->getPlaceholder();
$this->assertInstanceOf(AttachmentType::class, $placeholder);
$this->assertSame('???', $placeholder->getName());
$annotation->placeholder = 'test';
$placeholder = $annotation->getPlaceholder();
$this->assertInstanceOf(AttachmentType::class, $placeholder);
$this->assertSame('test', $placeholder->getName());
}
}

View file

@ -45,15 +45,18 @@ namespace App\Tests\Security;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Security\UserChecker; use App\Security\UserChecker;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
use Symfony\Component\Security\Core\Exception\DisabledException; use Symfony\Component\Security\Core\Exception\DisabledException;
class UserCheckerTest extends TestCase class UserCheckerTest extends WebTestCase
{ {
protected $service; protected $service;
protected function setUp(): void protected function setUp(): void
{ {
$this->service = new UserChecker(); self::bootKernel();
$this->service = self::getContainer()->get(UserChecker::class);
} }
public function testThrowDisabledException(): void public function testThrowDisabledException(): void
@ -66,7 +69,7 @@ class UserCheckerTest extends TestCase
//An disabled user must throw an exception //An disabled user must throw an exception
$user->setDisabled(true); $user->setDisabled(true);
$this->expectException(DisabledException::class); $this->expectException(CustomUserMessageAccountStatusException::class);
$this->service->checkPostAuth($user); $this->service->checkPostAuth($user);
} }
} }

View file

@ -40,23 +40,25 @@ declare(strict_types=1);
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
namespace App\Tests\Services; namespace App\Tests\Services\UserSystem;
use App\Entity\UserSystem\Group; use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\PermissionData;
use App\Entity\UserSystem\PermissionsEmbed; use App\Entity\UserSystem\PermissionsEmbed;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Services\PermissionResolver; use App\Services\UserSystem\PermissionManager;
use InvalidArgumentException; use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class PermissionResolverTest extends WebTestCase class PermissionManagerTest extends WebTestCase
{ {
protected $user_withoutGroup; protected $user_withoutGroup;
protected $user; protected $user;
protected $group; protected $group;
/** /**
* @var PermissionResolver * @var PermissionManager
*/ */
protected $service; protected $service;
@ -66,75 +68,58 @@ class PermissionResolverTest extends WebTestCase
//Get an service instance. //Get an service instance.
self::bootKernel(); self::bootKernel();
$this->service = self::$container->get(PermissionResolver::class); $this->service = self::getContainer()->get(PermissionManager::class);
//Set up a mocked user //Set up a mocked user
$user_embed = new PermissionsEmbed(); $user_perms = new PermissionData();
$user_embed->setPermissionValue('parts', 0, true) //read $user_perms->setPermissionValue('parts', 'read', true) //read
->setPermissionValue('parts', 2, false) //edit ->setPermissionValue('parts', 'edit', false) //edit
->setPermissionValue('parts', 4, null) //create ->setPermissionValue('parts', 'create', null) //create
->setPermissionValue('parts', 30, null) //move ->setPermissionValue('parts', 'move', null) //move
->setPermissionValue('parts', 8, null); //delete ->setPermissionValue('parts', 'delete', null); //delete
$this->user = $this->createMock(User::class); $this->user = $this->createMock(User::class);
$this->user->method('getPermissions')->willReturn($user_embed); $this->user->method('getPermissions')->willReturn($user_perms);
$this->user_withoutGroup = $this->createMock(User::class); $this->user_withoutGroup = $this->createMock(User::class);
$this->user_withoutGroup->method('getPermissions')->willReturn($user_embed); $this->user_withoutGroup->method('getPermissions')->willReturn($user_perms);
$this->user_withoutGroup->method('getGroup')->willReturn(null); $this->user_withoutGroup->method('getGroup')->willReturn(null);
//Set up a faked group //Set up a faked group
$group1_embed = new PermissionsEmbed(); $group1_perms = new PermissionData();
$group1_embed->setPermissionValue('parts', 6, true) $group1_perms
->setPermissionValue('parts', 8, false) ->setPermissionValue('parts', 'delete', false)
->setPermissionValue('parts', 10, null) ->setPermissionValue('parts', 'search', null)
->setPermissionValue('parts', 0, false) ->setPermissionValue('parts', 'read', false)
->setPermissionValue('parts', 30, true) ->setPermissionValue('parts', 'show_history', true)
->setPermissionValue('parts', 2, true); ->setPermissionValue('parts', 'edit', true);
$this->group = $this->createMock(Group::class); $this->group = $this->createMock(Group::class);
$this->group->method('getPermissions')->willReturn($group1_embed); $this->group->method('getPermissions')->willReturn($group1_perms);
//Set this group for the user //Set this group for the user
$this->user->method('getGroup')->willReturn($this->group); $this->user->method('getGroup')->willReturn($this->group);
//parent group //parent group
$parent_group_embed = new PermissionsEmbed(); $parent_group_perms = new PermissionData();
$parent_group_embed->setPermissionValue('parts', 12, true) $parent_group_perms->setPermissionValue('parts', 'all_parts', true)
->setPermissionValue('parts', 14, false) ->setPermissionValue('parts', 'no_price_parts', false)
->setPermissionValue('parts', 16, null); ->setPermissionValue('parts', 'obsolete_parts', null);
$parent_group = $this->createMock(Group::class); $parent_group = $this->createMock(Group::class);
$parent_group->method('getPermissions')->willReturn($parent_group_embed); $parent_group->method('getPermissions')->willReturn($parent_group_perms);
$this->group->method('getParent')->willReturn($parent_group); $this->group->method('getParent')->willReturn($parent_group);
} }
public function getPermissionNames(): array public function getPermissionNames(): array
{ {
//List all possible operation names. //List some permission names
return [ return [
[PermissionsEmbed::PARTS], ['parts'],
[PermissionsEmbed::USERS], ['system'],
[PermissionsEmbed::PARTS_ORDERDETAILS], ['footprints'],
[PermissionsEmbed::PARTS_NAME], ['suppliers'],
[PermissionsEmbed::PARTS_ORDER], ['tools']
[PermissionsEmbed::PARTS_MINAMOUNT],
[PermissionsEmbed::PARTS_MANUFACTURER],
[PermissionsEmbed::DEVICES],
[PermissionsEmbed::PARTS_FOOTPRINT],
[PermissionsEmbed::PARTS_DESCRIPTION],
[PermissionsEmbed::PARTS_COMMENT],
[PermissionsEmbed::PARTS_ATTACHMENTS],
[PermissionsEmbed::MANUFACTURERS],
[PermissionsEmbed::LABELS],
[PermissionsEmbed::DATABASE],
[PermissionsEmbed::GROUPS],
[PermissionsEmbed::FOOTRPINTS],
[PermissionsEmbed::DEVICE_PARTS],
[PermissionsEmbed::CATEGORIES],
[PermissionsEmbed::PARTS_PRICES],
[PermissionsEmbed::ATTACHMENT_TYPES],
[PermissionsEmbed::CONFIG],
]; ];
} }
@ -214,4 +199,96 @@ class PermissionResolverTest extends WebTestCase
$this->assertNull($this->service->inherit($this->user_withoutGroup, 'parts', 'show_history')); $this->assertNull($this->service->inherit($this->user_withoutGroup, 'parts', 'show_history'));
$this->assertNull($this->service->inherit($this->user_withoutGroup, 'parts', 'delete')); $this->assertNull($this->service->inherit($this->user_withoutGroup, 'parts', 'delete'));
} }
public function testSetPermission(): void
{
$user = new User();
//Set permission to true
$this->service->setPermission($user, 'parts', 'read', true);
$this->assertTrue($this->service->dontInherit($user, 'parts', 'read'));
$this->assertTrue($this->service->inherit($user, 'parts', 'read'));
//Set permission to false
$this->service->setPermission($user, 'parts', 'read', false);
$this->assertFalse($this->service->dontInherit($user, 'parts', 'read'));
$this->assertFalse($this->service->inherit($user, 'parts', 'read'));
//Set permission to null
$this->service->setPermission($user, 'parts', 'read', null);
$this->assertNull($this->service->dontInherit($user, 'parts', 'read'));
$this->assertNull($this->service->inherit($user, 'parts', 'read'));
}
public function testSetAllPermissions(): void
{
$user = new User();
//Set all permissions to true
$this->service->setAllPermissions($user, true);
$this->assertTrue($this->service->dontInherit($user, 'parts', 'read'));
$this->assertTrue($this->service->dontInherit($user, 'parts', 'create'));
$this->assertTrue($this->service->dontInherit($user, 'categories', 'edit'));
//Set all permissions to false
$this->service->setAllPermissions($user, false);
$this->assertFalse($this->service->dontInherit($user, 'parts', 'read'));
$this->assertFalse($this->service->dontInherit($user, 'parts', 'create'));
$this->assertFalse($this->service->dontInherit($user, 'categories', 'edit'));
//Set all permissions to null
$this->service->setAllPermissions($user, null);
$this->assertNull($this->service->dontInherit($user, 'parts', 'read'));
$this->assertNull($this->service->dontInherit($user, 'parts', 'create'));
$this->assertNull($this->service->dontInherit($user, 'categories', 'edit'));
}
public function testSetAllOperationsOfPermission(): void
{
$user = new User();
//Set all operations of permission to true
$this->service->setAllOperationsOfPermission($user, 'parts', true);
$this->assertTrue($this->service->dontInherit($user, 'parts', 'read'));
$this->assertTrue($this->service->dontInherit($user, 'parts', 'create'));
$this->assertTrue($this->service->dontInherit($user, 'parts', 'edit'));
//Set all operations of permission to false
$this->service->setAllOperationsOfPermission($user, 'parts', false);
$this->assertFalse($this->service->dontInherit($user, 'parts', 'read'));
$this->assertFalse($this->service->dontInherit($user, 'parts', 'create'));
$this->assertFalse($this->service->dontInherit($user, 'parts', 'edit'));
//Set all operations of permission to null
$this->service->setAllOperationsOfPermission($user, 'parts', null);
$this->assertNull($this->service->dontInherit($user, 'parts', 'read'));
$this->assertNull($this->service->dontInherit($user, 'parts', 'create'));
$this->assertNull($this->service->dontInherit($user, 'parts', 'edit'));
}
public function testEnsureCorrectSetOperations(): void
{
//Create an empty user (all permissions are inherit)
$user = new User();
//ensure that all permissions are inherit
$this->assertNull($this->service->inherit($user, 'parts', 'read'));
$this->assertNull($this->service->inherit($user, 'parts', 'edit'));
$this->assertNull($this->service->inherit($user, 'categories', 'read'));
//Set some permissions
$this->service->setPermission($user, 'parts', 'create', true);
//Until now only the create permission should be set
$this->assertTrue($this->service->dontInherit($user, 'parts', 'create'));
$this->assertNull($this->service->dontInherit($user, 'parts', 'read'));
//Now we call the ensureCorrectSetOperations method
$this->service->ensureCorrectSetOperations($user);
//Now all permissions should be set
$this->assertTrue($this->service->dontInherit($user, 'parts', 'create'));
$this->assertTrue($this->service->dontInherit($user, 'parts', 'read'));
$this->assertTrue($this->service->dontInherit($user, 'parts', 'edit'));
$this->assertTrue($this->service->dontInherit($user, 'categories', 'read'));
}
} }

View file

@ -40,9 +40,9 @@ declare(strict_types=1);
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
namespace App\Tests\Services\TFA; namespace App\Tests\Services\UserSystem\TFA;
use App\Services\TFA\BackupCodeGenerator; use App\Services\UserSystem\TFA\BackupCodeGenerator;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use RuntimeException; use RuntimeException;

View file

@ -40,10 +40,10 @@ declare(strict_types=1);
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
namespace App\Tests\Services\TFA; namespace App\Tests\Services\UserSystem\TFA;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Services\TFA\BackupCodeManager; use App\Services\UserSystem\TFA\BackupCodeManager;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class BackupCodeManagerTest extends WebTestCase class BackupCodeManagerTest extends WebTestCase

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more