Completed
Push — master ( 55a2d1...8b4cce )
by Daniel
18:24
created

SecurityTest   C

Complexity

Total Complexity 38

Size/Duplication

Total Lines 738
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 19

Importance

Changes 0
Metric Value
dl 0
loc 738
rs 5.775
c 0
b 0
f 0
wmc 38
lcom 2
cbo 19

24 Methods

Rating   Name   Duplication   Size   Complexity  
B setUp() 0 24 2
A tearDown() 0 19 3
A testAccessingAuthenticatedPageRedirectsToLoginForm() 0 18 1
A testPermissionFailureSetsCorrectFormMessages() 0 58 1
A getRecursive() 0 9 4
B testAutomaticRedirectionOnLogin() 0 43 2
B testLogInAsSomeoneElse() 0 34 1
A testMemberIDInSessionDoesntExistInDatabaseHasToLogin() 0 20 1
B testLoginUsernamePersists() 0 30 1
A testExternalBackUrlRedirectionDisallowed() 0 48 1
B testExpiredPassword() 0 38 1
A testChangePasswordForLoggedInUsers() 0 23 1
B testChangePasswordFromLostPassword() 0 41 1
B testAlternatingRepeatedLoginAttempts() 0 29 1
A testSuccessfulLoginAttempts() 0 23 1
A testDatabaseIsReadyWithInsufficientMemberColumns() 0 23 1
B testRepeatedLoginAttemptsLockingPeopleOut() 0 84 5
B testUnsuccessfulLoginAttempts() 0 36 1
A testSecurityControllerSendsRobotsTagHeader() 0 7 1
A testDoNotSendEmptyRobotsHeaderIfNotDefined() 0 7 1
A doTestLoginForm() 0 17 1
A doTestChangepasswordForm() 0 13 1
A assertHasMessage() 0 12 3
A getValidationResult() 0 11 2
1
<?php
2
3
namespace SilverStripe\Security\Tests;
4
5
use SilverStripe\ORM\DataObject;
6
use SilverStripe\ORM\FieldType\DBDatetime;
7
use SilverStripe\ORM\FieldType\DBClassName;
8
use SilverStripe\ORM\DB;
9
use SilverStripe\ORM\ValidationResult;
10
use SilverStripe\Security\Authenticator;
11
use SilverStripe\Security\LoginAttempt;
12
use SilverStripe\Security\Member;
13
use SilverStripe\Security\MemberAuthenticator;
14
use SilverStripe\Security\Security;
15
use SilverStripe\Security\Permission;
16
use SilverStripe\Core\Config\Config;
17
use SilverStripe\Core\Convert;
18
use SilverStripe\Dev\FunctionalTest;
19
use SilverStripe\Dev\TestOnly;
20
use SilverStripe\Control\HTTPResponse;
21
use SilverStripe\Control\Session;
22
use SilverStripe\Control\Director;
23
use SilverStripe\Control\Controller;
24
use SilverStripe\i18n\i18n;
25
26
/**
27
 * Test the security class, including log-in form, change password form, etc
28
 */
29
class SecurityTest extends FunctionalTest
30
{
31
    protected static $fixture_file = 'MemberTest.yml';
32
33
    protected $autoFollowRedirection = false;
34
35
    protected $priorAuthenticators = array();
36
37
    protected $priorDefaultAuthenticator = null;
38
39
    protected $priorUniqueIdentifierField = null;
40
41
    protected $priorRememberUsername = null;
42
43
    protected $extraControllers = [
44
        SecurityTest\NullController::class,
45
        SecurityTest\SecuredController::class,
46
    ];
47
48
    public function setUp()
49
    {
50
        // This test assumes that MemberAuthenticator is present and the default
51
        $this->priorAuthenticators = Authenticator::get_authenticators();
52
        $this->priorDefaultAuthenticator = Authenticator::get_default_authenticator();
53
        foreach ($this->priorAuthenticators as $authenticator) {
54
            Authenticator::unregister($authenticator);
55
        }
56
57
        Authenticator::register(MemberAuthenticator::class);
58
        Authenticator::set_default_authenticator(MemberAuthenticator::class);
59
60
        // And that the unique identified field is 'Email'
61
        $this->priorUniqueIdentifierField = Member::config()->unique_identifier_field;
0 ignored issues
show
Documentation introduced by
The property unique_identifier_field does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
62
        $this->priorRememberUsername = Security::config()->remember_username;
0 ignored issues
show
Documentation introduced by
The property remember_username does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
63
        /**
64
 * @skipUpgrade
65
*/
66
        Member::config()->unique_identifier_field = 'Email';
0 ignored issues
show
Documentation introduced by
The property unique_identifier_field does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
67
68
        parent::setUp();
69
70
        Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', '/');
71
    }
72
73
    public function tearDown()
74
    {
75
        // Restore selected authenticator
76
77
        // MemberAuthenticator might not actually be present
78
        if (!in_array(MemberAuthenticator::class, $this->priorAuthenticators)) {
79
            Authenticator::unregister(MemberAuthenticator::class);
80
        }
81
        foreach ($this->priorAuthenticators as $authenticator) {
82
            Authenticator::register($authenticator);
83
        }
84
        Authenticator::set_default_authenticator($this->priorDefaultAuthenticator);
85
86
        // Restore unique identifier field
87
        Member::config()->unique_identifier_field = $this->priorUniqueIdentifierField;
0 ignored issues
show
Documentation introduced by
The property unique_identifier_field does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
88
        Security::config()->remember_username = $this->priorRememberUsername;
0 ignored issues
show
Documentation introduced by
The property remember_username does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
89
90
        parent::tearDown();
91
    }
92
93
    public function testAccessingAuthenticatedPageRedirectsToLoginForm()
94
    {
95
        $this->autoFollowRedirection = false;
96
97
        $response = $this->get('SecurityTest_SecuredController');
98
        $this->assertEquals(302, $response->getStatusCode());
99
        $this->assertContains(
100
            Config::inst()->get(Security::class, 'login_url'),
101
            $response->getHeader('Location')
102
        );
103
104
        $this->logInWithPermission('ADMIN');
105
        $response = $this->get('SecurityTest_SecuredController');
106
        $this->assertEquals(200, $response->getStatusCode());
107
        $this->assertContains('Success', $response->getBody());
108
109
        $this->autoFollowRedirection = true;
110
    }
111
112
    public function testPermissionFailureSetsCorrectFormMessages()
113
    {
114
        Config::nest();
115
116
        // Controller that doesn't attempt redirections
117
        $controller = new SecurityTest\NullController();
118
        $controller->setResponse(new HTTPResponse());
119
120
        Security::permissionFailure($controller, array('default' => 'Oops, not allowed'));
121
        $this->assertEquals('Oops, not allowed', Session::get('Security.Message.message'));
122
123
        // Test that config values are used correctly
124
        Config::inst()->update(Security::class, 'default_message_set', 'stringvalue');
125
        Security::permissionFailure($controller);
126
        $this->assertEquals(
127
            'stringvalue',
128
            Session::get('Security.Message.message'),
129
            'Default permission failure message value was not present'
130
        );
131
132
        Config::inst()->remove(Security::class, 'default_message_set');
133
        Config::inst()->update(Security::class, 'default_message_set', array('default' => 'arrayvalue'));
134
        Security::permissionFailure($controller);
135
        $this->assertEquals(
136
            'arrayvalue',
137
            Session::get('Security.Message.message'),
138
            'Default permission failure message value was not present'
139
        );
140
141
        // Test that non-default messages work.
142
        // NOTE: we inspect the response body here as the session message has already
143
        // been fetched and output as part of it, so has been removed from the session
144
        $this->logInWithPermission('EDITOR');
145
146
        Config::inst()->update(
147
            Security::class,
148
            'default_message_set',
149
            array('default' => 'default', 'alreadyLoggedIn' => 'You are already logged in!')
150
        );
151
        Security::permissionFailure($controller);
152
        $this->assertContains(
153
            'You are already logged in!',
154
            $controller->getResponse()->getBody(),
155
            'Custom permission failure message was ignored'
156
        );
157
158
        Security::permissionFailure(
159
            $controller,
160
            array('default' => 'default', 'alreadyLoggedIn' => 'One-off failure message')
161
        );
162
        $this->assertContains(
163
            'One-off failure message',
164
            $controller->getResponse()->getBody(),
165
            "Message set passed to Security::permissionFailure() didn't override Config values"
166
        );
167
168
        Config::unnest();
169
    }
170
171
    /**
172
     * Follow all redirects recursively
173
     *
174
     * @param  string $url
175
     * @param  int    $limit Max number of requests
176
     * @return HTTPResponse
177
     */
178
    protected function getRecursive($url, $limit = 10)
179
    {
180
        $this->cssParser = null;
181
        $response = $this->mainSession->get($url);
182
        while (--$limit > 0 && $response instanceof HTTPResponse && $response->getHeader('Location')) {
183
            $response = $this->mainSession->followRedirection();
184
        }
185
        return $response;
186
    }
187
188
    public function testAutomaticRedirectionOnLogin()
189
    {
190
        // BackURL with permission error (not authenticated) should not redirect
191
        if ($member = Member::currentUser()) {
192
            $member->logOut();
193
        }
194
        $response = $this->getRecursive('SecurityTest_SecuredController');
195
        $this->assertContains(Convert::raw2xml("That page is secured."), $response->getBody());
196
        $this->assertContains('<input type="submit" name="action_dologin"', $response->getBody());
197
198
        // Non-logged in user should not be redirected, but instead shown the login form
199
        // No message/context is available as the user has not attempted to view the secured controller
200
        $response = $this->getRecursive('Security/login?BackURL=SecurityTest_SecuredController/');
201
        $this->assertNotContains(Convert::raw2xml("That page is secured."), $response->getBody());
202
        $this->assertNotContains(Convert::raw2xml("You don't have access to this page"), $response->getBody());
203
        $this->assertContains('<input type="submit" name="action_dologin"', $response->getBody());
204
205
        // BackURL with permission error (wrong permissions) should not redirect
206
        $this->logInAs('grouplessmember');
207
        $response = $this->getRecursive('SecurityTest_SecuredController');
208
        $this->assertContains(Convert::raw2xml("You don't have access to this page"), $response->getBody());
209
        $this->assertContains(
210
            '<input type="submit" name="action_logout" value="Log in as someone else"',
211
            $response->getBody()
212
        );
213
214
        // Directly accessing this page should attempt to follow the BackURL, but stop when it encounters the error
215
        $response = $this->getRecursive('Security/login?BackURL=SecurityTest_SecuredController/');
216
        $this->assertContains(Convert::raw2xml("You don't have access to this page"), $response->getBody());
217
        $this->assertContains(
218
            '<input type="submit" name="action_logout" value="Log in as someone else"',
219
            $response->getBody()
220
        );
221
222
        // Check correctly logged in admin doesn't generate the same errors
223
        $this->logInAs('admin');
224
        $response = $this->getRecursive('SecurityTest_SecuredController');
225
        $this->assertContains(Convert::raw2xml("Success"), $response->getBody());
226
227
        // Directly accessing this page should attempt to follow the BackURL and succeed
228
        $response = $this->getRecursive('Security/login?BackURL=SecurityTest_SecuredController/');
229
        $this->assertContains(Convert::raw2xml("Success"), $response->getBody());
230
    }
231
232
    public function testLogInAsSomeoneElse()
233
    {
234
        $member = DataObject::get_one(Member::class);
235
236
        /* Log in with any user that we can find */
237
        $this->session()->inst_set('loggedInAs', $member->ID);
238
239
        /* View the Security/login page */
240
        $response = $this->get(Config::inst()->get(Security::class, 'login_url'));
241
242
        $items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.action');
243
244
        /* We have only 1 input, one to allow the user to log in as someone else */
245
        $this->assertEquals(count($items), 1, 'There is 1 input, allowing the user to log in as someone else.');
246
247
        $this->autoFollowRedirection = true;
248
249
        /* Submit the form, using only the logout action and a hidden field for the authenticator */
250
        $response = $this->submitForm(
251
            'MemberLoginForm_LoginForm',
252
            null,
253
            array(
254
                'AuthenticationMethod' => MemberAuthenticator::class,
255
                'action_dologout' => 1,
256
            )
257
        );
258
259
        /* We get a good response */
260
        $this->assertEquals($response->getStatusCode(), 200, 'We have a 200 OK response');
261
        $this->assertNotNull($response->getBody(), 'There is body content on the page');
262
263
        /* Log the user out */
264
        $this->session()->inst_set('loggedInAs', null);
265
    }
266
267
    public function testMemberIDInSessionDoesntExistInDatabaseHasToLogin()
268
    {
269
        /* Log in with a Member ID that doesn't exist in the DB */
270
        $this->session()->inst_set('loggedInAs', 500);
271
272
        $this->autoFollowRedirection = true;
273
274
        /* Attempt to get into the admin section */
275
        $response = $this->get(Config::inst()->get(Security::class, 'login_url'));
276
277
        $items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.text');
278
279
        /* We have 2 text inputs - one for email, and another for the password */
280
        $this->assertEquals(count($items), 2, 'There are 2 inputs - one for email, another for password');
281
282
        $this->autoFollowRedirection = false;
283
284
        /* Log the user out */
285
        $this->session()->inst_set('loggedInAs', null);
286
    }
287
288
    public function testLoginUsernamePersists()
289
    {
290
        // Test that username does not persist
291
        $this->session()->inst_set('SessionForms.MemberLoginForm.Email', '[email protected]');
292
        Security::config()->remember_username = false;
0 ignored issues
show
Documentation introduced by
The property remember_username does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
293
        $this->get(Config::inst()->get(Security::class, 'login_url'));
294
        $items = $this
295
            ->cssParser()
296
            ->getBySelector('#MemberLoginForm_LoginForm #MemberLoginForm_LoginForm_Email');
297
        $this->assertEquals(1, count($items));
298
        $this->assertEmpty((string)$items[0]->attributes()->value);
299
        $this->assertEquals('off', (string)$items[0]->attributes()->autocomplete);
300
        $form = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm');
301
        $this->assertEquals(1, count($form));
302
        $this->assertEquals('off', (string)$form[0]->attributes()->autocomplete);
303
304
        // Test that username does persist when necessary
305
        $this->session()->inst_set('SessionForms.MemberLoginForm.Email', '[email protected]');
306
        Security::config()->remember_username = true;
0 ignored issues
show
Documentation introduced by
The property remember_username does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
307
        $this->get(Config::inst()->get(Security::class, 'login_url'));
308
        $items = $this
309
            ->cssParser()
310
            ->getBySelector('#MemberLoginForm_LoginForm #MemberLoginForm_LoginForm_Email');
311
        $this->assertEquals(1, count($items));
312
        $this->assertEquals('[email protected]', (string)$items[0]->attributes()->value);
313
        $this->assertNotEquals('off', (string)$items[0]->attributes()->autocomplete);
314
        $form = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm');
315
        $this->assertEquals(1, count($form));
316
        $this->assertNotEquals('off', (string)$form[0]->attributes()->autocomplete);
317
    }
318
319
    public function testExternalBackUrlRedirectionDisallowed()
320
    {
321
        // Test internal relative redirect
322
        $response = $this->doTestLoginForm('[email protected]', '1nitialPassword', 'testpage');
323
        $this->assertEquals(302, $response->getStatusCode());
324
        $this->assertRegExp(
325
            '/testpage/',
326
            $response->getHeader('Location'),
327
            "Internal relative BackURLs work when passed through to login form"
328
        );
329
        // Log the user out
330
        $this->session()->inst_set('loggedInAs', null);
331
332
        // Test internal absolute redirect
333
        $response = $this->doTestLoginForm(
334
            '[email protected]',
335
            '1nitialPassword',
336
            Director::absoluteBaseURL() . 'testpage'
337
        );
338
        // for some reason the redirect happens to a relative URL
339
        $this->assertRegExp(
340
            '/^' . preg_quote(Director::absoluteBaseURL(), '/') . 'testpage/',
341
            $response->getHeader('Location'),
342
            "Internal absolute BackURLs work when passed through to login form"
343
        );
344
        // Log the user out
345
        $this->session()->inst_set('loggedInAs', null);
346
347
        // Test external redirect
348
        $response = $this->doTestLoginForm('[email protected]', '1nitialPassword', 'http://myspoofedhost.com');
349
        $this->assertNotRegExp(
350
            '/^' . preg_quote('http://myspoofedhost.com', '/') . '/',
351
            (string)$response->getHeader('Location'),
352
            "Redirection to external links in login form BackURL gets prevented as a measure against spoofing attacks"
353
        );
354
355
        // Test external redirection on ChangePasswordForm
356
        $this->get('Security/changepassword?BackURL=http://myspoofedhost.com');
357
        $changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
358
        $this->assertNotRegExp(
359
            '/^' . preg_quote('http://myspoofedhost.com', '/') . '/',
360
            (string)$changedResponse->getHeader('Location'),
361
            "Redirection to external links in change password form BackURL gets prevented to stop spoofing attacks"
362
        );
363
364
        // Log the user out
365
        $this->session()->inst_set('loggedInAs', null);
366
    }
367
368
    /**
369
     * Test that the login form redirects to the change password form after logging in with an expired password
370
     */
371
    public function testExpiredPassword()
372
    {
373
        /* BAD PASSWORDS ARE LOCKED OUT */
374
        $badResponse = $this->doTestLoginForm('[email protected]', 'badpassword');
375
        $this->assertEquals(302, $badResponse->getStatusCode());
376
        $this->assertRegExp('/Security\/login/', $badResponse->getHeader('Location'));
377
        $this->assertNull($this->session()->inst_get('loggedInAs'));
378
379
        /* UNEXPIRED PASSWORD GO THROUGH WITHOUT A HITCH */
380
        $goodResponse = $this->doTestLoginForm('[email protected]', '1nitialPassword');
381
        $this->assertEquals(302, $goodResponse->getStatusCode());
382
        $this->assertEquals(
383
            Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
384
            $goodResponse->getHeader('Location')
385
        );
386
        $this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
387
388
        /* EXPIRED PASSWORDS ARE SENT TO THE CHANGE PASSWORD FORM */
389
        $expiredResponse = $this->doTestLoginForm('[email protected]', '1nitialPassword');
390
        $this->assertEquals(302, $expiredResponse->getStatusCode());
391
        $this->assertEquals(
392
            '/Security/changepassword',
393
            $expiredResponse->getHeader('Location')
394
        );
395
        $this->assertEquals(
396
            $this->idFromFixture(Member::class, 'expiredpassword'),
397
            $this->session()->inst_get('loggedInAs')
398
        );
399
400
        // Make sure it redirects correctly after the password has been changed
401
        $this->mainSession->followRedirection();
402
        $changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
403
        $this->assertEquals(302, $changedResponse->getStatusCode());
404
        $this->assertEquals(
405
            Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
406
            $changedResponse->getHeader('Location')
407
        );
408
    }
409
410
    public function testChangePasswordForLoggedInUsers()
411
    {
412
        $goodResponse = $this->doTestLoginForm('[email protected]', '1nitialPassword');
413
414
        // Change the password
415
        $this->get('Security/changepassword?BackURL=test/back');
416
        $changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
417
        $this->assertEquals(302, $changedResponse->getStatusCode());
418
        $this->assertEquals(
419
            Controller::join_links(Director::absoluteBaseURL(), 'test/back'),
420
            $changedResponse->getHeader('Location')
421
        );
422
        $this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
423
424
        // Check if we can login with the new password
425
        $goodResponse = $this->doTestLoginForm('[email protected]', 'changedPassword');
426
        $this->assertEquals(302, $goodResponse->getStatusCode());
427
        $this->assertEquals(
428
            Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
429
            $goodResponse->getHeader('Location')
430
        );
431
        $this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
432
    }
433
434
    public function testChangePasswordFromLostPassword()
435
    {
436
        $admin = $this->objFromFixture(Member::class, 'test');
437
        $admin->FailedLoginCount = 99;
0 ignored issues
show
Documentation introduced by
The property FailedLoginCount does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
438
        $admin->LockedOutUntil = DBDatetime::now()->Format('Y-m-d H:i:s');
0 ignored issues
show
Documentation introduced by
The property LockedOutUntil does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
439
        $admin->write();
440
441
        $this->assertNull($admin->AutoLoginHash, 'Hash is empty before lost password');
442
443
        // Request new password by email
444
        $response = $this->get('Security/lostpassword');
445
        $response = $this->post('Security/LostPasswordForm', array('Email' => '[email protected]'));
446
447
        $this->assertEmailSent('[email protected]');
448
449
        // Load password link from email
450
        $admin = DataObject::get_by_id(Member::class, $admin->ID);
451
        $this->assertNotNull($admin->AutoLoginHash, 'Hash has been written after lost password');
452
453
        // We don't have access to the token - generate a new token and hash pair.
454
        $token = $admin->generateAutologinTokenAndStoreHash();
455
456
        // Check.
457
        $response = $this->get('Security/changepassword/?m='.$admin->ID.'&t=' . $token);
458
        $this->assertEquals(302, $response->getStatusCode());
459
        $this->assertEquals(Director::baseUrl() . 'Security/changepassword', $response->getHeader('Location'));
460
461
        // Follow redirection to form without hash in GET parameter
462
        $response = $this->get('Security/changepassword');
463
        $changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
464
        $this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
465
466
        // Check if we can login with the new password
467
        $goodResponse = $this->doTestLoginForm('[email protected]', 'changedPassword');
468
        $this->assertEquals(302, $goodResponse->getStatusCode());
469
        $this->assertEquals($this->idFromFixture(Member::class, 'test'), $this->session()->inst_get('loggedInAs'));
470
471
        $admin = DataObject::get_by_id(Member::class, $admin->ID, false);
472
        $this->assertNull($admin->LockedOutUntil);
473
        $this->assertEquals(0, $admin->FailedLoginCount);
474
    }
475
476
    public function testRepeatedLoginAttemptsLockingPeopleOut()
477
    {
478
        $local = i18n::get_locale();
479
        i18n::set_locale('en_US');
480
481
        Member::config()->lock_out_after_incorrect_logins = 5;
0 ignored issues
show
Documentation introduced by
The property lock_out_after_incorrect_logins does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
482
        Member::config()->lock_out_delay_mins = 15;
0 ignored issues
show
Documentation introduced by
The property lock_out_delay_mins does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
483
484
        // Login with a wrong password for more than the defined threshold
485
        for ($i = 1; $i <= Member::config()->lock_out_after_incorrect_logins+1; $i++) {
486
            $this->doTestLoginForm('[email protected]', 'incorrectpassword');
487
            $member = DataObject::get_by_id(Member::class, $this->idFromFixture(Member::class, 'test'));
488
489
            if ($i < Member::config()->lock_out_after_incorrect_logins) {
490
                $this->assertNull(
491
                    $member->LockedOutUntil,
492
                    'User does not have a lockout time set if under threshold for failed attempts'
493
                );
494
                $this->assertHasMessage(
495
                    _t(
496
                        'Member.ERRORWRONGCRED',
497
                        'The provided details don\'t seem to be correct. Please try again.'
498
                    )
499
                );
500
            } else {
501
                // Fuzzy matching for time to avoid side effects from slow running tests
502
                $this->assertGreaterThan(
503
                    time() + 14*60,
504
                    strtotime($member->LockedOutUntil),
505
                    'User has a lockout time set after too many failed attempts'
506
                );
507
            }
508
509
            $msg = _t(
510
                'Member.ERRORLOCKEDOUT2',
511
                'Your account has been temporarily disabled because of too many failed attempts at ' .
512
                'logging in. Please try again in {count} minutes.',
513
                null,
514
                array('count' => Member::config()->lock_out_delay_mins)
515
            );
516
            if ($i > Member::config()->lock_out_after_incorrect_logins) {
517
                $this->assertHasMessage($msg);
518
            }
519
        }
520
521
        $this->doTestLoginForm('[email protected]', '1nitialPassword');
522
        $this->assertNull(
523
            $this->session()->inst_get('loggedInAs'),
524
            'The user can\'t log in after being locked out, even with the right password'
525
        );
526
527
        // (We fake this by re-setting LockedOutUntil)
528
        $member = DataObject::get_by_id(Member::class, $this->idFromFixture(Member::class, 'test'));
529
        $member->LockedOutUntil = date('Y-m-d H:i:s', time() - 30);
0 ignored issues
show
Documentation introduced by
The property LockedOutUntil does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
530
        $member->write();
531
        $this->doTestLoginForm('[email protected]', '1nitialPassword');
532
        $this->assertEquals(
533
            $this->session()->inst_get('loggedInAs'),
534
            $member->ID,
535
            'After lockout expires, the user can login again'
536
        );
537
538
        // Log the user out
539
        $this->session()->inst_set('loggedInAs', null);
540
541
        // Login again with wrong password, but less attempts than threshold
542
        for ($i = 1; $i < Member::config()->lock_out_after_incorrect_logins; $i++) {
543
            $this->doTestLoginForm('[email protected]', 'incorrectpassword');
544
        }
545
        $this->assertNull($this->session()->inst_get('loggedInAs'));
546
        $this->assertHasMessage(
547
            _t('Member.ERRORWRONGCRED', 'The provided details don\'t seem to be correct. Please try again.'),
548
            'The user can retry with a wrong password after the lockout expires'
549
        );
550
551
        $this->doTestLoginForm('[email protected]', '1nitialPassword');
552
        $this->assertEquals(
553
            $this->session()->inst_get('loggedInAs'),
554
            $member->ID,
555
            'The user can login successfully after lockout expires, if staying below the threshold'
556
        );
557
558
        i18n::set_locale($local);
559
    }
560
561
    public function testAlternatingRepeatedLoginAttempts()
562
    {
563
        Member::config()->lock_out_after_incorrect_logins = 3;
0 ignored issues
show
Documentation introduced by
The property lock_out_after_incorrect_logins does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
564
565
        // ATTEMPTING LOG-IN TWICE WITH ONE ACCOUNT AND TWICE WITH ANOTHER SHOULDN'T LOCK ANYBODY OUT
566
567
        $this->doTestLoginForm('[email protected]', 'incorrectpassword');
568
        $this->doTestLoginForm('[email protected]', 'incorrectpassword');
569
570
        $this->doTestLoginForm('[email protected]', 'incorrectpassword');
571
        $this->doTestLoginForm('[email protected]', 'incorrectpassword');
572
573
        $member1 = DataObject::get_by_id(Member::class, $this->idFromFixture(Member::class, 'test'));
574
        $member2 = DataObject::get_by_id(Member::class, $this->idFromFixture(Member::class, 'noexpiry'));
575
576
        $this->assertNull($member1->LockedOutUntil);
577
        $this->assertNull($member2->LockedOutUntil);
578
579
        // BUT, DOING AN ADDITIONAL LOG-IN WITH EITHER OF THEM WILL LOCK OUT, SINCE THAT IS THE 3RD FAILURE IN
580
        // THIS SESSION
581
582
        $this->doTestLoginForm('[email protected]', 'incorrectpassword');
583
        $member1 = DataObject::get_by_id(Member::class, $this->idFromFixture(Member::class, 'test'));
584
        $this->assertNotNull($member1->LockedOutUntil);
585
586
        $this->doTestLoginForm('[email protected]', 'incorrectpassword');
587
        $member2 = DataObject::get_by_id(Member::class, $this->idFromFixture(Member::class, 'noexpiry'));
588
        $this->assertNotNull($member2->LockedOutUntil);
589
    }
590
591
    public function testUnsuccessfulLoginAttempts()
592
    {
593
        Security::config()->login_recording = true;
0 ignored issues
show
Documentation introduced by
The property login_recording does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
594
595
        /* UNSUCCESSFUL ATTEMPTS WITH WRONG PASSWORD FOR EXISTING USER ARE LOGGED */
596
        $this->doTestLoginForm('[email protected]', 'wrongpassword');
597
        $attempt = DataObject::get_one(
598
            LoginAttempt::class,
599
            array(
600
            '"LoginAttempt"."Email"' => '[email protected]'
601
            )
602
        );
603
        $this->assertTrue(is_object($attempt));
604
        $member = DataObject::get_one(
605
            Member::class,
606
            array(
607
            '"Member"."Email"' => '[email protected]'
608
            )
609
        );
610
        $this->assertEquals($attempt->Status, 'Failure');
611
        $this->assertEquals($attempt->Email, '[email protected]');
612
        $this->assertEquals($attempt->Member()->toMap(), $member->toMap());
613
614
        /* UNSUCCESSFUL ATTEMPTS WITH NONEXISTING USER ARE LOGGED */
615
        $this->doTestLoginForm('[email protected]', 'wrongpassword');
616
        $attempt = DataObject::get_one(
617
            LoginAttempt::class,
618
            array(
619
            '"LoginAttempt"."Email"' => '[email protected]'
620
            )
621
        );
622
        $this->assertTrue(is_object($attempt));
623
        $this->assertEquals($attempt->Status, 'Failure');
624
        $this->assertEquals($attempt->Email, '[email protected]');
625
        $this->assertNotEmpty($this->getValidationResult()->getMessages(), 'An invalid email returns a message.');
626
    }
627
628
    public function testSuccessfulLoginAttempts()
629
    {
630
        Security::config()->login_recording = true;
0 ignored issues
show
Documentation introduced by
The property login_recording does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
631
632
        /* SUCCESSFUL ATTEMPTS ARE LOGGED */
633
        $this->doTestLoginForm('[email protected]', '1nitialPassword');
634
        $attempt = DataObject::get_one(
635
            LoginAttempt::class,
636
            array(
637
            '"LoginAttempt"."Email"' => '[email protected]'
638
            )
639
        );
640
        $member = DataObject::get_one(
641
            Member::class,
642
            array(
643
            '"Member"."Email"' => '[email protected]'
644
            )
645
        );
646
        $this->assertTrue(is_object($attempt));
647
        $this->assertEquals($attempt->Status, 'Success');
648
        $this->assertEquals($attempt->Email, '[email protected]');
649
        $this->assertEquals($attempt->Member()->toMap(), $member->toMap());
650
    }
651
652
    public function testDatabaseIsReadyWithInsufficientMemberColumns()
653
    {
654
        $old = Security::$force_database_is_ready;
0 ignored issues
show
Documentation introduced by
The property $force_database_is_ready is declared private in SilverStripe\Security\Security. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
655
        Security::$force_database_is_ready = null;
0 ignored issues
show
Documentation introduced by
The property $force_database_is_ready is declared private in SilverStripe\Security\Security. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
656
        Security::$database_is_ready = false;
0 ignored issues
show
Documentation introduced by
The property $database_is_ready is declared private in SilverStripe\Security\Security. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
657
        DBClassName::clear_classname_cache();
658
659
        // Assumption: The database has been built correctly by the test runner,
660
        // and has all columns present in the ORM
661
        /**
662
 * @skipUpgrade
663
*/
664
        DB::get_schema()->renameField('Member', 'Email', 'Email_renamed');
665
666
        // Email column is now missing, which means we're not ready to do permission checks
667
        $this->assertFalse(Security::database_is_ready());
668
669
        // Rebuild the database (which re-adds the Email column), and try again
670
        $this->resetDBSchema(true);
671
        $this->assertTrue(Security::database_is_ready());
672
673
        Security::$force_database_is_ready = $old;
0 ignored issues
show
Documentation introduced by
The property $force_database_is_ready is declared private in SilverStripe\Security\Security. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
674
    }
675
676
    public function testSecurityControllerSendsRobotsTagHeader()
677
    {
678
        $response = $this->get(Config::inst()->get(Security::class, 'login_url'));
679
        $robotsHeader = $response->getHeader('X-Robots-Tag');
680
        $this->assertNotNull($robotsHeader);
681
        $this->assertContains('noindex', $robotsHeader);
682
    }
683
684
    public function testDoNotSendEmptyRobotsHeaderIfNotDefined()
685
    {
686
        Config::inst()->remove(Security::class, 'robots_tag');
687
        $response = $this->get(Config::inst()->get(Security::class, 'login_url'));
688
        $robotsHeader = $response->getHeader('X-Robots-Tag');
689
        $this->assertNull($robotsHeader);
690
    }
691
692
    /**
693
     * Execute a log-in form using Director::test().
694
     * Helper method for the tests above
695
     */
696
    public function doTestLoginForm($email, $password, $backURL = 'test/link')
697
    {
698
        $this->get(Config::inst()->get(Security::class, 'logout_url'));
699
        $this->session()->inst_set('BackURL', $backURL);
700
        $this->get(Config::inst()->get(Security::class, 'login_url'));
701
702
        return $this->submitForm(
703
            "MemberLoginForm_LoginForm",
704
            null,
705
            array(
706
                'Email' => $email,
707
                'Password' => $password,
708
                'AuthenticationMethod' => MemberAuthenticator::class,
709
                'action_dologin' => 1,
710
            )
711
        );
712
    }
713
714
    /**
715
     * Helper method to execute a change password form
716
     */
717
    public function doTestChangepasswordForm($oldPassword, $newPassword)
718
    {
719
        return $this->submitForm(
720
            "ChangePasswordForm_ChangePasswordForm",
721
            null,
722
            array(
723
                'OldPassword' => $oldPassword,
724
                'NewPassword1' => $newPassword,
725
                'NewPassword2' => $newPassword,
726
                'action_doChangePassword' => 1,
727
            )
728
        );
729
    }
730
731
    /**
732
     * Assert this message is in the current login form errors
733
     *
734
     * @param string $expected
735
     * @param string $errorMessage
736
     */
737
    protected function assertHasMessage($expected, $errorMessage = null)
738
    {
739
        $messages = [];
740
        $result = $this->getValidationResult();
741
        if ($result) {
742
            foreach ($result->getMessages() as $message) {
743
                $messages[] = $message['message'];
744
            }
745
        }
746
747
        $this->assertContains($expected, $messages, $errorMessage);
748
    }
749
750
    /**
751
     * Get validation result from last login form submission
752
     *
753
     * @return ValidationResult
754
     */
755
    protected function getValidationResult()
756
    {
757
        $result = $this->session()->inst_get('FormInfo.MemberLoginForm_LoginForm.result');
758
        if ($result) {
759
            /**
760
 * @var ValidationResult $resultObj
761
*/
762
            return unserialize($result);
763
        }
764
        return null;
765
    }
766
}
767