Completed
Branch master (6afa16)
by Pierre-Henry
33:46
created

Token::__destruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 31 and the first side effect is on line 15.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * @title          Token CSRF (Cross-site request forgery)
4
 * @desc           Protects against Cross-site request forgery attack.
5
 *
6
 * @author         Pierre-Henry Soria <[email protected]>
7
 * @copyright      (c) 2012-2017, Pierre-Henry Soria. All Rights Reserved.
8
 * @license        GNU General Public License; See PH7.LICENSE.txt and PH7.COPYRIGHT.txt in the root directory.
9
 * @package        PH7 / Framework / Security / CSRF
10
 * @version        1.2
11
 */
12
13
namespace PH7\Framework\Security\CSRF;
14
15
defined('PH7') or exit('Restricted access');
16
17
use PH7\Framework\Session\Session;
18
use PH7\Framework\Navigation\Browser;
19
use PH7\Framework\Util\Various;
20
use PH7\Framework\Mvc\Model\DbConfig;
21
use PH7\Framework\Mvc\Request\Http;
22
use PH7\Framework\Ip\Ip;
23
use PH7\UserCore;
24
use PH7\AdminCore;
25
use PH7\AffiliateCore;
26
27
/**
28
 * This class provides functions of numbers against the XSS (Cross-site scripting) vulnerability.
29
 * PH Security Token (PHST)
30
 */
31
final class Token
32
{
33
    /**
34
     * @internal We have commented on "security_token_http_referer_*" because it causes bugs and it doesn't
35
     * play a big role for safety because this variable can be changed by users (and the web browser).
36
     */
37
38
    const VAR_NAME = 'pHST';
39
40
    /** @var Session */
41
    private $_oSession;
42
43
    /** @var null|string */
44
    private $_sHttpReferer;
45
46
    /** @var null|string */
47
    private $_sUserAgent;
48
49
    public function __construct()
50
    {
51
        $this->_oSession = new Session;
52
53
        $oBrowser = new Browser;
54
        $this->_sHttpReferer = $oBrowser->getHttpReferer();
0 ignored issues
show
Documentation Bug introduced by
It seems like $oBrowser->getHttpReferer() can also be of type array. However, the property $_sHttpReferer is declared as type null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
55
        $this->_sUserAgent = $oBrowser->getUserAgent();
0 ignored issues
show
Documentation Bug introduced by
It seems like $oBrowser->getUserAgent() can also be of type array. However, the property $_sUserAgent is declared as type null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
56
        unset($oBrowser);
57
    }
58
59
    /**
60
     * Generate a random token.
61
     *
62
     * @param string $sName
63
     *
64
     * @return string The Token generated random.
65
     */
66
    public function generate($sName)
67
    {
68
        // If the token is still valid, it returns the correct token
69
        if ($this->_oSession->exists('security_token_' . $sName)) {
70
            return $this->_oSession->get('security_token_' . $sName);
71
        } else {
72
            $sToken = Various::genRnd($sName);
73
74
            $aSessionData = [
75
                'security_token_' . $sName => $sToken,
76
                'security_token_time_' . $sName => time(),
77
                //'security_token_http_referer_' . $sName => $this->_sHttpReferer,
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
78
                'security_token_ip_' . $sName => Ip::get(),
79
                'security_token_http_user_agent_' . $sName => $this->_sUserAgent
80
            ];
81
82
            $this->_oSession->set($aSessionData);
83
            return $sToken;
84
        }
85
    }
86
87
    /**
88
     * @param string $sName Name of the Token.
89
     *
90
     * @param string $sInputToken The name of the token inserted in the hidden tag of the form.
91
     * (e.g. for a from with method "post" and the field "<input type="hidden" name="my_token" />" the name of the token is "$_POST['my_token']" Default NULL
92
     *
93
     * @param integer $iTime Lifetime of token in seconds. Default NULL (value specified in the database settings).
94
     *
95
     * @return boolean Returns TRUE if the token is validated, FALSE otherwise.
96
     */
97
    public function check($sName, $sInputToken = null, $iTime = null)
98
    {
99
        $iTime = (empty($iTime)) ? DbConfig::getSetting('securityTokenLifetime') : $iTime;
100
101
        // The default tag name for the security token
102
        $sInputToken = (empty($sInputToken)) ? (new Http)->post('security_token') : $sInputToken;
103
104
        $aCheckSession = [
105
            'security_token_' . $sName,
106
            'security_token_time_' . $sName,
107
            //'security_token_http_referer_' . $sName,
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
108
            'security_token_ip_' . $sName,
109
            'security_token_http_user_agent_' . $sName
110
        ];
111
112
        if ($this->_oSession->exists($aCheckSession) && !empty($sInputToken))
113
            if ($this->_oSession->get('security_token_' . $sName) === $sInputToken)
114
                if ($this->_oSession->get('security_token_time_' . $sName) >= (time() - $iTime))
115
                    //if ($this->_sHttpReferer === $this->_oSession->get('security_token_http_referer_' . $sName))
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
116
                        if (Ip::get() === $this->_oSession->get('security_token_ip_' . $sName))
117
                            if ($this->_sUserAgent === $this->_oSession->get('security_token_http_user_agent_' . $sName))
118
                            {
119
                                // Delete the token and data sessions expired
120
                                $this->_oSession->remove($aCheckSession);
121
                                return true;
122
                            }
123
124
        // Delete the token and data sessions expired
125
        $this->_oSession->remove($aCheckSession);
126
        return false;
127
    }
128
129
    /**
130
     * The Get Token parameter for the URL if someone is logged (User, Admin or Affiliate), nothing otherwise.
131
     *
132
     * @return string
133
     */
134
    public function url()
135
    {
136
        return ($this->currentSess() !== true) ? '?' . static::VAR_NAME . '=' . $this->currentSess() : '';
137
    }
138
139
    /**
140
     * Checks the URL Token.
141
     *
142
     * @return boolean
143
     */
144
    public function checkUrl()
145
    {
146
        $oHttpRequest = new Http;
147
        $bRet = ( ($this->currentSess() === true) || $oHttpRequest->currentUrl() === PH7_URL_ROOT || ($oHttpRequest->get(static::VAR_NAME) === $this->currentSess()) );
148
        unset($oHttpRequest);
149
150
        return $bRet;
151
    }
152
153
    /**
154
     * Gets The Current Session Token.
155
     *
156
     * @return string|boolean The "token" if a user is logged or "true" if no user is logged.
157
     */
158
    protected function currentSess()
159
    {
160
        if (UserCore::auth())
161
            $sToken = $this->_oSession->get('member_token');
162
        elseif (AdminCore::auth())
163
            $sToken = $this->_oSession->get('admin_token');
164
        elseif (AffiliateCore::auth())
165
            $sToken = $this->_oSession->get('affiliate_token');
166
        else $sToken = true; // If nobody is logged on, we did not need to do this test, so it returns true
167
168
        return $sToken;
169
    }
170
171
    /**
172
     * Clone is set to private to stop cloning.
173
     */
174
    private function __clone() {}
175
}
176