diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index ab82de3..ea3a54f 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -20,22 +20,7 @@ public function getConfigTreeBuilder() $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('happy_r_google_api'); - $rootNode - ->children() - ->scalarNode('application_name')->isRequired()->cannotBeEmpty()->end() - ->scalarNode('oauth2_client_id')->isRequired()->cannotBeEmpty()->end() - ->scalarNode('oauth2_client_secret')->isRequired()->cannotBeEmpty()->end() - ->scalarNode('oauth2_redirect_uri')->isRequired()->cannotBeEmpty()->end() - ->scalarNode('developer_key')->isRequired()->cannotBeEmpty()->end() - ->scalarNode('site_name')->isRequired()->cannotBeEmpty()->end() - - ->scalarNode('authClass')->end() - ->scalarNode('ioClass')->end() - ->scalarNode('cacheClass')->end() - ->scalarNode('basePath')->end() - ->scalarNode('ioFileCache_directory')->end() - //end rootnode children - ->end(); + $this->configureAccountNode($rootNode); //let use the api defaults //$this->addServicesSection($rootNode); @@ -43,7 +28,6 @@ public function getConfigTreeBuilder() return $treeBuilder; } - /** * Add the service section * @@ -144,4 +128,100 @@ private function addServicesSection(ArrayNodeDefinition $rootNode) ; } + + /** + * Add properties and validation for account configuration. + * + * @param ArrayNodeDefinition $node + */ + private function configureAccountNode(ArrayNodeDefinition $node) + { + $node + ->beforeNormalization() + ->ifTrue(function ($v) { return is_array($v) && !array_key_exists('accounts', $v) && !array_key_exists('account', $v); }) + ->then(function ($v) { + // Key that should not be rewritten to the account config + $excludedKeys = array('default_account' => true); + $account = array(); + + foreach ($v as $key => $value) { + if (isset($excludedKeys[$key])) { + continue; + } + + $account[$key] = $v[$key]; + unset($v[$key]); + } + + $v['default_account'] = isset($v['default_account']) ? (string) $v['default_account'] : 'default'; + $v['accounts'] = array($v['default_account'] => $account); + + return $v; + }) + ->end() + ->children() + ->scalarNode('default_account')->cannotBeEmpty()->defaultValue('default')->end() + ->end() + ->fixXmlConfig('account') + ->children() + ->arrayNode('accounts') + ->isRequired() + ->requiresAtLeastOneElement() + ->useAttributeAsKey('name') + ->prototype('array') + ->validate() + ->always( + function ($v) { + $required = array(); + + switch ($v['type']) { + case 'web': + $required = array('oauth2_client_secret', 'oauth2_redirect_uri', 'developer_key', 'site_name'); + break; + + case 'service': + if ((isset($v['getenv']) && true === $v['getenv']) || isset($v['json_file']) || isset($v['access_token'])) { + return $v; + } + + $required = array('oauth2_client_email', 'oauth2_private_key', 'oauth2_scopes'); + break; + } + + foreach ($required as $key) { + if (!isset($v[$key]) || empty($v[$key])) { + throw new \InvalidArgumentException(sprintf('"%s" is not set or empty', $key)); + } + } + + return $v; + } + ) + ->end() + ->children() + ->enumNode('type')->values(array('web', 'service'))->cannotBeEmpty()->defaultValue('web')->end() + ->scalarNode('application_name')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('oauth2_client_id')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('oauth2_client_secret')->end() + ->scalarNode('oauth2_client_email')->end() + ->variableNode('access_token')->end() + ->scalarNode('oauth2_private_key')->end() + ->scalarNode('oauth2_redirect_uri')->end() + ->variableNode('oauth2_scopes')->end() + ->scalarNode('developer_key')->end() + ->scalarNode('site_name')->end() + ->scalarNode('getenv')->end() + ->scalarNode('json_file')->end() + + ->scalarNode('authClass')->end() + ->scalarNode('ioClass')->end() + ->scalarNode('cacheClass')->end() + ->scalarNode('basePath')->end() + ->scalarNode('ioFileCache_directory')->end() + ->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/DependencyInjection/HappyRGoogleApiExtension.php b/DependencyInjection/HappyRGoogleApiExtension.php index ec78141..6408fb3 100644 --- a/DependencyInjection/HappyRGoogleApiExtension.php +++ b/DependencyInjection/HappyRGoogleApiExtension.php @@ -3,28 +3,72 @@ namespace HappyR\Google\ApiBundle\DependencyInjection; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\Config\FileLocator; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension; use Symfony\Component\DependencyInjection\Loader; -/** - * 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 HappyRGoogleApiExtension extends Extension +class HappyRGoogleApiExtension extends ConfigurableExtension { /** * {@inheritDoc} */ - public function load(array $configs, ContainerBuilder $container) + public function loadInternal(array $config, ContainerBuilder $container) { - $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); + foreach ($config['accounts'] as $name => $account) { + $this->loadAccount($name, $account, $container); + } - $container->setParameter('happy_r_google_api', $config); + // Backwards compatibility + $default = $config['default_account']; + $container->setParameter('happy_r_google_api', array_merge($config['accounts'][$default], $config)); + $container->setAlias('happyr.google.api.client', sprintf('happyr.google.api.%s_client', $default)); + $container->setAlias('happyr.google.api.analytics', sprintf('happyr.google.api.%s_analytics', $default)); + $container->setAlias('happyr.google.api.youtube', sprintf('happyr.google.api.%s_youtube', $default)); + } + + /** + * Define services for each account configuration. + * + * @param string $name The account name + * @param array $config The account configuration + * @param ContainerBuilder $container The container builder + */ + public function loadAccount($name, array $config, ContainerBuilder $container) + { + $clientId = sprintf('happyr.google.api.%s_client', $name); + $client = new Definition( + 'HappyR\Google\ApiBundle\Services\GoogleClient', + array($config, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)) + ); + + $client->addTag('monolog.logger', array('channel' => 'google_client')); + + $container->setDefinition($clientId, $client); + + $container->setDefinition( + sprintf('happyr.google.api.%s_analytics', $name), + new Definition( + 'HappyR\Google\ApiBundle\Services\AnalyticsService', + array(new Reference($clientId)) + ) + ); + + $container->setDefinition( + sprintf('happyr.google.api.%s_youtube', $name), + new Definition( + 'HappyR\Google\ApiBundle\Services\YoutubeService', + array(new Reference($clientId)) + ) + ); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('services.yml'); + $container->setDefinition( + sprintf('happyr.google.api.%s_groups_migration', $name), + new Definition( + 'HappyR\Google\ApiBundle\Services\GroupsMigrationService', + array(new Reference($clientId)) + ) + ); } } diff --git a/README.md b/README.md index 30bb14d..2c4bc6a 100644 --- a/README.md +++ b/README.md @@ -60,5 +60,45 @@ happy_r_google_api: site_name: mysite.com ``` +#### Advanced configuration + +You can set up multiple accounts, including service accounts. + +``` yaml +# app/config/config.yml +happy_r_google_api: + accounts: + + # regular web authentication + default: + type: web + application_name: MySite + oauth2_client_id: + oauth2_client_secret: + oauth2_redirect_uri: + developer_key: + site_name: mysite.com + + # Credentials from GOOGLE_APPLICATION_CREDENTIALS environment variable (recommended) + service_env: + type: service + getenv: true + + # Credentials from service-account.json + service_file: + type: service + json_file: /path/to/your/service-account.json + + # with service credentials, example to access Google Analytics + service_account: + type: service + application_name: MySite + oauth2_client_id: + oauth2_client_email: + oauth2_private_key: + oauth2_scopes: + - https://www.googleapis.com/auth/analytics.readonly +``` + [1]: https://github.com/google/google-api-php-client diff --git a/Resources/config/services.yml b/Resources/config/services.yml deleted file mode 100644 index b5d59ff..0000000 --- a/Resources/config/services.yml +++ /dev/null @@ -1,18 +0,0 @@ -services: - happyr.google.api.client: - class: HappyR\Google\ApiBundle\Services\GoogleClient - arguments: ["%happy_r_google_api%", "@?logger"] - tags: - - { name: monolog.logger, channel: google_client } - - happyr.google.api.analytics: - class: HappyR\Google\ApiBundle\Services\AnalyticsService - arguments: ["@happyr.google.api.client"] - - happyr.google.api.youtube: - class: HappyR\Google\ApiBundle\Services\YoutubeService - arguments: ["@happyr.google.api.client"] - - happyr.google.api.groups_migration: - class: HappyR\Google\ApiBundle\Services\GroupsMigrationService - arguments: ["@happyr.google.api.client"] diff --git a/Services/GoogleClient.php b/Services/GoogleClient.php index 066c91c..3905de4 100644 --- a/Services/GoogleClient.php +++ b/Services/GoogleClient.php @@ -36,13 +36,57 @@ public function __construct(array $config, LoggerInterface $symfonyLogger = null } } - $client -> setApplicationName($config['application_name']); - $client -> setClientId($config['oauth2_client_id']); - $client -> setClientSecret($config['oauth2_client_secret']); - $client -> setRedirectUri($config['oauth2_redirect_uri']); - $client -> setDeveloperKey($config['developer_key']); + $client->setApplicationName($config['application_name']); + $client->setClientId($config['oauth2_client_id']); - $this -> client = $client; + switch ($config['type']) { + case 'web': + $client->setClientSecret($config['oauth2_client_secret']); + $client->setRedirectUri($config['oauth2_redirect_uri']); + $client->setDeveloperKey($config['developer_key']); + break; + + case 'service': + $client->setAccessType('offline'); + $client->setClientSecret($config['oauth2_client_secret']); + $client->setRedirectUri($config['oauth2_redirect_uri']); + + if (isset($config['oauth2_scopes'])) { + $client->setScopes($config['oauth2_scopes']); + } + + if (isset($config['getenv']) && true === $config['getenv']) { + $client->useApplicationDefaultCredentials(); + + } else if (isset($config['json_file'])) { + $client->setAuthConfigFile($config['json_file']); + + } else if (isset($config['access_token'])) { + $client->setAccessToken($config['access_token']); + + } else if (class_exists('\Google_Auth_AssertionCredentials')) { + //BC for Google API 1.0 + $client->setAssertionCredentials( + new \Google_Auth_AssertionCredentials( + $config['oauth2_client_email'], + $config['oauth2_scopes'], + $config['oauth2_private_key'] + ) + ); + } else { + $client->setAuthConfig( + array( + 'type' => 'service_account', + 'client_id' => $config['oauth2_client_id'], + 'client_email' => $config['oauth2_client_email'], + 'private_key' => $config['oauth2_private_key'], + ) + ); + } + break; + } + + $this->client = $client; } /** diff --git a/composer.json b/composer.json index e5b809f..9f5ecff 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,8 @@ ], "require": { "php": ">=5.3.2", - "symfony/framework-bundle": "2.*|~3.0", - "symfony/yaml": "2.*|~3.0", + "symfony/framework-bundle": "2.*|~3.0|^4.0|^5.0|^6.0", + "symfony/yaml": "2.*|~3.0|^4.0|^5.0|^6.0", "google/apiclient": "1.*|2.*" }, "require-dev": {