CodeIgniter4のバリデーション

この記事は CodeIgniter Advent Calendar 2022 - Qiita の3日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

2種類のバリデーションルール

CodeIgniter4には現在 2種類のバリデーションルールがあります。 Traditional と Strict です。

Traditional は v4.2 までのデフォルトのルールです。v4.3 からはデフォルト設定が Strict に変更される予定です。

なお、設定ファイルはプロジェクトのインストール時に app/Config/ フォルダに作成されるため、composer update するだけでは変更されません。

Traditionalルール

v4.2 までのデフォルトのルールです。

基本的に POSTデータを検証することを前提としており、文字列への型変換が起こります。文字列でないデータを検証する場合、TypeErrorが発生したり、型変換により予期せぬ結果が返ることがまれに起こり得ます。

例えば、JSON でのリクエストに true が含まれる場合、true'1' に変換されます。仮に integer ルールで検証する場合、'1' はパスしますので、true がパスすることになります。

Strictルール

v4.3 からのデフォルトのルールです。

v4.2.0 で導入された新しいバリデーションルールクラスです。app/Config/Validation.php で設定することで使えます。

型変換はしません。

新たなプロジェクトでは、こちらを使えばOKです。

既存プロジェクトでも、こちらを使うべきですが、もし、Traditionalルールの振る舞いに依存したコードがあれば、Strictルールに変更すると検証結果が変わる可能性があります。ただし、その場合でも以前の検証結果が間違っていた可能性が高いですが。

コントローラでのバリデーション

コントローラには、ヘルパーメソッドとして、validate()validateData() が用意されています。

基本的に validateData() を使いましょう。

これは、validate() だとどのデータを検証しているかが開発者にはよくわからないためです。 validate() は検証するデータを指定できません。内部で自動的に検証するデータを選択しています。

検証するデータは、$this->request->getJSON()$this->request->getRawInput() または $this->request->getVar() で取得できるデータのいずれかです。つまり、JSONデータか POST か GETデータです。

問題なのは、どのデータを検証するかは、リクエストにより変わるという点です。

攻撃者はどんなリクエストも送信できますので、開発者が想定しているリクエストでないものが送信される可能性があります。そうすると、検証されたデータとその後に実際に使用するデータが異なる可能性が生じます。

普通のHTMLフォームなので、開発者が POSTデータを想定している場合でも、JSONリクエストを送られる可能性があります。

CodeIgniter4のバリデーションクラスには、検証済みのデータを取得する方法はありませんので、開発者が検証されていない空のデータを使い処理を実行するコードを書いてしまう可能性があります。 $this->request->getPost() の値を検証済みだと考えて使うコードでも、実際には $this->request->getJSON() の値が検証されており、$this->request->getPost() のデータは空というケースが考えられます。 そうなると、予期せぬ攻撃が可能な場合があります。

モデルでのバリデーション

CodeIgniter4ではモデルでのバリデーション機能も提供されています。

しかし、モデルでのバリデーションは基本的にはお薦めしません。

お薦めしない理由

これは、バリデーションのタイミングが遅いためです。

モデルでのバリデーションは、データをデータベースへ保存する直前に検証します。 なので、これは不正なデータがデータベースに保存されることを防ぐ意味があります。

しかし、バリデーションとは本来入力直後に他の処理を実行する前に行うものです。 検証済みでないデータを処理することはリスクがあるため、まずバリデーションします。

なので、モデルでのバリデーションをするにしても、コントローラでのバリデーションをやめるべきではありません。 そうすると、バリデーションコードを二重に書くことになり、あまり嬉しくありません。

また、オブジェクト(のデータ)をデータベースに保存する場合、そもそもそのオブジェクトのデータがすべて不正でない完全な状態のオブジェクトの方が扱いやすいです。そうすると、オブジェクト生成時にバリデーションをした方がよく、モデルでのバリデーションはやはりタイミングとして遅すぎ、あまり役に立ちません。

それから、モデルでのバリデーションを使う場合は、データの保存時に必ずバリデーションに失敗していないかをチェックしなければいけません。バリデーションが失敗した場合は、モデルは false を返します。

以上の理由から、モデルでのバリデーションは基本的にはお薦めしません。

データベースにデータを保存して終わりという単純な処理の場合や、入力データはまず必ずデータベースに保存し、それを取り出して使うという場合しか、使いどころはないように思います。

バリデーションルールのスキップ

それから、モデルでのバリデーションでは、UPDATE処理の場合、提供されたフィールド以外のバリデーションルールは自動的にクリアされ、検証が実行されます。

例えば、モデルに以下のバリデーションルールを設定してある場合でも、

protected $validationRules = [
    'username'     => 'required|max_length[32]|alpha_numeric_space|min_length[3]',
    'email'        => 'required|max_length[32]|valid_email|is_unique[users.email]',
    'password'     => 'required|max_length[128]|min_length[8]',
    'pass_confirm' => 'required_with[password]||max_length[128]|matches[password]',
];

更新するデータが username のみの場合は、他のバリデーションルールは一時的にクリアされ、 以下のルールで検証が実行されます。

protected $validationRules = [
    'username' => 'required|max_length[32]|alpha_numeric_space|min_length[3]',
];

これは、そうしないと、一部のフィールドのみの更新がバリデーションエラーになってできないためです。

しかし、これは、必要なフィールドがない不正なデータでも検証にパスする可能性があるということです。 具体的には、required_with を使う場合に、必須になるフィールドがなくても更新されてしまうケースが考えられます。

そのような場合は、cleanRules(false) をコールし、バリデーションルールのスキップをやめ、更新時でもすべての(あるいは 実際に必要な)フィールドのデータを渡す必要があります。

まとめ

  • バリデーションルールは Strictルールを使いましょう。
  • コントローラでは $this->validateData() を使いましょう。
  • モデルでのバリデーションはあまりお薦めしません。

この記事は CodeIgniter Advent Calendar 2022 - Qiita の3日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

参考

Tags: codeigniter, codeigniter4, validation, security

本当は緩いCodeIgniterのvalid_urlバリデーションルール

この記事は CodeIgniter Advent Calendar 2022 - Qiita の2日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

動作確認環境

  • CodeIgniter 4.3 ブランチ (f414d79c5d1212cf8a06b26b1aaf809efd998e71)
  • CodeIgniter 3.1-stable ブランチ (45576ef6e62b5ff59da67f1697ee8c57809c7719)

URLの検証ルール

CodeIgniterのバリデーションには、URLを検証する valid_url ルールがあります。

なお、このルールは値の形式的なチェックであり、実際にそのURLが存在するかどうかは確認しません。

実際のコードで確認してみましょう。

$validation = Services::validation();

$validation->setRules([
    'url' => 'valid_url',
]);

$data = [
    'url' => 'https://codeigniter.com/',
];
dd($validation->run($data));

結果:

$validation->run(...) boolean true
⧉ Called from .../app/Controllers/Home.php:20 [dd()]

上記のように、https://codeigniter.com/ に対して true が返りました。いいですね。

不正なURL

検証するデータを以下のように変更してみます。

$data = [
    'url' => 'xhttps://codeigniter.com/',
];

結果:

$validation->run(...) boolean false
⧉ Called from .../app/Controllers/Home.php:20 [dd()]

xhttps://codeigniter.com/ はURLとして正しくないため false が返りました。

プロトコルなし

では、以下はどうでしょうか?

$data = [
    'url' => 'codeigniter.com/',
];

これも true が返ります。プロトコルがなくても許容されるようです。

URLとは思えない文字列

それでは、以下はどうでしょうか?

$data = [
    'url' => 'foo.bar',
];

これも true です。形式としてドメイン名だと言えなくもありませんが。

$data = [
    'url' => 'foo',
];

?! 何とこれも true です。

valid_url は、ブラウザのアドレスバーに入力してOKなものはOKという仕様らしく、ホスト名のみの入力も可能なため、これも妥当な値だとされています。

さて、では次の値はどうでしょう?

$data = [
    'url' => 'http:8080//abc.com',
];

これも true です。もはや訳がわかりません。これは流石にバグと言わざるを得ない気がしますが。

上記は、CodeIgniter4での結果ですが、CodeIgniter3でも全く同じです。 CodeIgniter3のルールがそのまま4にポートされています。

valid_url_strict

CodeIgniter v4.1.5 より、より厳密にURLを検証できる valid_url_strict ルールが追加されています。 このルールはPHPの FILTER_VALIDATE_URL を利用します。

$validation = Services::validation();

$validation->setRules([
    'url' => 'valid_url_strict',
]);

$data = [
    'url' => 'http:8080//abc.com',
];
dd($validation->run($data));

結果:

$validation->run(...) boolean false
⧉ Called from .../app/Controllers/Home.php:20 [dd()]

きちんと false になりました。

valid_url_strict ルールはデフォルトでは、プロトコルとして httphttps が許可されています。 パラメータとして許可するプロトコルを指定できます。プロトコルを指定する場合は許可するものをすべて記載します。

$validation->setRules([
    'url' => 'valid_url_strict[mailto,http,https]',
]);

上記は、mailtohttphttps だけを許可します。

まとめ

  • CodeIgniterの valid_url ルールは、URLっぽいものはOKという緩いルールです。
  • URLの形式妥当性を厳密にチェックしたい場合は、CodeIgniter4では valid_url_strict を使います。

この記事は CodeIgniter Advent Calendar 2022 - Qiita の2日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

参考

Tags: codeigniter, codeigniter4, security, validation

Devilboxを使いCodeIgniter4の開発環境を構築する

この記事は CodeIgniter Advent Calendar 2022 - Qiita の1日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

Devilboxを使うと、DockerでCodeIgniter4(3でもOK)の開発環境を簡単に構築できます。

Devilboxのメリット

Dockerでの開発環境の構築方法はいろいろなツールなどがありますが、Devilboxは基本的に docker-composer そのままです。 独自コマンドなどは必要ありません。

以下のようないろいろなサーバが一発で構築でき、phpMyAdminなどの管理ツールも含まれています。

  • MariaDB 10.6.7
  • PostgreSQL 14.2
  • Redis 6.2.7
  • Memcached 1.6.17
  • MongoDB 5.0.13

また、独自証明書のSSLサーバも含まれています。

それから、英語ですがドキュメントが充実しています。

動作確認環境

  • CodeIgniter 4.2.10
  • Devilbox v2.2.0 (2022-04-14)
  • Docker Desktop 4.14.1
  • macOS 10.15.7

Devilboxの動作には、Docker EngineとDocker Composeが必要です。 Dockerを起動した状態で以下の作業を行って下さい。

1. Devilboxのインストール

以下のコマンドを実行し、Devilboxをインストールします。

$ git clone https://github.com/cytopia/devilbox --depth=1

2. .envファイルの設定

.envファイルを作成します。

$ cd devilbox/
$ cp env-example .env

自分のユーザーIDとグループIDを調べます。

$ id -u
$ id -g

.envファイルに自分のIDを設定します。

--- env-example 2022-11-30 12:13:08.000000000 +0900
+++ .env    2022-11-30 12:16:03.000000000 +0900
@@ -132,8 +132,8 @@
 ###
 ### Type `id` on the terminal to find out your values
 ###
-NEW_UID=1000
-NEW_GID=1000
+NEW_UID=501
+NEW_GID=20


 ###

macOSなので、MOUNT_OPTIONSを設定します。

@@ -448,7 +448,7 @@
 ###
 ### MOUNT_OPTIONS=,z
 ###
-MOUNT_OPTIONS=
+MOUNT_OPTIONS=,cached


 ###

3. コンテナの起動

$ docker-compose up

4. Devilbox intranet

ブラウザで http://127.0.0.1/ にアクセスします。

以下のコンテナが動作していることが、わかります。

  • BIND 9.16.29
  • PHP 8.1.6
  • Nginx 1.20.2
  • MariaDB 10.6.7
  • PostgreSQL 14.2
  • Redis 6.2.7
  • Memcached 1.6.17
  • MongoDB 5.0.13

4. PHPコンテナに入る

付属のシェルスクリプトを起動して、コンテナに入ります。

$ ./shell.sh
------------------------------------------------------------------------------------------
                              _            _ _ _               
                             | |          (_) | |              
                           __| | _____   ___| | |__   _____  __
                          / _` |/ _ \ \ / / | | '_ \ / _ \ \/ /
                         | (_| |  __/\ V /| | | |_) | (_) >  < 
                          \__,_|\___| \_/ |_|_|_.__/ \___/_/\_\

                                  http://devilbox.org
                            https://devilbox.readthedocs.io



                                    Available Tools
          https://devilbox.readthedocs.io/en/latest/readings/available-tools.html

                           How to work inside this PHP container
 https://devilbox.readthedocs.io/en/latest/intermediate/work-inside-the-php-container.html



                    | Available Dirs   | Description                  |
                    |------------------|------------------------------|
                    | /shared/httpd    | Project base directory       |
                    | /shared/backups  | Backup directory             |
                    | /var/mail        | Email directory              |
                    | /var/log         | Log file directory           |

------------------------------------------------------------------------------------------

devilbox@php-8.1.6 in /shared/httpd $ 

5. CodeIgniter4プロジェクトの作成

まず、バーチャルホスト用のディレクトリ(my-ci)を作成します。

devilbox@php-8.1.6 in /shared/httpd $ mkdir my-ci

プロジェクト名(ci4app)を指定してCodeIgniter4のプロジェクトを作成します。

$ cd my-ci/
devilbox@php-8.1.6 in /shared/httpd/my-ci $ composer create-project codeigniter4/appstarter ci4app

ホストOS上では、devilbox/data/www/my-ci/ci4app/ にインストールされました。

ドキュメントルートにシンボリックリンクを張ります。

devilbox@php-8.1.6 in /shared/httpd/my-ci $ ln -s ci4app/public htdocs

5. DNSの設定

/etc/hosts に以下を追加します。

# devilbox
127.0.0.1 my-ci.loc

6. CodeIgniter4の設定

env をコピーして .env を作成し、変更します。

データベースへのアクセス情報などを設定します。

--- env 2022-10-31 09:26:15.000000000 +0900
+++ .env    2022-11-30 15:16:20.000000000 +0900
@@ -14,13 +14,13 @@
 # ENVIRONMENT
 #--------------------------------------------------------------------

-# CI_ENVIRONMENT = production
+CI_ENVIRONMENT = development

 #--------------------------------------------------------------------
 # APP
 #--------------------------------------------------------------------

-# app.baseURL = ''
+app.baseURL = 'http://my-ci.loc/'
 # If you have trouble with `.`, you could also use `_`.
 # app_baseURL = ''
 # app.forceGlobalSecureRequests = false
@@ -39,13 +39,13 @@
 # DATABASE
 #--------------------------------------------------------------------

-# database.default.hostname = localhost
-# database.default.database = ci4
-# database.default.username = root
-# database.default.password = root
-# database.default.DBDriver = MySQLi
-# database.default.DBPrefix =
-# database.default.port = 3306
+database.default.hostname = 127.0.0.1
+database.default.database = my_ci
+database.default.username = root
+database.default.password =
+database.default.DBDriver = MySQLi
+database.default.DBPrefix =
+database.default.port = 3306

 # database.tests.hostname = localhost
 # database.tests.database = ci4_test

その他の詳しい設定方法は、CodeIgniter 4.2のインストール方法【2022年最新版】を参照してください。

Welcomeページの確認

ブラウザで http://my-ci.loc/ にアクセスします。

Welcomeページが表示されました。

コンテナの起動

次回以降のコンテナの起動は、以下のコマンドを実行します。

$ docker-compose up -d

必要なコンテナだけを起動することもできます。

$ docker-compose up -d httpd php mysql

コンテナの停止

コンテナの停止は、以下のコマンドです。

$ docker-compose stop
$ docker-compose rm -f

この記事は CodeIgniter Advent Calendar 2022 - Qiita の1日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

参考

関連

Tags: codeigniter, codeigniter4, docker