From fce2ca821b3bf3dff4c90fe6efe600ab271be7ad Mon Sep 17 00:00:00 2001 From: Lars Vierbergen Date: Mon, 23 Oct 2017 08:23:51 +0200 Subject: [PATCH] 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. --- .../AuthserverOAuthAccountExtension.php | 34 +++++++++++-------- DependencyInjection/Configuration.php | 10 +++--- EventListener/RegistrationHandlerListener.php | 13 ++++++- .../OAuthExternalAccountProvider.php | 21 +++++++----- ResourceOwner/ResourceOwnerConfig.php | 31 +++++++++-------- ResourceOwner/ResourceOwnerMap.php | 24 +++++++++++-- Resources/config/services.xml | 13 ++++++- 7 files changed, 101 insertions(+), 45 deletions(-) diff --git a/DependencyInjection/AuthserverOAuthAccountExtension.php b/DependencyInjection/AuthserverOAuthAccountExtension.php index f7512c5..d7b58b7 100644 --- a/DependencyInjection/AuthserverOAuthAccountExtension.php +++ b/DependencyInjection/AuthserverOAuthAccountExtension.php @@ -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); } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 0649397..eb10739 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -17,7 +17,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - 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 diff --git a/EventListener/RegistrationHandlerListener.php b/EventListener/RegistrationHandlerListener.php index 6da310a..6029145 100644 --- a/EventListener/RegistrationHandlerListener.php +++ b/EventListener/RegistrationHandlerListener.php @@ -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); } diff --git a/ExternalAccount/OAuthExternalAccountProvider.php b/ExternalAccount/OAuthExternalAccountProvider.php index 46ceec0..8b87dee 100644 --- a/ExternalAccount/OAuthExternalAccountProvider.php +++ b/ExternalAccount/OAuthExternalAccountProvider.php @@ -17,41 +17,46 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - 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() diff --git a/ResourceOwner/ResourceOwnerConfig.php b/ResourceOwner/ResourceOwnerConfig.php index e3fd3d0..9b9b5bd 100644 --- a/ResourceOwner/ResourceOwnerConfig.php +++ b/ResourceOwner/ResourceOwnerConfig.php @@ -17,20 +17,19 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - 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; } - } diff --git a/ResourceOwner/ResourceOwnerMap.php b/ResourceOwner/ResourceOwnerMap.php index 7af6988..24377dd 100644 --- a/ResourceOwner/ResourceOwnerMap.php +++ b/ResourceOwner/ResourceOwnerMap.php @@ -17,11 +17,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - 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]; } diff --git a/Resources/config/services.xml b/Resources/config/services.xml index ef51dfb..caddaab 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -30,10 +30,21 @@ + + + + - + + + + + + + +