PDO シングルトン SQLインジェクションのときに実装していなかった UserModel
及び LoginController
のメソッドを定義してゆきます。
前回作成したデータベース・クラスのメソッドは必要な場所に、追記しております。
LoginController
<?php
namespace MyApp\controller;
use \MyApp\model\UserModel;
use \MyApp\common\Db;
use \MyApp\common\InvalidErrorException;
use \MyApp\common\ExceptionCode;
/**
* UserController
*/
class LoginController
{
/**
* ログイン成功時の遷移先
*/
const TARGET_PAGE = '/dashboard.php';
/**
* セッションに保存する名前
*/
const LOGINUSER = 'loginUserModel';
/**
* メールアドレスとパスワードでログインする
* @return void
*/
static public function login()
{
// POSTされていないときは、処理を中断する
if (!filter_input_array(INPUT_POST)) {
return;
}
//フォームからの値を受け取る
$email = filter_input(INPUT_POST, 'email');
$password = filter_input(INPUT_POST, 'password');
// いずれかが空文字の場合何もしない
if ('' == $email || '' == $password) {
return;
}
//トランザクションを開始する
Db::transaction();
// email から ユーザーモデル を取得する
$objUserModel = new UserModel();
$objUserModel->getModelByEmail($email);
// ロックされたアカウントかどうかをチェックする
if ($objUserModel->isAccountLock()) {
Db::commit();
// ロックされている
throw new InvalidErrorException(ExceptionCode::INVALID_LOCK);
}
//パスワードチェック
if (!$objUserModel->checkPassword($password)) {
//ログイン失敗
$objUserModel->loginFailureIncrement();
Db::commit();
throw new InvalidErrorException(ExceptionCode::INVALID_LOGIN_FAIL);
}
// ログイン失敗をリセット
$objUserModel->loginFailureReset();
//コミット
Db::commit();
//セッション固定攻撃対策
session_regenerate_id(true);
//セッションに保存
$_SESSION[self::LOGINUSER] = $objUserModel;
//ページ遷移
header(sprintf("location: %s", self::TARGET_PAGE));
}
/**
* ログインしているかどうかチェックする
* @return bool
*/
static public function checkLogin()
{
$objUserModel = (isset($_SESSION[self::LOGINUSER])) ?
$_SESSION[self::LOGINUSER] :
null;
if (is_object($objUserModel) && 0 < $objUserModel->getUserId()) {
return;
}
header('Location: /');
}
/**
* ログイン中のユーザーモデルを取得する
* @return UserModel
*/
static public function getLoginUser()
{
return $_SESSION[self::LOGINUSER];
}
/**
* ログアウトする
* @return void
*/
static public function logout()
{
$_SESSION = [];
session_destroy();
header('Location: /');
}
}
ExceptionCode.class.php
<?php
namespace MyApp\common;
/**
* ExceptionCode.class.php
* @since 2015/07/25
*/
class ExceptionCode
{
const INVALID_ERR = 1000;
const INVALID_LOCK = 1001;
const INVALID_LOGIN_FAIL = 1002;
const APPLICATION_ERR = 2000;
const SYSTEM_ERR = 3000;
const TEMPLATE_ERR = 3001;
private static $_arrMessage = array(
self::INVALID_ERR => 'エラーが発生しました。'
, self::INVALID_LOCK => 'アカウントがロックされています。'
, self::INVALID_LOGIN_FAIL => 'ログインに失敗しました。'
, self::APPLICATION_ERR => 'アプリケーション・エラーが発生しました。'
, self::SYSTEM_ERR => 'システム・エラーが発生しました。'
, self::TEMPLATE_ERR => 'テンプレート[%s]が見つかりません。'
);
static public function getMessage($intCode)
{
if (array_key_exists($intCode, self::$_arrMessage)) {
return self::$_arrMessage[$intCode];
}
}
}
基本設計、ユーザーモデル で作成した、UserModel.class.php
に以下のメソッドを作成します。SQLが書かれる UserDao.class.php
も合わせて、記述します。
getModelByEmail($email)
isAccountLock()
checkPassword($password)
loginFailureIncrement()
loginFailureReset()
save()
setPropaties()
UserModel.class.php
<?php
namespace MyApp\model;
use MyApp\dao\UserDao;
/**
* UserModel
*/
final class UserModel
{
/**
* アカウントをロックするログイン失敗回数
*/
const LOCK_COUNT = 3;
/**
* アカウントをロックする時間(分)
*/
const LOCK_MINUTE = 15;
/**
* ユーザーID
*/
private $_userId = null;
/**
* パスワード(ハッシュ)
*/
private $_password = null;
/**
* 表示名
*/
private $_displayName = null;
/**
* メールアドレス
*/
private $_email = null;
/**
* トークン
*/
private $_token = null;
/**
* ログイン失敗回数
*/
private $_loginFailureCount = null;
/**
* ログイン失敗日時
*/
private $_loginFailureDatetime = null;
/**
* 削除フラグ
*/
private $_deleteFlag = null;
/**
* メールアドレスからユーザーを検索する
* @param string $strEmail
* @return \MyApp\model\UserModel
*/
public function getModelByEmail($strEmail)
{
$dao = UserDao::getDaoFromEmail($strEmail);
return (isset($dao[0])) ? $this->setProperty(reset($dao)) : null;
}
/**
* パスワードが一致しているかどうかを判定する
* @param type $password
* @return bool
*/
public function checkPassword($password)
{
$hash = $this->getPassword();
return password_verify($password, $hash);
}
/**
* ログイン失敗をリセットする
* 1以上のときに0にする
* @return bool
*/
public function loginFailureReset()
{
$count = $this->getLoginFailureCount();
if (0 < $count) {
$this->setLoginFailureCount(0)
->setLoginFailureDatetime(null);
return $this->save();
}
//変更の必要がない
return true;
}
/**
* ログイン失敗をインクリメントする
* 指定回数(self::LOCK_COUNT)に満たないときのみ+1
* @return bool
*/
public function loginFailureIncrement()
{
$count = $this->getLoginFailureCount();
if (self::LOCK_COUNT > $count) {
$now = (new \DateTime())->format('Y-m-d H:i:s');
$this->setLoginFailureCount(1 + $count)
->setLoginFailureDatetime($now);
return $this->save();
}
//ログイン失敗が設定以上のとき
return true;
}
/**
* アカウントがロックされているかどうかを判定する
* @return bool ロックされていたら true
*/
public function isAccountLock()
{
$count = $this->getLoginFailureCount();
$datetime = $this->getLoginFailureDatetime();
$lastFailureDatetime = new \DateTime($datetime);
$interval = new \DateInterval(
sprintf('PT%dM', self::LOCK_MINUTE)
);
$lastFailureDatetime->add($interval);
//設定時間以内で、かつ設定回数以上の失敗を記録しているとき
if ($lastFailureDatetime > new \DateTime() && self::LOCK_COUNT <= $count) {
return true;
}
return false;
}
/**
* プロパティをセットする
* @param array $arrDao
* @return \MyApp\model\UserModel
*/
private function setProperty(array $arrDao)
{
$this->setUserId($arrDao['userId'])
->setDisplayName($arrDao['displayName'])
->setEmail($arrDao['email'])
->setPassword($arrDao['password'])
->setToken($arrDao['token'])
->setLoginFailureCount($arrDao['loginFailureCount'])
->setLoginFailureDatetime($arrDao['loginFailureDatetime'])
->setDeleteFlag($arrDao['deleteFlag']);
return $this;
}
/**
* 更新する
* @return bool
*/
public function save()
{
return UserDao::save($this);
}
/**
* 新規作成する
* @return bool
*/
public function create()
{
return UserDao::insert($this);
}
/**
* ユーザーIDを設定する
* @param int $userId
* @return \MyApp\model\UserModel
*/
public function setUserId($userId)
{
$this->_userId = $userId;
return $this;
}
/**
* パスワード(ハッシュ)を設定する
* @param string $password
* @return \MyApp\model\UserModel
*/
public function setPassword($password)
{
$this->_password = $password;
return $this;
}
/**
* 表示名を設定する
* @param string $displayName
* @return \MyApp\model\UserModel
*/
public function setDisplayName($displayName)
{
$this->_displayName = $displayName;
return $this;
}
/**
* メールアドレスを設定する
* @param string $email
* @return \MyApp\model\UserModel
*/
public function setEmail($email)
{
$this->_email = $email;
return $this;
}
/**
* トークンを設定する
* @param string $token
* @return \MyApp\model\UserModel
*/
public function setToken($token)
{
$this->_token = $token;
return $this;
}
/**
* ログイン失敗回数を設定する
* @param int $loginFailureCount
* @return \MyApp\model\UserModel
*/
public function setLoginFailureCount($loginFailureCount)
{
$this->_loginFailureCount = $loginFailureCount;
return $this;
}
/**
* ログイン失敗日時を設定する
* @param string $loginFailureDatetime
* @return \MyApp\model\UserModel
*/
public function setLoginFailureDatetime($loginFailureDatetime)
{
$this->_loginFailureDatetime = $loginFailureDatetime;
return $this;
}
/**
* 削除フラグを設定する
* @param bool $deleteFlag
* @return \MyApp\model\UserModel
*/
public function setDeleteFlag($deleteFlag)
{
$this->_deleteFlag = $deleteFlag;
return $this;
}
/**
* ユーザーIDを取得する
* @return int
*/
public function getUserId()
{
return $this->_userId;
}
/**
* パスワード(ハッシュ)を取得する
* @return string
*/
public function getPassword()
{
return $this->_password;
}
/**
* 表示名を取得する
* @return string
*/
public function getDisplayName()
{
return $this->_displayName;
}
/**
* メールアドレスを取得する
* @return string
*/
public function getEmail()
{
return $this->_email;
}
/**
* トークンを取得する
* @return string
*/
public function getToken()
{
return $this->_token;
}
/**
* ログイン失敗回数を取得する
* @return int
*/
public function getLoginFailureCount()
{
return $this->_loginFailureCount;
}
/**
* ログイン失敗日時を取得する
* @return string
*/
public function getLoginFailureDatetime()
{
return $this->_loginFailureDatetime;
}
/**
* 削除フラグを取得する
* @return bool
*/
public function getDeleteFlag()
{
return $this->_deleteFlag;
}
}
UserDao.class.php
<?php
namespace MyApp\dao;
use MyApp\common\Db;
use MyApp\model\UserModel;
/**
* UserDao.class.php
*/
class UserDao
{
/**
* ユーザーIDから配列を取得する
* @param type $intUserId
* @return array
*/
public static function getDaoFromUserId($intUserId, $intDeleteFlag = null)
{
$sql = "SELECT ";
$sql .= "`userId`";
$sql .= ", `password`";
$sql .= ", `displayName`";
$sql .= ", `email`";
$sql .= ", `token`";
$sql .= ", `loginFailureCount`";
$sql .= ", `loginFailureDatetime`";
$sql .= ", `deleteFlag` ";
$sql .= "FROM `tbl_user` ";
$sql .= "WHERE `userId` = :userId ";
$arr = array();
$arr[':userId'] = $intUserId;
if (!is_null($intDeleteFlag) && in_array($intDeleteFlag, array(0, 1))) {
$sql .= "AND `deleteFlag` = :deleteFlag ";
$arr[':deleteFlag'] = $intDeleteFlag;
}
return Db::select($sql, $arr);
}
/**
* メールアドレスから配列を取得する
* @param type $strEmail
* @return array
*/
public static function getDaoFromEmail($strEmail, $intDeleteFlag = null)
{
$sql = "SELECT ";
$sql .= "`userId`";
$sql .= ", `password`";
$sql .= ", `displayName`";
$sql .= ", `email`";
$sql .= ", `token`";
$sql .= ", `loginFailureCount`";
$sql .= ", `loginFailureDatetime`";
$sql .= ", `deleteFlag` ";
$sql .= "FROM `tbl_user` ";
$sql .= "WHERE `email` = :email ";
$arr = array();
$arr[':email'] = $strEmail;
if (!is_null($intDeleteFlag) && in_array($intDeleteFlag, array(0, 1))) {
$sql .= "AND `deleteFlag` = :deleteFlag ";
$arr[':deleteFlag'] = $intDeleteFlag;
}
return Db::select($sql, $arr);
}
/**
* 更新する
* @param UserModel $objUserModel
* @return bool
*/
public static function save(UserModel $objUserModel)
{
$sql = "UPDATE ";
$sql .= "`tbl_user` ";
$sql .= "SET ";
$sql .= "`password` = :password ";
$sql .= ", `displayName` = :displayName ";
$sql .= ", `email` = :email ";
$sql .= ", `token` = :token ";
$sql .= ", `loginFailureCount` = :loginFailureCount ";
$sql .= ", `loginFailureDatetime` = :loginFailureDatetime ";
$sql .= ", `deleteFlag` = :deleteFlag ";
$sql .= "WHERE `userId` = :userId ";
$arr = array();
$arr[':userId'] = $objUserModel->getUserId();
$arr[':password'] = $objUserModel->getPassword();
$arr[':displayName'] = $objUserModel->getDisplayName();
$arr[':email'] = $objUserModel->getEmail();
$arr[':token'] = $objUserModel->getToken();
$arr[':loginFailureCount'] = $objUserModel->getLoginFailureCount();
$arr[':loginFailureDatetime'] = $objUserModel->getLoginFailureDatetime();
$arr[':deleteFlag'] = $objUserModel->getDeleteFlag();
return Db::update($sql, $arr);
}
/**
* 新規作成する
* @return int
*/
public static function insert(UserModel $objUserModel)
{
$sql = "INSERT INTO ";
$sql .= "`tbl_user` ";
$sql .= "(";
$sql .= "`userId`";
$sql .= ", `password`";
$sql .= ", `displayName`";
$sql .= ", `email`";
$sql .= ", `token`";
$sql .= ", `loginFailureCount`";
$sql .= ", `loginFailureDatetime`";
$sql .= ", `deleteFlag`";
$sql .= ") VALUES (";
$sql .= "NULL ";
$sql .= ", :password ";
$sql .= ", :displayName ";
$sql .= ", :email ";
$sql .= ", :token ";
$sql .= ", :loginFailureCount ";
$sql .= ", :loginFailureDatetime ";
$sql .= ", :deleteFlag ";
$sql .= ")";
$arr = array();
$arr[':password'] = $objUserModel->getPassword();
$arr[':displayName'] = $objUserModel->getDisplayName();
$arr[':email'] = $objUserModel->getEmail();
$arr[':token'] = $objUserModel->getToken();
$arr[':loginFailureCount'] = $objUserModel->getLoginFailureCount();
$arr[':loginFailureDatetime'] = $objUserModel->getLoginFailureDatetime();
$arr[':deleteFlag'] = $objUserModel->getDeleteFlag();
return Db::insert($sql, $arr);
}
}