Skip to content

Commit 5754acf

Browse files
committed
DB: Updated handling of deleted user ID handling in DB
Updated uses of user ID to nullify on delete. Added testing to cover deletion of user relations. Added model factories to support changes and potential other tests. Cleans existing ID references in the DB via migration.
1 parent 4c7d642 commit 5754acf

20 files changed

+495
-44
lines changed

app/Access/Mfa/MfaValue.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use BookStack\Users\Models\User;
66
use Carbon\Carbon;
7+
use Illuminate\Database\Eloquent\Factories\HasFactory;
78
use Illuminate\Database\Eloquent\Model;
89

910
/**
@@ -16,6 +17,8 @@
1617
*/
1718
class MfaValue extends Model
1819
{
20+
use HasFactory;
21+
1922
protected static $unguarded = true;
2023

2124
const METHOD_TOTP = 'totp';

app/Access/SocialAccount.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@
55
use BookStack\Activity\Models\Loggable;
66
use BookStack\App\Model;
77
use BookStack\Users\Models\User;
8+
use Illuminate\Database\Eloquent\Factories\HasFactory;
9+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
810

911
/**
10-
* Class SocialAccount.
11-
*
1212
* @property string $driver
1313
* @property User $user
1414
*/
1515
class SocialAccount extends Model implements Loggable
1616
{
17-
protected $fillable = ['user_id', 'driver', 'driver_id', 'timestamps'];
17+
use HasFactory;
1818

19-
public function user()
19+
protected $fillable = ['user_id', 'driver', 'driver_id'];
20+
21+
/**
22+
* @return BelongsTo<User, $this>
23+
*/
24+
public function user(): BelongsTo
2025
{
2126
return $this->belongsTo(User::class);
2227
}

app/Activity/Models/Activity.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use BookStack\Entities\Models\Entity;
77
use BookStack\Permissions\Models\JointPermission;
88
use BookStack\Users\Models\User;
9+
use Illuminate\Database\Eloquent\Factories\HasFactory;
910
use Illuminate\Database\Eloquent\Relations\BelongsTo;
1011
use Illuminate\Database\Eloquent\Relations\HasMany;
1112
use Illuminate\Database\Eloquent\Relations\MorphTo;
@@ -24,6 +25,8 @@
2425
*/
2526
class Activity extends Model
2627
{
28+
use HasFactory;
29+
2730
/**
2831
* Get the loggable model related to this activity.
2932
* Currently only used for entities (previously entity_[id/type] columns).

app/Activity/Models/Favourite.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44

55
use BookStack\App\Model;
66
use BookStack\Permissions\Models\JointPermission;
7+
use Illuminate\Database\Eloquent\Factories\HasFactory;
78
use Illuminate\Database\Eloquent\Relations\HasMany;
89
use Illuminate\Database\Eloquent\Relations\MorphTo;
910

1011
class Favourite extends Model
1112
{
13+
use HasFactory;
14+
1215
protected $fillable = ['user_id'];
1316

1417
/**

app/Activity/Models/Watch.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BookStack\Activity\WatchLevels;
66
use BookStack\Permissions\Models\JointPermission;
77
use Carbon\Carbon;
8+
use Illuminate\Database\Eloquent\Factories\HasFactory;
89
use Illuminate\Database\Eloquent\Model;
910
use Illuminate\Database\Eloquent\Relations\HasMany;
1011
use Illuminate\Database\Eloquent\Relations\MorphTo;
@@ -20,6 +21,8 @@
2021
*/
2122
class Watch extends Model
2223
{
24+
use HasFactory;
25+
2326
protected $guarded = [];
2427

2528
public function watchable(): MorphTo

app/Entities/Models/Deletion.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use BookStack\Activity\Models\Loggable;
66
use BookStack\Users\Models\User;
7+
use Illuminate\Database\Eloquent\Factories\HasFactory;
78
use Illuminate\Database\Eloquent\Model;
89
use Illuminate\Database\Eloquent\Relations\BelongsTo;
910
use Illuminate\Database\Eloquent\Relations\MorphTo;
@@ -17,6 +18,8 @@
1718
*/
1819
class Deletion extends Model implements Loggable
1920
{
21+
use HasFactory;
22+
2023
protected $hidden = [];
2124

2225
/**

app/Entities/Models/PageRevision.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use BookStack\App\Model;
77
use BookStack\Users\Models\User;
88
use Carbon\Carbon;
9+
use Illuminate\Database\Eloquent\Factories\HasFactory;
910
use Illuminate\Database\Eloquent\Relations\BelongsTo;
1011

1112
/**
@@ -30,6 +31,8 @@
3031
*/
3132
class PageRevision extends Model implements Loggable
3233
{
34+
use HasFactory;
35+
3336
protected $fillable = ['name', 'text', 'summary'];
3437
protected $hidden = ['html', 'markdown', 'text'];
3538

app/Users/Models/User.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
use Illuminate\Support\Collection;
3232

3333
/**
34-
* Class User.
35-
*
3634
* @property int $id
3735
* @property string $name
3836
* @property string $slug

app/Users/UserRepo.php

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
use BookStack\Access\UserInviteException;
66
use BookStack\Access\UserInviteService;
77
use BookStack\Activity\ActivityType;
8-
use BookStack\Entities\EntityProvider;
9-
use BookStack\Entities\Models\Entity;
108
use BookStack\Exceptions\NotifyException;
119
use BookStack\Exceptions\UserUpdateException;
1210
use BookStack\Facades\Activity;
@@ -27,7 +25,6 @@ public function __construct(
2725
) {
2826
}
2927

30-
3128
/**
3229
* Get a user by their email address.
3330
*/
@@ -161,15 +158,12 @@ public function update(User $user, array $data, bool $manageUsersAllowed): User
161158
*
162159
* @throws Exception
163160
*/
164-
public function destroy(User $user, ?int $newOwnerId = null)
161+
public function destroy(User $user, ?int $newOwnerId = null): void
165162
{
166163
$this->ensureDeletable($user);
167164

168-
$user->socialAccounts()->delete();
169-
$user->apiTokens()->delete();
170-
$user->favourites()->delete();
171-
$user->mfaValues()->delete();
172-
$user->watches()->delete();
165+
$this->removeUserDependantRelations($user);
166+
$this->nullifyUserNonDependantRelations($user);
173167
$user->delete();
174168

175169
// Delete user profile images
@@ -178,17 +172,53 @@ public function destroy(User $user, ?int $newOwnerId = null)
178172
// Delete related activities
179173
setting()->deleteUserSettings($user->id);
180174

175+
// Migrate or nullify ownership
176+
$newOwner = null;
181177
if (!empty($newOwnerId)) {
182178
$newOwner = User::query()->find($newOwnerId);
183-
if (!is_null($newOwner)) {
184-
$this->migrateOwnership($user, $newOwner);
185-
}
186-
// TODO - Should be be nullifying ownership instead?
187179
}
180+
$this->migrateOwnership($user, $newOwner);
188181

189182
Activity::add(ActivityType::USER_DELETE, $user);
190183
}
191184

185+
protected function removeUserDependantRelations(User $user): void
186+
{
187+
$user->apiTokens()->delete();
188+
$user->socialAccounts()->delete();
189+
$user->favourites()->delete();
190+
$user->mfaValues()->delete();
191+
$user->watches()->delete();
192+
193+
$tables = ['email_confirmations', 'user_invites', 'views'];
194+
foreach ($tables as $table) {
195+
DB::table($table)->where('user_id', '=', $user->id)->delete();
196+
}
197+
}
198+
protected function nullifyUserNonDependantRelations(User $user): void
199+
{
200+
$toNullify = [
201+
'activities' => ['user_id'],
202+
'attachments' => ['created_by', 'updated_by'],
203+
'comments' => ['created_by', 'updated_by'],
204+
'deletions' => ['deleted_by'],
205+
'entities' => ['created_by', 'updated_by'],
206+
'images' => ['created_by', 'updated_by'],
207+
'imports' => ['created_by'],
208+
'joint_permissions' => ['owner_id'],
209+
'page_revisions' => ['created_by'],
210+
'sessions' => ['user_id'],
211+
];
212+
213+
foreach ($toNullify as $table => $columns) {
214+
foreach ($columns as $column) {
215+
DB::table($table)
216+
->where($column, '=', $user->id)
217+
->update([$column => null]);
218+
}
219+
}
220+
}
221+
192222
/**
193223
* @throws NotifyException
194224
*/
@@ -206,11 +236,12 @@ protected function ensureDeletable(User $user): void
206236
/**
207237
* Migrate ownership of items in the system from one user to another.
208238
*/
209-
protected function migrateOwnership(User $fromUser, User $toUser): void
239+
protected function migrateOwnership(User $fromUser, User|null $toUser): void
210240
{
241+
$newOwnerValue = $toUser ? $toUser->id : null;
211242
DB::table('entities')
212243
->where('owned_by', '=', $fromUser->id)
213-
->update(['owned_by' => $toUser->id]);
244+
->update(['owned_by' => $newOwnerValue]);
214245
}
215246

216247
/**
@@ -248,7 +279,7 @@ protected function isOnlyAdmin(User $user): bool
248279
*
249280
* @throws UserUpdateException
250281
*/
251-
protected function setUserRoles(User $user, array $roles)
282+
protected function setUserRoles(User $user, array $roles): void
252283
{
253284
$roles = array_filter(array_values($roles));
254285

@@ -261,7 +292,7 @@ protected function setUserRoles(User $user, array $roles)
261292

262293
/**
263294
* Check if the given user is the last admin and their new roles no longer
264-
* contains the admin role.
295+
* contain the admin role.
265296
*/
266297
protected function demotingLastAdmin(User $user, array $newRoles): bool
267298
{
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Database\Factories\Access\Mfa;
4+
5+
use BookStack\Users\Models\User;
6+
use Illuminate\Database\Eloquent\Factories\Factory;
7+
8+
/**
9+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Access\Mfa\MfaValue>
10+
*/
11+
class MfaValueFactory extends Factory
12+
{
13+
protected $model = \BookStack\Access\Mfa\MfaValue::class;
14+
15+
/**
16+
* Define the model's default state.
17+
*
18+
* @return array<string, mixed>
19+
*/
20+
public function definition(): array
21+
{
22+
return [
23+
'user_id' => User::factory(),
24+
'method' => 'totp',
25+
'value' => '123456',
26+
];
27+
}
28+
}

0 commit comments

Comments
 (0)