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日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

参考

Date: 2022/12/03

Tags: codeigniter, codeigniter4, validation, security