Passed
Push — master ( 6ea154...39b2f2 )
by Robbie
43:39 queued 36:31
created

getTokenCookieSecure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Security\MemberAuthenticator;
4
5
use SilverStripe\Control\Cookie;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\ORM\FieldType\DBDatetime;
8
use SilverStripe\Security\AuthenticationHandler;
9
use SilverStripe\Security\IdentityStore;
10
use SilverStripe\Security\Member;
11
use SilverStripe\Security\RememberLoginHash;
12
use SilverStripe\Security\Security;
13
14
/**
15
 * Authenticate a member pased on a session cookie
16
 */
17
class CookieAuthenticationHandler implements AuthenticationHandler
18
{
19
20
    /**
21
     * @var string
22
     */
23
    private $deviceCookieName;
24
25
    /**
26
     * @var string
27
     */
28
    private $tokenCookieName;
29
30
    /**
31
     * @var boolean
32
     */
33
    private $tokenCookieSecure = false;
34
35
    /**
36
     * @var IdentityStore
37
     */
38
    private $cascadeInTo;
39
40
    /**
41
     * Get the name of the cookie used to track this device
42
     *
43
     * @return string
44
     */
45
    public function getDeviceCookieName()
46
    {
47
        return $this->deviceCookieName;
48
    }
49
50
    /**
51
     * Set the name of the cookie used to track this device
52
     *
53
     * @param string $deviceCookieName
54
     * @return $this
55
     */
56
    public function setDeviceCookieName($deviceCookieName)
57
    {
58
        $this->deviceCookieName = $deviceCookieName;
59
        return $this;
60
    }
61
62
    /**
63
     * Get the name of the cookie used to store an login token
64
     *
65
     * @return string
66
     */
67
    public function getTokenCookieName()
68
    {
69
        return $this->tokenCookieName;
70
    }
71
72
    /**
73
     * Set the name of the cookie used to store an login token
74
     *
75
     * @param string $tokenCookieName
76
     * @return $this
77
     */
78
    public function setTokenCookieName($tokenCookieName)
79
    {
80
        $this->tokenCookieName = $tokenCookieName;
81
        return $this;
82
    }
83
84
    /**
85
     * Get the name of the cookie used to store an login token
86
     *
87
     * @return string
88
     */
89
    public function getTokenCookieSecure()
90
    {
91
        return $this->tokenCookieSecure;
92
    }
93
94
    /**
95
     * Set cookie with HTTPS only flag
96
     *
97
     * @param string $tokenCookieSecure
98
     * @return $this
99
     */
100
    public function setTokenCookieSecure($tokenCookieSecure)
101
    {
102
        $this->tokenCookieSecure = $tokenCookieSecure;
0 ignored issues
show
Documentation Bug introduced by
The property $tokenCookieSecure was declared of type boolean, but $tokenCookieSecure is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
103
        return $this;
104
    }
105
106
    /**
107
     * Once a member is found by authenticateRequest() pass it to this identity store
108
     *
109
     * @return IdentityStore
110
     */
111
    public function getCascadeInTo()
112
    {
113
        return $this->cascadeInTo;
114
    }
115
116
    /**
117
     * Set the name of the cookie used to store an login token
118
     *
119
     * @param IdentityStore $cascadeInTo
120
     * @return $this
121
     */
122
    public function setCascadeInTo(IdentityStore $cascadeInTo)
123
    {
124
        $this->cascadeInTo = $cascadeInTo;
125
        return $this;
126
    }
127
128
    /**
129
     * @param HTTPRequest $request
130
     * @return Member
131
     */
132
    public function authenticateRequest(HTTPRequest $request)
133
    {
134
        $uidAndToken = Cookie::get($this->getTokenCookieName());
135
        $deviceID = Cookie::get($this->getDeviceCookieName());
136
137
        if ($deviceID === null || strpos($uidAndToken, ':') === false) {
138
            return null;
139
        }
140
141
        list($uid, $token) = explode(':', $uidAndToken, 2);
142
143
        if (!$uid || !$token) {
144
            return null;
145
        }
146
147
        // check if autologin token matches
148
        /** @var Member $member */
149
        $member = Member::get()->byID($uid);
0 ignored issues
show
Bug introduced by
$uid of type string is incompatible with the type integer expected by parameter $id of SilverStripe\ORM\DataList::byID(). ( Ignorable by Annotation )

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

149
        $member = Member::get()->byID(/** @scrutinizer ignore-type */ $uid);
Loading history...
150
        if (!$member) {
0 ignored issues
show
introduced by
$member is of type SilverStripe\Security\Member, thus it always evaluated to true. If $member can have other possible types, add them to src/Security/MemberAuthe...thenticationHandler.php:148
Loading history...
151
            return null;
152
        }
153
154
        $hash = $member->encryptWithUserSettings($token);
155
156
        /** @var RememberLoginHash $rememberLoginHash */
157
        $rememberLoginHash = RememberLoginHash::get()
158
            ->filter([
159
                'MemberID' => $member->ID,
160
                'DeviceID' => $deviceID,
161
                'Hash' => $hash,
162
            ])->first();
163
        if (!$rememberLoginHash) {
0 ignored issues
show
introduced by
$rememberLoginHash is of type SilverStripe\Security\RememberLoginHash, thus it always evaluated to true. If $rememberLoginHash can have other possible types, add them to src/Security/MemberAuthe...thenticationHandler.php:156
Loading history...
164
            return null;
165
        }
166
167
        // Check for expired token
168
        $expiryDate = new \DateTime($rememberLoginHash->ExpiryDate);
169
        $now = DBDatetime::now();
170
        $now = new \DateTime($now->Rfc2822());
171
        if ($now > $expiryDate) {
172
            return null;
173
        }
174
175
        if ($this->cascadeInTo) {
176
            // @todo look at how to block "regular login" triggers from happening here
177
            // @todo deal with the fact that the Session::current_session() isn't correct here :-/
178
            $this->cascadeInTo->logIn($member, false, $request);
179
        }
180
181
        // @todo Consider whether response should be part of logIn() as well
182
183
        // Renew the token
184
        $rememberLoginHash->renew();
185
        $tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
186
        Cookie::set(
187
            $this->getTokenCookieName(),
188
            $member->ID . ':' . $rememberLoginHash->getToken(),
189
            $tokenExpiryDays,
190
            null,
191
            null,
192
            false,
193
            true
194
        );
195
196
        // Audit logging hook
197
        $member->extend('memberAutoLoggedIn');
198
199
        return $member;
200
    }
201
202
    /**
203
     * @param Member $member
204
     * @param bool $persistent
205
     * @param HTTPRequest $request
206
     */
207
    public function logIn(Member $member, $persistent = false, HTTPRequest $request = null)
208
    {
209
        // Cleans up any potential previous hash for this member on this device
210
        if ($alcDevice = Cookie::get($this->getDeviceCookieName())) {
211
            RememberLoginHash::get()->filter('DeviceID', $alcDevice)->removeAll();
212
        }
213
214
        // Set a cookie for persistent log-ins
215
        if ($persistent) {
216
            $rememberLoginHash = RememberLoginHash::generate($member);
217
            $tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
218
            $deviceExpiryDays = RememberLoginHash::config()->uninherited('device_expiry_days');
219
            $secure = $this->getTokenCookieSecure();
220
            Cookie::set(
221
                $this->getTokenCookieName(),
222
                $member->ID . ':' . $rememberLoginHash->getToken(),
223
                $tokenExpiryDays,
224
                null,
225
                null,
226
                $secure,
0 ignored issues
show
Bug introduced by
$secure of type string is incompatible with the type boolean expected by parameter $secure of SilverStripe\Control\Cookie::set(). ( Ignorable by Annotation )

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

226
                /** @scrutinizer ignore-type */ $secure,
Loading history...
227
                true
228
            );
229
            Cookie::set(
230
                $this->getDeviceCookieName(),
231
                $rememberLoginHash->DeviceID,
232
                $deviceExpiryDays,
233
                null,
234
                null,
235
                $secure,
236
                true
237
            );
238
        } else {
239
            // Clear a cookie for non-persistent log-ins
240
            $this->clearCookies();
241
        }
242
    }
243
244
    /**
245
     * @param HTTPRequest $request
246
     */
247
    public function logOut(HTTPRequest $request = null)
248
    {
249
        $member = Security::getCurrentUser();
250
        if ($member) {
0 ignored issues
show
introduced by
$member is of type SilverStripe\Security\Member, thus it always evaluated to true.
Loading history...
251
            RememberLoginHash::clear($member, Cookie::get($this->getDeviceCookieName()));
252
        }
253
        $this->clearCookies();
254
255
        if ($this->cascadeInTo) {
256
            $this->cascadeInTo->logOut($request);
257
        }
258
259
        Security::setCurrentUser(null);
260
    }
261
262
    /**
263
     * Clear the cookies set for the user
264
     */
265
    protected function clearCookies()
266
    {
267
        $secure = $this->getTokenCookieSecure();
268
        Cookie::set($this->getTokenCookieName(), null, null, null, null, $secure);
0 ignored issues
show
Bug introduced by
$secure of type string is incompatible with the type boolean expected by parameter $secure of SilverStripe\Control\Cookie::set(). ( Ignorable by Annotation )

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

268
        Cookie::set($this->getTokenCookieName(), null, null, null, null, /** @scrutinizer ignore-type */ $secure);
Loading history...
269
        Cookie::set($this->getDeviceCookieName(), null, null, null, null, $secure);
270
        Cookie::force_expiry($this->getTokenCookieName(), null, null, null, null, $secure);
0 ignored issues
show
Unused Code introduced by
The call to SilverStripe\Control\Cookie::force_expiry() has too many arguments starting with $secure. ( Ignorable by Annotation )

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

270
        Cookie::/** @scrutinizer ignore-call */ 
271
                force_expiry($this->getTokenCookieName(), null, null, null, null, $secure);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
271
        Cookie::force_expiry($this->getDeviceCookieName(), null, null, null, null, $secure);
272
    }
273
}
274