Hook registration plugin to be able to register directly with a linked external account

master
Lars Vierbergen 7 years ago
parent 758e1344d7
commit 088e997203
  1. 14
      DependencyInjection/AuthserverExternalAccountExtension.php
  2. 52
      Entity/TemporaryUser.php
  3. 123
      EventListener/RegistrationHandlerListener.php
  4. 6
      Resources/config/services.xml
  5. 47
      Resources/migrations/VersionAuthserverExternalAccount20171020080018.php
  6. 43
      Resources/migrations/VersionAuthserverExternalAccount20171020220239.php
  7. 45
      Resources/migrations/VersionAuthserverExternalAccount20171020220751.php
  8. 48
      Security/Core/User/TemporaryUserProvider.php

@ -24,13 +24,14 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class AuthserverExternalAccountExtension extends Extension
class AuthserverExternalAccountExtension extends Extension implements PrependExtensionInterface
{
/**
* {@inheritDoc}
@ -42,4 +43,15 @@ class AuthserverExternalAccountExtension extends Extension
$loader = new Loader\XmlFileLoader($container, $fileLocator);
$loader->load('services.xml');
}
public function prepend(ContainerBuilder $container)
{
$container->prependExtensionConfig('security', [
'providers' => [
'temporary_user' => [
'id' => 'vierbergenlars.authserver_external_account.temporary_user_provider'
]
]
]);
}
}

@ -0,0 +1,52 @@
<?php
/**
* Authserver, an OAuth2-based single-signon authentication provider written in PHP.
*
* Copyright (C) $today.date Lars Vierbergen
*
* his program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace vierbergenlars\AuthserverExternalAccountBundle\Entity;
use Registration\Entity\TemporaryUser as BaseTemporaryUser;
class TemporaryUser extends BaseTemporaryUser
{
/**
*
* @var ExternalUser
*/
private $externalUser;
public function getExternalUser()
{
return $this->externalUser;
}
public function setExternalUser(ExternalUser $externalUser)
{
$this->externalUser = $externalUser;
}
public function getUsername()
{
return '$' . $this->externalUser->getProvider() . '$' . $this->externalUser->getProviderRef() . '$';
}
public function getDisplayName()
{
return $this->externalUser->getProviderFriendlyName();
}
}

@ -0,0 +1,123 @@
<?php
/**
* Authserver, an OAuth2-based single-signon authentication provider written in PHP.
*
* Copyright (C) $today.date Lars Vierbergen
*
* his program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace vierbergenlars\AuthserverExternalAccountBundle\EventListener;
use Registration\Event\RegistrationHandleEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Registration\RegistrationEvents;
use Registration\Event\RegistrationFormEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use vierbergenlars\AuthserverExternalAccountBundle\Entity\TemporaryUser;
use Doctrine\ORM\EntityManagerInterface;
class RegistrationHandlerListener implements EventSubscriberInterface
{
/**
*
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
*
* @var EntityManagerInterface
*/
private $em;
public static function getSubscribedEvents()
{
return [
RegistrationEvents::BUILD_FORM => [
'onBuildForm',
10
],
RegistrationEvents::HANDLE_FORM => [
[
'onHandleFormSetPasswordEnabled',
10
],
[
'onHandleFormConnectExternal',
0
],
[
'onHandleFormLogoutExternal',
-250
]
]
];
}
public function __construct(EntityManagerInterface $em, TokenStorageInterface $tokenStorage)
{
$this->em = $em;
$this->tokenStorage = $tokenStorage;
}
private function getTemporaryUser()
{
$token = $this->tokenStorage->getToken();
if (!$token)
return null;
$user = $token->getUser();
if ($user instanceof TemporaryUser)
return $user;
return null;
}
public function onBuildForm(RegistrationFormEvent $event)
{
if ($user = $this->getTemporaryUser()) {
$event->getFormBuilder()->remove('password');
$event->getFormBuilder()
->getData()
->setDisplayName($user->getDisplayName())
->setPasswordEnabled(2);
}
}
public function onHandleFormSetPasswordEnabled(RegistrationHandleEvent $event)
{
$user = $event->getForm()->getData();
if (!$user)
return;
/* @var $user User */
$user->setPasswordEnabled(2);
}
public function onHandleFormConnectExternal(RegistrationHandleEvent $event)
{
$user = $event->getForm()->getData();
if (!$user)
return;
/* @var $user User */
if ($temporaryUser = $this->getTemporaryUser()) {
$temporaryUser->getExternalUser()->setUser($user);
$this->em->persist($temporaryUser->getExternalUser());
}
}
public function onHandleFormLogoutExternal(RegistrationHandleEvent $event)
{
if ($this->getTemporaryUser())
$this->tokenStorage->setToken(null);
}
}

@ -32,6 +32,12 @@
<argument type="service" id="vierbergenlars.authserver_external_account.account_provider_manager" />
<tag name="kernel.event_subscriber" />
</service>
<service class="vierbergenlars\AuthserverExternalAccountBundle\EventListener\RegistrationHandlerListener">
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service" id="security.token_storage" />
<tag name="kernel.event_subscriber" />
</service>
<service id="vierbergenlars.authserver_external_account.account_provider_manager" class="vierbergenlars\AuthserverExternalAccountBundle\ExternalAccount\ExternalAccountProviderManager" />
<service id="vierbergenlars.authserver_external_account.temporary_user_provider" class="vierbergenlars\AuthserverExternalAccountBundle\Security\Core\User\TemporaryUserProvider" />
</services>
</container>

@ -0,0 +1,47 @@
<?php
/**
* Authserver, an OAuth2-based single-signon authentication provider written in PHP.
*
* Copyright (C) $today.date Lars Vierbergen
*
* his program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
class VersionAuthserverExternalAccount20171020080018 extends AbstractMigration
{
public function up(Schema $schema)
{
$externalUser = $schema->getTable('vierbergenlars_external_account_external_user');
foreach ($externalUser->getForeignKeys() as $fk)
$externalUser->removeForeignKey($fk->getName());
$externalUser->addForeignKeyConstraint('auth_users', [
'user_id'
], [
'id'
], [
'onDelete' => 'CASCADE'
], 'fk_vl_ea_external_user');
}
public function down(Schema $schema)
{}
}

@ -0,0 +1,43 @@
<?php
/**
* Authserver, an OAuth2-based single-signon authentication provider written in PHP.
*
* Copyright (C) $today.date Lars Vierbergen
*
* his program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
class VersionAuthserverExternalAccount20171020220239 extends AbstractMigration
{
public function up(Schema $schema)
{
$condition = 'WHERE ea1.id > ea2.id AND ea1.provider = ea2.provider AND ea1.provider_ref = ea2.provider_ref';
$duplicates = $this->connection->fetchAll('SELECT DISTINCT ea1.id AS id FROM vierbergenlars_external_account_external_user AS ea1 JOIN vierbergenlars_external_account_external_user ea2 ' . $condition);
$duplicateIds = array_map(function ($row) {
return $row['id'];
}, $duplicates);
$this->connection->executeUpdate('DELETE FROM vierbergenlars_external_account_external_user WHERE id IN(' . implode(',', $duplicateIds) . ')');
}
public function down(Schema $schema)
{
$this->throwIrreversibleMigrationException('Duplicate external account references have been cleared.');
}
}

@ -0,0 +1,45 @@
<?php
/**
* Authserver, an OAuth2-based single-signon authentication provider written in PHP.
*
* Copyright (C) $today.date Lars Vierbergen
*
* his program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
class VersionAuthserverExternalAccount20171020220751 extends AbstractMigration
{
public function up(Schema $schema)
{
$externalUser = $schema->getTable('vierbergenlars_external_account_external_user');
if ($externalUser->hasIndex('uniq_vl_ea_external_user'))
$externalUser->dropIndex('uniq_vl_ea_external_user');
$externalUser->addUniqueIndex([
'provider',
'provider_ref'
], 'uniq_vl_ea_external_user');
}
public function down(Schema $schema)
{
$this->throwIrreversibleMigrationException('Duplicate values on (provider, provider_ref) are no longer allowed.');
}
}

@ -0,0 +1,48 @@
<?php
/**
* Authserver, an OAuth2-based single-signon authentication provider written in PHP.
*
* Copyright (C) $today.date Lars Vierbergen
*
* his program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace vierbergenlars\AuthserverExternalAccountBundle\Security\Core\User;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use vierbergenlars\AuthserverExternalAccountBundle\Entity\TemporaryUser;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class TemporaryUserProvider implements UserProviderInterface
{
public function supportsClass($class)
{
return $class === TemporaryUser::class;
}
public function refreshUser(UserInterface $user)
{
if (!$this->supportsClass(get_class($user)))
throw new UnsupportedUserException(sprintf('Expected instance of %s, got instance of %s', TemporaryUser::class, get_class($user)));
return $user;
}
public function loadUserByUsername($username)
{
throw new UsernameNotFoundException();
}
}