Add parameter to trust or not trust the verification of email addresses from an external service.

Trusted services will mark the email address as verified upon registration. The user is still allowed to modify the email address in the registration form, but if an
other email address is provided, it will not be verified automatically.
master
Lars Vierbergen 7 years ago
parent 8cc5a7f3f6
commit fce2ca821b
  1. 34
      DependencyInjection/AuthserverOAuthAccountExtension.php
  2. 10
      DependencyInjection/Configuration.php
  3. 13
      EventListener/RegistrationHandlerListener.php
  4. 21
      ExternalAccount/OAuthExternalAccountProvider.php
  5. 31
      ResourceOwner/ResourceOwnerConfig.php
  6. 24
      ResourceOwner/ResourceOwnerMap.php
  7. 13
      Resources/config/services.xml

@ -34,14 +34,19 @@ use vierbergenlars\AuthserverOAuthAccountBundle\ResourceOwner\ResourceOwnerConfi
class AuthserverOAuthAccountExtension extends Extension implements PrependExtensionInterface
{
const USER_PROVIDER_SERVICE = 'vierbergenlars.authserver_oauth_account.user_provider';
const RESOURCE_OWNER_MAP_SERVICE = 'vierbergenlars.authserver_oauth_account.resource_owner_map';
public function prepend(ContainerBuilder $container)
{
$container->prependExtensionConfig('hwi_oauth', [
'firewall_names' => ['public'],
'firewall_names' => [
'public'
],
'connect' => [
'account_connector' => self::USER_PROVIDER_SERVICE,
'account_connector' => self::USER_PROVIDER_SERVICE
]
]);
@ -50,46 +55,45 @@ class AuthserverOAuthAccountExtension extends Extension implements PrependExtens
$processor = new Processor();
$config = $processor->processConfiguration(new Configuration(), $configs);
$container->prependExtensionConfig('hwi_oauth', [
'resource_owners' => array_map(function($resource_owner) {
'resource_owners' => array_map(function ($resource_owner) {
return $resource_owner['config'];
}, $config['resource_owners']),
}, $config['resource_owners'])
]);
$container->loadFromExtension('security', [
'firewalls' => [
'public' => [
'oauth' => [
'resource_owners' => array_combine(array_keys($config['resource_owners']), array_map(function($roName) {
return '/login/oauth/'.$roName;
'resource_owners' => array_combine(array_keys($config['resource_owners']), array_map(function ($roName) {
return '/login/oauth/' . $roName;
}, array_keys($config['resource_owners']))),
'login_path' => 'app_login',
'failure_path' => 'app_login',
'oauth_user_provider' => [
'service' => self::USER_PROVIDER_SERVICE,
],
],
'service' => self::USER_PROVIDER_SERVICE
]
]
]
],
]
]);
}
public function load(array $configs, ContainerBuilder $container)
{
$servicesDirectory = __DIR__.'/../Resources/config';
$servicesDirectory = __DIR__ . '/../Resources/config';
$fileLocator = new FileLocator($servicesDirectory);
$xmlLoader = new Loader\XmlFileLoader($container, $fileLocator);
$xmlLoader->load('services.xml');
$processor = new Processor();
$config = $processor->processConfiguration(new Configuration(), $configs);
$container->getDefinition(self::RESOURCE_OWNER_MAP_SERVICE)->setArgument(0, $config['resource_owners']);
foreach($config['resource_owners'] as $name => $config)
{
foreach ($config['resource_owners'] as $name => $config) {
$service = new DefinitionDecorator('vierbergenlars.authserver_oauth_account.external_account_provider.abstract');
$service->replaceArgument(0, $name);
$service->replaceArgument(1, $config);
$service->addTag(AuthserverExternalAccountBundle::EXTERNAL_ACCOUNT_PROVIDER_TAG);
$container->setDefinition('vierbergenlars.authserver_oauth_account.external_account_provider.impl.'.$name, $service);
$container->setDefinition('vierbergenlars.authserver_oauth_account.external_account_provider.impl.' . $name, $service);
}
}

@ -17,7 +17,6 @@
* 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\AuthserverOAuthAccountBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
@ -30,15 +29,17 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritDoc}
*
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('oauth');
// @formatter:off
$rootNode->children()
->arrayNode('resource_owners')
->useAttributeAsKey('name')
@ -49,6 +50,7 @@ class Configuration implements ConfigurationInterface
->end()
->scalarNode('service_name')->isRequired()->end()
->scalarNode('icon')->defaultNull()->end()
->booleanNode('trust_email_verification')->defaultFalse()->end()
->arrayNode('login_button')
->addDefaultsIfNotSet()
->children()
@ -68,7 +70,7 @@ class Configuration implements ConfigurationInterface
->end()
->end()
;
// @formatter:on
// Here you should define the parameters that are allowed to
// configure your bundle. See the documentation linked above for

@ -27,6 +27,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInt
use vierbergenlars\AuthserverOAuthAccountBundle\Entity\TemporaryUser;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\EmailAddress;
use vierbergenlars\AuthserverOAuthAccountBundle\ResourceOwner\ResourceOwnerMap;
class RegistrationHandlerListener implements EventSubscriberInterface
{
@ -43,6 +44,12 @@ class RegistrationHandlerListener implements EventSubscriberInterface
*/
private $em;
/**
*
* @var ResourceOwnerMap
*/
private $resourceOwnerMap;
public static function getSubscribedEvents()
{
return [
@ -57,10 +64,11 @@ class RegistrationHandlerListener implements EventSubscriberInterface
];
}
public function __construct(EntityManagerInterface $em, TokenStorageInterface $tokenStorage)
public function __construct(EntityManagerInterface $em, TokenStorageInterface $tokenStorage, ResourceOwnerMap $resourceOwnerMap)
{
$this->em = $em;
$this->tokenStorage = $tokenStorage;
$this->resourceOwnerMap = $resourceOwnerMap;
}
private function getTemporaryUser()
@ -96,6 +104,9 @@ class RegistrationHandlerListener implements EventSubscriberInterface
return;
/* @var $user \App\Entity\User */
if ($temporaryUser = $this->getTemporaryUser()) {
if (!$this->resourceOwnerMap->getOwnerFromExternalUser($temporaryUser->getExternalUser())
->isTrustEmailVerification())
return;
if ($temporaryUser->getEmail() && $temporaryUser->getEmail() === $user->getPrimaryEmailAddress()->getEmail()) {
$user->getPrimaryEmailAddress()->setVerified(true);
}

@ -17,41 +17,46 @@
* 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\AuthserverOAuthAccountBundle\ExternalAccount;
use HWI\Bundle\OAuthBundle\Security\OAuthUtils;
use Symfony\Component\HttpFoundation\Request;
use vierbergenlars\AuthserverExternalAccountBundle\ExternalAccount\ExternalAccountProviderInterface;
use vierbergenlars\AuthserverExternalAccountBundle\ValueObject\Button;
use vierbergenlars\AuthserverOAuthAccountBundle\ResourceOwner\ResourceOwnerConfig;
use vierbergenlars\AuthserverOAuthAccountBundle\ResourceOwner\ResourceOwnerMap;
class OAuthExternalAccountProvider implements ExternalAccountProviderInterface
{
/**
*
* @var ResourceOwnerConfig
*/
private $resourceOwnerConfig;
/**
*
* @var
*/
private $name;
/**
*
* @var OAuthUtils
*/
private $OAuthUtils;
public function __construct($name, $resourceOwnerConfig, OAuthUtils $OAuthUtils)
public function __construct($name, ResourceOwnerMap $resourceOwnerMap, OAuthUtils $OAuthUtils)
{
$this->resourceOwnerConfig = new ResourceOwnerConfig($resourceOwnerConfig);
$this->resourceOwnerConfig = $resourceOwnerMap[$name];
$this->name = $name;
$this->OAuthUtils = $OAuthUtils;
}
public function getName()
{
return 'oauth_'.$this->name;
return 'oauth_' . $this->name;
}
public function getServiceName()
@ -67,15 +72,15 @@ class OAuthExternalAccountProvider implements ExternalAccountProviderInterface
public function getLoginButton()
{
return new Button($this->resourceOwnerConfig->getLoginButton() + [
'url' => $this->OAuthUtils->getLoginUrl(new Request(), $this->name),
'url' => $this->OAuthUtils->getLoginUrl(new Request(), $this->name)
]);
}
public function getConnectButton()
{
return new Button($this->resourceOwnerConfig->getConnectButton() + [
'url' => $this->OAuthUtils->getLoginUrl(new Request(), $this->name),
]);
'url' => $this->OAuthUtils->getLoginUrl(new Request(), $this->name)
]);
}
public function hasConnect()

@ -17,20 +17,19 @@
* 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\AuthserverOAuthAccountBundle\ResourceOwner;
class ResourceOwnerConfig
{
/**
*
* @var array
*/
private $config;
public function __construct(array $config)
{
$this->config = $config;
}
@ -39,6 +38,11 @@ class ResourceOwnerConfig
return $this->config['config'];
}
public function isTrustEmailVerification()
{
return $this->config['trust_email_verification'];
}
public function getServiceName()
{
return $this->config['service_name'];
@ -52,11 +56,11 @@ class ResourceOwnerConfig
public function getLoginButton()
{
$config = $this->config['login_button'];
foreach([
'icon' => $this->getIcon(),
'label' => $this->getServiceName().' Login',
] as $k=>$v)
$config[$k] = $config[$k]?:$v;
foreach ([
'icon' => $this->getIcon(),
'label' => $this->getServiceName() . ' Login'
] as $k => $v)
$config[$k] = $config[$k] ?: $v;
return $config;
}
@ -64,13 +68,12 @@ class ResourceOwnerConfig
public function getConnectButton()
{
$config = $this->config['connect_button'];
foreach([
'icon' => $this->getIcon(),
'label' => 'Connect with '.$this->getServiceName(),
] as $k=>$v)
$config[$k] = $config[$k]?:$v;
foreach ([
'icon' => $this->getIcon(),
'label' => 'Connect with ' . $this->getServiceName()
] as $k => $v)
$config[$k] = $config[$k] ?: $v;
return $config;
}
}

@ -17,11 +17,13 @@
* 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\AuthserverOAuthAccountBundle\ResourceOwner;
use vierbergenlars\AuthserverExternalAccountBundle\Entity\ExternalUser;
class ResourceOwnerMap implements \ArrayAccess, \IteratorAggregate
{
private $config;
public function __construct(array $config)
@ -29,6 +31,22 @@ class ResourceOwnerMap implements \ArrayAccess, \IteratorAggregate
$this->config = $config;
}
/**
*
* @param ExternalUser $externalUser
* @throws \InvalidArgumentException
* @return ResourceOwnerConfig
*/
public function getOwnerFromExternalUser(ExternalUser $externalUser)
{
$provider = $externalUser->getProvider();
if (strpos($provider, 'oauth_') !== 0)
throw new \InvalidArgumentException(sprintf('External user provider should start with "oauth_", got "%s"', $provider));
$name = substr($provider, strlen('oauth_'));
return $this->offsetGet($name);
}
public function offsetExists($offset)
{
return isset($this->config[$offset]);
@ -36,7 +54,9 @@ class ResourceOwnerMap implements \ArrayAccess, \IteratorAggregate
public function offsetGet($offset)
{
if(!$this->config[$offset] instanceof ResourceOwnerConfig)
if (!$this->offsetExists($offset))
throw new \OutOfBoundsException(sprintf('Resource owner "%s" does not exist.', $offset));
if (!$this->config[$offset] instanceof ResourceOwnerConfig)
$this->config[$offset] = new ResourceOwnerConfig($this->config[$offset]);
return $this->config[$offset];
}

@ -30,10 +30,21 @@
<argument type="service" id="hwi_oauth.resource_ownermap.public" />
</service>
<service id="vierbergenlars.authserver_oauth_account.resource_owner_map" class="vierbergenlars\AuthserverOAuthAccountBundle\ResourceOwner\ResourceOwnerMap">
<argument type="collection" />
</service>
<service id="vierbergenlars.authserver_oauth_account.external_account_provider.abstract" class="vierbergenlars\AuthserverOAuthAccountBundle\ExternalAccount\OAuthExternalAccountProvider" abstract="true">
<argument />
<argument />
<argument type="service" id="vierbergenlars.authserver_oauth_account.resource_owner_map" />
<argument type="service" id="hwi_oauth.security.oauth_utils" />
</service>
<service class="vierbergenlars\AuthserverOAuthAccountBundle\EventListener\RegistrationHandlerListener">
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service" id="security.token_storage" />
<argument type="service" id="vierbergenlars.authserver_oauth_account.resource_owner_map" />
<tag name="kernel.event_subscriber" />
</service>
</services>
</container>