diff --git a/app/Http/Controllers/Sharing/BrowseShareController.php b/app/Http/Controllers/Sharing/BrowseShareController.php index 70beef7d..8861b6b4 100644 --- a/app/Http/Controllers/Sharing/BrowseShareController.php +++ b/app/Http/Controllers/Sharing/BrowseShareController.php @@ -27,12 +27,10 @@ class BrowseShareController extends Controller * @param Share $shared * @return Collection */ - public function get_public_folders($id, Share $shared) + public function browse_folder($id, Share $shared) { - // Abort if folder is protected - if ($shared->is_protected) { - abort(403, "Sorry, you don't have permission"); - } + // Check ability to access protected share record + $this->helper->check_protected_share_record($shared); // Check if user can get directory $this->helper->check_item_access($id, $shared); @@ -57,12 +55,10 @@ class BrowseShareController extends Controller * @param Share $shared * @return Collection */ - public function search_public(Request $request, Share $shared) + public function search(Request $request, Share $shared) { - // Abort if folder is protected - if ($shared->is_protected) { - abort(403, "Sorry, you don't have permission"); - } + // Check ability to access protected share record + $this->helper->check_protected_share_record($shared); // Search files id db $searched_files = File::search($request->input('query')) @@ -108,8 +104,11 @@ class BrowseShareController extends Controller * @param Share $shared * @return array */ - public function get_public_navigation_tree(Share $shared) + public function navigation_tree(Share $shared) { + // Check ability to access protected share record + $this->helper->check_protected_share_record($shared); + // Check if user can get directory $this->helper->check_item_access($shared->item_id, $shared); diff --git a/app/Http/Controllers/Sharing/FileSharedAccessController.php b/app/Http/Controllers/Sharing/FileSharedAccessController.php index 596c3b5e..3a4dde6f 100644 --- a/app/Http/Controllers/Sharing/FileSharedAccessController.php +++ b/app/Http/Controllers/Sharing/FileSharedAccessController.php @@ -54,15 +54,14 @@ class FileSharedAccessController extends Controller * Get file public * * @param $filename + * @param $permission * @param Share $shared * @return mixed */ - public function get_file_public($filename, Share $shared) + public function get_file_public($filename, $permission, Share $shared) { - // Abort if shared is protected - if ($shared->is_protected) { - abort(403, "Sorry, you don't have permission"); - } + // Check ability to access protected share files + $this->helper->check_protected_share_record($shared, $permission); // Get file record $file = UserFile::where('user_id', $shared->user_id) @@ -86,15 +85,14 @@ class FileSharedAccessController extends Controller * Get public image thumbnail * * @param $filename + * @param $permission * @param Share $shared * @return mixed */ - public function get_thumbnail_public($filename, Share $shared) + public function get_thumbnail_public($filename, $permission, Share $shared) { - // Abort if thumbnail is protected - if ($shared->is_protected) { - abort(403, "Sorry, you don't have permission"); - } + // Check ability to access protected share files + $this->helper->check_protected_share_record($shared, $permission); // Get file record $file = UserFile::where('user_id', $shared->user_id) diff --git a/app/Http/Controllers/Sharing/ServeSharedController.php b/app/Http/Controllers/Sharing/ServeSharedController.php index af1843ef..0e9869e9 100644 --- a/app/Http/Controllers/Sharing/ServeSharedController.php +++ b/app/Http/Controllers/Sharing/ServeSharedController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Sharing; use App\Http\Controllers\Controller; use App\Http\Requests\Share\AuthenticateShareRequest; +use App\Http\Resources\FileResource; use App\Http\Resources\ShareResource; use App\Models\Share; use App\Models\Setting; @@ -118,20 +119,17 @@ class ServeSharedController extends Controller */ public function file_public(Share $shared) { - // Abort if file is protected - if ($shared->is_protected) { - abort(403, "Sorry, you don't have permission"); - } + // Check ability to access protected share files + $this->helper->check_protected_share_record($shared); // Get file $file = File::where('user_id', $shared->user_id) ->where('id', $shared->item_id) - ->firstOrFail(['name', 'basename', 'thumbnail', 'type', 'filesize', 'mimetype']); + ->firstOrFail(); - // Set urls + // Set access urls $file->setPublicUrl($shared->token); - // Return record - return $file; + return response(new FileResource($file), 200); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 61e128aa..41f9652d 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -15,6 +15,7 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ + // \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, \Fruitcake\Cors\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, diff --git a/app/Http/Middleware/SharedAuth.php b/app/Http/Middleware/SharedAuth.php deleted file mode 100644 index 27457a3e..00000000 --- a/app/Http/Middleware/SharedAuth.php +++ /dev/null @@ -1,29 +0,0 @@ -bearerToken()) { - if ($request->hasCookie('shared_access_token')) { - - $shared_access_token = $request->cookie('shared_access_token'); - - $request->headers->add(['Authorization' => 'Bearer ' . $shared_access_token]); - - } - } - return $next($request); - } -} diff --git a/app/Http/Resources/FileResource.php b/app/Http/Resources/FileResource.php new file mode 100644 index 00000000..a0973320 --- /dev/null +++ b/app/Http/Resources/FileResource.php @@ -0,0 +1,35 @@ + [ + 'id' => $this->id, + 'type' => 'file', + 'attributes' => [ + 'name' => $this->name, + 'basename' => $this->basename, + 'mimetype' => $this->mimetype, + 'filesize' => $this->filesize, + 'type' => $this->type, + 'file_url' => $this->file_url, + 'thumbnail' => $this->thumbnail, + 'created_at' => $this->created_at, + 'updated_at' => $this->created_at, + ] + ], + ]; + } +} diff --git a/app/Http/Resources/GatewayCollection.php b/app/Http/Resources/GatewayCollection.php deleted file mode 100644 index 7944bcf1..00000000 --- a/app/Http/Resources/GatewayCollection.php +++ /dev/null @@ -1,23 +0,0 @@ - $this->collection, - ]; - } -} diff --git a/app/Http/Resources/GatewayResource.php b/app/Http/Resources/GatewayResource.php deleted file mode 100644 index 8c7a02fc..00000000 --- a/app/Http/Resources/GatewayResource.php +++ /dev/null @@ -1,36 +0,0 @@ - [ - 'id' => (string)$this->id, - 'type' => 'gateways', - 'attributes' => [ - 'status' => $this->status, - 'sandbox' => $this->sandbox, - 'name' => $this->name, - 'slug' => $this->slug, - 'logo' => $this->logo, - 'client_id' => $this->client_id, - 'secret' => $this->secret, - 'webhook' => $this->webhook, - 'payment_processed' => $this->payment_processed, - 'optional' => $this->optional, - ] - ] - ]; - } -} diff --git a/app/Models/File.php b/app/Models/File.php index 56ba806b..0db56450 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -94,9 +94,9 @@ class File extends Model public function getThumbnailAttribute() { // Get thumbnail from external storage - if ($this->attributes['thumbnail'] && is_storage_driver(['s3', 'spaces', 'wasabi', 'backblaze'])) { + if ($this->attributes['thumbnail'] && ! is_storage_driver(['local'])) { - return Storage::temporaryUrl('file-manager/' . $this->attributes['thumbnail'], now()->addHour()); + return Storage::temporaryUrl('files/' . $this->attributes['thumbnail'], now()->addHour()); } // Get thumbnail from local storage @@ -106,7 +106,7 @@ class File extends Model $route = route('thumbnail', ['name' => $this->attributes['thumbnail']]); if ($this->public_access) { - return $route . '/public/' . $this->public_access; + return "$route/$this->public_access"; } return $route; @@ -123,7 +123,7 @@ class File extends Model public function getFileUrlAttribute() { // Get file from external storage - if (is_storage_driver(['s3', 'spaces', 'wasabi', 'backblaze'])) { + if (! is_storage_driver(['local'])) { $file_pretty_name = is_storage_driver('backblaze') ? Str::snake(mb_strtolower($this->attributes['name'])) @@ -144,7 +144,7 @@ class File extends Model $route = route('file', ['name' => $this->attributes['basename']]); if ($this->public_access) { - return $route . '/public/' . $this->public_access; + return "$route/$this->public_access"; } return $route; @@ -198,8 +198,8 @@ class File extends Model { parent::boot(); - static::creating(function ($model) { - $model->id = (string)Str::uuid(); + static::creating(function ($file) { + $file->id = (string)Str::uuid(); }); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index bf6e052e..56e5611b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -25,8 +25,6 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { - Schema::defaultStringLength(191); - $get_time_locale = App::getLocale() . '_' . mb_strtoupper(App::getLocale()); // Set locale for carbon dates diff --git a/app/Services/HelperService.php b/app/Services/HelperService.php index 3d618c8a..42d8d9ba 100644 --- a/app/Services/HelperService.php +++ b/app/Services/HelperService.php @@ -4,9 +4,11 @@ namespace App\Services; use App\Models\File; use App\Models\Folder; +use App\Models\Share; use Aws\Exception\MultipartUploadException; use Aws\S3\MultipartUploader; use DB; +use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; @@ -291,4 +293,34 @@ class HelperService return [$folders, $files]; } + + /** + * @param Share $shared + */ + function check_protected_share_record(Share $shared): void + { + if ($shared->is_protected) { + + $abort_message = "Sorry, you don't have permission"; + + if (!request()->hasCookie('share_session')) { + abort(403, $abort_message); + } + + // Get shared session + $share_session = json_decode( + request()->cookie('share_session') + ); + + // Check if is requested same share record + if ($share_session->token !== $shared->token) { + abort(403, $abort_message); + } + + // Check if share record was authenticated previously via ServeSharedController@authenticate + if (!$share_session->authenticated) { + abort(403, $abort_message); + } + } + } } \ No newline at end of file diff --git a/database/migrations/2019_08_15_171328_create_file_manager_folders.php b/database/migrations/2019_08_15_171328_create_file_manager_folders.php index 8294aea0..6b8234de 100644 --- a/database/migrations/2019_08_15_171328_create_file_manager_folders.php +++ b/database/migrations/2019_08_15_171328_create_file_manager_folders.php @@ -20,6 +20,8 @@ class CreateFileManagerFolders extends Migration $table->text('name'); $table->string('color')->nullable(); $table->longText('emoji')->nullable(); + + // TODO: upravit user scope $table->enum('user_scope', ['master', 'editor', 'visitor'])->default('master'); $table->softDeletes(); $table->timestamps(); diff --git a/database/migrations/2019_08_15_171345_create_file_manager_files.php b/database/migrations/2019_08_15_171345_create_file_manager_files.php index 989d8cc8..722cdc8c 100644 --- a/database/migrations/2019_08_15_171345_create_file_manager_files.php +++ b/database/migrations/2019_08_15_171345_create_file_manager_files.php @@ -27,6 +27,8 @@ class CreateFileManagerFiles extends Migration $table->text('type')->nullable(); $table->longText('metadata')->nullable(); + + // TODO: upravit user scope $table->enum('user_scope', ['master', 'editor', 'visitor'])->default('master'); $table->softDeletes(); diff --git a/index.html b/index.html new file mode 100644 index 00000000..1ab5110b --- /dev/null +++ b/index.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/public/mix-manifest.json b/public/mix-manifest.json index f8a09113..1964ef38 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -66,7 +66,7 @@ "/chunks/setup-wizard.js": "/chunks/setup-wizard.js?id=c6b88005b133268ed88f", "/chunks/shared.js": "/chunks/shared.js?id=2c38f535d52e0e448846", "/chunks/shared-files.js": "/chunks/shared-files.js?id=e6f7de2910d85a2dd3e4", - "/chunks/shared/authenticate.js": "/chunks/shared/authenticate.js?id=9024f3b0b3bed191fe8d", + "/chunks/shared/authenticate.js": "/chunks/shared/authenticate.js?id=f6155ac74f9bb460cf65", "/chunks/shared/file-browser.js": "/chunks/shared/file-browser.js?id=3127fab4cfd3d5f00a72", "/chunks/shared/single-file.js": "/chunks/shared/single-file.js?id=298e41c1c453bf1fde9b", "/chunks/sign-in.js": "/chunks/sign-in.js?id=af61663f3e69eae3e5ee", @@ -88,5 +88,13 @@ "/chunks/users.js": "/chunks/users.js?id=9085e0306eafce8f9fce", "/vendors~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-i~68e3c6cf.js": "/vendors~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-i~68e3c6cf.js?id=b490efdc8470a6c2f625", "/vendors~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-i~a4a4a595.js": "/vendors~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-i~a4a4a595.js?id=be99c10088b00578891b", - "/vendors~chunks/files~chunks/platform~chunks/shared~chunks/shared-files~chunks/shared/file-browser~ch~52c14f2e.js": "/vendors~chunks/files~chunks/platform~chunks/shared~chunks/shared-files~chunks/shared/file-browser~ch~52c14f2e.js?id=66afa0e341251a68c3d3" + "/vendors~chunks/files~chunks/platform~chunks/shared~chunks/shared-files~chunks/shared/file-browser~ch~52c14f2e.js": "/vendors~chunks/files~chunks/platform~chunks/shared~chunks/shared-files~chunks/shared/file-browser~ch~52c14f2e.js?id=66afa0e341251a68c3d3", + "/js/main.723fdf599ac2ecb35271.hot-update.js": "/js/main.723fdf599ac2ecb35271.hot-update.js", + "/js/main.4a8fc01e2ff5a4e6ad28.hot-update.js": "/js/main.4a8fc01e2ff5a4e6ad28.hot-update.js", + "/js/main.83e4a1fba24fb3ed581d.hot-update.js": "/js/main.83e4a1fba24fb3ed581d.hot-update.js", + "/js/main.4cd1727b0d5ad436dba4.hot-update.js": "/js/main.4cd1727b0d5ad436dba4.hot-update.js", + "/js/main.7e6541d1cbf9901a3e5a.hot-update.js": "/js/main.7e6541d1cbf9901a3e5a.hot-update.js", + "/js/main.51418924870a080afea3.hot-update.js": "/js/main.51418924870a080afea3.hot-update.js", + "/js/main.869d3fad73dd6fe243f9.hot-update.js": "/js/main.869d3fad73dd6fe243f9.hot-update.js", + "/js/main.d7f2bec845fc64b7d5e4.hot-update.js": "/js/main.d7f2bec845fc64b7d5e4.hot-update.js" } diff --git a/resources/js/store/modules/fileBrowser.js b/resources/js/store/modules/fileBrowser.js index 77d5595d..8ee33f3f 100644 --- a/resources/js/store/modules/fileBrowser.js +++ b/resources/js/store/modules/fileBrowser.js @@ -154,12 +154,16 @@ const actions = { // Get route let route = undefined - if (getters.sharedDetail && getters.sharedDetail.is_protected) - route = '/api/browse/search/private' - else if (getters.sharedDetail && !getters.sharedDetail.is_protected) - route = '/api/browse/search/public/' + router.currentRoute.params.token - else + if (getters.sharedDetail) { + let permission = getters.sharedDetail.is_protected + ? 'private' + : 'public' + + route = `/api/browse/search/${permission}/${router.currentRoute.params.token}` + + } else { route = '/api/browse/search' + } axios .get(route, { @@ -177,12 +181,11 @@ const actions = { // Get route let route = undefined - if (getters.sharedDetail && getters.sharedDetail.is_protected) - route = '/api/browse/navigation/private' - else if (getters.sharedDetail && !getters.sharedDetail.is_protected) - route = '/api/browse/navigation/public/' + router.currentRoute.params.token - else + if (getters.sharedDetail) { + route = `/api/browse/navigation/${router.currentRoute.params.token}` + } else { route = '/api/browse/navigation' + } axios .get(route + getters.sorting.URI) diff --git a/resources/js/store/modules/fileFunctions.js b/resources/js/store/modules/fileFunctions.js index 65e6c7e5..1f396d2e 100644 --- a/resources/js/store/modules/fileFunctions.js +++ b/resources/js/store/modules/fileFunctions.js @@ -22,10 +22,9 @@ const actions = { message: i18n.t('popup_zipping.message') }) - // Get route - let route = getters.sharedDetail && !getters.sharedDetail.is_protected - ? '/api/zip/folder/' + folder.id + '/public/' + router.currentRoute.params.token - : '/api/zip/folder/' + folder.id + let route = getters.sharedDetail + ? `/api/zip/folder/${folder.id}/${router.currentRoute.params.token}` + : `/api/zip/folder/${folder.id}` axios.get(route) .then(response => { @@ -37,7 +36,6 @@ const actions = { .finally(() => { commit('PROCESSING_POPUP', undefined) }) - }, downloadFiles: ({commit, getters}) => { let files = [] @@ -46,8 +44,8 @@ const actions = { getters.fileInfoDetail.forEach(file => files.push(file.id)) // Get route - let route = getters.sharedDetail && !getters.sharedDetail.is_protected - ? '/api/zip/files/public/' + router.currentRoute.params.token + let route = getters.sharedDetail + ? `/api/zip/files/${router.currentRoute.params.token}` : '/api/zip/files' commit('PROCESSING_POPUP', { @@ -87,8 +85,8 @@ const actions = { commit('CLEAR_FILEINFO_DETAIL') // Get route - let route = getters.sharedDetail && !getters.sharedDetail.is_protected - ? '/api/editor/move/public/' + router.currentRoute.params.token + let route = getters.sharedDetail + ? `/api/editor/move/${router.currentRoute.params.token}` : '/api/move' console.log(to_item); @@ -114,8 +112,8 @@ const actions = { createFolder: ({commit, getters, dispatch}, folder) => { // Get route - let route = getters.sharedDetail && !getters.sharedDetail.is_protected - ? '/api/editor/create-folder/public/' + router.currentRoute.params.token + let route = getters.sharedDetail + ? `/api/editor/create-folder/${router.currentRoute.params.token}` : '/api/create-folder' axios @@ -149,9 +147,9 @@ const actions = { commit('UPDATE_NAME_IN_FAVOURITES', data) // Get route - let route = getters.sharedDetail && !getters.sharedDetail.is_protected - ? '/api/editor/rename/' + data.id + '/public/' + router.currentRoute.params.token - : '/api/rename/' + data.id + let route = getters.sharedDetail + ? `/api/editor/rename/${data.id}/${router.currentRoute.params.token}` + : `/api/rename/${data.id}` axios .post(route, { @@ -172,8 +170,8 @@ const actions = { return new Promise((resolve, reject) => { // Get route - let route = getters.sharedDetail && !getters.sharedDetail.is_protected - ? '/api/editor/upload/public/' + router.currentRoute.params.token + let route = getters.sharedDetail + ? `/api/editor/upload/${router.currentRoute.params.token}` : '/api/upload' // Create cancel token for axios cancellation @@ -341,8 +339,8 @@ const actions = { } // Get route - let route = getters.sharedDetail && !getters.sharedDetail.is_protected - ? '/api/editor/remove/public/' + router.currentRoute.params.token + let route = getters.sharedDetail + ? `/api/editor/remove/${router.currentRoute.params.token}` : '/api/remove' axios diff --git a/resources/js/store/modules/sharing.js b/resources/js/store/modules/sharing.js index d0611db2..821d30f7 100644 --- a/resources/js/store/modules/sharing.js +++ b/resources/js/store/modules/sharing.js @@ -38,13 +38,9 @@ const actions = { payload.folder.location = 'public' - let route = getters.sharedDetail.is_protected - ? '/api/browse/folders/' + payload.folder.id + '/private' - : '/api/browse/folders/' + payload.folder.id + '/public/' + router.currentRoute.params.token - return new Promise((resolve, reject) => { axios - .get(route + getters.sorting.URI) + .get(`/api/browse/folders/${payload.folder.id}/${router.currentRoute.params.token}${getters.sorting.URI}`) .then(response => { commit('LOADING_STATE', {loading: false, data: response.data}) commit('STORE_CURRENT_FOLDER', payload.folder) @@ -66,6 +62,32 @@ const actions = { }) }) }, + getSingleFile: ({commit}) => { + + axios.get(`/api/browse/file/${router.currentRoute.params.token}`) + .then(response => { + commit('STORE_SHARED_FILE', response.data) + }) + }, + getShareDetail: ({commit, state}, token) => { + return new Promise((resolve, reject) => { + axios + .get(`/api/browse/share/${token}`) + .then(response => { + resolve(response) + + // Commit shared item options + commit('SET_SHARED_DETAIL', response.data.data.attributes) + commit('SET_PERMISSION', response.data.data.attributes.permission) + }) + .catch(error => { + reject(error) + + if (error.response.status == 404) + router.push({name: 'NotFound'}) + }) + }) + }, shareCancel: ({commit, getters}, singleItem) => { return new Promise((resolve, reject) => { @@ -109,37 +131,6 @@ const actions = { }) }) }, - getSingleFile: ({commit, state}) => { - - let route = state.sharedDetail.is_protected - ? '/api/browse/files/private' - : '/api/browse/files/' + router.currentRoute.params.token + '/public' - - axios.get(route) - .then(response => { - commit('STORE_SHARED_FILE', response.data) - }) - }, - getShareDetail: ({commit, state}, token) => { - return new Promise((resolve, reject) => { - axios - .get(`/api/browse/shared/${token}`) - .then(response => { - resolve(response) - - // Commit shared item options - commit('SET_SHARED_DETAIL', response.data.data.attributes) - commit('SET_PERMISSION', response.data.data.attributes.permission) - }) - .catch(error => { - reject(error) - - if (error.response.status == 404) { - router.push({name: 'NotFound'}) - } - }) - }) - }, } const mutations = { SET_SHARED_DETAIL(state, data) { diff --git a/resources/js/store/modules/userAuth.js b/resources/js/store/modules/userAuth.js index 75979fe1..0b2e10ca 100644 --- a/resources/js/store/modules/userAuth.js +++ b/resources/js/store/modules/userAuth.js @@ -35,7 +35,7 @@ const actions = { ) }) }, - logOut: ({getters, commit}) => { + logOut: ({commit}) => { let popup = setTimeout(() => { commit('PROCESSING_POPUP', { diff --git a/resources/js/views/Shared/SharedAuthentication.vue b/resources/js/views/Shared/SharedAuthentication.vue index e666920c..47e43b57 100644 --- a/resources/js/views/Shared/SharedAuthentication.vue +++ b/resources/js/views/Shared/SharedAuthentication.vue @@ -42,7 +42,7 @@ }, data() { return { - password: undefined, + password: 'secret', isLoading: false, } }, @@ -61,9 +61,18 @@ axios .post('/api/browse/authenticate/' + this.$route.params.token, { password: this.password - }).then(() => { + }) + .then(response => { - // todo: Redirect to file browser page + // Show file browser + if (response.data.data.attributes.type === 'folder' && this.$router.currentRoute.name !== 'SharedFileBrowser') { + this.$router.push({name: 'SharedFileBrowser'}) + } + + // Show single file + if (response.data.data.attributes.type !== 'folder' && this.$router.currentRoute.name !== 'SharedSingleFile') { + this.$router.push({name: 'SharedSingleFile'}) + } }) .catch(error => { diff --git a/routes/file.php b/routes/file.php index bcc011c1..09068ffb 100644 --- a/routes/file.php +++ b/routes/file.php @@ -8,8 +8,8 @@ Route::get('/avatars/{avatar}', [FileAccessController::class, 'get_avatar'])->na Route::get('/system/{image}', [FileAccessController::class, 'get_system_image']); // Get public thumbnails and files -Route::get('/thumbnail/{name}/public/{shared}', [FileSharedAccessController::class, 'get_thumbnail_public']); -Route::get('/file/{name}/public/{shared}', [FileSharedAccessController::class, 'get_file_public']); +Route::get('/thumbnail/{name}/{shared}', [FileSharedAccessController::class, 'get_thumbnail_public']); +Route::get('/file/{name}/{shared}', [FileSharedAccessController::class, 'get_file_public']); Route::get('/zip/{id}/public/{token}', [FileSharedAccessController::class, 'get_zip_public'])->name('zip_public'); // User master,editor,visitor access to image thumbnails and file downloads diff --git a/routes/share.php b/routes/share.php index 76864197..76f8175c 100644 --- a/routes/share.php +++ b/routes/share.php @@ -6,39 +6,30 @@ use App\Http\Controllers\Sharing\EditShareItemsController; use App\Http\Controllers\FileManager\ShareController; use App\Http\Controllers\Sharing\ServeSharedController; -// Editor functions +// Browse functions Route::group(['prefix' => 'editor'], function () { - Route::post('/create-folder/public/{shared}', [EditShareItemsController::class, 'create_folder']); - Route::patch('/rename/{id}/public/{shared}', [EditShareItemsController::class, 'rename_item']); - Route::post('/remove/public/{shared}', [EditShareItemsController::class, 'delete_item']); - Route::post('/upload/public/{shared}', [EditShareItemsController::class, 'upload']); - Route::post('/move/public/{shared}', [EditShareItemsController::class, 'move']); + Route::post('/create-folder/{shared}', [EditShareItemsController::class, 'create_folder']); + Route::patch('/rename/{id}/{shared}', [EditShareItemsController::class, 'rename_item']); + Route::post('/remove/{shared}', [EditShareItemsController::class, 'delete_item']); + Route::post('/upload/{shared}', [EditShareItemsController::class, 'upload']); + Route::post('/move/{shared}', [EditShareItemsController::class, 'move']); }); -// Editor/Visitor zip functions +// Zip shared items Route::group(['prefix' => 'zip'], function () { - Route::post('/files/public/{shared}', [EditShareItemsController::class, 'zip_multiple_files']); - Route::get('/folder/{id}/public/{shared}', [EditShareItemsController::class, 'zip_folder']); + Route::post('/files/{shared}', [EditShareItemsController::class, 'zip_multiple_files']); + Route::get('/folder/{id}/{shared}', [EditShareItemsController::class, 'zip_folder']); }); // Browse share content Route::group(['prefix' => 'browse'], function () { - Route::get('/navigation/public/{shared}', [BrowseShareController::class, 'get_public_navigation_tree']); - Route::get('/folders/{id}/public/{shared}', [BrowseShareController::class, 'get_public_folders']); - Route::get('/search/public/{shared}', [BrowseShareController::class, 'search_public']); + Route::get('/navigation/{shared}', [BrowseShareController::class, 'navigation_tree']); + Route::get('/folders/{id}/{shared}', [BrowseShareController::class, 'browse_folder']); + Route::get('/search/{shared}', [BrowseShareController::class, 'search']); Route::post('/authenticate/{shared}', [ServeSharedController::class, 'authenticate']); - Route::get('/files/{shared}/public', [ServeSharedController::class, 'file_public']); - Route::get('/shared/{shared}', [ShareController::class, 'show']); -}); - -// Private sharing secured by password -// TODO: tests -Route::group(['middleware' => ['auth:api', 'auth.shared', 'scope:visitor,editor']], function () { - Route::get('/folders/{id}/private', [ServeSharedController::class, 'get_private_folders']); - Route::get('/navigation/private', [ServeSharedController::class, 'get_private_navigation_tree']); - Route::get('/search/private', [ServeSharedController::class, 'search_private']); - Route::get('/files/private', [ServeSharedController::class, 'file_private']); + Route::get('/file/{shared}', [ServeSharedController::class, 'file_public']); + Route::get('/share/{shared}', [ShareController::class, 'show']); }); Route::get('/og-site/{shared}', [AppFunctionsController::class, 'og_site']); \ No newline at end of file diff --git a/tests/Feature/Share/PrivateShareContentAccessTest.php b/tests/Feature/Share/PrivateFilesAccessTest.php similarity index 97% rename from tests/Feature/Share/PrivateShareContentAccessTest.php rename to tests/Feature/Share/PrivateFilesAccessTest.php index 6c1df057..5ab9e67e 100644 --- a/tests/Feature/Share/PrivateShareContentAccessTest.php +++ b/tests/Feature/Share/PrivateFilesAccessTest.php @@ -8,7 +8,7 @@ use App\Services\SetupService; use Illuminate\Foundation\Testing\DatabaseMigrations; use Tests\TestCase; -class PrivateShareContentAccessTest extends TestCase +class PrivateFilesAccessTest extends TestCase { use DatabaseMigrations; diff --git a/tests/Feature/Share/PrivateVisitorTest.php b/tests/Feature/Share/PrivateVisitorTest.php new file mode 100644 index 00000000..0b6a7a9c --- /dev/null +++ b/tests/Feature/Share/PrivateVisitorTest.php @@ -0,0 +1,106 @@ +setup = app()->make(SetupService::class); + } + + /** + * @test + */ + public function authenticated_visitor_get_folder_content() + { + $user = User::factory(User::class) + ->create(); + + $root = Folder::factory(Folder::class) + ->create([ + 'name' => 'root', + 'user_id' => $user->id, + ]); + + $share = Share::factory(Share::class) + ->create([ + 'item_id' => $root->id, + 'user_id' => $user->id, + 'type' => 'folder', + 'is_protected' => true, + 'permission' => 'editor', + ]); + + $folder = Folder::factory(Folder::class) + ->create([ + 'parent_id' => $root->id, + 'name' => 'Documents', + "user_scope" => "master", + 'user_id' => $user->id, + ]); + + $file = File::factory(File::class) + ->create([ + 'folder_id' => $root->id, + 'name' => 'Document', + 'basename' => 'document.pdf', + "mimetype" => "application/pdf", + "user_scope" => "master", + "type" => "file", + 'user_id' => $user->id, + ]); + + $this->withUnencryptedCookie('share_session', json_encode([ + 'token' => $share->token, + 'authenticated' => true, + ])) + ->get("/api/browse/folders/$root->id/private/$share->token") + ->assertStatus(200) + ->assertExactJson([ + [ + "id" => $folder->id, + "user_id" => $user->id, + "parent_id" => $root->id, + "name" => "Documents", + "color" => null, + "emoji" => null, + "user_scope" => "master", + "deleted_at" => null, + "created_at" => $folder->created_at, + "updated_at" => $folder->updated_at->toJson(), + "items" => 0, + "trashed_items" => 0, + "type" => "folder", + ], + [ + "id" => $file->id, + "user_id" => $user->id, + "folder_id" => $root->id, + "thumbnail" => null, + "name" => "Document", + "basename" => "document.pdf", + "mimetype" => "application/pdf", + "filesize" => $file->filesize, + "type" => "file", + "metadata" => null, + "user_scope" => "master", + "deleted_at" => null, + "created_at" => $file->created_at, + "updated_at" => $file->updated_at->toJson(), + "file_url" => "http://localhost/file/document.pdf/private/$share->token", + ] + ]); + } +} diff --git a/tests/Feature/Share/PublicFilesAccessTest.php b/tests/Feature/Share/PublicFilesAccessTest.php new file mode 100644 index 00000000..22cafed3 --- /dev/null +++ b/tests/Feature/Share/PublicFilesAccessTest.php @@ -0,0 +1,254 @@ +setup = app()->make(SetupService::class); + } + + /** + * @test + */ + public function it_get_public_file_record_and_download_file_within() + { + Storage::fake('local'); + + $this->setup->create_directories(); + + collect(['private', 'public']) + ->each(function ($permission) { + + $user = User::factory(User::class) + ->create(); + + $document = UploadedFile::fake() + ->create(Str::random() . '-fake-file.pdf', 1000, 'application/pdf'); + + Storage::putFileAs("files/$user->id", $document, $document->name); + + $file = File::factory(File::class) + ->create([ + 'filesize' => $document->getSize(), + 'user_id' => $user->id, + 'basename' => $document->name, + 'name' => 'fake-file.pdf', + ]); + + $share = Share::factory(Share::class) + ->create([ + 'item_id' => $file->id, + 'user_id' => $user->id, + 'type' => 'file', + 'is_protected' => $permission === 'private', + 'password' => \Hash::make('secret'), + ]); + + if ($permission === 'private') { + + $cookie = ['share_session' => json_encode([ + 'token' => $share->token, + 'authenticated' => true, + ])]; + + $this->disableCookieEncryption(); + $this->defaultCookies = $cookie; + + $this->get("/api/browse/file/$share->token/private") + ->assertStatus(200) + ->assertJsonFragment([ + 'basename' => $document->name + ]); + + $this->get("/file/$document->name/private/$share->token") + ->assertStatus(200); + } + + if ($permission === 'public') { + + $this->get("/api/browse/file/$share->token/public") + ->assertStatus(200) + ->assertJsonFragment([ + 'basename' => $document->name + ]); + + // Get shared file + $this->get("/file/$document->name/public/$share->token") + ->assertStatus(200); + } + + /*$this->assertDatabaseHas('traffic', [ + 'user_id' => $user->id, + ]);*/ + }); + } + + /** + * @test + */ + public function it_try_to_get_protected_file_record() + { + $share = Share::factory(Share::class) + ->create([ + 'type' => 'file', + 'is_protected' => true, + ]); + + // Get share record + $this->get("/api/browse/file/$share->token/public") + ->assertStatus(403); + } + + /** + * @test + */ + public function it_get_shared_image() + { + Storage::fake('local'); + + $this->setup->create_directories(); + + $user = User::factory(User::class) + ->create(); + + $thumbnail = UploadedFile::fake() + ->image(Str::random() . '-fake-image.jpg'); + + Storage::putFileAs("files/$user->id", $thumbnail, $thumbnail->name); + + $file = File::factory(File::class) + ->create([ + 'user_id' => $user->id, + 'thumbnail' => $thumbnail->name, + 'basename' => $thumbnail->name, + 'name' => 'fake-thumbnail.jpg', + 'type' => 'image', + 'mimetype' => 'jpg', + ]); + + $share = Share::factory(Share::class) + ->create([ + 'item_id' => $file->id, + 'user_id' => $user->id, + 'type' => 'file', + 'is_protected' => false, + ]); + + $this->get("/shared/$share->token") + ->assertStatus(200); + } + + /** + * @test + */ + public function it_get_public_thumbnail() + { + Storage::fake('local'); + + $this->setup->create_directories(); + + collect(['private', 'public']) + ->each(function ($permission) { + + $user = User::factory(User::class) + ->create(); + + $thumbnail = UploadedFile::fake() + ->image(Str::random() . '-fake-thumbnail.jpg'); + + Storage::putFileAs("files/$user->id", $thumbnail, $thumbnail->name); + + $file = File::factory(File::class) + ->create([ + 'user_id' => $user->id, + 'thumbnail' => $thumbnail->name, + 'name' => 'fake-thumbnail.jpg', + ]); + + $share = Share::factory(Share::class) + ->create([ + 'item_id' => $file->id, + 'user_id' => $user->id, + 'type' => 'file', + 'is_protected' => $permission === 'private', + 'password' => \Hash::make('secret'), + ]); + + // Get thumbnail file + if ($permission === 'private') { + $this->withCookie('share_session', json_encode([ + 'token' => $share->token, + 'authenticated' => true, + ])) + ->get("/thumbnail/$thumbnail->name/private/$share->token") + ->assertStatus(200); + } + + if ($permission === 'public') { + $this->get("/thumbnail/$thumbnail->name/public/$share->token") + ->assertStatus(200); + } + + $this->assertDatabaseMissing('traffic', [ + 'user_id' => $user->id, + 'download' => null, + ]); + }); + } + + /** + * @test + */ + public function it_download_publicly_zipped_files() + { + Storage::fake('local'); + + $this->setup->create_directories(); + + $user = User::factory(User::class) + ->create(); + + $share = Share::factory(Share::class) + ->create([ + 'user_id' => $user->id, + 'type' => 'folder', + 'is_protected' => false, + ]); + + $zip = Zip::factory(Zip::class)->create([ + 'basename' => 'EHWKcuvKzA4Gv29v-archive.zip', + 'user_id' => $user->id, + 'shared_token' => $share->token, + ]); + + $file = UploadedFile::fake() + ->create($zip->basename, 1000, 'application/zip'); + + Storage::putFileAs("zip", $file, $file->name); + + $this->get("/zip/$zip->id/public/$share->token") + ->assertStatus(200); + + $this->assertDatabaseMissing('traffic', [ + 'user_id' => $user->id, + 'download' => null, + ]); + } +} diff --git a/tests/Feature/Share/PublicShareContentAccessTest.php b/tests/Feature/Share/PublicShareContentAccessTest.php deleted file mode 100644 index f90e443e..00000000 --- a/tests/Feature/Share/PublicShareContentAccessTest.php +++ /dev/null @@ -1,211 +0,0 @@ -setup = app()->make(SetupService::class); - } - - /** - * @test - */ - public function it_get_public_file_record_and_download_file_within() - { - Storage::fake('local'); - - $this->setup->create_directories(); - - $user = User::factory(User::class) - ->create(); - - $document = UploadedFile::fake() - ->create(Str::random() . '-fake-file.pdf', 1000, 'application/pdf'); - - Storage::putFileAs("files/$user->id", $document, $document->name); - - $file = File::factory(File::class) - ->create([ - 'filesize' => $document->getSize(), - 'user_id' => $user->id, - 'basename' => $document->name, - 'name' => 'fake-file.pdf', - ]); - - $share = Share::factory(Share::class) - ->create([ - 'item_id' => $file->id, - 'user_id' => $user->id, - 'type' => 'file', - 'is_protected' => false, - ]); - - // Get share record - $this->get("/api/browse/files/$share->token/public") - ->assertStatus(200) - ->assertJsonFragment([ - 'basename' => $document->name - ]); - - // Get shared file - $this->get("/file/$document->name/public/$share->token") - ->assertStatus(200); - - $this->assertDatabaseHas('traffic', [ - 'user_id' => $user->id, - 'download' => '1024000', - ]); - } - - /** - * @test - */ - public function it_try_to_get_protected_file_record() - { - $share = Share::factory(Share::class) - ->create([ - 'type' => 'file', - 'is_protected' => true, - ]); - - // Get share record - $this->get("/api/browse/files/$share->token/public") - ->assertStatus(403); - } - - /** - * @test - */ - public function it_get_shared_image() - { - Storage::fake('local'); - - $this->setup->create_directories(); - - $user = User::factory(User::class) - ->create(); - - $thumbnail = UploadedFile::fake() - ->image(Str::random() . '-fake-image.jpg'); - - Storage::putFileAs("files/$user->id", $thumbnail, $thumbnail->name); - - $file = File::factory(File::class) - ->create([ - 'user_id' => $user->id, - 'thumbnail' => $thumbnail->name, - 'basename' => $thumbnail->name, - 'name' => 'fake-thumbnail.jpg', - 'type' => 'image', - 'mimetype' => 'jpg', - ]); - - $share = Share::factory(Share::class) - ->create([ - 'item_id' => $file->id, - 'user_id' => $user->id, - 'type' => 'file', - 'is_protected' => false, - ]); - - $this->get("/shared/$share->token") - ->assertStatus(200); - } - - /** - * @test - */ - public function it_download_public_thumbnail() - { - Storage::fake('local'); - - $this->setup->create_directories(); - - $user = User::factory(User::class) - ->create(); - - $thumbnail = UploadedFile::fake() - ->image(Str::random() . '-fake-thumbnail.jpg'); - - Storage::putFileAs("files/$user->id", $thumbnail, $thumbnail->name); - - $file = File::factory(File::class) - ->create([ - 'user_id' => $user->id, - 'thumbnail' => $thumbnail->name, - 'name' => 'fake-thumbnail.jpg', - ]); - - $share = Share::factory(Share::class) - ->create([ - 'item_id' => $file->id, - 'user_id' => $user->id, - 'type' => 'file', - 'is_protected' => false, - ]); - - // Get thumbnail file - $this->get("/thumbnail/$thumbnail->name/public/$share->token") - ->assertStatus(200); - - $this->assertDatabaseMissing('traffic', [ - 'user_id' => $user->id, - 'download' => null, - ]); - } - - /** - * @test - */ - public function it_download_publicly_zipped_files() - { - Storage::fake('local'); - - $this->setup->create_directories(); - - $user = User::factory(User::class) - ->create(); - - $share = Share::factory(Share::class) - ->create([ - 'user_id' => $user->id, - 'type' => 'folder', - 'is_protected' => false, - ]); - - $zip = Zip::factory(Zip::class)->create([ - 'basename' => 'EHWKcuvKzA4Gv29v-archive.zip', - 'user_id' => $user->id, - 'shared_token' => $share->token, - ]); - - $file = UploadedFile::fake() - ->create($zip->basename, 1000, 'application/zip'); - - Storage::putFileAs("zip", $file, $file->name); - - $this->get("/zip/$zip->id/public/$share->token") - ->assertStatus(200); - - $this->assertDatabaseMissing('traffic', [ - 'user_id' => $user->id, - 'download' => null, - ]); - } -} diff --git a/tests/Feature/Share/ShareEditorTest.php b/tests/Feature/Share/PublicVisitorTest.php similarity index 99% rename from tests/Feature/Share/ShareEditorTest.php rename to tests/Feature/Share/PublicVisitorTest.php index c7e151ab..b26dde67 100644 --- a/tests/Feature/Share/ShareEditorTest.php +++ b/tests/Feature/Share/PublicVisitorTest.php @@ -15,7 +15,7 @@ use Laravel\Sanctum\Sanctum; use Storage; use Tests\TestCase; -class ShareEditorTest extends TestCase +class PrivateVisitorTest extends TestCase { use DatabaseMigrations; @@ -775,7 +775,7 @@ class ShareEditorTest extends TestCase ]); - $this->getJson("/api/browse/files/$share->token/public") + $this->getJson("/api/browse/file/$share->token/public") ->assertStatus(200) ->assertJsonFragment([ 'name' => 'Document'