diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index eb233c3..f415be5 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -53,6 +53,7 @@ class UserController extends Controller $attributes = $request->validated(); $attributes['remember_token'] = Str::random(10); $attributes['password'] = Hash::make($attributes['password']); + $attributes['admin'] = $attributes['admin'] ?? false; $user->fill($attributes)->save(); session()->flash('message', "User {$user->name} updated!"); return redirect()->route('users.index'); diff --git a/app/Http/Requests/UpdateUserRequest.php b/app/Http/Requests/UpdateUserRequest.php index bdc2f8b..62e4b48 100644 --- a/app/Http/Requests/UpdateUserRequest.php +++ b/app/Http/Requests/UpdateUserRequest.php @@ -18,6 +18,7 @@ class UpdateUserRequest extends FormRequest 'name' => ['nullable', 'string'], 'password' => ['nullable', 'string', 'confirmed'], 'password_confirmation' => ['nullable', 'string'], + 'admin' => ['nullable', 'boolean'], ]; if (!$this->user) { $rules['password'] = ['required', 'string', 'confirmed']; diff --git a/app/Models/User.php b/app/Models/User.php index 680195f..9cff4d2 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -15,9 +15,11 @@ use Illuminate\Support\Facades\Auth; * App\Models\User * * @property int $id + * @property string $slug * @property string $name * @property string $username * @property string $password + * @property bool $admin * @property string|null $remember_token * @property Carbon|null $created_at * @property Carbon|null $updated_at @@ -38,6 +40,10 @@ use Illuminate\Support\Facades\Auth; * @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|User whereUsername($value) + * @method static \Illuminate\Database\Eloquent\Builder|User findSimilarSlugs(string $attribute, array $config, string $slug) + * @method static \Illuminate\Database\Eloquent\Builder|User whereAdmin($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereSlug($value) + * @method static \Illuminate\Database\Eloquent\Builder|User withUniqueSlugConstraints(\Illuminate\Database\Eloquent\Model $model, string $attribute, array $config, string $slug) * @mixin \Eloquent */ final class User extends Authenticatable @@ -51,6 +57,7 @@ final class User extends Authenticatable 'username', 'password', 'name', + 'admin', ]; /** @@ -61,6 +68,13 @@ final class User extends Authenticatable 'remember_token', ]; + /** + * @inheritdoc + */ + protected $casts = [ + 'admin' => 'bool', + ]; + /** * Get the User's goals. */ diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php index 33d7b4a..a77e091 100644 --- a/app/Policies/UserPolicy.php +++ b/app/Policies/UserPolicy.php @@ -9,11 +9,18 @@ class UserPolicy { use HandlesAuthorization; + /** + * Determine whether the user can administer (the application). + */ + public function administer(User $user): bool { + return $user->admin; + } + /** * Determine whether the user can delete the model. */ public function delete(User $user, User $model): bool { - return $user->id !== $model->id; + return $this->administer($user) && $user->id !== $model->id; } } diff --git a/database/Factories/UserFactory.php b/database/Factories/UserFactory.php index f2d1bb8..8b3192d 100644 --- a/database/Factories/UserFactory.php +++ b/database/Factories/UserFactory.php @@ -23,7 +23,16 @@ class UserFactory extends Factory 'username' => $this->faker->unique()->userName, 'password' => Hash::make('password'), 'name' => $this->faker->name, + 'admin' => $this->faker->boolean, 'remember_token' => Str::random(10), ]; } + + /** + * Create an admin user. + */ + public function admin(): static + { + return $this->state(['admin' => true]); + } } diff --git a/database/Seeders/DatabaseSeeder.php b/database/Seeders/DatabaseSeeder.php index 94e71ef..fa79562 100644 --- a/database/Seeders/DatabaseSeeder.php +++ b/database/Seeders/DatabaseSeeder.php @@ -21,7 +21,7 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - $user = User::factory()->create([ + $user = User::factory()->admin()->create([ 'username' => 'kcal', 'password' => Hash::make('kcal'), 'name' => 'Admin', diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 7d768d6..3c86ef4 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -19,6 +19,7 @@ class CreateUsersTable extends Migration $table->string('username')->unique(); $table->string('password'); $table->string('name'); + $table->boolean('admin')->default(false); $table->rememberToken(); $table->timestamps(); }); diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index d86df3c..4b53bbb 100644 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -34,7 +34,9 @@
Goals - Users + @can('administer', \App\Models\User::class) + Users + @endcan
@csrf Logout diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 1574a8b..66b6ec1 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -53,6 +53,17 @@ :hasError="$errors->has('password')" :required="!$user->exists"/>
+ + +
+ + + +
+ diff --git a/resources/views/users/index.blade.php b/resources/views/users/index.blade.php index ba99de1..1f5c8f8 100644 --- a/resources/views/users/index.blade.php +++ b/resources/views/users/index.blade.php @@ -13,6 +13,7 @@ Username Name + Admin Created Updated   @@ -23,6 +24,7 @@ {{ $user->username }} {{ $user->name }} + @if($user->admin) Yes @else No @endif {{ $user->created_at }} {{ $user->updated_at }} diff --git a/routes/admin.php b/routes/admin.php index 42ff408..18d41f9 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Route; |-------------------------------------------------------------------------- */ -Route::middleware(['auth', 'can:administer'])->group(function () { +Route::middleware(['auth', 'can:administer,\App\Models\User'])->group(function () { Route::resource('users', UserController::class); Route::get('/users/{user}/delete', [UserController::class, 'delete'])->name('users.delete'); }); diff --git a/tests/Feature/Http/Controllers/UserControllerTest.php b/tests/Feature/Http/Controllers/UserControllerTest.php index f14a751..622db8a 100644 --- a/tests/Feature/Http/Controllers/UserControllerTest.php +++ b/tests/Feature/Http/Controllers/UserControllerTest.php @@ -66,4 +66,12 @@ class UserControllerTest extends HttpControllerTestCase $response->assertForbidden(); } + public function testCanNotAccessIndexAsNonAdmin(): void { + $this->logout(); + $this->loginUser(); + $index_url = action([$this->class(), 'index']); + $response = $this->get($index_url); + $response->assertForbidden(); + } + } diff --git a/tests/LoggedInTestCase.php b/tests/LoggedInTestCase.php index fd900f1..3605f13 100644 --- a/tests/LoggedInTestCase.php +++ b/tests/LoggedInTestCase.php @@ -9,7 +9,7 @@ abstract class LoggedInTestCase extends TestCase public function setUp(): void { parent::setUp(); - $this->loginUser(); + $this->loginAdmin(); } } diff --git a/tests/LogsIn.php b/tests/LogsIn.php index 5023d4f..ec4c245 100644 --- a/tests/LogsIn.php +++ b/tests/LogsIn.php @@ -9,11 +9,12 @@ trait LogsIn protected User $user; /** - * Creates a user and logs the user in. + * Creates an admin and logs in. */ - public function loginUser(): void + public function loginAdmin(): void { $this->user = User::factory() + ->admin() ->hasGoals(2) ->hasJournalEntries(5) ->create(); @@ -22,4 +23,23 @@ trait LogsIn 'password' => 'password', ]); } + + /** + * Creates a regular user and logs in. + */ + public function loginUser(): void + { + $this->user = User::factory() + ->hasGoals(2) + ->hasJournalEntries(5) + ->create(['admin' => false]); + $this->post('/login', [ + 'username' => $this->user->username, + 'password' => 'password', + ]); + } + + public function logout(): void { + $this->post('/logout'); + } }