diff --git a/app/File.php b/app/File.php
index ebfa0a296..055f217bd 100644
--- a/app/File.php
+++ b/app/File.php
@@ -7,12 +7,20 @@ class File extends Ownable
 
     /**
      * Get the page this file was uploaded to.
-     * @return mixed
+     * @return Page
      */
     public function page()
     {
         return $this->belongsTo(Page::class, 'uploaded_to');
     }
 
+    /**
+     * Get the url of this file.
+     * @return string
+     */
+    public function getUrl()
+    {
+        return '/files/' . $this->id;
+    }
 
 }
diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php
index b97112c1c..e09fb98c6 100644
--- a/app/Http/Controllers/FileController.php
+++ b/app/Http/Controllers/FileController.php
@@ -1,10 +1,7 @@
-<?php
-
-namespace BookStack\Http\Controllers;
+<?php namespace BookStack\Http\Controllers;
 
 use BookStack\Exceptions\FileUploadException;
 use BookStack\File;
-use BookStack\Page;
 use BookStack\Repos\PageRepo;
 use BookStack\Services\FileService;
 use Illuminate\Http\Request;
@@ -37,16 +34,18 @@ class FileController extends Controller
      */
     public function upload(Request $request)
     {
-        // TODO - Add file upload permission check
-        // TODO - ensure user has permission to edit relevant page.
         // TODO - ensure uploads are deleted on page delete.
-
         $this->validate($request, [
             'uploaded_to' => 'required|integer|exists:pages,id'
         ]);
 
-        $uploadedFile = $request->file('file');
         $pageId = $request->get('uploaded_to');
+        $page = $this->pageRepo->getById($pageId);
+
+        $this->checkPermission('file-create-all');
+        $this->checkOwnablePermission('page-update', $page);
+
+        $uploadedFile = $request->file('file');
 
         try {
             $file = $this->fileService->saveNewUpload($uploadedFile, $pageId);
@@ -62,10 +61,10 @@ class FileController extends Controller
      * @param $pageId
      * @return mixed
      */
-    public function getFilesForPage($pageId)
+    public function listForPage($pageId)
     {
-        // TODO - check view permission on page?
         $page = $this->pageRepo->getById($pageId);
+        $this->checkOwnablePermission('page-view', $page);
         return response()->json($page->files);
     }
 
@@ -75,17 +74,47 @@ class FileController extends Controller
      * @param Request $request
      * @return mixed
      */
-    public function sortFilesForPage($pageId, Request $request)
+    public function sortForPage($pageId, Request $request)
     {
         $this->validate($request, [
             'files' => 'required|array',
             'files.*.id' => 'required|integer',
         ]);
         $page = $this->pageRepo->getById($pageId);
+        $this->checkOwnablePermission('page-update', $page);
+
         $files = $request->get('files');
         $this->fileService->updateFileOrderWithinPage($files, $pageId);
         return response()->json(['message' => 'File order updated']);
     }
 
+    /**
+     * Get a file from storage.
+     * @param $fileId
+     */
+    public function get($fileId)
+    {
+        $file = $this->file->findOrFail($fileId);
+        $page = $this->pageRepo->getById($file->uploaded_to);
+        $this->checkOwnablePermission('page-view', $page);
 
+        $fileContents = $this->fileService->getFile($file);
+        return response($fileContents, 200, [
+            'Content-Type' => 'application/octet-stream',
+            'Content-Disposition' => 'attachment; filename="'. $file->name .'"'
+        ]);
+    }
+
+    /**
+     * Delete a specific file in the system.
+     * @param $fileId
+     * @return mixed
+     */
+    public function delete($fileId)
+    {
+        $file = $this->file->findOrFail($fileId);
+        $this->checkOwnablePermission($file, 'file-delete');
+        $this->fileService->deleteFile($file);
+        return response()->json(['message' => 'File deleted']);
+    }
 }
diff --git a/app/Services/FileService.php b/app/Services/FileService.php
index 689fa0600..7429f0e64 100644
--- a/app/Services/FileService.php
+++ b/app/Services/FileService.php
@@ -4,12 +4,24 @@
 use BookStack\Exceptions\FileUploadException;
 use BookStack\File;
 use Exception;
+use Illuminate\Contracts\Filesystem\FileNotFoundException;
 use Illuminate\Support\Collection;
 use Symfony\Component\HttpFoundation\File\UploadedFile;
 
 class FileService extends UploadService
 {
 
+    /**
+     * Get a file from storage.
+     * @param File $file
+     * @return string
+     */
+    public function getFile(File $file)
+    {
+        $filePath = $this->getStorageBasePath() . $file->path;
+        return $this->getStorage()->get($filePath);
+    }
+
     /**
      * Store a new file upon user upload.
      * @param UploadedFile $uploadedFile
@@ -76,4 +88,22 @@ class FileService extends UploadService
         }
     }
 
+    /**
+     * Delete a file and any empty folders the deletion leaves.
+     * @param File $file
+     */
+    public function deleteFile(File $file)
+    {
+        $storedFilePath = $this->getStorageBasePath() . $file->path;
+        $storage = $this->getStorage();
+        $dirPath = dirname($storedFilePath);
+
+        $storage->delete($storedFilePath);
+        if (count($storage->allFiles($dirPath)) === 0) {
+            $storage->deleteDirectory($dirPath);
+        }
+
+        $file->delete();
+    }
+
 }
\ No newline at end of file
diff --git a/database/migrations/2016_10_09_142037_create_files_table.php b/database/migrations/2016_10_09_142037_create_files_table.php
index 4eaa86aeb..57ddd1202 100644
--- a/database/migrations/2016_10_09_142037_create_files_table.php
+++ b/database/migrations/2016_10_09_142037_create_files_table.php
@@ -28,6 +28,26 @@ class CreateFilesTable extends Migration
             $table->index('uploaded_to');
             $table->timestamps();
         });
+
+        // Get roles with permissions we need to change
+        $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
+
+        // Create & attach new entity permissions
+        $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
+        $entity = 'File';
+        foreach ($ops as $op) {
+            $permissionId = DB::table('role_permissions')->insertGetId([
+                'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)),
+                'display_name' => $op . ' ' . $entity . 's',
+                'created_at' => \Carbon\Carbon::now()->toDateTimeString(),
+                'updated_at' => \Carbon\Carbon::now()->toDateTimeString()
+            ]);
+            DB::table('permission_role')->insert([
+                'role_id' => $adminRoleId,
+                'permission_id' => $permissionId
+            ]);
+        }
+
     }
 
     /**
@@ -38,5 +58,17 @@ class CreateFilesTable extends Migration
     public function down()
     {
         Schema::dropIfExists('files');
+
+        // Get roles with permissions we need to change
+        $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
+
+        // Create & attach new entity permissions
+        $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
+        $entity = 'File';
+        foreach ($ops as $op) {
+            $permName = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
+            $permission = DB::table('role_permissions')->where('name', '=', $permName)->get();
+            DB::table('permission_role')->where('permission_id', '=', $permission->id)->delete();
+        }
     }
 }
diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js
index 52477a4ad..b5353e7d9 100644
--- a/resources/assets/js/controllers.js
+++ b/resources/assets/js/controllers.js
@@ -575,9 +575,9 @@ module.exports = function (ngApp, events) {
              */
             function getFiles() {
                 let url = window.baseUrl(`/files/get/page/${pageId}`)
-                $http.get(url).then(responseData => {
-                    $scope.files = responseData.data;
-                    currentOrder = responseData.data.map(file => {return file.id}).join(':');
+                $http.get(url).then(resp => {
+                    $scope.files = resp.data;
+                    currentOrder = resp.data.map(file => {return file.id}).join(':');
                 });
             }
             getFiles();
@@ -595,6 +595,17 @@ module.exports = function (ngApp, events) {
                 events.emit('success', 'File uploaded');
             };
 
+            /**
+             * Delete a file from the server and, on success, the local listing.
+             * @param file
+             */
+            $scope.deleteFile = function(file) {
+                  $http.delete(`/files/${file.id}`).then(resp => {
+                      events.emit('success', resp.data.message);
+                      $scope.files.splice($scope.files.indexOf(file), 1);
+                  });
+            };
+
         }]);
 
 };
diff --git a/resources/views/pages/form-toolbox.blade.php b/resources/views/pages/form-toolbox.blade.php
index 3a344b651..9ebf223f0 100644
--- a/resources/views/pages/form-toolbox.blade.php
+++ b/resources/views/pages/form-toolbox.blade.php
@@ -46,7 +46,7 @@
                 <tr ng-repeat="file in files track by $index">
                     <td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
                     <td ng-bind="file.name"></td>
-                    <td width="10" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td>
+                    <td width="10" ng-click="deleteFile(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td>
                 </tr>
                 </tbody>
             </table>
diff --git a/resources/views/pages/sidebar-tree-list.blade.php b/resources/views/pages/sidebar-tree-list.blade.php
index 5fcec8731..fa9cc84aa 100644
--- a/resources/views/pages/sidebar-tree-list.blade.php
+++ b/resources/views/pages/sidebar-tree-list.blade.php
@@ -1,6 +1,15 @@
 
 <div class="book-tree" ng-non-bindable>
 
+    @if ($page->files->count() > 0)
+        <h6 class="text-muted">Attachments</h6>
+        @foreach($page->files as $file)
+            <div class="attachment">
+                <a href="{{ $file->getUrl() }}"><i class="zmdi zmdi-file"></i> {{ $file->name }}</a>
+            </div>
+        @endforeach
+    @endif
+
     @if (isset($pageNav) && $pageNav)
         <h6 class="text-muted">Page Navigation</h6>
         <div class="sidebar-page-nav menu">
@@ -10,8 +19,6 @@
                 </li>
             @endforeach
         </div>
-
-
     @endif
 
     <h6 class="text-muted">Book Navigation</h6>
diff --git a/resources/views/settings/roles/form.blade.php b/resources/views/settings/roles/form.blade.php
index 5e653f8de..4f1987c03 100644
--- a/resources/views/settings/roles/form.blade.php
+++ b/resources/views/settings/roles/form.blade.php
@@ -106,6 +106,19 @@
                             <label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) All</label>
                         </td>
                     </tr>
+                    <tr>
+                        <td>Attached <br>Files</td>
+                        <td>@include('settings/roles/checkbox', ['permission' => 'file-create-all'])</td>
+                        <td style="line-height:1.2;"><small class="faded">Controlled by the asset they are uploaded to</small></td>
+                        <td>
+                            <label>@include('settings/roles/checkbox', ['permission' => 'file-update-own']) Own</label>
+                            <label>@include('settings/roles/checkbox', ['permission' => 'file-update-all']) All</label>
+                        </td>
+                        <td>
+                            <label>@include('settings/roles/checkbox', ['permission' => 'file-delete-own']) Own</label>
+                            <label>@include('settings/roles/checkbox', ['permission' => 'file-delete-all']) All</label>
+                        </td>
+                    </tr>
                 </table>
             </div>
         </div>
diff --git a/routes/web.php b/routes/web.php
index 7e05af483..514f82f99 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -88,9 +88,11 @@ Route::group(['middleware' => 'auth'], function () {
     });
 
     // File routes
+    Route::get('/files/{id}', 'FileController@get');
     Route::post('/files/upload', 'FileController@upload');
-    Route::get('/files/get/page/{pageId}', 'FileController@getFilesForPage');
-    Route::put('/files/sort/page/{pageId}', 'FileController@sortFilesForPage');
+    Route::get('/files/get/page/{pageId}', 'FileController@listForPage');
+    Route::put('/files/sort/page/{pageId}', 'FileController@sortForPage');
+    Route::delete('/files/{id}', 'FileController@delete');
 
     // AJAX routes
     Route::put('/ajax/page/{id}/save-draft', 'PageController@saveDraft');