mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-07 19:06:05 +03:00
Compare commits
24 Commits
v23.06.2
...
user_permi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3083979855 | ||
|
|
55642a33ee | ||
|
|
93ba572369 | ||
|
|
a825f27930 | ||
|
|
932e1d7c61 | ||
|
|
2f1491c5a4 | ||
|
|
026e9030b9 | ||
|
|
451e4ac452 | ||
|
|
7330139555 | ||
|
|
39acbeac68 | ||
|
|
2d9d2bba80 | ||
|
|
adabf06dbe | ||
|
|
5ffc10e688 | ||
|
|
6a6f5e4d19 | ||
|
|
491beee93e | ||
|
|
f844ae0902 | ||
|
|
d54ea1b3ed | ||
|
|
e8a8fedfd6 | ||
|
|
60bf838a4a | ||
|
|
0411185fbb | ||
|
|
93cbd3b8aa | ||
|
|
7a269e7689 | ||
|
|
f8c4725166 | ||
|
|
1c53ffc4d1 |
@@ -37,10 +37,8 @@ MAIL_FROM=bookstack@example.com
|
||||
# SMTP mail options
|
||||
# These settings can be checked using the "Send a Test Email"
|
||||
# feature found in the "Settings > Maintenance" area of the system.
|
||||
# For more detailed documentation on mail options, refer to:
|
||||
# https://www.bookstackapp.com/docs/admin/email-webhooks/#email-configuration
|
||||
MAIL_HOST=localhost
|
||||
MAIL_PORT=587
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
# Each option is shown with it's default value.
|
||||
# Do not copy this whole file to use as your '.env' file.
|
||||
|
||||
# The details here only serve as a quick reference.
|
||||
# Please refer to the BookStack documentation for full details:
|
||||
# https://www.bookstackapp.com/docs/
|
||||
|
||||
# Application environment
|
||||
# Can be 'production', 'development', 'testing' or 'demo'
|
||||
APP_ENV=production
|
||||
@@ -69,20 +65,20 @@ DB_PASSWORD=database_user_password
|
||||
# certificate itself (Common Name or Subject Alternative Name).
|
||||
MYSQL_ATTR_SSL_CA="/path/to/ca.pem"
|
||||
|
||||
# Mail configuration
|
||||
# Refer to https://www.bookstackapp.com/docs/admin/email-webhooks/#email-configuration
|
||||
# Mail system to use
|
||||
# Can be 'smtp' or 'sendmail'
|
||||
MAIL_DRIVER=smtp
|
||||
|
||||
# Mail sending options
|
||||
MAIL_FROM=mail@bookstackapp.com
|
||||
MAIL_FROM_NAME=BookStack
|
||||
|
||||
# SMTP mail options
|
||||
MAIL_HOST=localhost
|
||||
MAIL_PORT=587
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_VERIFY_SSL=true
|
||||
|
||||
MAIL_SENDMAIL_COMMAND="/usr/sbin/sendmail -bs"
|
||||
|
||||
# Cache & Session driver to use
|
||||
# Can be 'file', 'database', 'memcached' or 'redis'
|
||||
@@ -272,7 +268,6 @@ OIDC_DUMP_USER_DETAILS=false
|
||||
OIDC_USER_TO_GROUPS=false
|
||||
OIDC_GROUPS_CLAIM=groups
|
||||
OIDC_REMOVE_FROM_GROUPS=false
|
||||
OIDC_EXTERNAL_ID_CLAIM=sub
|
||||
|
||||
# Disable default third-party services such as Gravatar and Draw.IO
|
||||
# Service-specific options will override this option
|
||||
@@ -323,13 +318,6 @@ FILE_UPLOAD_SIZE_LIMIT=50
|
||||
# Can be 'a4' or 'letter'.
|
||||
EXPORT_PAGE_SIZE=a4
|
||||
|
||||
# Set path to wkhtmltopdf binary for PDF generation.
|
||||
# Can be 'false' or a path path like: '/home/bins/wkhtmltopdf'
|
||||
# When false, BookStack will attempt to find a wkhtmltopdf in the application
|
||||
# root folder then fall back to the default dompdf renderer if no binary exists.
|
||||
# Only used if 'ALLOW_UNTRUSTED_SERVER_FETCHING=true' which disables security protections.
|
||||
WKHTMLTOPDF=false
|
||||
|
||||
# Allow <script> tags in page content
|
||||
# Note, if set to 'true' the page editor may still escape scripts.
|
||||
ALLOW_CONTENT_SCRIPTS=false
|
||||
@@ -380,4 +368,4 @@ LOG_FAILED_LOGIN_CHANNEL=errorlog_plain_webserver
|
||||
# IP address '146.191.42.4' would result in '146.191.x.x' being logged.
|
||||
# For the IPv6 address '2001:db8:85a3:8d3:1319:8a2e:370:7348' this would result as:
|
||||
# '2001:db8:85a3:8d3:x:x:x:x'
|
||||
IP_ADDRESS_PRECISION=4
|
||||
IP_ADDRESS_PRECISION=4
|
||||
52
.github/translators.txt
vendored
52
.github/translators.txt
vendored
@@ -176,7 +176,7 @@ Alexander Predl (Harveyhase68) :: German
|
||||
Rem (Rem9000) :: Dutch
|
||||
Michał Stelmach (stelmach-web) :: Polish
|
||||
arniom :: French
|
||||
REMOVED_USER :: ; French; Dutch; Turkish
|
||||
REMOVED_USER :: ; Dutch; Turkish
|
||||
林祖年 (contagion) :: Chinese Traditional
|
||||
Siamak Guodarzi (siamakgoudarzi88) :: Persian
|
||||
Lis Maestrelo (lismtrl) :: Portuguese, Brazilian
|
||||
@@ -283,13 +283,13 @@ Kuchinashi Hoshikawa (kuchinashi) :: Chinese Simplified
|
||||
digilady :: Greek
|
||||
Linus (LinusOP) :: Swedish
|
||||
Felipe Cardoso (felipecardosoruff) :: Portuguese, Brazilian
|
||||
RandomUser0815 :: German Informal; German
|
||||
RandomUser0815 :: German
|
||||
Ismael Mesquita (mesquitoliveira) :: Portuguese, Brazilian
|
||||
구인회 (laskdjlaskdj12) :: Korean
|
||||
LiZerui (CNLiZerui) :: Chinese Traditional
|
||||
Fabrice Boyer (FabriceBoyer) :: French
|
||||
mikael (bitcanon) :: Swedish
|
||||
Matthias Mai (schnapsidee) :: German; German Informal
|
||||
Matthias Mai (schnapsidee) :: German
|
||||
Ufuk Ayyıldız (ufukayyildiz) :: Turkish
|
||||
Jan Mitrof (jan.kachlik) :: Czech
|
||||
edwardsmirnov :: Russian
|
||||
@@ -298,49 +298,3 @@ shotu :: French
|
||||
Cesar_Lopez_Aguillon :: Spanish
|
||||
bdewoop :: German
|
||||
dina davoudi (dina.davoudi) :: Persian
|
||||
Angelos Chouvardas (achouvardas) :: Greek
|
||||
rndrss :: Portuguese, Brazilian
|
||||
rirac294 :: Russian
|
||||
David Furman (thefourCraft) :: Hebrew
|
||||
Pafzedog :: French
|
||||
Yllelder :: Spanish
|
||||
Adrian Ocneanu (aocneanu) :: Romanian
|
||||
Eduardo Castanho (EduardoCastanho) :: Portuguese
|
||||
VIET NAM VPS (vietnamvps) :: Vietnamese
|
||||
m4tthi4s :: French
|
||||
toras9000 :: Japanese
|
||||
pathab :: German
|
||||
MichelSchoon85 :: Dutch
|
||||
Jøran Haugli (haugli92) :: Norwegian Bokmal
|
||||
Vasileios Kouvelis (VasilisKouvelis) :: Greek
|
||||
Dremski :: Bulgarian
|
||||
Frédéric SENE (nothingfr) :: French
|
||||
bendem :: French
|
||||
kostasdizas :: Greek
|
||||
Ricardo Schroeder (brownstone666) :: Portuguese, Brazilian
|
||||
Eitan MG (EitanMG) :: Hebrew
|
||||
Robin Flikkema (RobinFlikkema) :: Dutch
|
||||
Michal Gurcik (mgurcik) :: Slovak
|
||||
Pooyan Arab (pooyanarab) :: Persian
|
||||
Ochi Darma Putra (troke12) :: Indonesian
|
||||
H.-H. Peng (Hsins) :: Chinese Traditional
|
||||
Mosi Wang (mosiwang) :: Chinese Traditional
|
||||
骆言 (LawssssCat) :: Chinese Simplified
|
||||
Stickers Gaming Shøw (StickerSGSHOW) :: French
|
||||
Le Van Chinh (Chino) (lvanchinh86) :: Vietnamese
|
||||
Rubens nagios (rubenix) :: Catalan
|
||||
Patrick Dantas (pa-tiq) :: Portuguese, Brazilian
|
||||
Michal (michalgurcik) :: Slovak
|
||||
Nepomacs :: German
|
||||
Rubens (rubenix) :: Catalan
|
||||
m4z :: German; German Informal
|
||||
TheRazvy :: Romanian
|
||||
Yossi Zilber (lortens) :: Hebrew; Uzbek
|
||||
desdinova :: French
|
||||
Ingus Rūķis (ingus.rukis) :: Latvian
|
||||
Eugene Pershin (SilentEugene) :: Russian
|
||||
周盛道 (zhoushengdao) :: Chinese Simplified
|
||||
hamidreza amini (hamidrezaamini2022) :: Persian
|
||||
Tomislav Kraljević (tomislav.kraljevic) :: Croatian
|
||||
Taygun Yıldırım (yildirimtaygun) :: Turkish
|
||||
robing29 :: German
|
||||
|
||||
16
.github/workflows/lint-js.yml
vendored
16
.github/workflows/lint-js.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: lint-js
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.ref != 'refs/heads/l10n_development' }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Install NPM deps
|
||||
run: npm ci
|
||||
|
||||
- name: Run formatting check
|
||||
run: npm run lint
|
||||
2
.github/workflows/test-migrations.yml
vendored
2
.github/workflows/test-migrations.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['8.0', '8.1', '8.2']
|
||||
php: ['7.4', '8.0', '8.1', '8.2']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
|
||||
4
.github/workflows/test-php.yml
vendored
4
.github/workflows/test-php.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['8.0', '8.1', '8.2']
|
||||
php: ['7.4', '8.0', '8.1', '8.2']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: gd, mbstring, json, curl, xml, mysql, ldap, gmp
|
||||
extensions: gd, mbstring, json, curl, xml, mysql, ldap
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,19 +1,16 @@
|
||||
/vendor
|
||||
/node_modules
|
||||
/.vscode
|
||||
/composer
|
||||
Homestead.yaml
|
||||
.env
|
||||
.idea
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/public/dist/*.map
|
||||
/public/dist
|
||||
/public/plugins
|
||||
/public/css/*.map
|
||||
/public/js/*.map
|
||||
/public/css
|
||||
/public/js
|
||||
/public/bower
|
||||
/public/build/
|
||||
/public/favicon.ico
|
||||
/storage/images
|
||||
_ide_helper.php
|
||||
/storage/debugbar
|
||||
@@ -23,10 +20,8 @@ yarn.lock
|
||||
nbproject
|
||||
.buildpath
|
||||
.project
|
||||
.nvmrc
|
||||
.settings/
|
||||
webpack-stats.json
|
||||
.phpunit.result.cache
|
||||
.DS_Store
|
||||
phpstan.neon
|
||||
esbuild-meta.json
|
||||
phpstan.neon
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2023, Dan Brown and the BookStack Project contributors.
|
||||
Copyright (c) 2015-2022, Dan Brown and the BookStack Project contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
|
||||
/**
|
||||
* Class Ldap
|
||||
* An object-orientated thin abstraction wrapper for common PHP LDAP functions.
|
||||
* Allows the standard LDAP functions to be mocked for testing.
|
||||
*/
|
||||
class Ldap
|
||||
{
|
||||
/**
|
||||
* Connect to an LDAP server.
|
||||
*
|
||||
* @return resource|\LDAP\Connection|false
|
||||
*/
|
||||
public function connect(string $hostName)
|
||||
{
|
||||
return ldap_connect($hostName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an LDAP option for the given connection.
|
||||
*
|
||||
* @param resource|\LDAP\Connection|null $ldapConnection
|
||||
*/
|
||||
public function setOption($ldapConnection, int $option, mixed $value): bool
|
||||
{
|
||||
return ldap_set_option($ldapConnection, $option, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start TLS on the given LDAP connection.
|
||||
*/
|
||||
public function startTls($ldapConnection): bool
|
||||
{
|
||||
return ldap_start_tls($ldapConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the version number for the given LDAP connection.
|
||||
*
|
||||
* @param resource|\LDAP\Connection $ldapConnection
|
||||
*/
|
||||
public function setVersion($ldapConnection, int $version): bool
|
||||
{
|
||||
return $this->setOption($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search LDAP tree using the provided filter.
|
||||
*
|
||||
* @param resource|\LDAP\Connection $ldapConnection
|
||||
*
|
||||
* @return resource|\LDAP\Result
|
||||
*/
|
||||
public function search($ldapConnection, string $baseDn, string $filter, array $attributes = null)
|
||||
{
|
||||
return ldap_search($ldapConnection, $baseDn, $filter, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entries from an LDAP search result.
|
||||
*
|
||||
* @param resource|\LDAP\Connection $ldapConnection
|
||||
* @param resource|\LDAP\Result $ldapSearchResult
|
||||
*/
|
||||
public function getEntries($ldapConnection, $ldapSearchResult): array|false
|
||||
{
|
||||
return ldap_get_entries($ldapConnection, $ldapSearchResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and get entries immediately.
|
||||
*
|
||||
* @param resource|\LDAP\Connection $ldapConnection
|
||||
*/
|
||||
public function searchAndGetEntries($ldapConnection, string $baseDn, string $filter, array $attributes = null): array|false
|
||||
{
|
||||
$search = $this->search($ldapConnection, $baseDn, $filter, $attributes);
|
||||
|
||||
return $this->getEntries($ldapConnection, $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to LDAP directory.
|
||||
*
|
||||
* @param resource|\LDAP\Connection $ldapConnection
|
||||
*/
|
||||
public function bind($ldapConnection, string $bindRdn = null, string $bindPassword = null): bool
|
||||
{
|
||||
return ldap_bind($ldapConnection, $bindRdn, $bindPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explode an LDAP dn string into an array of components.
|
||||
*/
|
||||
public function explodeDn(string $dn, int $withAttrib): array|false
|
||||
{
|
||||
return ldap_explode_dn($dn, $withAttrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a string for use in an LDAP filter.
|
||||
*/
|
||||
public function escape(string $value, string $ignore = '', int $flags = 0): string
|
||||
{
|
||||
return ldap_escape($value, $ignore, $flags);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Models;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Permissions\Models\JointPermission;
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -19,8 +17,6 @@ use Illuminate\Support\Str;
|
||||
* @property string $entity_type
|
||||
* @property int $entity_id
|
||||
* @property int $user_id
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class Activity extends Model
|
||||
{
|
||||
@@ -44,12 +40,6 @@ class Activity extends Model
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function jointPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(JointPermission::class, 'entity_id', 'entity_id')
|
||||
->whereColumn('activities.entity_type', '=', 'joint_permissions.entity_type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns text from the language files, Looks up by using the activity key.
|
||||
*/
|
||||
@@ -1,13 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Tools;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Activity\DispatchWebhookJob;
|
||||
use BookStack\Activity\Models\Activity;
|
||||
use BookStack\Activity\Models\Loggable;
|
||||
use BookStack\Activity\Models\Webhook;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -1,14 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Activity\Models\Activity;
|
||||
use BookStack\Auth\Permissions\PermissionApplicator;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
@@ -16,6 +15,8 @@ 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;
|
||||
@@ -26,9 +27,11 @@ class ActivityQueries
|
||||
*/
|
||||
public function latest(int $count = 20, int $page = 0): array
|
||||
{
|
||||
$query = Activity::query()->select($this->fieldsForLists);
|
||||
$activityList = $this->permissions
|
||||
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
|
||||
->restrictEntityRelationQuery($query, 'activities', 'entity_id', 'entity_type')
|
||||
->orderBy('created_at', 'desc')
|
||||
->whereNotNull('activities.entity_id')
|
||||
->with(['user', 'entity'])
|
||||
->skip($count * $page)
|
||||
->take($count)
|
||||
@@ -79,10 +82,12 @@ class ActivityQueries
|
||||
*/
|
||||
public function userActivity(User $user, int $count = 20, int $page = 0): array
|
||||
{
|
||||
$query = Activity::query()->select($this->fieldsForLists);
|
||||
$activityList = $this->permissions
|
||||
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
|
||||
->restrictEntityRelationQuery($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();
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
class ActivityType
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Models;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Users\Models\HasCreatorAndUpdater;
|
||||
use BookStack\Model;
|
||||
use BookStack\Traits\HasCreatorAndUpdater;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Activity\Models\Comment;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Facades\Activity as ActivityService;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
|
||||
/**
|
||||
* Class CommentRepo.
|
||||
*/
|
||||
class CommentRepo
|
||||
{
|
||||
/**
|
||||
* @var Comment
|
||||
*/
|
||||
protected $comment;
|
||||
|
||||
public function __construct(Comment $comment)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a comment by ID.
|
||||
*/
|
||||
public function getById(int $id): Comment
|
||||
{
|
||||
return Comment::query()->findOrFail($id);
|
||||
return $this->comment->newQuery()->findOrFail($id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +35,7 @@ class CommentRepo
|
||||
public function create(Entity $entity, string $text, ?int $parent_id): Comment
|
||||
{
|
||||
$userId = user()->id;
|
||||
$comment = new Comment();
|
||||
$comment = $this->comment->newInstance();
|
||||
|
||||
$comment->text = $text;
|
||||
$comment->html = $this->commentToHtml($text);
|
||||
@@ -70,7 +82,7 @@ class CommentRepo
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
return $converter->convert($commentText);
|
||||
return $converter->convertToHtml($commentText);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,8 +90,9 @@ class CommentRepo
|
||||
*/
|
||||
protected function getNextLocalId(Entity $entity): int
|
||||
{
|
||||
$currentMaxId = $entity->comments()->max('local_id');
|
||||
/** @var Comment $comment */
|
||||
$comment = $entity->comments(false)->orderBy('local_id', 'desc')->first();
|
||||
|
||||
return $currentMaxId + 1;
|
||||
return ($comment->local_id ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Activity\Models\Loggable;
|
||||
use BookStack\Activity\Models\Webhook;
|
||||
use BookStack\Activity\Tools\WebhookFormatter;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@@ -24,23 +22,27 @@ class DispatchWebhookJob implements ShouldQueue
|
||||
use SerializesModels;
|
||||
|
||||
protected Webhook $webhook;
|
||||
protected string $event;
|
||||
protected User $initiator;
|
||||
protected int $initiatedTime;
|
||||
protected array $webhookData;
|
||||
|
||||
/**
|
||||
* @var string|Loggable
|
||||
*/
|
||||
protected $detail;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Webhook $webhook, string $event, Loggable|string $detail)
|
||||
public function __construct(Webhook $webhook, string $event, $detail)
|
||||
{
|
||||
$this->webhook = $webhook;
|
||||
$this->event = $event;
|
||||
$this->detail = $detail;
|
||||
$this->initiator = user();
|
||||
$this->initiatedTime = time();
|
||||
|
||||
$themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $event, $this->webhook, $detail, $this->initiator, $this->initiatedTime);
|
||||
$this->webhookData = $themeResponse ?? WebhookFormatter::getDefault($event, $this->webhook, $detail, $this->initiator, $this->initiatedTime)->format();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,13 +52,15 @@ class DispatchWebhookJob implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime);
|
||||
$webhookData = $themeResponse ?? WebhookFormatter::getDefault($this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime)->format();
|
||||
$lastError = null;
|
||||
|
||||
try {
|
||||
$response = Http::asJson()
|
||||
->withOptions(['allow_redirects' => ['strict' => true]])
|
||||
->timeout($this->webhook->timeout)
|
||||
->post($this->webhook->endpoint, $this->webhookData);
|
||||
->post($this->webhook->endpoint, $webhookData);
|
||||
} catch (\Exception $exception) {
|
||||
$lastError = $exception->getMessage();
|
||||
Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with error \"{$lastError}\"");
|
||||
19
app/Actions/Favourite.php
Normal file
19
app/Actions/Favourite.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class Favourite extends Model
|
||||
{
|
||||
protected $fillable = ['user_id'];
|
||||
|
||||
/**
|
||||
* Get the related model that can be favourited.
|
||||
*/
|
||||
public function favouritable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Tools;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
class IpFormatter
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Queries;
|
||||
namespace BookStack\Actions\Queries;
|
||||
|
||||
use BookStack\Activity\Models\Webhook;
|
||||
use BookStack\Actions\Webhook;
|
||||
use BookStack\Util\SimpleListOptions;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Models;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Permissions\Models\JointPermission;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
/**
|
||||
@@ -29,12 +27,6 @@ class Tag extends Model
|
||||
return $this->morphTo('entity');
|
||||
}
|
||||
|
||||
public function jointPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(JointPermission::class, 'entity_id', 'entity_id')
|
||||
->whereColumn('tags.entity_type', '=', 'joint_permissions.entity_type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full URL to start a tag name search for this tag name.
|
||||
*/
|
||||
@@ -1,8 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Tools;
|
||||
|
||||
use BookStack\Activity\Models\Tag;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
class TagClassGenerator
|
||||
{
|
||||
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Activity\Models\Tag;
|
||||
use BookStack\Auth\Permissions\PermissionApplicator;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use BookStack\Util\SimpleListOptions;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -12,9 +11,11 @@ use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TagRepo
|
||||
{
|
||||
public function __construct(
|
||||
protected PermissionApplicator $permissions
|
||||
) {
|
||||
protected PermissionApplicator $permissions;
|
||||
|
||||
public function __construct(PermissionApplicator $permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,15 +29,16 @@ 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(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'),
|
||||
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"),
|
||||
])
|
||||
->orderBy($sort, $listOptions->getOrder());
|
||||
|
||||
@@ -89,7 +91,6 @@ class TagRepo
|
||||
{
|
||||
$query = Tag::query()
|
||||
->select('*', DB::raw('count(*) as count'))
|
||||
->where('value', '!=', '')
|
||||
->groupBy('value');
|
||||
|
||||
if ($searchTerm) {
|
||||
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Models;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Permissions\Models\JointPermission;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use BookStack\Interfaces\Viewable;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
/**
|
||||
@@ -29,12 +28,6 @@ class View extends Model
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function jointPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(JointPermission::class, 'entity_id', 'viewable_id')
|
||||
->whereColumn('views.viewable_type', '=', 'joint_permissions.entity_type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the current user's view count for the given viewable model.
|
||||
*/
|
||||
@@ -54,4 +47,12 @@ class View extends Model
|
||||
|
||||
return $view->views;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all views from the system.
|
||||
*/
|
||||
public static function clearAll()
|
||||
{
|
||||
static::query()->truncate();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Models;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -1,14 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Tools;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Activity\Models\Loggable;
|
||||
use BookStack\Activity\Models\Webhook;
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class WebhookFormatter
|
||||
@@ -17,14 +15,18 @@ class WebhookFormatter
|
||||
protected string $event;
|
||||
protected User $initiator;
|
||||
protected int $initiatedTime;
|
||||
protected string|Loggable $detail;
|
||||
|
||||
/**
|
||||
* @var string|Loggable
|
||||
*/
|
||||
protected $detail;
|
||||
|
||||
/**
|
||||
* @var array{condition: callable(string, Model):bool, format: callable(Model):void}[]
|
||||
*/
|
||||
protected $modelFormatters = [];
|
||||
|
||||
public function __construct(string $event, Webhook $webhook, string|Loggable $detail, User $initiator, int $initiatedTime)
|
||||
public function __construct(string $event, Webhook $webhook, $detail, User $initiator, int $initiatedTime)
|
||||
{
|
||||
$this->webhook = $webhook;
|
||||
$this->event = $event;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Models;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Models;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Permissions\Models\JointPermission;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class Favourite extends Model
|
||||
{
|
||||
protected $fillable = ['user_id'];
|
||||
|
||||
/**
|
||||
* Get the related model that can be favourited.
|
||||
*/
|
||||
public function favouritable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function jointPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(JointPermission::class, 'entity_id', 'favouritable_id')
|
||||
->whereColumn('favourites.favouritable_type', '=', 'joint_permissions.entity_type');
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Activity\Tools;
|
||||
|
||||
use BookStack\Activity\Models\Comment;
|
||||
use BookStack\Entities\Models\Page;
|
||||
|
||||
class CommentTree
|
||||
{
|
||||
/**
|
||||
* The built nested tree structure array.
|
||||
* @var array{comment: Comment, depth: int, children: array}[]
|
||||
*/
|
||||
protected array $tree;
|
||||
protected array $comments;
|
||||
|
||||
public function __construct(
|
||||
protected Page $page
|
||||
) {
|
||||
$this->comments = $this->loadComments();
|
||||
$this->tree = $this->createTree($this->comments);
|
||||
}
|
||||
|
||||
public function enabled(): bool
|
||||
{
|
||||
return !setting('app-disable-comments');
|
||||
}
|
||||
|
||||
public function empty(): bool
|
||||
{
|
||||
return count($this->tree) === 0;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->comments);
|
||||
}
|
||||
|
||||
public function get(): array
|
||||
{
|
||||
return $this->tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Comment[] $comments
|
||||
*/
|
||||
protected function createTree(array $comments): array
|
||||
{
|
||||
$byId = [];
|
||||
foreach ($comments as $comment) {
|
||||
$byId[$comment->local_id] = $comment;
|
||||
}
|
||||
|
||||
$childMap = [];
|
||||
foreach ($comments as $comment) {
|
||||
$parent = $comment->parent_id;
|
||||
if (is_null($parent) || !isset($byId[$parent])) {
|
||||
$parent = 0;
|
||||
}
|
||||
|
||||
if (!isset($childMap[$parent])) {
|
||||
$childMap[$parent] = [];
|
||||
}
|
||||
$childMap[$parent][] = $comment->local_id;
|
||||
}
|
||||
|
||||
$tree = [];
|
||||
foreach ($childMap[0] ?? [] as $childId) {
|
||||
$tree[] = $this->createTreeForId($childId, 0, $byId, $childMap);
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
protected function createTreeForId(int $id, int $depth, array &$byId, array &$childMap): array
|
||||
{
|
||||
$childIds = $childMap[$id] ?? [];
|
||||
$children = [];
|
||||
|
||||
foreach ($childIds as $childId) {
|
||||
$children[] = $this->createTreeForId($childId, $depth + 1, $byId, $childMap);
|
||||
}
|
||||
|
||||
return [
|
||||
'comment' => $byId[$id],
|
||||
'depth' => $depth,
|
||||
'children' => $children,
|
||||
];
|
||||
}
|
||||
|
||||
protected function loadComments(): array
|
||||
{
|
||||
if (!$this->enabled()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->page->comments()
|
||||
->with('createdBy')
|
||||
->get()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Api;
|
||||
|
||||
use BookStack\Http\ApiController;
|
||||
use BookStack\Http\Controllers\Api\ApiController;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -16,8 +16,8 @@ use ReflectionMethod;
|
||||
|
||||
class ApiDocsGenerator
|
||||
{
|
||||
protected array $reflectionClasses = [];
|
||||
protected array $controllerClasses = [];
|
||||
protected $reflectionClasses = [];
|
||||
protected $controllerClasses = [];
|
||||
|
||||
/**
|
||||
* Load the docs form the cache if existing
|
||||
@@ -139,10 +139,9 @@ class ApiDocsGenerator
|
||||
protected function parseDescriptionFromMethodComment(string $comment): string
|
||||
{
|
||||
$matches = [];
|
||||
preg_match_all('/^\s*?\*\s?($|((?![\/@\s]).*?))$/m', $comment, $matches);
|
||||
preg_match_all('/^\s*?\*\s((?![@\s]).*?)$/m', $comment, $matches);
|
||||
|
||||
$text = implode(' ', $matches[1] ?? []);
|
||||
return str_replace(' ', "\n", $text);
|
||||
return implode(' ', $matches[1] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
namespace BookStack\Api;
|
||||
|
||||
use BookStack\Activity\Models\Loggable;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -21,8 +20,6 @@ use Illuminate\Support\Carbon;
|
||||
*/
|
||||
class ApiToken extends Model implements Loggable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['name', 'expires_at'];
|
||||
protected $casts = [
|
||||
'expires_at' => 'date:Y-m-d',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Api;
|
||||
|
||||
use BookStack\Access\LoginService;
|
||||
use BookStack\Auth\Access\LoginService;
|
||||
use BookStack\Exceptions\ApiAuthException;
|
||||
use Illuminate\Auth\GuardHelpers;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\App\Providers;
|
||||
|
||||
use BookStack\Translation\FileLoader;
|
||||
use BookStack\Translation\MessageSelector;
|
||||
use Illuminate\Translation\TranslationServiceProvider as BaseProvider;
|
||||
use Illuminate\Translation\Translator;
|
||||
|
||||
class TranslationServiceProvider extends BaseProvider
|
||||
{
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->registerLoader();
|
||||
|
||||
// This is a tweak upon Laravel's based translation service registration to allow
|
||||
// usage of a custom MessageSelector class
|
||||
$this->app->singleton('translator', function ($app) {
|
||||
$loader = $app['translation.loader'];
|
||||
|
||||
// When registering the translator component, we'll need to set the default
|
||||
// locale as well as the fallback locale. So, we'll grab the application
|
||||
// configuration so we can easily get both of these values from there.
|
||||
$locale = $app['config']['app.locale'];
|
||||
|
||||
$trans = new Translator($loader, $locale);
|
||||
$trans->setFallback($app['config']['app.fallback_locale']);
|
||||
$trans->setSelector(new MessageSelector());
|
||||
|
||||
return $trans;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Register the translation line loader.
|
||||
* Overrides the default register action from Laravel so a custom loader can be used.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function registerLoader()
|
||||
{
|
||||
$this->app->singleton('translation.loader', function ($app) {
|
||||
return new FileLoader($app['files'], $app['path.lang']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\App;
|
||||
namespace BookStack;
|
||||
|
||||
class Application extends \Illuminate\Foundation\Application
|
||||
{
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\ConfirmationEmailException;
|
||||
use BookStack\Notifications\ConfirmEmail;
|
||||
use BookStack\Users\Models\User;
|
||||
|
||||
class EmailConfirmationService extends UserTokenService
|
||||
{
|
||||
protected string $tokenTable = 'email_confirmations';
|
||||
protected int $expiryTime = 24;
|
||||
protected $tokenTable = 'email_confirmations';
|
||||
protected $expiryTime = 24;
|
||||
|
||||
/**
|
||||
* Create new confirmation for a user,
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\UserProvider;
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Users\Models\Role;
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class GroupSyncService
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Guards;
|
||||
namespace BookStack\Auth\Access\Guards;
|
||||
|
||||
/**
|
||||
* Saml2 Session Guard.
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Guards;
|
||||
namespace BookStack\Auth\Access\Guards;
|
||||
|
||||
use BookStack\Access\RegistrationService;
|
||||
use BookStack\Auth\Access\RegistrationService;
|
||||
use Illuminate\Auth\GuardHelpers;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Guards;
|
||||
namespace BookStack\Auth\Access\Guards;
|
||||
|
||||
use BookStack\Access\LdapService;
|
||||
use BookStack\Access\RegistrationService;
|
||||
use BookStack\Auth\Access\LdapService;
|
||||
use BookStack\Auth\Access\RegistrationService;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\JsonDebugException;
|
||||
use BookStack\Exceptions\LdapException;
|
||||
use BookStack\Exceptions\LoginAttemptEmailNeededException;
|
||||
use BookStack\Exceptions\LoginAttemptException;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Contracts\Auth\UserProvider;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
use Illuminate\Support\Str;
|
||||
136
app/Auth/Access/Ldap.php
Normal file
136
app/Auth/Access/Ldap.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
/**
|
||||
* Class Ldap
|
||||
* An object-orientated thin abstraction wrapper for common PHP LDAP functions.
|
||||
* Allows the standard LDAP functions to be mocked for testing.
|
||||
*/
|
||||
class Ldap
|
||||
{
|
||||
/**
|
||||
* Connect to an LDAP server.
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function connect(string $hostName, int $port)
|
||||
{
|
||||
return ldap_connect($hostName, $port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a LDAP option for the given connection.
|
||||
*
|
||||
* @param resource $ldapConnection
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setOption($ldapConnection, int $option, $value): bool
|
||||
{
|
||||
return ldap_set_option($ldapConnection, $option, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start TLS on the given LDAP connection.
|
||||
*/
|
||||
public function startTls($ldapConnection): bool
|
||||
{
|
||||
return ldap_start_tls($ldapConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the version number for the given ldap connection.
|
||||
*
|
||||
* @param resource $ldapConnection
|
||||
*/
|
||||
public function setVersion($ldapConnection, int $version): bool
|
||||
{
|
||||
return $this->setOption($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search LDAP tree using the provided filter.
|
||||
*
|
||||
* @param resource $ldapConnection
|
||||
* @param string $baseDn
|
||||
* @param string $filter
|
||||
* @param array|null $attributes
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function search($ldapConnection, $baseDn, $filter, array $attributes = null)
|
||||
{
|
||||
return ldap_search($ldapConnection, $baseDn, $filter, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entries from an ldap search result.
|
||||
*
|
||||
* @param resource $ldapConnection
|
||||
* @param resource $ldapSearchResult
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEntries($ldapConnection, $ldapSearchResult)
|
||||
{
|
||||
return ldap_get_entries($ldapConnection, $ldapSearchResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and get entries immediately.
|
||||
*
|
||||
* @param resource $ldapConnection
|
||||
* @param string $baseDn
|
||||
* @param string $filter
|
||||
* @param array|null $attributes
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function searchAndGetEntries($ldapConnection, $baseDn, $filter, array $attributes = null)
|
||||
{
|
||||
$search = $this->search($ldapConnection, $baseDn, $filter, $attributes);
|
||||
|
||||
return $this->getEntries($ldapConnection, $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to LDAP directory.
|
||||
*
|
||||
* @param resource $ldapConnection
|
||||
* @param string $bindRdn
|
||||
* @param string $bindPassword
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function bind($ldapConnection, $bindRdn = null, $bindPassword = null)
|
||||
{
|
||||
return ldap_bind($ldapConnection, $bindRdn, $bindPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explode a LDAP dn string into an array of components.
|
||||
*
|
||||
* @param string $dn
|
||||
* @param int $withAttrib
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function explodeDn(string $dn, int $withAttrib)
|
||||
{
|
||||
return ldap_explode_dn($dn, $withAttrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a string for use in an LDAP filter.
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $ignore
|
||||
* @param int $flags
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function escape(string $value, string $ignore = '', int $flags = 0)
|
||||
{
|
||||
return ldap_escape($value, $ignore, $flags);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\JsonDebugException;
|
||||
use BookStack\Exceptions\LdapException;
|
||||
use BookStack\Uploads\UserAvatars;
|
||||
use BookStack\Users\Models\User;
|
||||
use ErrorException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -15,19 +15,26 @@ use Illuminate\Support\Facades\Log;
|
||||
*/
|
||||
class LdapService
|
||||
{
|
||||
protected Ldap $ldap;
|
||||
protected GroupSyncService $groupSyncService;
|
||||
protected UserAvatars $userAvatars;
|
||||
|
||||
/**
|
||||
* @var resource|\LDAP\Connection
|
||||
* @var resource
|
||||
*/
|
||||
protected $ldapConnection;
|
||||
|
||||
protected array $config;
|
||||
protected bool $enabled;
|
||||
|
||||
public function __construct(
|
||||
protected Ldap $ldap,
|
||||
protected UserAvatars $userAvatars,
|
||||
protected GroupSyncService $groupSyncService
|
||||
) {
|
||||
/**
|
||||
* LdapService constructor.
|
||||
*/
|
||||
public function __construct(Ldap $ldap, UserAvatars $userAvatars, GroupSyncService $groupSyncService)
|
||||
{
|
||||
$this->ldap = $ldap;
|
||||
$this->userAvatars = $userAvatars;
|
||||
$this->groupSyncService = $groupSyncService;
|
||||
$this->config = config('services.ldap');
|
||||
$this->enabled = config('auth.method') === 'ldap';
|
||||
}
|
||||
@@ -52,7 +59,7 @@ class LdapService
|
||||
|
||||
// Clean attributes
|
||||
foreach ($attributes as $index => $attribute) {
|
||||
if (str_starts_with($attribute, 'BIN;')) {
|
||||
if (strpos($attribute, 'BIN;') === 0) {
|
||||
$attributes[$index] = substr($attribute, strlen('BIN;'));
|
||||
}
|
||||
}
|
||||
@@ -75,7 +82,7 @@ class LdapService
|
||||
* Get the details of a user from LDAP using the given username.
|
||||
* User found via configurable user filter.
|
||||
*
|
||||
* @throws LdapException|JsonDebugException
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function getUserDetails(string $userName): ?array
|
||||
{
|
||||
@@ -119,7 +126,7 @@ class LdapService
|
||||
*/
|
||||
protected function getUserResponseProperty(array $userDetails, string $propertyKey, $defaultValue)
|
||||
{
|
||||
$isBinary = str_starts_with($propertyKey, 'BIN;');
|
||||
$isBinary = strpos($propertyKey, 'BIN;') === 0;
|
||||
$propertyKey = strtolower($propertyKey);
|
||||
$value = $defaultValue;
|
||||
|
||||
@@ -163,11 +170,11 @@ class LdapService
|
||||
* Bind the system user to the LDAP connection using the given credentials
|
||||
* otherwise anonymous access is attempted.
|
||||
*
|
||||
* @param resource|\LDAP\Connection $connection
|
||||
* @param resource $connection
|
||||
*
|
||||
* @throws LdapException
|
||||
*/
|
||||
protected function bindSystemUser($connection): void
|
||||
protected function bindSystemUser($connection)
|
||||
{
|
||||
$ldapDn = $this->config['dn'];
|
||||
$ldapPass = $this->config['pass'];
|
||||
@@ -190,7 +197,7 @@ class LdapService
|
||||
*
|
||||
* @throws LdapException
|
||||
*
|
||||
* @return resource|\LDAP\Connection
|
||||
* @return resource
|
||||
*/
|
||||
protected function getConnection()
|
||||
{
|
||||
@@ -209,8 +216,8 @@ class LdapService
|
||||
$this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
|
||||
}
|
||||
|
||||
$ldapHost = $this->parseServerString($this->config['server']);
|
||||
$ldapConnection = $this->ldap->connect($ldapHost);
|
||||
$serverDetails = $this->parseServerString($this->config['server']);
|
||||
$ldapConnection = $this->ldap->connect($serverDetails['host'], $serverDetails['port']);
|
||||
|
||||
if ($ldapConnection === false) {
|
||||
throw new LdapException(trans('errors.ldap_cannot_connect'));
|
||||
@@ -235,16 +242,23 @@ class LdapService
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an LDAP server string and return the host suitable for a connection.
|
||||
* Parse a LDAP server string and return the host and port for a connection.
|
||||
* Is flexible to formats such as 'ldap.example.com:8069' or 'ldaps://ldap.example.com'.
|
||||
*/
|
||||
protected function parseServerString(string $serverString): string
|
||||
protected function parseServerString(string $serverString): array
|
||||
{
|
||||
if (str_starts_with($serverString, 'ldaps://') || str_starts_with($serverString, 'ldap://')) {
|
||||
return $serverString;
|
||||
$serverNameParts = explode(':', $serverString);
|
||||
|
||||
// If we have a protocol just return the full string since PHP will ignore a separate port.
|
||||
if ($serverNameParts[0] === 'ldaps' || $serverNameParts[0] === 'ldap') {
|
||||
return ['host' => $serverString, 'port' => 389];
|
||||
}
|
||||
|
||||
return "ldap://{$serverString}";
|
||||
// Otherwise, extract the port out
|
||||
$hostName = $serverNameParts[0];
|
||||
$ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389;
|
||||
|
||||
return ['host' => $hostName, 'port' => $ldapPort];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,7 +386,7 @@ class LdapService
|
||||
* @throws LdapException
|
||||
* @throws JsonDebugException
|
||||
*/
|
||||
public function syncGroups(User $user, string $username): void
|
||||
public function syncGroups(User $user, string $username)
|
||||
{
|
||||
$userLdapGroups = $this->getUserGroups($username);
|
||||
$this->groupSyncService->syncUserWithFoundGroups($user, $userLdapGroups, $this->config['remove_from_groups']);
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Access\Mfa\MfaSession;
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Auth\Access\Mfa\MfaSession;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\LoginAttemptException;
|
||||
use BookStack\Exceptions\StoppedAuthenticationException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use BookStack\Users\Models\User;
|
||||
use Exception;
|
||||
|
||||
class LoginService
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Mfa;
|
||||
namespace BookStack\Auth\Access\Mfa;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Mfa;
|
||||
namespace BookStack\Auth\Access\Mfa;
|
||||
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Auth\User;
|
||||
|
||||
class MfaSession
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Mfa;
|
||||
namespace BookStack\Auth\Access\Mfa;
|
||||
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Auth\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Mfa;
|
||||
namespace BookStack\Auth\Access\Mfa;
|
||||
|
||||
use BaconQrCode\Renderer\Color\Rgb;
|
||||
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
|
||||
@@ -8,7 +8,7 @@ use BaconQrCode\Renderer\ImageRenderer;
|
||||
use BaconQrCode\Renderer\RendererStyle\Fill;
|
||||
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
||||
use BaconQrCode\Writer;
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Auth\User;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use PragmaRX\Google2FA\Support\Constants;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Mfa;
|
||||
namespace BookStack\Auth\Access\Mfa;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use League\OAuth2\Client\Token\AccessToken;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
use Exception;
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
class OidcIdToken
|
||||
{
|
||||
protected array $header;
|
||||
protected array $payload;
|
||||
protected string $signature;
|
||||
protected string $issuer;
|
||||
protected array $tokenParts = [];
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $header;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $payload;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $signature;
|
||||
|
||||
/**
|
||||
* @var array[]|string[]
|
||||
*/
|
||||
protected array $keys;
|
||||
protected $keys;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $issuer;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $tokenParts = [];
|
||||
|
||||
public function __construct(string $token, string $issuer, array $keys)
|
||||
{
|
||||
@@ -87,14 +106,6 @@ class OidcIdToken
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the existing claim data of this token with that provided.
|
||||
*/
|
||||
public function replaceClaims(array $claims): void
|
||||
{
|
||||
$this->payload = $claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the structure of the given token and ensure we have the required pieces.
|
||||
* As per https://datatracker.ietf.org/doc/html/rfc7519#section-7.2.
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
class OidcInvalidKeyException extends \Exception
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
use Exception;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
use Exception;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
use phpseclib3\Crypt\Common\PublicKey;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
use League\OAuth2\Client\Grant\AbstractGrant;
|
||||
use League\OAuth2\Client\Provider\AbstractProvider;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
@@ -1,16 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access\Oidc;
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
use BookStack\Access\GroupSyncService;
|
||||
use BookStack\Access\LoginService;
|
||||
use BookStack\Access\RegistrationService;
|
||||
use BookStack\Auth\Access\GroupSyncService;
|
||||
use BookStack\Auth\Access\LoginService;
|
||||
use BookStack\Auth\Access\RegistrationService;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\JsonDebugException;
|
||||
use BookStack\Exceptions\StoppedAuthenticationException;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
|
||||
@@ -23,12 +21,24 @@ use Psr\Http\Client\ClientInterface as HttpClient;
|
||||
*/
|
||||
class OidcService
|
||||
{
|
||||
protected RegistrationService $registrationService;
|
||||
protected LoginService $loginService;
|
||||
protected HttpClient $httpClient;
|
||||
protected GroupSyncService $groupService;
|
||||
|
||||
/**
|
||||
* OpenIdService constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected RegistrationService $registrationService,
|
||||
protected LoginService $loginService,
|
||||
protected HttpClient $httpClient,
|
||||
protected GroupSyncService $groupService
|
||||
RegistrationService $registrationService,
|
||||
LoginService $loginService,
|
||||
HttpClient $httpClient,
|
||||
GroupSyncService $groupService
|
||||
) {
|
||||
$this->registrationService = $registrationService;
|
||||
$this->loginService = $loginService;
|
||||
$this->httpClient = $httpClient;
|
||||
$this->groupService = $groupService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,8 +198,7 @@ class OidcService
|
||||
*/
|
||||
protected function getUserDetails(OidcIdToken $token): array
|
||||
{
|
||||
$idClaim = $this->config()['external_id_claim'];
|
||||
$id = $token->getClaim($idClaim);
|
||||
$id = $token->getClaim('sub');
|
||||
|
||||
return [
|
||||
'external_id' => $id,
|
||||
@@ -216,16 +225,6 @@ class OidcService
|
||||
$settings->keys,
|
||||
);
|
||||
|
||||
$returnClaims = Theme::dispatch(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $idToken->getAllClaims(), [
|
||||
'access_token' => $accessToken->getToken(),
|
||||
'expires_in' => $accessToken->getExpires(),
|
||||
'refresh_token' => $accessToken->getRefreshToken(),
|
||||
]);
|
||||
|
||||
if (!is_null($returnClaims)) {
|
||||
$idToken->replaceClaims($returnClaims);
|
||||
}
|
||||
|
||||
if ($this->config()['dump_user_details']) {
|
||||
throw new JsonDebugException($idToken->getAllClaims());
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Auth\SocialAccount;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Users\UserRepo;
|
||||
use Exception;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\JsonDebugException;
|
||||
use BookStack\Exceptions\SamlException;
|
||||
use BookStack\Exceptions\StoppedAuthenticationException;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\Users\Models\User;
|
||||
use Exception;
|
||||
use OneLogin\Saml2\Auth;
|
||||
use OneLogin\Saml2\Constants;
|
||||
@@ -67,7 +67,7 @@ class Saml2Service
|
||||
$returnRoute,
|
||||
[],
|
||||
$user->email,
|
||||
session()->get('saml2_session_index'),
|
||||
null,
|
||||
true,
|
||||
Constants::NAMEID_EMAIL_ADDRESS
|
||||
);
|
||||
@@ -118,7 +118,6 @@ class Saml2Service
|
||||
|
||||
$attrs = $toolkit->getAttributes();
|
||||
$id = $toolkit->getNameId();
|
||||
session()->put('saml2_session_index', $toolkit->getSessionIndex());
|
||||
|
||||
return $this->processLoginCallback($id, $attrs);
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Auth\Access\handler;
|
||||
use BookStack\Auth\SocialAccount;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\SocialDriverNotConfigured;
|
||||
use BookStack\Exceptions\SocialSignInAccountNotUsed;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Contracts\Factory as Socialite;
|
||||
@@ -1,18 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Notifications\UserInvite;
|
||||
use BookStack\Users\Models\User;
|
||||
|
||||
class UserInviteService extends UserTokenService
|
||||
{
|
||||
protected string $tokenTable = 'user_invites';
|
||||
protected int $expiryTime = 336; // Two weeks
|
||||
protected $tokenTable = 'user_invites';
|
||||
protected $expiryTime = 336; // Two weeks
|
||||
|
||||
/**
|
||||
* Send an invitation to a user to sign into BookStack
|
||||
* Removes existing invitation tokens.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function sendInvitation(User $user)
|
||||
{
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\UserTokenExpiredException;
|
||||
use BookStack\Exceptions\UserTokenNotFoundException;
|
||||
use BookStack\Users\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -14,29 +14,41 @@ class UserTokenService
|
||||
{
|
||||
/**
|
||||
* Name of table where user tokens are stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $tokenTable = 'user_tokens';
|
||||
protected $tokenTable = 'user_tokens';
|
||||
|
||||
/**
|
||||
* Token expiry time in hours.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected int $expiryTime = 24;
|
||||
protected $expiryTime = 24;
|
||||
|
||||
/**
|
||||
* Delete all tokens that belong to a user.
|
||||
* Delete all email confirmations that belong to a user.
|
||||
*
|
||||
* @param User $user
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function deleteByUser(User $user): void
|
||||
public function deleteByUser(User $user)
|
||||
{
|
||||
DB::table($this->tokenTable)
|
||||
return DB::table($this->tokenTable)
|
||||
->where('user_id', '=', $user->id)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user id from a token, while checking the token exists and has not expired.
|
||||
* Get the user id from a token, while check the token exists and has not expired.
|
||||
*
|
||||
* @param string $token
|
||||
*
|
||||
* @throws UserTokenNotFoundException
|
||||
* @throws UserTokenExpiredException
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function checkTokenAndGetUserId(string $token): int
|
||||
{
|
||||
@@ -55,6 +67,8 @@ class UserTokenService
|
||||
|
||||
/**
|
||||
* Creates a unique token within the email confirmation database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateToken(): string
|
||||
{
|
||||
@@ -68,6 +82,10 @@ class UserTokenService
|
||||
|
||||
/**
|
||||
* Generate and store a token for the given user.
|
||||
*
|
||||
* @param User $user
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function createTokenForUser(User $user): string
|
||||
{
|
||||
@@ -84,6 +102,10 @@ class UserTokenService
|
||||
|
||||
/**
|
||||
* Check if the given token exists.
|
||||
*
|
||||
* @param string $token
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function tokenExists(string $token): bool
|
||||
{
|
||||
@@ -93,8 +115,12 @@ class UserTokenService
|
||||
|
||||
/**
|
||||
* Get a token entry for the given token.
|
||||
*
|
||||
* @param string $token
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
protected function getEntryByToken(string $token): ?stdClass
|
||||
protected function getEntryByToken(string $token)
|
||||
{
|
||||
return DB::table($this->tokenTable)
|
||||
->where('token', '=', $token)
|
||||
@@ -103,6 +129,10 @@ class UserTokenService
|
||||
|
||||
/**
|
||||
* Check if the given token entry has expired.
|
||||
*
|
||||
* @param stdClass $tokenEntry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function entryExpired(stdClass $tokenEntry): bool
|
||||
{
|
||||
18
app/Auth/Permissions/CollapsedPermission.php
Normal file
18
app/Auth/Permissions/CollapsedPermission.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?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';
|
||||
}
|
||||
278
app/Auth/Permissions/CollapsedPermissionBuilder.php
Normal file
278
app/Auth/Permissions/CollapsedPermissionBuilder.php
Normal file
@@ -0,0 +1,278 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
69
app/Auth/Permissions/EntityPermission.php
Normal file
69
app/Auth/Permissions/EntityPermission.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $role_id
|
||||
* @property int $user_id
|
||||
* @property int $entity_id
|
||||
* @property string $entity_type
|
||||
* @property boolean $view
|
||||
* @property boolean $create
|
||||
* @property boolean $update
|
||||
* @property boolean $delete
|
||||
*/
|
||||
class EntityPermission extends Model
|
||||
{
|
||||
public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
|
||||
|
||||
protected $fillable = ['role_id', 'user_id', 'view', 'create', 'update', 'delete'];
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* Get the role assigned to this entity permission.
|
||||
*/
|
||||
public function role(): BelongsTo
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
37
app/Auth/Permissions/EntityPermissionMap.php
Normal file
37
app/Auth/Permissions/EntityPermissionMap.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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] ?? [];
|
||||
}
|
||||
}
|
||||
389
app/Auth/Permissions/PermissionApplicator.php
Normal file
389
app/Auth/Permissions/PermissionApplicator.php
Normal file
@@ -0,0 +1,389 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Model;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Checks if an entity has a restriction set upon it.
|
||||
*
|
||||
* @param HasCreatorAndUpdater|HasOwner $ownable
|
||||
*/
|
||||
public function checkOwnableUserAccess(Model $ownable, string $permission): bool
|
||||
{
|
||||
$explodedPermission = explode('-', $permission);
|
||||
$action = $explodedPermission[1] ?? $explodedPermission[0];
|
||||
$fullPermission = count($explodedPermission) > 1 ? $permission : $ownable->getMorphClass() . '-' . $permission;
|
||||
|
||||
$user = $this->currentUser();
|
||||
$userRoleIds = $this->getCurrentUserRoleIds();
|
||||
|
||||
$allRolePermission = $user->can($fullPermission . '-all');
|
||||
$ownRolePermission = $user->can($fullPermission . '-own');
|
||||
$nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
|
||||
$ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by';
|
||||
$ownableFieldVal = $ownable->getAttribute($ownerField);
|
||||
|
||||
if (is_null($ownableFieldVal)) {
|
||||
throw new InvalidArgumentException("{$ownerField} field used but has not been loaded");
|
||||
}
|
||||
|
||||
$isOwner = $user->id === $ownableFieldVal;
|
||||
$hasRolePermission = $allRolePermission || ($isOwner && $ownRolePermission);
|
||||
|
||||
// Handle non entity specific jointPermissions
|
||||
if (in_array($explodedPermission[0], $nonJointPermissions)) {
|
||||
return $hasRolePermission;
|
||||
}
|
||||
|
||||
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $user->id, $action);
|
||||
|
||||
return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$this->ensureValidEntityAction($action);
|
||||
|
||||
$adminRoleId = Role::getSystemRole('admin')->id;
|
||||
if (in_array($adminRoleId, $userRoleIds)) {
|
||||
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];
|
||||
if ($entity instanceof Page && $entity->chapter_id) {
|
||||
$typeIdList[] = 'chapter:' . $entity->chapter_id;
|
||||
}
|
||||
|
||||
if ($entity instanceof Page || $entity instanceof Chapter) {
|
||||
$typeIdList[] = 'book:' . $entity->book_id;
|
||||
}
|
||||
|
||||
$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();
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return user-level permission if exists
|
||||
if (count($permitsByType['user']) > 0) {
|
||||
return boolval(array_values($permitsByType['user'])[0]);
|
||||
}
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user has the given permission for any items in the system.
|
||||
* Can be passed an entity instance to filter on a specific type.
|
||||
*/
|
||||
public function checkUserHasEntityPermissionOnAny(string $action, string $entityClass = ''): bool
|
||||
{
|
||||
$this->ensureValidEntityAction($action);
|
||||
|
||||
$permissionQuery = EntityPermission::query()
|
||||
->where($action, '=', true)
|
||||
->where(function (Builder $query) {
|
||||
$query->whereIn('role_id', $this->getCurrentUserRoleIds())
|
||||
->orWhere('user_id', '=', $this->currentUser()->id);
|
||||
});
|
||||
|
||||
if (!empty($entityClass)) {
|
||||
/** @var Entity $entityInstance */
|
||||
$entityInstance = app()->make($entityClass);
|
||||
$permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
|
||||
}
|
||||
|
||||
$hasPermission = $permissionQuery->count() > 0;
|
||||
|
||||
return $hasPermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$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']));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
public function restrictDraftsOnPageQuery(Builder $query): Builder
|
||||
{
|
||||
return $query->where(function (Builder $query) {
|
||||
$query->where('draft', '=', false)
|
||||
->orWhere(function (Builder $query) {
|
||||
$query->where('draft', '=', true)
|
||||
->where('owned_by', '=', $this->currentUser()->id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter items that have entities set as a polymorphic relation.
|
||||
* For simplicity, this will not return results attached to draft pages.
|
||||
* Draft pages should never really have related items though.
|
||||
*
|
||||
* @param Builder|QueryBuilder $query
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
||||
}, '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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add conditions to a query for a model that's a relation of a page, so only the model results
|
||||
* on visible pages are returned by the query.
|
||||
* Is effectively the same as "restrictEntityRelationQuery" but takes into account page drafts
|
||||
* while not expecting a polymorphic relation, Just a simpler one-page-to-many-relations set-up.
|
||||
*/
|
||||
public function restrictPageRelationQuery(Builder $query, string $tableName, string $pageIdColumn): Builder
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user.
|
||||
*/
|
||||
protected function currentUser(): User
|
||||
{
|
||||
return user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the roles for the current logged-in user.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
protected function getCurrentUserRoleIds(): array
|
||||
{
|
||||
if (auth()->guest()) {
|
||||
return [Role::getSystemRole('public')->id];
|
||||
}
|
||||
|
||||
return $this->currentUser()->roles->pluck('id')->values()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the given action is a valid and expected entity action.
|
||||
* Throws an exception if invalid otherwise does nothing.
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function ensureValidEntityAction(string $action): void
|
||||
{
|
||||
if (!in_array($action, EntityPermission::PERMISSIONS)) {
|
||||
throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Permissions;
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Permissions\Models\EntityPermission;
|
||||
use BookStack\Users\Models\Role;
|
||||
|
||||
class PermissionFormData
|
||||
{
|
||||
@@ -22,19 +21,32 @@ class PermissionFormData
|
||||
{
|
||||
return $this->entity->permissions()
|
||||
->with('role')
|
||||
->where('role_id', '!=', 0)
|
||||
->whereNotNull('role_id')
|
||||
->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()->pluck('role_id');
|
||||
$assigned = $this->entity->permissions()->whereNotNull('role_id')->pluck('role_id');
|
||||
return Role::query()
|
||||
->where('system_name', '!=', 'admin')
|
||||
->whereNotIn('id', $assigned)
|
||||
@@ -50,20 +62,19 @@ class PermissionFormData
|
||||
{
|
||||
/** @var ?EntityPermission $permission */
|
||||
$permission = $this->entity->permissions()
|
||||
->where('role_id', '=', 0)
|
||||
->whereNull(['role_id', 'user_id'])
|
||||
->first();
|
||||
return $permission ?? (new EntityPermission());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "Everyone Else" role entry.
|
||||
* Check if the "Everyone else" option is inheriting default role system permissions.
|
||||
* Is determined by any system entity_permission existing for the current entity.
|
||||
*/
|
||||
public function everyoneElseRole(): Role
|
||||
public function everyoneElseInheriting(): bool
|
||||
{
|
||||
return (new Role())->forceFill([
|
||||
'id' => 0,
|
||||
'display_name' => trans('entities.permissions_role_everyone_else'),
|
||||
'description' => trans('entities.permissions_role_everyone_else_desc'),
|
||||
]);
|
||||
return !$this->entity->permissions()
|
||||
->whereNull(['role_id', 'user_id'])
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Permissions;
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Permissions\Models\RolePermission;
|
||||
use BookStack\Users\Models\Role;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PermissionsRepo
|
||||
{
|
||||
protected JointPermissionBuilder $permissionBuilder;
|
||||
protected CollapsedPermissionBuilder $permissionBuilder;
|
||||
protected array $systemRoles = ['admin', 'public'];
|
||||
|
||||
public function __construct(JointPermissionBuilder $permissionBuilder)
|
||||
/**
|
||||
* PermissionsRepo constructor.
|
||||
*/
|
||||
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
|
||||
{
|
||||
$this->permissionBuilder = $permissionBuilder;
|
||||
}
|
||||
@@ -39,7 +41,7 @@ class PermissionsRepo
|
||||
/**
|
||||
* Get a role via its ID.
|
||||
*/
|
||||
public function getRoleById(int $id): Role
|
||||
public function getRoleById($id): Role
|
||||
{
|
||||
return Role::query()->findOrFail($id);
|
||||
}
|
||||
@@ -50,12 +52,11 @@ class PermissionsRepo
|
||||
public function saveNewRole(array $roleData): Role
|
||||
{
|
||||
$role = new Role($roleData);
|
||||
$role->mfa_enforced = boolval($roleData['mfa_enforced'] ?? false);
|
||||
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
|
||||
$role->save();
|
||||
|
||||
$permissions = $roleData['permissions'] ?? [];
|
||||
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
|
||||
$this->assignRolePermissions($role, $permissions);
|
||||
$this->permissionBuilder->rebuildForRole($role);
|
||||
|
||||
Activity::add(ActivityType::ROLE_CREATE, $role);
|
||||
|
||||
@@ -64,45 +65,41 @@ class PermissionsRepo
|
||||
|
||||
/**
|
||||
* Updates an existing role.
|
||||
* Ensures Admin system role always have core permissions.
|
||||
* Ensure Admin role always have core permissions.
|
||||
*/
|
||||
public function updateRole($roleId, array $roleData): Role
|
||||
public function updateRole($roleId, array $roleData)
|
||||
{
|
||||
$role = $this->getRoleById($roleId);
|
||||
|
||||
if (isset($roleData['permissions'])) {
|
||||
$this->assignRolePermissions($role, $roleData['permissions']);
|
||||
}
|
||||
|
||||
$role->fill($roleData);
|
||||
$role->save();
|
||||
$this->permissionBuilder->rebuildForRole($role);
|
||||
|
||||
Activity::add(ActivityType::ROLE_UPDATE, $role);
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a list of permission names to the given role.
|
||||
*/
|
||||
protected function assignRolePermissions(Role $role, array $permissionNameArray = []): void
|
||||
{
|
||||
$permissions = [];
|
||||
$permissionNameArray = array_values($permissionNameArray);
|
||||
|
||||
// Ensure the admin system role retains vital system permissions
|
||||
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
|
||||
if ($role->system_name === 'admin') {
|
||||
$permissionNameArray = array_unique(array_merge($permissionNameArray, [
|
||||
$permissions = array_merge($permissions, [
|
||||
'users-manage',
|
||||
'user-roles-manage',
|
||||
'restrictions-manage-all',
|
||||
'restrictions-manage-own',
|
||||
'settings-manage',
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($permissionNameArray)) {
|
||||
$this->assignRolePermissions($role, $permissions);
|
||||
|
||||
$role->fill($roleData);
|
||||
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
|
||||
$role->save();
|
||||
|
||||
Activity::add(ActivityType::ROLE_UPDATE, $role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a list of permission names to a role.
|
||||
*/
|
||||
protected function assignRolePermissions(Role $role, array $permissionNameArray = [])
|
||||
{
|
||||
$permissions = [];
|
||||
$permissionNameArray = array_values($permissionNameArray);
|
||||
|
||||
if ($permissionNameArray) {
|
||||
$permissions = RolePermission::query()
|
||||
->whereIn('name', $permissionNameArray)
|
||||
->pluck('id')
|
||||
@@ -115,13 +112,13 @@ class PermissionsRepo
|
||||
/**
|
||||
* Delete a role from the system.
|
||||
* Check it's not an admin role or set as default before deleting.
|
||||
* If a migration Role ID is specified the users assign to the current role
|
||||
* If an migration Role ID is specified the users assign to the current role
|
||||
* will be added to the role of the specified id.
|
||||
*
|
||||
* @throws PermissionsException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function deleteRole(int $roleId, int $migrateRoleId = 0): void
|
||||
public function deleteRole($roleId, $migrateRoleId)
|
||||
{
|
||||
$role = $this->getRoleById($roleId);
|
||||
|
||||
@@ -132,7 +129,7 @@ class PermissionsRepo
|
||||
throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
|
||||
}
|
||||
|
||||
if ($migrateRoleId !== 0) {
|
||||
if ($migrateRoleId) {
|
||||
$newRole = Role::query()->find($migrateRoleId);
|
||||
if ($newRole) {
|
||||
$users = $role->users()->pluck('id')->toArray();
|
||||
@@ -141,7 +138,7 @@ class PermissionsRepo
|
||||
}
|
||||
|
||||
$role->entityPermissions()->delete();
|
||||
$role->jointPermissions()->delete();
|
||||
$role->collapsedPermissions()->delete();
|
||||
Activity::add(ActivityType::ROLE_DELETE, $role);
|
||||
$role->delete();
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Permissions\Models;
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Users\Models\Role;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $display_name
|
||||
*/
|
||||
class RolePermission extends Model
|
||||
{
|
||||
11
app/Auth/Permissions/SimpleEntityData.php
Normal file
11
app/Auth/Permissions/SimpleEntityData.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
class SimpleEntityData
|
||||
{
|
||||
public int $id;
|
||||
public string $type;
|
||||
public ?int $book_id;
|
||||
public ?int $chapter_id;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Users\Queries;
|
||||
namespace BookStack\Auth\Queries;
|
||||
|
||||
use BookStack\Users\Models\Role;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Util\SimpleListOptions;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
@@ -15,7 +15,7 @@ class RolesAllPaginatedAndSorted
|
||||
{
|
||||
$sort = $listOptions->getSort();
|
||||
if ($sort === 'created_at') {
|
||||
$sort = 'roles.created_at';
|
||||
$sort = 'users.created_at';
|
||||
}
|
||||
|
||||
$query = Role::query()->select(['*'])
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Users\Queries;
|
||||
namespace BookStack\Auth\Queries;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Users\Models\User;
|
||||
|
||||
/**
|
||||
* Get asset created counts for the given user.
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Users\Queries;
|
||||
namespace BookStack\Auth\Queries;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Users\Queries;
|
||||
namespace BookStack\Auth\Queries;
|
||||
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Util\SimpleListOptions;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Users\Models;
|
||||
namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Activity\Models\Loggable;
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Permissions\Models\EntityPermission;
|
||||
use BookStack\Permissions\Models\JointPermission;
|
||||
use BookStack\Permissions\Models\RolePermission;
|
||||
use BookStack\Auth\Permissions\CollapsedPermission;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Auth\Permissions\RolePermission;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
@@ -27,14 +27,10 @@ class Role extends Model implements Loggable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['display_name', 'description', 'external_auth_id', 'mfa_enforced'];
|
||||
protected $fillable = ['display_name', 'description', 'external_auth_id'];
|
||||
|
||||
protected $hidden = ['pivot'];
|
||||
|
||||
protected $casts = [
|
||||
'mfa_enforced' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* The roles that belong to the role.
|
||||
*/
|
||||
@@ -43,14 +39,6 @@ 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.
|
||||
*/
|
||||
@@ -67,6 +55,14 @@ 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.
|
||||
*/
|
||||
@@ -111,13 +107,7 @@ class Role extends Model implements Loggable
|
||||
*/
|
||||
public static function getSystemRole(string $systemName): ?self
|
||||
{
|
||||
static $cache = [];
|
||||
|
||||
if (!isset($cache[$systemName])) {
|
||||
$cache[$systemName] = static::query()->where('system_name', '=', $systemName)->first();
|
||||
}
|
||||
|
||||
return $cache[$systemName];
|
||||
return static::query()->where('system_name', '=', $systemName)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Access;
|
||||
namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Activity\Models\Loggable;
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Model;
|
||||
|
||||
/**
|
||||
* Class SocialAccount.
|
||||
@@ -1,15 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Users\Models;
|
||||
namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Access\Mfa\MfaValue;
|
||||
use BookStack\Access\SocialAccount;
|
||||
use BookStack\Activity\Models\Favourite;
|
||||
use BookStack\Activity\Models\Loggable;
|
||||
use BookStack\Actions\Favourite;
|
||||
use BookStack\Api\ApiToken;
|
||||
use BookStack\App\Model;
|
||||
use BookStack\App\Sluggable;
|
||||
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;
|
||||
use BookStack\Model;
|
||||
use BookStack\Notifications\ResetPassword;
|
||||
use BookStack\Uploads\Image;
|
||||
use Carbon\Carbon;
|
||||
@@ -73,7 +74,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password', 'remember_token', 'system_name', 'email_confirmed', 'external_auth_id', 'email',
|
||||
'created_at', 'updated_at', 'image_id', 'roles', 'avatar', 'user_id', 'pivot',
|
||||
'created_at', 'updated_at', 'image_id', 'roles', 'avatar', 'user_id',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -201,7 +202,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
public function attachRole(Role $role)
|
||||
{
|
||||
$this->roles()->attach($role->id);
|
||||
$this->unsetRelation('roles');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,6 +300,22 @@ 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.
|
||||
*/
|
||||
@@ -1,16 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Users;
|
||||
namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Access\UserInviteService;
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Auth\Access\UserInviteService;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Exceptions\NotifyException;
|
||||
use BookStack\Exceptions\UserUpdateException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Uploads\UserAvatars;
|
||||
use BookStack\Users\Models\Role;
|
||||
use BookStack\Users\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -155,6 +153,8 @@ class UserRepo
|
||||
$user->apiTokens()->delete();
|
||||
$user->favourites()->delete();
|
||||
$user->mfaValues()->delete();
|
||||
$user->collapsedPermissions()->delete();
|
||||
$user->entityPermissions()->delete();
|
||||
$user->delete();
|
||||
|
||||
// Delete user profile images
|
||||
@@ -236,8 +236,6 @@ class UserRepo
|
||||
*/
|
||||
protected function setUserRoles(User $user, array $roles)
|
||||
{
|
||||
$roles = array_filter(array_values($roles));
|
||||
|
||||
if ($this->demotingLastAdmin($user, $roles)) {
|
||||
throw new UserUpdateException(trans('errors.role_cannot_remove_only_admin'), $user->getEditUrl());
|
||||
}
|
||||
@@ -8,8 +8,6 @@
|
||||
* Do not edit this file unless you're happy to maintain any changes yourself.
|
||||
*/
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
return [
|
||||
|
||||
// The environment to run BookStack in.
|
||||
@@ -100,13 +98,7 @@ return [
|
||||
// Encryption cipher
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
// Maintenance Mode Driver
|
||||
'maintenance' => [
|
||||
'driver' => 'file',
|
||||
// 'store' => 'redis',
|
||||
],
|
||||
|
||||
// Application Service Providers
|
||||
// Application Services Provides
|
||||
'providers' => [
|
||||
|
||||
// Laravel Framework Service Providers...
|
||||
@@ -139,19 +131,68 @@ return [
|
||||
SocialiteProviders\Manager\ServiceProvider::class,
|
||||
|
||||
// BookStack custom service providers
|
||||
\BookStack\App\Providers\ThemeServiceProvider::class,
|
||||
\BookStack\App\Providers\AppServiceProvider::class,
|
||||
\BookStack\App\Providers\AuthServiceProvider::class,
|
||||
\BookStack\App\Providers\EventServiceProvider::class,
|
||||
\BookStack\App\Providers\RouteServiceProvider::class,
|
||||
\BookStack\App\Providers\TranslationServiceProvider::class,
|
||||
\BookStack\App\Providers\ValidationRuleServiceProvider::class,
|
||||
\BookStack\App\Providers\ViewTweaksServiceProvider::class,
|
||||
BookStack\Providers\ThemeServiceProvider::class,
|
||||
BookStack\Providers\AppServiceProvider::class,
|
||||
BookStack\Providers\AuthServiceProvider::class,
|
||||
BookStack\Providers\EventServiceProvider::class,
|
||||
BookStack\Providers\RouteServiceProvider::class,
|
||||
BookStack\Providers\TranslationServiceProvider::class,
|
||||
BookStack\Providers\ValidationRuleServiceProvider::class,
|
||||
BookStack\Providers\ViewTweaksServiceProvider::class,
|
||||
],
|
||||
|
||||
// Class Aliases
|
||||
// This array of class aliases to be registered on application start.
|
||||
'aliases' => Facade::defaultAliases()->merge([
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Class Aliases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array of class aliases will be registered when this application
|
||||
| is started. However, feel free to register as many as you wish as
|
||||
| the aliases are "lazy" loaded so they don't hinder performance.
|
||||
|
|
||||
*/
|
||||
|
||||
// Class aliases, Registered on application start
|
||||
'aliases' => [
|
||||
// Laravel
|
||||
'App' => Illuminate\Support\Facades\App::class,
|
||||
'Arr' => Illuminate\Support\Arr::class,
|
||||
'Artisan' => Illuminate\Support\Facades\Artisan::class,
|
||||
'Auth' => Illuminate\Support\Facades\Auth::class,
|
||||
'Blade' => Illuminate\Support\Facades\Blade::class,
|
||||
'Bus' => Illuminate\Support\Facades\Bus::class,
|
||||
'Cache' => Illuminate\Support\Facades\Cache::class,
|
||||
'Config' => Illuminate\Support\Facades\Config::class,
|
||||
'Cookie' => Illuminate\Support\Facades\Cookie::class,
|
||||
'Crypt' => Illuminate\Support\Facades\Crypt::class,
|
||||
'Date' => Illuminate\Support\Facades\Date::class,
|
||||
'DB' => Illuminate\Support\Facades\DB::class,
|
||||
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
|
||||
'Event' => Illuminate\Support\Facades\Event::class,
|
||||
'File' => Illuminate\Support\Facades\File::class,
|
||||
'Gate' => Illuminate\Support\Facades\Gate::class,
|
||||
'Hash' => Illuminate\Support\Facades\Hash::class,
|
||||
'Http' => Illuminate\Support\Facades\Http::class,
|
||||
'Lang' => Illuminate\Support\Facades\Lang::class,
|
||||
'Log' => Illuminate\Support\Facades\Log::class,
|
||||
'Mail' => Illuminate\Support\Facades\Mail::class,
|
||||
'Notification' => Illuminate\Support\Facades\Notification::class,
|
||||
'Password' => Illuminate\Support\Facades\Password::class,
|
||||
'Queue' => Illuminate\Support\Facades\Queue::class,
|
||||
'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class,
|
||||
'Redirect' => Illuminate\Support\Facades\Redirect::class,
|
||||
// 'Redis' => Illuminate\Support\Facades\Redis::class,
|
||||
'Request' => Illuminate\Support\Facades\Request::class,
|
||||
'Response' => Illuminate\Support\Facades\Response::class,
|
||||
'Route' => Illuminate\Support\Facades\Route::class,
|
||||
'Schema' => Illuminate\Support\Facades\Schema::class,
|
||||
'Session' => Illuminate\Support\Facades\Session::class,
|
||||
'Storage' => Illuminate\Support\Facades\Storage::class,
|
||||
'Str' => Illuminate\Support\Str::class,
|
||||
'URL' => Illuminate\Support\Facades\URL::class,
|
||||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
|
||||
// Laravel Packages
|
||||
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
|
||||
|
||||
@@ -161,7 +202,7 @@ return [
|
||||
// Custom BookStack
|
||||
'Activity' => BookStack\Facades\Activity::class,
|
||||
'Theme' => BookStack\Facades\Theme::class,
|
||||
])->toArray(),
|
||||
],
|
||||
|
||||
// Proxy configuration
|
||||
'proxies' => env('APP_PROXIES', ''),
|
||||
|
||||
@@ -59,12 +59,12 @@ return [
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => \BookStack\Users\Models\User::class,
|
||||
'model' => \BookStack\Auth\User::class,
|
||||
],
|
||||
|
||||
'external' => [
|
||||
'driver' => 'external-users',
|
||||
'model' => \BookStack\Users\Models\User::class,
|
||||
'model' => \BookStack\Auth\User::class,
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
|
||||
@@ -14,7 +14,7 @@ return [
|
||||
// This option controls the default broadcaster that will be used by the
|
||||
// framework when an event needs to be broadcast. This can be set to
|
||||
// any of the connections defined in the "connections" array below.
|
||||
'default' => 'null',
|
||||
'default' => env('BROADCAST_DRIVER', 'pusher'),
|
||||
|
||||
// Broadcast Connections
|
||||
// Here you may define all of the broadcast connections that will be used
|
||||
@@ -22,7 +22,21 @@ return [
|
||||
// each available type of connection are provided inside this array.
|
||||
'connections' => [
|
||||
|
||||
// Default options removed since we don't use broadcasting.
|
||||
'pusher' => [
|
||||
'driver' => 'pusher',
|
||||
'key' => env('PUSHER_APP_KEY'),
|
||||
'secret' => env('PUSHER_APP_SECRET'),
|
||||
'app_id' => env('PUSHER_APP_ID'),
|
||||
'options' => [
|
||||
'cluster' => env('PUSHER_APP_CLUSTER'),
|
||||
'useTLS' => true,
|
||||
],
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'driver' => 'log',
|
||||
|
||||
@@ -87,6 +87,6 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache_'),
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'),
|
||||
|
||||
];
|
||||
|
||||
@@ -33,20 +33,17 @@ return [
|
||||
'driver' => 'local',
|
||||
'root' => public_path(),
|
||||
'visibility' => 'public',
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
'local_secure_attachments' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('uploads/files/'),
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
'local_secure_images' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('uploads/images/'),
|
||||
'visibility' => 'public',
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
@@ -57,7 +54,6 @@ return [
|
||||
'bucket' => env('STORAGE_S3_BUCKET', 'your-bucket'),
|
||||
'endpoint' => env('STORAGE_S3_ENDPOINT', null),
|
||||
'use_path_style_endpoint' => env('STORAGE_S3_ENDPOINT', null) !== null,
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -21,15 +21,6 @@ return [
|
||||
// one of the channels defined in the "channels" configuration array.
|
||||
'default' => env('LOG_CHANNEL', 'single'),
|
||||
|
||||
// Deprecations Log Channel
|
||||
// This option controls the log channel that should be used to log warnings
|
||||
// regarding deprecated PHP and library features. This allows you to get
|
||||
// your application ready for upcoming major versions of dependencies.
|
||||
'deprecations' => [
|
||||
'channel' => 'null',
|
||||
'trace' => false,
|
||||
],
|
||||
|
||||
// Log Channels
|
||||
// Here you may configure the log channels for your application. Out of
|
||||
// the box, Laravel uses the Monolog PHP logging library. This gives
|
||||
|
||||
@@ -8,17 +8,19 @@
|
||||
* Do not edit this file unless you're happy to maintain any changes yourself.
|
||||
*/
|
||||
|
||||
// Configured mail encryption method.
|
||||
// STARTTLS should still be attempted, but tls/ssl forces TLS usage.
|
||||
$mailEncryption = env('MAIL_ENCRYPTION', null);
|
||||
|
||||
return [
|
||||
|
||||
// Mail driver to use.
|
||||
// From Laravel 7+ this is MAIL_MAILER in laravel.
|
||||
// Kept as MAIL_DRIVER in BookStack to prevent breaking change.
|
||||
// Options: smtp, sendmail, log, array
|
||||
'default' => env('MAIL_DRIVER', 'smtp'),
|
||||
'driver' => env('MAIL_DRIVER', 'smtp'),
|
||||
|
||||
// SMTP host address
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
|
||||
// SMTP host port
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
|
||||
// Global "From" address & name
|
||||
'from' => [
|
||||
@@ -26,44 +28,17 @@ return [
|
||||
'name' => env('MAIL_FROM_NAME', 'BookStack'),
|
||||
],
|
||||
|
||||
// Mailer Configurations
|
||||
// Available mailing methods and their settings.
|
||||
'mailers' => [
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'scheme' => null,
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'verify_peer' => env('MAIL_VERIFY_SSL', true),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN'),
|
||||
'tls_required' => ($mailEncryption === 'tls' || $mailEncryption === 'ssl'),
|
||||
],
|
||||
// Email encryption protocol
|
||||
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs'),
|
||||
],
|
||||
// SMTP server username
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
// SMTP server password
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
],
|
||||
],
|
||||
// Sendmail application path
|
||||
'sendmail' => '/usr/sbin/sendmail -bs',
|
||||
|
||||
// Email markdown configuration
|
||||
'markdown' => [
|
||||
@@ -72,4 +47,11 @@ return [
|
||||
resource_path('views/vendor/mail'),
|
||||
],
|
||||
],
|
||||
|
||||
// Log Channel
|
||||
// If you are using the "log" driver, you may specify the logging channel
|
||||
// if you prefer to keep mail messages separate from other log entries
|
||||
// for simpler reading. Otherwise, the default channel will be used.
|
||||
'log_channel' => env('MAIL_LOG_CHANNEL'),
|
||||
|
||||
];
|
||||
|
||||
@@ -8,12 +8,9 @@ return [
|
||||
// Dump user details after a login request for debugging purposes
|
||||
'dump_user_details' => env('OIDC_DUMP_USER_DETAILS', false),
|
||||
|
||||
// Claim, within an OpenId token, to find the user's display name
|
||||
// Attribute, within a OpenId token, to find the user's display name
|
||||
'display_name_claims' => explode('|', env('OIDC_DISPLAY_NAME_CLAIMS', 'name')),
|
||||
|
||||
// Claim, within an OpenID token, to use to connect a BookStack user to the OIDC user.
|
||||
'external_id_claim' => env('OIDC_EXTERNAL_ID_CLAIM', 'sub'),
|
||||
|
||||
// OAuth2/OpenId client id, as configured in your Authorization server.
|
||||
'client_id' => env('OIDC_CLIENT_ID', null),
|
||||
|
||||
|
||||
@@ -16,20 +16,11 @@ return [
|
||||
'app-editor' => 'wysiwyg',
|
||||
'app-color' => '#206ea7',
|
||||
'app-color-light' => 'rgba(32,110,167,0.15)',
|
||||
'link-color' => '#206ea7',
|
||||
'bookshelf-color' => '#a94747',
|
||||
'book-color' => '#077b70',
|
||||
'chapter-color' => '#af4d0d',
|
||||
'page-color' => '#206ea7',
|
||||
'page-draft-color' => '#7e50b1',
|
||||
'app-color-dark' => '#195785',
|
||||
'app-color-light-dark' => 'rgba(32,110,167,0.15)',
|
||||
'link-color-dark' => '#429fe3',
|
||||
'bookshelf-color-dark' => '#ff5454',
|
||||
'book-color-dark' => '#389f60',
|
||||
'chapter-color-dark' => '#ee7a2d',
|
||||
'page-color-dark' => '#429fe3',
|
||||
'page-draft-color-dark' => '#a66ce8',
|
||||
'app-custom-head' => false,
|
||||
'registration-enabled' => false,
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use BookStack\Uploads\ImageService;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CleanupImagesCommand extends Command
|
||||
class CleanupImages extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
@@ -25,49 +25,60 @@ class CleanupImagesCommand extends Command
|
||||
*/
|
||||
protected $description = 'Cleanup images and drawings';
|
||||
|
||||
protected $imageService;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param \BookStack\Uploads\ImageService $imageService
|
||||
*/
|
||||
public function __construct(ImageService $imageService)
|
||||
{
|
||||
$this->imageService = $imageService;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(ImageService $imageService): int
|
||||
public function handle()
|
||||
{
|
||||
$checkRevisions = !$this->option('all');
|
||||
$dryRun = !$this->option('force');
|
||||
$checkRevisions = $this->option('all') ? false : true;
|
||||
$dryRun = $this->option('force') ? false : true;
|
||||
|
||||
if (!$dryRun) {
|
||||
$this->warn("This operation is destructive and is not guaranteed to be fully accurate.\nEnsure you have a backup of your images.\n");
|
||||
$proceed = $this->confirm("Are you sure you want to proceed?");
|
||||
$proceed = $this->confirm("This operation is destructive and is not guaranteed to be fully accurate.\nEnsure you have a backup of your images.\nAre you sure you want to proceed?");
|
||||
if (!$proceed) {
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$deleted = $imageService->deleteUnusedImages($checkRevisions, $dryRun);
|
||||
$deleted = $this->imageService->deleteUnusedImages($checkRevisions, $dryRun);
|
||||
$deleteCount = count($deleted);
|
||||
|
||||
if ($dryRun) {
|
||||
$this->comment('Dry run, no images have been deleted');
|
||||
$this->comment('Dry run, No images have been deleted');
|
||||
$this->comment($deleteCount . ' images found that would have been deleted');
|
||||
$this->showDeletedImages($deleted);
|
||||
$this->comment('Run with -f or --force to perform deletions');
|
||||
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->showDeletedImages($deleted);
|
||||
$this->comment($deleteCount . ' images deleted');
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function showDeletedImages($paths): void
|
||||
protected function showDeletedImages($paths)
|
||||
{
|
||||
if ($this->getOutput()->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($paths) > 0) {
|
||||
$this->line('Images to delete:');
|
||||
}
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$this->line($path);
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Activity\Models\Activity;
|
||||
use BookStack\Actions\Activity;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearActivityCommand extends Command
|
||||
class ClearActivity extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
@@ -21,13 +21,27 @@ class ClearActivityCommand extends Command
|
||||
*/
|
||||
protected $description = 'Clear user activity from the system';
|
||||
|
||||
protected $activity;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param Activity $activity
|
||||
*/
|
||||
public function __construct(Activity $activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(): int
|
||||
public function handle()
|
||||
{
|
||||
Activity::query()->truncate();
|
||||
$this->activity->newQuery()->truncate();
|
||||
$this->comment('System activity cleared');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace BookStack\Console\Commands;
|
||||
use BookStack\Entities\Models\PageRevision;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearRevisionsCommand extends Command
|
||||
class ClearRevisions extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
@@ -23,14 +23,28 @@ class ClearRevisionsCommand extends Command
|
||||
*/
|
||||
protected $description = 'Clear page revisions';
|
||||
|
||||
protected $pageRevision;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param PageRevision $pageRevision
|
||||
*/
|
||||
public function __construct(PageRevision $pageRevision)
|
||||
{
|
||||
$this->pageRevision = $pageRevision;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(): int
|
||||
public function handle()
|
||||
{
|
||||
$deleteTypes = $this->option('all') ? ['version', 'update_draft'] : ['version'];
|
||||
PageRevision::query()->whereIn('type', $deleteTypes)->delete();
|
||||
$this->pageRevision->newQuery()->whereIn('type', $deleteTypes)->delete();
|
||||
$this->comment('Revisions deleted');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Activity\Models\View;
|
||||
use BookStack\Actions\View;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearViewsCommand extends Command
|
||||
class ClearViews extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
@@ -22,12 +22,21 @@ class ClearViewsCommand extends Command
|
||||
protected $description = 'Clear all view-counts for all entities';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function handle(): int
|
||||
public function __construct()
|
||||
{
|
||||
View::query()->truncate();
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
View::clearAll();
|
||||
$this->comment('Views cleared');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Tools\PermissionsUpdater;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CopyShelfPermissionsCommand extends Command
|
||||
class CopyShelfPermissions extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
@@ -25,10 +25,25 @@ class CopyShelfPermissionsCommand extends Command
|
||||
*/
|
||||
protected $description = 'Copy shelf permissions to all child books';
|
||||
|
||||
protected PermissionsUpdater $permissionsUpdater;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(PermissionsUpdater $permissionsUpdater)
|
||||
{
|
||||
$this->permissionsUpdater = $permissionsUpdater;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(PermissionsUpdater $permissionsUpdater): int
|
||||
public function handle()
|
||||
{
|
||||
$shelfSlug = $this->option('slug');
|
||||
$cascadeAll = $this->option('all');
|
||||
@@ -37,7 +52,7 @@ class CopyShelfPermissionsCommand extends Command
|
||||
if (!$cascadeAll && !$shelfSlug) {
|
||||
$this->error('Either a --slug or --all option must be provided.');
|
||||
|
||||
return 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cascadeAll) {
|
||||
@@ -48,7 +63,7 @@ class CopyShelfPermissionsCommand extends Command
|
||||
);
|
||||
|
||||
if (!$continue && !$this->hasOption('no-interaction')) {
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
$shelves = Bookshelf::query()->get(['id']);
|
||||
@@ -62,11 +77,10 @@ class CopyShelfPermissionsCommand extends Command
|
||||
}
|
||||
|
||||
foreach ($shelves as $shelf) {
|
||||
$permissionsUpdater->updateBookPermissionsFromShelf($shelf, false);
|
||||
$this->permissionsUpdater->updateBookPermissionsFromShelf($shelf, false);
|
||||
$this->info('Copied permissions for shelf [' . $shelf->id . ']');
|
||||
}
|
||||
|
||||
$this->info('Permissions copied for ' . $shelves->count() . ' shelves.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,17 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Users\Models\Role;
|
||||
use BookStack\Users\UserRepo;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
||||
|
||||
class CreateAdminCommand extends Command
|
||||
class CreateAdmin extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
@@ -30,10 +32,25 @@ class CreateAdminCommand extends Command
|
||||
*/
|
||||
protected $description = 'Add a new admin user to the system';
|
||||
|
||||
protected $userRepo;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(UserRepo $userRepo)
|
||||
{
|
||||
$this->userRepo = $userRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @throws NotFoundException
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(UserRepo $userRepo): int
|
||||
public function handle()
|
||||
{
|
||||
$details = $this->snakeCaseOptions();
|
||||
|
||||
@@ -65,17 +82,17 @@ class CreateAdminCommand extends Command
|
||||
$this->error($error);
|
||||
}
|
||||
|
||||
return 1;
|
||||
return SymfonyCommand::FAILURE;
|
||||
}
|
||||
|
||||
$user = $userRepo->createWithoutActivity($validator->validated());
|
||||
$user = $this->userRepo->createWithoutActivity($validator->validated());
|
||||
$user->attachRole(Role::getSystemRole('admin'));
|
||||
$user->email_confirmed = true;
|
||||
$user->save();
|
||||
|
||||
$this->info("Admin account with email \"{$user->email}\" successfully created!");
|
||||
|
||||
return 0;
|
||||
return SymfonyCommand::SUCCESS;
|
||||
}
|
||||
|
||||
protected function snakeCaseOptions(): array
|
||||
53
app/Console/Commands/DeleteUsers.php
Normal file
53
app/Console/Commands/DeleteUsers.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DeleteUsers extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:delete-users';
|
||||
|
||||
protected $userRepo;
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Delete users that are not "admin" or system users';
|
||||
|
||||
public function __construct(UserRepo $userRepo)
|
||||
{
|
||||
$this->userRepo = $userRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$confirm = $this->ask('This will delete all users from the system that are not "admin" or system users. Are you sure you want to continue? (Type "yes" to continue)');
|
||||
$numDeleted = 0;
|
||||
if (strtolower(trim($confirm)) === 'yes') {
|
||||
$totalUsers = User::query()->count();
|
||||
$users = User::query()->whereNull('system_name')->with('roles')->get();
|
||||
foreach ($users as $user) {
|
||||
if ($user->hasSystemRole('admin')) {
|
||||
// don't delete users with "admin" role
|
||||
continue;
|
||||
}
|
||||
$this->userRepo->destroy($user);
|
||||
$numDeleted++;
|
||||
}
|
||||
$this->info("Deleted $numDeleted of $totalUsers total users.");
|
||||
} else {
|
||||
$this->info('Exiting...');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Users\UserRepo;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DeleteUsersCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:delete-users';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Delete users that are not "admin" or system users';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(UserRepo $userRepo): int
|
||||
{
|
||||
$this->warn('This will delete all users from the system that are not "admin" or system users.');
|
||||
$confirm = $this->confirm('Are you sure you want to continue?');
|
||||
|
||||
if (!$confirm) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$totalUsers = User::query()->count();
|
||||
$numDeleted = 0;
|
||||
$users = User::query()->whereNull('system_name')->with('roles')->get();
|
||||
|
||||
foreach ($users as $user) {
|
||||
if ($user->hasSystemRole('admin')) {
|
||||
// don't delete users with "admin" role
|
||||
continue;
|
||||
}
|
||||
$userRepo->destroy($user);
|
||||
$numDeleted++;
|
||||
}
|
||||
|
||||
$this->info("Deleted $numDeleted of $totalUsers total users.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,19 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Activity\CommentRepo;
|
||||
use BookStack\Activity\Models\Comment;
|
||||
use BookStack\Actions\Comment;
|
||||
use BookStack\Actions\CommentRepo;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RegenerateCommentContentCommand extends Command
|
||||
class RegenerateCommentContent extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:regenerate-comment-content
|
||||
{--database= : The database connection to use}';
|
||||
protected $signature = 'bookstack:regenerate-comment-content {--database= : The database connection to use.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -25,18 +24,34 @@ class RegenerateCommentContentCommand extends Command
|
||||
protected $description = 'Regenerate the stored HTML of all comments';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @var CommentRepo
|
||||
*/
|
||||
public function handle(CommentRepo $commentRepo): int
|
||||
protected $commentRepo;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(CommentRepo $commentRepo)
|
||||
{
|
||||
$this->commentRepo = $commentRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$connection = DB::getDefaultConnection();
|
||||
if ($this->option('database') !== null) {
|
||||
DB::setDefaultConnection($this->option('database'));
|
||||
}
|
||||
|
||||
Comment::query()->chunk(100, function ($comments) use ($commentRepo) {
|
||||
Comment::query()->chunk(100, function ($comments) {
|
||||
foreach ($comments as $comment) {
|
||||
$comment->html = $commentRepo->commentToHtml($comment->text);
|
||||
$comment->html = $this->commentRepo->commentToHtml($comment->text);
|
||||
$comment->save();
|
||||
}
|
||||
});
|
||||
@@ -2,19 +2,18 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RegeneratePermissionsCommand extends Command
|
||||
class RegeneratePermissions extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:regenerate-permissions
|
||||
{--database= : The database connection to use}';
|
||||
protected $signature = 'bookstack:regenerate-permissions {--database= : The database connection to use.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -23,10 +22,23 @@ class RegeneratePermissionsCommand extends Command
|
||||
*/
|
||||
protected $description = 'Regenerate all system permissions';
|
||||
|
||||
protected CollapsedPermissionBuilder $permissionBuilder;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
|
||||
{
|
||||
$this->permissionBuilder = $permissionBuilder;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(JointPermissionBuilder $permissionBuilder): int
|
||||
public function handle()
|
||||
{
|
||||
$connection = DB::getDefaultConnection();
|
||||
|
||||
@@ -34,7 +46,7 @@ class RegeneratePermissionsCommand extends Command
|
||||
DB::setDefaultConnection($this->option('database'));
|
||||
}
|
||||
|
||||
$permissionBuilder->rebuildForAll();
|
||||
$this->permissionBuilder->rebuildForAll();
|
||||
|
||||
DB::setDefaultConnection($connection);
|
||||
$this->comment('Permissions regenerated');
|
||||
@@ -6,15 +6,14 @@ use BookStack\References\ReferenceStore;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RegenerateReferencesCommand extends Command
|
||||
class RegenerateReferences extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:regenerate-references
|
||||
{--database= : The database connection to use}';
|
||||
protected $signature = 'bookstack:regenerate-references {--database= : The database connection to use.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -23,10 +22,25 @@ class RegenerateReferencesCommand extends Command
|
||||
*/
|
||||
protected $description = 'Regenerate all the cross-item model reference index';
|
||||
|
||||
protected ReferenceStore $references;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(ReferenceStore $references)
|
||||
{
|
||||
$this->references = $references;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(ReferenceStore $references): int
|
||||
public function handle()
|
||||
{
|
||||
$connection = DB::getDefaultConnection();
|
||||
|
||||
@@ -34,7 +48,7 @@ class RegenerateReferencesCommand extends Command
|
||||
DB::setDefaultConnection($this->option('database'));
|
||||
}
|
||||
|
||||
$references->updateForAllPages();
|
||||
$this->references->updateForAllPages();
|
||||
|
||||
DB::setDefaultConnection($connection);
|
||||
|
||||
@@ -7,15 +7,14 @@ use BookStack\Search\SearchIndex;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RegenerateSearchCommand extends Command
|
||||
class RegenerateSearch extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:regenerate-search
|
||||
{--database= : The database connection to use}';
|
||||
protected $signature = 'bookstack:regenerate-search {--database= : The database connection to use.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -25,16 +24,32 @@ class RegenerateSearchCommand extends Command
|
||||
protected $description = 'Re-index all content for searching';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @var SearchIndex
|
||||
*/
|
||||
public function handle(SearchIndex $searchIndex): int
|
||||
protected $searchIndex;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(SearchIndex $searchIndex)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->searchIndex = $searchIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$connection = DB::getDefaultConnection();
|
||||
if ($this->option('database') !== null) {
|
||||
DB::setDefaultConnection($this->option('database'));
|
||||
}
|
||||
|
||||
$searchIndex->indexAllEntities(function (Entity $model, int $processed, int $total): void {
|
||||
$this->searchIndex->indexAllEntities(function (Entity $model, int $processed, int $total): void {
|
||||
$this->info('Indexed ' . class_basename($model) . ' entries (' . $processed . '/' . $total . ')');
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user