Skip to content

Commit 0d93792

Browse files
authored
Fix #952 - Session not working with database handler
* Added test for Issue #952 * Providing temps class to fix Laravel version * Update Changelog * Update userfrosting/session version
1 parent ae83bd9 commit 0d93792

12 files changed

Lines changed: 508 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [v4.2.1]
99

10+
### Added
11+
- `UserFrosting\Sprinkle\Core\Database\Models\Session` model for the `sessions` db table.
12+
- `TEST_SESSION_HANDLER` environment variable to set the session save handler to use for Testing.
13+
- `withDatabaseSessionHandler` Trait for testing. Use `$this->useDatabaseSessionHandler()` to use database session handler in tests.
14+
1015
### Fixed
1116
- Italian translation ([#950])
1217
- User Registration failing when trying to register two accounts with the same email address ([#953])
1318
- Bad test case for `CoreController::getAsset`.
1419
- User Model `forceDelete` doesn't remove the record from the DB ([#951])
1520
- Fix PHP Fatal error that can be thrown when registering a new User
21+
- Session not working with database handler ([#952])
1622

1723
## [v4.2.0]
1824
### Changed Requirements
@@ -715,6 +721,7 @@ See [http://learn.userfrosting.com/upgrading/40-to-41](Upgrading 4.0.x to 4.1.x
715721
[#940]: https://github.com/userfrosting/UserFrosting/issues/940
716722
[#950]: https://github.com/userfrosting/UserFrosting/issues/950
717723
[#951]: https://github.com/userfrosting/UserFrosting/issues/951
724+
[#952]: https://github.com/userfrosting/UserFrosting/issues/952
718725
[#953]: https://github.com/userfrosting/UserFrosting/issues/953
719726

720727
[v4.2.0]: https://github.com/userfrosting/UserFrosting/compare/v4.1.22...v4.2.0

app/sprinkles/account/tests/Integration/AuthenticatorTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
use UserFrosting\Sprinkle\Account\Authenticate\Authenticator;
1313
use UserFrosting\Sprinkle\Account\Facades\Password;
1414
use UserFrosting\Sprinkle\Account\Tests\withTestUser;
15+
use UserFrosting\Sprinkle\Core\Database\Models\Session as SessionTable;
1516
use UserFrosting\Sprinkle\Core\Tests\TestDatabase;
1617
use UserFrosting\Sprinkle\Core\Tests\RefreshDatabase;
18+
use UserFrosting\Sprinkle\Core\Tests\withDatabaseSessionHandler;
1719
use UserFrosting\Tests\TestCase;
1820

1921
/**
@@ -26,6 +28,7 @@ class AuthenticatorTest extends TestCase
2628
use TestDatabase;
2729
use RefreshDatabase;
2830
use withTestUser;
31+
use withDatabaseSessionHandler;
2932

3033
/**
3134
* Setup the test database.
@@ -80,6 +83,54 @@ public function testLogin(Authenticator $authenticator)
8083
$this->assertNotSame($testUser->id, $this->ci->session[$key]);
8184
}
8285

86+
/**
87+
* @depends testConstructor
88+
* @param Authenticator $authenticator
89+
*/
90+
public function testLoginWithSessionDatabase(Authenticator $authenticator)
91+
{
92+
// Reset CI Session
93+
$this->useDatabaseSessionHandler();
94+
95+
// Create a test user
96+
$testUser = $this->createTestUser();
97+
98+
// Check the table
99+
$this->assertSame(0, SessionTable::count());
100+
101+
// Test session to avoid false positive
102+
$key = $this->ci->config['session.keys.current_user_id'];
103+
$this->assertNull($this->ci->session[$key]);
104+
$this->assertNotSame($testUser->id, $this->ci->session[$key]);
105+
106+
// Login the test user
107+
$authenticator->login($testUser, false);
108+
109+
// Test session to see if user was logged in
110+
$this->assertNotNull($this->ci->session[$key]);
111+
$this->assertSame($testUser->id, $this->ci->session[$key]);
112+
113+
// Close session to initiate write
114+
session_write_close();
115+
116+
// Check the table again
117+
$this->assertSame(1, SessionTable::count());
118+
119+
// Reopen session
120+
$this->ci->session->start();
121+
122+
// Must logout to avoid test issue
123+
$authenticator->logout(true);
124+
125+
// We'll test the logout system works too while we're at it (and depend on it)
126+
$key = $this->ci->config['session.keys.current_user_id'];
127+
$this->assertNull($this->ci->session[$key]);
128+
$this->assertNotSame($testUser->id, $this->ci->session[$key]);
129+
130+
// Make sure table entry has been removed
131+
$this->assertSame(0, SessionTable::count());
132+
}
133+
83134
/**
84135
* @depends testConstructor
85136
* @expectedException \UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountInvalidException

app/sprinkles/core/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"userfrosting/cache": "~4.2.0",
5252
"userfrosting/fortress": "~4.2.0",
5353
"userfrosting/i18n": "~4.2.0",
54-
"userfrosting/session": "~4.2.0",
54+
"userfrosting/session": "~4.2.2",
5555
"userfrosting/support": "~4.2.0",
5656
"vlucas/phpdotenv": "^2"
5757
},

app/sprinkles/core/config/testing.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
* Disable native sessions in tests
6161
*/
6262
'session' => [
63-
'handler' => 'array'
63+
'handler' => getenv('TEST_SESSION_HANDLER') ?: 'array'
6464
],
6565
/*
6666
* Database to use when using the TestDatabase Trait
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
/**
3+
* UserFrosting (http://www.userfrosting.com)
4+
*
5+
* @link https://github.com/userfrosting/UserFrosting
6+
* @copyright Copyright (c) 2019 Alexander Weissman
7+
* @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License)
8+
*/
9+
10+
namespace UserFrosting\Sprinkle\Core\Database\Models;
11+
12+
/**
13+
* Session Class
14+
*
15+
* Represents a session object as stored in the database.
16+
*/
17+
class Session extends Model
18+
{
19+
/**
20+
* @var string The name of the table for the current model.
21+
*/
22+
protected $table = 'sessions';
23+
}

app/sprinkles/core/src/ServicesProvider/ServicesProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use Illuminate\Database\Events\QueryExecuted;
1717
use Illuminate\Events\Dispatcher;
1818
use Illuminate\Filesystem\Filesystem;
19-
use Illuminate\Session\DatabaseSessionHandler;
2019
use Illuminate\Session\FileSessionHandler;
2120
use Interop\Container\ContainerInterface;
2221
use League\FactoryMuffin\FactoryMuffin;
@@ -50,6 +49,7 @@
5049
use UserFrosting\Sprinkle\Core\Database\Seeder\Seeder;
5150
use UserFrosting\Sprinkle\Core\Filesystem\FilesystemManager;
5251
use UserFrosting\Sprinkle\Core\Router;
52+
use UserFrosting\Sprinkle\Core\Session\DatabaseSessionHandler;
5353
use UserFrosting\Sprinkle\Core\Session\NullSessionHandler;
5454
use UserFrosting\Sprinkle\Core\Throttle\Throttler;
5555
use UserFrosting\Sprinkle\Core\Throttle\ThrottleRule;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
/**
3+
* UserFrosting (http://www.userfrosting.com)
4+
*
5+
* @link https://github.com/userfrosting/UserFrosting
6+
* @copyright Copyright (c) 2019 Alexander Weissman
7+
* @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License)
8+
*/
9+
10+
namespace UserFrosting\Sprinkle\Core\Session;
11+
12+
use Illuminate\Session\DatabaseSessionHandler as LaravelDatabaseSessionHandler;
13+
14+
/**
15+
* Temp class until we update to Laravel 5.5.
16+
* A bug was fixed in 5.5, which caused https://github.com/userfrosting/UserFrosting/issues/952
17+
* @see https://github.com/laravel/framework/commit/24356a8ca677ba589b8f2d00f24ce3e9a7a1e02d#diff-9a772ff9941d635b86a27ca1ea149e73
18+
*/
19+
class DatabaseSessionHandler extends LaravelDatabaseSessionHandler
20+
{
21+
/**
22+
* {@inheritdoc}
23+
*/
24+
public function read($sessionId)
25+
{
26+
$session = (object) $this->getQuery()->find($sessionId);
27+
28+
if ($this->expired($session)) {
29+
$this->exists = true;
30+
31+
return '';
32+
}
33+
34+
if (isset($session->payload)) {
35+
$this->exists = true;
36+
37+
return base64_decode($session->payload);
38+
}
39+
40+
return '';
41+
}
42+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
/**
3+
* UserFrosting (http://www.userfrosting.com)
4+
*
5+
* @link https://github.com/userfrosting/UserFrosting
6+
* @copyright Copyright (c) 2019 Alexander Weissman
7+
* @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License)
8+
*/
9+
10+
namespace UserFrosting\Sprinkle\Account\Tests\Integration\Session;
11+
12+
use UserFrosting\Session\Session;
13+
use UserFrosting\Sprinkle\Core\Database\Models\Session as SessionTable;
14+
use UserFrosting\Sprinkle\Core\Session\DatabaseSessionHandler;
15+
use UserFrosting\Sprinkle\Core\Tests\TestDatabase;
16+
use UserFrosting\Sprinkle\Core\Tests\RefreshDatabase;
17+
use UserFrosting\Sprinkle\Core\Tests\withDatabaseSessionHandler;
18+
use UserFrosting\Tests\TestCase;
19+
20+
/**
21+
* Integration tests for the session service.
22+
*/
23+
class SessionDatabaseHandlerTest extends TestCase
24+
{
25+
use TestDatabase;
26+
use RefreshDatabase;
27+
use withDatabaseSessionHandler;
28+
29+
public function setUp()
30+
{
31+
parent::setUp();
32+
33+
$this->setupTestDatabase();
34+
$this->refreshDatabase();
35+
}
36+
37+
/**
38+
* Test session table connection & existance
39+
*/
40+
public function testSessionTable()
41+
{
42+
$connection = $this->ci->db->connection();
43+
$config = $this->ci->config;
44+
$table = $config['session.database.table'];
45+
46+
// Check connexion is ok and returns what's expected from DatabaseSessionHandler
47+
$this->assertInstanceOf(\Illuminate\Database\ConnectionInterface::class, $connection);
48+
$this->assertInstanceOf(\Illuminate\Database\Query\Builder::class, $connection->table($table));
49+
50+
// Check table exist
51+
$this->assertTrue($connection->getSchemaBuilder()->hasTable($table));
52+
}
53+
54+
/**
55+
* @depends testSessionTable
56+
*/
57+
public function testSessionWrite()
58+
{
59+
$config = $this->ci->config;
60+
$connection = $this->ci->db->connection();
61+
62+
// Define random session ID
63+
$session_id = 'test'.rand(1, 100000);
64+
65+
// Make sure db is empty at first
66+
$this->assertEquals(0, SessionTable::count());
67+
$this->assertNull(SessionTable::find($session_id));
68+
69+
// Get handler
70+
$handler = new DatabaseSessionHandler($connection, $config['session.database.table'], $config['session.minutes']);
71+
72+
// Write session
73+
// https://github.com/laravel/framework/blob/5.4/src/Illuminate/Session/DatabaseSessionHandler.php#L132
74+
$this->assertTrue($handler->write($session_id, 'foo'));
75+
76+
// Closing the handler does nothing anyway
77+
// https://github.com/laravel/framework/blob/5.4/src/Illuminate/Session/DatabaseSessionHandler.php#L78
78+
$this->assertTrue($handler->close());
79+
80+
// Read session
81+
// https://github.com/laravel/framework/blob/5.4/src/Illuminate/Session/DatabaseSessionHandler.php#L86-L101
82+
$this->assertSame('foo', $handler->read($session_id));
83+
84+
// Check manually that the file has been written
85+
$this->assertNotEquals(0, SessionTable::count());
86+
$this->assertNotNull(SessionTable::find($session_id));
87+
$this->assertSame(base64_encode('foo'), SessionTable::find($session_id)->payload);
88+
89+
// Destroy session
90+
// https://github.com/laravel/framework/blob/5.4/src/Illuminate/Session/DatabaseSessionHandler.php#L256
91+
$this->assertTrue($handler->destroy($session_id));
92+
93+
// Check db to make sure it's gone
94+
$this->assertEquals(0, SessionTable::count());
95+
$this->assertNull(SessionTable::find($session_id));
96+
}
97+
98+
/**
99+
* Simulate session service with database handler.
100+
* We can't use the real service as it is created before we can even setup
101+
* the in-memory database with the basic table we need
102+
*
103+
* @depends testSessionWrite
104+
*/
105+
public function testUsingSessionDouble()
106+
{
107+
$this->ci->session->destroy();
108+
109+
$config = $this->ci->config;
110+
$connection = $this->ci->db->connection();
111+
$handler = new DatabaseSessionHandler($connection, $config['session.database.table'], $config['session.minutes']);
112+
$session = new Session($handler, $config['session']);
113+
114+
$this->assertInstanceOf(Session::class, $session);
115+
$this->assertInstanceOf(DatabaseSessionHandler::class, $session->getHandler());
116+
$this->assertSame($handler, $session->getHandler());
117+
118+
$this->sessionTests($session);
119+
}
120+
121+
/**
122+
* @depends testUsingSessionDouble
123+
*/
124+
public function testUsingSessionService()
125+
{
126+
// Reset CI Session
127+
$this->useDatabaseSessionHandler();
128+
129+
// Make sure config is set
130+
$this->sessionTests($this->ci->session);
131+
}
132+
133+
/**
134+
* @param Session $session
135+
*/
136+
protected function sessionTests(Session $session)
137+
{
138+
// Make sure session service have correct instance
139+
$this->assertInstanceOf(Session::class, $session);
140+
$this->assertInstanceOf(DatabaseSessionHandler::class, $session->getHandler());
141+
142+
// Destroy previously defined session
143+
$session->destroy();
144+
145+
// Start new one and validate status
146+
$this->assertSame(PHP_SESSION_NONE, $session->status());
147+
$session->start();
148+
$this->assertSame(PHP_SESSION_ACTIVE, $session->status());
149+
150+
// Get id
151+
$session_id = $session->getId();
152+
153+
// Set something to the session
154+
$session->set('foo', 'bar');
155+
$this->assertEquals('bar', $session->get('foo'));
156+
157+
// Close session to initiate write
158+
session_write_close();
159+
160+
// Make sure db was filled with something
161+
$this->assertNotEquals(0, SessionTable::count());
162+
$this->assertNotNull(SessionTable::find($session_id));
163+
}
164+
}

0 commit comments

Comments
 (0)