Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/Access/Mfa/MfaValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use BookStack\Users\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

/**
Expand All @@ -16,6 +17,8 @@
*/
class MfaValue extends Model
{
use HasFactory;

protected static $unguarded = true;

const METHOD_TOTP = 'totp';
Expand Down
13 changes: 9 additions & 4 deletions app/Access/SocialAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@
use BookStack\Activity\Models\Loggable;
use BookStack\App\Model;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
* Class SocialAccount.
*
* @property string $driver
* @property User $user
*/
class SocialAccount extends Model implements Loggable
{
protected $fillable = ['user_id', 'driver', 'driver_id', 'timestamps'];
use HasFactory;

public function user()
protected $fillable = ['user_id', 'driver', 'driver_id'];

/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
Expand Down
3 changes: 3 additions & 0 deletions app/Activity/Models/Activity.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use BookStack\Entities\Models\Entity;
use BookStack\Permissions\Models\JointPermission;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
Expand All @@ -24,6 +25,8 @@
*/
class Activity extends Model
{
use HasFactory;

/**
* Get the loggable model related to this activity.
* Currently only used for entities (previously entity_[id/type] columns).
Expand Down
3 changes: 3 additions & 0 deletions app/Activity/Models/Favourite.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

use BookStack\App\Model;
use BookStack\Permissions\Models\JointPermission;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class Favourite extends Model
{
use HasFactory;

protected $fillable = ['user_id'];

/**
Expand Down
3 changes: 3 additions & 0 deletions app/Activity/Models/Watch.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use BookStack\Activity\WatchLevels;
use BookStack\Permissions\Models\JointPermission;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
Expand All @@ -20,6 +21,8 @@
*/
class Watch extends Model
{
use HasFactory;

protected $guarded = [];

public function watchable(): MorphTo
Expand Down
3 changes: 3 additions & 0 deletions app/Entities/Models/Deletion.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use BookStack\Activity\Models\Loggable;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
Expand All @@ -17,6 +18,8 @@
*/
class Deletion extends Model implements Loggable
{
use HasFactory;

protected $hidden = [];

/**
Expand Down
3 changes: 3 additions & 0 deletions app/Entities/Models/PageRevision.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use BookStack\App\Model;
use BookStack\Users\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
Expand All @@ -30,6 +31,8 @@
*/
class PageRevision extends Model implements Loggable
{
use HasFactory;

protected $fillable = ['name', 'text', 'summary'];
protected $hidden = ['html', 'markdown', 'text'];

Expand Down
2 changes: 0 additions & 2 deletions app/Users/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
use Illuminate\Support\Collection;

/**
* Class User.
*
* @property int $id
* @property string $name
* @property string $slug
Expand Down
64 changes: 47 additions & 17 deletions app/Users/UserRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
use BookStack\Access\UserInviteException;
use BookStack\Access\UserInviteService;
use BookStack\Activity\ActivityType;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\Entity;
use BookStack\Exceptions\NotifyException;
use BookStack\Exceptions\UserUpdateException;
use BookStack\Facades\Activity;
Expand All @@ -27,7 +25,6 @@ public function __construct(
) {
}


/**
* Get a user by their email address.
*/
Expand Down Expand Up @@ -161,15 +158,12 @@ public function update(User $user, array $data, bool $manageUsersAllowed): User
*
* @throws Exception
*/
public function destroy(User $user, ?int $newOwnerId = null)
public function destroy(User $user, ?int $newOwnerId = null): void
{
$this->ensureDeletable($user);

$user->socialAccounts()->delete();
$user->apiTokens()->delete();
$user->favourites()->delete();
$user->mfaValues()->delete();
$user->watches()->delete();
$this->removeUserDependantRelations($user);
$this->nullifyUserNonDependantRelations($user);
$user->delete();

// Delete user profile images
Expand All @@ -178,17 +172,52 @@ public function destroy(User $user, ?int $newOwnerId = null)
// Delete related activities
setting()->deleteUserSettings($user->id);

// Migrate or nullify ownership
$newOwner = null;
if (!empty($newOwnerId)) {
$newOwner = User::query()->find($newOwnerId);
if (!is_null($newOwner)) {
$this->migrateOwnership($user, $newOwner);
}
// TODO - Should be be nullifying ownership instead?
}
$this->migrateOwnership($user, $newOwner);

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

protected function removeUserDependantRelations(User $user): void
{
$user->apiTokens()->delete();
$user->socialAccounts()->delete();
$user->favourites()->delete();
$user->mfaValues()->delete();
$user->watches()->delete();

$tables = ['email_confirmations', 'user_invites', 'views'];
foreach ($tables as $table) {
DB::table($table)->where('user_id', '=', $user->id)->delete();
}
}
protected function nullifyUserNonDependantRelations(User $user): void
{
$toNullify = [
'attachments' => ['created_by', 'updated_by'],
'comments' => ['created_by', 'updated_by'],
'deletions' => ['deleted_by'],
'entities' => ['created_by', 'updated_by'],
'images' => ['created_by', 'updated_by'],
'imports' => ['created_by'],
'joint_permissions' => ['owner_id'],
'page_revisions' => ['created_by'],
'sessions' => ['user_id'],
];

foreach ($toNullify as $table => $columns) {
foreach ($columns as $column) {
DB::table($table)
->where($column, '=', $user->id)
->update([$column => null]);
}
}
}

/**
* @throws NotifyException
*/
Expand All @@ -206,11 +235,12 @@ protected function ensureDeletable(User $user): void
/**
* Migrate ownership of items in the system from one user to another.
*/
protected function migrateOwnership(User $fromUser, User $toUser): void
protected function migrateOwnership(User $fromUser, User|null $toUser): void
{
$newOwnerValue = $toUser ? $toUser->id : null;
DB::table('entities')
->where('owned_by', '=', $fromUser->id)
->update(['owned_by' => $toUser->id]);
->update(['owned_by' => $newOwnerValue]);
}

/**
Expand Down Expand Up @@ -248,7 +278,7 @@ protected function isOnlyAdmin(User $user): bool
*
* @throws UserUpdateException
*/
protected function setUserRoles(User $user, array $roles)
protected function setUserRoles(User $user, array $roles): void
{
$roles = array_filter(array_values($roles));

Expand All @@ -261,7 +291,7 @@ protected function setUserRoles(User $user, array $roles)

/**
* Check if the given user is the last admin and their new roles no longer
* contains the admin role.
* contain the admin role.
*/
protected function demotingLastAdmin(User $user, array $newRoles): bool
{
Expand Down
28 changes: 28 additions & 0 deletions database/factories/Access/Mfa/MfaValueFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Database\Factories\Access\Mfa;

use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Access\Mfa\MfaValue>
*/
class MfaValueFactory extends Factory
{
protected $model = \BookStack\Access\Mfa\MfaValue::class;

/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'method' => 'totp',
'value' => '123456',
];
}
}
29 changes: 29 additions & 0 deletions database/factories/Access/SocialAccountFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Database\Factories\Access;

use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Access\SocialAccount>
*/
class SocialAccountFactory extends Factory
{
protected $model = \BookStack\Access\SocialAccount::class;

/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'driver' => 'github',
'driver_id' => '123456',
'avatar' => '',
];
}
}
34 changes: 34 additions & 0 deletions database/factories/Activity/Models/ActivityFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Database\Factories\Activity\Models;

use BookStack\Activity\ActivityType;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Activity\Models\Activity>
*/
class ActivityFactory extends Factory
{
protected $model = \BookStack\Activity\Models\Activity::class;

/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$activities = ActivityType::all();
$activity = $activities[array_rand($activities)];
return [
'type' => $activity,
'detail' => 'Activity detail for ' . $activity,
'user_id' => User::factory(),
'ip' => $this->faker->ipv4(),
'loggable_id' => null,
'loggable_type' => null,
];
}
}
31 changes: 31 additions & 0 deletions database/factories/Activity/Models/FavouriteFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Database\Factories\Activity\Models;

use BookStack\Entities\Models\Book;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Activity\Models\Favourite>
*/
class FavouriteFactory extends Factory
{
protected $model = \BookStack\Activity\Models\Favourite::class;

/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$book = Book::query()->first();

return [
'user_id' => User::factory(),
'favouritable_id' => $book->id,
'favouritable_type' => 'book',
];
}
}
Loading