atayahmet/glad

View on GitHub
src/Glad/Author.php

Summary

Maintainability
D
2 days
Test Coverage
<?php

namespace Glad;

use Glad\Interfaces\ConditionsInterface;
use Glad\Interfaces\CryptInterface;
use Glad\Interfaces\HashInterface;
use Glad\Interfaces\CookerInterface;
use Glad\Interfaces\DatabaseServiceInterface;
use Glad\Event\Dispatcher;
use Glad\GladProvider;
use Glad\Constants;
use Glad\Injector;
use SessionHandlerInterface;
use Closure;
use ReflectionClass;
use ErrorException;

/**
 * Auth process class
 *
 * @author Ahmet ATAY
 * @category Authentication
 * @package Glad
 * @copyright 2015
 * @license http://opensource.org/licenses/MIT MIT license
 * @link https://github.com/atayahmet/glad
 */
class Author
{
    /**
     * Repository instance
     *
     * @var object
     */
    protected static $repository;
    
    /**
     * Constant class instance
     *
     * @var object
     */
    protected static $constants;

    /**
     * Implemented model object
     *
     * @var object
      */
    protected static $model;

    /**
     * Query builder object
     *
     * @var object
     */
    protected static $queryBuilder;

    /**
     * Identity fields
     *
     * @var array
     */
    protected static $fieldIdentity;

    /**
     * Injector class
     *
     * @var object
     */
    protected static $injector;

    /**
     * Temp user data
     *
     * @var array
     */
    protected static $tempUser;

    /**
     * Safe user data
     *
     * @var array
     */
    protected static $user;

    /**
     * User session data
     *
     * @var array
     */
    protected static $userData;

    /**
     * Register transaction result
     *
     * @var bool
     */
    protected static $registerResult;

    /**
     * Login after register
     *
     * @var bool
     */
    protected static $registerLogin = true;

    /**
     * Change transaction result
     *
     * @var bool
     */
    protected static $changeResult;

    /**
     * Remember me
     *
     * @var bool
     */
    protected static $rememberMe = false;

    /**
     * All the results of operations
     *
     * @var bool
     */
    protected static $processResult = false;

    /**
     * status of operations
     *
     * @var bool
     */
    protected static $status = false;

    /**
     * Instance of EventDispatcher class
     *
     * @var object
     */
    protected static $eventDispatcher;

    /**
     * Instance of Reflection class
     *
     * @var object
     */
    protected static $reflection;

    /**
     * CryptInterface instance
     *
     * @var object
     */
    protected static $crypt;

    /**
     * CookerInterface instance 
     *
     * @var object
     */
    protected static $cooker;

    /**
     * Session token id
     *
     * @var string
     */
    protected static $tokenId;

    /**
     * SimpleXMLElement class instance
     *
     * @var object
     */
    protected static $simpleXML;

    protected static $env;

    /**
     * Class constructor
     *
     * @param object $constants
     * @param object $cooker
     * @param object $injector
     * @param object $crypt
     * @param object $databaseService
     * @param object $repository
     * @param object $eventDispatcher
     *
     */ 
    public function __construct(Constants $constants, CookerInterface $cooker, Injector $injector, CryptInterface $crypt, DatabaseServiceInterface $databaseService, SessionHandlerInterface $repository, Dispatcher $eventDispatcher)
    {
        static::$constants = $constants;
        static::$injector = $injector;
        static::$repository = $repository;
        static::$cooker = $cooker;
        static::$eventDispatcher = $eventDispatcher;
        static::$crypt = $crypt;
        static::$eventDispatcher->setInstance(static::getInstance());
        static::$model = $databaseService->get(static::$injector->get('db'));
        static::$env = php_sapi_name();
        static::setSession(static::$repository);
    }

    /**
     * Start session process
     *
     * @param object $repository SessionHandlerInterface
     *
     * @return void
     */ 
    protected static function setSession(SessionHandlerInterface $repository)
    {
        $activeDriver = static::$constants->repository['driver'];
        $config = static::$constants->repository['options'][$activeDriver];
        static::$tokenId = static::$cooker->get($config['name']);

        if(static::$env == 'cli') return false;

        session_set_save_handler($repository, true);
        $repository->openSession($config, static::$crypt);

        
        if(! static::$tokenId) {
            static::$tokenId = sha1(time());
            static::$cooker->set(
                    $config['name'],
                    static::$tokenId,
                    static::currentTime()+$config['timeout'],
                    "/",
                    static::$constants->cookieDomain,
                    false,
                    true
                );
        }
    }

    /**
     * New account handler method
     *
     * @param array $credentials
     *
     * @return self instance
     */ 
    public static function register(HashInterface $hash, array $credentials)
    {
        static::resetCheckVariables();
        if(static::guest() === true) {
            static::checkIdentityAsParameter($credentials);
            
            if(! static::checkIdentityForRealUser($credentials)) {
                static::$registerResult = false;
            }else{
                static::$tempUser = $credentials;

                $cost = static::$constants->cost;
                $credentials['password'] = $hash->make($credentials['password'], $cost);

                static::$registerResult = static::$model->insert($credentials);
                static::$user = $credentials;

                if(static::$registerResult) {
                    static::$processResult = true;
                    static::$registerLogin = false;
                }
            }
        }

        return static::getInstance();
    }

    /**
     * Change the user information
     *
     * @param object $credentials
     *
     * @return self instance
     */ 
    public static function change(HashInterface $hash, array $credentials)
    {
        static::resetCheckVariables();

        if(static::check() === true) {

            $credentials = static::cryptPasswordIfFieldExists($hash, $credentials);
            $tableIncrementField = static::$constants->id;
            $where = ['and' => [$tableIncrementField => static::getUserId()]];
            static::$changeResult = static::$model->update($where, $credentials);

            if(static::$changeResult){
                static::$user = static::$model->getIdentityWithId(static::getUserId());
                static::setRemember(static::$user);
                static::$processResult = true;
            }
        }
        return static::getInstance();
    }

    /**
     * Crypt password if password field 
     *
     * @param object $credentials
     *
     * @return array
     */ 
    protected static function cryptPasswordIfFieldExists(HashInterface $hash, array $credentials)
    {
        $fields = static::$constants->authFields;

        if(isset($credentials[$fields['password']])) {
            $cost = static::$constants->cost;
            $credentials['password'] = $hash->make($credentials['password'], $cost);
        }
        return $credentials;
    }

    /**
     * User login process
     *
     * @param object $hash
     * @param array $user
     * @param bool $remember
     *
     * @return self instance
     */ 
    public static function login(HashInterface $hash, array $user, $remember = false)
    {
        static::resetCheckVariables();
        $passField = static::$constants->authFields['password'];

        if(!isset($user[$passField]) || static::check() === true){
            return static::getInstance();
        }

        $result = static::$model->getIdentity(static::getIdField($user));
        static::$user = static::resolveDbResult($result);

        if(count(static::$user) < 1) return static::getInstance();

        if(!isset(static::$user[$passField])){
            return static::getInstance();
        }
        
        $login = $hash->verify($user[$passField], static::$user[$passField]);

        if($login === true) {
            if($remember === true) {
                static::setRemember(static::$user);
            }

            static::$rememberMe = $remember;
            static::$processResult = true;
        }
        return static::getInstance();
    }

    /**
     * Start remember process
     *
     * @param array $userData
     *
     * @return void
     */
    protected static function setRemember(array $userData)
    {
        $rememberConf = static::$constants->remember;

        if($rememberConf['enabled'] === true) {
            
            if(array_key_exists($rememberConf['field'], $userData) === false) {
                throw new ErrorException($rememberConf['field'] . " fields is missing on database");
            }

            $cookieData  = static::$cooker->get($rememberConf['cookieName']);
            $userOldData = static::$crypt->decrypt($cookieData, static::$constants->secret);

            if(is_array($userOldData) && array_key_exists($rememberConf['field'], $userOldData)) {
                
                $cookieName = $rememberConf['cookieName'];
                $lifeTime = static::currentTime()+$rememberConf['lifetime'];

                $token = static::$crypt->encrypt(static::currentTime()+$rememberConf['lifetime'], static::$constants->secret);
                
                $userData[$rememberConf['field']] = $token;
                $cryptedValue = static::$crypt->encrypt(json_encode($userData), static::$constants->secret);
                
                $setResult = static::$cooker->set(
                        $cookieName,
                        $cryptedValue,
                        $lifeTime,
                        "/",
                        static::$constants->cookieDomain,
                        false,
                        true
                    );

                if($setResult) {
                    $where = ['and' => [static::$constants->id => $userData[static::$constants->id]]];
                    static::$model->update($where,[$rememberConf['field'] => $token]);    
                }
            }
        }
    }

    /**
     * Login process with remember data 
     *
     * @return bool|array
     */
    protected static function loginFromRemember()
    {
        $rememberConf = static::$constants->remember;

        if($rememberConf['enabled'] === true) {
            $cookieName = $rememberConf['cookieName'];

            if(static::$cooker->has($cookieName)) {
                 
                 $userData = static::$crypt->decrypt(static::$cooker->get($cookieName), static::$constants->secret);
                 $userDataArr = json_decode($userData , true);
                 if(! json_last_error() && isset($userDataArr[$rememberConf['field']])) {
                     
                     $token = $userDataArr[$rememberConf['field']];
                     $tokenDecrypted = static::$crypt->decrypt($token, static::$constants->secret);

                     if(intval($tokenDecrypted) >= static::currentTime()) {
                         static::setRemember($userDataArr);
                         static::$user = static::resolveDbResult($userDataArr);
                         $result = static::setUserRepository(static::$user);
                         return $result;
                     }
                 }
            }
        }
        return false;
    }

    /**
     * Applies some conditions after transaction
     *
     * @param instance Closure
     *
     * @return bool
     */ 
    public static function apply(Closure $apply)
    {
        // $processResult: result of all processes variable
        if(static::$processResult === true){
            $apply(static::getInstance());

            if(static::getInstance()->conditionsRun()){
                return true;
            }
            return static::$processResult = false;
        }
    }

    /**
     * Registering event at some methods
     *
     * @param string $name
     * @param instance Closure
     *
     * @return self instance
     */ 
    public static function event($name, Closure $event)
    {
        static::$eventDispatcher->set($name, $event);
        return static::getInstance();
    }

    /**
     * Registering validate condition for some transactions
     *
     * @param string $conditions
     * @param instance Glad\Interfaces\ConditionsInterface
     *
     * @return self instance
     */ 
    public function conditions(array $conditions, ConditionsInterface $cond)
    {
        $cond->add($conditions);
        return static::getInstance();
    }

    /**
     * Run the conditions
     *
     * @param instance Glad\Interfaces\ConditionsInterface
     *
     * @return bool
     */ 
    protected function conditionsRun(ConditionsInterface $conditions)
    {
        if(static::$user && static::$processResult == true && $conditions->apply(static::$user, [], static::$eventDispatcher)){
            return static::status();
        }
        return false;
    }

    /**
     * User login process by user id
     *
     * @param int $userId
     *
     * @return bool
     */ 
    public static function loginByUserId($userId, $remember = false)
    {
        static::resetCheckVariables();

        if(static::check() === true || $userId == null) return static::getInstance();

        $result = static::$model->getIdentityWithId($userId);

        if(count($result) > 0) {
            
            static::$user = static::resolveDbResult($result);
            
            if(isset(static::$user) && is_array(static::$user)){
                static::$rememberMe = $remember;
                static::$processResult = true;
                static::status();
            }
        }
        return static::getInstance();
    }

    /**
     * User logout
     *
     * @return void
     */ 
    public static function logout()
    {
        static::resetCheckVariables();

        $expire = (static::currentTime()-static::$constants->remember['lifetime']);
        
        $activeDriver = static::$constants->repository['driver'];
        $config = static::$constants->repository['options'][$activeDriver];

        if(static::$tokenId) {
            static::$repository->destroy(static::$tokenId);
            static::$cooker->set(
                    $config['name'],
                    'glad',
                    $expire,
                    "/",
                    static::$constants->cookieDomain,
                    false,
                    true
                );
        }

        static::$cooker->set(
                    static::$constants->remember['cookieName'],
                    'glad',
                    $expire,
                    "/",
                    static::$constants->cookieDomain,
                    false,
                    true
                );
        static::$userData = static::userData();
    }

    /**
     * Reset the check variables
     *
     * @return void
     */ 
    protected static function resetCheckVariables()
    {
        static::$processResult = false;
        static::$status = false;
    }

    /**
     * Sets user data to repository
     *
     * @param $user array
     *
     * @return bool
     */ 
    protected static function setUserRepository(array $user)
    {
        if(static::$registerLogin) {
            $userData = [
                'userData' => $user,
                'auth' => ['status' => true]
            ];
            $activeDriver = static::$constants->repository['driver'];
            $config = static::$constants->repository['options'][$activeDriver];
            static::$tokenId = static::$cooker->get($config['name']);
            return static::$repository->write(static::$tokenId, $userData);
        }
    }

    /**
     * Gets user data from repository
     *
     * @return array
     */ 
    public static function userData()
    {
        $data = static::readSession();

        if($data && is_array($data)) {
            $passField = static::$constants->authFields['password'];

            unset($data['userData'][$passField]);
            
            return $data['userData'];
        }
    }

    /**
     * Gets container class instane
     *
     * @return object
     */ 
    protected static function getInstance()
    {
        return static::$injector->inject('Glad\Glad');
    }

    /**
     * Login to after register transaction
     *
     * @return void|bool
     */ 
    public static function andLogin()
    {
        if(static::status()){
            static::getInstance()->login(static::$tempUser);
            static::$tempUser = [];
            static::$registerLogin = true;
        }
        return false;
    }

    /**
     * Gets process status and set user data
     *
     * @return bool
     */ 
    public static function status()
    {
        if(static::$processResult === true) {
            static::setUserRepository(static::$user);
            static::$userData = static::readSession();
            static::$processResult = false;
            static::$status = true;
        }
        return static::$status;
    }

    /**
     * Controls the given parameter
     *
     * @param array $credentials
     * @return bool|exception
     */ 
    protected static function checkIdentityAsParameter($credentials)
    {
        try {
            $fields = static::$constants->authFields;
            
            foreach($fields['identity'] as $field){
                if(! isset($credentials[$field])){
                    throw new \Exception("Identity fields is missing");
                }
            }

            if( !$credentials[$fields['password']]) {
                throw new \Exception("Password field required!", 1);
            }

            return true;
        }
        catch(\Exception $e){
            throw $e;
        }
    }

    /**
     * Return the user id
     *
     * @return userId|null
     */ 
    public static function getUserId()
    {
        $userData = static::getData();

        $tableIncrementField = static::$constants->id;
        
        if(isset($userData[$tableIncrementField])) {
            
            $userId = $userData[$tableIncrementField];

            return is_numeric($userId) ? (int)$userId : $userId;
        }
    }

    /**
     * Return user data if user logged in
     *
     * @return array|null
     */ 
    protected static function getData()
    {
        if(static::authStatus()) {
            return static::userData();
        }
    }

    /**
     * Check the user in database by parameters
     * Arranges the data coming from database
     *
     * @param array $credentials
     *
     * @return bool
     */ 
    protected static function checkIdentityForRealUser(array $credentials)
    {
        $result = static::$model->getIdentity(static::getIdField($credentials));
        $result = static::resolveDbResult($result);

        $activeDriver = static::$constants->repository['driver'];
        $config = static::$constants->repository['options'][$activeDriver];
        static::$tokenId = static::$cooker->get($config['name']);
        return count($result) < 1;
    }

    /**
     * Check and gets authenticate fields
     *
     * @param array $credentials
     *
     * @return array
     */ 
    protected static function getIdField(array $credentials)
    {
        $identity = static::$constants->authFields['identity'];
        $fields = [];

        if(is_array($identity)){
            foreach($identity as $id){
                if(isset($credentials[$id])){
                    $fields[$id] = static::xssClean($credentials[$id]);
                }
            }
        }

        return $fields;
    }

    /**
     * Controls the given data from implemented model
     *
     * @param array $result
     *
     * @return array|exception
     */ 
    protected static function resolveDbResult($result)
    {
        $exception = false;

        if(!isset($result) || !$result) return [];
        if(! is_array(reset($result))) return $result;

        foreach($result as $key => $value){
            if(is_numeric($key) && (!is_array($result[$key]) && !is_object($result[$key])) ){
                $exception = true;
            }
        }

        if(! $exception){
            return (array)reset($result);
        }

        throw new \Exception('return data incorrect');
    }

    /**
     * Clean xss data
     *
     * @param string $input
     *
     * @return string
     */ 
    protected static function xssClean($input)
    {
        $input = strip_tags($input);
        $input = filter_var($input, FILTER_SANITIZE_STRING);

        return $input;
    }

    /**
     * Gets user logged in status
     *
     * @return bool
     */ 
    public static function check()
    {
        return static::authStatus();
    }

    /**
     * Gets user log out status
     *
     * @return bool
     */ 
    public static function guest()
    {
        return !static::authStatus();
    }

    /**
     * Runs some methods
     *
     * @param string $method
     *
     * @return bool|null
     */ 
    public static function is($method)
    {
        if(static::_hasMethod($method)) {
            return static::$method();
        }
    }

    /**
     * Export User data as json
     *
     * @return string
     */ 
    public static function toJson()
    {
        return json_encode(static::getData());
    }

    /**
     * Export User data as array
     *
     * @return array
     */ 
    public static function toArray()
    {
        return static::getData();
    }

    /**
     * Export User data as xml
     *
     * @return string|null
     */ 
    public static function toXml()
    {
        if(  ! class_exists('SimpleXMLElement')) {
            throw new \Exception('SimpleXMLElement class not found');
        }

        if(! is_object(static::$simpleXML)) {
            static::$simpleXML = new \SimpleXMLElement('<root/>');
        }
        
        $data = static::getData();

        if(is_array($data)) {
            foreach($data as $field => $value) {
                static::$simpleXML->addChild($field, $value);
            }
            return static::$simpleXML->asXML();
        }
    }

    /**
     * Export User data as stdObject
     *
     * @return object
     */ 
    public static function toObject()
    {   
        return (object)static::getData();
    }   

    /**
     * Detects the presence of the methods by ReflectionClass
     *
     * @param string $method
     *
     * @return bool
     */ 
    protected static function _hasMethod($method)
    {
        if(is_null(static::$reflection)) {
            static::$reflection = new ReflectionClass("Glad\Author");
        }
        return static::$reflection->hasMethod($method);
    }

    /**
     * Read session data and refresh session expiration
     *
     * @return array
     */
    protected static function readSession()
    {
        $data = static::$repository->read(static::$tokenId);

        if(! empty($data)) {
            static::$repository->write(static::$tokenId, $data, $refresh = true);
        }
        return $data;
    }

    /**
     * Return user logged in status to other methods
     *
     * @return bool
     */ 
    protected static function authStatus()
    {
        static::getInstance()->conditionsRun();

        if(! static::$userData) {
            static::$userData = static::readSession();
        }

        $authData = static::$userData;

        if($authData && isset($authData['auth'])) {
            return isset($authData['auth']['status']) && $authData['auth']['status'] === true;
        }else{
            return static::loginFromRemember();
        }
    }

    /**
     * Current timestamp
     *
     * @return integer
     */
    protected static function currentTime()
    {
        return time();
    }
}