<?php
/*
* This file is part of the HWIOAuthBundle package.
*
* (c) Hardware.Info <opensource@hardware.info>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace HWI\Bundle\OAuthBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* Configuration for the extension.
*
* @author Alexander <iam.asm89@gmail.com>
*/
class Configuration implements ConfigurationInterface
{
/**
* Array of supported resource owners, indentation is intentional to easily notice
* which resource is of which type.
*
* @var array
*/
private static $resourceOwners = array(
'oauth2' => array(
'amazon',
'asana',
'auth0',
'azure',
'bitbucket2',
'bitly',
'box',
'bufferapp',
'clever',
'dailymotion',
'deviantart',
'deezer',
'disqus',
'eve_online',
'eventbrite',
'facebook',
'fiware',
'foursquare',
'github',
'gitlab',
'google',
'youtube',
'hubic',
'instagram',
'jawbone',
'linkedin',
'mailru',
'odnoklassniki',
'office365',
'paypal',
'qq',
'reddit',
'runkeeper',
'salesforce',
'sensio_connect',
'sina_weibo',
'slack',
'spotify',
'soundcloud',
'stack_exchange',
'strava',
'toshl',
'trakt',
'twitch',
'vkontakte',
'windows_live',
'wordpress',
'wunderlist',
'yandex',
'37signals',
'itembase',
),
'oauth1' => array(
'bitbucket',
'discogs',
'dropbox',
'flickr',
'jira',
'stereomood',
'trello',
'twitter',
'xing',
'yahoo',
),
);
/**
* Return the type (OAuth1 or OAuth2) of given resource owner.
*
* @param string $resourceOwner
*
* @return string
*/
public static function getResourceOwnerType($resourceOwner)
{
$resourceOwner = strtolower($resourceOwner);
if ('oauth1' === $resourceOwner || 'oauth2' === $resourceOwner) {
return $resourceOwner;
}
if (in_array($resourceOwner, static::$resourceOwners['oauth1'], true)) {
return 'oauth1';
}
return 'oauth2';
}
/**
* Checks that given resource owner is supported by this bundle.
*
* @param string $resourceOwner
*
* @return bool
*/
public static function isResourceOwnerSupported($resourceOwner)
{
$resourceOwner = strtolower($resourceOwner);
if ('oauth1' === $resourceOwner || 'oauth2' === $resourceOwner) {
return true;
}
if (in_array($resourceOwner, static::$resourceOwners['oauth1'], true)) {
return true;
}
return in_array($resourceOwner, static::$resourceOwners['oauth2'], true);
}
/**
* Generates the configuration tree builder.
*
* @return TreeBuilder $builder The tree builder
*/
public function getConfigTreeBuilder()
{
$builder = new TreeBuilder();
$rootNode = $builder->root('hwi_oauth');
$rootNode
->fixXmlConfig('firewall_name')
->children()
->arrayNode('firewall_names')
->isRequired()
->requiresAtLeastOneElement()
->prototype('scalar')->end()
->end()
->scalarNode('target_path_parameter')->defaultNull()->end()
->booleanNode('use_referer')->defaultFalse()->end()
->booleanNode('failed_use_referer')->defaultFalse()->end()
->scalarNode('failed_auth_path')->defaultValue('hwi_oauth_connect')->end()
->scalarNode('grant_rule')
->defaultValue('IS_AUTHENTICATED_REMEMBERED')
->validate()
->ifTrue(function ($role) {
return !('IS_AUTHENTICATED_REMEMBERED' === $role || 'IS_AUTHENTICATED_FULLY' === $role);
})
->thenInvalid('Unknown grant role set "%s".')
->end()
->end()
->end()
;
$this->addHttpClientConfiguration($rootNode);
$this->addConnectConfiguration($rootNode);
$this->addFosubConfiguration($rootNode);
$this->addResourceOwnersConfiguration($rootNode);
return $builder;
}
private function addResourceOwnersConfiguration(ArrayNodeDefinition $node)
{
$node
->fixXmlConfig('resource_owner')
->children()
->arrayNode('resource_owners')
->isRequired()
->useAttributeAsKey('name')
->prototype('array')
->ignoreExtraKeys()
->children()
->scalarNode('base_url')->end()
->scalarNode('access_token_url')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('authorization_url')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('request_token_url')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('revoke_token_url')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('infos_url')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('client_id')->cannotBeEmpty()->end()
->scalarNode('client_secret')->cannotBeEmpty()->end()
->scalarNode('realm')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('scope')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('user_response_class')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('service')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('class')
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->scalarNode('type')
->validate()
->ifTrue(function ($type) {
return !Configuration::isResourceOwnerSupported($type);
})
->thenInvalid('Unknown resource owner type "%s".')
->end()
->validate()
->ifTrue(function ($v) {
return empty($v);
})
->thenUnset()
->end()
->end()
->arrayNode('paths')
->useAttributeAsKey('name')
->prototype('variable')
->validate()
->ifTrue(function ($v) {
if (null === $v) {
return true;
}
if (is_array($v)) {
return 0 === count($v);
}
if (is_string($v)) {
return empty($v);
}
return !is_numeric($v);
})
->thenInvalid('Path can be only string or array type.')
->end()
->end()
->end()
->arrayNode('options')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->end()
->validate()
->ifTrue(function ($c) {
// skip if this contains a service
if (isset($c['service'])) {
return false;
}
// for each type at least these have to be set
foreach (array('type', 'client_id', 'client_secret') as $child) {
if (!isset($c[$child])) {
return true;
}
}
return false;
})
->thenInvalid("You should set at least the 'type', 'client_id' and the 'client_secret' of a resource owner.")
->end()
->validate()
->ifTrue(function ($c) {
// Skip if this contains a service or a class
if (isset($c['service']) || isset($c['class'])) {
return false;
}
// Only validate the 'oauth2' and 'oauth1' type
if ('oauth2' !== $c['type'] && 'oauth1' !== $c['type']) {
return false;
}
$children = array('authorization_url', 'access_token_url', 'request_token_url', 'infos_url');
foreach ($children as $child) {
// This option exists only for OAuth1.0a
if ('request_token_url' === $child && 'oauth2' === $c['type']) {
continue;
}
if (!isset($c[$child])) {
return true;
}
}
return false;
})
->thenInvalid("All parameters are mandatory for types 'oauth2' and 'oauth1'. Check if you're missing one of: 'access_token_url', 'authorization_url', 'infos_url' and 'request_token_url' for 'oauth1'.")
->end()
->validate()
->ifTrue(function ($c) {
// skip if this contains a service
if (isset($c['service']) || isset($c['class'])) {
return false;
}
// Only validate the 'oauth2' and 'oauth1' type
if ('oauth2' !== $c['type'] && 'oauth1' !== $c['type']) {
return false;
}
// one of this two options must be set
if (0 === count($c['paths'])) {
return !isset($c['user_response_class']);
}
foreach (array('identifier', 'nickname', 'realname') as $child) {
if (!isset($c['paths'][$child])) {
return true;
}
}
return false;
})
->thenInvalid("At least the 'identifier', 'nickname' and 'realname' paths should be configured for 'oauth2' and 'oauth1' types.")
->end()
->validate()
->ifTrue(function ($c) {
if (isset($c['service'])) {
// ignore paths & options if none were set
return 0 !== count($c['paths']) || 0 !== count($c['options']) || 3 < count($c);
}
return false;
})
->thenInvalid("If you're setting a 'service', no other arguments should be set.")
->end()
->validate()
->ifTrue(function ($c) {
if (!isset($c['class'])) {
return false;
}
return 'oauth2' !== $c['type'] && 'oauth1' !== $c['type'];
})
->thenInvalid("If you're setting a 'class', you must provide a 'oauth1' or 'oauth2' type")
->end()
->end()
->end()
->end()
;
}
private function addHttpClientConfiguration(ArrayNodeDefinition $node)
{
$node
->children()
->arrayNode('http')
->addDefaultsIfNotSet()
->children()
->scalarNode('client')->defaultValue('httplug.client.default')->end()
->scalarNode('message_factory')->defaultValue('httplug.message_factory.default')->end()
->end()
->end()
->end()
;
}
private function addConnectConfiguration(ArrayNodeDefinition $node)
{
$node
->children()
->arrayNode('connect')
->children()
->booleanNode('confirmation')->defaultTrue()->end()
->scalarNode('account_connector')->cannotBeEmpty()->end()
->scalarNode('registration_form_handler')->cannotBeEmpty()->end()
->scalarNode('registration_form')->cannotBeEmpty()->end()
->end()
->end()
->end()
;
}
private function addFosubConfiguration(ArrayNodeDefinition $node)
{
$node
->children()
->arrayNode('fosub')
->children()
->scalarNode('username_iterations')->defaultValue(5)->cannotBeEmpty()->end()
->arrayNode('properties')
->isRequired()
->useAttributeAsKey('name')
->prototype('scalar')
->end()
->end()
->end()
->end()
->end()
;
}
}