Compare commits

..

325 Commits

Author SHA1 Message Date
Dan Brown
bf6a6af683 Updated version and assets for release v22.11 2022-11-30 12:30:21 +00:00
Dan Brown
914790fd99 Merge branch 'development' into release 2022-11-30 12:29:52 +00:00
Dan Brown
edb0c6a9e8 Updated version and assets for release v22.10.2 2022-11-02 15:22:13 +00:00
Dan Brown
84049de696 Merge branch 'v22-10' into release 2022-11-02 15:19:33 +00:00
Dan Brown
da0531e63b Updated version and assets for release v22.10.1 2022-10-21 21:52:32 +01:00
Dan Brown
421dc75f4e Merge branch 'development' into release 2022-10-21 21:52:16 +01:00
Dan Brown
8ae91df038 Updated version and assets for release v22.10 2022-10-21 11:16:45 +01:00
Dan Brown
64b41dd626 Merge branch 'development' into release 2022-10-21 11:16:25 +01:00
Dan Brown
ebd6e4d3a2 Updated version and assets for release v22.09.1 2022-09-20 13:19:34 +01:00
Dan Brown
80374aea5c Merge branch 'development' into release 2022-09-20 13:19:03 +01:00
Dan Brown
2ac9efae7d Updated version and assets for release v22.09 2022-09-08 12:41:09 +01:00
Dan Brown
a11d565ba4 Merge branch 'development' into release 2022-09-08 12:40:57 +01:00
Dan Brown
1fdf854ea7 Updated version and assets for release v22.07.3 2022-08-11 15:17:06 +01:00
Dan Brown
e9c9792cb9 Merge branch 'development' into release 2022-08-11 15:16:34 +01:00
Dan Brown
5ae524c25a Updated version and assets for release v22.07.2 2022-08-09 13:55:52 +01:00
Dan Brown
0d7287fc8b Merge branch 'development' into release 2022-08-09 13:55:40 +01:00
Dan Brown
e77c96f6b7 Updated version and assets for release v22.07.1 2022-08-02 11:47:25 +01:00
Dan Brown
9b8a10dd3a Merge branch 'development' into release 2022-08-02 11:47:08 +01:00
Dan Brown
49200ca5ce Updated version and assets for release v22.07 2022-07-28 14:53:15 +01:00
Dan Brown
34aa4dbf10 Merge branch 'development' into release 2022-07-28 14:53:01 +01:00
Dan Brown
5ee79d16c9 Updated version and assets for release v22.06.2 2022-06-28 11:57:37 +01:00
Dan Brown
a1ea4006e0 Merge branch 'development' into release 2022-06-28 11:57:24 +01:00
Dan Brown
9078188939 Updated version and assets for release v22.06.1 2022-06-25 14:33:07 +01:00
Dan Brown
ed0aad1a7a Merge branch 'development' into release 2022-06-25 14:32:49 +01:00
Dan Brown
5c59cfb020 Updated version and assets for release v22.06 2022-06-24 11:50:56 +01:00
Dan Brown
3ca15ad68a Merge branch 'development' into release 2022-06-24 11:45:29 +01:00
Dan Brown
60014989f5 Updated version and assets for release v22.04.2 2022-05-09 16:10:16 +01:00
Dan Brown
57b10f195e Merge branch 'development' into release 2022-05-09 16:09:54 +01:00
Dan Brown
b1e95eb39f Updated version and assets for release v22.04.1 2022-05-04 21:26:58 +01:00
Dan Brown
b3da77b8f9 Merge branch 'development' into release 2022-05-04 21:26:31 +01:00
Dan Brown
1a345b74bb Updated version and assets for release v22.04 2022-04-29 15:55:32 +01:00
Dan Brown
8ffc3a4abf Merge branch 'development' into release 2022-04-29 15:55:05 +01:00
Dan Brown
7233c1c7b2 Updated version and assets for release v22.03.1 2022-03-30 19:37:07 +01:00
Dan Brown
1309a01131 Merge branch 'development' into release 2022-03-30 19:36:45 +01:00
Dan Brown
0333185b6d Updated version and assets for release v22.03 2022-03-30 13:49:17 +01:00
Dan Brown
83f89f64e8 Merge branch 'development' into release 2022-03-30 13:49:05 +01:00
Dan Brown
11a1a6fb16 Updated version and assets for release v22.02.3 2022-03-07 15:12:22 +00:00
Dan Brown
882c609296 Merge branch 'development' into release 2022-03-07 15:12:09 +00:00
Dan Brown
176a0dcd59 Updated version and assets for release v22.02.2 2022-03-01 22:45:41 +00:00
Dan Brown
94b0f70bfa Merge branch 'development' into release 2022-03-01 22:45:12 +00:00
Dan Brown
08b2a77d41 Updated version and assets for release v22.02.1 2022-02-27 17:46:06 +00:00
Dan Brown
3e8e9a23cf Merge branch 'development' into release 2022-02-27 17:45:49 +00:00
Dan Brown
58b83b64c8 Updated version and assets for release v22.02 2022-02-26 12:01:44 +00:00
Dan Brown
dfe4cde6ee Merge branch 'development' into release 2022-02-26 12:00:46 +00:00
Dan Brown
d11144d9e2 Updated version and assets for release v21.12.5 2022-02-06 15:49:23 +00:00
Dan Brown
f96b0ea5f3 Merge branch 'development' into release 2022-02-06 15:48:55 +00:00
Dan Brown
815f8d79ed Updated version and assets for release v21.12.4 2022-02-01 11:52:24 +00:00
Dan Brown
b62dab32e0 Merge branch 'development' into release 2022-02-01 11:51:48 +00:00
Dan Brown
262f863981 Updated version and assets for release v21.12.3 2022-01-24 22:49:42 +00:00
Dan Brown
a4c94390a1 Merge branch 'master' into release 2022-01-24 22:49:31 +00:00
Dan Brown
53f3cca85d Updated version and assets for release v21.12.2 2022-01-10 18:23:44 +00:00
Dan Brown
ed08bbcecc Merge branch 'master' into release 2022-01-10 18:23:19 +00:00
Dan Brown
de97ebf9b7 Updated version and assets for release v21.12.1 2022-01-06 12:20:37 +00:00
Dan Brown
f492a660a8 Merge branch 'master' into release 2022-01-06 12:20:26 +00:00
Dan Brown
09436836a5 Updated version and assets for release v21.12 2021-12-22 17:04:18 +00:00
Dan Brown
bb455d7788 Merge branch 'master' into release 2021-12-22 17:03:50 +00:00
Dan Brown
009212ab80 Updated version and assets for release v21.11.3 2021-12-15 14:08:37 +00:00
Dan Brown
ba9cb591c8 Merge branch 'master' into release 2021-12-15 14:08:17 +00:00
Dan Brown
d00ac2f34e Updated version and assets for release v21.11.2 2021-11-30 14:30:19 +00:00
Dan Brown
bd4dc6d463 Merge branch 'master' into release 2021-11-30 14:29:53 +00:00
Dan Brown
d91180a909 Updated version and assets for release v21.11.1 2021-11-23 20:44:36 +00:00
Dan Brown
bc2913a5cb Merge branch 'master' into release 2021-11-23 20:44:12 +00:00
Dan Brown
4802394562 Updated version and assets for release v21.11 2021-11-16 13:22:24 +00:00
Dan Brown
1755556468 Merge branch 'master' into release 2021-11-16 13:21:44 +00:00
Dan Brown
01cdbdb7ae Updated version and assets for release v21.10.3 2021-11-01 13:31:10 +00:00
Dan Brown
fc8bbf3eab Merge branch 'master' into release 2021-11-01 13:30:36 +00:00
Dan Brown
3cdab19319 Updated version and assets for release v21.10.2 2021-10-28 15:57:04 +01:00
Dan Brown
5661d20e87 Merge branch 'master' into release 2021-10-28 15:56:49 +01:00
Dan Brown
91f80123e8 Merge branch 'master' into release 2021-10-27 12:35:00 +01:00
Dan Brown
7a0636d0f8 Updated version and assets for release v21.10.1 2021-10-27 12:31:40 +01:00
Dan Brown
0fe5bdfbac Updated version and assets for release v21.10 2021-10-25 15:59:23 +01:00
Dan Brown
f88687e977 Merge branch 'master' into release 2021-10-25 15:58:59 +01:00
Dan Brown
68d437d05b Updated version and assets for release v21.08.6 2021-10-15 14:34:44 +01:00
Dan Brown
1e56aaea04 Merge branch 'master' into release 2021-10-15 14:34:23 +01:00
Dan Brown
dab170a6fe Updated version and assets for release v21.08.5 2021-10-08 22:25:36 +01:00
Dan Brown
a8de717d9b Merge branch 'master' into release 2021-10-08 22:25:05 +01:00
Dan Brown
78fe95b6fc Updated version and assets for release v21.08.4 2021-10-04 16:25:24 +01:00
Dan Brown
e0c24e41aa Merge branch 'master' into release 2021-10-04 16:24:54 +01:00
Dan Brown
fa8553839b Updated version and assets for release v21.08.3 2021-09-12 16:31:02 +01:00
Dan Brown
b8fcefc794 Merge branch 'master' into release 2021-09-12 16:30:35 +01:00
Dan Brown
88bcb68fcb Updated version and assets for release v21.08.2 2021-09-04 15:07:20 +01:00
Dan Brown
7c000553ae Merge branch 'master' into release 2021-09-04 15:06:33 +01:00
Dan Brown
391fa35c80 Updated version and assets for release v21.08.1 2021-09-02 21:13:09 +01:00
Dan Brown
c6773a8c9f Merge branch 'master' into release 2021-09-02 21:12:06 +01:00
Dan Brown
9b226e7d39 Updated version and assets for release v21.08 2021-08-31 22:07:53 +01:00
Dan Brown
9865446267 Merge branch 'master' into release 2021-08-31 22:07:23 +01:00
Dan Brown
926abbe776 Updated version and assets for release v21.05.4 2021-08-04 21:29:10 +01:00
Dan Brown
4fabef3a57 Merge branch 'v21.05.x' into release 2021-08-04 21:28:45 +01:00
Dan Brown
5ef4cd80c3 Updated version and assets for release v21.05.3 2021-07-03 11:59:52 +01:00
Dan Brown
e01f23583f Merge branch 'v21.05.x' into release 2021-07-03 11:59:21 +01:00
Dan Brown
7792cb3915 Updated version and assets for release v21.05.2 2021-06-13 14:26:34 +01:00
Dan Brown
be26253a18 Merge branch 'master' into release 2021-06-13 14:25:39 +01:00
Dan Brown
1bdd1f8189 Updated version for release v21.05.1 2021-06-04 23:09:42 +01:00
Dan Brown
fa62c79b17 Merge branch 'master' into release 2021-06-04 23:08:59 +01:00
Dan Brown
d7d8fa1e5b Updated version and assets for release v21.05 2021-05-30 16:17:56 +01:00
Dan Brown
18562f1e10 Merge branch 'master' into release 2021-05-30 16:17:44 +01:00
Dan Brown
86090a694f Updated version and assets for release v21.04.6 2021-05-24 13:06:03 +01:00
Dan Brown
1ee8287c73 Merge branch 'v21.04.x' into release 2021-05-24 13:05:34 +01:00
Dan Brown
8eb98cd591 Updated version and assets for release v21.04.5 2021-05-15 17:56:29 +01:00
Dan Brown
0f9ba21b05 Merge branch 'v21.04.x' into release 2021-05-15 17:56:03 +01:00
Dan Brown
834f8e7046 Updated version and assets for release v21.04.4 2021-05-09 14:46:05 +01:00
Dan Brown
32e3399334 Merge branch 'master' into release 2021-05-09 14:45:36 +01:00
Dan Brown
2d8698a218 Updated version and assets for release v21.04.3 2021-04-27 22:01:37 +01:00
Dan Brown
454fb883a2 Merge branch 'master' into release 2021-04-27 22:01:15 +01:00
Dan Brown
6f4a6ab8ea Updated version for release v21.04.2 2021-04-20 22:37:05 +01:00
Dan Brown
9c4b6f36f1 Merge branch 'master' into release 2021-04-20 22:36:35 +01:00
Dan Brown
78886b1e67 Updated version and assets for release v21.04.1 2021-04-19 22:26:19 +01:00
Dan Brown
d9debaf032 Merge branch 'master' into release 2021-04-19 22:25:29 +01:00
Dan Brown
d4360d6347 Updated version and assets for release v21.04 2021-04-09 21:18:32 +01:00
Dan Brown
175b1785c0 Merge branch 'master' into release 2021-04-09 21:18:09 +01:00
Dan Brown
c8740c0171 Updated version for release v0.31.8 2021-03-13 15:32:54 +00:00
Dan Brown
91ee895a74 Merge branch 'v0.31.x' into release 2021-03-13 15:32:06 +00:00
Dan Brown
a045e46571 Updated version for release v0.31.7 2021-03-02 21:19:17 +00:00
Dan Brown
44eaa65c3b Merge branch 'v0.31.x' into release 2021-03-02 21:18:31 +00:00
Dan Brown
0a22af7b14 Updated version for release v0.31.6 2021-02-06 14:41:19 +00:00
Dan Brown
b54702ab08 Merge branch 'v0.31.x' into release 2021-02-06 14:40:47 +00:00
Dan Brown
c4fdcfc5d1 Updated version for release v0.31.5 2021-02-02 20:58:06 +00:00
Dan Brown
cb8117e8df Merge branch 'v0.31.x' into release 2021-02-02 20:57:41 +00:00
Dan Brown
5a218d5056 Updated version and assets for release v0.31.4 2021-01-16 17:50:45 +00:00
Dan Brown
8dbc5cf9c6 Merge branch 'master' into release 2021-01-16 17:50:11 +00:00
Dan Brown
71e81615a3 Updated version for release v0.31.3 2021-01-10 23:29:58 +00:00
Dan Brown
611d37da04 Merge branch 'master' into release 2021-01-10 23:29:11 +00:00
Dan Brown
0e799a3857 Updated version and assets for release v0.31.2 2021-01-10 14:05:16 +00:00
Dan Brown
b91d6e2bfa Merge branch 'master' into release 2021-01-10 14:04:59 +00:00
Dan Brown
ea16ad7e94 Updated version and assets for release v0.31.1 2021-01-04 18:41:55 +00:00
Dan Brown
ba6eb54552 Merge branch 'master' into release 2021-01-04 18:41:26 +00:00
Dan Brown
f705e7683b Updated assets for release v0.31.0 again 2021-01-03 22:33:36 +00:00
Dan Brown
dc996adb20 Merge branch 'master' into release 2021-01-03 22:32:40 +00:00
Dan Brown
a64c638ccc Updated version and assets for release v0.31.0 2021-01-03 21:52:37 +00:00
Dan Brown
359c067279 Merge branch 'master' into release 2021-01-03 21:52:00 +00:00
Dan Brown
66a746e297 Updated version for release v0.30.7 2020-12-18 14:13:40 +00:00
Dan Brown
a4d43ee24b Merge branch 'v0.30.x' into release 2020-12-18 14:13:19 +00:00
Dan Brown
f7793a70a9 Updated version for release v0.30.6 2020-12-17 21:07:06 +00:00
Dan Brown
ceba3d31fb Merge branch 'v0.30.x' into release 2020-12-17 21:03:20 +00:00
Dan Brown
eecc08edde Updated version for release v0.30.5 2020-12-06 21:05:43 +00:00
Dan Brown
eb19aadc75 Merge branch 'v0.30.x' into release 2020-12-06 21:05:11 +00:00
Dan Brown
06c81e69b9 Updated version and assets for release v0.30.4 2020-10-31 16:52:33 +00:00
Dan Brown
3dc3d4a639 Merge branch 'master' into release 2020-10-31 16:51:54 +00:00
Dan Brown
94c59c1e3d Updated version and assets for release v0.30.3 2020-10-13 22:50:52 +01:00
Dan Brown
4d2205853a Merge branch 'master' into release 2020-10-13 22:50:30 +01:00
Dan Brown
751772b87a Updated version and assets for release v0.30.2 2020-09-30 22:44:58 +01:00
Dan Brown
76e30869e1 Merge branch 'master' into release 2020-09-30 22:44:17 +01:00
Dan Brown
3edc9fe9eb Updated version and assets for release v0.30.1 2020-09-26 17:51:37 +01:00
Dan Brown
616c62703e Merge branch 'master' into release 2020-09-26 17:50:25 +01:00
Dan Brown
ecd56917e7 Updated version and assets for release v0.30.0 2020-09-20 10:33:18 +01:00
Dan Brown
e22c9cae91 Merge branch 'master' into release 2020-09-20 10:30:10 +01:00
Dan Brown
29ddb6e1b9 Updated version and assets for release v0.29.3 2020-05-12 22:34:01 +01:00
Dan Brown
2ff90e2ff0 Merge branch 'master' into release 2020-05-12 22:33:27 +01:00
Dan Brown
04ecc128a2 Updated version and assets for release v0.29.2 2020-05-02 11:49:21 +01:00
Dan Brown
87d1d3423b Merge branch 'master' into release 2020-05-02 11:48:48 +01:00
Dan Brown
4818192a2a Updated version and assets for release v0.29.1 2020-04-28 12:30:31 +01:00
Dan Brown
965dd97f54 Merge branch 'master' into release 2020-04-28 12:30:09 +01:00
Dan Brown
195b74926c Updated version and assets for release v0.29.0 2020-04-13 16:10:23 +01:00
Dan Brown
2120db12b2 Merge branch 'master' into release 2020-04-13 16:10:11 +01:00
Dan Brown
ed563fef28 Updated version and assets for release v0.28.3 2020-03-14 22:31:42 +00:00
Dan Brown
0d31a8e3f1 Merge branch 'master' into release 2020-03-14 22:31:11 +00:00
Dan Brown
b8354b974b Updated version and assets for release v0.28.2 2020-02-15 22:36:08 +00:00
Dan Brown
034c1e289d Merge branch 'master' into release 2020-02-15 22:35:46 +00:00
Dan Brown
f31605a3de Updated version and assets for release v0.28.1 2020-02-15 22:08:06 +00:00
Dan Brown
e7cc75c74d Merge branch 'master' into release 2020-02-15 22:07:17 +00:00
Dan Brown
4b79d5e4e8 Updated version and assets for release v0.28.0 2020-02-03 22:44:45 +00:00
Dan Brown
34854915b3 Merge branch 'master' into release 2020-02-03 22:43:58 +00:00
Dan Brown
af6f34b529 Updated version and assets for release v0.27.5 2019-10-16 16:35:50 +01:00
Dan Brown
fb82a2b896 Merge branch 'patching-v0.27' into release 2019-10-16 16:35:10 +01:00
Dan Brown
5b464938b6 Updated version and assets for release v0.27.4 2019-09-07 13:30:08 +01:00
Dan Brown
81f954890d Merge branch 'patching-v0.27' into release 2019-09-07 13:29:53 +01:00
Dan Brown
0e2bbcec62 Updated version and assets for release v0.27.3 2019-09-03 21:50:12 +01:00
Dan Brown
fdd339f525 Merge branch 'master' into release 2019-09-03 21:49:46 +01:00
Dan Brown
8cf7d6a83d Updated version and assets for release v0.27.2 2019-09-01 12:12:23 +01:00
Dan Brown
58a5008718 Merge branch 'master' into release 2019-09-01 12:12:10 +01:00
Dan Brown
c44a8df55d Updated version and assets for release v0.27.1 2019-09-01 11:13:50 +01:00
Dan Brown
ff1494c519 Merge branch 'master' into release 2019-09-01 11:13:18 +01:00
Dan Brown
b8ce8fd852 Updated assets for release v0.27 2019-08-31 14:16:14 +01:00
Dan Brown
75e7454a5f Merge branch 'master' into release and set version 2019-08-31 14:15:18 +01:00
Dan Brown
2558ea8931 Updated version for release v0.26.4 2019-08-06 21:42:09 +01:00
Dan Brown
ac0f47a4b2 Merge branch 'v0.26' into release 2019-08-06 21:41:06 +01:00
Dan Brown
4f16129869 Updated version for release v0.26.3 2019-07-10 20:21:22 +01:00
Dan Brown
64a8037fdd Merge branch 'v0.26' into release 2019-07-10 20:19:54 +01:00
Dan Brown
7502ba1bc8 Updated version and assets for release v0.26.2 2019-05-27 13:48:20 +01:00
Dan Brown
33a04697ef Merge branch 'master' into release 2019-05-27 13:47:47 +01:00
Dan Brown
b70a5c0cdb Updated version and assets for release v0.26.1 2019-05-07 23:05:47 +01:00
Dan Brown
9443ae9f40 Merge branch 'master' into release 2019-05-07 23:05:10 +01:00
Dan Brown
220c2a4102 Updated version and assets for release v0.26.0 2019-05-06 18:58:56 +01:00
Dan Brown
e9914eb301 Merge branch 'master' into release 2019-05-06 18:57:58 +01:00
Dan Brown
934512d09c Updated version and assets for release v0.25.5 2019-03-24 19:45:17 +00:00
Dan Brown
9102c90986 Merge branch 'master' into release 2019-03-24 19:45:00 +00:00
Dan Brown
c3e74219c4 Updated version and assets for release v0.25.4 2019-03-21 19:46:19 +00:00
Dan Brown
13c9d7bc2d Merge branch 'master' into release 2019-03-21 19:43:48 +00:00
Dan Brown
119b539586 Updated version and assets for release v0.25.3 2019-03-21 00:03:26 +00:00
Dan Brown
29a5c180f0 Merge branch 'master' into release 2019-03-21 00:02:33 +00:00
Dan Brown
7906602291 Updated version and assets for release v0.25.2 2019-03-10 13:45:21 +00:00
Dan Brown
6dafe773ff Merge branch 'master' into release 2019-03-10 13:44:29 +00:00
Dan Brown
25bc28a1be Updated version and assets for release v0.25.1 2019-01-20 15:42:32 +00:00
Dan Brown
4c561c7fa0 Merge branch 'master' into release 2019-01-20 15:41:24 +00:00
Dan Brown
95b3e78573 Updated version and assets for release v0.25.0 2019-01-12 22:48:53 +00:00
Dan Brown
63a345bc93 Merge branch 'master' into release 2019-01-12 22:47:07 +00:00
Dan Brown
e093a172cb Updated assets and version for release v0.24.3 2018-11-27 21:52:20 +00:00
Dan Brown
4b01f8934b Merge branch 'master' into release 2018-11-27 21:51:32 +00:00
Dan Brown
bc116b45b5 Re-updated assets for release v0.24.2 2018-11-10 16:10:22 +00:00
Dan Brown
a059960b9e Merge branch 'master' into release 2018-11-10 16:09:14 +00:00
Dan Brown
7770966fed Updated assets for release v0.24.2 2018-11-10 16:01:55 +00:00
Dan Brown
d7adcf6c69 Merge branch 'master' into release 2018-11-10 16:01:01 +00:00
Dan Brown
04a364dcc3 Incremented version for v0.24.1 2018-09-24 16:34:16 +01:00
Dan Brown
db83ac7eaa Merge branch 'master' into release 2018-09-24 16:32:30 +01:00
Dan Brown
3ca9dddf61 Merge branch 'master' into release 2018-09-24 15:59:39 +01:00
Dan Brown
bf74f53ca7 Updated assets for release and incremented version 2018-09-24 12:18:27 +01:00
Dan Brown
9d67efb4a4 Merge branch 'master' into release 2018-09-24 12:08:21 +01:00
Dan Brown
3a39b9f440 Merge pull request #1022 from BookStackApp/revert-983-master
Revert "Update german translation"
2018-09-22 18:33:29 +01:00
Dan Brown
27f7aab375 Revert "Update german translation" 2018-09-22 18:33:15 +01:00
Dan Brown
337da0c467 Merge pull request #983 from vriic/master
Update german translation
2018-09-22 18:27:04 +01:00
Nikolai Nikolajevic
f56b3560c4 Update german translation 2018-08-23 16:17:46 +02:00
Dan Brown
02dfe11ce6 Increment version for release v0.23.2 2018-08-19 15:33:23 +01:00
Dan Brown
83d06beb70 Merge branch 'master' into release 2018-08-19 15:33:10 +01:00
Dan Brown
a8cfc059c8 Updated version for release v0.23.1 2018-08-12 14:22:53 +01:00
Dan Brown
1614b2bab0 Merge branch 'master' into release 2018-08-12 14:22:17 +01:00
Dan Brown
4bdec0d214 Updated version and assets for release v0.23 2018-07-29 20:28:49 +01:00
Dan Brown
6a7d7e7c2b Merge branch 'master' into release 2018-07-29 20:26:00 +01:00
Dan Brown
30d4674657 Updated assets for release v0.22 2018-05-28 14:19:14 +01:00
Dan Brown
9f961f95f8 Merge branch 'master' into release 2018-05-28 14:19:04 +01:00
Dan Brown
bab99a26ec Updated assets and version for v0.21 release 2018-04-22 20:21:22 +01:00
Dan Brown
9a7fecd269 Merge branch 'master' into release 2018-04-22 20:19:02 +01:00
Dan Brown
a8dc0d449b Updated the version because i'm such a plonker
And forgot to do this last release.
I wonder if there's a simple commit hook that could prevent the same two
versions twice in a row?
2018-03-30 15:41:46 +01:00
Dan Brown
a0381f76bf Merge branch 'v0.20' into release 2018-03-30 15:33:23 +01:00
Dan Brown
6102f66daa Updated assets for release v0.20.1 2018-03-25 16:58:14 +01:00
Dan Brown
c6134d162d Merge branch 'master' into release 2018-03-25 16:54:48 +01:00
Dan Brown
2046f9b9de Updated assets for release v0.20.0 2018-02-11 18:20:17 +00:00
Dan Brown
ac3ba594a4 Merge branch 'master' into release and updated version 2018-02-11 18:19:38 +00:00
Dan Brown
22df25a480 Updated assets and version for v0.19.0 2017-12-10 18:21:07 +00:00
Dan Brown
8b30c7f02e Merge branch 'master' into release 2017-12-10 18:19:20 +00:00
Dan Brown
757cdddc7c Updated version and JS for release v0.18.5 2017-11-11 18:33:04 +00:00
Dan Brown
df95e99680 Updated assets and version for release v0.18.4 2017-10-15 19:28:29 +01:00
Dan Brown
5a6d544db7 Merge branch 'master' into release 2017-10-15 19:27:50 +01:00
Dan Brown
16117d329c Merge branch 'master' into release, Updated version 2017-10-06 21:05:45 +01:00
Dan Brown
e90da18ada Updated assets and version for v0.18.2 release 2017-10-01 18:12:59 +01:00
Dan Brown
a08d80e1cc Merge branch 'master' into release 2017-10-01 18:12:07 +01:00
Dan Brown
6258175922 Updated assets and version for v0.18.1 release 2017-09-20 21:36:17 +01:00
Dan Brown
15736777a0 Merge branch 'master' into release 2017-09-20 21:35:33 +01:00
Dan Brown
75915e8a94 Updated assets for release v0.18 2017-09-10 17:07:57 +01:00
Dan Brown
9bde0ae4ea Merge branch 'master' into release 2017-09-10 17:05:05 +01:00
Dan Brown
0c802d1f86 Updated assets and version for release v0.17.4 2017-07-28 13:04:21 +01:00
Dan Brown
b7a96c6466 Merge branch 'master' into release 2017-07-28 13:03:36 +01:00
Dan Brown
4b645a82c7 Updated version for release 2017-07-22 17:27:01 +01:00
Dan Brown
d599b77b6f Merge branch 'master' into release 2017-07-22 17:26:44 +01:00
Dan Brown
26e93dc8c1 Updated assets and version for release v0.17.2 2017-07-22 16:49:07 +01:00
Dan Brown
a4c9a8491b Merge branch 'master' into release 2017-07-22 16:46:57 +01:00
Dan Brown
70ee636d87 Updated css and version for release 2017-07-10 20:52:32 +01:00
Dan Brown
b35f6dbb03 Merge branch 'master' into release 2017-07-10 20:51:25 +01:00
Dan Brown
67d9e24d8f Merge branch 'master' into release
Also updated assets, Version number
2017-07-02 22:52:26 +01:00
Dan Brown
3903fda6ca Incremented version 2017-06-04 15:38:49 +01:00
Dan Brown
441e46ebaa Merge branch 'v0.16' into release 2017-06-04 15:38:29 +01:00
Dan Brown
1f4260f359 Updated version for release v0.16.2 2017-05-07 19:35:51 +01:00
Dan Brown
dc0bf8ad4e Merge branch 'master' into release 2017-05-07 19:35:34 +01:00
Dan Brown
102e326e6a Updated JS and version for release v0.16.1 2017-04-30 19:51:23 +01:00
Dan Brown
2b25bf6f3b Merge branch 'master' into release 2017-04-30 19:50:29 +01:00
Dan Brown
f93280696d Updated assets for release v0.16 2017-04-23 20:42:28 +01:00
Dan Brown
1787391b07 Merge branch 'master' into release 2017-04-23 20:41:45 +01:00
Dan Brown
a74a8ee483 Updated version for v0.15.3 2017-03-23 22:22:16 +00:00
Dan Brown
7fa5405cb7 Merge branch 'master' into release 2017-03-23 22:21:04 +00:00
Dan Brown
6725ddcc41 Updated version for release v0.15.2 2017-03-05 15:50:52 +00:00
Dan Brown
bce941db3f Merge branch 'master' into release 2017-03-05 15:49:47 +00:00
Dan Brown
6d926048ec Updated to version v0.15.1 2017-02-27 16:59:10 +00:00
Dan Brown
5335c973b4 Merge branch 'master' into release 2017-02-27 16:58:20 +00:00
Dan Brown
15c3e5c96e Updated assets for release v0.15 2017-02-27 14:58:02 +00:00
Dan Brown
a5d5904969 Merge branch 'master' into release 2017-02-27 14:57:38 +00:00
Dan Brown
598758b991 Updated version for v0.14.3 2017-02-05 21:23:27 +00:00
Dan Brown
9926e23bc8 Merge branch 'v0.14' into release 2017-02-05 21:21:54 +00:00
Dan Brown
5d3264bc63 Updated assets for release v0.14.2 2017-02-01 22:27:04 +00:00
Dan Brown
d71f819f95 Merge branch 'v0.14' into release 2017-02-01 22:22:38 +00:00
Dan Brown
ee13509760 Updated version number 2017-01-23 22:28:31 +00:00
Dan Brown
82d7bb1f32 Merge branch 'master' into release 2017-01-23 22:28:02 +00:00
Dan Brown
cdfda508d8 Updated assets for release v0.14 2017-01-22 12:36:10 +00:00
Dan Brown
da941e584f Merge branch 'master' into release ready for v0.14 2017-01-22 12:31:27 +00:00
Dan Brown
65874d7b96 Updated assets for release v0.13.1 2016-11-27 19:42:33 +00:00
Dan Brown
ac9b8f405c Merge fixes from master for release v0.13.1 2016-11-27 19:41:12 +00:00
Dan Brown
8d1419a12e Update assets and version for release v0.13 2016-11-13 12:29:52 +00:00
Dan Brown
04f7a7d301 Merge branch 'master' into release 2016-11-13 12:26:56 +00:00
Dan Brown
c10d2a1493 Updated assets for release v0.12.2 2016-10-30 13:19:19 +00:00
Dan Brown
97bbf79ffd Merge branch 'v0.12' into release 2016-10-30 13:18:23 +00:00
Dan Brown
f7b01ae53d Updated assets for release v0.12.1 2016-09-06 20:50:15 +01:00
Dan Brown
d704e1dbba Merge branch 'master' into release 2016-09-06 20:49:15 +01:00
Dan Brown
ef2ff5e093 Updated assets for release v0.12 2016-09-05 19:49:42 +01:00
Dan Brown
7caed3b0db Merge branch 'master' into release 2016-09-05 19:35:21 +01:00
Dan Brown
45641d0754 Updated assets for release v0.11.2 2016-08-21 14:56:29 +01:00
Dan Brown
4b1d08ba99 Merge branch 'v0.11' into release 2016-08-21 14:55:11 +01:00
Dan Brown
160fa99ba4 Updated assets for release v0.11.1 2016-08-14 12:40:55 +01:00
Dan Brown
d2a5ab49ed Merge branch 'v0.11' into release 2016-08-14 12:37:48 +01:00
Dan Brown
c6404d8917 Updated assets for release v0.11 2016-07-03 10:56:16 +01:00
Dan Brown
7113807f12 Merge branch 'master' into release 2016-07-03 10:52:04 +01:00
Dan Brown
be711215e8 Updated assets for release v0.10 2016-05-22 15:12:47 +01:00
Dan Brown
7e3b404240 Merge branch 'master' into release for version v0.10 2016-05-22 15:11:50 +01:00
Dan Brown
e86901ca20 Updated assets for release v0.9.3 2016-05-03 21:13:02 +01:00
Dan Brown
bdfa61c8b2 Merge branch 'v0.9' into release 2016-05-03 21:11:01 +01:00
Dan Brown
2cc36787f5 Updated assets for release 0.9.2 2016-04-15 19:57:02 +01:00
Dan Brown
448ac61b48 Merge branch 'master' into release 2016-04-15 19:52:59 +01:00
Dan Brown
753f6394f7 Merge branch 'master' into release 2016-04-12 20:09:14 +01:00
Dan Brown
b1faf65934 Updated assets for release 0.9.0 2016-04-09 15:49:02 +01:00
Dan Brown
09f478bd74 Merge branch 'master' into release 2016-04-09 15:47:14 +01:00
Dan Brown
a0497feddd Updated assets for release 0.8.2 2016-03-30 21:44:30 +01:00
Dan Brown
789693bde9 Merge branch 'v0.8' into release 2016-03-30 21:32:46 +01:00
Dan Brown
1fe933e4ea Merge branch 'master' into release 2016-03-13 15:38:06 +00:00
Dan Brown
724b4b5a70 Updated assets for release 0.8.0 2016-03-13 15:15:14 +00:00
Dan Brown
1778a56146 Merge branch 'master' into release 2016-03-13 15:13:23 +00:00
Dan Brown
744865fcb2 Updated assets for release 0.7.6 2016-03-06 13:28:44 +00:00
Dan Brown
7f8c8b448d Merged branch master into release 2016-03-06 13:26:29 +00:00
Dan Brown
a67c53826d Updated assets for release 0.7.5 2016-02-25 21:24:09 +00:00
Dan Brown
14b131e850 Merge branch 'master' into release 2016-02-25 21:23:06 +00:00
Dan Brown
9b55a52b85 Updated assets for release 0.7.4 2016-02-11 22:35:01 +00:00
Dan Brown
db1d10e80f Merge branch 'master' into release 2016-02-11 22:29:29 +00:00
Dan Brown
1be576966f Updated assets for release 0.7.3 2016-02-08 20:47:33 +00:00
Dan Brown
b97e792c5f Merge branch 'master' into release 2016-02-08 20:45:48 +00:00
Dan Brown
8dec674cc3 Merge branch 'master' into release 2016-02-02 07:35:20 +00:00
Dan Brown
f784c03746 Merge branch 'master' into release 2016-02-01 18:31:04 +00:00
Dan Brown
148e172fe8 Updated assets for release 0.7 2016-01-31 18:03:55 +00:00
Dan Brown
56ae86646f Merge branch 'master' into release 2016-01-31 18:01:25 +00:00
Dan Brown
1d2b6fdfa2 Add updated assets 2016-01-02 14:50:59 +00:00
Dan Brown
4fc75beed4 Merge branch 'master' into release 2016-01-02 14:49:05 +00:00
Dan Brown
3b3bc0c4bf Updated compiled assets 2015-12-31 17:26:22 +00:00
Dan Brown
910faab88e Merge branch 'master' into release 2015-12-31 17:22:03 +00:00
Dan Brown
f184d763ad Added build folder to release 2015-12-16 17:53:53 +00:00
Dan Brown
a91d42634d Merge branch 'master' into release 2015-12-16 17:29:34 +00:00
Dan Brown
f517ef3616 Added new asset structure 2015-12-16 17:27:53 +00:00
Dan Brown
e99507ddcf Merge branch 'master' into release 2015-12-16 17:21:21 +00:00
Dan Brown
d2cacf1945 Release update 2015-12-01 21:30:21 +00:00
Dan Brown
448ac1405b Merge branch 'master' into release 2015-12-01 21:15:08 +00:00
Dan Brown
6ad21ce885 Added built assets for release 2015-11-30 21:59:34 +00:00
112 changed files with 1232 additions and 2473 deletions

6
.gitignore vendored
View File

@@ -5,10 +5,10 @@ Homestead.yaml
.idea
npm-debug.log
yarn-error.log
/public/dist
/public/dist/*.map
/public/plugins
/public/css
/public/js
/public/css/*.map
/public/js/*.map
/public/bower
/public/build/
/storage/images

View File

@@ -15,8 +15,6 @@ class ActivityQueries
{
protected PermissionApplicator $permissions;
protected array $fieldsForLists = ['id', 'type', 'detail', 'activities.entity_type', 'activities.entity_id', 'user_id', 'created_at'];
public function __construct(PermissionApplicator $permissions)
{
$this->permissions = $permissions;
@@ -27,11 +25,9 @@ class ActivityQueries
*/
public function latest(int $count = 20, int $page = 0): array
{
$query = Activity::query()->select($this->fieldsForLists);
$activityList = $this->permissions
->restrictEntityRelationQuery($query, 'activities', 'entity_id', 'entity_type')
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')
->whereNotNull('activities.entity_id')
->with(['user', 'entity'])
->skip($count * $page)
->take($count)
@@ -82,12 +78,10 @@ class ActivityQueries
*/
public function userActivity(User $user, int $count = 20, int $page = 0): array
{
$query = Activity::query()->select($this->fieldsForLists);
$activityList = $this->permissions
->restrictEntityRelationQuery($query, 'activities', 'entity_id', 'entity_type')
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')
->where('user_id', '=', $user->id)
->whereNotNull('activities.entity_id')
->skip($count * $page)
->take($count)
->get();

View File

@@ -29,16 +29,15 @@ class TagRepo
$sort = 'value';
}
$entityTypeCol = DB::getTablePrefix() . 'tags.entity_type';
$query = Tag::query()
->select([
'name',
($searchTerm || $nameFilter) ? 'value' : DB::raw('COUNT(distinct value) as `values`'),
DB::raw('COUNT(id) as usages'),
DB::raw("SUM(IF({$entityTypeCol} = 'page', 1, 0)) as page_count"),
DB::raw("SUM(IF({$entityTypeCol} = 'chapter', 1, 0)) as chapter_count"),
DB::raw("SUM(IF({$entityTypeCol} = 'book', 1, 0)) as book_count"),
DB::raw("SUM(IF({$entityTypeCol} = 'bookshelf', 1, 0)) as shelf_count"),
DB::raw('SUM(IF(entity_type = \'page\', 1, 0)) as page_count'),
DB::raw('SUM(IF(entity_type = \'chapter\', 1, 0)) as chapter_count'),
DB::raw('SUM(IF(entity_type = \'book\', 1, 0)) as book_count'),
DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
])
->orderBy($sort, $listOptions->getOrder());

View File

@@ -1,18 +0,0 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Model;
/**
* @property int $id
* @property ?int $role_id
* @property ?int $user_id
* @property string $entity_type
* @property int $entity_id
* @property bool $view
*/
class CollapsedPermission extends Model
{
protected $table = 'entity_permissions_collapsed';
}

View File

@@ -1,278 +0,0 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Facades\DB;
/**
* Collapsed permissions act as a "flattened" view of entity-level permissions in the system
* so inheritance does not have to managed as part of permission querying.
*/
class CollapsedPermissionBuilder
{
/**
* Re-generate all collapsed permissions from scratch.
*/
public function rebuildForAll()
{
DB::table('entity_permissions_collapsed')->truncate();
// Chunk through all books
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) {
$this->buildForBooks($books, false);
});
// Chunk through all bookshelves
Bookshelf::query()->withTrashed()
->select(['id'])
->chunk(50, function (EloquentCollection $shelves) {
$this->generateCollapsedPermissions($shelves->all());
});
}
/**
* Rebuild the collapsed permissions for a particular entity.
*/
public function rebuildForEntity(Entity $entity)
{
$entities = [$entity];
if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildForBooks($books, true);
return;
}
/** @var BookChild $entity */
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity instanceof Page && $entity->chapter_id) {
$entities[] = $entity->chapter;
}
if ($entity instanceof Chapter) {
foreach ($entity->pages as $page) {
$entities[] = $page;
}
}
$this->buildForEntities($entities);
}
/**
* Get a query for fetching a book with its children.
*/
protected function bookFetchQuery(): Builder
{
return Book::query()->withTrashed()
->select(['id'])->with([
'chapters' => function ($query) {
$query->withTrashed()->select(['id', 'book_id']);
},
'pages' => function ($query) {
$query->withTrashed()->select(['id', 'book_id', 'chapter_id']);
},
]);
}
/**
* Build collapsed permissions for the given books.
*/
protected function buildForBooks(EloquentCollection $books, bool $deleteOld)
{
$entities = clone $books;
/** @var Book $book */
foreach ($books->all() as $book) {
foreach ($book->getRelation('chapters') as $chapter) {
$entities->push($chapter);
}
foreach ($book->getRelation('pages') as $page) {
$entities->push($page);
}
}
if ($deleteOld) {
$this->deleteForEntities($entities->all());
}
$this->generateCollapsedPermissions($entities->all());
}
/**
* Rebuild the collapsed permissions for a collection of entities.
*/
protected function buildForEntities(array $entities)
{
$this->deleteForEntities($entities);
$this->generateCollapsedPermissions($entities);
}
/**
* Delete the stored collapsed permissions for a list of entities.
*
* @param Entity[] $entities
*/
protected function deleteForEntities(array $entities)
{
$simpleEntities = $this->entitiesToSimpleEntities($entities);
$idsByType = $this->entitiesToTypeIdMap($simpleEntities);
DB::transaction(function () use ($idsByType) {
foreach ($idsByType as $type => $ids) {
foreach (array_chunk($ids, 1000) as $idChunk) {
DB::table('entity_permissions_collapsed')
->where('entity_type', '=', $type)
->whereIn('entity_id', $idChunk)
->delete();
}
}
});
}
/**
* Convert the given list of entities into "SimpleEntityData" representations
* for faster usage and property access.
*
* @param Entity[] $entities
*
* @return SimpleEntityData[]
*/
protected function entitiesToSimpleEntities(array $entities): array
{
$simpleEntities = [];
foreach ($entities as $entity) {
$attrs = $entity->getAttributes();
$simple = new SimpleEntityData();
$simple->id = $attrs['id'];
$simple->type = $entity->getMorphClass();
$simple->book_id = $attrs['book_id'] ?? null;
$simple->chapter_id = $attrs['chapter_id'] ?? null;
$simpleEntities[] = $simple;
}
return $simpleEntities;
}
/**
* Create & Save collapsed entity permissions.
*
* @param Entity[] $originalEntities
*/
protected function generateCollapsedPermissions(array $originalEntities)
{
$entities = $this->entitiesToSimpleEntities($originalEntities);
$collapsedPermData = [];
// Fetch related entity permissions
$permissions = $this->getEntityPermissionsForEntities($entities);
// Create a mapping of explicit entity permissions
$permissionMap = new EntityPermissionMap($permissions);
// Create Joint Permission Data
foreach ($entities as $entity) {
array_push($collapsedPermData, ...$this->createCollapsedPermissionData($entity, $permissionMap));
}
DB::transaction(function () use ($collapsedPermData) {
foreach (array_chunk($collapsedPermData, 1000) as $dataChunk) {
DB::table('entity_permissions_collapsed')->insert($dataChunk);
}
});
}
/**
* Create collapsed permission data for the given entity using the given permission map.
*/
protected function createCollapsedPermissionData(SimpleEntityData $entity, EntityPermissionMap $permissionMap): array
{
$chain = [
$entity->type . ':' . $entity->id,
$entity->chapter_id ? ('chapter:' . $entity->chapter_id) : null,
$entity->book_id ? ('book:' . $entity->book_id) : null,
];
$permissionData = [];
$overridesApplied = [];
foreach ($chain as $entityTypeId) {
if ($entityTypeId === null) {
continue;
}
$permissions = $permissionMap->getForEntity($entityTypeId);
foreach ($permissions as $permission) {
$related = $permission->getAssignedType() . ':' . $permission->getAssignedTypeId();
if (!isset($overridesApplied[$related])) {
$permissionData[] = [
'role_id' => $permission->role_id,
'user_id' => $permission->user_id,
'view' => $permission->view,
'entity_type' => $entity->type,
'entity_id' => $entity->id,
];
$overridesApplied[$related] = true;
}
}
}
return $permissionData;
}
/**
* From the given entity list, provide back a mapping of entity types to
* the ids of that given type. The type used is the DB morph class.
*
* @param SimpleEntityData[] $entities
*
* @return array<string, int[]>
*/
protected function entitiesToTypeIdMap(array $entities): array
{
$idsByType = [];
foreach ($entities as $entity) {
if (!isset($idsByType[$entity->type])) {
$idsByType[$entity->type] = [];
}
$idsByType[$entity->type][] = $entity->id;
}
return $idsByType;
}
/**
* Get the entity permissions for all the given entities.
*
* @param SimpleEntityData[] $entities
*
* @return EntityPermission[]
*/
protected function getEntityPermissionsForEntities(array $entities): array
{
$idsByType = $this->entitiesToTypeIdMap($entities);
$permissionFetch = EntityPermission::query()
->where(function (Builder $query) use ($idsByType) {
foreach ($idsByType as $type => $ids) {
$query->orWhere(function (Builder $query) use ($type, $ids) {
$query->where('entity_type', '=', $type)->whereIn('entity_id', $ids);
});
}
});
return $permissionFetch->get()->all();
}
}

View File

@@ -3,14 +3,13 @@
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* @property int $id
* @property int $role_id
* @property int $user_id
* @property int $entity_id
* @property string $entity_type
* @property boolean $view
@@ -22,9 +21,17 @@ class EntityPermission extends Model
{
public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
protected $fillable = ['role_id', 'user_id', 'view', 'create', 'update', 'delete'];
protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
public $timestamps = false;
/**
* Get this restriction's attached entity.
*/
public function restrictable(): MorphTo
{
return $this->morphTo('restrictable');
}
/**
* Get the role assigned to this entity permission.
*/
@@ -32,38 +39,4 @@ class EntityPermission extends Model
{
return $this->belongsTo(Role::class);
}
/**
* Get the user assigned to this entity permission.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Get the type of entity permission this is.
* Will be one of: user, role, fallback
*/
public function getAssignedType(): string
{
if ($this->user_id) {
return 'user';
}
if ($this->role_id) {
return 'role';
}
return 'fallback';
}
/**
* Get the ID for the assigned type of permission.
* (Role/User ID). Defaults to 0 for fallback.
*/
public function getAssignedTypeId(): int
{
return $this->user_id ?? $this->role_id ?? 0;
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace BookStack\Auth\Permissions;
class EntityPermissionMap
{
protected array $map = [];
/**
* @param EntityPermission[] $permissions
*/
public function __construct(array $permissions = [])
{
foreach ($permissions as $entityPermission) {
$this->addPermission($entityPermission);
}
}
protected function addPermission(EntityPermission $permission)
{
$entityCombinedId = $permission->entity_type . ':' . $permission->entity_id;
if (!isset($this->map[$entityCombinedId])) {
$this->map[$entityCombinedId] = [];
}
$this->map[$entityCombinedId][] = $permission;
}
/**
* @return EntityPermission[]
*/
public function getForEntity(string $typeIdString): array
{
return $this->map[$typeIdString] ?? [];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Entities\Models\Entity;
use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class JointPermission extends Model
{
protected $primaryKey = null;
public $timestamps = false;
/**
* Get the role that this points to.
*/
public function role(): BelongsTo
{
return $this->belongsTo(Role::class);
}
/**
* Get the entity this points to.
*/
public function entity(): MorphOne
{
return $this->morphOne(Entity::class, 'entity');
}
}

View File

@@ -0,0 +1,408 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Facades\DB;
/**
* Joint permissions provide a pre-query "cached" table of view permissions for all core entity
* types for all roles in the system. This class generates out that table for different scenarios.
*/
class JointPermissionBuilder
{
/**
* @var array<string, array<int, SimpleEntityData>>
*/
protected array $entityCache;
/**
* Re-generate all entity permission from scratch.
*/
public function rebuildForAll()
{
JointPermission::query()->truncate();
// Get all roles (Should be the most limited dimension)
$roles = Role::query()->with('permissions')->get()->all();
// Chunk through all books
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->withTrashed()->select(['id', 'owned_by'])
->chunk(50, function (EloquentCollection $shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles);
});
}
/**
* Rebuild the entity jointPermissions for a particular entity.
*/
public function rebuildForEntity(Entity $entity)
{
$entities = [$entity];
if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildJointPermissionsForBooks($books, Role::query()->with('permissions')->get()->all(), true);
return;
}
/** @var BookChild $entity */
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity instanceof Page && $entity->chapter_id) {
$entities[] = $entity->chapter;
}
if ($entity instanceof Chapter) {
foreach ($entity->pages as $page) {
$entities[] = $page;
}
}
$this->buildJointPermissionsForEntities($entities);
}
/**
* Build the entity jointPermissions for a particular role.
*/
public function rebuildForRole(Role $role)
{
$roles = [$role];
$role->jointPermissions()->delete();
$role->load('permissions');
// Chunk through all books
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->select(['id', 'owned_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles);
});
}
/**
* Prepare the local entity cache and ensure it's empty.
*
* @param SimpleEntityData[] $entities
*/
protected function readyEntityCache(array $entities)
{
$this->entityCache = [];
foreach ($entities as $entity) {
if (!isset($this->entityCache[$entity->type])) {
$this->entityCache[$entity->type] = [];
}
$this->entityCache[$entity->type][$entity->id] = $entity;
}
}
/**
* Get a book via ID, Checks local cache.
*/
protected function getBook(int $bookId): SimpleEntityData
{
return $this->entityCache['book'][$bookId];
}
/**
* Get a chapter via ID, Checks local cache.
*/
protected function getChapter(int $chapterId): SimpleEntityData
{
return $this->entityCache['chapter'][$chapterId];
}
/**
* Get a query for fetching a book with its children.
*/
protected function bookFetchQuery(): Builder
{
return Book::query()->withTrashed()
->select(['id', 'owned_by'])->with([
'chapters' => function ($query) {
$query->withTrashed()->select(['id', 'owned_by', 'book_id']);
},
'pages' => function ($query) {
$query->withTrashed()->select(['id', 'owned_by', 'book_id', 'chapter_id']);
},
]);
}
/**
* Build joint permissions for the given book and role combinations.
*/
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
{
$entities = clone $books;
/** @var Book $book */
foreach ($books->all() as $book) {
foreach ($book->getRelation('chapters') as $chapter) {
$entities->push($chapter);
}
foreach ($book->getRelation('pages') as $page) {
$entities->push($page);
}
}
if ($deleteOld) {
$this->deleteManyJointPermissionsForEntities($entities->all());
}
$this->createManyJointPermissions($entities->all(), $roles);
}
/**
* Rebuild the entity jointPermissions for a collection of entities.
*/
protected function buildJointPermissionsForEntities(array $entities)
{
$roles = Role::query()->get()->values()->all();
$this->deleteManyJointPermissionsForEntities($entities);
$this->createManyJointPermissions($entities, $roles);
}
/**
* Delete all the entity jointPermissions for a list of entities.
*
* @param Entity[] $entities
*/
protected function deleteManyJointPermissionsForEntities(array $entities)
{
$simpleEntities = $this->entitiesToSimpleEntities($entities);
$idsByType = $this->entitiesToTypeIdMap($simpleEntities);
DB::transaction(function () use ($idsByType) {
foreach ($idsByType as $type => $ids) {
foreach (array_chunk($ids, 1000) as $idChunk) {
DB::table('joint_permissions')
->where('entity_type', '=', $type)
->whereIn('entity_id', $idChunk)
->delete();
}
}
});
}
/**
* @param Entity[] $entities
*
* @return SimpleEntityData[]
*/
protected function entitiesToSimpleEntities(array $entities): array
{
$simpleEntities = [];
foreach ($entities as $entity) {
$attrs = $entity->getAttributes();
$simple = new SimpleEntityData();
$simple->id = $attrs['id'];
$simple->type = $entity->getMorphClass();
$simple->owned_by = $attrs['owned_by'] ?? 0;
$simple->book_id = $attrs['book_id'] ?? null;
$simple->chapter_id = $attrs['chapter_id'] ?? null;
$simpleEntities[] = $simple;
}
return $simpleEntities;
}
/**
* Create & Save entity jointPermissions for many entities and roles.
*
* @param Entity[] $originalEntities
* @param Role[] $roles
*/
protected function createManyJointPermissions(array $originalEntities, array $roles)
{
$entities = $this->entitiesToSimpleEntities($originalEntities);
$this->readyEntityCache($entities);
$jointPermissions = [];
// Fetch related entity permissions
$permissions = $this->getEntityPermissionsForEntities($entities);
// Create a mapping of explicit entity permissions
$permissionMap = [];
foreach ($permissions as $permission) {
$key = $permission->entity_type . ':' . $permission->entity_id . ':' . $permission->role_id;
$permissionMap[$key] = $permission->view;
}
// Create a mapping of role permissions
$rolePermissionMap = [];
foreach ($roles as $role) {
foreach ($role->permissions as $permission) {
$rolePermissionMap[$role->getRawAttribute('id') . ':' . $permission->getRawAttribute('name')] = true;
}
}
// Create Joint Permission Data
foreach ($entities as $entity) {
foreach ($roles as $role) {
$jointPermissions[] = $this->createJointPermissionData(
$entity,
$role->getRawAttribute('id'),
$permissionMap,
$rolePermissionMap,
$role->system_name === 'admin'
);
}
}
DB::transaction(function () use ($jointPermissions) {
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
DB::table('joint_permissions')->insert($jointPermissionChunk);
}
});
}
/**
* From the given entity list, provide back a mapping of entity types to
* the ids of that given type. The type used is the DB morph class.
*
* @param SimpleEntityData[] $entities
*
* @return array<string, int[]>
*/
protected function entitiesToTypeIdMap(array $entities): array
{
$idsByType = [];
foreach ($entities as $entity) {
if (!isset($idsByType[$entity->type])) {
$idsByType[$entity->type] = [];
}
$idsByType[$entity->type][] = $entity->id;
}
return $idsByType;
}
/**
* Get the entity permissions for all the given entities.
*
* @param SimpleEntityData[] $entities
*
* @return EntityPermission[]
*/
protected function getEntityPermissionsForEntities(array $entities): array
{
$idsByType = $this->entitiesToTypeIdMap($entities);
$permissionFetch = EntityPermission::query()
->where(function (Builder $query) use ($idsByType) {
foreach ($idsByType as $type => $ids) {
$query->orWhere(function (Builder $query) use ($type, $ids) {
$query->where('entity_type', '=', $type)->whereIn('entity_id', $ids);
});
}
});
return $permissionFetch->get()->all();
}
/**
* Create entity permission data for an entity and role
* for a particular action.
*/
protected function createJointPermissionData(SimpleEntityData $entity, int $roleId, array $permissionMap, array $rolePermissionMap, bool $isAdminRole): array
{
$permissionPrefix = $entity->type . '-view';
$roleHasPermission = isset($rolePermissionMap[$roleId . ':' . $permissionPrefix . '-all']);
$roleHasPermissionOwn = isset($rolePermissionMap[$roleId . ':' . $permissionPrefix . '-own']);
if ($isAdminRole) {
return $this->createJointPermissionDataArray($entity, $roleId, true, true);
}
if ($this->entityPermissionsActiveForRole($permissionMap, $entity, $roleId)) {
$hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $roleId);
return $this->createJointPermissionDataArray($entity, $roleId, $hasAccess, $hasAccess);
}
if ($entity->type === 'book' || $entity->type === 'bookshelf') {
return $this->createJointPermissionDataArray($entity, $roleId, $roleHasPermission, $roleHasPermissionOwn);
}
// For chapters and pages, Check if explicit permissions are set on the Book.
$book = $this->getBook($entity->book_id);
$hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $book, $roleId);
$hasPermissiveAccessToParents = !$this->entityPermissionsActiveForRole($permissionMap, $book, $roleId);
// For pages with a chapter, Check if explicit permissions are set on the Chapter
if ($entity->type === 'page' && $entity->chapter_id !== 0) {
$chapter = $this->getChapter($entity->chapter_id);
$chapterRestricted = $this->entityPermissionsActiveForRole($permissionMap, $chapter, $roleId);
$hasPermissiveAccessToParents = $hasPermissiveAccessToParents && !$chapterRestricted;
if ($chapterRestricted) {
$hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $chapter, $roleId);
}
}
return $this->createJointPermissionDataArray(
$entity,
$roleId,
($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)),
($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents))
);
}
/**
* Check if entity permissions are defined within the given map, for the given entity and role.
* Checks for the default `role_id=0` backup option as a fallback.
*/
protected function entityPermissionsActiveForRole(array $permissionMap, SimpleEntityData $entity, int $roleId): bool
{
$keyPrefix = $entity->type . ':' . $entity->id . ':';
return isset($permissionMap[$keyPrefix . $roleId]) || isset($permissionMap[$keyPrefix . '0']);
}
/**
* Check for an active restriction in an entity map.
*/
protected function mapHasActiveRestriction(array $entityMap, SimpleEntityData $entity, int $roleId): bool
{
$roleKey = $entity->type . ':' . $entity->id . ':' . $roleId;
$defaultKey = $entity->type . ':' . $entity->id . ':0';
return $entityMap[$roleKey] ?? $entityMap[$defaultKey] ?? false;
}
/**
* Create an array of data with the information of an entity jointPermissions.
* Used to build data for bulk insertion.
*/
protected function createJointPermissionDataArray(SimpleEntityData $entity, int $roleId, bool $permissionAll, bool $permissionOwn): array
{
return [
'entity_id' => $entity->id,
'entity_type' => $entity->type,
'has_permission' => $permissionAll,
'has_permission_own' => $permissionOwn,
'owned_by' => $entity->owned_by,
'role_id' => $roleId,
];
}
}

View File

@@ -12,8 +12,6 @@ use BookStack\Traits\HasCreatorAndUpdater;
use BookStack\Traits\HasOwner;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
class PermissionApplicator
@@ -50,7 +48,7 @@ class PermissionApplicator
return $hasRolePermission;
}
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $user->id, $action);
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action);
return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions;
}
@@ -59,7 +57,7 @@ class PermissionApplicator
* Check if there are permissions that are applicable for the given entity item, action and roles.
* Returns null when no entity permissions are in force.
*/
protected function hasEntityPermission(Entity $entity, array $userRoleIds, int $userId, string $action): ?bool
protected function hasEntityPermission(Entity $entity, array $userRoleIds, string $action): ?bool
{
$this->ensureValidEntityAction($action);
@@ -68,63 +66,38 @@ class PermissionApplicator
return true;
}
// The array order here is very important due to the fact we walk up the chain
// in the flattening loop below. Earlier items in the chain have higher priority.
$typeIdList = [$entity->getMorphClass() . ':' . $entity->id];
// The chain order here is very important due to the fact we walk up the chain
// in the loop below. Earlier items in the chain have higher priority.
$chain = [$entity];
if ($entity instanceof Page && $entity->chapter_id) {
$typeIdList[] = 'chapter:' . $entity->chapter_id;
$chain[] = $entity->chapter;
}
if ($entity instanceof Page || $entity instanceof Chapter) {
$typeIdList[] = 'book:' . $entity->book_id;
$chain[] = $entity->book;
}
$relevantPermissions = EntityPermission::query()
->where(function (Builder $query) use ($typeIdList) {
foreach ($typeIdList as $typeId) {
$query->orWhere(function (Builder $query) use ($typeId) {
[$type, $id] = explode(':', $typeId);
$query->where('entity_type', '=', $type)
->where('entity_id', '=', $id);
});
}
})->where(function (Builder $query) use ($userRoleIds, $userId) {
$query->whereIn('role_id', $userRoleIds)
->orWhere('user_id', '=', $userId)
->orWhere(function (Builder $query) {
$query->whereNull(['role_id', 'user_id']);
});
})->get(['entity_id', 'entity_type', 'role_id', 'user_id', $action])
->all();
foreach ($chain as $currentEntity) {
$allowedByRoleId = $currentEntity->permissions()
->whereIn('role_id', [0, ...$userRoleIds])
->pluck($action, 'role_id');
$permissionMap = new EntityPermissionMap($relevantPermissions);
$permitsByType = ['user' => [], 'fallback' => [], 'role' => []];
// Collapse and simplify permission structure
foreach ($typeIdList as $typeId) {
$permissions = $permissionMap->getForEntity($typeId);
foreach ($permissions as $permission) {
$related = $permission->getAssignedType();
$relatedId = $permission->getAssignedTypeId();
if (!isset($permitsByType[$related][$relatedId])) {
$permitsByType[$related][$relatedId] = $permission->$action;
}
// Continue up the chain if no applicable entity permission overrides.
if ($allowedByRoleId->isEmpty()) {
continue;
}
}
// Return user-level permission if exists
if (count($permitsByType['user']) > 0) {
return boolval(array_values($permitsByType['user'])[0]);
}
// If we have user-role-specific permissions set, allow if any of those
// role permissions allow access.
$hasDefault = $allowedByRoleId->has(0);
if (!$hasDefault || $allowedByRoleId->count() > 1) {
return $allowedByRoleId->search(function (bool $allowed, int $roleId) {
return $roleId !== 0 && $allowed;
}) !== false;
}
// Return grant or reject from role-level if exists
if (count($permitsByType['role']) > 0) {
return boolval(max($permitsByType['role']));
}
// Return fallback permission if exists
if (count($permitsByType['fallback']) > 0) {
return boolval($permitsByType['fallback'][0]);
// Otherwise, return the default "Other roles" fallback value.
return $allowedByRoleId->get(0);
}
return null;
@@ -140,10 +113,7 @@ class PermissionApplicator
$permissionQuery = EntityPermission::query()
->where($action, '=', true)
->where(function (Builder $query) {
$query->whereIn('role_id', $this->getCurrentUserRoleIds())
->orWhere('user_id', '=', $this->currentUser()->id);
});
->whereIn('role_id', $this->getCurrentUserRoleIds());
if (!empty($entityClass)) {
/** @var Entity $entityInstance */
@@ -160,140 +130,18 @@ class PermissionApplicator
* Limit the given entity query so that the query will only
* return items that the user has view permission for.
*/
public function restrictEntityQuery(Builder $query, string $morphClass): Builder
public function restrictEntityQuery(Builder $query): Builder
{
$this->applyPermissionsToQuery($query, $query->getModel()->getTable(), $morphClass, 'id', '');
return $query;
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyPermissionsToQuery($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn): void
{
if ($this->currentUser()->hasSystemRole('admin')) {
return;
}
$this->applyFallbackJoin($query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
$this->applyRoleJoin($query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
$this->applyUserJoin($query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
$this->applyPermissionWhereFilter($query, $queryTable, $entityTypeLimiter, $entityTypeColumn);
}
/**
* Apply the where condition to a permission restricting query, to limit based upon the values of the joined
* permission data. Query must have joins pre-applied.
* Either entityTypeLimiter or entityTypeColumn should be supplied, with the other empty.
* Both should not be applied since that would conflict upon intent.
* @param Builder|QueryBuilder $query
*/
protected function applyPermissionWhereFilter($query, string $queryTable, string $entityTypeLimiter, string $entityTypeColumn)
{
$abilities = ['all' => [], 'own' => []];
$types = $entityTypeLimiter ? [$entityTypeLimiter] : ['page', 'chapter', 'bookshelf', 'book'];
$fullEntityTypeColumn = $queryTable . '.' . $entityTypeColumn;
foreach ($types as $type) {
$abilities['all'][$type] = userCan($type . '-view-all');
$abilities['own'][$type] = userCan($type . '-view-own');
}
$abilities['all'] = array_filter($abilities['all']);
$abilities['own'] = array_filter($abilities['own']);
$query->where(function (Builder $query) use ($abilities, $fullEntityTypeColumn, $entityTypeColumn) {
$query->where('perms_user', '=', 1)
->orWhere(function (Builder $query) {
$query->whereNull('perms_user')->where('perms_role', '=', 1);
})->orWhere(function (Builder $query) {
$query->whereNull(['perms_user', 'perms_role'])
->where('perms_fallback', '=', 1);
});
if (count($abilities['all']) > 0) {
$query->orWhere(function (Builder $query) use ($abilities, $fullEntityTypeColumn, $entityTypeColumn) {
$query->whereNull(['perms_user', 'perms_role', 'perms_fallback']);
if ($entityTypeColumn) {
$query->whereIn($fullEntityTypeColumn, array_keys($abilities['all']));
}
});
}
if (count($abilities['own']) > 0) {
$query->orWhere(function (Builder $query) use ($abilities, $fullEntityTypeColumn, $entityTypeColumn) {
$query->whereNull(['perms_user', 'perms_role', 'perms_fallback'])
->where('owned_by', '=', $this->currentUser()->id);
if ($entityTypeColumn) {
$query->whereIn($fullEntityTypeColumn, array_keys($abilities['all']));
}
});
}
return $query->where(function (Builder $parentQuery) {
$parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) {
$permissionQuery->whereIn('role_id', $this->getCurrentUserRoleIds())
->where(function (Builder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
});
});
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyPermissionJoin(callable $joinCallable, string $subAlias, $query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
{
$joinCondition = $this->getJoinCondition($queryTable, $subAlias, $entityIdColumn, $entityTypeColumn);
$query->joinSub(function (QueryBuilder $joinQuery) use ($joinCallable, $entityTypeLimiter) {
$joinQuery->select(['entity_id', 'entity_type'])->from('entity_permissions_collapsed')
->groupBy('entity_id', 'entity_type');
$joinCallable($joinQuery);
if ($entityTypeLimiter) {
$joinQuery->where('entity_type', '=', $entityTypeLimiter);
}
}, $subAlias, $joinCondition, null, null, 'left');
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyUserJoin($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
{
$this->applyPermissionJoin(function (QueryBuilder $joinQuery) {
$joinQuery->selectRaw('max(view) as perms_user')
->where('user_id', '=', $this->currentUser()->id);
}, 'p_u', $query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyRoleJoin($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
{
$this->applyPermissionJoin(function (QueryBuilder $joinQuery) {
$joinQuery->selectRaw('max(view) as perms_role')
->whereIn('role_id', $this->getCurrentUserRoleIds());
}, 'p_r', $query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyFallbackJoin($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
{
$this->applyPermissionJoin(function (QueryBuilder $joinQuery) {
$joinQuery->selectRaw('max(view) as perms_fallback')
->whereNull(['role_id', 'user_id']);
}, 'p_f', $query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
}
protected function getJoinCondition(string $queryTable, string $joinTableName, string $entityIdColumn, string $entityTypeColumn): callable
{
return function (JoinClause $join) use ($queryTable, $joinTableName, $entityIdColumn, $entityTypeColumn) {
$join->on($queryTable . '.' . $entityIdColumn, '=', $joinTableName . '.entity_id');
if ($entityTypeColumn) {
$join->on($queryTable . '.' . $entityTypeColumn, '=', $joinTableName . '.entity_type');
}
};
}
/**
* Extend the given page query to ensure draft items are not visible
* unless created by the given user.
@@ -318,23 +166,30 @@ class PermissionApplicator
*/
public function restrictEntityRelationQuery($query, string $tableName, string $entityIdColumn, string $entityTypeColumn)
{
$query->leftJoinSub(function (QueryBuilder $query) {
$query->select(['id as entity_id', DB::raw("'page' as entity_type"), 'owned_by', 'deleted_at', 'draft'])->from('pages');
$tablesByType = ['page' => 'pages', 'book' => 'books', 'chapter' => 'chapters', 'bookshelf' => 'bookshelves'];
foreach ($tablesByType as $type => $table) {
$query->unionAll(function (QueryBuilder $query) use ($type, $table) {
$query->select(['id as entity_id', DB::raw("'{$type}' as entity_type"), 'owned_by', 'deleted_at', DB::raw('0 as draft')])->from($table);
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
$pageMorphClass = (new Page())->getMorphClass();
$q = $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
/** @var Builder $permissionQuery */
$permissionQuery->select(['role_id'])->from('joint_permissions')
->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->whereColumn('joint_permissions.entity_type', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
->whereIn('joint_permissions.role_id', $this->getCurrentUserRoleIds())
->where(function (QueryBuilder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
})->where(function ($query) use ($tableDetails, $pageMorphClass) {
/** @var Builder $query */
$query->where($tableDetails['entityTypeColumn'], '!=', $pageMorphClass)
->orWhereExists(function (QueryBuilder $query) use ($tableDetails, $pageMorphClass) {
$query->select('id')->from('pages')
->whereColumn('pages.id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->where($tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'], '=', $pageMorphClass)
->where('pages.draft', '=', false);
});
}
}, 'entities', function (JoinClause $join) use ($tableName, $entityIdColumn, $entityTypeColumn) {
$join->on($tableName . '.' . $entityIdColumn, '=', 'entities.entity_id')
->on($tableName . '.' . $entityTypeColumn, '=', 'entities.entity_type');
});
$this->applyPermissionsToQuery($query, $tableName, '', $entityIdColumn, $entityTypeColumn);
// TODO - Test page draft access (Might allow drafts which should not be seen)
return $query;
return $q;
}
/**
@@ -345,12 +200,50 @@ class PermissionApplicator
*/
public function restrictPageRelationQuery(Builder $query, string $tableName, string $pageIdColumn): Builder
{
$fullPageIdColumn = $tableName . '.' . $pageIdColumn;
$morphClass = (new Page())->getMorphClass();
$this->applyPermissionsToQuery($query, $tableName, $morphClass, $pageIdColumn, '');
// TODO - Draft display
// TODO - Likely need owned_by entity join workaround as used above
return $query;
$existsQuery = function ($permissionQuery) use ($fullPageIdColumn, $morphClass) {
/** @var Builder $permissionQuery */
$permissionQuery->select('joint_permissions.role_id')->from('joint_permissions')
->whereColumn('joint_permissions.entity_id', '=', $fullPageIdColumn)
->where('joint_permissions.entity_type', '=', $morphClass)
->whereIn('joint_permissions.role_id', $this->getCurrentUserRoleIds())
->where(function (QueryBuilder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
};
$q = $query->where(function ($query) use ($existsQuery, $fullPageIdColumn) {
$query->whereExists($existsQuery)
->orWhere($fullPageIdColumn, '=', 0);
});
// Prevent visibility of non-owned draft pages
$q->whereExists(function (QueryBuilder $query) use ($fullPageIdColumn) {
$query->select('id')->from('pages')
->whereColumn('pages.id', '=', $fullPageIdColumn)
->where(function (QueryBuilder $query) {
$query->where('pages.draft', '=', false)
->orWhere('pages.owned_by', '=', $this->currentUser()->id);
});
});
return $q;
}
/**
* Add the query for checking the given user id has permission
* within the join_permissions table.
*
* @param QueryBuilder|Builder $query
*/
protected function addJointHasPermissionCheck($query, int $userIdToCheck)
{
$query->where('joint_permissions.has_permission', '=', true)->orWhere(function ($query) use ($userIdToCheck) {
$query->where('joint_permissions.has_permission_own', '=', true)
->where('joint_permissions.owned_by', '=', $userIdToCheck);
});
}
/**

View File

@@ -21,32 +21,19 @@ class PermissionFormData
{
return $this->entity->permissions()
->with('role')
->whereNotNull('role_id')
->where('role_id', '!=', 0)
->get()
->sortBy('role.display_name')
->all();
}
/**
* Get the permissions with assigned users.
*/
public function permissionsWithUsers(): array
{
return $this->entity->permissions()
->with('user')
->whereNotNull('user_id')
->get()
->sortBy('user.name')
->all();
}
/**
* Get the roles that don't yet have specific permissions for the
* entity we're managing permissions for.
*/
public function rolesNotAssigned(): array
{
$assigned = $this->entity->permissions()->whereNotNull('role_id')->pluck('role_id');
$assigned = $this->entity->permissions()->pluck('role_id');
return Role::query()
->where('system_name', '!=', 'admin')
->whereNotIn('id', $assigned)
@@ -62,19 +49,20 @@ class PermissionFormData
{
/** @var ?EntityPermission $permission */
$permission = $this->entity->permissions()
->whereNull(['role_id', 'user_id'])
->where('role_id', '=', 0)
->first();
return $permission ?? (new EntityPermission());
}
/**
* Check if the "Everyone else" option is inheriting default role system permissions.
* Is determined by any system entity_permission existing for the current entity.
* Get the "Everyone Else" role entry.
*/
public function everyoneElseInheriting(): bool
public function everyoneElseRole(): Role
{
return !$this->entity->permissions()
->whereNull(['role_id', 'user_id'])
->exists();
return (new Role())->forceFill([
'id' => 0,
'display_name' => trans('entities.permissions_role_everyone_else'),
'description' => trans('entities.permissions_role_everyone_else_desc'),
]);
}
}

View File

@@ -11,13 +11,13 @@ use Illuminate\Database\Eloquent\Collection;
class PermissionsRepo
{
protected CollapsedPermissionBuilder $permissionBuilder;
protected array $systemRoles = ['admin', 'public'];
protected JointPermissionBuilder $permissionBuilder;
protected $systemRoles = ['admin', 'public'];
/**
* PermissionsRepo constructor.
*/
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
public function __construct(JointPermissionBuilder $permissionBuilder)
{
$this->permissionBuilder = $permissionBuilder;
}
@@ -57,6 +57,7 @@ class PermissionsRepo
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
$this->assignRolePermissions($role, $permissions);
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_CREATE, $role);
@@ -87,6 +88,7 @@ class PermissionsRepo
$role->fill($roleData);
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
$role->save();
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_UPDATE, $role);
}
@@ -138,7 +140,7 @@ class PermissionsRepo
}
$role->entityPermissions()->delete();
$role->collapsedPermissions()->delete();
$role->jointPermissions()->delete();
Activity::add(ActivityType::ROLE_DELETE, $role);
$role->delete();
}

View File

@@ -6,6 +6,7 @@ class SimpleEntityData
{
public int $id;
public string $type;
public int $owned_by;
public ?int $book_id;
public ?int $chapter_id;
}

View File

@@ -2,8 +2,8 @@
namespace BookStack\Auth;
use BookStack\Auth\Permissions\CollapsedPermission;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Interfaces\Loggable;
use BookStack\Model;
@@ -39,6 +39,14 @@ class Role extends Model implements Loggable
return $this->belongsToMany(User::class)->orderBy('name', 'asc');
}
/**
* Get all related JointPermissions.
*/
public function jointPermissions(): HasMany
{
return $this->hasMany(JointPermission::class);
}
/**
* The RolePermissions that belong to the role.
*/
@@ -55,14 +63,6 @@ class Role extends Model implements Loggable
return $this->hasMany(EntityPermission::class);
}
/**
* Get all related entity collapsed permissions.
*/
public function collapsedPermissions(): HasMany
{
return $this->hasMany(CollapsedPermission::class);
}
/**
* Check if this role has a permission.
*/

View File

@@ -5,8 +5,6 @@ namespace BookStack\Auth;
use BookStack\Actions\Favourite;
use BookStack\Api\ApiToken;
use BookStack\Auth\Access\Mfa\MfaValue;
use BookStack\Auth\Permissions\CollapsedPermission;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Interfaces\Loggable;
use BookStack\Interfaces\Sluggable;
@@ -300,22 +298,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
}, 'activities', 'users.id', '=', 'activities.user_id');
}
/**
* Get the entity permissions assigned to this specific user.
*/
public function entityPermissions(): HasMany
{
return $this->hasMany(EntityPermission::class);
}
/**
* Get all related entity collapsed permissions.
*/
public function collapsedPermissions(): HasMany
{
return $this->hasMany(CollapsedPermission::class);
}
/**
* Get the url for editing this user.
*/

View File

@@ -153,8 +153,6 @@ class UserRepo
$user->apiTokens()->delete();
$user->favourites()->delete();
$user->mfaValues()->delete();
$user->collapsedPermissions()->delete();
$user->entityPermissions()->delete();
$user->delete();
// Delete user profile images

View File

@@ -2,7 +2,7 @@
namespace BookStack\Console\Commands;
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
@@ -22,12 +22,12 @@ class RegeneratePermissions extends Command
*/
protected $description = 'Regenerate all system permissions';
protected CollapsedPermissionBuilder $permissionBuilder;
protected JointPermissionBuilder $permissionBuilder;
/**
* Create a new command instance.
*/
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
public function __construct(JointPermissionBuilder $permissionBuilder)
{
$this->permissionBuilder = $permissionBuilder;
parent::__construct();

View File

@@ -7,9 +7,9 @@ use BookStack\Actions\Comment;
use BookStack\Actions\Favourite;
use BookStack\Actions\Tag;
use BookStack\Actions\View;
use BookStack\Auth\Permissions\CollapsedPermission;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Interfaces\Deletable;
@@ -69,7 +69,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function scopeVisible(Builder $query): Builder
{
return app()->make(PermissionApplicator::class)->restrictEntityQuery($query, $this->getMorphClass());
return app()->make(PermissionApplicator::class)->restrictEntityQuery($query);
}
/**
@@ -187,11 +187,11 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
}
/**
* Get the entity collapsed permissions this is connected to.
* Get the entity jointPermissions this is connected to.
*/
public function collapsedPermissions(): MorphMany
public function jointPermissions(): MorphMany
{
return $this->morphMany(CollapsedPermission::class, 'entity');
return $this->morphMany(JointPermission::class, 'entity');
}
/**
@@ -292,7 +292,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function rebuildPermissions()
{
app()->make(CollapsedPermissionBuilder::class)->rebuildForEntity(clone $this);
app()->make(JointPermissionBuilder::class)->rebuildForEntity(clone $this);
}
/**

View File

@@ -10,6 +10,7 @@ use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Entity;
use BookStack\Facades\Activity;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
class PermissionsUpdater
{
@@ -57,30 +58,13 @@ class PermissionsUpdater
protected function formatPermissionsFromRequestToEntityPermissions(array $permissions): array
{
$formatted = [];
$columnsByType = [
'role' => 'role_id',
'user' => 'user_id',
'fallback' => '',
];
foreach ($permissions as $type => $byId) {
$column = $columnsByType[$type] ?? null;
if (is_null($column)) {
continue;
}
foreach ($byId as $id => $info) {
$entityPermissionData = [];
if (!empty($column)) {
$entityPermissionData[$column] = $id;
}
foreach (EntityPermission::PERMISSIONS as $permission) {
$entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
}
$formatted[] = $entityPermissionData;
foreach ($permissions as $roleId => $info) {
$entityPermissionData = ['role_id' => $roleId];
foreach (EntityPermission::PERMISSIONS as $permission) {
$entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
}
$formatted[] = $entityPermissionData;
}
return $formatted;

View File

@@ -372,7 +372,7 @@ class TrashCan
$entity->permissions()->delete();
$entity->tags()->delete();
$entity->comments()->delete();
$entity->collapsedPermissions()->delete();
$entity->jointPermissions()->delete();
$entity->searchTerms()->delete();
$entity->deletions()->delete();
$entity->favourites()->delete();

View File

@@ -5,7 +5,6 @@ namespace BookStack\Http\Controllers;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\PermissionFormData;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
@@ -163,35 +162,10 @@ class PermissionsController extends Controller
{
$this->checkPermissionOr('restrictions-manage-all', fn() => userCan('restrictions-manage-own'));
/** @var Role $role */
$role = Role::query()->findOrFail($roleId);
return view('form.entity-permissions-row', [
'modelType' => 'role',
'modelId' => $role->id,
'modelName' => $role->display_name,
'modelDescription' => $role->description,
'permission' => new EntityPermission(),
'entityType' => $entityType,
'inheriting' => false,
]);
}
/**
* Get an empty entity permissions form row for the given user.
*/
public function formRowForUser(string $entityType, string $userId)
{
$this->checkPermissionOr('restrictions-manage-all', fn() => userCan('restrictions-manage-own'));
/** @var User $user */
$user = User::query()->findOrFail($userId);
return view('form.entity-permissions-row', [
'modelType' => 'user',
'modelId' => $user->id,
'modelName' => $user->name,
'modelDescription' => '',
'role' => $role,
'permission' => new EntityPermission(),
'entityType' => $entityType,
'inheriting' => false,

View File

@@ -223,7 +223,7 @@ class SearchRunner
});
$subQuery->groupBy('entity_type', 'entity_id');
$entityQuery->joinSub($subQuery, 's', 'id', '=', 's.entity_id');
$entityQuery->joinSub($subQuery, 's', 'id', '=', 'entity_id');
$entityQuery->addSelect('s.score');
$entityQuery->orderBy('score', 'desc');
}

View File

@@ -55,9 +55,8 @@ function hasAppAccess(): bool
}
/**
* Check if the current user has a permission.
* Checks a generic role permission or, if an ownable model is passed in, it will
* check against the given entity model, taking into account entity-level permissions.
* Check if the current user has a permission. If an ownable element
* is passed in the jointPermissions are checked against that particular item.
*/
function userCan(string $permission, Model $ownable = null): bool
{

View File

@@ -1,47 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class AddUserIdToEntityPermissions extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('entity_permissions', function (Blueprint $table) {
$table->unsignedInteger('role_id')->nullable()->default(null)->change();
$table->unsignedInteger('user_id')->nullable()->default(null)->index();
});
DB::table('entity_permissions')
->where('role_id', '=', 0)
->update(['role_id' => null]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
DB::table('entity_permissions')
->whereNull('role_id')
->update(['role_id' => 0]);
DB::table('entity_permissions')
->whereNotNull('user_id')
->delete();
Schema::table('entity_permissions', function (Blueprint $table) {
$table->unsignedInteger('role_id')->nullable(false)->change();
$table->dropColumn('user_id');
});
}
}

View File

@@ -1,40 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCollapsedRolePermissionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// TODO - Drop joint permissions
// TODO - Run collapsed table rebuild.
Schema::create('entity_permissions_collapsed', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('role_id')->nullable()->index();
$table->unsignedInteger('user_id')->nullable()->index();
$table->string('entity_type');
$table->unsignedInteger('entity_id');
$table->boolean('view')->index();
$table->index(['entity_type', 'entity_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('entity_permissions_collapsed');
}
}

View File

@@ -3,7 +3,7 @@
namespace Database\Seeders;
use BookStack\Api\ApiToken;
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Auth\Role;
use BookStack\Auth\User;
@@ -69,7 +69,7 @@ class DummyContentSeeder extends Seeder
]);
$token->save();
app(CollapsedPermissionBuilder::class)->rebuildForAll();
app(JointPermissionBuilder::class)->rebuildForAll();
app(SearchIndex::class)->indexAllEntities();
}
}

View File

@@ -2,7 +2,7 @@
namespace Database\Seeders;
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
@@ -35,7 +35,7 @@ class LargeContentSeeder extends Seeder
$largeBook->chapters()->saveMany($chapters);
$all = array_merge([$largeBook], array_values($pages->all()), array_values($chapters->all()));
app()->make(CollapsedPermissionBuilder::class)->rebuildForEntity($largeBook);
app()->make(JointPermissionBuilder::class)->rebuildForEntity($largeBook);
app()->make(SearchIndex::class)->indexEntities($all);
}
}

View File

@@ -1,421 +0,0 @@
# Permission Scenario Testing
Due to complexity that can arise in the various combinations of permissions, this document details scenarios and their expected results.
Test cases are written ability abstract, since all abilities should act the same in theory. Functional test cases may test abilities separate due to implementation differences.
Tests are categorised by the most specific element involved in the scenario, where the below list is most specific to least:
- User entity permissions.
- Role entity permissions.
- Fallback entity permissions.
- Role permissions.
- TODO - Test fallback in the context of the above.
## General Permission Logical Rules
The below are some general rules we follow to standardise the behaviour of permissions in the platform:
- Most specific permission application (as above) take priority and can deny less specific permissions.
- Parent user/role entity permissions that may be inherited, are considered to essentially be applied on the item they are inherited to unless a lower level has its own permission rule for an already specific role/user.
- Where both grant and deny exist at the same specificity, we side towards grant.
## Cases
### Content Role Permissions
These are tests related to item/entity permissions that are set only at a role level.
#### test_01_allow
- Role A has role all-page permission.
- User has Role A.
User granted page permission.
#### test_02_deny
- Role A has no page permission.
- User has Role A.
User denied page permission.
#### test_10_allow_on_own_with_own
- Role A has role own-page permission.
- User has Role A.
- User is owner of page.
User granted page permission.
#### test_11_deny_on_other_with_own
- Role A has role own-page permission.
- User has Role A.
- User is not owner of page.
User denied page permission.
#### test_20_multiple_role_conflicting_all
- Role A has role all-page permission.
- Role B has no page permission.
- User has Role A & B.
User granted page permission.
#### test_21_multiple_role_conflicting_own
- Role A has role own-page permission.
- Role B has no page permission.
- User has Role A & B.
- User is owner of page.
User granted page permission.
---
### Entity Role Permissions
These are tests related to entity-level role-specific permission overrides.
#### test_01_explicit_allow
- Page permissions have inherit disabled.
- Role A has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_02_explicit_deny
- Page permissions have inherit disabled.
- Role A has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_03_same_level_conflicting
- Page permissions have inherit disabled.
- Role A has entity allow page permission.
- Role B has entity deny page permission.
- User has both Role A & B.
User granted page permission.
Explicit grant overrides entity deny at same level.
#### test_20_inherit_allow
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity allow chapter permission.
- User has Role A.
User granted page permission.
#### test_21_inherit_deny
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity deny chapter permission.
- User has Role A.
User denied page permission.
#### test_22_same_level_conflict_inherit
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity deny chapter permission.
- Role B has entity allow chapter permission.
- User has both Role A & B.
User granted page permission.
#### test_30_child_inherit_override_allow
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity deny chapter permission.
- Role A has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_31_child_inherit_override_deny
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity allow chapter permission.
- Role A has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_40_multi_role_inherit_conflict_override_deny
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity deny page permission.
- Role B has entity allow chapter permission.
- User has Role A & B.
User granted page permission.
#### test_41_multi_role_inherit_conflict_retain_allow
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity allow page permission.
- Role B has entity deny chapter permission.
- User has Role A & B.
User granted page permission.
#### test_50_role_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- Role A has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_51_role_override_deny
- Page permissions have inherit enabled.
- Role A has no page-view-all role permission.
- Role A has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_60_inherited_role_override_allow
- Page permissions have inherit enabled.
- Chapter permissions have inherit enabled.
- Role A has no page role permission.
- Role A has entity allow chapter permission.
- User has Role A.
User granted page permission.
#### test_61_inherited_role_override_deny
- Page permissions have inherit enabled.
- Chapter permissions have inherit enabled.
- Role A has page role permission.
- Role A has entity denied chapter permission.
- User has Role A.
User denied page permission.
#### test_62_inherited_role_override_deny_on_own
- Page permissions have inherit enabled.
- Chapter permissions have inherit enabled.
- Role A has own-page role permission.
- Role A has entity denied chapter permission.
- User has Role A.
- User owns Page.
User denied page permission.
#### test_70_multi_role_inheriting_deny
- Page permissions have inherit enabled.
- Role A has all page role permission.
- Role B has entity denied page permission.
- User has Role A and B.
User denied page permission.
#### test_80_multi_role_inherited_deny_via_parent
- Page permissions have inherit enabled.
- Chapter permissions have inherit enabled.
- Role A has all-pages role permission.
- Role B has entity denied chapter permission.
- User has Role A & B.
User denied page permission.
---
### Entity User Permissions
These are tests related to entity-level user-specific permission overrides.
#### test_01_explicit_allow
- Page permissions have inherit disabled.
- User has entity allow page permission.
User granted page permission.
#### test_02_explicit_deny
- Page permissions have inherit disabled.
- User has entity deny page permission.
User denied page permission.
#### test_10_allow_inherit
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity allow chapter permission.
User granted page permission.
#### test_11_deny_inherit
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity deny chapter permission.
User denied page permission.
#### test_12_allow_inherit_override
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity deny chapter permission.
- User has entity allow page permission.
User granted page permission.
#### test_13_deny_inherit_override
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity allow chapter permission.
- User has entity deny page permission.
User denied page permission.
#### test_40_entity_role_override_allow
- Page permissions have inherit disabled.
- User has entity allow page permission.
- Role A has entity deny page permission.
- User has role A.
User granted page permission.
#### test_41_entity_role_override_deny
- Page permissions have inherit disabled.
- User has entity deny page permission.
- Role A has entity allow page permission.
- User has role A.
User denied page permission.
#### test_42_entity_role_override_allow_via_inherit
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity allow chapter permission.
- Role A has entity deny page permission.
- User has role A.
User granted page permission.
#### test_43_entity_role_override_deny_via_inherit
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity deny chapter permission.
- Role A has entity allow page permission.
- User has role A.
User denied page permission.
#### test_50_role_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- User has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_51_role_override_deny
- Page permissions have inherit enabled.
- Role A has all-page role permission.
- User has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_60_inherited_role_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- User has entity allow chapter permission.
- User has Role A.
User granted page permission.
#### test_61_inherited_role_override_deny
- Page permissions have inherit enabled.
- Role A has view-all page role permission.
- User has entity deny chapter permission.
- User has Role A.
User denied page permission.
#### test_61_inherited_role_override_deny_on_own
- Page permissions have inherit enabled.
- Role A has view-own page role permission.
- User has entity deny chapter permission.
- User has Role A.
- User owns Page.
User denied page permission.
#### test_70_all_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- Role A has entity deny page permission.
- User has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_71_all_override_deny
- Page permissions have inherit enabled.
- Role A has page-all role permission.
- Role A has entity allow page permission.
- User has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_80_inherited_all_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- Role A has entity deny chapter permission.
- User has entity allow chapter permission.
- User has Role A.
User granted page permission.
#### test_81_inherited_all_override_deny
- Page permissions have inherit enabled.
- Role A has view-all page role permission.
- Role A has entity allow chapter permission.
- User has entity deny chapter permission.
- User has Role A.
User denied page permission.

62
public/dist/app.js vendored Normal file

File diff suppressed because one or more lines are too long

32
public/dist/code.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/dist/export-styles.css vendored Normal file

File diff suppressed because one or more lines are too long

1
public/dist/print-styles.css vendored Normal file
View File

@@ -0,0 +1 @@
:root{--color-primary: #206ea7;--color-primary-light: rgba(32,110,167,0.15);--color-page: #206ea7;--color-page-draft: #7e50b1;--color-chapter: #af4d0d;--color-book: #077b70;--color-bookshelf: #a94747;--bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(0, 0, 0,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E")}:root.dark-mode{--bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(255, 255, 255,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E");color-scheme:only dark}:root:not(.dark-mode){color-scheme:only light}header{display:none}html,body{font-size:12px;background-color:#fff}.page-content{margin:0 auto}.print-hidden{display:none !important}.tri-layout-container{grid-template-columns:1fr;grid-template-areas:"b";margin-inline-start:0;margin-inline-end:0;display:block}.card{box-shadow:none}.content-wrap.card{padding-inline-start:0;padding-inline-end:0}/*# sourceMappingURL=print-styles.css.map */

1
public/dist/styles.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -10,8 +10,6 @@ export class EntityPermissions extends Component {
this.everyoneInheritToggle = this.$refs.everyoneInherit;
this.roleSelect = this.$refs.roleSelect;
this.roleContainer = this.$refs.roleContainer;
this.userContainer = this.$refs.userContainer;
this.userSelectContainer = this.$refs.userSelectContainer;
this.setupListeners();
}
@@ -20,7 +18,7 @@ export class EntityPermissions extends Component {
// "Everyone Else" inherit toggle
this.everyoneInheritToggle.addEventListener('change', event => {
const inherit = event.target.checked;
const permissions = document.querySelectorAll('input[name^="permissions[fallback]"]');
const permissions = document.querySelectorAll('input[name^="permissions[0]["]');
for (const permission of permissions) {
permission.disabled = inherit;
permission.checked = false;
@@ -30,7 +28,7 @@ export class EntityPermissions extends Component {
// Remove role row button click
this.container.addEventListener('click', event => {
const button = event.target.closest('button');
if (button && button.dataset.modelType) {
if (button && button.dataset.roleId) {
this.removeRowOnButtonClick(button)
}
});
@@ -42,14 +40,6 @@ export class EntityPermissions extends Component {
this.addRoleRow(roleId);
}
});
// User select change
this.userSelectContainer.querySelector('input[name="user_select"]').addEventListener('change', event => {
const userId = event.target.value;
if (userId) {
this.addUserRow(userId);
}
});
}
async addRoleRow(roleId) {
@@ -62,50 +52,23 @@ export class EntityPermissions extends Component {
}
// Get and insert new row
const resp = await window.$http.get(`/permissions/role-form-row/${this.entityType}/${roleId}`);
const resp = await window.$http.get(`/permissions/form-row/${this.entityType}/${roleId}`);
const row = htmlToDom(resp.data);
this.roleContainer.append(row);
this.roleSelect.disabled = false;
}
async addUserRow(userId) {
const exists = this.userContainer.querySelector(`[name^="permissions[user][${userId}]"]`) !== null;
if (exists) {
return;
}
const toggle = this.userSelectContainer.querySelector('.dropdown-search-toggle-select');
toggle.classList.add('disabled');
this.userContainer.style.pointerEvents = 'none';
// Get and insert new row
const resp = await window.$http.get(`/permissions/user-form-row/${this.entityType}/${userId}`);
const row = htmlToDom(resp.data);
this.userContainer.append(row);
toggle.classList.remove('disabled');
this.userContainer.style.pointerEvents = null;
/** @var {UserSelect} **/
const userSelect = window.$components.firstOnElement(this.userSelectContainer.querySelector('.dropdown-search'), 'user-select');
userSelect.reset();
}
removeRowOnButtonClick(button) {
const row = button.closest('.item-list-row');
const modelId = button.dataset.modelId;
const modelName = button.dataset.modelName;
const modelType = button.dataset.modelType;
const roleId = button.dataset.roleId;
const roleName = button.dataset.roleName;
const option = document.createElement('option');
option.value = modelId;
option.textContent = modelName;
if (modelType === 'role') {
this.roleSelect.append(option);
}
option.value = roleId;
option.textContent = roleName;
this.roleSelect.append(option);
row.remove();
}

View File

@@ -8,9 +8,6 @@ export class UserSelect extends Component {
this.input = this.$refs.input;
this.userInfoContainer = this.$refs.userInfo;
this.initialValue = this.input.value;
this.initialContent = this.userInfoContainer.innerHTML;
onChildEvent(this.container, 'a.dropdown-search-item', 'click', this.selectUser.bind(this));
}
@@ -22,13 +19,6 @@ export class UserSelect extends Component {
this.hide();
}
reset() {
this.input.value = this.initialValue;
this.userInfoContainer.innerHTML = this.initialContent;
this.input.dispatchEvent(new Event('change', {bubbles: true}));
this.hide();
}
hide() {
/** @var {Dropdown} **/
const dropdown = window.$components.firstOnElement(this.container, 'dropdown');

View File

@@ -18,7 +18,6 @@ return [
'name' => 'Name',
'description' => 'Description',
'role' => 'Role',
'user' => 'User',
'cover_image' => 'Cover image',
'cover_image_description' => 'This image should be approx 440x250px.',

View File

@@ -50,7 +50,6 @@ return [
'permissions_role_everyone_else' => 'Everyone Else',
'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.',
'permissions_role_override' => 'Override permissions for role',
'permissions_user_override' => 'Override permissions for user',
'permissions_inherit_defaults' => 'Inherit defaults',
// Search

View File

@@ -1,8 +1,5 @@
{{--
$modelType - The type of permission model; String matching one of: user, role, fallback
$modelId - The ID of the permission model.
$modelName - The name of the permission model.
$modelDescription - The description of the permission model.
$role - The Role to display this row for.
$entityType - String identifier for type of entity having permissions applied.
$permission - The entity permission containing the permissions.
$inheriting - Boolean if the current row should be marked as inheriting default permissions. Used for "Everyone Else" role.
@@ -10,21 +7,21 @@ $inheriting - Boolean if the current row should be marked as inheriting default
<div component="permissions-table" class="item-list-row flex-container-row justify-space-between wrap">
<div class="gap-x-m flex-container-row items-center px-l py-m flex">
<div class="text-large" title="{{ $modelType === 'fallback' ? trans('entities.permissions_role_everyone_else') : ($modelType === 'role' ? trans('common.role') : trans('common.user')) }}">
@icon($modelType === 'fallback' ? 'groups' : ($modelType === 'role' ? 'role' : 'user'))
<div class="text-large" title="{{ $role->id === 0 ? trans('entities.permissions_role_everyone_else') : trans('common.role') }}">
@icon($role->id === 0 ? 'groups' : 'role')
</div>
<span>
<strong>{{ $modelName }}</strong> <br>
<small class="text-muted">{{ $modelDescription }}</small>
<strong>{{ $role->display_name }}</strong> <br>
<small class="text-muted">{{ $role->description }}</small>
</span>
@if($modelType !== 'fallback')
@if($role->id !== 0)
<button type="button"
class="ml-auto flex-none text-small text-primary text-button hover-underline item-list-row-toggle-all hide-under-s"
refs="permissions-table@toggle-all"
><strong>{{ trans('common.toggle_all') }}</strong></button>
@endif
</div>
@if($modelType === 'fallback')
@if($role->id === 0)
<div class="px-l flex-container-row items-center" refs="entity-permissions@everyone-inherit">
@include('form.custom-checkbox', [
'name' => 'entity-permissions-inherit',
@@ -35,12 +32,12 @@ $inheriting - Boolean if the current row should be marked as inheriting default
</div>
@endif
<div class="flex-container-row justify-space-between gap-x-xl wrap items-center">
<input type="hidden" name="permissions[{{ $modelType }}][{{ $modelId }}][active]"
<input type="hidden" name="permissions[{{ $role->id }}][active]"
@if($inheriting) disabled="disabled" @endif
value="true">
<div class="px-l">
@include('form.custom-checkbox', [
'name' => 'permissions[' . $modelType . '][' . $modelId . '][view]',
'name' => 'permissions[' . $role->id . '][view]',
'label' => trans('common.view'),
'value' => 'true',
'checked' => $permission->view,
@@ -50,7 +47,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
@if($entityType !== 'page')
<div class="px-l">
@include('form.custom-checkbox', [
'name' => 'permissions[' . $modelType . '][' . $modelId . '][create]',
'name' => 'permissions[' . $role->id . '][create]',
'label' => trans('common.create'),
'value' => 'true',
'checked' => $permission->create,
@@ -60,7 +57,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
@endif
<div class="px-l">
@include('form.custom-checkbox', [
'name' => 'permissions[' . $modelType . '][' . $modelId . '][update]',
'name' => 'permissions[' . $role->id . '][update]',
'label' => trans('common.update'),
'value' => 'true',
'checked' => $permission->update,
@@ -69,7 +66,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
</div>
<div class="px-l">
@include('form.custom-checkbox', [
'name' => 'permissions[' . $modelType . '][' . $modelId . '][delete]',
'name' => 'permissions[' . $role->id . '][delete]',
'label' => trans('common.delete'),
'value' => 'true',
'checked' => $permission->delete,
@@ -77,13 +74,12 @@ $inheriting - Boolean if the current row should be marked as inheriting default
])
</div>
</div>
@if($modelType !== 'fallback')
@if($role->id !== 0)
<div class="flex-container-row items-center px-m py-s">
<button type="button"
class="text-neg p-m icon-button"
data-model-type="{{ $modelType }}"
data-model-id="{{ $modelId }}"
data-model-name="{{ $modelName }}"
data-role-id="{{ $role->id }}"
data-role-name="{{ $role->display_name }}"
title="{{ trans('common.remove') }}">
@icon('close') <span class="hide-over-m ml-xs">{{ trans('common.remove') }}</span>
</button>

View File

@@ -35,35 +35,11 @@
<hr>
<div refs="entity-permissions@user-container" class="item-list mt-m mb-m">
@foreach($data->permissionsWithUsers() as $permission)
@include('form.entity-permissions-row', [
'permission' => $permission,
'modelType' => 'user',
'modelId' => $permission->user->id,
'modelName' => $permission->user->name,
'modelDescription' => '',
'entityType' => $model->getType(),
'inheriting' => false,
])
@endforeach
</div>
<div class="flex-container-row justify-flex-end mb-xl">
<div refs="entity-permissions@user-select-container" class="flex-container-row items-center gap-m">
<label for="user_select" class="m-none p-none"><span class="bold">{{ trans('entities.permissions_user_override') }}</span></label>
@include('form.user-select', ['name' => 'user_select', 'user' => null])
</div>
</div>
<div refs="entity-permissions@role-container" class="item-list mt-m mb-m">
@foreach($data->permissionsWithRoles() as $permission)
@include('form.entity-permissions-row', [
'permission' => $permission,
'modelType' => 'role',
'modelId' => $permission->role->id,
'modelName' => $permission->role->display_name,
'modelDescription' => $permission->role->description,
'role' => $permission->role,
'entityType' => $model->getType(),
'inheriting' => false,
])
@@ -84,13 +60,10 @@
<div class="item-list mt-m mb-xl">
@include('form.entity-permissions-row', [
'modelType' => 'fallback',
'modelId' => 0,
'modelName' => trans('entities.permissions_role_everyone_else'),
'modelDescription' => trans('entities.permissions_role_everyone_else_desc'),
'role' => $data->everyoneElseRole(),
'permission' => $data->everyoneElseEntityPermission(),
'entityType' => $model->getType(),
'inheriting' => $data->everyoneElseInheriting(),
'inheriting' => !$model->permissions()->where('role_id', '=', 0)->exists(),
])
</div>

View File

@@ -217,8 +217,7 @@ Route::middleware('auth')->group(function () {
Route::get('/home', [HomeController::class, 'index']);
// Permissions
Route::get('/permissions/role-form-row/{entityType}/{roleId}', [PermissionsController::class, 'formRowForRole']);
Route::get('/permissions/user-form-row/{entityType}/{userId}', [PermissionsController::class, 'formRowForUser']);
Route::get('/permissions/form-row/{entityType}/{roleId}', [PermissionsController::class, 'formRowForRole']);
// Maintenance
Route::get('/settings/maintenance', [MaintenanceController::class, 'index']);

View File

@@ -23,17 +23,17 @@ class AuditLogTest extends TestCase
public function test_only_accessible_with_right_permissions()
{
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$this->actingAs($viewer);
$resp = $this->get('/settings/audit');
$this->assertPermissionError($resp);
$this->permissions->grantUserRolePermissions($viewer, ['settings-manage']);
$this->giveUserPermissions($viewer, ['settings-manage']);
$resp = $this->get('/settings/audit');
$this->assertPermissionError($resp);
$this->permissions->grantUserRolePermissions($viewer, ['users-manage']);
$this->giveUserPermissions($viewer, ['users-manage']);
$resp = $this->get('/settings/audit');
$resp->assertStatus(200);
$resp->assertSeeText('Audit Log');
@@ -41,7 +41,7 @@ class AuditLogTest extends TestCase
public function test_shows_activity()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
$this->actingAs($admin);
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -56,7 +56,7 @@ class AuditLogTest extends TestCase
public function test_shows_name_for_deleted_items()
{
$this->actingAs($this->users->admin());
$this->actingAs($this->getAdmin());
$page = $this->entities->page();
$pageName = $page->name;
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -71,12 +71,12 @@ class AuditLogTest extends TestCase
public function test_shows_activity_for_deleted_users()
{
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$this->actingAs($viewer);
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
$this->actingAs($this->users->admin());
$this->actingAs($this->getAdmin());
app(UserRepo::class)->destroy($viewer);
$resp = $this->get('settings/audit');
@@ -85,7 +85,7 @@ class AuditLogTest extends TestCase
public function test_filters_by_key()
{
$this->actingAs($this->users->admin());
$this->actingAs($this->getAdmin());
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -98,7 +98,7 @@ class AuditLogTest extends TestCase
public function test_date_filters()
{
$this->actingAs($this->users->admin());
$this->actingAs($this->getAdmin());
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -120,8 +120,8 @@ class AuditLogTest extends TestCase
public function test_user_filter()
{
$admin = $this->users->admin();
$editor = $this->users->editor();
$admin = $this->getAdmin();
$editor = $this->getEditor();
$this->actingAs($admin);
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -142,7 +142,7 @@ class AuditLogTest extends TestCase
public function test_ip_address_logged_and_visible()
{
config()->set('app.proxies', '*');
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
@@ -166,7 +166,7 @@ class AuditLogTest extends TestCase
public function test_ip_address_is_searchable()
{
config()->set('app.proxies', '*');
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
@@ -192,7 +192,7 @@ class AuditLogTest extends TestCase
{
config()->set('app.proxies', '*');
config()->set('app.env', 'demo');
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
@@ -215,7 +215,7 @@ class AuditLogTest extends TestCase
{
config()->set('app.proxies', '*');
config()->set('app.ip_address_precision', 2);
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [

View File

@@ -88,7 +88,7 @@ class WebhookCallTest extends TestCase
]);
$webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
$page = $this->entities->page();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->runEvent(ActivityType::PAGE_UPDATE, $page, $editor);
@@ -111,7 +111,7 @@ class WebhookCallTest extends TestCase
protected function runEvent(string $event, $detail = '', ?User $user = null)
{
if (is_null($user)) {
$user = $this->users->editor();
$user = $this->getEditor();
}
$this->actingAs($user);

View File

@@ -41,7 +41,7 @@ class WebhookFormatTesting extends TestCase
protected function getWebhookData(string $event, $detail): array
{
$webhook = Webhook::factory()->make();
$user = $this->users->editor();
$user = $this->getEditor();
$formatter = WebhookFormatter::getDefault($event, $webhook, $detail, $user, time());
return $formatter->format();

View File

@@ -135,7 +135,7 @@ class WebhookManagementTest extends TestCase
public function test_settings_manage_permission_required_for_webhook_routes()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor);
$routes = [
@@ -153,7 +153,7 @@ class WebhookManagementTest extends TestCase
$this->assertPermissionError($resp);
}
$this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
$this->giveUserPermissions($editor, ['settings-manage']);
foreach ($routes as [$method, $endpoint]) {
$resp = $this->call($method, $endpoint);

View File

@@ -16,8 +16,8 @@ class ApiAuthTest extends TestCase
public function test_requests_succeed_with_default_auth()
{
$viewer = $this->users->viewer();
$this->permissions->grantUserRolePermissions($viewer, ['access-api']);
$viewer = $this->getViewer();
$this->giveUserPermissions($viewer, ['access-api']);
$resp = $this->get($this->endpoint);
$resp->assertStatus(401);
@@ -63,7 +63,7 @@ class ApiAuthTest extends TestCase
auth()->logout();
$accessApiPermission = RolePermission::getByName('access-api');
$editorRole = $this->users->editor()->roles()->first();
$editorRole = $this->getEditor()->roles()->first();
$editorRole->detachPermission($accessApiPermission);
$resp = $this->get($this->endpoint, $this->apiAuthHeader());
@@ -73,7 +73,7 @@ class ApiAuthTest extends TestCase
public function test_api_access_permission_required_to_access_api_with_session_auth()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor, 'standard');
$resp = $this->get($this->endpoint);
@@ -81,7 +81,7 @@ class ApiAuthTest extends TestCase
auth('standard')->logout();
$accessApiPermission = RolePermission::getByName('access-api');
$editorRole = $this->users->editor()->roles()->first();
$editorRole = $this->getEditor()->roles()->first();
$editorRole->detachPermission($accessApiPermission);
$editor = User::query()->where('id', '=', $editor->id)->first();
@@ -114,7 +114,7 @@ class ApiAuthTest extends TestCase
public function test_token_expiry_checked()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$token = $editor->apiTokens()->first();
$resp = $this->get($this->endpoint, $this->apiAuthHeader());
@@ -130,7 +130,7 @@ class ApiAuthTest extends TestCase
public function test_email_confirmation_checked_using_api_auth()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$editor->email_confirmed = false;
$editor->save();

View File

@@ -50,7 +50,7 @@ class AttachmentsApiTest extends TestCase
],
]]);
$this->permissions->setEntityPermissions($page, [], []);
$this->entities->setPermissions($page, [], []);
$resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
$resp->assertJsonMissing(['data' => [
@@ -246,13 +246,13 @@ class AttachmentsApiTest extends TestCase
public function test_attachment_not_visible_on_other_users_draft()
{
$this->actingAsApiAdmin();
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$page->draft = true;
$page->owned_by = $editor->id;
$page->save();
$this->permissions->regenerateForEntity($page);
$this->entities->regenPermissions($page);
$attachment = $this->createAttachmentForPage($page, [
'name' => 'my attachment',
@@ -342,7 +342,7 @@ class AttachmentsApiTest extends TestCase
protected function createAttachmentForPage(Page $page, $attributes = []): Attachment
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
/** @var Attachment $attachment */
$attachment = $page->attachments()->forceCreate(array_merge([
'uploaded_to' => $page->id,

View File

@@ -246,7 +246,7 @@ class BooksApiTest extends TestCase
{
$types = ['html', 'plaintext', 'pdf', 'markdown'];
$this->actingAsApiEditor();
$this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
$this->removePermissionFromUser($this->getEditor(), 'content-export');
$book = $this->entities->book();
foreach ($types as $type) {

View File

@@ -221,7 +221,7 @@ class ChaptersApiTest extends TestCase
{
$types = ['html', 'plaintext', 'pdf', 'markdown'];
$this->actingAsApiEditor();
$this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
$this->removePermissionFromUser($this->getEditor(), 'content-export');
$chapter = Chapter::visible()->has('pages')->first();
foreach ($types as $type) {

View File

@@ -209,7 +209,7 @@ class PagesApiTest extends TestCase
$this->actingAsApiEditor();
$page = $this->entities->page();
$chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
$this->permissions->setEntityPermissions($chapter, ['view'], [$this->users->editor()->roles()->first()]);
$this->entities->setPermissions($chapter, ['view'], [$this->getEditor()->roles()->first()]);
$details = [
'name' => 'My updated API page',
'chapter_id' => $chapter->id,
@@ -315,7 +315,7 @@ class PagesApiTest extends TestCase
{
$types = ['html', 'plaintext', 'pdf', 'markdown'];
$this->actingAsApiEditor();
$this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
$this->removePermissionFromUser($this->getEditor(), 'content-export');
$page = $this->entities->page();
foreach ($types as $type) {

View File

@@ -21,8 +21,8 @@ class RecycleBinApiTest extends TestCase
public function test_settings_manage_permission_needed_for_all_endpoints()
{
$editor = $this->users->editor();
$this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
$editor = $this->getEditor();
$this->giveUserPermissions($editor, ['settings-manage']);
$this->actingAs($editor);
foreach ($this->endpointMap as [$method, $uri]) {
@@ -34,8 +34,8 @@ class RecycleBinApiTest extends TestCase
public function test_restrictions_manage_all_permission_needed_for_all_endpoints()
{
$editor = $this->users->editor();
$this->permissions->grantUserRolePermissions($editor, ['restrictions-manage-all']);
$editor = $this->getEditor();
$this->giveUserPermissions($editor, ['restrictions-manage-all']);
$this->actingAs($editor);
foreach ($this->endpointMap as [$method, $uri]) {
@@ -47,7 +47,7 @@ class RecycleBinApiTest extends TestCase
public function test_index_endpoint_returns_expected_page()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
$page = $this->entities->page();
$book = $this->entities->book();
@@ -82,7 +82,7 @@ class RecycleBinApiTest extends TestCase
public function test_index_endpoint_returns_children_count()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
$book = Book::query()->whereHas('pages')->whereHas('chapters')->withCount(['pages', 'chapters'])->first();
$this->actingAs($admin)->delete($book->getUrl());
@@ -109,7 +109,7 @@ class RecycleBinApiTest extends TestCase
public function test_index_endpoint_returns_parent()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
$page = $this->entities->pageWithinChapter();
$this->actingAs($admin)->delete($page->getUrl());

View File

@@ -12,7 +12,7 @@ trait TestsApi
*/
protected function actingAsApiEditor()
{
$this->actingAs($this->users->editor(), 'api');
$this->actingAs($this->getEditor(), 'api');
return $this;
}
@@ -22,7 +22,7 @@ trait TestsApi
*/
protected function actingAsApiAdmin()
{
$this->actingAs($this->users->admin(), 'api');
$this->actingAs($this->getAdmin(), 'api');
return $this;
}

View File

@@ -175,7 +175,7 @@ class UsersApiTest extends TestCase
{
$this->actingAsApiAdmin();
/** @var User $user */
$user = $this->users->admin();
$user = $this->getAdmin();
$roles = Role::query()->pluck('id');
$resp = $this->putJson($this->baseEndpoint . "/{$user->id}", [
'name' => 'My updated user',
@@ -204,7 +204,7 @@ class UsersApiTest extends TestCase
{
$this->actingAsApiAdmin();
/** @var User $user */
$user = $this->users->admin();
$user = $this->getAdmin();
$roleCount = $user->roles()->count();
$resp = $this->putJson($this->baseEndpoint . "/{$user->id}", []);
@@ -222,7 +222,7 @@ class UsersApiTest extends TestCase
{
$this->actingAsApiAdmin();
/** @var User $user */
$user = User::query()->where('id', '!=', $this->users->admin()->id)
$user = User::query()->where('id', '!=', $this->getAdmin()->id)
->whereNull('system_name')
->first();
@@ -236,7 +236,7 @@ class UsersApiTest extends TestCase
{
$this->actingAsApiAdmin();
/** @var User $user */
$user = User::query()->where('id', '!=', $this->users->admin()->id)
$user = User::query()->where('id', '!=', $this->getAdmin()->id)
->whereNull('system_name')
->first();
$entityChain = $this->entities->createChainBelongingToUser($user);

View File

@@ -44,7 +44,7 @@ class AuthTest extends TestCase
public function test_mfa_session_cleared_on_logout()
{
$user = $this->users->editor();
$user = $this->getEditor();
$mfaSession = $this->app->make(MfaSession::class);
$mfaSession->markVerifiedForUser($user);
@@ -94,7 +94,7 @@ class AuthTest extends TestCase
public function test_login_authenticates_nonadmins_on_default_guard_only()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$editor->password = bcrypt('password');
$editor->save();
@@ -120,7 +120,7 @@ class AuthTest extends TestCase
public function test_logged_in_user_with_unconfirmed_email_is_logged_out()
{
$this->setSettings(['registration-confirmation' => 'true']);
$user = $this->users->editor();
$user = $this->getEditor();
$user->email_confirmed = false;
$user->save();

View File

@@ -11,7 +11,7 @@ class GroupSyncServiceTest extends TestCase
{
public function test_user_is_assigned_to_matching_roles()
{
$user = $this->users->viewer();
$user = $this->getViewer();
$roleA = Role::factory()->create(['display_name' => 'Wizards']);
$roleB = Role::factory()->create(['display_name' => 'Gremlins']);
@@ -33,7 +33,7 @@ class GroupSyncServiceTest extends TestCase
public function test_multiple_values_in_role_external_auth_id_handled()
{
$user = $this->users->viewer();
$user = $this->getViewer();
$role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales, engineering, developers, marketers']);
$this->assertFalse($user->hasRole($role->id));
@@ -45,7 +45,7 @@ class GroupSyncServiceTest extends TestCase
public function test_commas_can_be_used_in_external_auth_id_if_escaped()
{
$user = $this->users->viewer();
$user = $this->getViewer();
$role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales\,-developers, marketers']);
$this->assertFalse($user->hasRole($role->id));
@@ -57,7 +57,7 @@ class GroupSyncServiceTest extends TestCase
public function test_external_auth_id_matches_ignoring_case()
{
$user = $this->users->viewer();
$user = $this->getViewer();
$role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'WaRRioRs']);
$this->assertFalse($user->hasRole($role->id));

View File

@@ -235,7 +235,7 @@ class LdapTest extends TestCase
public function test_user_edit_form()
{
$editUser = $this->users->viewer();
$editUser = $this->getNormalUser();
$editPage = $this->asAdmin()->get("/settings/users/{$editUser->id}");
$editPage->assertSee('Edit User');
$editPage->assertDontSee('Password');
@@ -257,7 +257,7 @@ class LdapTest extends TestCase
public function test_non_admins_cannot_change_auth_id()
{
$testUser = $this->users->viewer();
$testUser = $this->getNormalUser();
$this->actingAs($testUser)
->get('/settings/users/' . $testUser->id)
->assertDontSee('External Authentication');

View File

@@ -70,7 +70,7 @@ class LoginAutoInitiateTest extends TestCase
config()->set([
'auth.method' => 'oidc',
]);
$this->actingAs($this->users->editor());
$this->actingAs($this->getEditor());
$req = $this->post('/logout');
$req->assertRedirect('/login?prevent_auto_init=true');

View File

@@ -13,7 +13,7 @@ class MfaConfigurationTest extends TestCase
{
public function test_totp_setup()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->assertDatabaseMissing('mfa_values', ['user_id' => $editor->id]);
// Setup page state
@@ -66,7 +66,7 @@ class MfaConfigurationTest extends TestCase
public function test_backup_codes_setup()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->assertDatabaseMissing('mfa_values', ['user_id' => $editor->id]);
// Setup page state
@@ -112,8 +112,8 @@ class MfaConfigurationTest extends TestCase
public function test_mfa_method_count_is_visible_on_user_edit_page()
{
$user = $this->users->editor();
$resp = $this->actingAs($this->users->admin())->get($user->getEditUrl());
$user = $this->getEditor();
$resp = $this->actingAs($this->getAdmin())->get($user->getEditUrl());
$resp->assertSee('0 methods configured');
MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, 'test');
@@ -127,17 +127,17 @@ class MfaConfigurationTest extends TestCase
public function test_mfa_setup_link_only_shown_when_viewing_own_user_edit_page()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
$resp = $this->actingAs($admin)->get($admin->getEditUrl());
$this->withHtml($resp)->assertElementExists('a[href$="/mfa/setup"]');
$resp = $this->actingAs($admin)->get($this->users->editor()->getEditUrl());
$resp = $this->actingAs($admin)->get($this->getEditor()->getEditUrl());
$this->withHtml($resp)->assertElementNotExists('a[href$="/mfa/setup"]');
}
public function test_mfa_indicator_shows_in_user_list()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
User::query()->where('id', '!=', $admin->id)->delete();
$resp = $this->actingAs($admin)->get('/settings/users');
@@ -150,7 +150,7 @@ class MfaConfigurationTest extends TestCase
public function test_remove_mfa_method()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
MfaValue::upsertWithValue($admin, MfaValue::METHOD_TOTP, 'test');
$this->assertEquals(1, $admin->mfaValues()->count());
@@ -168,7 +168,7 @@ class MfaConfigurationTest extends TestCase
public function test_totp_setup_url_shows_correct_user_when_setup_forced_upon_login()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
/** @var Role $role */
$role = $admin->roles()->first();
$role->mfa_enforced = true;

View File

@@ -140,7 +140,7 @@ class MfaVerificationTest extends TestCase
public function test_both_mfa_options_available_if_set_on_profile()
{
$user = $this->users->editor();
$user = $this->getEditor();
$user->password = Hash::make('password');
$user->save();
@@ -165,7 +165,7 @@ class MfaVerificationTest extends TestCase
public function test_mfa_required_with_no_methods_leads_to_setup()
{
$user = $this->users->editor();
$user = $this->getEditor();
$user->password = Hash::make('password');
$user->save();
/** @var Role $role */
@@ -222,7 +222,7 @@ class MfaVerificationTest extends TestCase
// Attempted login user, who has configured mfa, access
// Sets up user that has MFA required after attempted login.
$loginService = $this->app->make(LoginService::class);
$user = $this->users->editor();
$user = $this->getEditor();
/** @var Role $role */
$role = $user->roles->first();
$role->mfa_enforced = true;
@@ -257,7 +257,7 @@ class MfaVerificationTest extends TestCase
protected function startTotpLogin(): array
{
$secret = $this->app->make(TotpService::class)->generateSecret();
$user = $this->users->editor();
$user = $this->getEditor();
$user->password = Hash::make('password');
$user->save();
MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, $secret);
@@ -274,7 +274,7 @@ class MfaVerificationTest extends TestCase
*/
protected function startBackupCodeLogin($codes = ['kzzu6-1pgll', 'bzxnf-plygd', 'bwdsp-ysl51', '1vo93-ioy7n', 'lf7nw-wdyka', 'xmtrd-oplac']): array
{
$user = $this->users->editor();
$user = $this->getEditor();
$user->password = Hash::make('password');
$user->save();
MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, json_encode($codes));

View File

@@ -93,7 +93,7 @@ class OidcTest extends TestCase
public function test_logout_route_functions()
{
$this->actingAs($this->users->editor());
$this->actingAs($this->getEditor());
$this->post('/logout');
$this->assertFalse(auth()->check());
}
@@ -228,7 +228,7 @@ class OidcTest extends TestCase
public function test_auth_login_as_existing_user()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$editor->external_auth_id = 'benny505';
$editor->save();
@@ -245,7 +245,7 @@ class OidcTest extends TestCase
public function test_auth_login_as_existing_user_email_with_different_auth_id_fails()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$editor->external_auth_id = 'editor101';
$editor->save();

View File

@@ -85,7 +85,7 @@ class ResetPasswordTest extends TestCase
public function test_reset_request_is_throttled()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
Notification::fake();
$this->get('/password/email');
$this->followingRedirects()->post('/password/email', [

View File

@@ -170,7 +170,7 @@ class Saml2Test extends TestCase
'saml2.onelogin.strict' => false,
]);
$resp = $this->actingAs($this->users->editor())->get('/');
$resp = $this->actingAs($this->getEditor())->get('/');
$this->withHtml($resp)->assertElementContains('form[action$="/saml2/logout"] button', 'Logout');
}

View File

@@ -77,18 +77,18 @@ class SocialAuthTest extends TestCase
// Test social callback with matching social account
DB::table('social_accounts')->insert([
'user_id' => $this->users->admin()->id,
'user_id' => $this->getAdmin()->id,
'driver' => 'github',
'driver_id' => 'logintest123',
]);
$resp = $this->followingRedirects()->get('/login/service/github/callback');
$resp->assertDontSee('login-form');
$this->assertActivityExists(ActivityType::AUTH_LOGIN, null, 'github; (' . $this->users->admin()->id . ') ' . $this->users->admin()->name);
$this->assertActivityExists(ActivityType::AUTH_LOGIN, null, 'github; (' . $this->getAdmin()->id . ') ' . $this->getAdmin()->name);
}
public function test_social_account_detach()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
config([
'GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc',
'APP_URL' => 'http://localhost',

View File

@@ -17,7 +17,7 @@ class UserInviteTest extends TestCase
public function test_user_creation_creates_invite()
{
Notification::fake();
$admin = $this->users->admin();
$admin = $this->getAdmin();
$email = Str::random(16) . '@example.com';
$resp = $this->actingAs($admin)->post('/settings/users/create', [
@@ -38,7 +38,7 @@ class UserInviteTest extends TestCase
public function test_user_invite_sent_in_selected_language()
{
Notification::fake();
$admin = $this->users->admin();
$admin = $this->getAdmin();
$email = Str::random(16) . '@example.com';
$resp = $this->actingAs($admin)->post('/settings/users/create', [
@@ -62,7 +62,7 @@ class UserInviteTest extends TestCase
public function test_invite_set_password()
{
Notification::fake();
$user = $this->users->viewer();
$user = $this->getViewer();
$inviteService = app(UserInviteService::class);
$inviteService->sendInvitation($user);
@@ -91,7 +91,7 @@ class UserInviteTest extends TestCase
public function test_invite_set_has_password_validation()
{
Notification::fake();
$user = $this->users->viewer();
$user = $this->getViewer();
$inviteService = app(UserInviteService::class);
$inviteService->sendInvitation($user);
@@ -126,7 +126,7 @@ class UserInviteTest extends TestCase
public function test_token_expires_after_two_weeks()
{
Notification::fake();
$user = $this->users->viewer();
$user = $this->getViewer();
$inviteService = app(UserInviteService::class);
$inviteService->sendInvitation($user);

View File

@@ -19,7 +19,7 @@ class ClearActivityCommandTest extends TestCase
$this->assertDatabaseHas('activities', [
'type' => 'page_update',
'entity_id' => $page->id,
'user_id' => $this->users->editor()->id,
'user_id' => $this->getEditor()->id,
]);
DB::rollBack();

View File

@@ -16,7 +16,7 @@ class ClearViewsCommandTest extends TestCase
$this->get($page->getUrl());
$this->assertDatabaseHas('views', [
'user_id' => $this->users->editor()->id,
'user_id' => $this->getEditor()->id,
'viewable_id' => $page->id,
'views' => 1,
]);
@@ -27,7 +27,7 @@ class ClearViewsCommandTest extends TestCase
$this->assertTrue($exitCode === 0, 'Command executed successfully');
$this->assertDatabaseMissing('views', [
'user_id' => $this->users->editor()->id,
'user_id' => $this->getEditor()->id,
]);
}
}

View File

@@ -18,11 +18,11 @@ class CopyShelfPermissionsCommandTest extends TestCase
{
$shelf = $this->entities->shelf();
$child = $shelf->books()->first();
$editorRole = $this->users->editor()->roles()->first();
$editorRole = $this->getEditor()->roles()->first();
$this->assertFalse($child->hasPermissions(), 'Child book should not be restricted by default');
$this->assertTrue($child->permissions()->count() === 0, 'Child book should have no permissions by default');
$this->permissions->setEntityPermissions($shelf, ['view', 'update'], [$editorRole]);
$this->entities->setPermissions($shelf, ['view', 'update'], [$editorRole]);
$this->artisan('bookstack:copy-shelf-permissions', [
'--slug' => $shelf->slug,
]);
@@ -43,11 +43,11 @@ class CopyShelfPermissionsCommandTest extends TestCase
$shelf = $this->entities->shelf();
Bookshelf::query()->where('id', '!=', $shelf->id)->delete();
$child = $shelf->books()->first();
$editorRole = $this->users->editor()->roles()->first();
$editorRole = $this->getEditor()->roles()->first();
$this->assertFalse($child->hasPermissions(), 'Child book should not be restricted by default');
$this->assertTrue($child->permissions()->count() === 0, 'Child book should have no permissions by default');
$this->permissions->setEntityPermissions($shelf, ['view', 'update'], [$editorRole]);
$this->entities->setPermissions($shelf, ['view', 'update'], [$editorRole]);
$this->artisan('bookstack:copy-shelf-permissions --all')
->expectsQuestion('Permission settings for all shelves will be cascaded. Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. Are you sure you want to proceed?', 'y');
$child = $shelf->books()->first();

View File

@@ -2,7 +2,8 @@
namespace Tests\Commands;
use BookStack\Auth\Permissions\CollapsedPermission;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Entities\Models\Page;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
@@ -12,23 +13,15 @@ class RegeneratePermissionsCommandTest extends TestCase
public function test_regen_permissions_command()
{
DB::rollBack();
$page = $this->entities->page();
$editor = $this->users->editor();
$this->permissions->addEntityPermission($page, ['view'], null, $editor);
CollapsedPermission::query()->truncate();
JointPermission::query()->truncate();
$page = Page::first();
$this->assertDatabaseMissing('entity_permissions_collapsed', ['entity_id' => $page->id]);
$this->assertDatabaseMissing('joint_permissions', ['entity_id' => $page->id]);
$exitCode = Artisan::call('bookstack:regenerate-permissions');
$this->assertTrue($exitCode === 0, 'Command executed successfully');
$this->assertDatabaseHas('entity_permissions_collapsed', [
'entity_id' => $page->id,
'user_id' => $editor->id,
'view' => 1,
]);
CollapsedPermission::query()->truncate();
DB::beginTransaction();
$this->assertDatabaseHas('joint_permissions', ['entity_id' => $page->id]);
}
}

View File

@@ -16,20 +16,21 @@ class BookShelfTest extends TestCase
public function test_shelves_shows_in_header_if_have_view_permissions()
{
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$resp = $this->actingAs($viewer)->get('/');
$this->withHtml($resp)->assertElementContains('header', 'Shelves');
$viewer->roles()->delete();
$this->giveUserPermissions($viewer);
$resp = $this->actingAs($viewer)->get('/');
$this->withHtml($resp)->assertElementNotContains('header', 'Shelves');
$this->permissions->grantUserRolePermissions($viewer, ['bookshelf-view-all']);
$this->giveUserPermissions($viewer, ['bookshelf-view-all']);
$resp = $this->actingAs($viewer)->get('/');
$this->withHtml($resp)->assertElementContains('header', 'Shelves');
$viewer->roles()->delete();
$this->permissions->grantUserRolePermissions($viewer, ['bookshelf-view-own']);
$this->giveUserPermissions($viewer, ['bookshelf-view-own']);
$resp = $this->actingAs($viewer)->get('/');
$this->withHtml($resp)->assertElementContains('header', 'Shelves');
}
@@ -37,14 +38,14 @@ class BookShelfTest extends TestCase
public function test_shelves_shows_in_header_if_have_any_shelve_view_permission()
{
$user = User::factory()->create();
$this->permissions->grantUserRolePermissions($user, ['image-create-all']);
$this->giveUserPermissions($user, ['image-create-all']);
$shelf = $this->entities->shelf();
$userRole = $user->roles()->first();
$resp = $this->actingAs($user)->get('/');
$this->withHtml($resp)->assertElementNotContains('header', 'Shelves');
$this->permissions->setEntityPermissions($shelf, ['view'], [$userRole]);
$this->entities->setPermissions($shelf, ['view'], [$userRole]);
$resp = $this->get('/');
$this->withHtml($resp)->assertElementContains('header', 'Shelves');
@@ -68,7 +69,7 @@ class BookShelfTest extends TestCase
$resp->assertSee($book->name);
$resp->assertSee($book->getUrl());
$this->permissions->setEntityPermissions($book, []);
$this->entities->setPermissions($book, []);
$resp = $this->asEditor()->get('/shelves');
$resp->assertDontSee($book->name);
@@ -92,7 +93,7 @@ class BookShelfTest extends TestCase
],
]));
$resp->assertRedirect();
$editorId = $this->users->editor()->id;
$editorId = $this->getEditor()->id;
$this->assertDatabaseHas('bookshelves', array_merge($shelfInfo, ['created_by' => $editorId, 'updated_by' => $editorId]));
$shelf = Bookshelf::where('name', '=', $shelfInfo['name'])->first();
@@ -185,13 +186,13 @@ class BookShelfTest extends TestCase
$this->withHtml($resp)->assertElementContains('.book-content a.grid-card:nth-child(1)', $books[0]->name);
$this->withHtml($resp)->assertElementNotContains('.book-content a.grid-card:nth-child(3)', $books[0]->name);
setting()->putUser($this->users->editor(), 'shelf_books_sort_order', 'desc');
setting()->putUser($this->getEditor(), 'shelf_books_sort_order', 'desc');
$resp = $this->asEditor()->get($shelf->getUrl());
$this->withHtml($resp)->assertElementNotContains('.book-content a.grid-card:nth-child(1)', $books[0]->name);
$this->withHtml($resp)->assertElementContains('.book-content a.grid-card:nth-child(3)', $books[0]->name);
setting()->putUser($this->users->editor(), 'shelf_books_sort_order', 'desc');
setting()->putUser($this->users->editor(), 'shelf_books_sort', 'name');
setting()->putUser($this->getEditor(), 'shelf_books_sort_order', 'desc');
setting()->putUser($this->getEditor(), 'shelf_books_sort', 'name');
$resp = $this->asEditor()->get($shelf->getUrl());
$this->withHtml($resp)->assertElementContains('.book-content a.grid-card:nth-child(1)', 'hdgfgdfg');
$this->withHtml($resp)->assertElementContains('.book-content a.grid-card:nth-child(2)', 'bsfsdfsdfsd');
@@ -223,7 +224,7 @@ class BookShelfTest extends TestCase
$resp->assertRedirect($shelf->getUrl());
$this->assertSessionHas('success');
$editorId = $this->users->editor()->id;
$editorId = $this->getEditor()->id;
$this->assertDatabaseHas('bookshelves', array_merge($shelfInfo, ['id' => $shelf->id, 'created_by' => $editorId, 'updated_by' => $editorId]));
$shelfPage = $this->get($shelf->getUrl());
@@ -293,11 +294,11 @@ class BookShelfTest extends TestCase
$resp->assertSee("action=\"{$shelf->getUrl('/copy-permissions')}\"", false);
$child = $shelf->books()->first();
$editorRole = $this->users->editor()->roles()->first();
$editorRole = $this->getEditor()->roles()->first();
$this->assertFalse($child->hasPermissions(), 'Child book should not be restricted by default');
$this->assertTrue($child->permissions()->count() === 0, 'Child book should have no permissions by default');
$this->permissions->setEntityPermissions($shelf, ['view', 'update'], [$editorRole]);
$this->entities->setPermissions($shelf, ['view', 'update'], [$editorRole]);
$resp = $this->post($shelf->getUrl('/copy-permissions'));
$child = $shelf->books()->first();

View File

@@ -221,7 +221,7 @@ class BookTest extends TestCase
public function test_books_view_shows_view_toggle_option()
{
/** @var Book $book */
$editor = $this->users->editor();
$editor = $this->getEditor();
setting()->putUser($editor, 'books_view_type', 'list');
$resp = $this->actingAs($editor)->get('/books');
@@ -304,7 +304,7 @@ class BookTest extends TestCase
// Hide child content
/** @var BookChild $page */
foreach ($book->getDirectChildren() as $child) {
$this->permissions->setEntityPermissions($child, [], []);
$this->entities->setPermissions($child, [], []);
}
$this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']);
@@ -318,8 +318,8 @@ class BookTest extends TestCase
{
/** @var Book $book */
$book = Book::query()->whereHas('chapters')->whereHas('directPages')->whereHas('chapters')->first();
$viewer = $this->users->viewer();
$this->permissions->grantUserRolePermissions($viewer, ['book-create-all']);
$viewer = $this->getViewer();
$this->giveUserPermissions($viewer, ['book-create-all']);
$this->actingAs($viewer)->post($book->getUrl('/copy'), ['name' => 'My copy book']);
/** @var Book $copy */
@@ -354,9 +354,9 @@ class BookTest extends TestCase
$shelfA->appendBook($book);
$shelfB->appendBook($book);
$viewer = $this->users->viewer();
$this->permissions->grantUserRolePermissions($viewer, ['book-update-all', 'book-create-all', 'bookshelf-update-all']);
$this->permissions->setEntityPermissions($shelfB);
$viewer = $this->getViewer();
$this->giveUserPermissions($viewer, ['book-update-all', 'book-create-all', 'bookshelf-update-all']);
$this->entities->setPermissions($shelfB);
$this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']);

View File

@@ -101,7 +101,7 @@ class ChapterTest extends TestCase
// Hide pages to all non-admin roles
/** @var Page $page */
foreach ($chapter->pages as $page) {
$this->permissions->setEntityPermissions($page, [], []);
$this->entities->setPermissions($page, [], []);
}
$this->asEditor()->post($chapter->getUrl('/copy'), [
@@ -116,8 +116,8 @@ class ChapterTest extends TestCase
public function test_copy_does_not_copy_pages_if_user_cant_page_create()
{
$chapter = $this->entities->chapterHasPages();
$viewer = $this->users->viewer();
$this->permissions->grantUserRolePermissions($viewer, ['chapter-create-all']);
$viewer = $this->getViewer();
$this->giveUserPermissions($viewer, ['chapter-create-all']);
// Lacking permission results in no copied pages
$this->actingAs($viewer)->post($chapter->getUrl('/copy'), [
@@ -128,7 +128,7 @@ class ChapterTest extends TestCase
$newChapter = Chapter::query()->where('name', '=', 'My copied chapter')->first();
$this->assertEquals(0, $newChapter->pages()->count());
$this->permissions->grantUserRolePermissions($viewer, ['page-create-all']);
$this->giveUserPermissions($viewer, ['page-create-all']);
// Having permission rules in copied pages
$this->actingAs($viewer)->post($chapter->getUrl('/copy'), [
@@ -144,7 +144,7 @@ class ChapterTest extends TestCase
{
$chapter = $this->entities->chapter();
$resp = $this->actingAs($this->users->viewer())->get($chapter->getUrl());
$resp = $this->actingAs($this->getViewer())->get($chapter->getUrl());
$this->withHtml($resp)->assertLinkNotExists($chapter->book->getUrl('sort'));
$resp = $this->asEditor()->get($chapter->getUrl());

View File

@@ -49,16 +49,16 @@ class ConvertTest extends TestCase
public function test_convert_chapter_to_book_requires_permissions()
{
$chapter = $this->entities->chapter();
$user = $this->users->viewer();
$user = $this->getViewer();
$permissions = ['chapter-delete-all', 'book-create-all', 'chapter-update-all'];
$this->permissions->grantUserRolePermissions($user, $permissions);
$this->giveUserPermissions($user, $permissions);
foreach ($permissions as $permission) {
$this->permissions->removeUserRolePermissions($user, [$permission]);
$this->removePermissionFromUser($user, $permission);
$resp = $this->actingAs($user)->post($chapter->getUrl('/convert-to-book'));
$this->assertPermissionError($resp);
$this->permissions->grantUserRolePermissions($user, [$permission]);
$this->giveUserPermissions($user, [$permission]);
}
$resp = $this->actingAs($user)->post($chapter->getUrl('/convert-to-book'));
@@ -122,16 +122,16 @@ class ConvertTest extends TestCase
public function test_book_convert_to_shelf_requires_permissions()
{
$book = $this->entities->book();
$user = $this->users->viewer();
$user = $this->getViewer();
$permissions = ['book-delete-all', 'bookshelf-create-all', 'book-update-all', 'book-create-all'];
$this->permissions->grantUserRolePermissions($user, $permissions);
$this->giveUserPermissions($user, $permissions);
foreach ($permissions as $permission) {
$this->permissions->removeUserRolePermissions($user, [$permission]);
$this->removePermissionFromUser($user, $permission);
$resp = $this->actingAs($user)->post($book->getUrl('/convert-to-shelf'));
$this->assertPermissionError($resp);
$this->permissions->grantUserRolePermissions($user, [$permission]);
$this->giveUserPermissions($user, [$permission]);
}
$resp = $this->actingAs($user)->post($book->getUrl('/convert-to-shelf'));

View File

@@ -11,8 +11,8 @@ class EntityAccessTest extends TestCase
public function test_entities_viewable_after_creator_deletion()
{
// Create required assets and revisions
$creator = $this->users->editor();
$updater = $this->users->viewer();
$creator = $this->getEditor();
$updater = $this->getViewer();
$entities = $this->entities->createChainBelongingToUser($creator, $updater);
app()->make(UserRepo::class)->destroy($creator);
$this->entities->updatePage($entities['page'], ['html' => '<p>hello!</p>>']);
@@ -23,8 +23,8 @@ class EntityAccessTest extends TestCase
public function test_entities_viewable_after_updater_deletion()
{
// Create required assets and revisions
$creator = $this->users->viewer();
$updater = $this->users->editor();
$creator = $this->getViewer();
$updater = $this->getEditor();
$entities = $this->entities->createChainBelongingToUser($creator, $updater);
app()->make(UserRepo::class)->destroy($updater);
$this->entities->updatePage($entities['page'], ['html' => '<p>Hello there!</p>']);

View File

@@ -132,7 +132,7 @@ class EntitySearchTest extends TestCase
public function test_search_filters()
{
$page = $this->entities->newPage(['name' => 'My new test quaffleachits', 'html' => 'this is about an orange donkey danzorbhsing']);
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor);
// Viewed filter searches
@@ -171,7 +171,7 @@ class EntitySearchTest extends TestCase
// Restricted filter
$this->get('/search?term=' . urlencode('danzorbhsing {is_restricted}'))->assertDontSee($page->name);
$this->permissions->setEntityPermissions($page, ['view'], [$editor->roles->first()]);
$this->entities->setPermissions($page, ['view'], [$editor->roles->first()]);
$this->get('/search?term=' . urlencode('danzorbhsing {is_restricted}'))->assertSee($page->name);
// Date filters
@@ -235,7 +235,7 @@ class EntitySearchTest extends TestCase
$this->withHtml($resp)->assertElementContains($baseSelector, $page->name);
$this->withHtml($resp)->assertElementNotContains($baseSelector, "You don't have the required permissions to select this item");
$resp = $this->actingAs($this->users->viewer())->get($searchUrl);
$resp = $this->actingAs($this->getViewer())->get($searchUrl);
$this->withHtml($resp)->assertElementContains($baseSelector, $page->name);
$this->withHtml($resp)->assertElementContains($baseSelector, "You don't have the required permissions to select this item");
}
@@ -246,7 +246,7 @@ class EntitySearchTest extends TestCase
$this->assertGreaterThan(2, count($chapter->pages), 'Ensure we\'re testing with at least 1 sibling');
$page = $chapter->pages->first();
$search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
$search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
$search->assertSuccessful();
foreach ($chapter->pages as $page) {
$search->assertSee($page->name);
@@ -261,7 +261,7 @@ class EntitySearchTest extends TestCase
$bookChildren = $page->book->getDirectChildren();
$this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
$search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
$search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
$search->assertSuccessful();
foreach ($bookChildren as $child) {
$search->assertSee($child->name);
@@ -276,7 +276,7 @@ class EntitySearchTest extends TestCase
$bookChildren = $chapter->book->getDirectChildren();
$this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
$search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter");
$search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter");
$search->assertSuccessful();
foreach ($bookChildren as $child) {
$search->assertSee($child->name);
@@ -291,7 +291,7 @@ class EntitySearchTest extends TestCase
$book = $books->first();
$this->assertGreaterThan(2, count($books), 'Ensure we\'re testing with at least 1 sibling');
$search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$book->id}&entity_type=book");
$search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$book->id}&entity_type=book");
$search->assertSuccessful();
foreach ($books as $expectedBook) {
$search->assertSee($expectedBook->name);
@@ -304,7 +304,7 @@ class EntitySearchTest extends TestCase
$shelf = $shelves->first();
$this->assertGreaterThan(2, count($shelves), 'Ensure we\'re testing with at least 1 sibling');
$search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$shelf->id}&entity_type=bookshelf");
$search = $this->actingAs($this->getViewer())->get("/search/entity/siblings?entity_id={$shelf->id}&entity_type=bookshelf");
$search->assertSuccessful();
foreach ($shelves as $expectedShelf) {
$search->assertSee($expectedShelf->name);

View File

@@ -275,7 +275,7 @@ class ExportTest extends TestCase
public function test_page_export_with_deleted_creator_and_updater()
{
$user = $this->users->viewer(['name' => 'ExportWizardTheFifth']);
$user = $this->getViewer(['name' => 'ExportWizardTheFifth']);
$page = $this->entities->page();
$page->created_by = $user->id;
$page->updated_by = $user->id;
@@ -409,7 +409,7 @@ class ExportTest extends TestCase
$chapter = $book->chapters()->first();
$page = $chapter->pages()->first();
$entities = [$book, $chapter, $page];
$user = $this->users->viewer();
$user = $this->getViewer();
$this->actingAs($user);
foreach ($entities as $entity) {
@@ -417,7 +417,8 @@ class ExportTest extends TestCase
$resp->assertSee('/export/pdf');
}
$this->permissions->removeUserRolePermissions($user, ['content-export']);
/** @var Role $role */
$this->removePermissionFromUser($user, 'content-export');
foreach ($entities as $entity) {
$resp = $this->get($entity->getUrl());

View File

@@ -483,7 +483,7 @@ class PageContentTest extends TestCase
{
$page = $this->entities->page();
$this->actingAs($this->users->admin())
$this->actingAs($this->getAdmin())
->put($page->getUrl(''), [
'name' => 'Testing',
'html' => '<p>&quot;Hello &amp; welcome&quot;</p>',

View File

@@ -39,7 +39,7 @@ class PageDraftTest extends TestCase
$this->withHtml($resp)->assertElementNotContains('[name="html"]', $addedContent);
$newContent = $this->page->html . $addedContent;
$newUser = $this->users->editor();
$newUser = $this->getEditor();
$this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
$resp = $this->actingAs($newUser)->get($this->page->getUrl('/edit'));
@@ -62,7 +62,7 @@ class PageDraftTest extends TestCase
$this->withHtml($resp)->assertElementNotContains('[name="html"]', $addedContent);
$newContent = $this->page->html . $addedContent;
$newUser = $this->users->editor();
$newUser = $this->getEditor();
$this->pageRepo->updatePageDraft($this->page, ['html' => $newContent]);
$this->actingAs($newUser)
@@ -75,8 +75,8 @@ class PageDraftTest extends TestCase
public function test_draft_save_shows_alert_if_draft_older_than_last_page_update()
{
$admin = $this->users->admin();
$editor = $this->users->editor();
$admin = $this->getAdmin();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($editor)->put('/ajax/page/' . $page->id . '/save-draft', [
@@ -109,8 +109,8 @@ class PageDraftTest extends TestCase
public function test_draft_save_shows_alert_if_draft_edit_started_by_someone_else()
{
$admin = $this->users->admin();
$editor = $this->users->editor();
$admin = $this->getAdmin();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($admin)->put('/ajax/page/' . $page->id . '/save-draft', [
@@ -143,7 +143,7 @@ class PageDraftTest extends TestCase
{
$book = $this->entities->book();
$chapter = $book->chapters->first();
$newUser = $this->users->editor();
$newUser = $this->getEditor();
$this->actingAs($newUser)->get($book->getUrl('/create-page'));
$this->get($chapter->getUrl('/create-page'));

View File

@@ -208,13 +208,13 @@ class PageRevisionTest extends TestCase
$page = $this->entities->page();
$this->createRevisions($page, 2);
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$this->actingAs($viewer);
$respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
$respHtml->assertElementNotContains('.actions a', 'Restore');
$respHtml->assertElementNotExists('form[action$="/restore"]');
$this->permissions->grantUserRolePermissions($viewer, ['page-update-all']);
$this->giveUserPermissions($viewer, ['page-update-all']);
$respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
$respHtml->assertElementContains('.actions a', 'Restore');
@@ -226,13 +226,13 @@ class PageRevisionTest extends TestCase
$page = $this->entities->page();
$this->createRevisions($page, 2);
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$this->actingAs($viewer);
$respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
$respHtml->assertElementNotContains('.actions a', 'Delete');
$respHtml->assertElementNotExists('form[action$="/delete"]');
$this->permissions->grantUserRolePermissions($viewer, ['page-delete-all']);
$this->giveUserPermissions($viewer, ['page-delete-all']);
$respHtml = $this->withHtml($this->get($page->getUrl('/revisions')));
$respHtml->assertElementContains('.actions a', 'Delete');

View File

@@ -25,7 +25,7 @@ class PageTemplateTest extends TestCase
public function test_manage_templates_permission_required_to_change_page_template_status()
{
$page = $this->entities->page();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor);
$pageUpdateData = [
@@ -40,7 +40,7 @@ class PageTemplateTest extends TestCase
'template' => false,
]);
$this->permissions->grantUserRolePermissions($editor, ['templates-manage']);
$this->giveUserPermissions($editor, ['templates-manage']);
$this->put($page->getUrl(), $pageUpdateData);
$this->assertDatabaseHas('pages', [
@@ -53,7 +53,7 @@ class PageTemplateTest extends TestCase
{
$content = '<div>my_custom_template_content</div>';
$page = $this->entities->page();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor);
$templateFetch = $this->get('/templates/' . $page->id);
@@ -73,7 +73,7 @@ class PageTemplateTest extends TestCase
public function test_template_endpoint_returns_paginated_list_of_templates()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor);
$toBeTemplates = Page::query()->orderBy('name', 'asc')->take(12)->get();

View File

@@ -38,8 +38,8 @@ class PageTest extends TestCase
public function test_page_view_when_creator_is_deleted_but_owner_exists()
{
$page = $this->entities->page();
$user = $this->users->viewer();
$owner = $this->users->editor();
$user = $this->getViewer();
$owner = $this->getEditor();
$page->created_by = $user->id;
$page->owned_by = $owner->id;
$page->save();
@@ -190,15 +190,15 @@ class PageTest extends TestCase
$page = $this->entities->page();
$currentBook = $page->book;
$newBook = Book::where('id', '!=', $currentBook->id)->first();
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$resp = $this->actingAs($viewer)->get($page->getUrl());
$resp->assertDontSee($page->getUrl('/copy'));
$newBook->owned_by = $viewer->id;
$newBook->save();
$this->permissions->grantUserRolePermissions($viewer, ['page-create-own']);
$this->permissions->regenerateForEntity($newBook);
$this->giveUserPermissions($viewer, ['page-create-own']);
$this->entities->regenPermissions($newBook);
$resp = $this->actingAs($viewer)->get($page->getUrl());
$resp->assertSee($page->getUrl('/copy'));
@@ -249,7 +249,7 @@ class PageTest extends TestCase
public function test_recently_updated_pages_view()
{
$user = $this->users->editor();
$user = $this->getEditor();
$content = $this->entities->createChainBelongingToUser($user);
$resp = $this->asAdmin()->get('/pages/recently-updated');
@@ -258,7 +258,7 @@ class PageTest extends TestCase
public function test_recently_updated_pages_view_shows_updated_by_details()
{
$user = $this->users->editor();
$user = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($user)->put($page->getUrl(), [
@@ -272,7 +272,7 @@ class PageTest extends TestCase
public function test_recently_updated_pages_view_shows_parent_chain()
{
$user = $this->users->editor();
$user = $this->getEditor();
$page = $this->entities->pageWithinChapter();
$this->actingAs($user)->put($page->getUrl(), [
@@ -287,7 +287,7 @@ class PageTest extends TestCase
public function test_recently_updated_pages_view_does_not_show_parent_if_not_visible()
{
$user = $this->users->editor();
$user = $this->getEditor();
$page = $this->entities->pageWithinChapter();
$this->actingAs($user)->put($page->getUrl(), [
@@ -295,8 +295,8 @@ class PageTest extends TestCase
'html' => '<p>Updated content</p>',
]);
$this->permissions->setEntityPermissions($page->book);
$this->permissions->setEntityPermissions($page, ['view'], [$user->roles->first()]);
$this->entities->setPermissions($page->book);
$this->entities->setPermissions($page, ['view'], [$user->roles->first()]);
$resp = $this->get('/pages/recently-updated');
$resp->assertDontSee($page->book->getShortName(42));

View File

@@ -53,7 +53,7 @@ class SortTest extends TestCase
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
$newChapter = $newBook->chapters()->first();
$movePageResp = $this->actingAs($this->users->editor())->put($page->getUrl('/move'), [
$movePageResp = $this->actingAs($this->getEditor())->put($page->getUrl('/move'), [
'entity_selection' => 'chapter:' . $newChapter->id,
]);
$page->refresh();
@@ -71,7 +71,7 @@ class SortTest extends TestCase
$page = $oldChapter->pages()->first();
$newBook = Book::query()->where('id', '!=', $oldChapter->book_id)->first();
$movePageResp = $this->actingAs($this->users->editor())->put($page->getUrl('/move'), [
$movePageResp = $this->actingAs($this->getEditor())->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$page->refresh();
@@ -89,16 +89,16 @@ class SortTest extends TestCase
$page = $this->entities->page();
$currentBook = $page->book;
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->permissions->setEntityPermissions($newBook, ['view', 'update', 'delete'], $editor->roles->all());
$this->entities->setPermissions($newBook, ['view', 'update', 'delete'], $editor->roles->all());
$movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$this->assertPermissionError($movePageResp);
$this->permissions->setEntityPermissions($newBook, ['view', 'update', 'delete', 'create'], $editor->roles->all());
$this->entities->setPermissions($newBook, ['view', 'update', 'delete', 'create'], $editor->roles->all());
$movePageResp = $this->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
@@ -114,10 +114,10 @@ class SortTest extends TestCase
$page = $this->entities->page();
$currentBook = $page->book;
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->permissions->setEntityPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$this->permissions->setEntityPermissions($page, ['view', 'update', 'create'], $editor->roles->all());
$this->entities->setPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$this->entities->setPermissions($page, ['view', 'update', 'create'], $editor->roles->all());
$movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
@@ -126,7 +126,7 @@ class SortTest extends TestCase
$pageView = $this->get($page->getUrl());
$pageView->assertDontSee($page->getUrl('/move'));
$this->permissions->setEntityPermissions($page, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$this->entities->setPermissions($page, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$movePageResp = $this->put($page->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
@@ -169,10 +169,10 @@ class SortTest extends TestCase
$chapter = $this->entities->chapter();
$currentBook = $chapter->book;
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->permissions->setEntityPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$this->permissions->setEntityPermissions($chapter, ['view', 'update', 'create'], $editor->roles->all());
$this->entities->setPermissions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$this->entities->setPermissions($chapter, ['view', 'update', 'create'], $editor->roles->all());
$moveChapterResp = $this->actingAs($editor)->put($chapter->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
@@ -181,7 +181,7 @@ class SortTest extends TestCase
$pageView = $this->get($chapter->getUrl());
$pageView->assertDontSee($chapter->getUrl('/move'));
$this->permissions->setEntityPermissions($chapter, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$this->entities->setPermissions($chapter, ['view', 'update', 'create', 'delete'], $editor->roles->all());
$moveChapterResp = $this->put($chapter->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
@@ -196,17 +196,17 @@ class SortTest extends TestCase
$chapter = $this->entities->chapter();
$currentBook = $chapter->book;
$newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->permissions->setEntityPermissions($newBook, ['view', 'update', 'delete'], [$editor->roles->first()]);
$this->permissions->setEntityPermissions($chapter, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
$this->entities->setPermissions($newBook, ['view', 'update', 'delete'], [$editor->roles->first()]);
$this->entities->setPermissions($chapter, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
$moveChapterResp = $this->actingAs($editor)->put($chapter->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
$this->assertPermissionError($moveChapterResp);
$this->permissions->setEntityPermissions($newBook, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
$this->entities->setPermissions($newBook, ['view', 'update', 'create', 'delete'], [$editor->roles->first()]);
$moveChapterResp = $this->put($chapter->getUrl('/move'), [
'entity_selection' => 'book:' . $newBook->id,
]);
@@ -313,7 +313,7 @@ class SortTest extends TestCase
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
$this->permissions->setEntityPermissions($otherChapter);
$this->entities->setPermissions($otherChapter);
$sortData = [
'id' => $page->id,
@@ -334,8 +334,8 @@ class SortTest extends TestCase
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
$editor = $this->users->editor();
$this->permissions->setEntityPermissions($otherChapter->book, ['update', 'delete'], [$editor->roles()->first()]);
$editor = $this->getEditor();
$this->entities->setPermissions($otherChapter->book, ['update', 'delete'], [$editor->roles()->first()]);
$sortData = [
'id' => $page->id,
@@ -356,8 +356,8 @@ class SortTest extends TestCase
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
$editor = $this->users->editor();
$this->permissions->setEntityPermissions($otherChapter, ['view', 'delete'], [$editor->roles()->first()]);
$editor = $this->getEditor();
$this->entities->setPermissions($otherChapter, ['view', 'delete'], [$editor->roles()->first()]);
$sortData = [
'id' => $page->id,
@@ -378,8 +378,8 @@ class SortTest extends TestCase
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
$editor = $this->users->editor();
$this->permissions->setEntityPermissions($page, ['view', 'delete'], [$editor->roles()->first()]);
$editor = $this->getEditor();
$this->entities->setPermissions($page, ['view', 'delete'], [$editor->roles()->first()]);
$sortData = [
'id' => $page->id,
@@ -400,8 +400,8 @@ class SortTest extends TestCase
$page = $this->entities->pageWithinChapter();
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->where('book_id', '!=', $page->book_id)->first();
$editor = $this->users->editor();
$this->permissions->setEntityPermissions($page, ['view', 'update'], [$editor->roles()->first()]);
$editor = $this->getEditor();
$this->entities->setPermissions($page, ['view', 'update'], [$editor->roles()->first()]);
$sortData = [
'id' => $page->id,

View File

@@ -75,7 +75,7 @@ class TagTest extends TestCase
$this->asEditor()->get('/ajax/tags/suggest/names?search=co')->assertSimilarJson(['color', 'country']);
// Set restricted permission the page
$this->permissions->setEntityPermissions($page, [], []);
$this->entities->setPermissions($page, [], []);
$this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->assertSimilarJson(['color', 'country']);
$this->asEditor()->get('/ajax/tags/suggest/names?search=co')->assertSimilarJson([]);
@@ -178,7 +178,7 @@ class TagTest extends TestCase
$resp = $this->get('/tags?name=SuperCategory');
$resp->assertSee('GreatTestContent');
$this->permissions->setEntityPermissions($page, [], []);
$this->entities->setPermissions($page, [], []);
$resp = $this->asEditor()->get('/tags');
$resp->assertDontSee('SuperCategory');

View File

@@ -11,7 +11,7 @@ class ErrorTest extends TestCase
// Due to middleware being handled differently this will not fail
// if our custom, middleware-loaded handler fails but this is here
// as a reminder and as a general check in the event of other issues.
$editor = $this->users->editor();
$editor = $this->getEditor();
$editor->name = 'tester';
$editor->save();
@@ -24,7 +24,7 @@ class ErrorTest extends TestCase
public function test_item_not_found_does_not_get_logged_to_file()
{
$this->actingAs($this->users->viewer());
$this->actingAs($this->getViewer());
$handler = $this->withTestLogger();
$book = $this->entities->book();
@@ -41,7 +41,7 @@ class ErrorTest extends TestCase
public function test_access_to_non_existing_image_location_provides_404_response()
{
$resp = $this->actingAs($this->users->viewer())->get('/uploads/images/gallery/2021-05/anonexistingimage.png');
$resp = $this->actingAs($this->getViewer())->get('/uploads/images/gallery/2021-05/anonexistingimage.png');
$resp->assertStatus(404);
$resp->assertSeeText('Image Not Found');
}

View File

@@ -10,7 +10,7 @@ class FavouriteTest extends TestCase
public function test_page_add_favourite_flow()
{
$page = $this->entities->page();
$editor = $this->users->editor();
$editor = $this->getEditor();
$resp = $this->actingAs($editor)->get($page->getUrl());
$this->withHtml($resp)->assertElementContains('button', 'Favourite');
@@ -33,7 +33,7 @@ class FavouriteTest extends TestCase
public function test_page_remove_favourite_flow()
{
$page = $this->entities->page();
$editor = $this->users->editor();
$editor = $this->getEditor();
Favourite::query()->forceCreate([
'user_id' => $editor->id,
'favouritable_id' => $page->id,
@@ -63,7 +63,7 @@ class FavouriteTest extends TestCase
$book->owned_by = $user->id;
$book->save();
$this->permissions->grantUserRolePermissions($user, ['book-view-own']);
$this->giveUserPermissions($user, ['book-view-own']);
$this->actingAs($user)->get($book->getUrl());
$resp = $this->post('/favourites/add', [
@@ -81,7 +81,7 @@ class FavouriteTest extends TestCase
public function test_each_entity_type_shows_favourite_button()
{
$this->actingAs($this->users->editor());
$this->actingAs($this->getEditor());
foreach ($this->entities->all() as $entity) {
$resp = $this->get($entity->getUrl());
@@ -94,13 +94,13 @@ class FavouriteTest extends TestCase
$this->setSettings(['app-public' => 'true']);
$resp = $this->get('/');
$this->withHtml($resp)->assertElementNotContains('header', 'My Favourites');
$resp = $this->actingAs($this->users->viewer())->get('/');
$resp = $this->actingAs($this->getViewer())->get('/');
$this->withHtml($resp)->assertElementContains('header a', 'My Favourites');
}
public function test_favourites_shown_on_homepage()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$resp = $this->actingAs($editor)->get('/');
$this->withHtml($resp)->assertElementNotExists('#top-favourites');
@@ -116,7 +116,7 @@ class FavouriteTest extends TestCase
public function test_favourites_list_page_shows_favourites_and_has_working_pagination()
{
$page = $this->entities->page();
$editor = $this->users->editor();
$editor = $this->getEditor();
$resp = $this->actingAs($editor)->get('/favourites');
$resp->assertDontSee($page->name);

View File

@@ -2,6 +2,8 @@
namespace Tests\Helpers;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
@@ -184,6 +186,44 @@ class EntityProvider
return $pageRepo->publishDraft($draftPage, $input);
}
/**
* Regenerate the permission for an entity.
* Centralised to manage clearing of cached elements between requests.
*/
public function regenPermissions(Entity $entity): void
{
$entity->rebuildPermissions();
$entity->load('jointPermissions');
}
/**
* Set the given entity as having restricted permissions, and apply the given
* permissions for the given roles.
* @param string[] $actions
* @param Role[] $roles
*/
public function setPermissions(Entity $entity, array $actions = [], array $roles = []): void
{
$entity->permissions()->delete();
$permissions = [
// Set default permissions to not allow actions so that only the provided role permissions are at play.
['role_id' => 0, 'view' => false, 'create' => false, 'update' => false, 'delete' => false],
];
foreach ($roles as $role) {
$permission = ['role_id' => $role->id];
foreach (EntityPermission::PERMISSIONS as $possibleAction) {
$permission[$possibleAction] = in_array($possibleAction, $actions);
}
$permissions[] = $permission;
}
$entity->permissions()->createMany($permissions);
$entity->load('permissions');
$this->regenPermissions($entity);
}
/**
* @param Entity|Entity[] $entities
*/

View File

@@ -1,136 +0,0 @@
<?php
namespace Tests\Helpers;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
class PermissionsProvider
{
protected UserRoleProvider $userRoleProvider;
public function __construct(UserRoleProvider $userRoleProvider)
{
$this->userRoleProvider = $userRoleProvider;
}
/**
* Grant role permissions to the provided user.
*/
public function grantUserRolePermissions(User $user, array $permissions): void
{
$newRole = $this->userRoleProvider->createRole($permissions);
$user->attachRole($newRole);
$user->load('roles');
$user->clearPermissionCache();
}
/**
* Completely remove specific role permissions from the provided user.
*/
public function removeUserRolePermissions(User $user, array $permissions): void
{
foreach ($permissions as $permissionName) {
/** @var RolePermission $permission */
$permission = RolePermission::query()
->where('name', '=', $permissionName)
->firstOrFail();
$roles = $user->roles()->whereHas('permissions', function ($query) use ($permission) {
$query->where('id', '=', $permission->id);
})->get();
/** @var Role $role */
foreach ($roles as $role) {
$role->detachPermission($permission);
}
$user->clearPermissionCache();
}
}
/**
* Change the owner of the given entity to the given user.
*/
public function changeEntityOwner(Entity $entity, User $newOwner): void
{
$entity->owned_by = $newOwner->id;
$entity->save();
$entity->rebuildPermissions();
}
/**
* Regenerate the permission for an entity.
* Centralised to manage clearing of cached elements between requests.
*/
public function regenerateForEntity(Entity $entity): void
{
$entity->rebuildPermissions();
}
/**
* Set the given entity as having restricted permissions, and apply the given
* permissions for the given roles.
* @param string[] $actions
* @param Role[] $roles
*/
public function setEntityPermissions(Entity $entity, array $actions = [], array $roles = [], $inherit = false): void
{
$entity->permissions()->delete();
$permissions = [];
if (!$inherit) {
// Set default permissions to not allow actions so that only the provided role permissions are at play.
$permissions[] = ['role_id' => null, 'user_id' => null, 'view' => false, 'create' => false, 'update' => false, 'delete' => false];
}
foreach ($roles as $role) {
$permissions[] = $this->actionListToEntityPermissionData($actions, $role->id);
}
$this->addEntityPermissionEntries($entity, $permissions);
}
public function addEntityPermission(Entity $entity, array $actionList, ?Role $role = null, ?User $user = null)
{
$permissionData = $this->actionListToEntityPermissionData($actionList, $role->id ?? null, $user->id ?? null);
$this->addEntityPermissionEntries($entity, [$permissionData]);
}
/**
* Disable inherited permissions on the given entity.
* Effectively sets the "Other Users" UI permission option to not inherit, with no permissions.
*/
public function disableEntityInheritedPermissions(Entity $entity): void
{
$entity->permissions()->whereNull(['user_id', 'role_id'])->delete();
$fallback = $this->actionListToEntityPermissionData([]);
$this->addEntityPermissionEntries($entity, [$fallback]);
}
protected function addEntityPermissionEntries(Entity $entity, array $entityPermissionData): void
{
$entity->permissions()->createMany($entityPermissionData);
$entity->load('permissions');
$this->regenerateForEntity($entity);
}
/**
* For the given simple array of string actions (view, create, update, delete), convert
* the format to entity permission data, where permission is granted if the action is in the
* given actionList array.
*/
protected function actionListToEntityPermissionData(array $actionList, int $roleId = null, int $userId = null): array
{
$permissionData = ['role_id' => $roleId, 'user_id' => $userId];
foreach (EntityPermission::PERMISSIONS as $possibleAction) {
$permissionData[$possibleAction] = in_array($possibleAction, $actionList);
}
return $permissionData;
}
}

View File

@@ -1,97 +0,0 @@
<?php
namespace Tests\Helpers;
use BookStack\Auth\Permissions\PermissionsRepo;
use BookStack\Auth\Role;
use BookStack\Auth\User;
class UserRoleProvider
{
protected ?User $admin = null;
protected ?User $editor = null;
/**
* Get a typical "Admin" user.
*/
public function admin(): User
{
if (is_null($this->admin)) {
$adminRole = Role::getSystemRole('admin');
$this->admin = $adminRole->users->first();
}
return $this->admin;
}
/**
* Get a typical "Editor" user.
*/
public function editor(): User
{
if ($this->editor === null) {
$editorRole = Role::getRole('editor');
$this->editor = $editorRole->users->first();
}
return $this->editor;
}
/**
* Get a typical "Viewer" user.
*/
public function viewer(array $attributes = []): User
{
$user = Role::getRole('viewer')->users()->first();
if (!empty($attributes)) {
$user->forceFill($attributes)->save();
}
return $user;
}
/**
* Create a new fresh user without any relations.
*/
public function newUser(array $attrs = []): User
{
return User::factory()->create($attrs);
}
/**
* Create a new fresh user, with the given attrs, that has assigned a fresh role
* that has the given role permissions.
* Intended as a helper to create a blank slate baseline user and role.
* @return array{0: User, 1: Role}
*/
public function newUserWithRole(array $userAttrs = [], array $rolePermissions = []): array
{
$user = $this->newUser($userAttrs);
$role = $this->attachNewRole($user, $rolePermissions);
return [$user, $role];
}
/**
* Attach a new role, with the given role permissions, to the given user
* and return that role.
*/
public function attachNewRole(User $user, array $rolePermissions = []): Role
{
$role = $this->createRole($rolePermissions);
$user->attachRole($role);
return $role;
}
/**
* Create a new basic role with the given role permissions.
*/
public function createRole(array $rolePermissions = []): Role
{
$permissionRepo = app(PermissionsRepo::class);
$roleData = Role::factory()->make()->toArray();
$roleData['permissions'] = array_flip($rolePermissions);
return $permissionRepo->saveNewRole($roleData);
}
}

View File

@@ -114,7 +114,7 @@ class HomepageTest extends TestCase
public function test_set_book_homepage()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
setting()->putUser($editor, 'books_view_type', 'grid');
$this->setSettings(['app-homepage-type' => 'books']);
@@ -133,7 +133,7 @@ class HomepageTest extends TestCase
public function test_set_bookshelves_homepage()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
setting()->putUser($editor, 'bookshelves_view_type', 'grid');
$shelf = $this->entities->shelf();
@@ -152,7 +152,7 @@ class HomepageTest extends TestCase
public function test_shelves_list_homepage_adheres_to_book_visibility_permissions()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
setting()->putUser($editor, 'bookshelves_view_type', 'list');
$this->setSettings(['app-homepage-type' => 'bookshelves']);
$this->asEditor();
@@ -167,13 +167,13 @@ class HomepageTest extends TestCase
// Ensure book no longer visible without view permission
$editor->roles()->detach();
$this->permissions->grantUserRolePermissions($editor, ['bookshelf-view-all']);
$this->giveUserPermissions($editor, ['bookshelf-view-all']);
$homeVisit = $this->get('/');
$this->withHtml($homeVisit)->assertElementContains('.content-wrap', $shelf->name);
$this->withHtml($homeVisit)->assertElementNotContains('.content-wrap', $book->name);
// Ensure is visible again with entity-level view permission
$this->permissions->setEntityPermissions($book, ['view'], [$editor->roles()->first()]);
$this->entities->setPermissions($book, ['view'], [$editor->roles()->first()]);
$homeVisit = $this->get('/');
$this->withHtml($homeVisit)->assertElementContains('.content-wrap', $shelf->name);
$this->withHtml($homeVisit)->assertElementContains('.content-wrap', $book->name);

View File

@@ -77,7 +77,7 @@ class LanguageTest extends TestCase
{
$this->asEditor();
$this->assertFalse(config('app.rtl'), 'App RTL config should be false by default');
setting()->putUser($this->users->editor(), 'language', 'ar');
setting()->putUser($this->getEditor(), 'language', 'ar');
$this->get('/');
$this->assertTrue(config('app.rtl'), 'App RTL config should have been set to true by middleware');
}

View File

@@ -2,7 +2,6 @@
namespace Tests\Permissions;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
@@ -21,8 +20,8 @@ class EntityPermissionsTest extends TestCase
protected function setUp(): void
{
parent::setUp();
$this->user = $this->users->editor();
$this->viewer = $this->users->viewer();
$this->user = $this->getEditor();
$this->viewer = $this->getViewer();
}
protected function setRestrictionsForTestRoles(Entity $entity, array $actions = [])
@@ -31,7 +30,7 @@ class EntityPermissionsTest extends TestCase
$this->user->roles->first(),
$this->viewer->roles->first(),
];
$this->permissions->setEntityPermissions($entity, $actions, $roles);
$this->entities->setPermissions($entity, $actions, $roles);
}
public function test_bookshelf_view_restriction()
@@ -379,10 +378,8 @@ class EntityPermissionsTest extends TestCase
$this->put($modelInstance->getUrl('/permissions'), [
'permissions' => [
'role' => [
$roleId => [
$permission => 'true',
],
$roleId => [
$permission => 'true',
],
],
]);
@@ -658,34 +655,6 @@ class EntityPermissionsTest extends TestCase
$resp->assertRedirect($book->getUrl('/page/test-page'));
}
public function test_access_to_item_prevented_if_inheritance_active_but_permission_prevented_via_role()
{
$user = $this->users->viewer();
$viewerRole = $user->roles->first();
$chapter = $this->entities->chapter();
$book = $chapter->book;
$this->permissions->setEntityPermissions($book, ['edit'], [$viewerRole], false);
$this->permissions->setEntityPermissions($chapter, [], [$viewerRole], true);
$this->assertFalse(userCan('chapter-update', $chapter));
}
public function test_access_to_item_allowed_if_inheritance_active_and_permission_prevented_via_role_but_allowed_via_parent()
{
$user = $this->users->viewer();
$viewerRole = $user->roles->first();
$editorRole = Role::getRole('Editor');
$user->attachRole($editorRole);
$chapter = $this->entities->chapter();
$book = $chapter->book;
$this->permissions->setEntityPermissions($book, ['edit'], [$editorRole], false);
$this->permissions->setEntityPermissions($chapter, [], [$viewerRole], true);
$this->assertTrue(userCan('chapter-update', $chapter));
}
public function test_book_permissions_can_be_generated_without_error_if_child_chapter_is_in_recycle_bin()
{
$book = $this->entities->bookHasChaptersAndPages();
@@ -696,7 +665,7 @@ class EntityPermissionsTest extends TestCase
$error = null;
try {
$this->permissions->setEntityPermissions($book, ['view'], []);
$this->entities->setPermissions($book, ['view'], []);
} catch (Exception $e) {
$error = $e;
}

View File

@@ -14,7 +14,7 @@ class ExportPermissionsTest extends TestCase
$pageContent = Str::random(48);
$page->html = '<p>' . $pageContent . '</p>';
$page->save();
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$this->actingAs($viewer);
$formats = ['html', 'plaintext'];
@@ -25,7 +25,7 @@ class ExportPermissionsTest extends TestCase
$resp->assertSee($pageContent);
}
$this->permissions->setEntityPermissions($page, []);
$this->entities->setPermissions($page, []);
foreach ($formats as $format) {
$resp = $this->get($chapter->getUrl("export/{$format}"));
@@ -42,7 +42,7 @@ class ExportPermissionsTest extends TestCase
$pageContent = Str::random(48);
$page->html = '<p>' . $pageContent . '</p>';
$page->save();
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$this->actingAs($viewer);
$formats = ['html', 'plaintext'];
@@ -53,7 +53,7 @@ class ExportPermissionsTest extends TestCase
$resp->assertSee($pageContent);
}
$this->permissions->setEntityPermissions($page, []);
$this->entities->setPermissions($page, []);
foreach ($formats as $format) {
$resp = $this->get($book->getUrl("export/{$format}"));

View File

@@ -22,7 +22,7 @@ class RolesTest extends TestCase
protected function setUp(): void
{
parent::setUp();
$this->user = $this->users->viewer();
$this->user = $this->getViewer();
}
public function test_admin_can_see_settings()
@@ -42,7 +42,7 @@ class RolesTest extends TestCase
public function test_role_cannot_be_deleted_if_default()
{
$newRole = $this->users->createRole();
$newRole = $this->createNewRole();
$this->setSettings(['registration-role' => $newRole->id]);
$deletePageUrl = '/settings/roles/delete/' . $newRole->id;
@@ -121,11 +121,11 @@ class RolesTest extends TestCase
{
/** @var Role $adminRole */
$adminRole = Role::query()->where('system_name', '=', 'admin')->first();
$adminUser = $this->users->admin();
$adminUser = $this->getAdmin();
$adminRole->users()->where('id', '!=', $adminUser->id)->delete();
$this->assertEquals(1, $adminRole->users()->count());
$viewerRole = $this->users->viewer()->roles()->first();
$viewerRole = $this->getViewer()->roles()->first();
$editUrl = '/settings/users/' . $adminUser->id;
$resp = $this->actingAs($adminUser)->put($editUrl, [
@@ -169,7 +169,7 @@ class RolesTest extends TestCase
$roleA = Role::query()->create(['display_name' => 'Entity Permissions Delete Test']);
$page = $this->entities->page();
$this->permissions->setEntityPermissions($page, ['view'], [$roleA]);
$this->entities->setPermissions($page, ['view'], [$roleA]);
$this->assertDatabaseHas('entity_permissions', [
'role_id' => $roleA->id,
@@ -214,7 +214,7 @@ class RolesTest extends TestCase
public function test_manage_user_permission()
{
$this->actingAs($this->user)->get('/settings/users')->assertRedirect('/');
$this->permissions->grantUserRolePermissions($this->user, ['users-manage']);
$this->giveUserPermissions($this->user, ['users-manage']);
$this->actingAs($this->user)->get('/settings/users')->assertOk();
}
@@ -222,9 +222,9 @@ class RolesTest extends TestCase
{
$usersLink = 'href="' . url('/settings/users') . '"';
$this->actingAs($this->user)->get('/')->assertDontSee($usersLink, false);
$this->permissions->grantUserRolePermissions($this->user, ['users-manage']);
$this->giveUserPermissions($this->user, ['users-manage']);
$this->actingAs($this->user)->get('/')->assertSee($usersLink, false);
$this->permissions->grantUserRolePermissions($this->user, ['settings-manage', 'users-manage']);
$this->giveUserPermissions($this->user, ['settings-manage', 'users-manage']);
$this->actingAs($this->user)->get('/')->assertDontSee($usersLink, false);
}
@@ -247,7 +247,7 @@ class RolesTest extends TestCase
'name' => 'my_new_name',
]);
$this->permissions->grantUserRolePermissions($this->user, ['users-manage']);
$this->giveUserPermissions($this->user, ['users-manage']);
$resp = $this->get($userProfileUrl)
->assertOk();
@@ -269,7 +269,7 @@ class RolesTest extends TestCase
{
$this->actingAs($this->user)->get('/settings/roles')->assertRedirect('/');
$this->get('/settings/roles/1')->assertRedirect('/');
$this->permissions->grantUserRolePermissions($this->user, ['user-roles-manage']);
$this->giveUserPermissions($this->user, ['user-roles-manage']);
$this->actingAs($this->user)->get('/settings/roles')->assertOk();
$this->get('/settings/roles/1')
->assertOk()
@@ -279,7 +279,7 @@ class RolesTest extends TestCase
public function test_settings_manage_permission()
{
$this->actingAs($this->user)->get('/settings/features')->assertRedirect('/');
$this->permissions->grantUserRolePermissions($this->user, ['settings-manage']);
$this->giveUserPermissions($this->user, ['settings-manage']);
$this->get('/settings/features')->assertOk();
$resp = $this->post('/settings/features', []);
@@ -295,7 +295,7 @@ class RolesTest extends TestCase
$this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
$this->get($page->getUrl('/permissions'))->assertRedirect('/');
$this->permissions->grantUserRolePermissions($this->user, ['restrictions-manage-all']);
$this->giveUserPermissions($this->user, ['restrictions-manage-all']);
$this->actingAs($this->user)->get($page->getUrl())->assertSee('Permissions');
@@ -325,7 +325,7 @@ class RolesTest extends TestCase
$this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
$this->get($page->getUrl('/permissions'))->assertRedirect('/');
$this->permissions->grantUserRolePermissions($this->user, ['restrictions-manage-own']);
$this->giveUserPermissions($this->user, ['restrictions-manage-own']);
// Check can't restrict other's content
$this->actingAs($this->user)->get($otherUsersPage->getUrl())->assertDontSee('Permissions');
@@ -350,7 +350,7 @@ class RolesTest extends TestCase
$this->withHtml($resp)->assertElementNotContains('.action-buttons', $text);
}
$this->permissions->grantUserRolePermissions($this->user, [$permission]);
$this->giveUserPermissions($this->user, [$permission]);
foreach ($accessUrls as $url) {
$this->actingAs($this->user)->get($url)->assertOk();
@@ -380,7 +380,7 @@ class RolesTest extends TestCase
$otherShelf = Bookshelf::query()->first();
$ownShelf = $this->entities->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
$ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
$this->permissions->regenerateForEntity($ownShelf);
$this->entities->regenPermissions($ownShelf);
$this->checkAccessPermission('bookshelf-update-own', [
$ownShelf->getUrl('/edit'),
@@ -406,12 +406,12 @@ class RolesTest extends TestCase
public function test_bookshelves_delete_own_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['bookshelf-update-all']);
$this->giveUserPermissions($this->user, ['bookshelf-update-all']);
/** @var Bookshelf $otherShelf */
$otherShelf = Bookshelf::query()->first();
$ownShelf = $this->entities->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
$ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
$this->permissions->regenerateForEntity($ownShelf);
$this->entities->regenPermissions($ownShelf);
$this->checkAccessPermission('bookshelf-delete-own', [
$ownShelf->getUrl('/delete'),
@@ -430,7 +430,7 @@ class RolesTest extends TestCase
public function test_bookshelves_delete_all_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['bookshelf-update-all']);
$this->giveUserPermissions($this->user, ['bookshelf-update-all']);
/** @var Bookshelf $otherShelf */
$otherShelf = Bookshelf::query()->first();
$this->checkAccessPermission('bookshelf-delete-all', [
@@ -486,7 +486,7 @@ class RolesTest extends TestCase
public function test_books_delete_own_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['book-update-all']);
$this->giveUserPermissions($this->user, ['book-update-all']);
/** @var Book $otherBook */
$otherBook = Book::query()->take(1)->get()->first();
$ownBook = $this->entities->createChainBelongingToUser($this->user)['book'];
@@ -506,7 +506,7 @@ class RolesTest extends TestCase
public function test_books_delete_all_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['book-update-all']);
$this->giveUserPermissions($this->user, ['book-update-all']);
/** @var Book $otherBook */
$otherBook = Book::query()->take(1)->get()->first();
$this->checkAccessPermission('book-delete-all', [
@@ -585,7 +585,7 @@ class RolesTest extends TestCase
public function test_chapter_delete_own_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['chapter-update-all']);
$this->giveUserPermissions($this->user, ['chapter-update-all']);
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->first();
$ownChapter = $this->entities->createChainBelongingToUser($this->user)['chapter'];
@@ -607,7 +607,7 @@ class RolesTest extends TestCase
public function test_chapter_delete_all_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['chapter-update-all']);
$this->giveUserPermissions($this->user, ['chapter-update-all']);
/** @var Chapter $otherChapter */
$otherChapter = Chapter::query()->first();
$this->checkAccessPermission('chapter-delete-all', [
@@ -645,7 +645,7 @@ class RolesTest extends TestCase
$ownChapter->getUrl() => 'New Page',
]);
$this->permissions->grantUserRolePermissions($this->user, ['page-create-own']);
$this->giveUserPermissions($this->user, ['page-create-own']);
foreach ($accessUrls as $index => $url) {
$resp = $this->actingAs($this->user)->get($url);
@@ -688,7 +688,7 @@ class RolesTest extends TestCase
$chapter->getUrl() => 'New Page',
]);
$this->permissions->grantUserRolePermissions($this->user, ['page-create-all']);
$this->giveUserPermissions($this->user, ['page-create-all']);
foreach ($accessUrls as $index => $url) {
$resp = $this->actingAs($this->user)->get($url);
@@ -742,7 +742,7 @@ class RolesTest extends TestCase
public function test_page_delete_own_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['page-update-all']);
$this->giveUserPermissions($this->user, ['page-update-all']);
/** @var Page $otherPage */
$otherPage = Page::query()->first();
$ownPage = $this->entities->createChainBelongingToUser($this->user)['page'];
@@ -764,7 +764,7 @@ class RolesTest extends TestCase
public function test_page_delete_all_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['page-update-all']);
$this->giveUserPermissions($this->user, ['page-update-all']);
/** @var Page $otherPage */
$otherPage = Page::query()->first();
@@ -823,7 +823,7 @@ class RolesTest extends TestCase
public function test_image_delete_own_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['image-update-all']);
$this->giveUserPermissions($this->user, ['image-update-all']);
$page = $this->entities->page();
$image = Image::factory()->create([
'uploaded_to' => $page->id,
@@ -833,7 +833,7 @@ class RolesTest extends TestCase
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
$this->permissions->grantUserRolePermissions($this->user, ['image-delete-own']);
$this->giveUserPermissions($this->user, ['image-delete-own']);
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
$this->assertDatabaseMissing('images', ['id' => $image->id]);
@@ -841,18 +841,18 @@ class RolesTest extends TestCase
public function test_image_delete_all_permission()
{
$this->permissions->grantUserRolePermissions($this->user, ['image-update-all']);
$admin = $this->users->admin();
$this->giveUserPermissions($this->user, ['image-update-all']);
$admin = $this->getAdmin();
$page = $this->entities->page();
$image = Image::factory()->create(['uploaded_to' => $page->id, 'created_by' => $admin->id, 'updated_by' => $admin->id]);
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
$this->permissions->grantUserRolePermissions($this->user, ['image-delete-own']);
$this->giveUserPermissions($this->user, ['image-delete-own']);
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
$this->permissions->grantUserRolePermissions($this->user, ['image-delete-all']);
$this->giveUserPermissions($this->user, ['image-delete-all']);
$this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
$this->assertDatabaseMissing('images', ['id' => $image->id]);
@@ -863,7 +863,7 @@ class RolesTest extends TestCase
// To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a.
$page = $this->entities->page();
$viewerRole = Role::getRole('viewer');
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$this->actingAs($viewer)->get($page->getUrl())->assertOk();
$this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [
@@ -877,18 +877,18 @@ class RolesTest extends TestCase
public function test_empty_state_actions_not_visible_without_permission()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
// Book links
$book = Book::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id]);
$this->permissions->regenerateForEntity($book);
$this->actingAs($this->users->viewer())->get($book->getUrl())
$this->entities->regenPermissions($book);
$this->actingAs($this->getViewer())->get($book->getUrl())
->assertDontSee('Create a new page')
->assertDontSee('Add a chapter');
// Chapter links
$chapter = Chapter::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]);
$this->permissions->regenerateForEntity($chapter);
$this->actingAs($this->users->viewer())->get($chapter->getUrl())
$this->entities->regenPermissions($chapter);
$this->actingAs($this->getViewer())->get($chapter->getUrl())
->assertDontSee('Create a new page')
->assertDontSee('Sort the current book');
}
@@ -901,7 +901,7 @@ class RolesTest extends TestCase
->addComment($ownPage)
->assertStatus(403);
$this->permissions->grantUserRolePermissions($this->user, ['comment-create-all']);
$this->giveUserPermissions($this->user, ['comment-create-all']);
$this->actingAs($this->user)
->addComment($ownPage)
@@ -911,7 +911,7 @@ class RolesTest extends TestCase
public function test_comment_update_own_permission()
{
$ownPage = $this->entities->createChainBelongingToUser($this->user)['page'];
$this->permissions->grantUserRolePermissions($this->user, ['comment-create-all']);
$this->giveUserPermissions($this->user, ['comment-create-all']);
$this->actingAs($this->user)->addComment($ownPage);
/** @var Comment $comment */
$comment = $ownPage->comments()->latest()->first();
@@ -919,7 +919,7 @@ class RolesTest extends TestCase
// no comment-update-own
$this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
$this->permissions->grantUserRolePermissions($this->user, ['comment-update-own']);
$this->giveUserPermissions($this->user, ['comment-update-own']);
// now has comment-update-own
$this->actingAs($this->user)->updateComment($comment)->assertOk();
@@ -936,7 +936,7 @@ class RolesTest extends TestCase
// no comment-update-all
$this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
$this->permissions->grantUserRolePermissions($this->user, ['comment-update-all']);
$this->giveUserPermissions($this->user, ['comment-update-all']);
// now has comment-update-all
$this->actingAs($this->user)->updateComment($comment)->assertOk();
@@ -946,7 +946,7 @@ class RolesTest extends TestCase
{
/** @var Page $ownPage */
$ownPage = $this->entities->createChainBelongingToUser($this->user)['page'];
$this->permissions->grantUserRolePermissions($this->user, ['comment-create-all']);
$this->giveUserPermissions($this->user, ['comment-create-all']);
$this->actingAs($this->user)->addComment($ownPage);
/** @var Comment $comment */
@@ -955,7 +955,7 @@ class RolesTest extends TestCase
// no comment-delete-own
$this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
$this->permissions->grantUserRolePermissions($this->user, ['comment-delete-own']);
$this->giveUserPermissions($this->user, ['comment-delete-own']);
// now has comment-update-own
$this->actingAs($this->user)->deleteComment($comment)->assertOk();
@@ -972,7 +972,7 @@ class RolesTest extends TestCase
// no comment-delete-all
$this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
$this->permissions->grantUserRolePermissions($this->user, ['comment-delete-all']);
$this->giveUserPermissions($this->user, ['comment-delete-all']);
// now has comment-delete-all
$this->actingAs($this->user)->deleteComment($comment)->assertOk();

View File

@@ -1,201 +0,0 @@
<?php
namespace Tests\Permissions\Scenarios;
class EntityRolePermissionsTest extends PermissionScenarioTestCase
{
public function test_01_explicit_allow()
{
[$user, $role] = $this->users->newUserWithRole();
$page = $this->entities->page();
$this->permissions->setEntityPermissions($page, ['view'], [$role], false);
$this->assertVisibleToUser($page, $user);
}
public function test_02_explicit_deny()
{
[$user, $role] = $this->users->newUserWithRole();
$page = $this->entities->page();
$this->permissions->setEntityPermissions($page, [], [$role], false);
$this->assertNotVisibleToUser($page, $user);
}
public function test_03_same_level_conflicting()
{
[$user, $roleA] = $this->users->newUserWithRole();
$roleB = $this->users->attachNewRole($user);
$page = $this->entities->page();
$this->permissions->disableEntityInheritedPermissions($page);
$this->permissions->addEntityPermission($page, [], $roleA);
$this->permissions->addEntityPermission($page, ['view'], $roleB);
$this->assertVisibleToUser($page, $user);
}
public function test_20_inherit_allow()
{
[$user, $roleA] = $this->users->newUserWithRole();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, ['view'], $roleA);
$this->assertVisibleToUser($page, $user);
}
public function test_21_inherit_deny()
{
[$user, $roleA] = $this->users->newUserWithRole();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, [], $roleA);
$this->assertNotVisibleToUser($page, $user);
}
public function test_22_same_level_conflict_inherit()
{
[$user, $roleA] = $this->users->newUserWithRole();
$roleB = $this->users->attachNewRole($user);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, [], $roleA);
$this->permissions->addEntityPermission($chapter, ['view'], $roleB);
$this->assertVisibleToUser($page, $user);
}
public function test_30_child_inherit_override_allow()
{
[$user, $roleA] = $this->users->newUserWithRole();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, [], $roleA);
$this->permissions->addEntityPermission($page, ['view'], $roleA);
$this->assertVisibleToUser($page, $user);
}
public function test_31_child_inherit_override_deny()
{
[$user, $roleA] = $this->users->newUserWithRole();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, ['view'], $roleA);
$this->permissions->addEntityPermission($page, [], $roleA);
$this->assertNotVisibleToUser($page, $user);
}
public function test_40_multi_role_inherit_conflict_override_deny()
{
[$user, $roleA] = $this->users->newUserWithRole();
$roleB = $this->users->attachNewRole($user);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($page, [], $roleA);
$this->permissions->addEntityPermission($chapter, ['view'], $roleB);
$this->assertVisibleToUser($page, $user);
}
public function test_41_multi_role_inherit_conflict_retain_allow()
{
[$user, $roleA] = $this->users->newUserWithRole();
$roleB = $this->users->attachNewRole($user);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($page, ['view'], $roleA);
$this->permissions->addEntityPermission($chapter, [], $roleB);
$this->assertVisibleToUser($page, $user);
}
public function test_50_role_override_allow()
{
[$user, $roleA] = $this->users->newUserWithRole();
$page = $this->entities->page();
$this->permissions->addEntityPermission($page, ['view'], $roleA);
$this->assertVisibleToUser($page, $user);
}
public function test_51_role_override_deny()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
$page = $this->entities->page();
$this->permissions->addEntityPermission($page, [], $roleA);
$this->assertNotVisibleToUser($page, $user);
}
public function test_60_inherited_role_override_allow()
{
[$user, $roleA] = $this->users->newUserWithRole([], []);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->addEntityPermission($chapter, ['view'], $roleA);
$this->assertVisibleToUser($page, $user);
}
public function test_61_inherited_role_override_deny()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->addEntityPermission($chapter, [], $roleA);
$this->assertNotVisibleToUser($page, $user);
}
public function test_62_inherited_role_override_deny_on_own()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-own']);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->addEntityPermission($chapter, [], $roleA);
$this->permissions->changeEntityOwner($page, $user);
$this->assertNotVisibleToUser($page, $user);
}
public function test_70_multi_role_inheriting_deny()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
$roleB = $this->users->attachNewRole($user);
$page = $this->entities->page();
$this->permissions->addEntityPermission($page, [], $roleB);
$this->assertNotVisibleToUser($page, $user);
}
public function test_80_multi_role_inherited_deny_via_parent()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
$roleB = $this->users->attachNewRole($user);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->addEntityPermission($chapter, [], $roleB);
$this->assertNotVisibleToUser($page, $user);
}
}

View File

@@ -1,209 +0,0 @@
<?php
namespace Tests\Permissions\Scenarios;
class EntityUserPermissionsTest extends PermissionScenarioTestCase
{
public function test_01_explicit_allow()
{
$user = $this->users->newUser();
$page = $this->entities->page();
$this->permissions->disableEntityInheritedPermissions($page);
$this->permissions->addEntityPermission($page, ['view'], null, $user);
$this->assertVisibleToUser($page, $user);
}
public function test_02_explicit_deny()
{
$user = $this->users->newUser();
$page = $this->entities->page();
$this->permissions->disableEntityInheritedPermissions($page);
$this->permissions->addEntityPermission($page, [], null, $user);
$this->assertNotVisibleToUser($page, $user);
}
public function test_10_allow_inherit()
{
$user = $this->users->newUser();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, ['view'], null, $user);
$this->assertVisibleToUser($page, $user);
}
public function test_11_deny_inherit()
{
$user = $this->users->newUser();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, [], null, $user);
$this->assertNotVisibleToUser($page, $user);
}
public function test_12_allow_inherit_override()
{
$user = $this->users->newUser();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, [], null, $user);
$this->permissions->addEntityPermission($page, ['view'], null, $user);
$this->assertVisibleToUser($page, $user);
}
public function test_13_deny_inherit_override()
{
$user = $this->users->newUser();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, ['view'], null, $user);
$this->permissions->addEntityPermission($page, ['deny'], null, $user);
$this->assertNotVisibleToUser($page, $user);
}
public function test_40_entity_role_override_allow()
{
[$user, $role] = $this->users->newUserWithRole();
$page = $this->entities->page();
$this->permissions->disableEntityInheritedPermissions($page);
$this->permissions->addEntityPermission($page, ['view'], null, $user);
$this->permissions->addEntityPermission($page, [], $role);
$this->assertVisibleToUser($page, $user);
}
public function test_41_entity_role_override_deny()
{
[$user, $role] = $this->users->newUserWithRole();
$page = $this->entities->page();
$this->permissions->disableEntityInheritedPermissions($page);
$this->permissions->addEntityPermission($page, [], null, $user);
$this->permissions->addEntityPermission($page, ['view'], $role);
$this->assertNotVisibleToUser($page, $user);
}
public function test_42_entity_role_override_allow_via_inherit()
{
[$user, $role] = $this->users->newUserWithRole();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, ['view'], null, $user);
$this->permissions->addEntityPermission($page, [], $role);
$this->assertVisibleToUser($page, $user);
}
public function test_43_entity_role_override_deny_via_inherit()
{
[$user, $role] = $this->users->newUserWithRole();
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->disableEntityInheritedPermissions($chapter);
$this->permissions->addEntityPermission($chapter, [], null, $user);
$this->permissions->addEntityPermission($page, ['view'], $role);
$this->assertNotVisibleToUser($page, $user);
}
public function test_50_role_override_allow()
{
[$user, $roleA] = $this->users->newUserWithRole();
$page = $this->entities->page();
$this->permissions->addEntityPermission($page, ['view'], null, $user);
$this->assertVisibleToUser($page, $user);
}
public function test_51_role_override_deny()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
$page = $this->entities->page();
$this->permissions->addEntityPermission($page, [], null, $user);
$this->assertNotVisibleToUser($page, $user);
}
public function test_60_inherited_role_override_allow()
{
[$user, $roleA] = $this->users->newUserWithRole([], []);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->addEntityPermission($chapter, ['view'], null, $user);
$this->assertVisibleToUser($page, $user);
}
public function test_61_inherited_role_override_deny()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->addEntityPermission($chapter, [], null, $user);
$this->assertNotVisibleToUser($page, $user);
}
public function test_61_inherited_role_override_deny_on_own()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-own']);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->addEntityPermission($chapter, [], null, $user);
$this->permissions->changeEntityOwner($page, $user);
$this->assertNotVisibleToUser($page, $user);
}
public function test_70_all_override_allow()
{
[$user, $roleA] = $this->users->newUserWithRole([], []);
$page = $this->entities->page();
$this->permissions->addEntityPermission($page, [], $roleA, null);
$this->permissions->addEntityPermission($page, ['view'], null, $user);
$this->assertVisibleToUser($page, $user);
}
public function test_71_all_override_deny()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
$page = $this->entities->page();
$this->permissions->addEntityPermission($page, ['view'], $roleA, null);
$this->permissions->addEntityPermission($page, [], null, $user);
$this->assertNotVisibleToUser($page, $user);
}
public function test_80_inherited_all_override_allow()
{
[$user, $roleA] = $this->users->newUserWithRole([], []);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->addEntityPermission($chapter, [], $roleA, null);
$this->permissions->addEntityPermission($chapter, ['view'], null, $user);
$this->assertVisibleToUser($page, $user);
}
public function test_81_inherited_all_override_deny()
{
[$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']);
$page = $this->entities->pageWithinChapter();
$chapter = $page->chapter;
$this->permissions->addEntityPermission($chapter, ['view'], $roleA, null);
$this->permissions->addEntityPermission($chapter, [], null, $user);
$this->assertNotVisibleToUser($page, $user);
}
}

View File

@@ -1,38 +0,0 @@
<?php
namespace Tests\Permissions\Scenarios;
use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
use Tests\TestCase;
// Cases defined in dev/docs/permission-scenario-testing.md
class PermissionScenarioTestCase extends TestCase
{
protected function assertVisibleToUser(Entity $entity, User $user)
{
$this->actingAs($user);
$funcView = userCan($entity->getMorphClass() . '-view', $entity);
$queryView = $entity->newQuery()->scopes(['visible'])->find($entity->id) !== null;
$id = $entity->getMorphClass() . ':' . $entity->id;
$msg = "Item [{$id}] should be visible but was not found via ";
$msg .= implode(' and ', array_filter([!$funcView ? 'userCan' : '', !$queryView ? 'query' : '']));
static::assertTrue($funcView && $queryView, $msg);
}
protected function assertNotVisibleToUser(Entity $entity, User $user)
{
$this->actingAs($user);
$funcView = userCan($entity->getMorphClass() . '-view', $entity);
$queryView = $entity->newQuery()->scopes(['visible'])->find($entity->id) !== null;
$id = $entity->getMorphClass() . ':' . $entity->id;
$msg = "Item [{$id}] should not be visible but was found via ";
$msg .= implode(' and ', array_filter([$funcView ? 'userCan' : '', $queryView ? 'query' : '']));
static::assertTrue(!$funcView && !$queryView, $msg);
}
}

View File

@@ -1,59 +0,0 @@
<?php
namespace Tests\Permissions\Scenarios;
class RoleContentPermissionsTest extends PermissionScenarioTestCase
{
public function test_01_allow()
{
[$user] = $this->users->newUserWithRole([], ['page-view-all']);
$page = $this->entities->page();
$this->assertVisibleToUser($page, $user);
}
public function test_02_deny()
{
[$user] = $this->users->newUserWithRole([], []);
$page = $this->entities->page();
$this->assertNotVisibleToUser($page, $user);
}
public function test_10_allow_on_own_with_own()
{
[$user] = $this->users->newUserWithRole([], ['page-view-own']);
$page = $this->entities->page();
$this->permissions->changeEntityOwner($page, $user);
$this->assertVisibleToUser($page, $user);
}
public function test_11_deny_on_other_with_own()
{
[$user] = $this->users->newUserWithRole([], ['page-view-own']);
$page = $this->entities->page();
$this->permissions->changeEntityOwner($page, $this->users->editor());
$this->assertNotVisibleToUser($page, $user);
}
public function test_20_multiple_role_conflicting_all()
{
[$user] = $this->users->newUserWithRole([], ['page-view-all']);
$this->users->attachNewRole($user, []);
$page = $this->entities->page();
$this->assertVisibleToUser($page, $user);
}
public function test_21_multiple_role_conflicting_own()
{
[$user] = $this->users->newUserWithRole([], ['page-view-own']);
$this->users->attachNewRole($user, []);
$page = $this->entities->page();
$this->permissions->changeEntityOwner($page, $user);
$this->assertVisibleToUser($page, $user);
}
}

View File

@@ -2,6 +2,7 @@
namespace Tests;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Auth\Role;
use BookStack\Auth\User;
@@ -88,6 +89,7 @@ class PublicActionTest extends TestCase
foreach (RolePermission::all() as $perm) {
$publicRole->attachPermission($perm);
}
$this->app->make(JointPermissionBuilder::class)->rebuildForRole($publicRole);
user()->clearPermissionCache();
$chapter = $this->entities->chapter();
@@ -171,7 +173,7 @@ class PublicActionTest extends TestCase
{
$this->setSettings(['app-public' => 'true']);
$book = $this->entities->book();
$this->permissions->setEntityPermissions($book);
$this->entities->setPermissions($book);
$resp = $this->get($book->getUrl());
$resp->assertSee('Book not found');

View File

@@ -91,7 +91,7 @@ class ReferencesTest extends TestCase
$pageB = $this->entities->page();
$this->createReference($pageB, $page);
$this->permissions->setEntityPermissions($pageB);
$this->entities->setPermissions($pageB);
$this->asEditor()->get($page->getUrl('/references'))->assertDontSee($pageB->name);
$this->asAdmin()->get($page->getUrl('/references'))->assertSee($pageB->name);

View File

@@ -14,7 +14,7 @@ class RecycleBinTest extends TestCase
public function test_recycle_bin_routes_permissions()
{
$page = $this->entities->page();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor)->delete($page->getUrl());
$deletion = Deletion::query()->firstOrFail();
@@ -33,7 +33,7 @@ class RecycleBinTest extends TestCase
$this->assertPermissionError($resp);
}
$this->permissions->grantUserRolePermissions($editor, ['restrictions-manage-all']);
$this->giveUserPermissions($editor, ['restrictions-manage-all']);
foreach ($routes as $route) {
[$method, $url] = explode(':', $route);
@@ -41,7 +41,7 @@ class RecycleBinTest extends TestCase
$this->assertPermissionError($resp);
}
$this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
$this->giveUserPermissions($editor, ['settings-manage']);
foreach ($routes as $route) {
DB::beginTransaction();
@@ -56,7 +56,7 @@ class RecycleBinTest extends TestCase
{
$page = $this->entities->page();
$book = Book::query()->whereHas('pages')->whereHas('chapters')->withCount(['pages', 'chapters'])->first();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor)->delete($page->getUrl());
$this->actingAs($editor)->delete($book->getUrl());
@@ -73,7 +73,7 @@ class RecycleBinTest extends TestCase
{
$page = $this->entities->page();
$book = Book::query()->where('id', '!=', $page->book_id)->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor)->delete($page->getUrl());
$this->actingAs($editor)->delete($book->getUrl());

View File

@@ -32,11 +32,11 @@ class RegenerateReferencesTest extends TestCase
public function test_settings_manage_permission_required()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$resp = $this->actingAs($editor)->post('/settings/maintenance/regenerate-references');
$this->assertPermissionError($resp);
$this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
$this->giveUserPermissions($editor, ['settings-manage']);
$resp = $this->actingAs($editor)->post('/settings/maintenance/regenerate-references');
$this->assertNotPermissionError($resp);

View File

@@ -20,7 +20,7 @@ class TestEmailTest extends TestCase
public function test_send_test_email_endpoint_sends_email_and_redirects_user_and_shows_notification()
{
Notification::fake();
$admin = $this->users->admin();
$admin = $this->getAdmin();
$sendReq = $this->actingAs($admin)->post('/settings/maintenance/send-test-email');
$sendReq->assertRedirect('/settings/maintenance#image-cleanup');
@@ -37,7 +37,7 @@ class TestEmailTest extends TestCase
$exception = new \Exception('A random error occurred when testing an email');
$mockDispatcher->shouldReceive('sendNow')->andThrow($exception);
$admin = $this->users->admin();
$admin = $this->getAdmin();
$sendReq = $this->actingAs($admin)->post('/settings/maintenance/send-test-email');
$sendReq->assertRedirect('/settings/maintenance#image-cleanup');
$this->assertSessionHas('error');
@@ -50,12 +50,12 @@ class TestEmailTest extends TestCase
public function test_send_test_email_requires_settings_manage_permission()
{
Notification::fake();
$user = $this->users->viewer();
$user = $this->getViewer();
$sendReq = $this->actingAs($user)->post('/settings/maintenance/send-test-email');
Notification::assertNothingSent();
$this->permissions->grantUserRolePermissions($user, ['settings-manage']);
$this->giveUserPermissions($user, ['settings-manage']);
$sendReq = $this->actingAs($user)->post('/settings/maintenance/send-test-email');
Notification::assertSentTo($user, TestEmail::class);
}

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