<?php
/*******************************************************************************

    Copyright 2013 Whole Foods Co-op

    This file is part of CORE-POS.

    IT CORE is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    IT CORE is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    in the file license.txt along with IT CORE; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*********************************************************************************/

if (!class_exists('FannieDB')) { 
    include_once(dirname(__FILE__).'/../data/FannieDB.php');
}

class FannieAuth 
{

    /**
      Check who is logged in
      @return string username or False

      If authentication is not enabled, this
      function returns the string 'null'. If
      an init.php file is in place it returns
      the string 'init'.
    */
    static public function checkLogin()
    {
        if (!self::enabled()) {
            return 'null';
        }
        if (self::initCheck()) {
            return 'init';
        }

        if (!isset($_COOKIE['session_data'])){
            return self::checkToken();
        }

        $cookie_data = base64_decode($_COOKIE['session_data']);
        $session_data = unserialize($cookie_data);

        $name = $session_data['name'];
        $session_id = $session_data['session_id'];

        if (!self::isAlphanumeric($name) or !self::isAlphanumeric($session_id)) {
            return false;
        }

        $sql = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        if (!$sql->isConnected()) {
            return false;
        }
        $checkQ = $sql->prepare("select * from Users AS u LEFT JOIN
                userSessions AS s ON u.uid=s.uid where u.name=? 
                and s.session_id=?");
        $checkR = $sql->execute($checkQ,array($name,$session_id));

        if ($sql->num_rows($checkR) == 0) {
            return false;
        }

        return $name;
    }

    private static function checkToken()
    {
        $headers = getallheaders();
        if (!isset($headers['Authorization'])) {
            return false;
        }
        $parts = explode(':', $headers['Authorization'], 2);
        if (count($parts) != 2) {
            return false;
        }
        $type = $parts[0];
        $token = $parts[1];
        if (trim(strtoupper($type)) != 'BEARER') {
            $log = FannieLogger::factory();
            $log->warning("Received Authorization header: " . $headers['Authorization']);
            return false; 
        }

        $sql = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        if (!$sql->isConnected()) {
            return false;
        }
        $checkP = $sql->prepare("SELECT username FROM UserTokens WHERE token=? AND revoked=0");
        $checkR = $sql->execute($checkP, array(trim($token)));
        if ($sql->numRows($checkR) == 0) {
            return false;
        }
        $checkW = $sql->fetchRow($checkR);

        return $checkW['username'];
    }

    /**
      Check if the current user has the given
      permission
      @param $auth authorization class name
      @param $sub optional subclass
      @return current username or False

      If authentication is not enabled, this
      function returns the string 'null'. If
      an init.php file is in place it returns
      the string 'init'.
    */
    static public function validateUserQuiet($auth, $sub='all')
    {
        if (!self::enabled()) {
            return 'null';
        }
        if (self::initCheck()) {
            return 'init';
        }

        $current_user = self::checkLogin();
        if (!$current_user) {
            return false;
        }

        $groupPriv = self::checkGroupAuth($current_user,$auth,$sub);
        if ($groupPriv) {
            return $current_user;
        }

        $priv = self::checkAuth($current_user,$auth,$sub);
        if (!$priv) {
            return false;
        }

        return $current_user;
    }

    static public function validateUserLimited($auth_class)
    {
        if (!self::enabled()) {
            return 'all';
        }
        if (self::initCheck()) {
            return 'all';
        }

        $current_user = self::checkLogin();
        if (!$current_user) {
            return false;
        }

        $as_user = self::userAuthRange($current_user, $auth_class);
        $as_group = self::groupAuthRange($current_user, $auth_class);

        if ($as_group === false && $as_user === false) {
            return false;
        } elseif ($as_user == 'all' || $as_group == 'all') {
            return 'all';
        } elseif ($as_user === false) {
            return $as_group;
        } elseif ($as_group === false) {
            return $as_user;
        } else {
            $full_range = array(
                $as_user[0] < $as_group[0] ? $as_user[0] : $as_group[0],
                $as_user[1] < $as_group[1] ? $as_user[1] : $as_group[1],
            );
            return $full_range;
        }
    }

    static private function userAuthRange($name, $auth_class)
    {
        if (self::initCheck()) {
            return 'all';
        }

        if (!self::isAlphanumeric($name) || !self::isAlphanumeric($auth_class)) {
            return false;
        }

        $uid = self::getUID($name);
        $dbc = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        $query = $dbc->prepare("
            SELECT MIN(sub_start) AS lowerBound,
                MAX(sub_end) AS upperBound
            FROM userPrivs
            WHERE uid=?
                AND auth_class=?
            GROUP BY uid,
                auth_class");
        $result = $dbc->execute($query, array($uid, $auth_class));
        if (!$result || $dbc->numRows($result) == 0) {
            return false;
        }

        $range = $dbc->fetchRow($result);
        if ($range['lowerBound'] == 'all' || $range['upperBound'] == 'all') {
            return 'all';
        } else {
            return array($range['lowerBound'], $range['upperBound']);
        }
    }

    static private function groupAuthRange($username, $auth_class)
    {
        if (self::initCheck()) {
            return 'all';
        }

        if (!self::isAlphanumeric($username) || !self::isAlphanumeric($auth_class)) {
            return false;
        }

        $dbc = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        $query = $dbc->prepare("
            SELECT MIN(sub_start) AS lowerBound,
                MAX(sub_end) AS upperBound
            FROM userGroupPrivs AS p
                INNER JOIN userGroups AS g ON p.gid=g.gid
            WHERE g.username=?
                AND p.auth=?
            GROUP BY g.username,
                p.auth");
        $result = $dbc->execute($query, array($username, $auth_class));
        if (!$result || $dbc->numRows($result) == 0) {
            return false;
        }

        $range = $dbc->fetchRow($result);
        if ($range['lowerBound'] == 'all' || $range['upperBound'] == 'all') {
            return 'all';
        } else {
            return array($range['lowerBound'], $range['upperBound']);
        }
    }

    /**
      Check if the given user has the given permission
      @param $name the username
      @param $auth_class the authorization class
      @param $sub optional subclass
      @return boolean
    */
    static private function checkAuth($name, $auth_class, $sub='all')
    {
        if (self::initCheck()) {
            return 'init';
        }

        if (!self::isAlphanumeric($name) || !self::isAlphanumeric($auth_class) || !self::isAlphanumeric($sub)) {
            return false;
        }

        $uid = self::getUID($name);
        if (!$uid) {
            return false;
        }
        $sql = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        $checkQ = $sql->prepare("select * from userPrivs where uid=? and auth_class=? and
                 ((? between sub_start and sub_end) or (sub_start='all' and sub_end='all'))");
        $checkR = $sql->execute($checkQ,array($uid,$auth_class,$sub));
        if ($sql->num_rows($checkR) == 0) {
            return false;
        }

        return true;
    }

    /**
      Check if the given user is part of a group that
      has the given permission
      @param $user the username
      @param $auth the authorization class
      @param $sub optional subclass
      @return boolean
    */
    static private function checkGroupAuth($user, $auth, $sub='all')
    {
        $sql = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        if (!self::isAlphaNumeric($user) || !self::isAlphaNumeric($auth) ||
            !self::isAlphaNumeric($sub)) {
            return false;
        }
        $checkQ = $sql->prepare("select g.gid  from userGroups as g, userGroupPrivs as p where
                        g.gid = p.gid and g.username=?
                        and p.auth=? and
                        ((? between p.sub_start and p.sub_end) or
                        (p.sub_start='all' and p.sub_end='all'))");
        $checkR = $sql->execute($checkQ,array($user,$auth,$sub));

        if ($sql->num_rows($checkR) == 0) {
            return false;
        }

        return true;
    }

    /**
      Get UID for given username
      @param $name the username
      @return string UID or False
    
      If authentication is not enabled,
      returns string '0000'.
    */
    public static function getUID($name=null) 
    {
        if (!self::enabled()) {
            return '0000';
        }

        if ($name === null) {
            $name = self::checkLogin();
            if ($name === false) {
                return false;
            }
        }

        $sql = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        $fetchQ = $sql->prepare("select uid from Users where name=?");
        $fetchR = $sql->execute($fetchQ,array($name));
        if ($sql->num_rows($fetchR) == 0) {
            return false;
        }
        $uid = $sql->fetchRow($fetchR);
        $uid = $uid[0];

        return $uid;
    }

    public static function getName($uid)
    {
        if (!self::enabled()) {
            return 'n/a';
        }

        $sql = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        $uid = str_pad($uid, 4, '0', STR_PAD_LEFT);
        $fetchQ = $sql->prepare("select name from Users where uid=?");
        return $sql->getValue($fetchQ, array($uid));
    }

    public static function getRealName($uid)
    {
        if (!self::enabled()) {
            return 'n/a';
        }

        $sql = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        $uid = str_pad($uid, 4, '0', STR_PAD_LEFT);
        $fetchQ = $sql->prepare("select real_name from Users where uid=?");
        return $sql->getValue($fetchQ, array($uid));
    }

    public static function getEmail($uid)
    {
        if (!self::enabled()) {
            return 'n/a';
        }

        $sql = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        $uid = str_pad($uid, 4, '0', STR_PAD_LEFT);
        $fetchQ = $sql->prepare("select email from Users where uid=?");
        return $sql->getValue($fetchQ, array($uid));
    }

    public static function hasEmail($uid)
    {
        if (!self::enabled()) {
            return false;
        }

        $sql = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        $uid = str_pad($uid, 4, '0', STR_PAD_LEFT);
        $fetchQ = $sql->prepare("select email from Users where uid=?");
        $email = $sql->getValue($fetchQ, array($uid));

        return strpos($email, '@');
    }

    /**
      Create/update authorization class
      @param $auth_class [string] class name
      @param $description [string] description of authorization class
      @return [boolean] success / failure
    */
    static public function createClass($auth_class, $description)
    {
        $dbc = FannieDB::get(FannieConfig::factory()->get('OP_DB'));
        if (!$dbc->tableExists('userKnownPrivs')) {
            return false;
        }
        $notes = str_replace("\n","<br />",$description);
        $model = new UserKnownPrivsModel($dbc);
        $model->auth_class($auth_class);
        $model->notes($notes);

        return $model->save() ? true : false;
    }

    /**
      Check if authentication is enabled in
      Fannie's configuration
      @return boolean
    */
    static private function enabled()
    {
        $enabled = FannieConfig::factory()->get('AUTH_ENABLED', false);

        return $enabled ? true : false;
    }

    /**
      Check if an init.php file exists
      @return boolean
    */
    static private function initCheck()
    {
        return file_exists(dirname(__FILE__).'/../../auth/init.php');
    }

    /**
      Check if a string is alphanumeric
      @return boolean
    */
    static private function isAlphanumeric($str)
    {
        if (preg_match("/^\\w*$/",$str) == 0) {
            return false;
        }

        return true;
    }
}

