]> BookStack Code Mirror - bookstack/commitdiff
URL Handling: Removed referrer-based redirect handling
authorDan Brown <redacted>
Sun, 10 Dec 2023 12:37:21 +0000 (12:37 +0000)
committerDan Brown <redacted>
Sun, 10 Dec 2023 12:37:21 +0000 (12:37 +0000)
Swapped back handling to instead be pre-determined instead of being
based upon session/referrer which would cause inconsistent results when
referrer data was not available (redirect to app-loaded images/files).

To support, this adds a mechansism to provide a URL through request
data.

Also cleaned up some imports in code while making changes.
Closes #4656.

17 files changed:
app/Access/Controllers/ForgotPasswordController.php
app/Access/Controllers/ResetPasswordController.php
app/Access/Controllers/SocialController.php
app/Activity/Controllers/FavouriteController.php
app/Activity/Controllers/WatchController.php
app/Entities/Controllers/ChapterController.php
app/Entities/Controllers/PageController.php
app/Http/Controller.php
app/Users/Controllers/RoleController.php
app/Users/Controllers/UserPreferencesController.php
resources/views/common/dark-mode-toggle.blade.php
resources/views/common/sort.blade.php
resources/views/entities/view-toggle.blade.php
tests/Activity/WatchTest.php
tests/FavouriteTest.php
tests/User/RoleManagementTest.php
tests/User/UserPreferencesTest.php

index bc59e9d2fcac392376c583e1ae3baab49a3d00aa..86fbe8fa36798085cbd492e44870f1b81f017784 100644 (file)
@@ -9,11 +9,6 @@ use Illuminate\Support\Facades\Password;
 
 class ForgotPasswordController extends Controller
 {
-    /**
-     * Create a new controller instance.
-     *
-     * @return void
-     */
     public function __construct()
     {
         $this->middleware('guest');
@@ -30,10 +25,6 @@ class ForgotPasswordController extends Controller
 
     /**
      * Send a reset link to the given user.
-     *
-     * @param \Illuminate\Http\Request $request
-     *
-     * @return \Illuminate\Http\RedirectResponse
      */
     public function sendResetLinkEmail(Request $request)
     {
@@ -56,13 +47,13 @@ class ForgotPasswordController extends Controller
             $message = trans('auth.reset_password_sent', ['email' => $request->get('email')]);
             $this->showSuccessNotification($message);
 
-            return back()->with('status', trans($response));
+            return redirect('/password/email')->with('status', trans($response));
         }
 
         // If an error was returned by the password broker, we will get this message
         // translated so we can notify a user of the problem. We'll redirect back
         // to where the users came from so they can attempt this process again.
-        return back()->withErrors(
+        return redirect('/password/email')->withErrors(
             ['email' => trans($response)]
         );
     }
index eae4e5e25c28ebc28b6be49a657a6dd6060bbb4e..a8a45dddf1f87f4ca80fb13951fafdf58c4fceab 100644 (file)
@@ -66,7 +66,7 @@ class ResetPasswordController extends Controller
         // redirect them back to where they came from with their error message.
         return $response === Password::PASSWORD_RESET
             ? $this->sendResetResponse()
-            : $this->sendResetFailedResponse($request, $response);
+            : $this->sendResetFailedResponse($request, $response, $request->get('token'));
     }
 
     /**
@@ -83,7 +83,7 @@ class ResetPasswordController extends Controller
     /**
      * Get the response for a failed password reset.
      */
-    protected function sendResetFailedResponse(Request $request, string $response): RedirectResponse
+    protected function sendResetFailedResponse(Request $request, string $response, string $token): RedirectResponse
     {
         // We show invalid users as invalid tokens as to not leak what
         // users may exist in the system.
@@ -91,7 +91,7 @@ class ResetPasswordController extends Controller
             $response = Password::INVALID_TOKEN;
         }
 
-        return redirect()->back()
+        return redirect("/password/reset/{$token}")
             ->withInput($request->only('email'))
             ->withErrors(['email' => trans($response)]);
     }
index bbdabb4ab02824ea11ccc87010eb98c28f2b3ea8..07f57062d01228662517facd8eb5ac4b55e01915 100644 (file)
@@ -91,7 +91,7 @@ class SocialController extends Controller
             return $this->socialRegisterCallback($socialDriver, $socialUser);
         }
 
-        return redirect()->back();
+        return redirect('/');
     }
 
     /**
index d2534ddfe090b9370f0b4f7b9a51eae512bb43f3..b3aff1cef421e5c572e27024db762f719bad9781 100644 (file)
@@ -2,9 +2,6 @@
 
 namespace BookStack\Activity\Controllers;
 
-use BookStack\Activity\Models\Favouritable;
-use BookStack\App\Model;
-use BookStack\Entities\Models\Entity;
 use BookStack\Entities\Queries\TopFavourites;
 use BookStack\Entities\Tools\MixedEntityRequestHelper;
 use BookStack\Http\Controller;
@@ -52,7 +49,7 @@ class FavouriteController extends Controller
             'name' => $entity->name,
         ]));
 
-        return redirect()->back();
+        return redirect($entity->getUrl());
     }
 
     /**
@@ -70,6 +67,6 @@ class FavouriteController extends Controller
             'name' => $entity->name,
         ]));
 
-        return redirect()->back();
+        return redirect($entity->getUrl());
     }
 }
index c0b1c58724926752c8656fc3685431f0e6361a9f..5df75da39cd5b997af3cd710cb553f1204f4f86f 100644 (file)
@@ -24,6 +24,6 @@ class WatchController extends Controller
 
         $this->showSuccessNotification(trans('activities.watch_update_level_notification'));
 
-        return redirect()->back();
+        return redirect($watchable->getUrl());
     }
 }
index ee1df05819aae47c6e3a991480f0f891e7f3e4e7..40a5373031733aa0b4bf1acfbbe95053afe01e72 100644 (file)
@@ -12,6 +12,7 @@ use BookStack\Entities\Tools\HierarchyTransformer;
 use BookStack\Entities\Tools\NextPreviousContentLocator;
 use BookStack\Exceptions\MoveOperationException;
 use BookStack\Exceptions\NotFoundException;
+use BookStack\Exceptions\NotifyException;
 use BookStack\Exceptions\PermissionsException;
 use BookStack\Http\Controller;
 use BookStack\References\ReferenceFetcher;
@@ -170,7 +171,7 @@ class ChapterController extends Controller
     /**
      * Perform the move action for a chapter.
      *
-     * @throws NotFoundException
+     * @throws NotFoundException|NotifyException
      */
     public function move(Request $request, string $bookSlug, string $chapterSlug)
     {
@@ -184,13 +185,13 @@ class ChapterController extends Controller
         }
 
         try {
-            $newBook = $this->chapterRepo->move($chapter, $entitySelection);
+            $this->chapterRepo->move($chapter, $entitySelection);
         } catch (PermissionsException $exception) {
             $this->showPermissionError();
         } catch (MoveOperationException $exception) {
             $this->showErrorNotification(trans('errors.selected_book_not_found'));
 
-            return redirect()->back();
+            return redirect($chapter->getUrl('/move'));
         }
 
         return redirect($chapter->getUrl());
@@ -231,7 +232,7 @@ class ChapterController extends Controller
         if (is_null($newParentBook)) {
             $this->showErrorNotification(trans('errors.selected_book_not_found'));
 
-            return redirect()->back();
+            return redirect($chapter->getUrl('/copy'));
         }
 
         $this->checkOwnablePermission('chapter-create', $newParentBook);
index 6249310650494d19c157bd6b56616d017fb12df1..4d8c7e809f9b087a12d5dff8702ec0c5a5d0fbc8 100644 (file)
@@ -391,7 +391,7 @@ class PageController extends Controller
         } catch (Exception $exception) {
             $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
 
-            return redirect()->back();
+            return redirect($page->getUrl('/move'));
         }
 
         return redirect($page->getUrl());
@@ -431,7 +431,7 @@ class PageController extends Controller
         if (is_null($newParent)) {
             $this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
 
-            return redirect()->back();
+            return redirect($page->getUrl('/copy'));
         }
 
         $this->checkOwnablePermission('page-create', $newParent);
index 6e81dfd65738942ee30cdb542fe9775360c6a8a1..8facf5dab3c3331d1538b373b68b269d6c6f53b4 100644 (file)
@@ -9,6 +9,8 @@ use BookStack\Facades\Activity;
 use Illuminate\Foundation\Bus\DispatchesJobs;
 use Illuminate\Foundation\Validation\ValidatesRequests;
 use Illuminate\Http\JsonResponse;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Http\Request;
 use Illuminate\Routing\Controller as BaseController;
 
 abstract class Controller extends BaseController
@@ -165,4 +167,20 @@ abstract class Controller extends BaseController
     {
         return ['image_extension', 'mimes:jpeg,png,gif,webp', 'max:' . (config('app.upload_limit') * 1000)];
     }
+
+    /**
+     * Redirect to the URL provided in the request as a '_return' parameter.
+     * Will check that the parameter leads to a URL under the root path of the system.
+     */
+    protected function redirectToRequest(Request $request): RedirectResponse
+    {
+        $basePath = url('/');
+        $returnUrl = $request->input('_return') ?? $basePath;
+
+        if (!str_starts_with($returnUrl, $basePath)) {
+            return redirect($basePath);
+        }
+
+        return redirect($returnUrl);
+    }
 }
index 0052d829dd905c0006d5615c51fa64e3f60ea704..a874ce4d60fc6e207579148be1fdd1104ca605c0 100644 (file)
@@ -154,7 +154,7 @@ class RoleController extends Controller
         } catch (PermissionsException $e) {
             $this->showErrorNotification($e->getMessage());
 
-            return redirect()->back();
+            return redirect("/settings/roles/delete/{$id}");
         }
 
         return redirect('/settings/roles');
index 3600dc55f59e0c09459abd937a61dec8eaf07da8..0bed2d22a434c61ff5c353aa83f44e06aa74f63c 100644 (file)
@@ -3,9 +3,6 @@
 namespace BookStack\Users\Controllers;
 
 use BookStack\Http\Controller;
-use BookStack\Permissions\PermissionApplicator;
-use BookStack\Settings\UserNotificationPreferences;
-use BookStack\Settings\UserShortcutMap;
 use BookStack\Users\UserRepo;
 use Illuminate\Http\Request;
 
@@ -23,7 +20,7 @@ class UserPreferencesController extends Controller
     {
         $valueViewTypes = ['books', 'bookshelves', 'bookshelf'];
         if (!in_array($type, $valueViewTypes)) {
-            return redirect()->back(500);
+            return $this->redirectToRequest($request);
         }
 
         $view = $request->get('view');
@@ -34,7 +31,7 @@ class UserPreferencesController extends Controller
         $key = $type . '_view_type';
         setting()->putForCurrentUser($key, $view);
 
-        return redirect()->back(302, [], "/");
+        return $this->redirectToRequest($request);
     }
 
     /**
@@ -44,7 +41,7 @@ class UserPreferencesController extends Controller
     {
         $validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users', 'roles', 'webhooks', 'tags', 'page_revisions'];
         if (!in_array($type, $validSortTypes)) {
-            return redirect()->back(500);
+            return $this->redirectToRequest($request);
         }
 
         $sort = substr($request->get('sort') ?: 'name', 0, 50);
@@ -55,18 +52,18 @@ class UserPreferencesController extends Controller
         setting()->putForCurrentUser($sortKey, $sort);
         setting()->putForCurrentUser($orderKey, $order);
 
-        return redirect()->back(302, [], "/");
+        return $this->redirectToRequest($request);
     }
 
     /**
      * Toggle dark mode for the current user.
      */
-    public function toggleDarkMode()
+    public function toggleDarkMode(Request $request)
     {
         $enabled = setting()->getForCurrentUser('dark-mode-enabled');
         setting()->putForCurrentUser('dark-mode-enabled', $enabled ? 'false' : 'true');
 
-        return redirect()->back();
+        return $this->redirectToRequest($request);
     }
 
     /**
index d6ecbc4d624c94e9b0f5e5197586ad21f1af0545..531755109add18a1fa80d58e220fe4a29dc9a4fa 100644 (file)
@@ -1,6 +1,7 @@
 <form action="{{ url('/preferences/toggle-dark-mode') }}" method="post">
     {{ csrf_field() }}
     {{ method_field('patch') }}
+    <input type="hidden" name="_return" value="{{ url()->current() }}">
     @if(setting()->getForCurrentUser('dark-mode-enabled'))
         <button class="{{ $classes ?? '' }}"><span>@icon('light-mode')</span><span>{{ trans('common.light_mode') }}</span></button>
     @else
index 29dfa60a1ac593246ec8760c0ebeb1fe08b60bfc..e5336d3a255a9c53990a46e527d5ec3df2620d8f 100644 (file)
@@ -13,6 +13,7 @@
               method="post"
           @endif
     >
+        <input type="hidden" name="_return" value="{{ url()->current() }}">
 
         @if($useQuery ?? false)
             @foreach(array_filter(request()->except(['sort', 'order'])) as $key => $value)
index 7546e230f734224423d8caa3f82ffaef1ad405fb..9d64e09c391c2ac23937ccd6afcdc1486c665427 100644 (file)
@@ -1,7 +1,8 @@
 <div>
     <form action="{{ url("/preferences/change-view/" . $type) }}" method="POST" class="inline">
-        {!! csrf_field() !!}
-        {!! method_field('PATCH') !!}
+        {{ csrf_field() }}
+        {{ method_field('patch') }}
+        <input type="hidden" name="_return" value="{{ url()->current() }}">
 
         @if ($view === 'list')
             <button type="submit" name="view" value="grid" class="icon-list-item text-link">
@@ -10,7 +11,7 @@
             </button>
         @else
             <button type="submit" name="view" value="list" class="icon-list-item text-link">
-                <span>@icon('list')</span>
+                <span class="icon">@icon('list')</span>
                 <span>{{ trans('common.list_view') }}</span>
             </button>
         @endif
index 2bf74c814d72fa9d7b0578e8debc419465452f46..38935bbf5c9e019175c3ff68d2bd4b90204c31f7 100644 (file)
@@ -63,8 +63,7 @@ class WatchTest extends TestCase
         $editor = $this->users->editor();
         $book = $this->entities->book();
 
-        $this->actingAs($editor)->get($book->getUrl());
-        $resp = $this->put('/watching/update', [
+        $resp = $this->actingAs($editor)->put('/watching/update', [
             'type' => $book->getMorphClass(),
             'id' => $book->id,
             'level' => 'comments'
index 48048e2845d64a4fbe503e394d69502186239918..11016af14bb0a7c922e86668d6896466536c38c8 100644 (file)
@@ -56,6 +56,23 @@ class FavouriteTest extends TestCase
         ]);
     }
 
+    public function test_add_and_remove_redirect_to_entity_without_history()
+    {
+        $page = $this->entities->page();
+
+        $resp = $this->asEditor()->post('/favourites/add', [
+            'type' => $page->getMorphClass(),
+            'id'   => $page->id,
+        ]);
+        $resp->assertRedirect($page->getUrl());
+
+        $resp = $this->asEditor()->post('/favourites/remove', [
+            'type' => $page->getMorphClass(),
+            'id'   => $page->id,
+        ]);
+        $resp->assertRedirect($page->getUrl());
+    }
+
     public function test_favourite_flow_with_own_permissions()
     {
         $book = $this->entities->book();
index 5722a0b08c8363a064f0bd49e19dae3f82ae8611..9e5cf78dd8463c4267acdf902b6a50b2890263a4 100644 (file)
@@ -235,7 +235,7 @@ class RoleManagementTest extends TestCase
         /** @var Role $publicRole */
         $publicRole = Role::getSystemRole('public');
         $resp = $this->asAdmin()->delete('/settings/roles/delete/' . $publicRole->id);
-        $resp->assertRedirect('/');
+        $resp->assertRedirect('/settings/roles/delete/' . $publicRole->id);
 
         $this->get('/settings/roles/delete/' . $publicRole->id);
         $resp = $this->delete('/settings/roles/delete/' . $publicRole->id);
index d78ac2ea7df2ec28876406323f88b245e32c65c8..ff3cb63ca70cee217c029254e5cb3c7f39bb82ab 100644 (file)
@@ -2,8 +2,6 @@
 
 namespace Tests\User;
 
-use BookStack\Activity\Tools\UserEntityWatchOptions;
-use BookStack\Activity\WatchLevels;
 use Tests\TestCase;
 
 class UserPreferencesTest extends TestCase
@@ -40,7 +38,7 @@ class UserPreferencesTest extends TestCase
             'sort'  => 'name',
             'order' => 'asc',
         ]);
-        $updateRequest->assertStatus(500);
+        $updateRequest->assertRedirect();
 
         $this->assertNotEmpty('name', setting()->getForCurrentUser('bookshelves_sort'));
         $this->assertNotEmpty('asc', setting()->getForCurrentUser('bookshelves_sort_order'));
@@ -141,7 +139,10 @@ class UserPreferencesTest extends TestCase
             ->assertElementNotExists('.featured-image-container')
             ->assertElementExists('.content-wrap .entity-list-item');
 
-        $req = $this->patch("/preferences/change-view/bookshelf", ['view' => 'grid']);
+        $req = $this->patch("/preferences/change-view/bookshelf", [
+            'view' => 'grid',
+            '_return' => $shelf->getUrl(),
+        ]);
         $req->assertRedirect($shelf->getUrl());
 
         $resp = $this->actingAs($editor)->get($shelf->getUrl())