<?php
//========================================================================================================================
/**
 * プリコントローラ
 *
 * @package     LabbitBox
 * @subpackage  LB_Auth
 * @author   "minotaur"<minotaur@labbitbox.org>
 * @since    2011/08/12
 */
//========================================================================================================================
require_once(BASE_LIB.'/Precontroller.php');
require_once(BASE_LIB.'/LbSession.php');
class LB_Auth_Precontroller extends Precontroller{

    /**
     * オーナーコード
     *
     * システムオーナー固有のID(外部コンテスト対策)
     *
     * @var string
     */
    private $_sysId;

    /**
     * コンストラクタ
     */
    public function __construct($application){
        parent::__construct($application);
    }

    /**
     * セッション有効時処理
     */
    public function sessionActive($lbSession){
        $this->_check($lbSession);
    }



    /**
     * セッション無効時処理
     *
     * 主にCLI対策など
     */
    public function sessionInactive($lbSession){
        $this->_check($lbSession);
    }



    /**
     * チェック
     */
    private function _check($lbSession){
        // 全アプリ使用のためのDBオブジェクトを準備
        require_once(BASE_LIB.'/DB/DB.php');
        $db = new DB($this->configById('DB'));
        $db->loadSql(dirname(__FILE__).'/LB_Auth_Precontroller.sql.xml');
        $this->shared()->add('DB', $db);
        $this->_sysId = $this->config('app.LB_Auth.sysId');
        $this->_auth();
    }



    /**
     * 認証
     *
     * ここでは単にセッションの値だけを見る
     */
    private function _auth(){
        $db = $this->shared('DB');
        //１．POST認証を検出すれば優先的にそれを扱う。
        //    検出されたら２へ
        //    検出されなかったら３へ
        if($_POST['lgif_email']){
            //２．POST認証を検証します。
            //    成功したら４へ
            //    失敗したら５へ
            $prm = NULL;
            $prm[':ACC_ID'] = $_POST['lgif_email'];
            $acc = $db->dataset('_precontroller_getAccountInfoByEmail', $prm)->getAll();//非ロック時
            $acc = $acc[0];
            $salt = $acc['SALT'];
            if(hash('sha256', trim($_POST['lgif_password']).$salt)===$acc['PWD']){
                //４．POST認証成功
                if($_POST['lgif_keepalive_check']){
                    $this->_renewCookie('NEW', $acc['ACC_NO']); // 長期ワンタイム認証導入処理
                }else{
                    $this->_renewCookie('DROP', NULL);      // 長期ワンタイム認証解除処理
                }
                return $this->_setAuth('POST', $acc['ACC_NO'], $acc);
            }else{
                //５．POST認証失敗
                $this->_renewCookie('DROP', NULL);      // 長期ワンタイム認証解除処理
                return $this->_setAuth('POST', NULL, NULL);
            }
        }else{
            //３．GET認証方法を試みます。この時点で認証COOKIEは破棄します。
            //　　　短期ワンタイム認証が検出されたら６へ
            //      長期ワンタイム認証が検出されたら７へ (故意以外での競合はありませんが短期優先です)
            //　　　GET認証が検出されなかったら８へ
            if($_GET['cd'] && $_GET['u']){
                //６．短期ワンタイム認証を検証します。
                //　　　成功したら９へ
                //　　　失敗したら１０へ
                $prm = NULL;
                $prm[':OTP']          = $_GET['cd'];
                $prm[':ACC_NO']       = $_GET['u'];
                $prm[':VALID_TIME']   = strftime('%Y/%m/%d %H:%M:%S');
                $prm[':PRM_ID']       = 'P';
                $prm[':ACC_LOCK'] = 'T';
                $acc = $db->dataset('_precontroller_checkOTP', $prm)->getAll();
                $acc = $acc[0];
                $this->_renewCookie('DROP', NULL);
                if(($acc['ACC_NO']) &&($acc['ENTRY_TIME'] < '1900/01/01')){//新規の場合のみ許可&ロック解除
                    //９．短期ワンタイム認証成功

                    // アカウントロックの解除
                    $prm = NULL;
                    $prm[':ACC_NO'] = $acc['ACC_NO'];
                    $db->query('_precontroller_unlockAccount', $prm );

                    // 短期OTPの削除
                    $prm = NULL;
                    $prm[':PRM_ID'] = 'P';
                    $prm[':OTP']    = $_GET['cd'];
                    $prm[':ACC_NO'] = $acc['ACC_NO'];
                    $db->query('_precontroller_dropOTP', $prm );

                    return $this->_setAuth('CD', $acc['ACC_NO'], $acc);
                }else{
                    $prm = NULL;
                    $prm[':OTP']          = $_GET['cd'];
                    $prm[':ACC_NO']        = $_GET['u'];
                    $prm[':VALID_TIME']   = strftime('%Y/%m/%d %H:%M:%S');
                    $acc = $db->dataset('_precontroller_checkOTP_repwd', $prm)->getAll();
                    $acc = $acc[0];
                    if($acc['ACC_NO']){
                        // 短期OTPの削除
                        $prm = NULL;
                        $prm[':PRM_ID'] = 'P';
                        $prm[':OTP']    = $_GET['cd'];
                        $prm[':ACC_NO'] = $acc['ACC_NO'];
                        $db->query('_precontroller_dropOTP', $prm );
                        return $this->_setAuth('CD', $acc['ACC_NO'], $acc);
                    }else{
                        //１０．短期ワンタイム認証失敗
                        return $this->_setAuth('ACC_NO', NULL, NULL);
                    }
                }
            }else if($_GET['lcd']){
                //７．長期ワンタイム認証を検証します。
                //　　　成功したら１１へ
                //　　　失敗したら１２へ
                $prm = NULL;
                $prm[':OTP']          = $_GET['lcd'];
                $prm[':ACC_NO']       = $_GET['u'];
                $prm[':VALID_TIME']   = strftime('%Y/%m/%d %H:%M:%S');
                $prm[':PRM_ID']       = 'C';
                $prm[':ACC_LOCK'] = 'F';
                $acc = $db->dataset('_precontroller_checkOTP', $prm)->getAll();
                $acc = $acc[0];
                if($acc['ACC_NO']){
                    //１１．長期ワンタイム認証成功
                    $this->_renewCookie('UPDATE', $acc['ACC_NO']);
                    return $this->_setAuth('LCD', $acc['ACC_NO'], $acc);
                }else{
                    //１２．長期ワンタイム認証失敗
                    $this->_renewCookie('DROP', NULL);
                    return $this->_setAuth('LCD', NULL, NULL);
                }
            }else{
                if($_POST['lgif_logout']==='force' || $_GET['logout']==='force'){
                    $this->_renewCookie('DROP', NULL);
                    return $this->_setAuth('SESSION', NULL, NULL);
                }else{
                    //８．COOKIE認証を試みます。
                    if($_COOKIE[$this->_sysId.'_keepalive']){
                        $prm = NULL;
                        $prm[':OTP']          = $_COOKIE[$this->_sysId.'_keepalive'];
                        $prm[':ACC_NO']       = $_COOKIE[$this->_sysId.'_keepalives'];
                        $prm[':VALID_TIME']   = strftime('%Y/%m/%d %H:%M:%S');
                        $prm[':PRM_ID']       = 'C';
                        $prm[':ACC_LOCK'] = 'F';
                        $acc = $db->dataset('_precontroller_checkOTP', $prm)->getAll();
                        $acc = $acc[0];
                        if($acc['ACC_NO']){
                            //１３．長期ワンタイム認証の成功
                            $this->_renewCookie('UPDATE', $acc['ACC_NO']);
                            return $this->_setAuth('LCD', $acc['ACC_NO'], $acc);
                        }else{
                            //１４．長期ワンタイム認証の失敗
                            $this->_renewCookie('DROP', NULL);
                            return $this->_setAuth('LCD', NULL, NULL);
                        }
                    }else{
                        //１５．セッション認証
                        if($_SESSION[$this->_sysId.'_auth']){
                            return $this->_setAuth('SESSION', $_SESSION[$this->_sysId.'_auth']);
                        }else{
                            $this->_renewCookie('DROP', NULL);
                            return $this->_setAuth('SESSION', NULL);
                        }
                    }
                }
            }
        }
    }

    /**
     * 認証結果authの設定
     *
     * sharedのauth_typeとauth_status, SESSIONを設定する
     *
     * @param string  $agAuthType 認証種別(POST, CD, LCD, COOKIE, SESSION)
     * @param integer $agAccNo    アカウント番号(或いはNULL)
     * @param array   $agAcc      アカウント情報配列(或いはNULL)☆SESSION時は使わない
     * @return variant FALSE:失敗, 整数:アカウント番号
     */
    private function _setAuth($agAuthType, $agAccNo, $agAcc = NULL){
        //「ログイン後の通常ページ遷移」以外では、ログ登録＆最終ログイン日時も取得
        if ($agAuthType !== 'SESSION'){//「ログイン後の通常ページ遷移」でない場合
            //認証成功時のみ最終ログイン日時を取得して$agAccに設定
            $wkCd       = 0;//初期値
            $wkP_Height = 0;
            $wkP_Width  = 0;
            if ($agAccNo){//認証成功時
                //検索共通のパラメータ準備
                $wkPrm = array();
                $wkPrm[':ACC_NO'] = $agAcc['ACC_NO'];
                //最終ログイン日時を取得
                $wkLastLoginDtArray = $this->shared('DB')->dataset('_precontroller_S_LAST_LOGIN', $wkPrm)->getAll();
                $agAcc['LAST_LOGIN_DT'] = $wkLastLoginDtArray[0]['LOGIN_TIME'];
            }
            
            //直前の「ESSIONアカウント情報」退避
            $wkSessionAccOld = $_SESSION[$this->_sysId.'_acc'];
            //SESSIONにアカウント情報を書き込む
            $_SESSION[$this->_sysId.'_acc'] = $agAcc;
            
            //LCD以外orLCDでSESSIONに変更があったか不明者の時のみ、アクセスログを書き込む
            if (($agAuthType !== 'LCD') || ($wkSessionAccOld == NULL) || (!$agAcc['ACC_NO']) || ($wkSessionAccOld['ACC_NO'] != $agAcc['ACC_NO'])){

                //ログイン履歴記録
                $wkLoginTime      = date('Y/m/d H:i:s', time());
                $wkPrm = array();
                $wkPrm[':ACC_NO'] = $agAcc['ACC_NO'];
                $wkPrm[':LOGIN_TIME'] = $wkLoginTime;
                $wkPrm[':AUTH_TYPE'] = $agAuthType;
                if ($agAccNo){
                    $wkPrm[':AUTH_STATUS'] = 'T';
                }else{
                    $wkPrm[':AUTH_STATUS'] = 'F';
                }
                $wkPrm[':IP_ADDRESS'] = $_SERVER['REMOTE_ADDR'];
                $wkPrm[':CREATE_USER'] = $agAcc['ACC_NO'];
                $wkPrm[':CREATE_TIME'] = $wkLoginTime;
                $this->shared('DB')->query('_precontroller_ADD_LOGIN_LOG',$wkPrm);
            }
        }
        
        //「ログイン後の通常ページ遷移」含む全てで行う処理
        $this->shared()->add('auth_type',   $agAuthType);
        $this->shared()->add('auth_status', ( $agAccNo ? 'SUCCEEDED' : 'FAILED' ) );
        if (!$agAccNo){//logoutと失敗用
            $_SESSION[$this->_sysId.'_acc'] = NULL;
        }
        $_SESSION[$this->_sysId.'_auth'] = ( $agAccNo ? $agAccNo : NULL );
        return ( $agAccNo ? $agAccNo : FALSE );
    }



    /**
     * Keepalive COOKIE 更新処理
     *
     * 長期ワンタイム認証による自動ログイン用COOKIE情報の更新処理
     *
     * @param integer $mode 'NEW':新規長期ワンタイム認証を設定, 'DROP':長期ワンタイム認証を棄却, 'UPDATE':コードは変えずに期間を延長する
     * @param integer $acc  アカウント番号(或いはNULL)
     * @return boolean 常にTRUE
     */
    private function _renewCookie($mode, $acc=NULL){
        $db = $this->shared('DB');
        $otp = NULL;
        if($_COOKIE[$this->_sysId.'_keepalive']){
            $otp = $_COOKIE[$this->_sysId.'_keepalive'];
        }
        if( ($mode==='NEW' || $mode==='DROP') && $_COOKIE[$this->_sysId.'_keepalive']){
            // 従来のCOOKIEに設定されていた長期ワンタイム認証をDBから棄却
            $prm = NULL;
            $prm[':PRM_ID'] = 'C';
            $prm[':OTP']    = $_COOKIE[$this->_sysId.'_keepalive'];
            $prm[':ACC_NO'] = $_COOKIE[$this->_sysId.'_keepalives'];
            $db->query('_precontroller_dropOTP', $prm );
            $otp = NULL;
        }
        if($mode==='UPDATE' && $acc){
            // 長期ワンタイムパスワードの延長をDBに申請
            $prm = NULL;
            $prm[':VALID_TIME'] = strftime('%Y/%m/%d %H:%M:%S', time()+($this->config('app.max_cd_term')*24*60*60));
            $prm[':PRM_ID']     = 'C';
            $prm[':OTP']        =  ( $_GET['lcd'] ? $_GET['lcd'] : $_COOKIE[$this->_sysId.'_keepalive'] );
            $prm[':ACC_NO']     = $acc;
            //$db->query('_precontroller_renewOTP', $prm );
        }
        if($mode==='NEW' && $acc){
            // 新規長期ワンタイムパスワードをDBに申請
            $otp = hash('md5', $this->_app->getRandomKey(4));
            $prm = NULL;
            $prm[':ACC_NO']      = $acc;
            $prm[':PRM_KEYWORD'] = $otp;
            $prm[':VALID_TIME']  = strftime('%Y/%m/%d %H:%M:%S', time()+($this->config('app.max_lcd_term')*24*60*60));
            $prm[':CREATE_USER'] = $acc;
            $prm[':CREATE_TIME'] = strftime('%Y/%m/%d %H:%M:%S');
            $db->query('_precontroller_registerOTP',$prm);
        }
        if($mode==='DROP'){
            $term = time()-3600;
        }else{
            $term = time()+($this->config('app.max_lcd_term')*24*60*60);
        }

        setcookie($this->_sysId.'_keepalive',  $otp, $term); // KeepaliveOTPの設定
        setcookie($this->_sysId.'_keepalives', $acc, $term); // Keepaliveアカウントの設定
        return TRUE;
    }

}

?>
