Session::getByHandle()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * This file is part of the Divergence package.
4
 *
5
 * (c) Henry Paradiz <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Divergence\Models\Auth;
12
13
use Divergence\Models\Model;
14
use Divergence\Models\Relations;
15
use Divergence\Models\Mapping\Column;
16
17
/**
18
 * Session object
19
 *
20
 * @author Henry Paradiz <[email protected]>
21
 * @author Chris Alfano <[email protected]>
22
 * @inheritDoc
23
 * @property string $Handle Unique identifier for this session used by the cookie.
24
 * @property string $LastRequest Timestamp of the last time this session was updated.
25
 * @property string $Binary Actually raw binary in the string
26
 */
27
class Session extends Model
28
{
29
    use Relations;
30
31
    // Session configurables
32
    public static string $cookieName = 's';
33
    public static string $cookieDomain = '';
34
    public static string $cookiePath = '/';
35
    public static $cookieSecure = false;
36
    public static $cookieExpires = false;
37
    public static $timeout = 31536000; //3600;
38
39
    // support subclassing
40
    public static $rootClass = __CLASS__;
41
    public static $defaultClass = __CLASS__;
42
    public static $subClasses = [__CLASS__];
43
44
    // ActiveRecord configuration
45
    public static $tableName = 'sessions';
46
    public static $singularNoun = 'session';
47
    public static $pluralNoun = 'sessions';
48
49
    #[Column(notnull: false, default:null)]
50
    protected $ContextClass;
51
52
    #[Column(type:'int', notnull: false, default:null)]
53
    protected $ContextID;
54
55
    #[Column(unique:true, length:32)]
56
    protected $Handle;
57
58
    #[Column(type:'timestamp', notnull:false)]
59
    protected $LastRequest;
60
61
    #[Column(type:'binary', length:16)]
62
    protected $LastIP;
63
64
    /**
65
     * Gets or sets up a session based on current cookies.
66
     * Will always update the current session's LastIP and LastRequest fields.
67
     *
68
     * @param boolean $create
69
     * @return static|false
70
     */
71 1
    public static function getFromRequest($create = true)
72
    {
73 1
        $sessionData = [
74 1
            'LastIP' => inet_pton($_SERVER['REMOTE_ADDR']),
75 1
            'LastRequest' => time(),
76 1
        ];
77
78
        // try to load from cookie
79 1
        if (!empty($_COOKIE[static::$cookieName])) {
80 1
            if ($Session = static::getByHandle($_COOKIE[static::$cookieName])) {
81
                // update session & check expiration
82 1
                $Session = static::updateSession($Session, $sessionData);
83
            }
84
        }
85
        // try to load from any request method
86 1
        if (empty($Session) && !empty($_REQUEST[static::$cookieName])) {
87 1
            if ($Session = static::getByHandle($_REQUEST[static::$cookieName])) {
88
                // update session & check expiration
89 1
                $Session = static::updateSession($Session, $sessionData);
90
            }
91
        }
92 1
        if (!empty($Session)) {
93
            // session found
94 1
            return $Session;
95 1
        } elseif ($create) {
96
            // create session
97 1
            return static::create($sessionData, true);
98
        } else {
99
            // no session available
100 1
            return false;
101
        }
102
    }
103
104 1
    public static function updateSession(Session $Session, $sessionData)
105
    {
106
        // check timestamp
107 1
        if (time() > $Session->__get('LastRequest') + static::$timeout) {
108 1
            $Session->terminate();
109
110 1
            return false;
111
        } else {
112
            // update session
113 1
            $Session->setFields($sessionData);
114 1
            if (function_exists('fastcgi_finish_request')) {
115
                // @codeCoverageIgnoreStart
116
                register_shutdown_function(function ($Session) {
117
                    $Session->save();
118
                }, $Session);
119
            // @codeCoverageIgnoreEnd
120
            } else {
121 1
                $Session->save();
122
            }
123
124 1
            return $Session;
125
        }
126
    }
127
128
    /**
129
     * Gets by handle.
130
     *
131
     * @param string $handle
132
     * @return static
133
     */
134 2
    public static function getByHandle($handle)
135
    {
136 2
        return static::getByField('Handle', $handle, true);
137
    }
138
139
    /**
140
     * @inheritDoc
141
     *
142
     * Saves the session to the database and sets the session cookie.
143
     * Will generate a unique handle for the session if none exists.
144
     *
145
     * @param boolean $deep
146
     * @return void
147
     */
148 1
    public function save($deep = true)
149
    {
150
        // set handle
151 1
        if (!$this->getValue('Handle')) {
152 1
            $this->setValue('Handle', static::generateUniqueHandle());
153
        }
154
155
        // call parent
156 1
        parent::save($deep);
157
158
        // set cookie
159 1
        if (!headers_sent()) {
160
            // @codeCoverageIgnoreStart
161
            setcookie(
162
                static::$cookieName,
163
                $this->getValue('Handle'),
0 ignored issues
show
Bug introduced by
It seems like $this->getValue('Handle') can also be of type array; however, parameter $value of setcookie() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
                /** @scrutinizer ignore-type */ $this->getValue('Handle'),
Loading history...
164
                static::$cookieExpires ? (time() + static::$cookieExpires) : 0,
165
                static::$cookiePath,
166
                static::$cookieDomain,
167
                static::$cookieSecure
168
            );
169
            // @codeCoverageIgnoreEnd
170
        }
171
    }
172
173
    /**
174
     * Attempts to unset the cookie.
175
     * Unsets the variable from the $_COOKIE global.
176
     * Deletes session from database.
177
     *
178
     * @return void
179
     */
180 1
    public function terminate()
181
    {
182 1
        if (!headers_sent()) {
183
            // @codeCoverageIgnoreStart
184
            setcookie(static::$cookieName, '', time() - 3600);
185
            // @codeCoverageIgnoreEnd
186
        }
187 1
        unset($_COOKIE[static::$cookieName]);
188
189 1
        $this->destroy();
190
    }
191
192
    /**
193
     * Makes a random 32 digit string by generating 16 random bytes
194
     * Is cryptographically secure.
195
     * @see http://php.net/manual/en/function.random-bytes.php
196
     *
197
     * @return string
198
     */
199 2
    public static function generateUniqueHandle()
200
    {
201
        do {
202 2
            $handle = bin2hex(random_bytes(16));
203 2
        } while (static::getByHandle($handle));
204
        // just in case checks if the handle exists in the database and if it does makes a new one.
205
        // chance of happening is 1 in 2^128 though so might want to remove the database call
206
207 2
        return $handle;
208
    }
209
}
210