[Laravel8.x] リリース前に特定のipのみログインできる様にする

リリース前にテストを行う場合に、特定のipからのアクセスのみログインできる様にしたい

$request->ip();でipを取得できる

    public function test(Request $request){
        $ip = [
            ['name'=>'a社 事業部', 'ip' => '127.0.0.1'],
            ['name'=>'a社 開発部', 'ip' => '127.0.0.2'],
            ['name'=>'b社', 'ip' => '127.0.0.3'],
            ['name'=>'myIP', 'ip' => '192.168.33.1'],
        ];
        
        $user_ip = $request->ip();
        $detect = collect($ip)->contains('ip', $request->ip());
        dd($detect);
        
        return view('admin.test');
    }

-> true

これをmiddlewareのrole_idの制御しているところで実装する。

    public function handle(Request $request, Closure $next)
    {
        
        
        $user = Auth::user();
        if(!$user->isAdmin()){

            $ip = [
                ['name'=>'a社 事業部', 'ip' => '127.0.0.1'],
                ['name'=>'a社 開発部', 'ip' => '127.0.0.2'],
                ['name'=>'b社', 'ip' => '127.0.0.3'],
                ['name'=>'myIP', 'ip' => '192.168.33.1'],
            ];
            
            $user_ip = $request->ip();
            $detect = collect($ip)->contains('ip', $request->ip());

            if(!$detect){
                Auth::logout();
                return redirect('/login')->withErrors(array('name' => 'ただ今ログインできません。'));    
            }
            return redirect()->intended('/hoge');
        }
        return $next($request);
    }

-> ただ今ログインできません。

なかなかテクニカルになってきた。

[Laravel8.x] usersテーブルの値がNULLかのMiddlewareを作る

ここではemailが登録されているかの判定をし、登録されていなければ登録画面へ飛ばす
is_nullで判定する。

$ php artisan make:middleware RegisteredEmail
app/Http/Kernel.php

    protected $routeMiddleware = [
        // 省略
        'RegisteredEmail'=>\App\Http\Middleware\RegisteredEmail::class,
    ];

app/Http/Middleware/RegisteredEmail.php

use Illuminate\Support\Facades\Auth;
    public function handle(Request $request, Closure $next)
    {
       $user = Auth::user();
        if(is_null($user->email)){
            return redirect()->intended('/auth');
        }
        return $next($request);
    }

route

Route::group(['middleware' => ['auth','RegisteredEmail']], function(){
	Route::get('/email_test', [AdminController::class, 'emailTest']);
});

なるほど、登録されてなければ登録して、って処理は多いと思う

[Laravel8.16.0] 一般権限と管理者権限で表示制御

mysql> describe users;
+—————————+—————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+—————————+—————–+——+—–+———+—————-+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| email | varchar(255) | NO | UNI | NULL | |
| email_verified_at | timestamp | YES | | NULL | |
| password | varchar(255) | NO | | NULL | |
| two_factor_secret | text | YES | | NULL | |
| two_factor_recovery_codes | text | YES | | NULL | |
| remember_token | varchar(100) | YES | | NULL | |
| current_team_id | bigint unsigned | YES | | NULL | |
| profile_photo_path | text | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| role_id | int | NO | | NULL | |
+—————————+—————–+——+—–+———+—————-+

$ php artisan make:middleware IsAdmin

app/Http/Middleware/Kernel.php

protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'IsAdmin'=>\App\Http\Middleware\IsAdmin::class,
    ];

app/Models/Users.php

public function isAdmin(){
        if($this->role_id == 1){
            return true;
        }
        return false;
    }

route

Route::get('/', function () {

	$user = Auth:: user();

	if($user->isAdmin()){
		echo "this user is admin";
	}
    // return view('welcome');
});

挙動確認

isAdmin.php

use Illuminate\Support\Facades\Auth;
public function handle(Request $request, Closure $next)
    {
        $user = Auth::user();
        if(!$user->isAdmin()){
            return redirect()->intended('/');
        }
        return $next($request);
    }

$ php artisan make:controller AdminController

AdminController.php

public function __construct(){
        $this->middleware('IsAdmin');
    }
 
    public function index(){
        return view('admin.index');
    }

route

use App\Http\Controllers\AdminController;
Route::get('/admin', [App\Http\Controllers\AdminController::class, 'index']);

もしくはrouteで制御

Route::group(['middleware' => ['auth','IsAdmin'] ], function(){
 	Route::get('/admin', [App\Http\Controllers\AdminController::class, 'index']);
 });

思い出したーーーーーー

AWS ALB常時SSL(HTTPS)のLaravel 6.x Middleware書き方

つらつらと書いていますが、仕様としては、middlewareを作成後、「### 5.Middleware(ForceHttpProtocol.php)にformatSchemeをoverrideする」に飛んで頂いて構いません。

# 背景
ACMで証明書を取得後、ALBでattachし、挙動テストすると、「完全には保護されていません」と表示される
->PRDのソースを確認すると、プロトコルがhttpで出力されている

<link rel="stylesheet" href="http://${domain}/css/main.css">

ソースコードではasset()で記載

<link rel="stylesheet" href="{{ asset('css/main.css') }}">

原因は、assetが、httpsではなく、httpを読みにいっているから。

# 要件
- 商用環境でhttp通信のリクエストの場合は、https通信にリダイレクト
- ただし、ローカル環境はhttp通信
- 商用環境リンクをフォームを含めて全てhttpsに変更

## 前準備
middlewareの追加
$ php artisan make:middleware ForceHttpProtocol

app/Http/kernel.php

protected $middleware = [
        //省略
        \App\Http\Middleware\ForceHttpProtocol::class,
    ];

### 1. secure()に書き換えるやり方
app/Http/Middleware/ForceHttpProtocol.php

public function handle($request, Closure $next)
    {
        if(!$request->secure() && env('APP_ENV') === 'production'){
            return redirect()->secure($request->getRequestUri());
        }
        return $next($request);
    }

上記では永久リダイレクトとなり、タイムアウトになる。

### 2. ‘https://’.$_SERVER[‘HTTP_HOST’].$_SERVER[‘REQUEST_URI’]にリダイレクト

public function handle($request, Closure $next)
    {
        if(!$this->is_ssl() && config('app.env') === 'production'){
            return redirect('https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
        }
        return $next($request);
    }

    public function is_ssl()
    {
        if ( isset($_SERVER['HTTPS']) === true )
        {
            return ( $_SERVER['HTTPS'] === 'on' or $_SERVER['HTTPS'] === '1' );
        }
        elseif ( isset($_SERVER['SSL']) === true )
        {
            return ( $_SERVER['SSL'] === 'on' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true )
        {
            return ( strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PORT']) === true )
        {
            return ( $_SERVER['HTTP_X_FORWARDED_PORT'] === '443' );
        }
        elseif ( isset($_SERVER['SERVER_PORT']) === true )
        {
            return ( $_SERVER['SERVER_PORT'] === '443' );
        }

        return false;
    }

これでhttpリクエストの場合も、httpsにリダイレクトされるようになる。
ただし、asset、routeなどのリンクが、httpsではなく、httpを読みにいく事象は解消されないまま

->urlのhelperを確認
Illuminate/Foundation/helper.php

function url($path = null, $parameters = [], $secure = null)

URLやViewのasset, redirectを全て書き換える?
嘘だろ?

### 3. パスをassetからurlに書き換える
AppServiceProvicerで商用ならtrue, 商用以外ならfalseのメソッドを作成

app/Providers/AppServiceProvider.php

use View;
public function boot()
    {
       $is_production = env('APP_ENV') === 'production' ? true: false;
       View::share('is_production', $is_production);
    }

続いて、viewで、assetをurlに変更して、上記の変数を第3パラメータに追加
resources/view/auth/login.blad.php

<form method="POST" action="{{ url('login', null, $is_production) }}">

一箇所修正して挙動確認。
URLがhttpsに変わっていることを確認
3~4ページぐらい修正を繰り返していると、早くも絶望的な気分になってくる

### 4. Laravelコレクティブ&Controllerでのhttpsパラメータの渡し方を確認
laravel collective

{!! Form::open(['method'=>'POST', 'action'=>['HogeController@confirm',null,$is_production] ]) !!}

controller

protected $is_production;

    public function __construct(){
        $this->is_production = env('APP_ENV') === 'production' ? true: false;
    }

// 戻るボタン処理
        if($action == '戻る'){
            return redirect()->action('HogeController@create', 302, null, $this->is_production)->withInput($inputs);
        }

うまくいかない。

縷々調査すると、ssetやrouteのプロトコルはformatSchemeで生成しているとのこと。
Illuminate/Routing/UrlGenerator.php

public function formatScheme($secure = null)
    {
        if (! is_null($secure)) {
            return $secure ? 'https://' : 'http://';
        }

        if (is_null($this->cachedScheme)) {
            $this->cachedScheme = $this->forceScheme ?: $this->request->getScheme().'://';
        }

        return $this->cachedScheme;
    }

### 5.Middleware(ForceHttpProtocol.php)にformatSchemeをoverrideする

use URL;
public function handle($request, Closure $next)
    {
        if(!$this->is_ssl() && config('app.env') === 'production'){
            return redirect('https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
        }
        if (env('APP_ENV') === 'production')
        {
            URL::forceScheme('https');
        }

        return $next($request);
    }

    public function is_ssl()
    {
        if ( isset($_SERVER['HTTPS']) === true )
        {
            return ( $_SERVER['HTTPS'] === 'on' or $_SERVER['HTTPS'] === '1' );
        }
        elseif ( isset($_SERVER['SSL']) === true )
        {
            return ( $_SERVER['SSL'] === 'on' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true )
        {
            return ( strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PORT']) === true )
        {
            return ( $_SERVER['HTTP_X_FORWARDED_PORT'] === '443' );
        }
        elseif ( isset($_SERVER['SERVER_PORT']) === true )
        {
            return ( $_SERVER['SERVER_PORT'] === '443' );
        }

        return false;
    }

このように書くことで、要件を満たすことができる
- 商用環境でhttp通信のリクエストの場合は、https通信にリダイレクト
- ただし、ローカル環境はhttp通信
- 商用環境リンクをフォームを含めて全てhttpsに変更