Completed
Pull Request — master (#7007)
by Simon
08:19
created

CookieAuthenticationHandler::authenticateRequest()   C

Complexity

Conditions 10
Paths 7

Size

Total Lines 70
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 39
nc 7
nop 1
dl 0
loc 70
rs 5.9999
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 IdentityStore
32
     */
33
    private $cascadeInTo;
34
35
    /**
36
     * Get the name of the cookie used to track this device
37
     *
38
     * @return string
39
     */
40
    public function getDeviceCookieName()
41
    {
42
        return $this->deviceCookieName;
43
    }
44
45
    /**
46
     * Set the name of the cookie used to track this device
47
     *
48
     * @param string $deviceCookieName
49
     * @return $this
50
     */
51
    public function setDeviceCookieName($deviceCookieName)
52
    {
53
        $this->deviceCookieName = $deviceCookieName;
54
        return $this;
55
    }
56
57
    /**
58
     * Get the name of the cookie used to store an login token
59
     *
60
     * @return string
61
     */
62
    public function getTokenCookieName()
63
    {
64
        return $this->tokenCookieName;
65
    }
66
67
    /**
68
     * Set the name of the cookie used to store an login token
69
     *
70
     * @param string $tokenCookieName
71
     * @return $this
72
     */
73
    public function setTokenCookieName($tokenCookieName)
74
    {
75
        $this->tokenCookieName = $tokenCookieName;
76
        return $this;
77
    }
78
79
    /**
80
     * Once a member is found by authenticateRequest() pass it to this identity store
81
     *
82
     * @return IdentityStore
83
     */
84
    public function getCascadeInTo()
85
    {
86
        return $this->cascadeInTo;
87
    }
88
89
    /**
90
     * Set the name of the cookie used to store an login token
91
     *
92
     * @param IdentityStore $cascadeInTo
93
     * @return $this
94
     */
95
    public function setCascadeInTo(IdentityStore $cascadeInTo)
96
    {
97
        $this->cascadeInTo = $cascadeInTo;
98
        return $this;
99
    }
100
101
    /**
102
     * @param HTTPRequest $request
103
     * @return Member
104
     */
105
    public function authenticateRequest(HTTPRequest $request)
106
    {
107
        $uidAndToken = Cookie::get($this->getTokenCookieName());
108
        $deviceID = Cookie::get($this->getDeviceCookieName());
109
110
        // @todo Consider better placement of database_is_ready test
111
        if ($deviceID === null || strpos($uidAndToken, ':') === false || !Security::database_is_ready()) {
112
            return null;
113
        }
114
115
        list($uid, $token) = explode(':', $uidAndToken, 2);
116
117
        if (!$uid || !$token) {
118
            return null;
119
        }
120
121
        // check if autologin token matches
122
        /** @var Member $member */
123
        $member = Member::get()->byID($uid);
124
        if (!$member) {
125
            return null;
126
        }
127
128
        $hash = $member->encryptWithUserSettings($token);
129
130
        /** @var RememberLoginHash $rememberLoginHash */
131
        $rememberLoginHash = RememberLoginHash::get()
132
            ->filter(array(
133
                'MemberID' => $member->ID,
134
                'DeviceID' => $deviceID,
135
                'Hash'     => $hash
136
            ))->first();
137
        if (!$rememberLoginHash) {
138
            return null;
139
        }
140
141
        // Check for expired token
142
        $expiryDate = new \DateTime($rememberLoginHash->ExpiryDate);
143
        $now = DBDatetime::now();
144
        $now = new \DateTime($now->Rfc2822());
145
        if ($now > $expiryDate) {
146
            return null;
147
        }
148
149
        if ($this->cascadeInTo) {
150
            // @todo look at how to block "regular login" triggers from happening here
151
            // @todo deal with the fact that the Session::current_session() isn't correct here :-/
152
            $this->cascadeInTo->logIn($member, false, $request);
153
        }
154
155
        // @todo Consider whether response should be part of logIn() as well
156
157
        // Renew the token
158
        $rememberLoginHash->renew();
159
        $tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
160
        Cookie::set(
161
            $this->getTokenCookieName(),
162
            $member->ID . ':' . $rememberLoginHash->getToken(),
163
            $tokenExpiryDays,
164
            null,
165
            null,
166
            false,
167
            true
168
        );
169
170
        // Audit logging hook
171
        $member->extend('memberAutoLoggedIn');
172
173
        return $member;
174
    }
175
176
    /**
177
     * @param Member $member
178
     * @param bool $persistent
179
     * @param HTTPRequest $request
180
     */
181
    public function logIn(Member $member, $persistent = false, HTTPRequest $request = null)
182
    {
183
        // Cleans up any potential previous hash for this member on this device
184
        if ($alcDevice = Cookie::get($this->getDeviceCookieName())) {
185
            RememberLoginHash::get()->filter('DeviceID', $alcDevice)->removeAll();
186
        }
187
188
        // Set a cookie for persistent log-ins
189
        if ($persistent) {
190
            $rememberLoginHash = RememberLoginHash::generate($member);
191
            $tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
192
            $deviceExpiryDays = RememberLoginHash::config()->uninherited('device_expiry_days');
193
            Cookie::set(
194
                $this->getTokenCookieName(),
195
                $member->ID . ':' . $rememberLoginHash->getToken(),
196
                $tokenExpiryDays,
197
                null,
198
                null,
199
                null,
200
                true
201
            );
202
            Cookie::set(
203
                $this->getDeviceCookieName(),
204
                $rememberLoginHash->DeviceID,
205
                $deviceExpiryDays,
206
                null,
207
                null,
208
                null,
209
                true
210
            );
211
        } else {
212
            // Clear a cookie for non-persistent log-ins
213
            $this->clearCookies();
214
        }
215
    }
216
217
    /**
218
     * @param HTTPRequest $request
219
     */
220
    public function logOut(HTTPRequest $request = null)
221
    {
222
        $member = Security::getCurrentUser();
223
        if ($member) {
224
            RememberLoginHash::clear($member, Cookie::get('alc_device'));
225
        }
226
        $this->clearCookies();
227
228
        if ($this->cascadeInTo) {
229
            $this->cascadeInTo->logOut($request);
230
        }
231
232
        Security::setCurrentUser(null);
233
    }
234
235
    /**
236
     * Clear the cookies set for the user
237
     */
238
    protected function clearCookies()
239
    {
240
        Cookie::set($this->getTokenCookieName(), null);
241
        Cookie::set($this->getDeviceCookieName(), null);
242
        Cookie::force_expiry($this->getTokenCookieName());
243
        Cookie::force_expiry($this->getDeviceCookieName());
244
    }
245
}
246