در حال بارگزاری ...

احراز هویت کاربران برای حذف یک پاسخ در لاراول

توسط مریم مهربان
آخرین به روز رسانی شنبه 16 آذر 1398

در این آموزش از لیداوب، سعی داریم نحوه پیاده سازی این سیاست را در لاراول دنبال کنیم.در ادامه این مقاله آموزشی با ما همراه باشید.

عموما قابلیت‌های وب سایت‌ها به دو دسته ویژه و عمومی تقسیم می‌شوند. امکانات ویژه مخصوص کاربران احراز هویت شده است و گاهی محدود به کاربران خاص می‌شود. برای مثال، ممکن است سیاست وب سایت شما اینگونه باشد که تنها فردی می‌تواند پاسخ به یک مطلب را حذف کند که آن پاسخ را ایجاد کرده باشد.

احراز هویت کاربران برای حذف یک پاسخ در لاراول

فرض کنید که در وب سایت شما، امکان حذف پاسخ داده شده به سوالات وجود داشته باشد و این قابلیت در تابع زیر، پیاده سازی شده است. هیچ کاربر مهمانی نباید بتواند این پاسخ را حذف کند، بلکه باید به صفحه ورود هدایت شود.

public function test_unauthorized_users_can_not_delete_replies()
{
    $this->withoutExceptionHandling();

    $reply = create('App\Reply');

    $this->delete('/replies/' . $reply->id)
        ->assertRedirect('/login');
}

اجرای کد بالا، منجر به خطای زیر می‌شود:

vagrant@homestead:~/Code/forumio$ phpunit --filter test_unauthorized_users_can_not_delete_replies
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 849 ms, Memory: 8.00MB

There was 1 error:

1) Tests\Feature\ParticipateInForumTest::test_unauthorized_users_can_not_delete_replies
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: DELETE http://localhost/replies/1

اضافه کردن مسیر

برای پیاده سازی سیاست موردنظرمان، می‌توانیم برای /replies/{reply} ، یک درخواست delete ثبت کنیم که تابع destroy() را در کنترلر RepliesController فراخوانی می‌کند.

<?php

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

Route::get('/threads', 'ThreadsController@index');
Route::get('/threads/create', 'ThreadsController@create');
Route::get('/threads/{channel}/{thread}', 'ThreadsController@show');
Route::delete('/threads/{channel}/{thread}', 'ThreadsController@destroy');
Route::post('/threads', 'ThreadsController@store');
Route::get('/threads/{channel}', 'ThreadsController@index');
Route::post('/threads/{channel}/{thread}/replies', 'RepliesController@store');
Route::delete('/replies/{reply}', 'RepliesController@destroy');
Route::post('/replies/{reply}/favorites', 'FavoritesController@store');
Route::get('/profiles/{user}', 'ProfilesController@show')->name('profile');

اضافه کردن تابع destroy

فایل RepliesController.php را باز می‌کنیم و کد تابع destroy() را به آن اضافه می‌کنیم:

<?php

namespace App\Http\Controllers;

use App\Reply;
use App\Thread;
use Illuminate\Http\Request;

class RepliesController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function store($channelId, Thread $thread)
    {
        $this->validate(request(), [
            'body' => 'required'
        ]);

        $thread->addReply([
            'body' => request('body'),
            'user_id' => auth()->id()
        ]);

        return back();
    }

    public function destroy(Reply $reply)
    {
        $reply->delete();

        return back();
    }
}

و تابع test_unathorized_users_can_not_delete_replies() را به صورت زیر تغییر می‌دهیم:

public function test_unauthorized_users_can_not_delete_replies()
{
    $this->withoutExceptionHandling();

    $reply = create('App\Reply');

    $this->signIn()
        ->delete('/replies/' . $reply->id)
        ->assertStatus(403);
}

برنامه بالا برای کاربران احراز هویت نشده، درست کار می‌کند. اما اگر پس از اعمال تغییرات بالا، یک کاربر احراز هویت شده اقدام به حذف پاسخ یک کاربر دیگر کند، به جای خطای 403 forbidden ، با خطای 302 (هدایت به صفحه ورود) مواجه می‌شود و این اشتباه است.

vagrant@homestead:~/Code/forumio$ phpunit --filter test_unauthorized_users_can_not_delete_replies
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 920 ms, Memory: 10.00MB

There was 1 failure:

1) Tests\Feature\ParticipateInForumTest::test_unauthorized_users_can_not_delete_replies
Expected status code 403 but received 302.
Failed asserting that false is true.

/home/vagrant/Code/forumio/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:78
/home/vagrant/Code/forumio/tests/Feature/ParticipateInForumTest.php:53

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

برای رفع این مشکل، می‌توانیم کد مربوط به کاربران احراز هویت شده را به صورت زیر تغییر دهیم:

public function test_authorized_users_can_delete_replies()
{
    $this->signIn();

    $reply = create('App\Reply', ['user_id' => auth()->id()]);

    $this->delete('/replies/' . $reply->id)->assertStatus(302);

    $this->assertDatabaseMissing('replies', ['id' => $reply->id]);
}

تنظیم یک سیاست جدید

برای پیاده سازی سیاستی که مشخص می‌کند آیا یک کاربر اجازه حذف پاسخی را دارد یا خیر، می‌توانیم از policy object در لاراول استفاده کنیم. به این منظور، ابتدا سیاست پاسخ (ReplyPolicy) را با دستور زیر ایجاد می‌کنیم:

php artisan make:policy ReplyPolicy

سپس با استفاده از تابع update()، اینگونه تعریف می‌کنیم که فقط شخصی اجازه پاک کردن پاسخ را دارد که شناسه آن با شناسه فرد پاسخ دهنده برابر باشد.

<?php

namespace App\Policies;

use App\Reply;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class ReplyPolicy
{
    use HandlesAuthorization;

    public function update(User $user, Reply $reply)
    {
        return $reply->user_id = $user->id;
    }
}

سپس این سیاست را در AuthServiceProvider ثبت می‌کنیم.

<?php

namespace App\Providers;

use function foo\func;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        //'App\Model' => 'App\Policies\ModelPolicy',
        'App\Thread' => 'App\Policies\ThreadPolicy',
        'App\Reply' => 'App\Policies\ReplyPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Gate::before(function ($user) {
            if ($user->name === 'Nikola Tesla') {
                return true;
            }
        });
    }
}

لازم است کد تابع destroy() را هم به صورت زیر تغییر دهیم. با این تغییرات، پیش از حذف واقعی پاسخ، باید از سد سیاست تعریف شده در تابع update() را نیز با موفقیت عبور کنیم.

public function destroy(Reply $reply)
{
    $this->authorize('update', $reply);

    $reply->delete();

    return back();
}

به این ترتیب، برنامه بررسی مجوز حذف پاسخ به صورت زیر خواهد بود:

public function test_unauthorized_users_can_not_delete_replies()
{
    $reply = create('App\Reply');

    $this->delete('/replies/' . $reply->id)
        ->assertRedirect('/login');

    $this->signIn()
        ->delete('/replies/' . $reply->id)
        ->assertStatus(403);
}

public function test_authorized_users_can_delete_replies()
{
    $this->signIn();

    $reply = create('App\Reply', ['user_id' => auth()->id()]);

    $this->delete('/replies/' . $reply->id)->assertStatus(302);

    $this->assertDatabaseMissing('replies', ['id' => $reply->id]);
}

 پس از پیاده سازی سیاست و تست آن، اکنون می‌توانید یک دکمه delete را هم به رابط گرافیکی خود اضافه کنید. برای اینکه سیاست موردنظر ما نیز در رابط گرافیکی پدیدار شود، می‌توانیم از @can directive استفاده کنیم که تنها دکمه را برای کاربران مجاز به حذف، نمایش دهد.

<div id="reply-{{ $reply->id }}" class="panel panel-default">

    <div class="panel-body">
        <div class="level">
            <h5 class="flex">
                <a href="{{ route('profile', $reply->owner) }}">
                    {{$reply->owner->name}}
                </a> said {{ $reply->created_at->diffForHumans() }}
            </h5>

            <div>
                <form method="POST" action="/replies/{{$reply->id}}/favorites">
                    {{csrf_field()}}
                    <button type="submit" class="btn btn-primary {{ $reply->isFavorited() ? 'disabled' : '' }}">
                        {{ $reply->favorites_count }} {{ str_plural('Favorite', $reply->favorites_count) }}
                    </button>
                </form>
            </div>
        </div>
    </div>

    <div class="panel-body">
        {{ $reply->body }}
    </div>

    @can('update', $reply)
        <div class="panel-footer">
            <form method="POST" action="/replies/{{$reply->id}}">
                {{ csrf_field() }}
                {{ method_field('DELETE') }}
                <button class="btn btn-danger btn-xs">Delete Reply</button>
            </form>
        </div>
    @endcan
</div>

 کاربران مجاز، تصویر زیر را مشاهده می‌کنند:

دستور نمایش دکمه delete

و اگر کاربری روی دکمه Delete مربوط به پاسخ خود کلیک کند، این پاسخ حذف می‌شود. علاوه بر این، چون دیگر پاسخی از این کاربر وجود ندارد، این دکمه نیز برای وی نمایش داده نمی‌شود.

 

حذف دکمه delete

مطالعه مقالات بیشتر در لیداوب:

امیدوارم این مطلب برای شما مفید باشد و بتوانید از آن در وب سایت خود استفاده کنید. با لیداوب همراه باشید.

 

دیدگاه ها

دیدگاه ها : 0


متاسفانه فقط اعضای سایت قادر به ثبت دیدگاه هستند

رایگان

اشتراک گذاری در
ثبت امتیاز
5 (1 رای)

   لطفا صبر کنید ...