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

@ -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

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en" trgLang="en">
<file id="security.en">
<unit id="aazoCks" name="user.login_error.user_disabled">
<segment>
<source>user.login_error.user_disabled</source>
<target>Your account is disabled! Contact an administrator if you think this is wrong.</target>
</segment>
</unit>
</file>
</xliff>

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