diff --git a/app/Attachments/AttachmentWriter.php b/app/Attachments/AttachmentWriter.php index e68a6ff..560a0b3 100644 --- a/app/Attachments/AttachmentWriter.php +++ b/app/Attachments/AttachmentWriter.php @@ -2,7 +2,6 @@ namespace App\Attachments; -use Illuminate\Http\UploadedFile; use Livewire\Wireable; interface AttachmentWriter extends Wireable diff --git a/app/Auth/Registration/Registrant.php b/app/Auth/Registration/Registrant.php index 9480304..c82e414 100644 --- a/app/Auth/Registration/Registrant.php +++ b/app/Auth/Registration/Registrant.php @@ -7,7 +7,7 @@ class Registrant public function __construct( private string $email, - private bool $isAdmin + private int $permissionLevel ) { } @@ -16,9 +16,9 @@ public function getEmail(): string return $this->email; } - public function isAdmin(): bool + public function getPermissionLevel(): int { - return $this->isAdmin; + return $this->permissionLevel; } } diff --git a/app/Auth/Registration/RegistrationTokenRepository.php b/app/Auth/Registration/RegistrationTokenRepository.php index 358e88d..b401a42 100644 --- a/app/Auth/Registration/RegistrationTokenRepository.php +++ b/app/Auth/Registration/RegistrationTokenRepository.php @@ -58,7 +58,7 @@ private function createRecord(Registrant $registrant, #[\SensitiveParameter] str { return [ 'email' => $registrant->getEmail(), - 'is_admin' => $registrant->isAdmin(), + 'permission_level' => $registrant->getPermissionLevel(), 'token' => Hash::make($token), 'created_at' => new Carbon ]; @@ -81,7 +81,7 @@ private function tokenExpired($createdAt) private function readRecord($record): Registrant { - return new Registrant($record['email'], $record['is_admin']); + return new Registrant($record['email'], $record['permission_level']); } public function deleteExisting(string $email): bool diff --git a/app/Http/Middleware/AdminAuthorization.php b/app/Http/Middleware/AdminAuthorization.php new file mode 100644 index 0000000..49bfe82 --- /dev/null +++ b/app/Http/Middleware/AdminAuthorization.php @@ -0,0 +1,14 @@ +hasHeader('Authorization'); + } + + public static function getAuthenticatedUser(Request $request): User|false + { + $credentials = base64_decode(substr($request->header('Authorization'), 6)); + list($username, $password) = explode(':', $credentials); + $user = User::where('email', $username)->first(); + if (isset($user) and Hash::check($password, $user->password)) { + return $user; + } + return false; + } + +} diff --git a/app/Http/Middleware/IsAdminMiddleware.php b/app/Http/Middleware/IsAdminMiddleware.php deleted file mode 100644 index c91d006..0000000 --- a/app/Http/Middleware/IsAdminMiddleware.php +++ /dev/null @@ -1,23 +0,0 @@ -check() && auth()->user()->is_admin) { - return $next($request); - } - abort(Response::HTTP_FORBIDDEN); - } -} diff --git a/app/Http/Middleware/MavenEditorAuthorization.php b/app/Http/Middleware/MavenEditorAuthorization.php new file mode 100644 index 0000000..0fabf3d --- /dev/null +++ b/app/Http/Middleware/MavenEditorAuthorization.php @@ -0,0 +1,14 @@ +check()) { + $user = auth()->user(); + } else { + // Fallback to basic Authorization if no standard auth + $user = BasicAuthorization::getAuthenticatedUser($request); + } + if (!isset($user) or !$user->hasPermission($this->getRequiredLevel())) { + abort(Response::HTTP_FORBIDDEN); + } + return $next($request); + } +} diff --git a/app/Livewire/Auth/Register.php b/app/Livewire/Auth/Register.php index 4b753d3..88583f9 100644 --- a/app/Livewire/Auth/Register.php +++ b/app/Livewire/Auth/Register.php @@ -12,6 +12,7 @@ use Livewire\Attributes\Locked; use Livewire\Attributes\Title; use Livewire\Component; +use const App\Models\USER_LEVEL; #[Title('Register')] #[Layout('components.layouts.auth')] @@ -60,8 +61,8 @@ public function register(): void // Since the user needs a token to register, we can consider this an email verification $user->markEmailAsVerified(); - if ($registrant->isAdmin()) { - $user->is_admin = true; + if ($registrant->getPermissionLevel() > USER_LEVEL) { + $user->permission_level = $registrant->getPermissionLevel(); $user->save(); } RegistrationTokenRepository::get()->deleteExisting($this->email); diff --git a/app/Livewire/Maven/DirectoryIndex.php b/app/Livewire/Maven/DirectoryIndex.php index 00efd9b..90c7a57 100644 --- a/app/Livewire/Maven/DirectoryIndex.php +++ b/app/Livewire/Maven/DirectoryIndex.php @@ -17,10 +17,13 @@ public function mount(?string $path = '') { $this->isRoot = $path == ''; $this->path = $path; - if (!Storage::directoryExists("maven$path")) { - return redirect(route('maven.download', $path)); + if (!Storage::disk('maven')->exists($path)) { + abort(404); } - $fullPath = Storage::path("maven/$path"); + if (!Storage::disk('maven')->directoryExists($path)) { + return redirect(route('maven.download', substr($path, 1))); + } + $fullPath = Storage::disk('maven')->path($path); foreach (new DirectoryIterator($fullPath) as $file) { if ($file->isDot()) continue; $path = $file->getRealPath(); diff --git a/app/Livewire/Posts/EditPost.php b/app/Livewire/Posts/EditPost.php index 56b1a81..01ea290 100644 --- a/app/Livewire/Posts/EditPost.php +++ b/app/Livewire/Posts/EditPost.php @@ -4,7 +4,6 @@ use App\Models\Post; use Illuminate\Validation\Rule; -use Livewire\Attributes\Title; use Livewire\Component; use Throwable; diff --git a/app/Livewire/Projects/EditProject.php b/app/Livewire/Projects/EditProject.php index 46f6422..9e444ca 100644 --- a/app/Livewire/Projects/EditProject.php +++ b/app/Livewire/Projects/EditProject.php @@ -3,7 +3,6 @@ namespace App\Livewire\Projects; use App\Models\Project; -use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Log; use Illuminate\Validation\Rule; use Livewire\Component; diff --git a/app/Livewire/Users/UserCell.php b/app/Livewire/Users/UserCell.php index 9b4731e..07651b2 100644 --- a/app/Livewire/Users/UserCell.php +++ b/app/Livewire/Users/UserCell.php @@ -7,19 +7,20 @@ class UserCell extends Component { + public User $user; - public bool $isAdmin; + public int $permissionLevel; public function mount(User $user) { $this->user = $user; - $this->isAdmin = $user->is_admin; + $this->permissionLevel = $user->permission_level; } - public function updateIsAdmin(): void + public function updatePermissionLevel(): void { $this->authorize('update', $this->user); - $this->user->is_admin = $this->isAdmin; + $this->user->permission_level = $this->permissionLevel; $this->user->save(); } diff --git a/app/Livewire/Users/UserIndex.php b/app/Livewire/Users/UserIndex.php index 0fb57eb..9ad9c4e 100644 --- a/app/Livewire/Users/UserIndex.php +++ b/app/Livewire/Users/UserIndex.php @@ -16,7 +16,7 @@ class UserIndex extends Component public $users; public string $newUserEmail = ''; - public bool $newUserIsAdmin = false; + public int $newUserPermissionLevel = 0; public function mount(): void { @@ -29,7 +29,7 @@ public function add(): void 'newUserEmail' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class.',email'] ]); - $token = RegistrationTokenRepository::get()->create(new Registrant($this->newUserEmail, $this->newUserIsAdmin)); + $token = RegistrationTokenRepository::get()->create(new Registrant($this->newUserEmail, $this->newUserPermissionLevel)); Mail::to($this->newUserEmail)->send(new Registration($this->newUserEmail, $token)); @@ -38,7 +38,7 @@ public function add(): void // Close modal and reset fields $this->modal('addUser')->close(); $this->newUserEmail = ''; - $this->newUserIsAdmin = false; + $this->newUserPermissionLevel = 0; } } diff --git a/app/Models/User.php b/app/Models/User.php index 3cb5ccb..9f4269f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -8,6 +8,12 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Support\Str; +const ADMIN_LEVEL = 2; +const MAVEN_EDITOR_LEVEL = 1; +const USER_LEVEL = 0; + +const PERMISSION_LEVEL_DISPLAY_NAMES = ['User', 'Maven Editor', 'Admin']; + class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ @@ -58,4 +64,18 @@ public function initials(): string ->map(fn ($word) => Str::substr($word, 0, 1)) ->implode(''); } + + public function hasPermission(int $level) { + return $this->permission_level >= $level; + } + + public function isMavenEditor(): bool + { + return $this->permission_level >= MAVEN_EDITOR_LEVEL; + } + + public function isAdmin(): bool + { + return $this->permission_level == ADMIN_LEVEL; + } } diff --git a/app/Policies/PostPolicy.php b/app/Policies/PostPolicy.php index 4e0025a..b5c9bcd 100644 --- a/app/Policies/PostPolicy.php +++ b/app/Policies/PostPolicy.php @@ -4,7 +4,6 @@ use App\Models\Post; use App\Models\User; -use Illuminate\Auth\Access\Response; /** * Post policy is quite simple at the moment: any users can view any posts, and admins can use any CRUD operation on @@ -33,7 +32,7 @@ public function view(User $user, Post $post): bool */ public function create(User $user): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } /** @@ -41,7 +40,7 @@ public function create(User $user): bool */ public function update(User $user, Post $post): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } /** @@ -49,6 +48,6 @@ public function update(User $user, Post $post): bool */ public function delete(User $user, Post $post): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } } diff --git a/app/Policies/ProjectPolicy.php b/app/Policies/ProjectPolicy.php index 8e4bc0d..e599e0b 100644 --- a/app/Policies/ProjectPolicy.php +++ b/app/Policies/ProjectPolicy.php @@ -32,7 +32,7 @@ public function view(User $user, Project $project): bool */ public function create(User $user): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } /** @@ -40,7 +40,7 @@ public function create(User $user): bool */ public function update(User $user, Project $project): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } /** @@ -48,6 +48,6 @@ public function update(User $user, Project $project): bool */ public function delete(User $user, Project $project): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } } diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php index 20641d9..fce092e 100644 --- a/app/Policies/UserPolicy.php +++ b/app/Policies/UserPolicy.php @@ -12,7 +12,7 @@ class UserPolicy */ public function viewAny(User $user): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } /** @@ -20,7 +20,7 @@ public function viewAny(User $user): bool */ public function view(User $user, User $model): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } /** @@ -28,7 +28,7 @@ public function view(User $user, User $model): bool */ public function create(User $user): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } /** @@ -36,7 +36,7 @@ public function create(User $user): bool */ public function update(User $user, User $model): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } /** @@ -44,7 +44,7 @@ public function update(User $user, User $model): bool */ public function delete(User $user, User $model): bool { - return isset($user) && $user->is_admin; + return isset($user) && $user->isAdmin(); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 937b7fe..531f377 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -21,7 +21,7 @@ public function register(): void public function boot(): void { Blade::if('isAdmin', function () { - return auth()->check() && auth()->user()->is_admin; + return auth()->check() && auth()->user()->isAdmin(); }); } } diff --git a/app/helpers.php b/app/helpers.php index e17e126..05b860b 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,11 +1,13 @@ first(); - return $user && Hash::check($password, $user->password); +function upload_to_maven($file, $path) { + $upload = fopen($path, 'w'); + $contents = $file instanceof UploadedFile ? $file->getContent() : stream_get_contents($file); + if (!$upload || !fwrite($upload, $contents)) { + return false; + } + fclose($upload); + return true; } diff --git a/bootstrap/app.php b/bootstrap/app.php index cbc3269..cf53dcd 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,5 +1,8 @@ withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: [ 'maven/*' + ])->alias([ + 'basic_auth' => BasicAuthorization::class, + 'maven_editor_auth' => MavenEditorAuthorization::class, + 'admin_auth' => AdminAuthorization::class ]); }) ->withExceptions(function (Exceptions $exceptions) { diff --git a/config/filesystems.php b/config/filesystems.php index b2326a8..2001572 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -72,6 +72,12 @@ 'visibility' => 'public' ], + 'maven' => [ + 'driver' => 'local', + 'root' => storage_path('maven'), + 'visibility' => 'public', + ], + ], /* diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 8dd8a17..6772aaf 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -5,6 +5,9 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; +use const App\Models\ADMIN_LEVEL; +use const App\Models\MAVEN_EDITOR_LEVEL; +use const App\Models\USER_LEVEL; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> @@ -29,7 +32,7 @@ public function definition(): array 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), 'remember_token' => Str::random(10), - 'is_admin' => false + 'permission_level' => 0 ]; } @@ -43,10 +46,24 @@ public function unverified(): static ]); } + public function mavenUser(): static + { + return $this->state(fn (array $attributes) => [ + 'permission_level' => USER_LEVEL, + ]); + } + + public function mavenEditor() + { + return $this->static(fn (array $attributes) => [ + 'permission_level' => MAVEN_EDITOR_LEVEL, + ]); + } + public function admin(): static { return $this->state(fn (array $attributes) => [ - 'is_admin' => true, + 'permission_level' => ADMIN_LEVEL, ]); } } diff --git a/database/migrations/2026_02_12_195742_replace_is_admin_with_permission_level_in_users_table.php b/database/migrations/2026_02_12_195742_replace_is_admin_with_permission_level_in_users_table.php new file mode 100644 index 0000000..ec65c6d --- /dev/null +++ b/database/migrations/2026_02_12_195742_replace_is_admin_with_permission_level_in_users_table.php @@ -0,0 +1,36 @@ +renameColumn('is_admin', 'permission_level'); + $table->integer('permission_level')->unsigned()->default(0)->change(); + }); + DB::table('users') + ->where('permission_level', '=', 1) + ->update(['permission_level' => 2]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->renameColumn('permission_level', 'is_admin'); + $table->boolean('is_admin')->default(false)->change(); + }); + DB::table('users') + ->where('is_admin', '>', 1) + ->update(['is_admin' => 1]); + } +}; diff --git a/database/migrations/2026_02_14_170418_replace_is_admin_with_permission_level_in_registration_tokens_table.php b/database/migrations/2026_02_14_170418_replace_is_admin_with_permission_level_in_registration_tokens_table.php new file mode 100644 index 0000000..7742e9d --- /dev/null +++ b/database/migrations/2026_02_14_170418_replace_is_admin_with_permission_level_in_registration_tokens_table.php @@ -0,0 +1,36 @@ +renameColumn('is_admin', 'permission_level'); + $table->integer('permission_level')->unsigned()->default(0)->change(); + }); + DB::table('registration_tokens') + ->where('permission_level', '=', 1) + ->update(['permission_level' => 2]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('registration_tokens', function (Blueprint $table) { + $table->renameColumn('permission_level', 'is_admin'); + $table->boolean('is_admin')->default(false)->change(); + }); + DB::table('registration_tokens') + ->where('is_admin', '>', 1) + ->update(['is_admin' => 1]); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index bcaec90..cf82843 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -5,6 +5,7 @@ use App\Models\User; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Hash; +use const App\Models\ADMIN_LEVEL; class DatabaseSeeder extends Seeder { @@ -19,7 +20,7 @@ public function run(): void 'name' => 'Harley O\'Connor', 'email' => 'admin@harleyoconnor.com', 'password' => Hash::make($password), - 'is_admin' => true, + 'permission_level' => ADMIN_LEVEL, ]); } } diff --git a/resources/views/livewire/users/user-cell.blade.php b/resources/views/livewire/users/user-cell.blade.php index 804d7fd..9a64ac3 100644 --- a/resources/views/livewire/users/user-cell.blade.php +++ b/resources/views/livewire/users/user-cell.blade.php @@ -1,3 +1,4 @@ +@php use const App\Models\PERMISSION_LEVEL_DISPLAY_NAMES; @endphp
{{ $user->name }} @@ -5,11 +6,20 @@
@if (auth()->user() && $user->id == auth()->user()->id) - - + (you) @else - - + + {{ PERMISSION_LEVEL_DISPLAY_NAMES[$permissionLevel] }} + + + @for($i = 0; $i < sizeof(PERMISSION_LEVEL_DISPLAY_NAMES); $i++) + {{ PERMISSION_LEVEL_DISPLAY_NAMES[$i] }} + @endfor + + + + @endif
diff --git a/resources/views/livewire/users/user-index.blade.php b/resources/views/livewire/users/user-index.blade.php index e10c88f..bc65864 100644 --- a/resources/views/livewire/users/user-index.blade.php +++ b/resources/views/livewire/users/user-index.blade.php @@ -1,3 +1,4 @@ +@php use const App\Models\PERMISSION_LEVEL_DISPLAY_NAMES; @endphp
Manage users @@ -21,7 +22,18 @@ - + + + {{ PERMISSION_LEVEL_DISPLAY_NAMES[$newUserPermissionLevel] }} + + + + @for($i = 0; $i < sizeof(PERMISSION_LEVEL_DISPLAY_NAMES); $i++) + {{ PERMISSION_LEVEL_DISPLAY_NAMES[$i] }} + @endfor + + +
diff --git a/routes/maven.php b/routes/maven.php index 24f6807..b4960e0 100644 --- a/routes/maven.php +++ b/routes/maven.php @@ -1,23 +1,15 @@ fileExists($path)) { return response('File not found', 404); } - return Storage::download("maven/$path"); + return Storage::disk('maven')->download($path); })->where('path', '.*')->name('maven.download'); // redirects to download route if path points to file @@ -26,26 +18,24 @@ function upload_to_maven($file, $path) { ->name('maven.directory-index'); // route used by publishing plugin to upload maven files -// TODO: permissions for maven publishing -Route::put('maven/{path}', function ($path) { - if (!authenticate_http_user()) { - return response('Unauthorized', 401); - } +Route::middleware(['basic_auth', 'maven_editor_auth']) + ->put('maven/{path}', function (Request $request, $path) { // Get the file from the put request - $file = fopen('php://input', 'r'); + $file = $request->file('file'); if (!$file) { - return response('Error reading file', 500); + $file = fopen('php://input', 'r'); + if (!$file) { + return response('Error reading file', 500); + } } - $path = Storage::path("maven/$path"); + $path = Storage::disk('maven')->path($path); // Make relevant directories if they do not already exist if (!is_dir(dirname($path))) { mkdir(substr($path, 0, strrpos($path, '/') + 1), 0755, true); } if (!upload_to_maven($file, $path)) { - fclose($file); return response('Error uploading file', 500); } - fclose($file); return response('File successfully uploaded', 200); })->where('path', '.*'); diff --git a/routes/web.php b/routes/web.php index ee170e1..8be5d19 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,5 @@ name('settings.password'); Route::get('settings/appearance', Appearance::class)->name('settings.appearance'); - Route::middleware(IsAdminMiddleware::class)->group(function () { + Route::middleware(['admin_auth'])->group(function () { Route::view('management', 'private.dashboard')->name('dashboard'); Route::get('management/portfolio', ProjectIndex::class)->name('management.portfolio.index'); Route::get('management/portfolio/create', CreateProject::class)->name('management.portfolio.create'); @@ -49,4 +48,4 @@ }); require __DIR__.'/auth.php'; -require_once __DIR__.'/maven.php'; +require __DIR__.'/maven.php'; diff --git a/tests/Feature/Auth/RegistrationTest.php b/tests/Feature/Auth/RegistrationTest.php index db7ef68..ef1d451 100644 --- a/tests/Feature/Auth/RegistrationTest.php +++ b/tests/Feature/Auth/RegistrationTest.php @@ -4,11 +4,12 @@ use App\Livewire\Auth\Register; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Livewire\Livewire; use Tests\TestCase; +use const App\Models\ADMIN_LEVEL; +use const App\Models\MAVEN_EDITOR_LEVEL; class RegistrationTest extends TestCase { @@ -35,7 +36,7 @@ public function test_new_users_can_register_with_token(): void { DB::table('registration_tokens')->insert([ 'email' => 'test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now() ]); @@ -53,7 +54,33 @@ public function test_new_users_can_register_with_token(): void $this->assertAuthenticated(); $this->assertDatabaseHas('users', [ - 'is_admin' => false + 'permission_level' => 0 + ]); + } + + public function test_new_users_granted_maven_editor_when_set(): void + { + DB::table('registration_tokens')->insert([ + 'email' => 'test@email.com', + 'permission_level' => MAVEN_EDITOR_LEVEL, + 'token' => Hash::make('test-token'), + 'created_at' => now() + ]); + + $response = Livewire::withQueryParams(['email' => 'test@email.com']) + ->test(Register::class, ['token' => 'test-token']) + ->set('name', 'Test User') + ->set('password', 'password') + ->set('password_confirmation', 'password') + ->call('register'); + + $response + ->assertHasNoErrors() + ->assertRedirect(route('dashboard', absolute: false)); + + $this->assertAuthenticated(); + $this->assertDatabaseHas('users', [ + 'permission_level' => MAVEN_EDITOR_LEVEL ]); } @@ -61,7 +88,7 @@ public function test_new_users_granted_admin_when_set(): void { DB::table('registration_tokens')->insert([ 'email' => 'test@email.com', - 'is_admin' => true, + 'permission_level' => ADMIN_LEVEL, 'token' => Hash::make('test-token'), 'created_at' => now() ]); @@ -79,7 +106,7 @@ public function test_new_users_granted_admin_when_set(): void $this->assertAuthenticated(); $this->assertDatabaseHas('users', [ - 'is_admin' => true + 'permission_level' => ADMIN_LEVEL ]); } } diff --git a/tests/Feature/Blog/ShowBlogTest.php b/tests/Feature/Blog/ShowBlogTest.php index 9af5a59..ad0a084 100644 --- a/tests/Feature/Blog/ShowBlogTest.php +++ b/tests/Feature/Blog/ShowBlogTest.php @@ -6,7 +6,6 @@ use App\Models\Post; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Storage; -use Livewire\Livewire; use Tests\TestCase; class ShowBlogTest extends TestCase diff --git a/tests/Feature/ManagementTest.php b/tests/Feature/ManagementTest.php index 5de539c..0633ede 100644 --- a/tests/Feature/ManagementTest.php +++ b/tests/Feature/ManagementTest.php @@ -2,7 +2,6 @@ namespace Tests\Feature; -use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; diff --git a/tests/Feature/MavenTest.php b/tests/Feature/MavenTest.php new file mode 100644 index 0000000..6314404 --- /dev/null +++ b/tests/Feature/MavenTest.php @@ -0,0 +1,144 @@ +get('/maven'); + $response->assertStatus(200); + } + + public function test_get_directory_displays_files_in_directory(): void + { + $this->maven()->put('test/testing.txt', '...'); + $this->maven()->put('test/testing2.txt', '...'); + $this->maven()->put('test/testing3.txt', '...'); + + $response = $this->get('/maven/test'); + + $response->assertStatus(200) + ->assertSeeText('testing.txt') + ->assertSeeText('testing2.txt') + ->assertSeeText('testing3.txt'); + } + + public function test_get_directory_doesnt_display_files_from_other_directories(): void + { + $this->maven()->makeDirectory('test'); + $this->maven()->put('testing.txt', '...'); + $this->maven()->put('test2/testing2.txt', '...'); + $this->maven()->put('test3/testing3.txt', '...'); + + $response = $this->get('/maven/test'); + + $response->assertStatus(200) + ->assertDontSeeText('testing.txt') + ->assertDontSeeText('testing2.txt') + ->assertDontSeeText('testing3.txt'); + } + + public function test_get_redirects_to_file_download_when_path_points_to_file(): void + { + $this->maven()->put('testing.txt', '...'); + + $response = $this->get('/maven/testing.txt'); + + $response->assertRedirect('/maven/download/testing.txt'); + } + + public function test_get_file_returns_file_download(): void + { + $this->maven()->put('testing.txt', '...'); + + $response = $this->get('/maven/download/testing.txt'); + + $response->assertStatus(200) + ->assertHeader('Content-Disposition', 'attachment; filename=testing.txt'); + } + + public function test_get_file_returns_not_found_if_file_doesnt_exist(): void + { + $response = $this->get('/maven/testing.txt'); + $response->assertStatus(404); + } + + public function test_put_with_no_credentials_returns_unauthorised(): void + { + $response = $this->put('/maven/testing.txt'); + $response->assertStatus(401); + } + + public function test_put_when_regular_user_returns_forbidden(): void + { + UserFactory::new(['email' => 'test@example.com', 'password' => 'test'])->create(); + + $response = $this->withBasicAuth('test@example.com', 'test') + ->put('/maven/testing.txt'); + + $response->assertStatus(403); + } + + public function test_put_when_maven_editor_returns_ok(): void + { + $file = UploadedFile::fake()->createWithContent('testing.txt', 'test'); + + $response = $this->withMavenEditor()->put('/maven/testing.txt', ['file' => $file]); + + $response->assertStatus(200); + } + + public function test_put_writes_content_correctly(): void + { + $file = UploadedFile::fake()->createWithContent('testing.txt', 'test'); + + $response = $this->withMavenEditor()->put('/maven/testing.txt', ['file' => $file]); + + $response->assertStatus(200); + $this->maven()->assertExists('testing.txt'); + $this->assertEquals('test', $this->maven()->get('testing.txt')); + } + + public function test_put_creates_new_directories(): void + { + $file = UploadedFile::fake()->createWithContent('testing.txt', 'test'); + + $response = $this->withMavenEditor() + ->put('/maven/a/nested/directory/testing.txt', ['file' => $file]); + + $response->assertStatus(200); + $this->maven()->assertExists('a/nested/directory/testing.txt'); + } + + private function withMavenEditor(): self + { + UserFactory::new([ + 'email' => 'test@example.com', + 'password' => 'test', + 'permission_level' => 1 + ])->create(); + return $this->withBasicAuth('test@example.com', 'test'); + } + + private function maven(): Filesystem + { + return Storage::disk('maven'); + } + +} diff --git a/tests/Feature/Settings/ProfileUpdateTest.php b/tests/Feature/Settings/ProfileUpdateTest.php index fccf19f..caf1b0c 100644 --- a/tests/Feature/Settings/ProfileUpdateTest.php +++ b/tests/Feature/Settings/ProfileUpdateTest.php @@ -14,7 +14,7 @@ class ProfileUpdateTest extends TestCase public function test_profile_page_is_displayed(): void { - $this->actingAs($user = User::factory()->create()); + $this->actingAs(User::factory()->create()); $this->get('/settings/profile')->assertOk(); } diff --git a/tests/Feature/Users/UserCellTest.php b/tests/Feature/Users/UserCellTest.php index f1b4b03..ef2a166 100644 --- a/tests/Feature/Users/UserCellTest.php +++ b/tests/Feature/Users/UserCellTest.php @@ -7,6 +7,8 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Livewire\Livewire; use Tests\TestCase; +use const App\Models\ADMIN_LEVEL; +use const App\Models\MAVEN_EDITOR_LEVEL; class UserCellTest extends TestCase { @@ -17,13 +19,13 @@ public function test_update_requires_auth(): void $user = User::factory()->create(); Livewire::test(UserCell::class, ['user' => $user]) - ->set('isAdmin', true) - ->call('updateIsAdmin') + ->set('permissionLevel', true) + ->call('updatePermissionLevel') ->assertForbidden(); $this->assertDatabaseHas('users', [ 'id' => $user->id, - 'is_admin' => false, + 'permission_level' => 0, ]); } @@ -33,13 +35,29 @@ public function test_update_requires_admin(): void $user = User::factory()->create(); Livewire::test(UserCell::class, ['user' => $user]) - ->set('isAdmin', true) - ->call('updateIsAdmin') + ->set('permissionLevel', true) + ->call('updatePermissionLevel') ->assertForbidden(); $this->assertDatabaseHas('users', [ 'id' => $user->id, - 'is_admin' => false, + 'permission_level' => 0, + ]); + } + + public function test_update_maven_editor(): void + { + $this->actingAsAdmin(); + $user = User::factory()->create(); + + Livewire::test(UserCell::class, ['user' => $user]) + ->set('permissionLevel', MAVEN_EDITOR_LEVEL) + ->call('updatePermissionLevel') + ->assertHasNoErrors(); + + $this->assertDatabaseHas('users', [ + 'id' => $user->id, + 'permission_level' => MAVEN_EDITOR_LEVEL, ]); } @@ -49,13 +67,13 @@ public function test_update_admin(): void $user = User::factory()->create(); Livewire::test(UserCell::class, ['user' => $user]) - ->set('isAdmin', true) - ->call('updateIsAdmin') + ->set('permissionLevel', ADMIN_LEVEL) + ->call('updatePermissionLevel') ->assertHasNoErrors(); $this->assertDatabaseHas('users', [ 'id' => $user->id, - 'is_admin' => true, + 'permission_level' => ADMIN_LEVEL, ]); } diff --git a/tests/Feature/Users/UserIndexTest.php b/tests/Feature/Users/UserIndexTest.php index f779fa2..ff557ff 100644 --- a/tests/Feature/Users/UserIndexTest.php +++ b/tests/Feature/Users/UserIndexTest.php @@ -100,7 +100,7 @@ public function test_add_user_updates_registration_tokens_table(): void $this->assertDatabaseHas('registration_tokens', [ 'email' => 'test@email.com', - 'is_admin' => false + 'permission_level' => 0 ]); } diff --git a/tests/TestCase.php b/tests/TestCase.php index fe518b3..3cd9f55 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -28,6 +28,12 @@ protected function actingAsUser(): TestCase return $this; } + protected function actingAsMavenEditor(): TestCase + { + $this->actingAs(User::factory()->mavenEditor()->create()); + return $this; + } + protected function actingAsAdmin(): TestCase { $this->actingAs(User::factory()->admin()->create()); diff --git a/tests/Unit/Auth/Registration/RegistrationTokenRepositoryTest.php b/tests/Unit/Auth/Registration/RegistrationTokenRepositoryTest.php index 7c3d9e1..b7d7c90 100644 --- a/tests/Unit/Auth/Registration/RegistrationTokenRepositoryTest.php +++ b/tests/Unit/Auth/Registration/RegistrationTokenRepositoryTest.php @@ -15,11 +15,11 @@ class RegistrationTokenRepositoryTest extends TestCase public function test_create_updates_database(): void { - RegistrationTokenRepository::get()->create(new Registrant('test@email.com', false)); + RegistrationTokenRepository::get()->create(new Registrant('test@email.com', 0)); $this->assertDatabaseHas('registration_tokens', [ 'email' => 'test@email.com', - 'is_admin' => false + 'permission_level' => 0 ]); } @@ -32,7 +32,7 @@ public function test_validate_returns_null_if_no_codes(): void public function test_validate_returns_null_if_no_codes_for_email(): void { - RegistrationTokenRepository::get()->create(new Registrant('test@email.com', false)); + RegistrationTokenRepository::get()->create(new Registrant('test@email.com', 0)); $result = RegistrationTokenRepository::get()->validate('another-test@email.com', 'test-token'); $this->assertNull($result); @@ -40,7 +40,7 @@ public function test_validate_returns_null_if_no_codes_for_email(): void public function test_validate_returns_null_if_token_incorrect(): void { - RegistrationTokenRepository::get()->create(new Registrant('test@email.com', false)); + RegistrationTokenRepository::get()->create(new Registrant('test@email.com', 0)); $result = RegistrationTokenRepository::get()->validate('test@email.com', 'test-token'); $this->assertNull($result); @@ -48,7 +48,7 @@ public function test_validate_returns_null_if_token_incorrect(): void public function test_validate_returns_null_if_token_correct_but_email_doesnt_match(): void { - $token = RegistrationTokenRepository::get()->create(new Registrant('test@email.com', false)); + $token = RegistrationTokenRepository::get()->create(new Registrant('test@email.com', 0)); $result = RegistrationTokenRepository::get()->validate('another-test@email.com', $token); $this->assertNull($result); @@ -58,7 +58,7 @@ public function test_validate_returns_null_if_token_expired(): void { DB::table('registration_tokens')->insert([ 'email' => 'test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now()->subHours(12)->subSecond(), ]); @@ -70,7 +70,7 @@ public function test_validate_returns_null_if_token_expired(): void public function test_validate_returns_data_if_token_correct(): void { - $token = RegistrationTokenRepository::get()->create(new Registrant('test@email.com', false)); + $token = RegistrationTokenRepository::get()->create(new Registrant('test@email.com', 0)); $result = RegistrationTokenRepository::get()->validate('test@email.com', $token); $this->assertNotNull($result); @@ -78,7 +78,7 @@ public function test_validate_returns_data_if_token_correct(): void public function test_validate_returns_correct_data(): void { - $registrant = new Registrant('test@email.com', false); + $registrant = new Registrant('test@email.com', 0); $token = RegistrationTokenRepository::get()->create($registrant); $result = RegistrationTokenRepository::get()->validate($registrant->getEmail(), $token); @@ -89,7 +89,7 @@ public function test_delete_existing_updates_database(): void { DB::table('registration_tokens')->insert([ 'email' => 'test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now() ]); @@ -105,13 +105,13 @@ public function test_delete_existing_only_deletes_for_specified_email(): void { DB::table('registration_tokens')->insert([ 'email' => 'test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now() ]); DB::table('registration_tokens')->insert([ 'email' => 'another-test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now() ]); @@ -130,26 +130,26 @@ public function test_delete_expired_doesnt_delete_unexpired_tokens(): void { DB::table('registration_tokens')->insert([ 'email' => 'test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now()->subHours(11)->subMinutes(59), ]); DB::table('registration_tokens')->insert([ 'email' => 'another-test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now()->subHours(7), ]); DB::table('registration_tokens')->insert([ 'email' => 'yet-another-test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now(), ]); // Control - should be deleted DB::table('registration_tokens')->insert([ 'email' => 'and-yet-another-test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now()->subHours(12)->subMinute(), ]); @@ -163,26 +163,26 @@ public function test_delete_expired_deletes_expired_tokens(): void { DB::table('registration_tokens')->insert([ 'email' => 'test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now()->subHours(12)->subSecond(), ]); DB::table('registration_tokens')->insert([ 'email' => 'another-test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now()->subDays(3), ]); DB::table('registration_tokens')->insert([ 'email' => 'yet-another-test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now()->subWeeks(4), ]); // Control - should not be deleted DB::table('registration_tokens')->insert([ 'email' => 'and-yet-another-test@email.com', - 'is_admin' => false, + 'permission_level' => 0, 'token' => Hash::make('test-token'), 'created_at' => now()->subHours(7), ]);