Laravel5.2が出てしまったので慌てて書く。こんなに次のリリースが早いとは考えていなかった。

まず、以下の通りにrolesというテーブルを作成する。

mysql> show create table roles;
| roles | CREATE TABLE `roles` (
 `role_id` tinyint(3) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(31) COLLATE utf8mb4_unicode_ci NOT NULL,
 `short_name` varchar(31) COLLATE utf8mb4_unicode_ci NOT NULL,
 `type` tinyint(1) unsigned NOT NULL COMMENT ‘should be a multiplier of 2’,
 PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |

テーブルの中身は例えばこのようにする。

mysql> select * from roles;
+———+—————+————+——+
| role_id | name          | short_name | type |
+———+—————+————+——+
|       1 | Administrator | admin      |    1 |
|       2 | Owner         | owner      |    2 |
|       3 | Manager       | manager    |    4 |
|       4 | Accountant    | accountant |    8 |
+———+—————+————+——+
4 rows in set (0.01 sec)

例では、Administrator、Owner、Manager、Accountantと4種類のユーザ権限を作成する。ここでtypeは権限の強さではなく、単に種類を分けるためのものだ。ただし、2のべき乗であること。

続いて、App\Role.phpを作成する。

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Role extends Model
{
  const TABLE = ‘roles’;
  const ROLE_ID = ‘role_id’;
  const ROLE_TYPE = ‘type’;
  const TABLE_ROLE_ID = self::TABLE . ‘.’ . self::ROLE_ID;
  const TABLE_ROLE_TYPE = self::TABLE . ‘.’ . self::ROLE_TYPE;
 
  protected $table = self::TABLE;
  protected $primaryKey = self::ROLE_ID;

  const ADMIN = 0b1; // 1
  const OWNER = 0b10; // 2
  const MANAGER = 0b100; // 4
  const ACCOUNTANT = 0b1000; // 8
 
  public function users()
  {
    return $this->hasMany(‘App\User’);
  }
}

App\Role.php上のADMIN、OWNER、MANAGER、ACCOUNTANTなどの定数はデータベースのtypeと同じ値を指定する。

次にアクセス制御を行うミドルウェア、App\Http\Middleware\RoleMiddleware.phpを作成する。

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use App\Role;
class RoleMiddleware
{
   /**
    * Handle an incoming request.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  \Closure  $next
    * @return mixed
    */
  public function handle($request, Closure $next, $mode)
  {
    if(!\Auth::check()) {
      // session expired.
      return redirect()->guest(‘auth/login’);
    }
    else {
      $admin = $mode & Role::ADMIN;
      $owner = $mode & Role::OWNER;
      $manager = $mode & Role::MANAGER;
      $accountant = $mode & Role::ACCOUNTANT;

      $type = \Auth::user()->role->type;
      if(($admin) && ($type == Role::ADMIN)) {
        return $next($request);
      }
      if(($owner) && ($type == Role::OWNER)) {
        return $next($request);
      }
      if(($manager) && ($type == Role::MANAGER)) {
        return $next($request);
      }
      if(($accountant) && ($type == Role::ACCOUNTANT)) {
        return $next($request);
      }
      return response(‘Access Denied.’, 403);
    }
  }
}

メソッドの引数として、$modeを追加する。これがどの権限がリソースにアクセスすることを許可されているかを示す。ADMIN(1)とOWNER(2)が許可されているのであれば、$modeには3(=1+2)が渡されてくる。ADMIN(1)、OWNER(2)、ACCOUNTANT(4)が許可されているのであれば、$modeには7(=1+2+4)が渡されてくる。

!\Auth::check()では、セッションタイムアウトを検出して、タイムアウトしている場合には、ユーザに最ログインを促す。

else文の中では、まず、$modeとそれぞれの権限のbit演算を行う。次にログインユーザの権限を取得する。そして、演算結果とユーザの権限を比較して、ユーザの権限が許可されたものであれば、リソースへのアクセスを認め、許可されていない場合には、HTTP STATUSの403を返すということを行う。

App\Http\Kernel.phpを編集して、作成したミドルウェアを登録しよう。

  protected $routeMiddleware = [
      ‘auth’ => \App\Http\Middleware\Authenticate::class,
      ‘auth.basic’ => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
      ‘guest’ => \App\Http\Middleware\RedirectIfAuthenticated::class,
      ‘role’ => \App\Http\Middleware\RoleMiddleware::class,
  ];

‘role’で始まる行が追加した行だ。
ユーザに権限を与えるためにusersテーブルにrole_idを追加する。

mysql> show create table users;
| users | CREATE TABLE `users` (
 `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT COMMENT ‘0 to 65535’,
 `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `password` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
 `role_id` tinyint(3) unsigned NOT NULL,
 `remember_token` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
 `created_at` timestamp NOT NULL DEFAULT ‘0000-00-00 00:00:00’,
 `updated_at` timestamp NOT NULL DEFAULT ‘0000-00-00 00:00:00’,
 `deleted_at` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `email` (`email`),
 KEY `user_id_from_users_to_roles` (`role_id`),
 CONSTRAINT `user_id_from_users_to_roles` FOREIGN KEY (`role_id`) REFERENCES `roles` (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |

Laravel5.1のmigrationでusersテーブルを作成している場合には、すでにusersテーブルが存在するので、role_idを追加すればよい。 ここにはrolesテーブルのrole_idを指定する。例では、Administratorであれば1、Ownerであれば2、Managerであれば3、Accountantであれば4となる。

最後にアクセス制御を行うリソースのコンストラクタにミドルウェアを参照するように設定する。
例えば、以下の例では、ExpensesControllerのコンストラクタにAdministratorとAccountantを指定して、他のユーザ権限であれば、このリソースへのアクセスを認めないようにしている。

>class ExpensesController extends Controller
{
  public function __construct()
  {
    $this->middleware(‘role:’ . (Role::ADMIN + Role::ACCOUNTANT)); // ADMIN(1) + ACCOUNTANT(8)
  } 

以上。
ミドルウェアについては、もっとシンプルに書ける気がしているが、今はこれ以上時間を費やすべきではないので、今後の課題。