├── DependencyInjection ├── KnpOAuthExtension.php └── Security │ └── Factory │ └── OAuthFactory.php ├── KnpOAuthBundle.php ├── README.md ├── Resources ├── config │ └── oauth.xml ├── doc │ ├── 01_index.md │ ├── 02_installation.md │ ├── 03_configuration.md │ ├── 04_builtin_oauth_providers.md │ ├── 05_custom_oauth_providers.md │ ├── 06_builtin_user_providers.md │ └── 07_cookbooks.md └── meta │ └── LICENSE ├── Security ├── Core │ ├── Authentication │ │ ├── Provider │ │ │ └── OAuthProvider.php │ │ └── Token │ │ │ └── OAuthToken.php │ ├── User │ │ └── OAuthUser.php │ └── UserProvider │ │ ├── EntityFactory.php │ │ ├── EntityUserProvider.php │ │ └── OAuthUserProvider.php └── Http │ ├── EntryPoint │ └── OAuthEntryPoint.php │ ├── Firewall │ └── OAuthListener.php │ └── OAuth │ ├── FacebookProvider.php │ ├── GithubProvider.php │ ├── GoogleProvider.php │ ├── OAuthProvider.php │ └── OAuthProviderInterface.php └── composer.json /DependencyInjection/KnpOAuthExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\DependencyInjection; 13 | 14 | use Symfony\Component\HttpKernel\DependencyInjection\Extension, 15 | Symfony\Component\DependencyInjection\Loader\XmlFileLoader, 16 | Symfony\Component\DependencyInjection\ContainerBuilder, 17 | Symfony\Component\Config\FileLocator; 18 | 19 | /** 20 | * KnpOAuthExtension 21 | * 22 | * @author Geoffrey Bachelet 23 | */ 24 | class KnpOAuthExtension extends Extension 25 | { 26 | /** 27 | * @{inheritDoc} 28 | */ 29 | public function load(array $configs, ContainerBuilder $container) 30 | { 31 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config/')); 32 | $loader->load('oauth.xml'); 33 | } 34 | } -------------------------------------------------------------------------------- /DependencyInjection/Security/Factory/OAuthFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\DependencyInjection\Security\Factory; 13 | 14 | use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory, 15 | Symfony\Component\DependencyInjection\ContainerBuilder, 16 | Symfony\Component\DependencyInjection\DefinitionDecorator, 17 | Symfony\Component\DependencyInjection\Reference, 18 | Symfony\Component\Config\Definition\Builder\NodeDefinition; 19 | 20 | /** 21 | * OAuthFactory 22 | * 23 | * @author Geoffrey Bachelet 24 | */ 25 | class OAuthFactory extends AbstractFactory 26 | { 27 | /** 28 | * Creates an OAuth provider for a given firewall 29 | * 30 | * @param Symfony\Component\DependencyInjection\ContainerBuilder $container 31 | * @param string $id The firewall id 32 | * @param array $config The firewall config 33 | * @return string The OAuth provider service id 34 | */ 35 | protected function createOAuthProvider(ContainerBuilder $container, $id, $config) 36 | { 37 | if (false !== strpos($config['oauth_provider'], '.')) { 38 | $baseOAuthProviderId = $config['oauth_provider']; 39 | } else { 40 | $baseOAuthProviderId = 'knp_oauth.security.oauth.'.$config['oauth_provider'].'_provider'; 41 | } 42 | 43 | $oauthProviderId = $baseOAuthProviderId.'.'.$id; 44 | 45 | $container 46 | ->setDefinition($oauthProviderId, new DefinitionDecorator($baseOAuthProviderId)) 47 | ->addArgument(new Reference('buzz.client')) 48 | ->addArgument(new Reference('security.http_utils')) 49 | ->addArgument($config); 50 | 51 | return $oauthProviderId; 52 | } 53 | 54 | /** 55 | * {@inheritDoc} 56 | */ 57 | protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId) 58 | { 59 | $providerId = 'knp_oauth.authentication.provider.oauth.'.$id; 60 | $oauthProviderId = $this->createOAuthProvider($container, $id, $config); 61 | 62 | $container 63 | ->setDefinition($providerId, new DefinitionDecorator('knp_oauth.authentication.provider.oauth')) 64 | ->addArgument(new Reference($userProviderId)) 65 | ->addArgument(new Reference($oauthProviderId)); 66 | 67 | return $providerId; 68 | } 69 | 70 | /** 71 | * {@inheritDoc} 72 | */ 73 | protected function createEntryPoint($container, $id, $config, $defaultEntryPoint) 74 | { 75 | $entryPointId = 'knp_oauth.authentication.entry_point.oauth.'.$id; 76 | $oauthProviderId = $this->createOAuthProvider($container, $id, $config); 77 | 78 | $container 79 | ->setDefinition($entryPointId, new DefinitionDecorator('knp_oauth.authentication.entry_point.oauth')) 80 | ->addArgument(new Reference('security.http_utils')) 81 | ->addArgument(new Reference($oauthProviderId)) 82 | ->addArgument($config['check_path']) 83 | ->addArgument($config['login_path']) 84 | ; 85 | 86 | return $entryPointId; 87 | } 88 | 89 | /** 90 | * {@inheritDoc} 91 | */ 92 | protected function createListener($container, $id, $config, $userProvider) 93 | { 94 | $listenerId = parent::createListener($container, $id, $config, $userProvider); 95 | $oauthProviderId = $this->createOAuthProvider($container, $id, $config); 96 | 97 | $container->getDefinition($listenerId) 98 | ->addMethodCall('setOAuthProvider', array(new Reference($oauthProviderId))) 99 | ; 100 | 101 | return $listenerId; 102 | } 103 | 104 | /** 105 | * {@inheritDoc} 106 | */ 107 | public function addConfiguration(NodeDefinition $node) 108 | { 109 | parent::addConfiguration($node); 110 | 111 | $builder = $node->children(); 112 | 113 | $builder 114 | ->scalarNode('oauth_provider') 115 | ->defaultValue('oauth') 116 | ->end() 117 | ->scalarNode('authorization_url') 118 | ->defaultNull() 119 | ->end() 120 | ->scalarNode('access_token_url') 121 | ->defaultNull() 122 | ->end() 123 | ->scalarNode('infos_url') 124 | ->defaultNull() 125 | ->end() 126 | ->scalarNode('username_path') 127 | ->defaultNull() 128 | ->end() 129 | ->scalarNode('client_id') 130 | ->cannotBeEmpty() 131 | ->isRequired() 132 | ->end() 133 | ->scalarNode('scope') 134 | ->isRequired() 135 | ->end() 136 | ->scalarNode('secret') 137 | ->cannotBeEmpty() 138 | ->isRequired() 139 | ->end() 140 | ; 141 | } 142 | 143 | /** 144 | * {@inheritDoc} 145 | */ 146 | protected function getListenerId() 147 | { 148 | return 'knp_oauth.authentication.listener.oauth'; 149 | } 150 | 151 | /** 152 | * {@inheritDoc} 153 | */ 154 | public function getKey() 155 | { 156 | return 'oauth'; 157 | } 158 | 159 | /** 160 | * {@inheritDoc} 161 | */ 162 | public function getPosition() 163 | { 164 | return 'http'; 165 | } 166 | } -------------------------------------------------------------------------------- /KnpOAuthBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle; 13 | 14 | use Symfony\Component\HttpKernel\Bundle\Bundle, 15 | Symfony\Component\DependencyInjection\ContainerBuilder; 16 | 17 | use Knp\Bundle\OAuthBundle\DependencyInjection\Security\Factory\OAuthFactory, 18 | Knp\Bundle\OAuthBundle\Security\Core\UserProvider\EntityFactory; 19 | 20 | /** 21 | * KnpOAuthBundle 22 | * 23 | * @author Geoffrey Bachelet 24 | */ 25 | class KnpOAuthBundle extends Bundle 26 | { 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | public function build(ContainerBuilder $container) 31 | { 32 | parent::build($container); 33 | 34 | $extension = $container->getExtension('security'); 35 | $extension->addSecurityListenerFactory(new OAuthFactory()); 36 | $extension->addUserProviderFactory(new EntityFactory('entity', 'doctrine.orm.security.user.provider')); 37 | } 38 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project is no longer maintained by KnpLabs 2 | Use [HWIOAuthBundle](https://github.com/hwi/HWIOAuthBundle) instead 3 | ------------------------------------------------------------------- 4 | 5 | # KnpOAuthBundle, an OAuth firewall for Symfony2 6 | 7 | ## Instructions for 2.1 branch 8 | 9 | Please note this is the 2.1 compatible branch of this bundle. If you are using Symfony 2.0.\*, then you should upgrade. If you can't, you can still use the [2.0 branch](https://github.com/KnpLabs/KnpOAuthBundle/tree/2.0). 10 | 11 | ## Introduction 12 | 13 | This bundle provides an OAuth firewall for Symfony2. 14 | 15 | You should really read the [full documentation](https://github.com/KnpLabs/KnpOAuthBundle/blob/master/Resources/doc/01_index.md), but if you're in a hurry (I know you are), this file should help you quickly getting a working setup. 16 | 17 | ## Requirements 18 | 19 | * Symfony (_2.1 (master branch) or later_) 20 | * Dependencies: 21 | * [`Buzz`](https://github.com/kriswallsmith/Buzz) (_0.5 or later_) 22 | * [`SensioBuzzBundle`](https://github.com/sensio/SensioBuzzBundle) 23 | 24 | ## Installation 25 | 26 | Add this to your `deps`: 27 | 28 | [Buzz] 29 | git=https://github.com/kriswallsmith/Buzz.git 30 | version=v0.5 31 | 32 | [BuzzBundle] 33 | git=https://github.com/sensio/SensioBuzzBundle.git 34 | target=/bundles/Sensio/Bundle/BuzzBundle 35 | 36 | [KnpOAuthBundle] 37 | git=https://github.com/KnpLabs/KnpOAuthBundle.git 38 | target=/bundles/Knp/Bundle/OAuthBundle 39 | 40 | Then run the usual `bin/vendors`: 41 | 42 | bin/vendors install 43 | 44 | Register autoloads: 45 | 46 | $loader->registerNamespaces(array( 47 | 'Knp' => __DIR__.'/../vendor/bundles', 48 | 'Buzz' => __DIR__.'/../vendor/Buzz/lib' 49 | )); 50 | 51 | Register the bundles in your `AppKernel`: 52 | 53 | $bundles = array( 54 | new Knp\Bundle\OAuthBundle\KnpOAuthBundle(), 55 | new Sensio\Bundle\BuzzBundle\SensioBuzzBundle(), 56 | ); 57 | 58 | ## Configuration 59 | 60 | Using the `KnpOAuthBundle` is just a matter of configuring an `oauth` firewall in your `security.yml`. The bundle exposes a number of configuration directives to suit your oauth needs. Here's a pretty standard security configuration: 61 | 62 | security: 63 | firewalls: 64 | login: 65 | pattern: ^/secured/login$ 66 | security: false 67 | secured_area: 68 | pattern: ^/secured/ 69 | oauth: 70 | oauth_provider: oauth 71 | authorization_url: https://github.com/login/oauth/authorize 72 | access_token_url: https://github.com/login/oauth/access_token 73 | infos_url: https://github.com/api/v2/json/user/show 74 | username_path: user.login 75 | client_id: 76 | secret: 77 | scope: 78 | check_path: /secured/login_check 79 | login_path: /secured/login 80 | 81 | Please see [the configuration reference](https://github.com/KnpLabs/KnpOAuthBundle/blob/master/Resources/doc/03_configuration.md) for a description of the configuration options. 82 | 83 | Right now, what you probably want to know is that this bundle comes with a few pre-configured oauth provider, namely: 84 | 85 | * `github` (required options: `client_id`, `secret`) 86 | * er... that's all for now. 87 | 88 | If you don't see your favorite provider in the list, don't worry, there are three solutions, depending on how much of a hurry you're in: 89 | 90 | 1. [Implement it](https://github.com/KnpLabs/KnpOAuthBundle/blob/master/Resources/doc/05_custom_oauth_providers.md) (and it would be awesome if you contributed it afterwards) 91 | 2. [Use the generic OAuth provider](https://github.com/KnpLabs/KnpOAuthBundle/blob/master/Resources/doc/04_builtin_oauth_providers.md) 92 | 3. [Ask us to implement it](https://github.com/KnpLabs/KnpOAuthBundle/issues/new). (please provide as much information as possible (`authorize_url`, `access_token_url`, `infos_url` (with its response format) and `username_path` would be nice)) 93 | 94 | ## User providers 95 | 96 | Most of the time, if you are using Doctrine, you will want to use the `EntityUserProvider`. 97 | 98 | This provider fetches users from the database and creates them on the fly if they don't already exist. It requires Doctrine to work. It works exactly like Doctrine's entity user provider, except its configuration key is `oauth_entity`: 99 | 100 | providers: 101 | secured_area: 102 | oauth_entity: 103 | class: KnpBundlesBundle:User 104 | property: name 105 | -------------------------------------------------------------------------------- /Resources/config/oauth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Knp\Bundle\OAuthBundle\Security\Http\Firewall\OAuthListener 9 | Knp\Bundle\OAuthBundle\Security\Core\Authentication\Provider\OAuthProvider 10 | Knp\Bundle\OAuthBundle\Security\Http\EntryPoint\OAuthEntryPoint 11 | Knp\Bundle\OAuthBundle\Security\Core\UserProvider\OAuthUserProvider 12 | Knp\Bundle\OAuthBundle\Security\Core\UserProvider\EntityUserProvider 13 | Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProvider 14 | Knp\Bundle\OAuthBundle\Security\Http\OAuth\GithubProvider 15 | Knp\Bundle\OAuthBundle\Security\Http\OAuth\FacebookProvider 16 | Knp\Bundle\OAuthBundle\Security\Http\OAuth\GoogleProvider 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Resources/doc/01_index.md: -------------------------------------------------------------------------------- 1 | # KnpOAuthBundle 2 | 3 | This bundle implements an OAuth firewall for use in your Symfony2 application. 4 | 5 | ## Requirements 6 | 7 | * Symfony version: _2.1 or later_ 8 | * Dependencies: 9 | * [`SensioBuzzBundle`](https://github.com/sensio/SensioBuzzBundle) 10 | * [`Buzz`](https://github.com/kriswallsmith/Buzz) 11 | 12 | ## Table of content 13 | 14 | * [Installation](02_installation.md) 15 | * [Configuration](03_configuration.md) 16 | * [Built-in OAuth Providers](04_builtin_oauth_providers.md) 17 | * [Custom OAuth Providers](05_custom_oauth_providers.md) 18 | * [Built-in User Providers](06_builtin_user_providers.md) 19 | * [Cookbooks](07_cookbooks.md) -------------------------------------------------------------------------------- /Resources/doc/02_installation.md: -------------------------------------------------------------------------------- 1 | # KnpOAuthBundle 2 | 3 | ## Installation 4 | 5 | Installing the `KnpOAuthBundle` looks a lot like installing any other bundle. 6 | 7 | This bundle requires [`SensioBuzzBundle`](https://github.com/sensio/SensioBuzzBundle) to work, which in turn requires the [`Buzz`](https://github.com/kriswallsmith/Buzz) library. So add the following lines to your `deps` file: 8 | 9 | [Buzz] 10 | git=https://github.com/kriswallsmith/Buzz.git 11 | version=v0.5 12 | 13 | [BuzzBundle] 14 | git=https://github.com/sensio/SensioBuzzBundle.git 15 | target=/bundles/Sensio/Bundle/BuzzBundle 16 | 17 | [KnpOAuthBundle] 18 | git=https://github.com/KnpLabs/KnpOAuthBundle.git 19 | target=/bundles/Knp/Bundle/OAuthBundle 20 | 21 | Of course, if you already use `Buzz` and/or the `SensioBuzzBundle`, don't add them again. 22 | 23 | Then you need to update your vendors: 24 | 25 | bin/vendors install 26 | 27 | If you don't already have bundles in the `Knp` or `Sensio` namespaces, you will need to register these namespaces to your `app/autoload.php`: 28 | 29 | $loader->registerNamespaces(array( 30 | 'Knp\\Bundle' => __DIR__.'/../vendor/bundles', 31 | 'Buzz' => __DIR__.'/../vendor/Buzz/lib' 32 | )); 33 | 34 | You're now ready to register the bundles into your `app/AppKernel.php`: 35 | 36 | $bundles = array( 37 | new Knp\Bundle\OAuthBundle\KnpOAuthBundle(), 38 | new Sensio\Bundle\BuzzBundle\SensioBuzzBundle(), 39 | ); 40 | 41 | 42 | Great! Everything is ready, let's proceed to the [configuration](03_configuration.md)! -------------------------------------------------------------------------------- /Resources/doc/03_configuration.md: -------------------------------------------------------------------------------- 1 | # KnpOAuthBundle 2 | 3 | You should already be familiar with [Symfony2's security configuration](http://symfony.com/doc/current/book/security.html). If you are not, now would be a good time to read it. 4 | 5 | ## Configuration 6 | 7 | In its most basic form, using this bundle is just a matter of creating an OAuth firewall with an `oauth` listener in your security configuration. The bundles exposes a number of configuration directives to fine-tune your OAuth usage. 8 | 9 | Here's a full-fledged example of configuration: 10 | 11 | security: 12 | firewalls: 13 | secured_area: 14 | pattern: ^/secured/ 15 | oauth: 16 | oauth_provider: oauth 17 | authorization_url: https://github.com/login/oauth/authorize 18 | access_token_url: https://github.com/login/oauth/access_token 19 | infos_url: https://github.com/api/v2/json/user/show 20 | username_path: user.login 21 | client_id: 22 | secret: 23 | scope: 24 | check_path: /secured/login_check 25 | login_path: /secured/login 26 | failure_path: / 27 | logout: 28 | path: /secured/logout 29 | target: / 30 | 31 | providers: 32 | main: 33 | oauth_entity: 34 | class: MyBundle:User 35 | property: username 36 | 37 | If you're familiar with the security component, you might have noticed that contrary to the form login listener, we don't disable security on the `login_path`. This is needed for this firewall to work _as expected_ when you hit the login page. 38 | 39 | Please note that most of these options are *optional* under certain conditions. Mostly, when you're using a provider that comes pre-configured with them. See the [built-in OAuth providers page](04_builtin_oauth_providers.md) for more information on that. 40 | 41 | The `check_path`, `login_path` and `failure_path` are standard Symfony2 security configuration directives. 42 | 43 | ### oauth_provider 44 | 45 | *default*: `oauth` 46 | 47 | The `oauth_provider` defines which provider to use. Most OAuth bundles will present you with a set of built-in providers, leaving you no choice of using a custom provider. This bundle is different. It's been built with genericity in mind from the beginning and you can use the `oauth` provider to use virtually any OAuth provider, provided it doesn't break the OAuth specification (for example, you can't use it with github). 48 | 49 | Available providers, as of now, are: 50 | 51 | * `oauth` 52 | * `github` 53 | 54 | ### client_id 55 | 56 | This is provided by your OAuth provider. 57 | 58 | ### secret 59 | 60 | This is provided by your OAuth provider too. 61 | 62 | ### scope 63 | 64 | The scope of the data you wish to retrieve about your users. You can require multiple scopes by separating them with a space. 65 | 66 | ### authorization_url 67 | 68 | The `authorization_url` is the URL used for the first OAuth round-trip. When initiating the authentication procedure, your users will be redirected there and presented, hopefuly, with the provider's OAuth authorization screen. 69 | 70 | ### access_token_url 71 | 72 | Your provider also exposes an URL to retrieve an `access_token`. This token is used to retrieve information about your users. 73 | 74 | ### infos_url 75 | 76 | This is the URL used to retrieve informations about your user. This may or may not be provided by your OAuth provider. The default OAuth provider expects this URL to return a `json` encoded response. If your provider does not return `json` data, you will have to implement a [custom OAuth provider](05_custom_oauth_providers.md). 77 | 78 | Please note that if you set `infos_url`, you must set `username_path` as well. 79 | 80 | ### username_path 81 | 82 | A dot separated path to search `infos_url`'s response for a username. For example, Github returns user information in the form of a `json` structure that looks a bit like that (truncated for clarity): 83 | 84 | { user: { login: 'geoffrey '} } 85 | 86 | In that case, the `username_path` would be `user.login`. 87 | 88 | Please note that if you set `username_path`, you must set `infos_url` as well. 89 | 90 | Now you probably want to know a bit more about [built-in oauth providers](04_builtin_oauth_providers.md). -------------------------------------------------------------------------------- /Resources/doc/04_builtin_oauth_providers.md: -------------------------------------------------------------------------------- 1 | # KnpOAuthBundle 2 | 3 | ## Built-in OAuth Providers 4 | 5 | ### oauth 6 | 7 | This is the most basic provider. It requires all of the available options to work, except `infos_url` and `username_path`. Although it will work without these options, I would not recommend such a setup since it's going to try to load user's using the access token as the username, which is a bit silly to say the least. 8 | 9 | ### github 10 | 11 | A provider pre-configured for [Github](http://github.com/). The only required options are `client_id` and `secret`. 12 | 13 | Defaults for the other options: 14 | 15 | authorize_url: https://github.com/login/oauth/authorize 16 | access_token_url: https://github.com/login/oauth/access_token 17 | infos_url: https://github.com/api/v2/json/user/show 18 | username_path: user.login 19 | scope: ~ 20 | 21 | You can override any of these options. 22 | 23 | Don't see what you need there? Try a [custom provider](05_custom_oauth_providers.md). -------------------------------------------------------------------------------- /Resources/doc/05_custom_oauth_providers.md: -------------------------------------------------------------------------------- 1 | # KnpOAuthBundle 2 | 3 | ## Custom OAuth Providers 4 | 5 | You can of course implement your own custom OAuth provider. The good news is it's a fairly easy three steps process: 6 | 7 | 1. Implement the `Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface` interface 8 | 2. Declare the corresponding service 9 | 3. Configure your firewall 10 | 11 | ### Implementing the interface 12 | 13 | The interface is not too hard to implement, it only consists in three methods: 14 | 15 | * `getUsername($accessToken)` must return the user's username. 16 | * `getAuthorizationUrl($loginCheckUrl, array $extraParameters = array())` must return the provider's authorization url. 17 | * `getAccessToken($code, array $extraParameters = array())` must return an access token. 18 | 19 | Please see `Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProvider` for an example implementation. 20 | 21 | To ease the task even a little more, you can extend the generic `OAuthProvider` provider. This provider comes with a few helper methods: 22 | 23 | * `configure()` is the place to put some custom logic (you can see the `GithubProvider` for an example of that). 24 | * `getOption($name)` retrieves an option, with existance check. 25 | * `httpRequest($url, $method)` is a small wrapper around `Buzz`. 26 | 27 | ### Declaring the DIC service 28 | 29 | Once your provider is implemented, you need to declare it as a DIC service. This step is fairly easy too, you just have to define your service in your configuration, under the `services` section: 30 | 31 | services: 32 | my_bundle.security.oauth.my_provider: 33 | class: MyBundle\Security\Http\OAuth\MyProvider 34 | 35 | See [Symfony's service container documentation](http://symfony.com/doc/current/book/service_container.html#creating-configuring-services-in-the-container) for more information on that. 36 | 37 | Bear with me, we're almost done. 38 | 39 | ### Configuring your firewall 40 | 41 | The `KnpOAuthBundle` tries to be clever, and decides that any OAuth provider containing a dot (`.`) is in fact a DIC service that we want to use. The configuration would then be: 42 | 43 | security: 44 | firewalls: 45 | secured_area: 46 | pattern: ^/secured/ 47 | oauth: 48 | oauth_provider: my_bundle.authentication.entry_point.my_provider 49 | client_id: 50 | secret: 51 | scope: 52 | check_path: /secured/login_check 53 | login_path: /secured/login 54 | failure_path: / 55 | 56 | What options are required is totally up to your provider's implementation, but you will most likely want to pre-configure most of them. 57 | 58 | Hurray! You have an OAuth provider! Will you have a [user provider](06_builtin_user_providers.md) with that? -------------------------------------------------------------------------------- /Resources/doc/06_builtin_user_providers.md: -------------------------------------------------------------------------------- 1 | # KnpOAuthBundle 2 | 3 | ## Built-in User Providers 4 | 5 | ### OAuthUserProvider 6 | 7 | This one does nothing but create valid users with default roles (`ROLE_USER` for now) and using `infos_url` in conjunction with `username_path` to provide `getUsername()`'s result. This `UserProvider` is used to represent *remote* OAuth user, when you don't need to do fancy things with your users, such as managing roles and ACLs. Example usage: 8 | 9 | providers: 10 | secured_area: 11 | id: knp_oauth.user.provider 12 | 13 | ### EntityUserProvider 14 | 15 | This provider fetches users from the database and creates them on the fly if they don't already exist. It requires Doctrine to work. It works exactly like Doctrine's entity user provider, except its configuration key is `oauth_entity`: 16 | 17 | providers: 18 | secured_area: 19 | oauth_entity: 20 | class: KnpBundlesBundle:User 21 | property: name 22 | 23 | Still have some questions? There might be something for you in the [cookbooks](07_cookbooks.md). -------------------------------------------------------------------------------- /Resources/doc/07_cookbooks.md: -------------------------------------------------------------------------------- 1 | # KnpOAuthBundle 2 | 3 | ## Cookbooks 4 | 5 | These cookbooks may or may not be specific to this bundle, but they are use-cases that are likely to be encountered, and for which the solution might not be obvious at the moment. 6 | 7 | ### Requiring full authentication on part of your application 8 | 9 | This is the [KnpBundles](http://knpbundles.com/) setup. Basically, you can browse most of the website anonymously, but you need full authentication for a few actions (registering a bundle, indicating that you recommend a bundle, etc). Here's the corresponding security configuration (this is the actual KnpBundles security configuration): 10 | 11 | security: 12 | firewalls: 13 | secured_area: 14 | anonymous: true 15 | pattern: ^/ 16 | oauth: 17 | oauth_provider: github 18 | client_id: %knp_bundles.github.client_id% 19 | secret: %knp_bundles.github.client_secret% 20 | scope: ~ 21 | check_path: /oauth/github 22 | login_path: /login 23 | failure_path: / 24 | logout: 25 | path: /logout 26 | target: / 27 | 28 | access_control: 29 | - { path: ^/login$, roles: ROLE_USER } 30 | - { path: ^/add, roles: ROLE_USER } 31 | - { path: change-usage-status$, roles: ROLE_USER } 32 | 33 | providers: 34 | secured_area: 35 | oauth_entity: 36 | class: KnpBundlesBundle:User 37 | property: name 38 | 39 | The key here is the `access_control` section. We define a firewall on the entire website that allows anonymous connections (anonymous users are given the `IS_AUTHENTICATED_ANONYMOUSLY` and that's all) so that everyone can browser the site, but still be authenticated in the Symfony2 meaning of the term. 40 | 41 | Then we need to tell Symfony that we want extra-security for some paths, using the `access_control`. We require the `ROLE_USER` role, thus forcing already anonymously authenticated user to re-authenticate to gain higher access privileges. Note that we force full authentication on the `login_path` to enforce using the OAuth Entry Point. -------------------------------------------------------------------------------- /Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 KnpLabs - http://www.knplabs.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Security/Core/Authentication/Provider/OAuthProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Core\Authentication\Provider; 13 | 14 | use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface, 15 | Symfony\Component\Security\Core\Authentication\Token\TokenInterface, 16 | Symfony\Component\Security\Core\User\UserProviderInterface; 17 | 18 | use Knp\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken, 19 | Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface; 20 | 21 | /** 22 | * OAuthProvider 23 | * 24 | * @author Geoffrey Bachelet 25 | */ 26 | class OAuthProvider implements AuthenticationProviderInterface 27 | { 28 | /** 29 | * @var Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface 30 | */ 31 | private $oauthProvider; 32 | 33 | /** 34 | * @var Symfony\Component\Security\Core\User\UserProviderInterface 35 | */ 36 | private $userProvider; 37 | 38 | /** 39 | * @param Symfony\Component\Security\Core\User\UserProviderInterface $userProvider 40 | * @param Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface $oauthProvider 41 | */ 42 | public function __construct(UserProviderInterface $userProvider, OAuthProviderInterface $oauthProvider) 43 | { 44 | $this->userProvider = $userProvider; 45 | $this->oauthProvider = $oauthProvider; 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | public function supports(TokenInterface $token) 52 | { 53 | return $token instanceof OAuthToken; 54 | } 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | public function authenticate(TokenInterface $token) 60 | { 61 | $username = $this->oauthProvider->getUsername($token->getCredentials()); 62 | $user = $this->userProvider->loadUserByUsername($username); 63 | 64 | $token = new OAuthToken($token->getCredentials(), $user->getRoles()); 65 | $token->setUser($user); 66 | $token->setAuthenticated(true); 67 | 68 | return $token; 69 | } 70 | } -------------------------------------------------------------------------------- /Security/Core/Authentication/Token/OAuthToken.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Core\Authentication\Token; 13 | 14 | use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; 15 | 16 | /** 17 | * OAuthToken 18 | * 19 | * @author Geoffrey Bachelet 20 | */ 21 | class OAuthToken extends AbstractToken 22 | { 23 | /** 24 | * @var string 25 | */ 26 | private $accessToken; 27 | 28 | /** 29 | * @param string $accessToken The OAuth access token 30 | * @param array $roles 31 | */ 32 | public function __construct($accessToken, array $roles = array()) 33 | { 34 | $this->accessToken = $accessToken; 35 | 36 | parent::__construct($roles); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public function getCredentials() 43 | { 44 | return $this->accessToken; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | public function isAuthenticated() 51 | { 52 | return count($this->getRoles()) > 0; 53 | } 54 | } -------------------------------------------------------------------------------- /Security/Core/User/OAuthUser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Core\User; 13 | 14 | use Symfony\Component\Security\Core\User\UserInterface; 15 | 16 | /** 17 | * OAuthUser 18 | * 19 | * @author Geoffrey Bachelet 20 | */ 21 | class OAuthUser implements UserInterface 22 | { 23 | /** 24 | * @param string $username 25 | */ 26 | public function __construct($username) 27 | { 28 | $this->username = $username; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function getRoles() 35 | { 36 | return array('ROLE_USER'); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public function getPassword() 43 | { 44 | return null; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | public function getSalt() 51 | { 52 | return null; 53 | } 54 | 55 | /** 56 | * {@inheritDoc} 57 | */ 58 | public function getUsername() 59 | { 60 | return $this->username; 61 | } 62 | 63 | /** 64 | * {@inheritDoc} 65 | */ 66 | public function eraseCredentials() 67 | { 68 | return true; 69 | } 70 | 71 | /** 72 | * {@inheritDoc} 73 | */ 74 | public function equals(UserInterface $user) 75 | { 76 | return $user->getUsername() == $this->getUsername(); 77 | } 78 | } -------------------------------------------------------------------------------- /Security/Core/UserProvider/EntityFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Core\UserProvider; 13 | 14 | use Symfony\Component\DependencyInjection\ContainerBuilder, 15 | Symfony\Component\DependencyInjection\DefinitionDecorator, 16 | Symfony\Component\Config\Definition\Builder\NodeDefinition, 17 | Symfony\Bridge\Doctrine\DependencyInjection\Security\UserProvider\EntityFactory as BaseEntityFactory; 18 | 19 | /** 20 | * EntityFactory 21 | * 22 | * @author Geoffrey Bachelet 23 | */ 24 | class EntityFactory extends BaseEntityFactory 25 | { 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | public function create(ContainerBuilder $container, $id, $config) 30 | { 31 | $container 32 | ->setDefinition($id, new DefinitionDecorator('knp_oauth.user.provider.entity')) 33 | ->addArgument($config['class']) 34 | ->addArgument($config['property']) 35 | ; 36 | } 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | public function getKey() 42 | { 43 | return 'oauth_entity'; 44 | } 45 | } -------------------------------------------------------------------------------- /Security/Core/UserProvider/EntityUserProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Core\UserProvider; 13 | 14 | use Symfony\Component\Security\Core\Exception\UsernameNotFoundException, 15 | Symfony\Bridge\Doctrine\Security\User\EntityUserProvider as BaseEntityUserProvider, 16 | Doctrine\Common\Persistence\ManagerRegistry; 17 | 18 | /** 19 | * EntityUserProvider 20 | * 21 | * @author Geoffrey Bachelet 22 | */ 23 | class EntityUserProvider extends BaseEntityUserProvider 24 | { 25 | private $class; 26 | private $property; 27 | private $em; 28 | 29 | public function __construct(ManagerRegistry $registry, $class, $property = null, $managerName = null) 30 | { 31 | $this->property = $property; 32 | $this->class = $class; 33 | 34 | $this->em = $registry->getManager($managerName); 35 | 36 | if (false !== strpos($this->class, ':')) { 37 | $metadata = $this->em->getClassMetadata($this->class); 38 | $this->class = $metadata->getName(); 39 | } 40 | 41 | parent::__construct($registry, $class, $property, $managerName); 42 | } 43 | 44 | /** 45 | * @var string $username 46 | * @return mixed An user entity 47 | */ 48 | public function createEntity($username) 49 | { 50 | $setter = 'set'.ucfirst($this->property); 51 | $user = new $this->class; 52 | 53 | call_user_func(array($user, $setter), $username); 54 | 55 | return $user; 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | public function loadUserByUsername($username) 62 | { 63 | try { 64 | $user = parent::loadUserByUsername($username); 65 | } catch (UsernameNotFoundException $e) { 66 | $user = $this->createEntity($username); 67 | $this->em->persist($user); 68 | $this->em->flush(); 69 | } 70 | 71 | return $user; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Security/Core/UserProvider/OAuthUserProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Core\UserProvider; 13 | 14 | use Symfony\Component\Security\Core\User\UserProviderInterface, 15 | Symfony\Component\Security\Core\User\UserInterface, 16 | Symfony\Component\Security\Core\Exception\UnsupportedUserException; 17 | 18 | use Knp\Bundle\OAuthBundle\Security\Core\User\OAuthUser; 19 | 20 | /** 21 | * OAuthUserProvider 22 | * 23 | * @author Geoffrey Bachelet 24 | */ 25 | class OAuthUserProvider implements UserProviderInterface 26 | { 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | public function loadUserByUsername($username) 31 | { 32 | return new OAuthUser($username); 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | public function refreshUser(UserInterface $user) 39 | { 40 | if (!$this->supportsClass(get_class($user))) { 41 | throw new UnsupportedUserException(sprintf('Unsupported user class "%s"', get_class($user))); 42 | } 43 | 44 | return $this->loadUserByUsername($user->getUsername()); 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | public function supportsClass($class) 51 | { 52 | return $class === 'Knp\\Bundle\\OAuthBundle\\Security\\Core\\User\\OAuthUser'; 53 | } 54 | } -------------------------------------------------------------------------------- /Security/Http/EntryPoint/OAuthEntryPoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Http\EntryPoint; 13 | 14 | use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface, 15 | Symfony\Component\Security\Core\Exception\AuthenticationException, 16 | Symfony\Component\Security\Http\HttpUtils, 17 | Symfony\Component\HttpFoundation\Request; 18 | 19 | use Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface; 20 | 21 | /** 22 | * OAuthEntryPoint 23 | * 24 | * @author Geoffrey Bachelet 25 | */ 26 | class OAuthEntryPoint implements AuthenticationEntryPointInterface 27 | { 28 | /** 29 | * @var Symfony\Component\Security\Http\HttpUtils 30 | */ 31 | private $httpUtils; 32 | 33 | /** 34 | * @var Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface 35 | */ 36 | private $oauthProvider; 37 | 38 | /** 39 | * @var string 40 | */ 41 | private $checkPath; 42 | 43 | /** 44 | * @param Symfony\Component\Security\Http\HttpUtils $httpUtils 45 | * @param Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface $oauthProvider 46 | * @param string $checkPath 47 | */ 48 | public function __construct(HttpUtils $httpUtils, OAuthProviderInterface $oauthProvider, $checkPath, $loginPath) 49 | { 50 | $this->httpUtils = $httpUtils; 51 | $this->oauthProvider = $oauthProvider; 52 | $this->checkPath = $checkPath; 53 | $this->loginPath = $loginPath; 54 | } 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | public function start(Request $request, AuthenticationException $authException = null) 60 | { 61 | if (!$this->httpUtils->checkRequestPath($request, $this->checkPath)) { 62 | if ($this->httpUtils->checkRequestPath($request, $this->loginPath)) { 63 | $request->getSession()->remove('_security.target_path'); 64 | } 65 | 66 | $authorizationUrl = $this->oauthProvider->getAuthorizationUrl($request); 67 | 68 | return $this->httpUtils->createRedirectResponse($request, $authorizationUrl); 69 | } 70 | 71 | throw $authException; 72 | } 73 | } -------------------------------------------------------------------------------- /Security/Http/Firewall/OAuthListener.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OauthBundle\Security\Http\Firewall; 13 | 14 | use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener, 15 | Symfony\Component\HttpFoundation\Request, 16 | Symfony\Component\Security\Core\Exception\AuthenticationException; 17 | 18 | use Knp\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken, 19 | Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface; 20 | 21 | /** 22 | * OAuthListener 23 | * 24 | * @author Geoffrey Bachelet 25 | */ 26 | class OAuthListener extends AbstractAuthenticationListener 27 | { 28 | /** 29 | * @var Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface 30 | */ 31 | private $oauthProvider; 32 | 33 | /** 34 | * @var Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface $oauthProvider 35 | */ 36 | public function setOAuthProvider(OAuthProviderInterface $oauthProvider) 37 | { 38 | $this->oauthProvider = $oauthProvider; 39 | } 40 | 41 | /** 42 | * {@inheritDoc} 43 | */ 44 | protected function attemptAuthentication(Request $request) 45 | { 46 | $accessToken = $this->oauthProvider->getAccessToken($request); 47 | 48 | $token = new OAuthToken($accessToken); 49 | 50 | return $this->authenticationManager->authenticate($token); 51 | } 52 | } -------------------------------------------------------------------------------- /Security/Http/OAuth/FacebookProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Http\OAuth; 13 | 14 | use Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProvider; 15 | 16 | /** 17 | * FacebookProvider 18 | * 19 | * @author Geoffrey Bachelet 20 | */ 21 | class FacebookProvider extends OAuthProvider 22 | { 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | protected $options = array( 27 | 'authorization_url' => 'https://www.facebook.com/dialog/oauth', 28 | 'access_token_url' => 'https://graph.facebook.com/oauth/access_token', 29 | 'infos_url' => 'https://graph.facebook.com/me', 30 | 'username_path' => 'name', 31 | ); 32 | 33 | /** 34 | * Facebook unfortunately breaks the spec by using commas instead of spaces 35 | * to separate scopes 36 | */ 37 | public function configure() 38 | { 39 | $this->options['scope'] = str_replace(',', ' ', $this->options['scope']); 40 | } 41 | } -------------------------------------------------------------------------------- /Security/Http/OAuth/GithubProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Http\OAuth; 13 | 14 | use Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProvider; 15 | 16 | /** 17 | * GithubProvider 18 | * 19 | * @author Geoffrey Bachelet 20 | */ 21 | class GithubProvider extends OAuthProvider 22 | { 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | protected $options = array( 27 | 'authorization_url' => 'https://github.com/login/oauth/authorize', 28 | 'access_token_url' => 'https://github.com/login/oauth/access_token', 29 | 'infos_url' => 'https://api.github.com/user', 30 | 'username_path' => 'login', 31 | ); 32 | 33 | /** 34 | * Github unfortunately breaks the spec by using commas instead of spaces 35 | * to separate scopes 36 | */ 37 | public function configure() 38 | { 39 | $this->options['scope'] = str_replace(',', ' ', $this->options['scope']); 40 | } 41 | } -------------------------------------------------------------------------------- /Security/Http/OAuth/GoogleProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Http\OAuth; 13 | 14 | use Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProvider; 15 | 16 | use Symfony\Component\HttpFoundation\Request; 17 | 18 | /** 19 | * GoogleProvider 20 | * 21 | * @author Geoffrey Bachelet 22 | */ 23 | class GoogleProvider extends OAuthProvider 24 | { 25 | /** 26 | * {@inheritDoc} 27 | */ 28 | protected $options = array( 29 | 'authorization_url' => 'https://accounts.google.com/o/oauth2/auth', 30 | 'access_token_url' => 'https://accounts.google.com/o/oauth2/token', 31 | 'infos_url' => 'https://www.googleapis.com/oauth2/v1/userinfo', 32 | 'username_path' => 'name', 33 | 'scope' => 'userinfo.profile', 34 | ); 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | public function getAccessToken(Request $request, array $extraParameters = array()) 40 | { 41 | $parameters = array_merge($extraParameters, array( 42 | 'code' => $request->get('code'), 43 | 'grant_type' => 'authorization_code', 44 | 'client_id' => $this->getOption('client_id'), 45 | 'client_secret' => $this->getOption('secret'), 46 | 'redirect_uri' => $this->getRedirectUri($request), 47 | )); 48 | 49 | $url = $this->getOption('access_token_url'); 50 | $content = http_build_query($parameters); 51 | 52 | $response = $this->httpRequest($url, $content); 53 | $response = json_decode($response); 54 | 55 | if (isset($response['error'])) { 56 | throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['error'])); 57 | } 58 | 59 | return $response['access_token']; 60 | } 61 | } -------------------------------------------------------------------------------- /Security/Http/OAuth/OAuthProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Http\OAuth; 13 | 14 | use Buzz\Client\ClientInterface as HttpClientInterface, 15 | Buzz\Message\Request as HttpRequest, 16 | Buzz\Message\Response as HttpResponse; 17 | 18 | use Symfony\Component\Security\Core\Exception\AuthenticationException, 19 | Symfony\Component\Security\Http\HttpUtils, 20 | Symfony\Component\HttpFoundation\Request; 21 | 22 | use Knp\Bundle\OAuthBundle\Security\Http\OAuth\OAuthProviderInterface; 23 | 24 | /** 25 | * OAuthProvider 26 | * 27 | * @author Geoffrey Bachelet 28 | */ 29 | class OAuthProvider implements OAuthProviderInterface 30 | { 31 | /** 32 | * @var array 33 | */ 34 | protected $options = array(); 35 | 36 | /** 37 | * @var Buzz\Client\ClientInterface 38 | */ 39 | protected $httpClient; 40 | 41 | /** 42 | * @param Buzz\Client\ClientInterface $httpClient 43 | * @param array $options 44 | */ 45 | public function __construct(HttpClientInterface $httpClient, HttpUtils $httpUtils, array $options) 46 | { 47 | if (null !== $options['infos_url'] && null === $options['username_path']) { 48 | throw new \InvalidArgumentException('You must set an "username_path" to use an "infos_url"'); 49 | } 50 | 51 | if (null === $options['infos_url'] && null !== $options['username_path']) { 52 | throw new \InvalidArgumentException('You must set an "infos_url" to use an "username_path"'); 53 | } 54 | 55 | /** 56 | * We want to merge passed options within existing options 57 | * but only if they are not null. This is a bit messy. Sorry. 58 | */ 59 | foreach ($options as $k => $v) { 60 | if (null === $v && array_key_exists($k, $this->options)) { 61 | unset($options[$k]); 62 | } 63 | } 64 | 65 | $this->options = array_merge($this->options, $options); 66 | $this->httpClient = $httpClient; 67 | $this->httpUtils = $httpUtils; 68 | 69 | $this->configure(); 70 | } 71 | 72 | /** 73 | * Gives a chance for extending providers to customize stuff 74 | */ 75 | public function configure() 76 | { 77 | 78 | } 79 | 80 | /** 81 | * @param Symfony\Component\HttpFoundation\Request $request 82 | * @return string 83 | */ 84 | public function getRedirectUri(Request $request) 85 | { 86 | return $this->httpUtils->createRequest($request, $this->getOption('check_path'))->getUri(); 87 | } 88 | 89 | /** 90 | * Retrieve an option by name 91 | * 92 | * @throws InvalidArgumentException When the option does not exist 93 | * @param string $name The option name 94 | * @return mixed The option value 95 | */ 96 | public function getOption($name) 97 | { 98 | if (!array_key_exists($name, $this->options)) { 99 | throw new \InvalidArgumentException(sprintf('Unknown option "%s"', $name)); 100 | } 101 | 102 | return $this->options[$name]; 103 | } 104 | 105 | /** 106 | * Performs an HTTP request 107 | * 108 | * @param string $url The url to fetch 109 | * @param string $method The HTTP method to use 110 | * @return string The response content 111 | */ 112 | protected function httpRequest($url, $content = null, $method = null) 113 | { 114 | if (null === $method) { 115 | $method = null === $content ? HttpRequest::METHOD_GET : HttpRequest::METHOD_POST; 116 | } 117 | 118 | $request = new HttpRequest($method, $url); 119 | $response = new HttpResponse(); 120 | 121 | $request->setContent($content); 122 | 123 | $this->httpClient->send($request, $response); 124 | 125 | return $response->getContent(); 126 | } 127 | 128 | /** 129 | * {@inheritDoc} 130 | */ 131 | public function getUsername($accessToken) 132 | { 133 | if ($this->getOption('infos_url') === null) { 134 | return $accessToken; 135 | } 136 | 137 | $url = $this->getOption('infos_url').'?'.http_build_query(array( 138 | 'access_token' => $accessToken 139 | )); 140 | 141 | $userInfos = json_decode($this->httpRequest($url), true); 142 | $usernamePath = explode('.', $this->getOption('username_path')); 143 | 144 | $username = $userInfos; 145 | 146 | foreach ($usernamePath as $path) { 147 | if (!array_key_exists($path, $username)) { 148 | throw new AuthenticationException(sprintf('Could not follow username path "%s" in OAuth provider response: %s', $this->getOption('username_path'), var_export($userInfos, true))); 149 | } 150 | $username = $username[$path]; 151 | } 152 | 153 | return $username; 154 | } 155 | 156 | /** 157 | * {@inheritDoc} 158 | */ 159 | public function getAuthorizationUrl(Request $request, array $extraParameters = array()) 160 | { 161 | $parameters = array_merge($extraParameters, array( 162 | 'response_type' => 'code', 163 | 'client_id' => $this->getOption('client_id'), 164 | 'scope' => $this->getOption('scope'), 165 | 'redirect_uri' => $this->getRedirectUri($request), 166 | )); 167 | 168 | return $this->getOption('authorization_url').'?'.http_build_query($parameters); 169 | } 170 | 171 | /** 172 | * {@inheritDoc} 173 | */ 174 | public function getAccessToken(Request $request, array $extraParameters = array()) 175 | { 176 | $parameters = array_merge($extraParameters, array( 177 | 'code' => $request->get('code'), 178 | 'grant_type' => 'authorization_code', 179 | 'client_id' => $this->getOption('client_id'), 180 | 'client_secret' => $this->getOption('secret'), 181 | 'redirect_uri' => $this->getRedirectUri($request), 182 | )); 183 | 184 | $url = $this->getOption('access_token_url').'?'.http_build_query($parameters); 185 | 186 | $response = array(); 187 | 188 | parse_str($this->httpRequest($url), $response); 189 | 190 | if (isset($response['error'])) { 191 | throw new AuthenticationException(sprintf('OAuth error: "%s"', $response['error'])); 192 | } 193 | 194 | return $response['access_token']; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Security/Http/OAuth/OAuthProviderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Knp\Bundle\OAuthBundle\Security\Http\OAuth; 13 | 14 | use Symfony\Component\HttpFoundation\Request; 15 | 16 | /** 17 | * OAuthProviderInterface 18 | * 19 | * @author Geoffrey Bachelet 20 | */ 21 | interface OAuthProviderInterface 22 | { 23 | /** 24 | * Retrieves the user's username from an access_token 25 | * 26 | * @param string $accessToken 27 | * @return string The username 28 | */ 29 | function getUsername($accessToken); 30 | 31 | /** 32 | * Returns the provider's authorization url 33 | * 34 | * @param string $loginCheckUrl You should set that as your redirect_uri 35 | * @param array $extraParameters An array of parameters to add to the url 36 | * @return string The authorization url 37 | */ 38 | function getAuthorizationUrl(Request $request, array $extraParameters = array()); 39 | 40 | /** 41 | * Retrieve an access token for a given code 42 | * 43 | * @param string $code The code 44 | * @param array $extraParameters An array of parameters to add to the url 45 | * @return string The access token 46 | */ 47 | function getAccessToken(Request $request, array $extraParameters = array()); 48 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knplabs/knp-oauth-bundle", 3 | "type": "symfony-bundle", 4 | "description": "An OAuth firewall for Symfony2", 5 | "keywords": ["security", "firewall", "oauth"], 6 | "homepage": "http://github.com/KnpLabs/KnpOAuthBundle", 7 | "license": "MIT", 8 | 9 | "authors": [ 10 | { 11 | "name": "KnpLabs Team", 12 | "homepage": "http://knplabs.com/" 13 | }, 14 | { 15 | "name": "Geoffrey Bachelet", 16 | "email": "geoffrey.bachelet@gmail.com" 17 | }, 18 | { 19 | "name": "Contributors", 20 | "homepage": "https://github.com/KnpLabs/KnpOAuthBundle/contributors" 21 | } 22 | ], 23 | 24 | "require": { 25 | "php": ">=5.3.2", 26 | "symfony/framework-bundle": "2.1.*", 27 | "sensio/buzz-bundle": "*" 28 | }, 29 | 30 | "suggest": { 31 | "symfony/doctrine-bundle": "*" 32 | }, 33 | 34 | "autoload": { 35 | "psr-0": { 36 | "Knp\\Bundle\\OAuthBundle": "" 37 | } 38 | }, 39 | 40 | "target-dir": "Knp/Bundle/OAuthBundle" 41 | } 42 | --------------------------------------------------------------------------------