-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[2.2][Security] concurrent sessions #786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c0b33f8
0f3d7c4
e9bcd84
9f4a52d
507befa
5559ef8
238606b
455197f
a4eeae5
aadb2a7
5b5edd8
33d83b4
624b01f
7032534
bc182f9
d0c826a
c9f08a4
13813da
8ef6923
f4f0a8c
e11a540
a3d0a59
c800408
9c8bd21
2efbf11
3cb882c
0aa9353
ff2122b
efc783b
d302e37
bed28d2
29e2f27
7a0f46a
ac7a1d6
b24a364
f7dfb3f
3cef9bb
d567980
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Bridge\Doctrine\Security\SessionRegistry; | ||
|
||
use Doctrine\DBAL\Schema\Schema as BaseSchema; | ||
|
||
/** | ||
* The schema used for the ACL system. | ||
* | ||
* @author Stefan Paschke <stefan.paschke@gmail.com> | ||
*/ | ||
final class Schema extends BaseSchema | ||
{ | ||
/** | ||
* Constructor | ||
* | ||
* @param array $options the names for tables | ||
*/ | ||
public function __construct(array $options) | ||
{ | ||
parent::__construct(); | ||
|
||
$this->addSessionInformationTable($options); | ||
} | ||
|
||
/** | ||
* Adds the session_information table to the schema | ||
*/ | ||
protected function addSessionInformationTable(array $options) | ||
{ | ||
$table = $this->createTable($options['session_information_table_name']); | ||
$table->addColumn('session_id', 'string'); | ||
$table->addColumn('username', 'string'); | ||
$table->addColumn('expired', 'datetime', array('unsigned' => true, 'notnull' => false)); | ||
$table->addColumn('last_request', 'datetime', array('unsigned' => true, 'notnull' => false)); | ||
$table->setPrimaryKey(array('session_id')); | ||
$table->addUniqueIndex(array('session_id')); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Bridge\Doctrine\Security\SessionRegistry; | ||
|
||
use Symfony\Component\Security\Http\Session\SessionInformation as BaseSessionInformation; | ||
|
||
/** | ||
* SessionInformation. | ||
* | ||
* Allows to persist SessionInformation using Doctrine DBAL. | ||
* | ||
* @author Stefan Paschke <stefan.paschke@gmail.com> | ||
*/ | ||
class SessionInformation extends BaseSessionInformation | ||
{ | ||
public function __construct($sessionId, $username, \DateTime $lastRequest = null, \DateTime $expired = null) | ||
{ | ||
parent::__construct($sessionId, $username); | ||
|
||
if (null !== $lastRequest) { | ||
$this->setLastRequest($lastRequest); | ||
} | ||
|
||
if (null !== $expired) { | ||
$this->setExpired($expired); | ||
} | ||
} | ||
|
||
public function getExpired() | ||
{ | ||
return parent::getExpired(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php | ||
|
||
namespace Symfony\Bridge\Doctrine\Security\SessionRegistry; | ||
|
||
use Doctrine\DBAL\Driver\Connection; | ||
use Symfony\Component\Security\Http\Session\SessionInformation; | ||
use Symfony\Component\Security\Http\Session\SessionRegistryStorageInterface; | ||
|
||
class SessionRegistryStorage implements SessionRegistryStorageInterface | ||
{ | ||
protected $connection; | ||
protected $options; | ||
|
||
public function __construct(Connection $connection, array $options) | ||
{ | ||
$this->connection = $connection; | ||
$this->options = $options; | ||
} | ||
|
||
/** | ||
* not implemented | ||
*/ | ||
public function getUsers() | ||
{ | ||
throw new \BadMethodCallException("Not implemented."); | ||
} | ||
|
||
/** | ||
* Obtains the maintained information for one session. | ||
* | ||
* @param string $sessionId the session identifier key. | ||
* @return SessionInformation a SessionInformation object. | ||
*/ | ||
public function getSessionInformation($sessionId) | ||
{ | ||
$statement = $this->connection->executeQuery( | ||
'SELECT * FROM '.$this->options['session_information_table_name'].' WHERE session_id = :session_id', | ||
array('session_id' => $sessionId) | ||
); | ||
|
||
$data = $statement->fetch(\PDO::FETCH_ASSOC); | ||
|
||
return $data ? $this->instantiateSessionInformationFromResultSet($data) : null; | ||
} | ||
|
||
/** | ||
* Obtains the maintained information for one user. | ||
* | ||
* @param string $username The user identifier. | ||
* @param boolean $includeExpiredSessions. | ||
* @return array An array of SessionInformation objects. | ||
*/ | ||
public function getSessionInformations($username, $includeExpiredSessions = false) | ||
{ | ||
$sessionInformations = array(); | ||
|
||
$statement = $this->connection->executeQuery( | ||
'SELECT * | ||
FROM '.$this->options['session_information_table_name'].' | ||
WHERE username = :username'.($includeExpiredSessions ? '' : ' AND expired IS NULL ').' | ||
ORDER BY last_request DESC', | ||
array('username' => $username) | ||
); | ||
|
||
while ($data = $statement->fetch(\PDO::FETCH_ASSOC)) | ||
{ | ||
$sessionInformations[] = $this->instantiateSessionInformationFromResultSet($data); | ||
} | ||
|
||
return $sessionInformations; | ||
} | ||
|
||
/** | ||
* Adds information for one session. | ||
* | ||
* @param string $sessionId the session identifier key. | ||
* @param SessionInformation a SessionInformation object. | ||
*/ | ||
public function setSessionInformation(SessionInformation $sessionInformation) | ||
{ | ||
$statement = $this->connection->prepare( | ||
'INSERT INTO '.$this->options['session_information_table_name'].' | ||
(session_id, username, last_request, expired) VALUES(:session_id, :username, :last_request, :expired) | ||
ON DUPLICATE KEY | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I can see your not using DQL or some sort of query abstraction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indeed you are right .. i hear @kimhemsoe is working on UPSERT abstraction for the Doctrine DBAL, so it might be in the summer release. for sqlite we can use REPLACE INTO, for postgresql (and maybe also DB2) MERGE etc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The best solution is to either.
As far as I can find PostgreSQL does not support MERGE/UPSERT just yet. https://wiki.postgresql.org/wiki/Add_MERGE_command_GSoC_2010 REPLACE INTO (In MySQL) is not completely safe, first it performs an DELETE (also firing any FK's or triggers) and then inserting it! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. REPLACE INTO should be avoided, but on SQLite its still better than anything else for UPSERT option 1) and 2) are problematic because it can trigger all sorts of ugly side effects if one causes such error conditions. i guess if users use a separate connection to the database for their session handler and the rest of the code, those side effects should not be a big issue, but then it could also lead to other issue with the RDBMS f.e. running out of connections. |
||
UPDATE username=:username, last_request=:last_request, expired=:expired'); | ||
|
||
$statement->bindValue('session_id', $sessionInformation->getSessionId()); | ||
$statement->bindValue('username', $sessionInformation->getUsername()); | ||
$statement->bindValue('last_request', $sessionInformation->getLastRequest(), 'datetime'); | ||
$statement->bindValue('expired', $sessionInformation->getExpired(), 'datetime'); | ||
$statement->execute(); | ||
} | ||
|
||
/** | ||
* Deletes the maintained information of one session. | ||
* | ||
* @param string $sessionId the session identifier key. | ||
*/ | ||
public function removeSessionInformation($sessionId) | ||
{ | ||
$this->connection->delete($this->options['session_information_table_name'], array('session_id' => $sessionId)); | ||
} | ||
|
||
private function instantiateSessionInformationFromResultSet($data) | ||
{ | ||
return new $this->options['session_information_class_name']( | ||
$data['session_id'], | ||
$data['username'], | ||
(null == $data['last_request']) ? null : new \DateTime($data['last_request']), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Same line below. |
||
(null == $data['expired']) ? null : new \DateTime($data['expired']) | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Bundle\SecurityBundle\Command; | ||
|
||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; | ||
use Symfony\Bridge\Doctrine\Security\SessionRegistry\Schema; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
/** | ||
* Installs the database schema required by the concurrent session Doctrine implementation | ||
* | ||
* @author Stefan Paschke <stefan.paschke@gmail.com> | ||
*/ | ||
class InitConcurrentSessionsCommand extends ContainerAwareCommand | ||
{ | ||
/** | ||
* @see Command | ||
*/ | ||
protected function configure() | ||
{ | ||
parent::configure(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not needed as the parent method (in the base Command class) is empty |
||
|
||
$this | ||
->setName('init:concurrent-session') | ||
->setDescription('Executes the SQL needed to generate the database schema required by the concurrent sessions feature.') | ||
->setHelp(<<<EOT | ||
The <info>init:concurrent-session</info> command executes the SQL needed to | ||
generate the database schema required by the concurrent session Doctrine implementation: | ||
|
||
<info>./app/console init:concurrent-session</info> | ||
|
||
You can also output the SQL instead of executing it: | ||
|
||
<info>./app/console init:concurrent-session --dump-sql</info> | ||
EOT | ||
); | ||
} | ||
|
||
/** | ||
* @see Command | ||
*/ | ||
protected function execute(InputInterface $input, OutputInterface $output) | ||
{ | ||
$connection = $this->getContainer()->get('security.session_registry.dbal.connection'); | ||
$sm = $connection->getSchemaManager(); | ||
$tableNames = $sm->listTableNames(); | ||
$tables = array( | ||
'session_information_table_name' => $this->getContainer()->getParameter('security.session_registry.dbal.session_information_table_name'), | ||
); | ||
|
||
foreach ($tables as $table) { | ||
if (in_array($table, $tableNames, true)) { | ||
$output->writeln(sprintf('The table "%s" already exists. Aborting.', $table)); | ||
|
||
return; | ||
} | ||
} | ||
|
||
$schema = new Schema($tables); | ||
foreach ($schema->toSql($connection->getDatabasePlatform()) as $sql) { | ||
$connection->exec($sql); | ||
} | ||
|
||
$output->writeln('concurrent session tables have been initialized successfully.'); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brace need to be in same line as
while
.