diff --git a/composer.json b/composer.json index 53c610f9..6b07c878 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "spatie/laravel-permission": "^3.18", "h5p/h5p-core": "dev-master", "h5p/h5p-editor": "dev-master", - "darkaonline/l5-swagger": "^8.0" + "darkaonline/l5-swagger": "^8.0", + "escolalms/core": "^1" }, "require-dev": { "phpunit/phpunit": "^9.0", @@ -35,4 +36,4 @@ ] } } -} \ No newline at end of file +} diff --git a/database/seeders/PermissionTableSeeder.php b/database/seeders/PermissionTableSeeder.php new file mode 100644 index 00000000..4ff06c55 --- /dev/null +++ b/database/seeders/PermissionTableSeeder.php @@ -0,0 +1,42 @@ +forgetCachedPermissions(); + + $apiAdmin = Role::findOrCreate('admin', 'api'); + $webAdmin = Role::findOrCreate('admin', 'web'); + $permissions = [ + H5PPermissionsEnum::H5P_LIST, + H5PPermissionsEnum::H5P_READ, + H5PPermissionsEnum::H5P_DELETE, + H5PPermissionsEnum::H5P_UPDATE, + H5PPermissionsEnum::H5P_CREATE, + H5PPermissionsEnum::H5P_LIBRARY_LIST, + H5PPermissionsEnum::H5P_LIBRARY_READ, + H5PPermissionsEnum::H5P_LIBRARY_DELETE, + H5PPermissionsEnum::H5P_LIBRARY_UPDATE, + H5PPermissionsEnum::H5P_LIBRARY_CREATE, + ]; + + foreach ($permissions as $permission) { + Permission::findOrCreate($permission, 'api'); + Permission::findOrCreate($permission, 'web'); + } + + $apiAdmin->givePermissionTo($permissions); + $webAdmin->givePermissionTo($permissions); + } +} diff --git a/src/AuthServiceProvider.php b/src/AuthServiceProvider.php new file mode 100644 index 00000000..f518cc73 --- /dev/null +++ b/src/AuthServiceProvider.php @@ -0,0 +1,22 @@ + H5PContentPolicy::class, + H5PLibrary::class => H5PLibraryPolicy::class, + ]; + + public function boot() + { + $this->registerPolicies(); + } +} diff --git a/src/Enums/H5PPermissionsEnum.php b/src/Enums/H5PPermissionsEnum.php new file mode 100644 index 00000000..325715f3 --- /dev/null +++ b/src/Enums/H5PPermissionsEnum.php @@ -0,0 +1,20 @@ +commands([H5PSeedCommand::class, StorageH5PLinkCommand::class]); $this->bindH5P(); + $this->app->register(AuthServiceProvider::class); } private function bindH5P(): void diff --git a/src/Http/Controllers/ContentApiController.php b/src/Http/Controllers/ContentApiController.php index b525bfc7..db0aa2d4 100644 --- a/src/Http/Controllers/ContentApiController.php +++ b/src/Http/Controllers/ContentApiController.php @@ -4,6 +4,9 @@ //use App\Http\Controllers\Controller; use EscolaLms\HeadlessH5P\Http\Controllers\Swagger\ContentApiSwagger; +use EscolaLms\HeadlessH5P\Http\Requests\ContentDeleteRequest; +use EscolaLms\HeadlessH5P\Http\Requests\ContentListRequest; +use EscolaLms\HeadlessH5P\Http\Requests\ContentReadRequest; use EscolaLms\HeadlessH5P\Http\Requests\ContentStoreRequest; use EscolaLms\HeadlessH5P\Http\Requests\LibraryStoreRequest; use EscolaLms\HeadlessH5P\Repositories\Contracts\H5PContentRepositoryContract; @@ -24,7 +27,7 @@ public function __construct(HeadlessH5PServiceContract $hh5pService, H5PContentR $this->contentRepository = $contentRepository; } - public function index(Request $request): JsonResponse + public function index(ContentListRequest $request): JsonResponse { $columns = ['title', 'id', 'library_id']; $list = $request->get('per_page') !== null && $request->get('per_page') == 0 ? $this->contentRepository->unpaginatedList($columns) : $this->contentRepository->list($request->get('per_page'), $columns); @@ -62,14 +65,14 @@ public function store(ContentStoreRequest $request): JsonResponse ], 200); } - public function destroy(Request $request, int $id): JsonResponse + public function destroy(ContentDeleteRequest $request, int $id): JsonResponse { try { $contentId = $this->contentRepository->delete($id); } catch (Exception $error) { return response()->json([ - 'error' => $error->getMessage(), - ], 422); + 'error' => $error->getMessage(), + ], 422); } return response()->json([ @@ -77,14 +80,14 @@ public function destroy(Request $request, int $id): JsonResponse ], 200); } - public function show(Request $request, int $id): JsonResponse + public function show(ContentReadRequest $request, int $id): JsonResponse { try { $settings = $this->hh5pService->getContentSettings($id); } catch (Exception $error) { return response()->json([ - 'error' => $error->getMessage(), - ], 422); + 'error' => $error->getMessage(), + ], 422); } return response()->json( @@ -99,8 +102,8 @@ public function upload(LibraryStoreRequest $request): JsonResponse $content = $this->contentRepository->upload($request->file('h5p_file')); } catch (Exception $error) { return response()->json([ - 'error' => $error->getMessage(), - ], 422); + 'error' => $error->getMessage(), + ], 422); } return response()->json( @@ -109,7 +112,7 @@ public function upload(LibraryStoreRequest $request): JsonResponse ); } - public function download(Request $request, $id) + public function download(ContentReadRequest $request, $id) { $filepath = $this->contentRepository->download($id); diff --git a/src/Http/Controllers/LibraryApiController.php b/src/Http/Controllers/LibraryApiController.php index 2d0cf16a..a9ca757a 100644 --- a/src/Http/Controllers/LibraryApiController.php +++ b/src/Http/Controllers/LibraryApiController.php @@ -3,6 +3,8 @@ namespace EscolaLms\HeadlessH5P\Http\Controllers; //use App\Http\Controllers\Controller; +use EscolaLms\HeadlessH5P\Http\Requests\LibraryDeleteRequest; +use EscolaLms\HeadlessH5P\Http\Requests\LibraryListRequest; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use EscolaLms\HeadlessH5P\Http\Controllers\Swagger\LibraryApiSwagger; @@ -21,7 +23,7 @@ public function __construct(HeadlessH5PServiceContract $hh5pService) $this->hh5pService = $hh5pService; } - public function index(Request $request): JsonResponse + public function index(LibraryListRequest $request): JsonResponse { $libraries = $this->hh5pService->listLibraries(); @@ -42,7 +44,7 @@ public function store(LibraryStoreRequest $request): JsonResponse ], $valid ? 200 : 422); } - public function libraries(Request $request): JsonResponse + public function libraries(LibraryListRequest $request): JsonResponse { $libraries = $this->hh5pService->getLibraries( $request->get('machineName'), @@ -53,10 +55,10 @@ public function libraries(Request $request): JsonResponse return response()->json($libraries, 200); } - public function destroy(Request $request, int $id): JsonResponse + public function destroy(LibraryDeleteRequest $request, int $id): JsonResponse { $valid = $this->hh5pService->deleteLibrary($id); - + return response()->json([ 'valid' => $valid, 'messages' => $valid ? "Library $id deleted" : "", diff --git a/src/Http/Controllers/Swagger/ContentApiSwagger.php b/src/Http/Controllers/Swagger/ContentApiSwagger.php index 83b75834..ae9411f9 100644 --- a/src/Http/Controllers/Swagger/ContentApiSwagger.php +++ b/src/Http/Controllers/Swagger/ContentApiSwagger.php @@ -2,6 +2,9 @@ namespace EscolaLms\HeadlessH5P\Http\Controllers\Swagger; +use EscolaLms\HeadlessH5P\Http\Requests\ContentDeleteRequest; +use EscolaLms\HeadlessH5P\Http\Requests\ContentListRequest; +use EscolaLms\HeadlessH5P\Http\Requests\ContentReadRequest; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -156,7 +159,7 @@ public function store(ContentStoreRequest $request): JsonResponse; * ) * ) */ - public function show(Request $request, int $id): JsonResponse; + public function show(ContentReadRequest $request, int $id): JsonResponse; /** * @OA\Post( @@ -253,7 +256,7 @@ public function update(ContentStoreRequest $request, int $id): JsonResponse; * ) * ) */ - public function index(ContentStoreRequest $request): JsonResponse; + public function index(ContentListRequest $request): JsonResponse; /** * @OA\Delete( @@ -293,7 +296,7 @@ public function index(ContentStoreRequest $request): JsonResponse; * ) * ) */ - public function destroy(Request $request, int $id): JsonResponse; + public function destroy(ContentDeleteRequest $request, int $id): JsonResponse; /** * @OA\Post( @@ -325,7 +328,7 @@ public function destroy(Request $request, int $id): JsonResponse; * ) */ public function upload(LibraryStoreRequest $request): JsonResponse; - + /** * @OA\Get( * path="/api/hh5p/content/{id}/export", @@ -364,5 +367,5 @@ public function upload(LibraryStoreRequest $request): JsonResponse; * ) * ) */ - public function download(Request $request, int $id); + public function download(ContentReadRequest $request, int $id); } diff --git a/src/Http/Controllers/Swagger/LibraryApiSwagger.php b/src/Http/Controllers/Swagger/LibraryApiSwagger.php index 0fad6138..8c07e3c7 100644 --- a/src/Http/Controllers/Swagger/LibraryApiSwagger.php +++ b/src/Http/Controllers/Swagger/LibraryApiSwagger.php @@ -2,6 +2,8 @@ namespace EscolaLms\HeadlessH5P\Http\Controllers\Swagger; +use EscolaLms\HeadlessH5P\Http\Requests\LibraryDeleteRequest; +use EscolaLms\HeadlessH5P\Http\Requests\LibraryListRequest; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -65,7 +67,7 @@ public function store(LibraryStoreRequest $request): JsonResponse; * ) * ) */ - public function destroy(Request $request, int $id): JsonResponse; + public function destroy(LibraryDeleteRequest $request, int $id): JsonResponse; /** * @OA\Get( @@ -83,7 +85,7 @@ public function destroy(Request $request, int $id): JsonResponse; * ) * ) */ - public function index(Request $request): JsonResponse; + public function index(LibraryListRequest $request): JsonResponse; /** * @OA\Get( @@ -145,5 +147,5 @@ public function index(Request $request): JsonResponse; * ) * ) */ - public function libraries(Request $request); + public function libraries(LibraryListRequest $request); } diff --git a/src/Http/Requests/ContentDeleteRequest.php b/src/Http/Requests/ContentDeleteRequest.php new file mode 100644 index 00000000..5663b3dd --- /dev/null +++ b/src/Http/Requests/ContentDeleteRequest.php @@ -0,0 +1,23 @@ +show($this->route('id')); + + return Gate::allows('delete', $h5pContent); + } + + public function rules(): array + { + return []; + } +} diff --git a/src/Http/Requests/ContentListRequest.php b/src/Http/Requests/ContentListRequest.php new file mode 100644 index 00000000..f00c4495 --- /dev/null +++ b/src/Http/Requests/ContentListRequest.php @@ -0,0 +1,20 @@ +show($this->route('id')); + + return Gate::allows('read', $h5pContent); + } + + public function rules(): array + { + return []; + } +} diff --git a/src/Http/Requests/ContentStoreRequest.php b/src/Http/Requests/ContentStoreRequest.php index 27814c45..5a4eae52 100644 --- a/src/Http/Requests/ContentStoreRequest.php +++ b/src/Http/Requests/ContentStoreRequest.php @@ -2,26 +2,26 @@ namespace EscolaLms\HeadlessH5P\Http\Requests; +use EscolaLms\HeadlessH5P\Models\H5PContent; +use EscolaLms\HeadlessH5P\Repositories\Contracts\H5PContentRepositoryContract; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Support\Facades\Gate; class ContentStoreRequest extends FormRequest { - /** - * Determine if the user is authorized to make this request. - * - * @return bool - */ - public function authorize() + public function authorize(): bool { - return true; + if ($this->route('id')) { + $h5PContentRepository = app(H5PContentRepositoryContract::class); + $h5pContent = $h5PContentRepository->show($this->route('id')); + + return Gate::allows('update', $h5pContent); + } + + return Gate::allows('update', H5PContent::class); } - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() + public function rules(): array { return [ 'title' => ['required', 'string'], diff --git a/src/Http/Requests/LibraryDeleteRequest.php b/src/Http/Requests/LibraryDeleteRequest.php new file mode 100644 index 00000000..f49eae91 --- /dev/null +++ b/src/Http/Requests/LibraryDeleteRequest.php @@ -0,0 +1,33 @@ +getLibraryById($this->route('id')); + + return Gate::allows('delete', $h5pLibrary); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return []; + } +} diff --git a/src/Http/Requests/LibraryListRequest.php b/src/Http/Requests/LibraryListRequest.php new file mode 100644 index 00000000..461099f7 --- /dev/null +++ b/src/Http/Requests/LibraryListRequest.php @@ -0,0 +1,20 @@ +route('id')) { + $h5PContentRepository = app(H5PContentRepositoryContract::class); + $h5pLibrary = $h5PContentRepository->getLibraryById($this->route('id')); + + return Gate::allows('update', $h5pLibrary); + } + + return Gate::allows('update', H5PLibrary::class); } /** diff --git a/src/Policies/H5PContentPolicy.php b/src/Policies/H5PContentPolicy.php new file mode 100644 index 00000000..3396cc40 --- /dev/null +++ b/src/Policies/H5PContentPolicy.php @@ -0,0 +1,37 @@ +can(H5PPermissionsEnum::H5P_LIST); + } + + public function read(?User $user): bool + { + return $user && $user->can(H5PPermissionsEnum::H5P_READ); + } + + public function create(?User $user): bool + { + return $user && $user->can(H5PPermissionsEnum::H5P_CREATE); + } + + public function delete(?User $user): bool + { + return $user && $user->can(H5PPermissionsEnum::H5P_DELETE); + } + + public function update(?User $user): bool + { + return $user && $user->can(H5PPermissionsEnum::H5P_UPDATE); + } +} diff --git a/src/Policies/H5PLibraryPolicy.php b/src/Policies/H5PLibraryPolicy.php new file mode 100644 index 00000000..b7f2429b --- /dev/null +++ b/src/Policies/H5PLibraryPolicy.php @@ -0,0 +1,37 @@ +can(H5PPermissionsEnum::H5P_LIBRARY_LIST); + } + + public function read(?User $user): bool + { + return $user && $user->can(H5PPermissionsEnum::H5P_LIBRARY_READ); + } + + public function create(?User $user): bool + { + return $user && $user->can(H5PPermissionsEnum::H5P_LIBRARY_CREATE); + } + + public function delete(?User $user): bool + { + return $user && $user->can(H5PPermissionsEnum::H5P_LIBRARY_DELETE); + } + + public function update(?User $user): bool + { + return $user && $user->can(H5PPermissionsEnum::H5P_LIBRARY_UPDATE); + } +} diff --git a/src/Repositories/Contracts/H5PContentRepositoryContract.php b/src/Repositories/Contracts/H5PContentRepositoryContract.php index 62cfaa0d..8611902b 100644 --- a/src/Repositories/Contracts/H5PContentRepositoryContract.php +++ b/src/Repositories/Contracts/H5PContentRepositoryContract.php @@ -10,13 +10,15 @@ interface H5PContentRepositoryContract { - public function create(string $title, string $library, string $params, string $nonce):int; + public function create(string $title, string $library, string $params, string $nonce): int; - public function edit(int $id, string $title, string $library, string $params, string $nonce):int; + public function edit(int $id, string $title, string $library, string $params, string $nonce): int; - public function list($per_page = 15, array $columns = ['*']):LengthAwarePaginator; + public function list($per_page = 15, array $columns = ['*']): LengthAwarePaginator; - public function unpaginatedList(array $columns = ['*']):Collection; + public function unpaginatedList(array $columns = ['*']): Collection; - public function delete(int $id):int; + public function delete(int $id): int; + + public function getLibraryById(int $id): H5PLibrary; } diff --git a/src/Repositories/H5PContentRepository.php b/src/Repositories/H5PContentRepository.php index 074a164d..32fa5ca3 100644 --- a/src/Repositories/H5PContentRepository.php +++ b/src/Repositories/H5PContentRepository.php @@ -125,8 +125,10 @@ public function list($per_page = 15, array $columns = ['*']): LengthAwarePaginat )->select($columns)->paginate(intval($per_page)); $paginator->getCollection()->transform(function ($content) { // Your code here - $content->library->makeHidden(['semantics']); - $content->library->setAppends([]); + if ($content->library) { + $content->library->makeHidden(['semantics']); + $content->library->setAppends([]); + } return $content; }); @@ -208,4 +210,9 @@ public function download($id): string return storage_path('app/h5p/exports/'.$filename); } + + public function getLibraryById(int $id): H5PLibrary + { + return H5PLibrary::findOrFail($id); + } } diff --git a/src/routes.php b/src/routes.php index 46da210b..849eb82b 100644 --- a/src/routes.php +++ b/src/routes.php @@ -6,25 +6,31 @@ use EscolaLms\HeadlessH5P\Http\Controllers\LibraryApiController; use Illuminate\Support\Facades\Route; -Route::group(['middleware' => ['api'], 'prefix' => 'api/hh5p'], function () { - Route::post('library', [LibraryApiController::class, 'store'])->name('hh5p.library.store'); - Route::get('library', [LibraryApiController::class, 'index'])->name('hh5p.library.list'); - Route::delete('library/{id}', [LibraryApiController::class, 'destroy'])->name('hh5p.library.delete'); - Route::get('editor', EditorApiController::class)->name('hh5p.editor.settings'); - Route::get('editor/{id}', EditorApiController::class)->name('hh5p.editor.contentSettings'); - Route::get('libraries', [LibraryApiController::class, 'libraries'])->name('hh5p.library.libraries'); - Route::post('libraries', [LibraryApiController::class, 'libraries'])->name('hh5p.library.libraries'); - Route::post('content/upload', [ContentApiController::class, 'upload'])->name('hh5p.content.upload'); - Route::post('content', [ContentApiController::class, 'store'])->name('hh5p.content.store'); - Route::post('content/{id}', [ContentApiController::class, 'update'])->name('hh5p.content.update'); - Route::delete('content/{id}', [ContentApiController::class, 'destroy'])->name('hh5p.content.destroy'); - Route::get('content', [ContentApiController::class, 'index'])->name('hh5p.content.index'); - Route::get('content/{id}/export', [ContentApiController::class, 'download'])->name('hh5p.content.export'); - Route::get('content/{id}', [ContentApiController::class, 'show'])->name('hh5p.content.show'); - Route::post('files', FilesApiController::class)->name('hh5p.files.upload'); - Route::post('files/{nonce}', FilesApiController::class)->name('hh5p.files.upload.nonce'); +Route::group(['middleware' => ['api'], 'prefix' => 'api'], function () { + Route::group(['prefix' => 'admin/hh5p'], function () { + Route::post('library', [LibraryApiController::class, 'store'])->name('hh5p.library.store'); + Route::get('library', [LibraryApiController::class, 'index'])->name('hh5p.library.list'); + Route::delete('library/{id}', [LibraryApiController::class, 'destroy'])->name('hh5p.library.delete'); + Route::get('editor', EditorApiController::class)->name('hh5p.editor.settings'); + Route::get('editor/{id}', EditorApiController::class)->name('hh5p.editor.contentSettings'); + Route::get('libraries', [LibraryApiController::class, 'libraries'])->name('hh5p.library.libraries'); + Route::post('libraries', [LibraryApiController::class, 'libraries'])->name('hh5p.library.libraries'); + Route::post('content/upload', [ContentApiController::class, 'upload'])->name('hh5p.content.upload'); + Route::post('content', [ContentApiController::class, 'store'])->name('hh5p.content.store'); + Route::post('content/{id}', [ContentApiController::class, 'update'])->name('hh5p.content.update'); + Route::delete('content/{id}', [ContentApiController::class, 'destroy'])->name('hh5p.content.destroy'); + Route::get('content', [ContentApiController::class, 'index'])->name('hh5p.content.index'); + Route::get('content/{id}/export', [ContentApiController::class, 'download'])->name('hh5p.content.export'); + Route::get('content/{id}', [ContentApiController::class, 'show'])->name('hh5p.content.show'); + Route::post('files', FilesApiController::class)->name('hh5p.files.upload'); + Route::post('files/{nonce}', FilesApiController::class)->name('hh5p.files.upload.nonce'); + }); - Route::get('/', function () { - return 'Hello World'; - })->name('hh5p.index'); // DO not remove this is needed as prefix for editor ajax calls + Route::group(['prefix' => 'hh5p'], function () { + Route::get('hh5p/content/{id}', [ContentApiController::class, 'show'])->name('hh5p.content.show'); + + Route::get('hh5p/', function () { + return 'Hello World'; + })->name('hh5p.index'); // DO not remove this is needed as prefix for editor ajax calls + }); }); diff --git a/testbench.yaml b/testbench.yaml index a35ee7c7..85272c55 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -7,4 +7,7 @@ env: - DB_PASSWORD=password providers: + - Spatie\Permission\PermissionServiceProvider + - Laravel\Passport\PassportServiceProvider + - EscolaLms\Core\EscolaLmsServiceProvider - EscolaLms\HeadlessH5P\HeadlessH5PServiceProvider diff --git a/tests/Api/ContentApiTest.php b/tests/Api/ContentApiTest.php index 414fbc47..57449d14 100644 --- a/tests/Api/ContentApiTest.php +++ b/tests/Api/ContentApiTest.php @@ -11,8 +11,14 @@ class ContentApiTest extends TestCase { + public function setUp(): void + { + parent::setUp(); // TODO: Change the autogenerated stub + } + public function testContentCreate() { + $this->authenticateAsAdmin(); $filename = 'arithmetic-quiz.h5p'; $filepath = realpath(__DIR__.'/../mocks/'.$filename); $storage_path = storage_path($filename); @@ -21,14 +27,14 @@ public function testContentCreate() $h5pFile = new UploadedFile($storage_path, 'arithmetic-quiz.h5p', 'application/pdf', null, true); - $response = $this->post('/api/hh5p/library', [ + $response = $this->actingAs($this->user, 'api')->post('/api/admin/hh5p/library', [ 'h5p_file' => $h5pFile, ]); $library = H5PLibrary::where('runnable', 1)->first(); // TODO this should be from factory ? - $response = $this->postJson('/api/hh5p/content', [ + $response = $this->actingAs($this->user, 'api')->postJson('/api/admin/hh5p/content', [ 'nonce' => bin2hex(random_bytes(4)), 'title' => 'The Title', 'library' => $library->uberName, @@ -41,7 +47,8 @@ public function testContentCreate() public function testContentCreateNoNonce() { - $response = $this->postJson('/api/hh5p/content', [ + $this->authenticateAsAdmin(); + $response = $this->actingAs($this->user, 'api')->postJson('/api/admin/hh5p/content', [ 'title' => 'The Title', 'library' => 'Invalid lib name', 'params' => '{"params":{"taskDescription":"Documentation tool","pagesList":[{"params":{"elementList":[{"params":{},"library":"H5P.Text 1.1","metadata":{"contentType":"Text","license":"U","title":"Untitled Text","authors":[],"changes":[],"extraTitle":"Untitled Text"},"subContentId":"da3387da-355a-49fb-92bc-3a9a4e4646a9"}],"helpTextLabel":"More information","helpText":""},"library":"H5P.StandardPage 1.5","metadata":{"contentType":"Standard page","license":"U","title":"Untitled Standard page","authors":[],"changes":[],"extraTitle":"Untitled Standard page"},"subContentId":"ac6ffdac-be02-448c-861c-969e6a09dbd5"}],"i10n":{"previousLabel":"poprzedni","nextLabel":"Next","closeLabel":"Close"}},"metadata":{"license":"U","authors":[],"changes":[],"extraTitle":"fdsfds","title":"fdsfds"}}', @@ -52,7 +59,8 @@ public function testContentCreateNoNonce() public function testContentCreateInvalidLibrary() { - $response = $this->postJson('/api/hh5p/content', [ + $this->authenticateAsAdmin(); + $response = $this->actingAs($this->user, 'api')->postJson('/api/admin/hh5p/content', [ 'nonce' => bin2hex(random_bytes(4)), 'title' => 'The Title', 'library' => 'Invalid lib name', @@ -64,9 +72,10 @@ public function testContentCreateInvalidLibrary() public function testContentCreateInvalidJson() { + $this->authenticateAsAdmin(); $library = H5PLibrary::where('runnable', 1)->first(); - $response = $this->postJson('/api/hh5p/content', [ + $response = $this->actingAs($this->user, 'api')->postJson('/api/admin/hh5p/content', [ 'nonce' => bin2hex(random_bytes(4)), 'title' => 'The Title', 'library' => $library->uberName, @@ -80,12 +89,13 @@ public function testContentCreateInvalidJson() public function testContentUpdate() { + $this->authenticateAsAdmin(); $content = H5PContent::first(); $library = H5PLibrary::where('runnable', 1)->first(); $id = $content->id; // TODO this should be from factory ? - $response = $this->postJson("/api/hh5p/content/$id", [ + $response = $this->actingAs($this->user, 'api')->postJson("/api/admin/hh5p/content/$id", [ 'nonce' => bin2hex(random_bytes(4)), 'title' => 'The Title', 'library' => $library->uberName, @@ -98,10 +108,11 @@ public function testContentUpdate() public function testContentUpdateNoNonce() { + $this->authenticateAsAdmin(); $content = H5PContent::first(); $id = $content->id; - $response = $this->postJson("/api/hh5p/content/$id", [ + $response = $this->actingAs($this->user, 'api')->postJson("/api/admin/hh5p/content/$id", [ 'title' => 'The Title', 'library' => 'Invalid lib name', 'params' => '{"params":{"taskDescription":"Documentation tool","pagesList":[{"params":{"elementList":[{"params":{},"library":"H5P.Text 1.1","metadata":{"contentType":"Text","license":"U","title":"Untitled Text","authors":[],"changes":[],"extraTitle":"Untitled Text"},"subContentId":"da3387da-355a-49fb-92bc-3a9a4e4646a9"}],"helpTextLabel":"More information","helpText":""},"library":"H5P.StandardPage 1.5","metadata":{"contentType":"Standard page","license":"U","title":"Untitled Standard page","authors":[],"changes":[],"extraTitle":"Untitled Standard page"},"subContentId":"ac6ffdac-be02-448c-861c-969e6a09dbd5"}],"i10n":{"previousLabel":"poprzedni","nextLabel":"Next","closeLabel":"Close"}},"metadata":{"license":"U","authors":[],"changes":[],"extraTitle":"fdsfds","title":"fdsfds"}}', @@ -112,10 +123,11 @@ public function testContentUpdateNoNonce() public function testContentUpdateInvalidLibrary() { + $this->authenticateAsAdmin(); $content = H5PContent::first(); $id = $content->id; - $response = $this->postJson("/api/hh5p/content/$id", [ + $response = $this->actingAs($this->user, 'api')->postJson("/api/admin/hh5p/content/$id", [ 'nonce' => bin2hex(random_bytes(4)), 'title' => 'The Title', 'library' => 'Invalid lib name', @@ -127,11 +139,12 @@ public function testContentUpdateInvalidLibrary() public function testContentUpdateInvalidJson() { + $this->authenticateAsAdmin(); $library = H5PLibrary::where('runnable', 1)->first(); $content = H5PContent::first(); $id = $content->id; - $response = $this->postJson("/api/hh5p/content/$id", [ + $response = $this->actingAs($this->user, 'api')->postJson("/api/admin/hh5p/content/$id", [ 'nonce' => bin2hex(random_bytes(4)), 'title' => 'The Title', 'library' => $library->uberName, @@ -143,7 +156,8 @@ public function testContentUpdateInvalidJson() public function testContentList() { - $response = $this->get('/api/hh5p/content'); + $this->authenticateAsAdmin(); + $response = $this->actingAs($this->user, 'api')->get('/api/admin/hh5p/content'); $response->assertStatus(200); $response->assertJsonStructure([ 'current_page', @@ -164,7 +178,8 @@ public function testContentList() public function testContentListPage() { - $response = $this->get('/api/hh5p/content?page=2'); + $this->authenticateAsAdmin(); + $response = $this->actingAs($this->user, 'api')->get('/api/admin/hh5p/content?page=2'); $response->assertStatus(200); $response->assertJsonStructure([ 'current_page', @@ -185,10 +200,11 @@ public function testContentListPage() public function testContentDelete() { + $this->authenticateAsAdmin(); $library = H5PLibrary::where('runnable', 1)->first(); // TODO this should be from factory ? - $response = $this->postJson('/api/hh5p/content', [ + $response = $this->actingAs($this->user, 'api')->postJson('/api/admin/hh5p/content', [ 'nonce' => bin2hex(random_bytes(4)), 'title' => 'The Title', 'library' => $library->uberName, @@ -199,19 +215,20 @@ public function testContentDelete() $id = $content->id; - $response = $this->delete("/api/hh5p/content/$id"); + $response = $this->actingAs($this->user, 'api')->delete("/api/admin/hh5p/content/$id"); $response->assertStatus(200); - $response = $this->delete("/api/hh5p/content/$id"); - $response->assertStatus(422); + $response = $this->actingAs($this->user, 'api')->delete("/api/admin/hh5p/content/$id"); + $response->assertStatus(404); } public function testContentShow() { + $this->authenticateAsAdmin(); $content = H5PContent::latest()->first(); $id = $content->id; - $response = $this->get("/api/hh5p/content/$id"); + $response = $this->actingAs($this->user, 'api')->get("/api/admin/hh5p/content/$id"); $response->assertStatus(200); $data = json_decode($response->getContent()); @@ -223,13 +240,15 @@ public function testContentShow() public function testContentShowNonExisiting() { + $this->authenticateAsAdmin(); $id = 999999; - $response = $this->get("/api/hh5p/content/$id"); - $response->assertStatus(422); + $response = $this->actingAs($this->user, 'api')->get("/api/admin/hh5p/content/$id"); + $response->assertStatus(404); } public function testContentUploadig() { + $this->authenticateAsAdmin(); $filename = 'arithmetic-quiz.h5p'; $filepath = realpath(__DIR__.'/../mocks/'.$filename); $storage_path = storage_path($filename); @@ -238,7 +257,7 @@ public function testContentUploadig() $h5pFile = new UploadedFile($storage_path, 'arithmetic-quiz.h5p', 'application/pdf', null, true); - $response = $this->post('/api/hh5p/content/upload', [ + $response = $this->actingAs($this->user, 'api')->post('/api/admin/hh5p/content/upload', [ 'h5p_file' => $h5pFile, ]); @@ -252,12 +271,70 @@ public function testContentUploadig() public function testContentExport() { + $this->authenticateAsAdmin(); $content = H5PContent::latest()->first(); $id = $content->id; - $response = $this->get("/api/hh5p/content/$id/export"); + $response = $this->actingAs($this->user, 'api')->get("/api/admin/hh5p/content/$id/export"); $response->assertStatus(200); $response->assertDownload(); } + + public function testGuestCannotCreateContent(): void + { + $library = H5PLibrary::where('runnable', 1)->first(); + + $response = $this->postJson('/api/admin/hh5p/content', [ + 'nonce' => bin2hex(random_bytes(4)), + 'title' => 'The Title', + 'library' => $library->uberName, + 'params' => '{"params":{"taskDescription":"Documentation tool","pagesList":[{"params":{"elementList":[{"params":{},"library":"H5P.Text 1.1","metadata":{"contentType":"Text","license":"U","title":"Untitled Text","authors":[],"changes":[],"extraTitle":"Untitled Text"},"subContentId":"da3387da-355a-49fb-92bc-3a9a4e4646a9"}],"helpTextLabel":"More information","helpText":""},"library":"H5P.StandardPage 1.5","metadata":{"contentType":"Standard page","license":"U","title":"Untitled Standard page","authors":[],"changes":[],"extraTitle":"Untitled Standard page"},"subContentId":"ac6ffdac-be02-448c-861c-969e6a09dbd5"}],"i10n":{"previousLabel":"poprzedni","nextLabel":"Next","closeLabel":"Close"}},"metadata":{"license":"U","authors":[],"changes":[],"extraTitle":"fdsfds","title":"fdsfds"}}', + ]); + + $response->assertForbidden(); + } + + public function testGuestCannotUpdateContent(): void + { + $content = H5PContent::first(); + $library = H5PLibrary::where('runnable', 1)->first(); + $id = $content->id; + + $response = $this->postJson("/api/admin/hh5p/content/$id", [ + 'nonce' => bin2hex(random_bytes(4)), + 'title' => 'The Title', + 'library' => $library->uberName, + 'params' => '{"params":{"taskDescription":"Documentation tool","pagesList":[{"params":{"elementList":[{"params":{},"library":"H5P.Text 1.1","metadata":{"contentType":"Text","license":"U","title":"Untitled Text","authors":[],"changes":[],"extraTitle":"Untitled Text"},"subContentId":"da3387da-355a-49fb-92bc-3a9a4e4646a9"}],"helpTextLabel":"More information","helpText":""},"library":"H5P.StandardPage 1.5","metadata":{"contentType":"Standard page","license":"U","title":"Untitled Standard page","authors":[],"changes":[],"extraTitle":"Untitled Standard page"},"subContentId":"ac6ffdac-be02-448c-861c-969e6a09dbd5"}],"i10n":{"previousLabel":"poprzedni","nextLabel":"Next","closeLabel":"Close"}},"metadata":{"license":"U","authors":[],"changes":[],"extraTitle":"fdsfds","title":"fdsfds"}}', + ]); + + $response->assertForbidden(); + } + + public function testGuestCannotListContent(): void + { + $response = $this->get('/api/admin/hh5p/content'); + + $response->assertForbidden(); + } + + public function testGuestCannotShowContent(): void + { + $content = H5PContent::latest()->first(); + $id = $content->id; + + $response = $this->get("/api/admin/hh5p/content/$id"); + + $response->assertForbidden(); + } + + public function testGuestCannotDeleteContent(): void + { + $library = H5PLibrary::where('runnable', 1)->first(); + $id = $library->id; + + $response = $this->delete("/api/admin/hh5p/library/$id"); + + $response->assertForbidden(); + } } diff --git a/tests/Api/EditorApiTest.php b/tests/Api/EditorApiTest.php index 16f496fd..633139ca 100644 --- a/tests/Api/EditorApiTest.php +++ b/tests/Api/EditorApiTest.php @@ -13,7 +13,8 @@ class EditorApiTest extends TestCase { public function test_editor_config_new() { - $response = $this->get('/api/hh5p/editor'); + $this->authenticateAsAdmin(); + $response = $this->actingAs($this->user, 'api')->get('/api/admin/hh5p/editor'); $response->assertStatus(200); $data = json_decode($response->getContent()); @@ -23,18 +24,19 @@ public function test_editor_config_new() public function test_editor_config_content() { + $this->authenticateAsAdmin(); $data = [ "library"=> "H5P.ArithmeticQuiz 1.1", "nonce"=>bin2hex(random_bytes(4)), "params"=> '{"params":{"quizType":"arithmetic","arithmeticType":"addition","equationType":"intermediate","useFractions":false,"maxQuestions":20,"UI":{"score":"Score:","time":"Time: @time","resultPageHeader":"Finished!","go":"GO!","startButton":"Start","retryButton":"Retry","correctText":"Correct","incorrectText":"Incorrect. Correct answer was :num","durationLabel":"Duration in hours, minutes and seconds.","humanizedQuestion":"What does :arithmetic equal?","humanizedEquation":"For the equation :equation, what does :item equal?","humanizedVariable":"What does :item equal?","plusOperator":"plus","subtractionOperator":"minus","multiplicationOperator":"times","divisionOperator":"delt på","equalitySign":"equals","slideOfTotal":"Slide :num of :total"},"intro":"Artimethic quiz"},"metadata":{"license":"U","authors":[],"changes":[],"extraTitle":"Artimethic quiz","title":"Artimethic quiz"}}', "title"=> "Artimethic quiz" ]; - - $response = $this->postJson('/api/hh5p/content', $data); + + $response = $this->actingAs($this->user, 'api')->postJson('/api/admin/hh5p/content', $data); $data = json_decode($response->getContent()); - $response = $this->get('/api/hh5p/editor/'.$data->id); + $response = $this->actingAs($this->user, 'api')->get('/api/admin/hh5p/editor/'.$data->id); $response->assertStatus(200); $data = json_decode($response->getContent()); diff --git a/tests/Api/LibraryApiTest.php b/tests/Api/LibraryApiTest.php index b5ec3fdf..17efa4af 100644 --- a/tests/Api/LibraryApiTest.php +++ b/tests/Api/LibraryApiTest.php @@ -13,6 +13,7 @@ class LibraryApiTest extends TestCase { public function test_library_uploadig() { + $this->authenticateAsAdmin(); $filename = 'arithmetic-quiz.h5p'; $filepath = realpath(__DIR__.'/../mocks/'.$filename); $storage_path = storage_path($filename); @@ -21,7 +22,7 @@ public function test_library_uploadig() $h5pFile = new UploadedFile($storage_path, 'arithmetic-quiz.h5p', 'application/pdf', null, true); - $response = $this->post('/api/hh5p/library', [ + $response = $this->actingAs($this->user, 'api')->post('/api/admin/hh5p/library', [ 'h5p_file' => $h5pFile, ]); @@ -34,7 +35,8 @@ public function test_library_uploadig() public function test_library_index() { - $response = $this->get('/api/hh5p/library'); + $this->authenticateAsAdmin(); + $response = $this->actingAs($this->user, 'api')->get('/api/admin/hh5p/library'); $response->assertStatus(200); $response->assertJsonStructure([ @@ -46,13 +48,48 @@ public function test_library_index() public function test_library_delete() { + $this->authenticateAsAdmin(); $library = H5PLibrary::where('runnable', 1)->first(); $id = $library->id; - $response = $this->delete("/api/hh5p/library/$id"); + $response = $this->actingAs($this->user, 'api')->delete("/api/admin/hh5p/library/$id"); $response->assertStatus(200); - $response = $this->delete("/api/hh5p/library/$id"); + $response = $this->actingAs($this->user, 'api')->delete("/api/admin/hh5p/library/$id"); $response->assertStatus(404); } + + public function testGuestCannotDeleteLibrary(): void + { + $library = H5PLibrary::first(); + $id = $library->id; + + $response = $this->delete("/api/admin/hh5p/library/$id"); + + $response->assertForbidden(); + } + + public function testGuestCannotIndexLibrary(): void + { + $response = $this->get('/api/admin/hh5p/library'); + + $response->assertForbidden(); + } + + public function testGuestCannotUploadLibrary() + { + $filename = 'arithmetic-quiz.h5p'; + $filepath = realpath(__DIR__.'/../mocks/'.$filename); + $storage_path = storage_path($filename); + + copy($filepath, $storage_path); + + $h5pFile = new UploadedFile($storage_path, 'arithmetic-quiz.h5p', 'application/pdf', null, true); + + $response = $this->post('/api/admin/hh5p/library', [ + 'h5p_file' => $h5pFile, + ]); + + $response->assertForbidden(); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index cfcbdd42..1d48fe47 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,20 +2,32 @@ namespace EscolaLms\HeadlessH5P\Tests; -use App\Models\User; -use Orchestra\Testbench\TestCase as OrchestraTestCase; +use EscolaLms\Core\EscolaLmsServiceProvider; +use EscolaLms\Core\Models\User; +use EscolaLms\HeadlessH5P\Database\Seeders\PermissionTableSeeder; +use EscolaLms\HeadlessH5P\Enums\H5PPermissionsEnum; +use Laravel\Passport\PassportServiceProvider; use EscolaLms\HeadlessH5P\HeadlessH5PServiceProvider; +use Spatie\Permission\PermissionServiceProvider; -class TestCase extends OrchestraTestCase +class TestCase extends \EscolaLms\Core\Tests\TestCase { + public $user; + protected function setUp(): void { parent::setUp(); + $this->seed(PermissionTableSeeder::class); } protected function getPackageProviders($app) { - return [HeadlessH5PServiceProvider::class]; + return [ + HeadlessH5PServiceProvider::class, + PermissionServiceProvider::class, + PassportServiceProvider::class, + EscolaLmsServiceProvider::class, + ]; } protected function getEnvironmentSetUp($app) @@ -24,4 +36,11 @@ protected function getEnvironmentSetUp($app) $app['config']->set('auth.providers.users.model', User::class); $app['config']->set('passport.client_uuids', true); } + + protected function authenticateAsAdmin(): void + { + $this->user = config('auth.providers.users.model')::factory()->create(); + $this->user->guard_name = 'api'; + $this->user->assignRole('admin'); + } }