Add Suspend and Ban features to Sentinel 2

by   PHP Laravel Sentinel


Tuesday, 10 May 2016



Link to Github repo

In this article, I will describe the various steps needed to suspend/ban a user with the Sentinel authorization and authentication package implemented in Laravel 5.2. These options are not provided natively by the package, although they reveal much useful in practice, in particular when you want to prevent a malicious user to keep coming back and register to your site after you deleted his/her account.

Context

We will mainly describe steps for the ban method since the suspension method is just an extension of it, where a user gets automatically unsuspended after a given period of time. The source project on which we will add some code is a simple integration of the Sentinel 2 package on the Laravel 5.2 framework with some basic functionality like user management already in place. Install this repo if you'd like to follow along.

Output

Here's a sample view of what we'll produce in this post: ban and suspension

Okay, let's get started!

Updating the Database

We're adding new information regarding our users, namely whether a user is banned or suspended. This is similar to the existing Sentinel activation method, which checks if a member has activated her account, and refuse access in case she hasn't. In fact, there is a whole verification process going on when a user wants to sign in and all we need to do is add two more checkpoints to it. So we'll extend the Throttle table by 4 columns to store a boolean and a date for both ban and suspension methods. Run php artisan make:migration update_throttle_table to generate a new migration, and modify its content to match what's below.

Please note:
  • indicates that you can safely copy the entire code block to your local file.
  • indicates that this is an excerpt of a longer code block, so copy only what's inside the -- marks and place it to its due location in your local file.

update_throttle_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class UpdateThrottleTable extends Migration
{
    public function up()
    {
        Schema::table('throttle', function (Blueprint $table) {
            $table->boolean('suspended')->default(0);
            $table->boolean('banned')->default(0);
            $table->timestamp('suspended_at')->nullable();
            $table->timestamp('banned_at')->nullable();
        });
    }

    public function down()
    {
        Schema::drop('activations');
        Schema::drop('persistences');
        Schema::drop('reminders');
        Schema::drop('roles');
        Schema::drop('role_users');
        Schema::drop('throttle');
        Schema::drop('users');
    }
}

Now run the new migration:

php artisan migrate and populate the table with php artisan db:seed

Building the model

Since we will have to retrieve data from this table, we will create a model and relate it to the user table. Run php artisan make:model Throttle

The throttle table is on a one to many relationship with the user table.

Throttle.php

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class Throttle extends Model
{
    protected $table = 'throttle';

    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

Add the following method in the user model to complete the relationship.

User.php

public function throttle()
{
    return $this->hasMany('App\Throttle');
}

That's all there is to it. Now that our data is in place, let's move on to the frontend.

Making the views

I like to set all views in place before touching on the controller, so let's add our buttons to the existing ones in user management.

admin.user.index.blade.php

<td>
{!! Form::open(array('route' => array('admin.user.destroy', $user->id), 'method' => 'delete')) !!}
<button class="btn btn-success" type="button" onClick="location.href='{{ route('admin.user.show', array($user->id)) }}'">Show</button>
<button class="btn btn-info" type="button" onClick="location.href='{{ route('admin.user.edit', array($user->id)) }}'">Edit</button>

@if ($user->id !== Sentinel::getUser()->id)
    @if (!$user->suspended)
        <button class="btn btn-default" type="button" onClick="location.href='{{ route('admin.user.suspend', array($user->id)) }}'">Suspend</button>
    @else
        <button class="btn btn-default" type="button" onClick="location.href='{{ route('admin.user.unsuspend', array($user->id)) }}'">Un-Suspend</button>
    @endif
    @if ($user->banned !== true)
        <button class="btn btn-default" type="button" onClick="location.href='{{ route('admin.user.ban', array($user->id)) }}'">Ban</button>
    @else
        <button class="btn btn-default" type="button" onClick="location.href='{{ route('admin.user.unban', array($user->id)) }}'">Un-Ban</button>
    @endif
    <button type="submit" class="btn btn-warning" onClick="return confirm('Are you sure you want to delete this user?')">Delete</button>
@else 
    <button class="btn btn-default disabled" type="button">Suspend</button>
    <button class="btn btn-default disabled" type="button">Ban</button>
    <button class="btn btn-warning disabled" type="button">Delete</button>
@endif
{!! Form::close() !!}
</td>

With to the condition, only the relevant button for the user status is displayed (button Ban if user is not banned and vice versa). admin.user.index page

Naturally, we have to define those two new Javascript generated routes.

routes.php

Route::group(['middleware' => 'admin'], function() {
---
Route::get('admin/user/{id}/suspend', array('as' => 'admin.user.suspend', 'uses' => 'UserController@suspend'));
Route::get('admin/user/{id}/unsuspend', array('as' => 'admin.user.unsuspend', 'uses' => 'UserController@unsuspend'));
Route::get('admin/user/{id}/ban', array('as' => 'admin.user.ban', 'uses' => 'UserController@ban'));
Route::get('admin/user/{id}/unban', array('as' => 'admin.user.unban', 'uses' => 'UserController@unban'));
});

Note that user id is written in clear text. It is always preferable to hash it, even though we remain in the back-end area. A nice package for this is hashids, so have a look at it. Here I will keep clear ids to save space.

Our routes make use of two methods from the UserController. Let's define those.

Defining the Controller

UserController.php

use App\Throttle;
use Suspension;
use Ban;

---

public function index()
{
    $users = User::all();

    foreach ($users as $user) {
        $suspended = Throttle::where('user_id', '=', $user->id)->where('suspended', '=', true)->count();
        $banned = Throttle::where('user_id', '=', $user->id)->where('banned', '=', true)->count();
        if ($suspended != 0) {
            $user->suspended = true;
        } else {
            $user->suspended = false;
        }
        if ($banned != 0) {
            $user->banned = true;
        } else {
            $user->banned = false;
        }
    }

    return View::make('admin.user.index')->with('users', $users);
}

---
public function suspend($id)
{
    $user = Sentinel::findById($id);
    $suspend = Suspension::suspend($user);

    Session::flash('success', 'User ' . $user->id . ' has been suspended for ' . Suspension::getSuspensionTime() . ' minutes!');
    return Redirect::route('admin.user.index');
}

public function unsuspend($id)
{
    $user = Sentinel::findById($id);
    $unsuspend = Suspension::unsuspend($user);

    Session::flash('success', 'User ' . $user->id . ' has been unsuspended!');
    return Redirect::route('admin.user.index');
}

public function ban($id)
{
    $user = Sentinel::findById($id);
    $ban = Ban::ban($user);

    Session::flash('success', 'User ' . $user->id . ' has been banned!');
    return Redirect::route('admin.user.index');
}


public function unban($id)
{
    $user = Sentinel::findById($id);
    $ban = Ban::unban($user);

    Session::flash('success', 'User ' . $user->id . ' has been unbanned!');
    return Redirect::route('admin.user.index');
}

Defining the facades

Sentinel makes use of the repository pattern. It brings an abstracted layer between data and business logic. So all our controller do is calling ban and unban methods using a facade. Let's create this facade in the Cartalyst vendor package, inside the Laravel/Facades directory. Type touch Suspension.php Ban.php to create a new file and edit it.

Suspension.php

<?php namespace Cartalyst\Sentinel\Laravel\Facades;

use Illuminate\Support\Facades\Facade;

class Suspension extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'sentinel.suspensions';
    }
}

Ban.php

<?php namespace Cartalyst\Sentinel\Laravel\Facades;

use Illuminate\Support\Facades\Facade;

class Ban extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'sentinel.bans';
    }
}

Register the new alias in the configuration:

app.php

'Suspension' => Cartalyst\Sentinel\Laravel\Facades\Suspension::class,
'Ban' => Cartalyst\Sentinel\Laravel\Facades\Ban::class,

Building the repositories

The Ban repository

All our business logic will reside in a new 'Bans' folder. So move to sentinel/src and type:

mkdir Bans

cd into this folder and create 3 new files:

touch EloquentBan.php BanRepositoryInterface.php IlluminateBanRepository.php

EloquentBan.php specify database interaction details.

EloquentBan.php

<?php namespace Cartalyst\Sentinel\Bans;

use Illuminate\Database\Eloquent\Model;

class EloquentBan extends Model
{
    protected $table = 'throttle';

    protected $fillable = [
        'user_id',
        'suspended',
        'banned',
        'suspended_at',
        'banned_at'
    ];

    public function getisBannedAttribute($isBanned)
    {
        return (bool) $isBanned;
    }

    public function setisBannedAttribute($isBanned)
    {
        $this->attributes['banned'] = (bool) $isBanned;
    }

}

IlluminateBanRepository.php define all methods for ban actions. Recall that ban and unban methods are called by our UserController.

IlluminateBanRepository.php

<?php namespace Cartalyst\Sentinel\Bans;

use Carbon\Carbon;
use Cartalyst\Sentinel\Users\UserInterface;
use Cartalyst\Support\Traits\RepositoryTrait;

class IlluminateBanRepository implements BanRepositoryInterface
{
    use RepositoryTrait;

    protected $model = 'Cartalyst\Sentinel\Bans\EloquentBan';

    public function create(UserInterface $user)
    {
        $ban = $this->createModel();

        $ban->user_id = $user->getUserId();

        $ban->save();

        return $ban;
    }

    public function exists(UserInterface $user)
    {
        $ban = $this
            ->createModel()
            ->newQuery()
            ->where('user_id', $user->getUserId());

        return $ban->first() ?: false;
    }

    public function ban(UserInterface $user)
    {
        $exists = $this->exists($user);

        if ($exists) {
            $ban = $this
                ->createModel()
                ->newQuery()
                ->where('user_id', $user->getUserId())
                ->where('banned', false)
                ->first();

            if ($ban === null) {
                return false;
            }

            $ban->fill([
                'banned'    => true,
                'banned_at' => Carbon::now(),
            ]);

            $ban->save();
            return true;

        } else {
            $ban = $this
                ->createModel();

            $ban->fill([
                'user_id' => $user->getUserId(),
                'banned'    => true,
                'banned_at' => Carbon::now(),
            ]);

            $ban->save();
            return true;
        }
    }

    public function unban(UserInterface $user)
    {
        if ($this->isBanned($user))
        {
            $ban = $this
                ->createModel()
                ->newQuery()
                ->where('user_id', $user->getUserId())
                ->where('banned', true)
                ->first();

            if ($ban === null) {
                return false;
            }

            $ban->fill([
                'banned'    => false,
                'banned_at' => null,
            ]);

            $ban->save();
            return true;
        }
    }

    public function isBanned(UserInterface $user)
    {
        $ban = $this
            ->createModel()
            ->newQuery()
            ->where('user_id', $user->getUserId())
            ->where('banned', true)
            ->first();

        return $ban ?: false;
    }

    public function remove(UserInterface $user)
    {
        $ban = $this->banned($user);

        if ($ban === false) {
            return false;
        }

        return $ban->delete();
    }
}

BanRepositoryInterface.php lists all those methods.

BanRepositoryInterface.php

<?php namespace Cartalyst\Sentinel\Bans;

use Cartalyst\Sentinel\Users\UserInterface;

interface BanRepositoryInterface
{
    public function create(UserInterface $user);

    public function exists(UserInterface $user);

    public function ban(UserInterface $user);

    public function isBanned(UserInterface $user);

    public function remove(UserInterface $user);
}

The Suspension repository

The same process goes on for the suspension repository. Inside the sentinel src directory, type mkdir Suspensions

Then move to the newly created directory and type touch EloquentSuspension.php SuspensionRepositoryInterface.php IlluminateSuspensionRepository.php

And define the following classes and interface:

EloquentSuspension.php

<?php namespace Cartalyst\Sentinel\Suspensions;

use Illuminate\Database\Eloquent\Model;

class EloquentSuspension extends Model
{
    protected $table = 'throttle';

    protected static $suspensionTime = 15;

    protected $fillable = [
        'user_id',
        'suspended',
        'banned',
        'suspended_at',
        'banned_at'
    ];

    public function getSuspendedAttribute($suspended)
    {
        return (bool) $suspended;
    }

    public function setSuspendedAttribute($suspended)
    {
        $this->attributes['suspended'] = (bool) $suspended;
    }
}

The suspension repo differentiates from the ban repo in the sense that when we suspend a user, the suspension time is registered and each time a user goes through the login process, we check not only if the user is suspended, but also, if that's the case, whether he should remain suspended and we remove suspended status if suspension has expired. For this we'll create a couple of methods that will inform the user for how long he is suspended.

IlluminateSuspensionRepository

<?php namespace Cartalyst\Sentinel\Suspensions;

use Carbon\Carbon;
use Cartalyst\Sentinel\Users\UserInterface;
use Cartalyst\Support\Traits\RepositoryTrait;

use App\User;
use DateTime;
use Session;

class IlluminateSuspensionRepository implements SuspensionRepositoryInterface
{
    use RepositoryTrait;

    protected $model = 'Cartalyst\Sentinel\Suspensions\EloquentSuspension';

    protected $expires = 259200;

    protected static $suspensionTime = 15;

    public function __construct($model = null, $expires = null)
    {
        if (isset($model)) {
            $this->model = $model;
        }

        if (isset($expires)) {
            $this->expires = $expires;
        }
    }

    public function all()
    {
        $users = User::all();

        foreach ($users as $user) {
            if ($user->isActivated()) {
                $user->status = "Active";
            } else {
                $user->status = "Not Active";
            }

            $throttle = $this->throttleProvider->findByUserId($user->id);

            if ($throttle->isSuspended()) {
                // User is Suspended
                $user->status = "Suspended";
            }

            if ($throttle->isBanned()) {
                // User is Banned
                $user->status = "Banned";
            }
        }

        return $users;
    }

    public function create(UserInterface $user)
    {
        $suspension = $this->createModel();

        $suspension->user_id = $user->getUserId();

        $suspension->save();

        return $suspension;
    }

    public function exists(UserInterface $user)
    {
        $suspension = $this
            ->createModel()
            ->newQuery()
            ->where('user_id', $user->getUserId());

        return $suspension->first() ?: false;
    }

    public function suspend(UserInterface $user)
    {
        $exists = $this->exists($user);

        if ($exists) {
            $suspend = $this
                ->createModel()
                ->newQuery()
                ->where('user_id', $user->getUserId())
                ->where('suspended', false)
                ->first();

            if ($suspend === null) {
                return false;
            }

            $suspend->fill([
                'suspended'    => true,
                'suspended_at' => Carbon::now(),
            ]);

            $suspend->save();
            return true;

        } else {
            $suspend = $this
                ->createModel();

            $suspend->fill([
                'user_id' => $user->getUserId(),
                'suspended'    => true,
                'suspended_at' => Carbon::now(),
            ]);

            $suspend->save();

            return true;
        }
    }

    public function unsuspend(UserInterface $user)
    {
        $suspend = $this
            ->createModel()
            ->newQuery()
            ->where('user_id', $user->getUserId())
            ->where('suspended', true)
            ->whereNotNull('suspended_at')
            ->first();

        if ($suspend === null) {
            return false;
        }

        $suspend->fill([
            'suspended'    => false,
            'suspended_at' => null,
        ]);

        $suspend->save();

        return true;
    }

    public function suspended(UserInterface $user)
    {
        $suspended = $this
            ->createModel()
            ->newQuery()
            ->where('user_id', $user->getUserId())
            ->where('suspended', true)
            ->first();

        if ($suspended !== null)
        {
            $this->removeSuspensionIfAllowed($user);
            return (bool) $this->suspended($user);
        } else {
            return false;
        }
    }

    public function isSuspended(UserInterface $user)
    {
        $isSuspended = $this
            ->createModel()
            ->newQuery()
            ->where('user_id', $user->getUserId())
            ->where('suspended', true)
            ->first();

        if ($isSuspended !== null) {

            $remove = $this->removeSuspensionIfAllowed($user);

            return $remove;
        } else {
            return false;
        }
    }

    public function remove(UserInterface $user)
    {
        $suspension = $this->suspended($user);

        if ($suspension === false) {
            return false;
        }

        return $suspension->delete();
    }

    public function removeExpired()
    {
        $expires = $this->expires();

        return $this
            ->createModel()
            ->newQuery()
            ->where('suspended', false)
            ->where('created_at', '<', $expires)
            ->delete();
    }

    protected function expires()
    {
        return Carbon::now()->subSeconds($this->expires);
    }

    protected function generateSuspensionCode()
    {
        return str_random(32);
    }

    public function getDates()
    {
        return array_merge(parent::getDates(), array('suspended_at', 'banned_at'));
    }

    public function toArray()
    {
        $result = parent::toArray();

        if (isset($result['suspended']))
        {
            $result['suspended'] = $this->getSuspendedAttribute($result['suspended']);
        }
        if (isset($result['banned']))
        {
            $result['banned'] = $this->getBannedAttribute($result['banned']);
        }
        if (isset($result['suspended_at']) and $result['suspended_at'] instanceof DateTime)
        {
            $result['suspended_at'] = $result['suspended_at']->format('Y-m-d H:i:s');
        }

        return $result;
    }

    public static function setSuspensionTime($minutes)
    {
        static::$suspensionTime = (int) $minutes;
    }

    public static function getSuspensionTime()
    {
        return static::$suspensionTime;
    }

    public function getSuspendedTime($user)
    {
            $suspendedAt = $this
                ->createModel()
                ->newQuery()
                ->where('user_id', $user->getUserId())
                ->where('suspended', true)
                ->whereNotNull('suspended_at')
                ->first();

            if ($suspendedAt === null) {
                return false;
            }

            return $suspendedAt->suspended_at;
    }

    public function removeSuspensionIfAllowed($user)
    {
        $flag = false;

        $suspensionTime = $this->getSuspensionTime();
        $suspendedAt = new DateTime($this->getSuspendedTime($user));
        $unsuspendAt = $suspendedAt->modify("+{$suspensionTime} minutes");
        $now = new DateTime();
        if ($unsuspendAt <= $now)
        {
            $this->unsuspend($user);

            unset($suspended);
            unset($unsuspendAt);
            unset($now);

            return false;
        } else {
            unset($suspended);
            unset($unsuspendAt);
            unset($now);

            return true;
        }
    }

    public function getRemainingSuspensionTime($user)
    {
        $suspensionTime = $this->getSuspensionTime();
        $suspendedAt = new DateTime($this->getSuspendedTime($user));
        $unsuspendAt = $suspendedAt->modify("+{$suspensionTime} minutes");
        $now = new DateTime();
        $timeLeft = $now->diff($unsuspendAt);

        $minutesLeft = ($timeLeft->s != 0 ?
                    ($timeLeft->days * 24 * 60) + ($timeLeft->h * 60) + ($timeLeft->i) + 1 :
                    ($timeLeft->days * 24 * 60) + ($timeLeft->h * 60) + ($timeLeft->i));
        return $minutesLeft;
    }
}

SuspensionRepositoryInterface

<?php namespace Cartalyst\Sentinel\Suspensions;

use Cartalyst\Sentinel\Users\UserInterface;

interface SuspensionRepositoryInterface
{
    public function create(UserInterface $user);

    public function exists(UserInterface $user);

    public function suspend(UserInterface $user);

    public function isSuspended(UserInterface $user);

    public function remove(UserInterface $user);

    public function removeExpired();
}

Building the checkpoints

The authentication process has to successfully pass through a number of checkpoints in order for the user to be granted access. There is already an existing activation checkpoint and we will add our ban checkpoint based on it.

The Ban checkpoint

Inside the Checkpoints folder type touch BanCheckpoint.php BannedException.php And edit the newly created files.

BanCheckpoint.php

<?php namespace Cartalyst\Sentinel\Checkpoints;

use Cartalyst\Sentinel\Bans\BanRepositoryInterface;
use Cartalyst\Sentinel\Users\UserInterface;

class BanCheckpoint implements CheckpointInterface
{
    use AuthenticatedCheckpoint;

    protected $bans;

    public function __construct(BanRepositoryInterface $bans)
    {
        $this->bans = $bans;
    }

    public function login(UserInterface $user)
    {
        return $this->checkBan($user);
    }

    public function check(UserInterface $user)
    {
        return $this->checkBan($user);
    }

    protected function checkBan(UserInterface $user)
    {
        $isBanned = $this->bans->isBanned($user);

        if ($isBanned) {
            $exception = new BannedException('You are banned.');

            $exception->setUser($user);

            throw $exception;
        }
    }
}

Let's build the exception to deal with failed authentication attempts due to a banned status.

BannedException.php

<?php namespace Cartalyst\Sentinel\Checkpoints;

use Cartalyst\Sentinel\Users\UserInterface;
use RuntimeException;

class BannedException extends RuntimeException
{
        protected $user;

        public function getUser()
        {
            return $this->user;
        }

        public function setUser(UserInterface $user)
        {
            $this->user = $user;
        }
}

The Suspension checkpoint

Inside the same Checkpoints folder, type touch SuspensionCheckpoint.php SuspendedException.php and edit the two new files.

SuspensionCheckpoint.php

<?php namespace Cartalyst\Sentinel\Checkpoints;

use Cartalyst\Sentinel\Suspensions\SuspensionRepositoryInterface;
use Cartalyst\Sentinel\Users\UserInterface;

class SuspensionCheckpoint implements CheckpointInterface
{
    use AuthenticatedCheckpoint;

    protected $suspensions;

    public function __construct(SuspensionRepositoryInterface $suspensions)
    {
        $this->suspensions = $suspensions;
    }

    public function login(UserInterface $user)
    {
        return $this->checkSuspension($user);
    }

    public function check(UserInterface $user)
    {
        return $this->checkSuspension($user);
    }

    protected function checkSuspension(UserInterface $user)
    {
        $suspended = $this->suspensions->isSuspended($user);

        if ($suspended) {
            $exception = new SuspendedException('You have been suspended for ' . $this->suspensions->getSuspensionTime() .' minutes. There remains ' . $this->suspensions->getRemainingSuspensionTime($user) . ' minute(s).');
            $exception->setUser($user);

            throw $exception;
        }
    }
}

Register the exception for suspended users.

SuspendedException.php

<?php namespace Cartalyst\Sentinel\Checkpoints;

use Cartalyst\Sentinel\Users\UserInterface;
use RuntimeException;

class SuspendedException extends RuntimeException
{
    protected $user;

    public function getUser()
    {
        return $this->user;
    }

    public function setUser(UserInterface $user)
    {
        $this->user = $user;
    }
}

Handle Errors

Let's handle the exceptions sent from the newly added checkpoints. Inside laravel app/Exceptions add these new redirections:

Handler.php

use Cartalyst\Sentinel\Checkpoints\SuspendedException;
use Cartalyst\Sentinel\Checkpoints\BannedException;
use Redirect;
---

public function render($request, Exception $e)
{
    if ($e instanceof SuspendedException) {
        return Redirect::route('login')->with('error', $e->getMessage());
    }

    if ($e instanceof BannedException) {
        return Redirect::route('login')->with('error', $e->getMessage());
    }

    return parent::render($request, $e);   
}

Do not forget to register new checkpoints in the Sentinel configuration file. It won't work otherwise!

config.php

'checkpoints' => [
    'throttle',
    'activation',
    'suspension',
    'ban',
],

Update Sentinel and Sentinel provider class

All there is left to do is to register all new defined variables and methods both in the Sentinel class (Sentinel.php) and in the Sentinel Service Provider Class (SentinelServiceProvider.php). As you'll see, it is kind of a long and repetitive process, but it's a necessary one.

The Sentinel class

Let's start with the Sentinel class. We'll add code for ban and suspension in the namespace, variable, constructor and methods of Sentinel, and place it always just below the related activation code, so that we keep a clean sheet.

Sentinel.php

use Cartalyst\Sentinel\Suspensions\SuspensionRepositoryInterface;
use Cartalyst\Sentinel\Bans\BanRepositoryInterface;
---
protected $suspensions;

protected $bans;
---
public function __construct(
    PersistenceRepositoryInterface $persistences,
    UserRepositoryInterface $users,
    RoleRepositoryInterface $roles,
    ActivationRepositoryInterface $activations,
    SuspensionRepositoryInterface $suspensions,
    BanRepositoryInterface $bans,
    Dispatcher $dispatcher
) {
    $this->persistences = $persistences;

    $this->users = $users;

    $this->roles = $roles;

    $this->activations = $activations;

    $this->suspensions = $suspensions;

    $this->bans = $bans;

    $this->dispatcher = $dispatcher;
}
---
public function getSuspensionRepository()
{
    return $this->suspensions;
}

public function setSuspensionRepository(SuspensionRepositoryInterface $suspensions)
{
    $this->suspensions = $suspensions;
}

public function getBanRepository()
{
    return $this->bans;
}

public function setBanRepository(BanRepositoryInterface $bans)
{
    $this->bans = $bans;
}

Register new services

Last step is to register all new services. Same procedure here: just add the lines below the related Activation namespaces and methods.

SentinelServiceProvider.php

use Cartalyst\Sentinel\Suspensions\IlluminateSuspensionRepository;
use Cartalyst\Sentinel\Bans\IlluminateBanRepository;
use Cartalyst\Sentinel\Checkpoints\SuspensionCheckpoint;
use Cartalyst\Sentinel\Checkpoints\BanCheckpoint;
---
protected function registerCheckpoints()
{
    ...
    $this->registerSuspensionCheckpoint();
    $this->registerBanCheckpoint();
    ...
}
---
protected function registerSuspensionCheckpoint()
{
    $this->registerSuspensions();

    $this->app->singleton('sentinel.checkpoint.suspension', function ($app) {
        return new SuspensionCheckpoint($app['sentinel.suspensions']);
    });
}

protected function registerSuspensions()
{
    $this->app->singleton('sentinel.suspensions', function ($app) {
        $config = $app['config']->get('cartalyst.sentinel');

        $model   = array_get($config, 'suspensions.model');
        $expires = array_get($config, 'suspensions.expires');

        return new IlluminateSuspensionRepository($model, $expires);
    });
}

protected function registerBanCheckpoint()
{
    $this->registerBans();

    $this->app->singleton('sentinel.checkpoint.ban', function ($app) {
        return new BanCheckpoint($app['sentinel.bans']);
    });
}

protected function registerBans()
{
    $this->app->singleton('sentinel.bans', function ($app) {
        $config = $app['config']->get('cartalyst.sentinel');

        $model   = array_get($config, 'bans.model');
        $expires = array_get($config, 'bans.expires');

        return new IlluminateBanRepository($model, $expires);
    });
}
---
protected function registerSentinel()
{
$this->app->singleton('sentinel', function ($app) {
    $sentinel = new Sentinel(
        $app['sentinel.persistence'],
        $app['sentinel.users'],
        $app['sentinel.roles'],
        $app['sentinel.activations'],
        $app['sentinel.suspensions'],
        $app['sentinel.bans'],
        $app['events']
    );

    ---
    $sentinel->setSuspensionRepository($app['sentinel.suspensions']);
    $sentinel->setBanRepository($app['sentinel.bans']);
    ---
}
---
public function provides()
{
  return [
    ---
    'sentinel.suspensions',
    'sentinel.checkpoint.suspension',
    'sentinel.bans',
    'sentinel.checkpoint.ban',
    ---
  ];
}
---

End Result

We are done. Suspended and banned users will not make it to our application. A flash info message appears when access is denied.

Flash message to banned users: banned user

Flash message to suspended users (with indication on the remaining suspension time): suspended user

Blog Search

About

Hi there! My name is Jean-Marc Kleger and I'm a web developer. Welcome to my blog where a share some tips on how to deal with a selection of challenges encountered in my day-to-day coding workflow. Most articles are related to their own Github repo so that you can quickly experiment the code. Don't hesitate also to make use of the comment section at the end of each post to share your knowledge on the topic. Enjoy the visit!