polyfony-inc/polyfony

View on GitHub
Private/Polyfony/Security/Accounts.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace Polyfony\Security;

// framework internals
use Polyfony\{ 
    Security, 
    Config, 
    Logger, 
    Store\Cookie, 
    Security\Accounts\PermissionsAccessors 
};

// models
use \Models\{
    Accounts as ExtendedAccounts,
    AccountsSessions,
    AccountsLogins,
    AccountsPermissions,
    AccountsPermissionsAssigned,
    AccountsRolesAssigned,
    AccountsRoles
};

class Accounts extends PermissionsAccessors {

    public static function getByLogin(
        string $posted_login
    ) :?Accounts {
        return ExtendedAccounts::_select()
            ->first()
            ->where([
                'login'        => $posted_login,
                'is_enabled'=> 1
            ])
            ->execute();
    }

    public static function getBySession(
        string $signature
    ) :?Accounts {
        return ExtendedAccounts::_select([
                'Accounts.*'
            ])
            ->join(
                'AccountsSessions', 
                'AccountsSessions.id_account', 
                'Accounts.id'
            )
            ->object('Accounts')
            ->first()
            ->where([
                'AccountsSessions.signature'    =>$signature,
                'Accounts.is_enabled'    =>1
            ])
            ->whereGreaterThan([
                'AccountsSessions.is_expiring_on'=>time()
            ])
            ->execute();
    }

    public function getSession() :AccountsSessions {

        return AccountsSessions::_select()
            ->first()
            ->where([
                'id_account'=>$this->get('id')
            ])
            ->execute();

    }

    public function disconnect() :bool {

        return 
            $this->terminateSession() && 
            $this->terminateCookie();

    }

    private function terminateSession() :bool {

        return $this
            ->getSession()
            ->delete();
            
    }

    private function terminateCookie() :bool {

        return !Cookie::has(Config::get('security', 'cookie')) ? true : 
            Cookie::remove(
                Config::get('security', 'cookie')
            );

    }

    public function hasExpired() :bool {
        return 
            $this->get('is_expiring_on') && 
            time() > $this->get('is_expiring_on',true);
    }

    public function login() :bool {

        // generate the expiration date
        $session_expiration = 
            time() + 
            ( 
                Config::get('security', 'session_duration') * 
                3600 
            );
        
        // generate a session key with its expiration, 
        // the login, the password, the ip, the user agent
        $session_signature = Security::getSignature(
            $this->get('login', true).
            $this->get('password', true).
            $session_expiration
        );

        // if we manage to open the session properly
        return 
            // if the cookie creation went right
            $this->createCookieSession($session_signature) && 
            // and the account record updating went right too
            $this->createDatabaseSession(
                $session_signature, 
                $session_expiration
            );

    }

    public function isBeingForcedFrom(
        string $originating_from
    ) :bool {
        // this new method prevents DDoS
        // since only the bruteforcer gets blocked, the actual user
        return 
            AccountsLogins::_select([
                'count'=>'id'
            ])
            ->where([
                'id_account'        =>$this->get('id'),
                'has_failed'        =>1,
                'originating_from'    =>$originating_from
            ])
            ->whereGreaterThan([
                'creation_date' => 
                    time() - 
                    Config::get(
                        'security', 
                        'forcing_timeframe'
                    )
            ])
            ->first()
            ->execute()
            ->get('count_id') > Config::get(
                'security', 
                'forcing_maximum_attempts'
            );

    }

    // first part of the session opening process
    private function createCookieSession(
        string $session_signature
    ) :bool {

        // store a cookie with our current session key in it
        return Cookie::put(
            Config::get('security', 'cookie'), 
            $session_signature, 
            true, 
            Config::get('security', 'session_duration')
        );

    }

    // second part of the session opening process
    private function createDatabaseSession(
        string $session_signature,
        int $expiration_date
    ) :bool {

        // remove any remaining session
        AccountsSessions::_delete()
            ->where(['id_account'=>$this->get('id')])
            ->execute();

        // open a new session
        return (new AccountsSessions)->set([
            'id_account'        => $this->get('id'),
            'is_expiring_on'    => $expiration_date,
            'signature'            => $session_signature,
        ])->save();

    }

    public function logSuccessfulLogin() :bool {

        // log the loggin action
        Logger::info("Account {$this->get('login')} has logged in");

        // also insert in a table
        return (new AccountsLogins)
            ->set([
                'originating_from'    =>Security::getSafeRemoteAddress(),
                'has_succeeded'        =>1,
                'id_account'        =>$this->get('id')
            ])
            ->save();
    }

    public function logFailedLogin(
        ?string $reason = ''
    ) :bool {

        // log the loggin action
        Logger::warning(
            "Account {$this->get('login')} has failed to log in" . 
            ($reason ? " ($reason)" : '')
        );

        // also insert in a table
        return (new AccountsLogins)
            ->set([
                'creation_date'        =>time(),
                'originating_from'    =>Security::getSafeRemoteAddress(),
                'has_failed'        =>1,
                'id_account'        =>$this->get('id')
            ])
            ->save();
    }

    public function setPassword(
        #[\SensitiveParameter]
        string $plaintext_password
    ) :self {
        return $this->set([
            'password'=>Security::getPassword($plaintext_password)
        ]);
    }

    public function hasThisPassword(
        #[\SensitiveParameter]
        string $plaintext_password_to_compare_against
    ) :bool {
        // compare the existing signature, with the signature of the password to check
        return 
            $this->get('password') === 
            Security::getPassword($plaintext_password_to_compare_against);

    }

}

?>