├── .github └── workflows │ └── php.yml ├── .gitignore ├── .php-cs-fixer.cache ├── .php_cs-fixer.php ├── Command ├── CleanCommand.php └── CreateClientCommand.php ├── Controller ├── AuthorizeController.php └── TokenController.php ├── DependencyInjection ├── Compiler │ ├── GrantExtensionsCompilerPass.php │ └── RequestStackCompilerPass.php ├── Configuration.php ├── FOSOAuthServerExtension.php └── Security │ └── Factory │ └── OAuthFactory.php ├── Document ├── AccessToken.php ├── AccessTokenManager.php ├── AuthCode.php ├── AuthCodeManager.php ├── Client.php ├── ClientManager.php ├── RefreshToken.php ├── RefreshTokenManager.php └── TokenManager.php ├── Entity ├── AccessToken.php ├── AccessTokenManager.php ├── AuthCode.php ├── AuthCodeManager.php ├── Client.php ├── ClientManager.php ├── RefreshToken.php ├── RefreshTokenManager.php └── TokenManager.php ├── Event └── OAuthEvent.php ├── FOSOAuthServerBundle.php ├── Form ├── Handler │ └── AuthorizeFormHandler.php ├── Model │ └── Authorize.php └── Type │ └── AuthorizeFormType.php ├── Makefile ├── Model ├── AccessToken.php ├── AccessTokenInterface.php ├── AccessTokenManagerInterface.php ├── AuthCode.php ├── AuthCodeInterface.php ├── AuthCodeManager.php ├── AuthCodeManagerInterface.php ├── Client.php ├── ClientInterface.php ├── ClientManager.php ├── ClientManagerInterface.php ├── RefreshToken.php ├── RefreshTokenInterface.php ├── RefreshTokenManagerInterface.php ├── Token.php ├── TokenInterface.php ├── TokenManager.php └── TokenManagerInterface.php ├── README.md ├── Resources ├── config │ ├── authorize.xml │ ├── couchdb.xml │ ├── doctrine │ │ ├── AccessToken.couchdb.xml │ │ ├── AccessToken.mongodb.xml │ │ ├── AccessToken.orm.xml │ │ ├── AuthCode.couchdb.xml │ │ ├── AuthCode.mongodb.xml │ │ ├── AuthCode.orm.xml │ │ ├── Client.couchdb.xml │ │ ├── Client.mongodb.xml │ │ ├── Client.orm.xml │ │ ├── RefreshToken.couchdb.xml │ │ ├── RefreshToken.mongodb.xml │ │ └── RefreshToken.orm.xml │ ├── mongodb.xml │ ├── oauth.xml │ ├── orm.xml │ ├── routing │ │ ├── authorize.xml │ │ └── token.xml │ ├── security.xml │ └── validation.xml ├── doc │ ├── a_note_about_security.md │ ├── adding_grant_extensions.md │ ├── configuration_reference.md │ ├── custom_db_driver.md │ ├── dealing_with_scopes.md │ ├── extending_the_authorization_page.md │ ├── extending_the_model.md │ ├── index.md │ └── the_oauth_event_class.md ├── meta │ └── LICENSE ├── translations │ ├── FOSOAuthServerBundle.de.yml │ ├── FOSOAuthServerBundle.en.yml │ ├── FOSOAuthServerBundle.fr.yml │ └── FOSOAuthServerBundle.sl.yml └── views │ ├── Authorize │ ├── authorize.html.twig │ └── authorize_content.html.twig │ ├── form.html.twig │ └── layout.html.twig ├── Security ├── Authenticator │ ├── OAuth2Passport.php │ ├── Oauth2Authenticator.php │ ├── Passport │ │ └── Badge │ │ │ └── AccessTokenBadge.php │ └── Token │ │ └── OAuthToken.php └── EntryPoint │ └── OAuthEntryPoint.php ├── Storage ├── GrantExtensionDispatcherInterface.php ├── GrantExtensionInterface.php └── OAuthStorage.php ├── Tests ├── Command │ ├── CleanCommandTest.php │ └── CreateClientCommandTest.php ├── Controller │ ├── AuthorizeControllerTest.php │ └── TokenControllerTest.php ├── DependencyInjection │ ├── Compiler │ │ ├── GrantExtensionsCompilerPassTest.php │ │ └── RequestStackCompilerPassTest.php │ ├── ConfigurationTest.php │ ├── FOSOAuthServerExtensionTest.php │ └── Security │ │ └── Factory │ │ └── OAuthFactoryTest.php ├── Document │ ├── AuthCodeManagerTest.php │ ├── ClientManagerTest.php │ └── TokenManagerTest.php ├── Entity │ ├── AuthCodeManagerTest.php │ ├── ClientManagerTest.php │ └── TokenManagerTest.php ├── Event │ └── OAuthEventTest.php ├── FOSOAuthServerBundleTest.php ├── Form │ ├── Handler │ │ └── AuthorizeFormHandlerTest.php │ └── Type │ │ └── AuthorizeFormTypeTest.php ├── Functional │ ├── AppKernel.php │ ├── BootTest.php │ ├── TestBundle │ │ ├── Entity │ │ │ ├── AccessToken.php │ │ │ ├── AuthCode.php │ │ │ ├── Client.php │ │ │ ├── RefreshToken.php │ │ │ └── User.php │ │ └── TestBundle.php │ ├── TestCase.php │ └── config │ │ ├── config.yml │ │ ├── config_orm.yml │ │ └── routing.yml ├── Model │ ├── ClientTest.php │ └── TokenTest.php ├── Security │ ├── Authenticator │ │ ├── Oauth2AuthenticatorTest.php │ │ └── Passport │ │ │ └── Badge │ │ │ └── AccessTokenBadgeTest.php │ └── EntryPoint │ │ └── OAuthEntryPointTest.php ├── Storage │ └── OAuthStorageTest.php ├── Util │ └── RandomTest.php └── bootstrap.php ├── UPGRADE.md ├── Util ├── LegacyFormHelper.php └── Random.php ├── composer.json ├── phpstan.neon └── phpunit.xml.dist /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ "5.1" ] 6 | pull_request: 7 | branches: [ "5.1" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Validate composer.json and composer.lock 21 | run: composer validate --strict 22 | 23 | - name: Cache Composer packages 24 | id: composer-cache 25 | uses: actions/cache@v3 26 | with: 27 | path: vendor 28 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-php- 31 | 32 | - name: Install dependencies 33 | run: composer install --prefer-dist --no-progress 34 | 35 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" 36 | # Docs: https://getcomposer.org/doc/articles/scripts.md 37 | 38 | - name: Run test suite 39 | uses: php-actions/phpunit@v3 40 | env: 41 | XDEBUG_MODE: coverage 42 | with: 43 | php_extensions: "xdebug" 44 | coverage_clover: "coverage.xml" 45 | args: --log-junit junit.xml 46 | 47 | - name: Upload coverage to Codecov 48 | uses: codecov/codecov-action@v5 49 | env: 50 | CODECOV_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }} 51 | 52 | - name: Upload test results to Codecov 53 | if: ${{ !cancelled() }} 54 | uses: codecov/test-results-action@v1 55 | with: 56 | token: ${{ secrets.CODECOV_ORG_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .phpunit.cache/ 2 | phpunit.xml 3 | Tests/autoload.php 4 | reports/ 5 | vendor/ 6 | composer.lock 7 | .php-cs-fixer.cache 8 | .phpunit.result.cache 9 | .DS_Store 10 | /phpunit.coverage.xml 11 | -------------------------------------------------------------------------------- /.php-cs-fixer.cache: -------------------------------------------------------------------------------- 1 | {"php":"8.0.3","version":"3.0.0:v3.0.0#c15377bdfa8d1ecf186f1deadec39c89984e1167","indent":" ","lineEnding":"\n","rules":{"blank_line_after_opening_tag":true,"braces":{"allow_single_line_anonymous_class_with_empty_body":true},"compact_nullable_typehint":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":{"elements":["const","method","property"]},"blank_line_after_namespace":true,"class_definition":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"Form\/Handler\/AuthorizeFormHandler.php":644364133,"Form\/Type\/AuthorizeFormType.php":2473882551,"Form\/Model\/Authorize.php":2277517082,"Util\/Random.php":802199793,"Util\/LegacyFormHelper.php":3463381423,"Security\/Firewall\/OAuthListener.php":3256280425,"Security\/EntryPoint\/OAuthEntryPoint.php":3300495943,"Security\/Authenticator\/Oauth2Authenticator.php":991017001,"Security\/Authentication\/Token\/OAuthToken.php":645099124,"Security\/Authentication\/Provider\/OAuthProvider.php":3873131618,"Entity\/RefreshToken.php":66504907,"Entity\/ClientManager.php":1452201554,"Entity\/TokenManager.php":3338740552,"Entity\/AccessToken.php":4251529712,"Entity\/RefreshTokenManager.php":414634294,"Entity\/AuthCode.php":2219407473,"Entity\/AuthCodeManager.php":2532413095,"Entity\/AccessTokenManager.php":4224539817,"Entity\/Client.php":1083124177,"FOSOAuthServerBundle.php":4124437143,"Tests\/Form\/Handler\/AuthorizeFormHandlerTest.php":1906221542,"Tests\/Form\/Type\/AuthorizeFormTypeTest.php":2238981306,"Tests\/Util\/RandomTest.php":1191179325,"Tests\/Security\/Firewall\/OAuthListenerTest.php":3407442028,"Tests\/Security\/Authentication\/Provider\/OAuthProviderTest.php":2457218384,"Tests\/Security\/Authentification\/Token\/OAuthTokenTest.php":2945053184,"Tests\/Entity\/AuthCodeManagerTest.php":2007661459,"Tests\/Entity\/ClientManagerTest.php":766396943,"Tests\/Entity\/TokenManagerTest.php":1473952290,"Tests\/Document\/AuthCodeManagerTest.php":3617953485,"Tests\/Document\/ClientManagerTest.php":1071185972,"Tests\/Document\/TokenManagerTest.php":917404959,"Tests\/bootstrap.php":2430042088,"Tests\/Storage\/OAuthStorageTest.php":64232207,"Tests\/Controller\/AuthorizeControllerTest.php":2739608664,"Tests\/Model\/TokenTest.php":1965290733,"Tests\/Command\/CleanCommandTest.php":3055535592,"Tests\/Command\/CreateClientCommandTest.php":2203162721,"Tests\/FOSOAuthServerBundleTest.php":522076447,"Tests\/TestCase.php":4027732966,"Tests\/DependencyInjection\/Security\/Factory\/OAuthFactoryTest.php":3931590390,"Tests\/DependencyInjection\/ConfigurationTest.php":648882677,"Tests\/DependencyInjection\/FOSOAuthServerExtensionTest.php":1379307327,"Tests\/DependencyInjection\/Compiler\/TokenStorageCompilerPassTest.php":2422335141,"Tests\/DependencyInjection\/Compiler\/GrantExtensionsCompilerPassTest.php":3352901713,"Tests\/DependencyInjection\/Compiler\/RequestStackCompilerPassTest.php":2150456782,"Tests\/Functional\/TestBundle\/TestBundle.php":1456131229,"Tests\/Functional\/TestBundle\/Entity\/RefreshToken.php":1237578767,"Tests\/Functional\/TestBundle\/Entity\/User.php":3730762552,"Tests\/Functional\/TestBundle\/Entity\/AccessToken.php":2940885721,"Tests\/Functional\/TestBundle\/Entity\/AuthCode.php":2122232803,"Tests\/Functional\/TestBundle\/Entity\/Client.php":2765761650,"Tests\/Functional\/BootTest.php":977017021,"Tests\/Functional\/AppKernel.php":1114496403,"Tests\/Functional\/TestCase.php":551953486,"Document\/RefreshToken.php":2691281249,"Document\/ClientManager.php":2350236724,"Document\/TokenManager.php":4092676326,"Document\/AccessToken.php":2761752328,"Document\/RefreshTokenManager.php":246784338,"Document\/AuthCode.php":3656095945,"Document\/AuthCodeManager.php":1873472755,"Document\/AccessTokenManager.php":2903162592,"Document\/Client.php":989000811,"Storage\/GrantExtensionDispatcherInterface.php":9006188,"Storage\/GrantExtensionInterface.php":3728531040,"Storage\/OAuthStorage.php":99029901,"Controller\/AuthorizeController.php":295173422,"Controller\/TokenController.php":1989989879,"Model\/ClientManagerInterface.php":404743545,"Model\/AuthCodeManagerInterface.php":3112175094,"Model\/RefreshToken.php":2462104020,"Model\/ClientManager.php":63621513,"Model\/Token.php":4084908361,"Model\/TokenManager.php":1931703876,"Model\/AccessToken.php":1063329293,"Model\/RefreshTokenManagerInterface.php":187414808,"Model\/ClientInterface.php":3292532149,"Model\/AuthCode.php":222711451,"Model\/AccessTokenManagerInterface.php":3048194508,"Model\/AccessTokenInterface.php":3847276407,"Model\/AuthCodeInterface.php":2660786798,"Model\/TokenManagerInterface.php":1010854742,"Model\/AuthCodeManager.php":4277460474,"Model\/TokenInterface.php":3661528244,"Model\/RefreshTokenInterface.php":4089106687,"Model\/Client.php":1588407887,"Command\/CreateClientCommand.php":2312819124,"Command\/CleanCommand.php":131382764,"DependencyInjection\/Security\/Factory\/OAuthFactory.php":4972243,"DependencyInjection\/FOSOAuthServerExtension.php":2654959019,"DependencyInjection\/Configuration.php":1694748975,"DependencyInjection\/Compiler\/RequestStackCompilerPass.php":1420693598,"DependencyInjection\/Compiler\/TokenStorageCompilerPass.php":1937726139,"DependencyInjection\/Compiler\/GrantExtensionsCompilerPass.php":853793336,"Event\/OAuthEvent.php":1210775945}} -------------------------------------------------------------------------------- /.php_cs-fixer.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 | $header = << 16 | 17 | For the full copyright and license information, please view the LICENSE 18 | file that was distributed with this source code. 19 | EOF; 20 | 21 | $finder = PhpCsFixer\Finder::create() 22 | ->in(__DIR__) 23 | ->exclude('vendor'); 24 | 25 | $config = new \PhpCsFixer\Config(); 26 | 27 | return $config 28 | ->setFinder($finder) 29 | ->setRiskyAllowed(true) 30 | ->setUsingCache(false) 31 | ->setRules( 32 | [ 33 | '@Symfony' => true, 34 | '@PHPUnit60Migration:risky' => true, 35 | 'array_syntax' => ['syntax' => 'short'], 36 | 'combine_consecutive_unsets' => true, 37 | 'declare_strict_types' => true, 38 | 'dir_constant' => true, 39 | 'header_comment' => ['header' => $header], 40 | 'linebreak_after_opening_tag' => true, 41 | 'mb_str_functions' => true, 42 | 'modernize_types_casting' => true, 43 | // 'native_function_invocation' => true, 44 | 'no_extra_blank_lines' => ['tokens' => ['continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block']], 45 | 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], 46 | 'no_php4_constructor' => true, 47 | 'echo_tag_syntax' => ['format' => 'long'], 48 | 'no_unreachable_default_argument_value' => true, 49 | 'no_useless_else' => true, 50 | 'no_useless_return' => true, 51 | 'not_operator_with_space' => false, 52 | 'not_operator_with_successor_space' => false, 53 | 'ordered_imports' => true, 54 | 'phpdoc_add_missing_param_annotation' => true, 55 | 'phpdoc_annotation_without_dot' => true, 56 | 'general_phpdoc_tag_rename' => false, 57 | 'phpdoc_no_empty_return' => false, 58 | 'phpdoc_order' => true, 59 | 'phpdoc_to_comment' => false, 60 | 'psr_autoloading' => true, 61 | 'semicolon_after_instruction' => true, 62 | 'single_import_per_statement' => true, 63 | 'strict_comparison' => true, 64 | 'strict_param' => true, 65 | 'yoda_style' => false 66 | ] 67 | ); 68 | -------------------------------------------------------------------------------- /Command/CleanCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Command; 15 | 16 | use FOS\OAuthServerBundle\Model\AuthCodeManagerInterface; 17 | use FOS\OAuthServerBundle\Model\TokenManagerInterface; 18 | use Symfony\Component\Console\Command\Command; 19 | use Symfony\Component\Console\Input\InputInterface; 20 | use Symfony\Component\Console\Output\OutputInterface; 21 | 22 | class CleanCommand extends Command 23 | { 24 | private TokenManagerInterface $accessTokenManager; 25 | private TokenManagerInterface $refreshTokenManager; 26 | private AuthCodeManagerInterface $authCodeManager; 27 | 28 | public function __construct( 29 | TokenManagerInterface $accessTokenManager, 30 | TokenManagerInterface $refreshTokenManager, 31 | AuthCodeManagerInterface $authCodeManager 32 | ) { 33 | parent::__construct(); 34 | 35 | $this->accessTokenManager = $accessTokenManager; 36 | $this->refreshTokenManager = $refreshTokenManager; 37 | $this->authCodeManager = $authCodeManager; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | protected function configure(): void 44 | { 45 | parent::configure(); 46 | 47 | $this 48 | ->setName('fos:oauth-server:clean') 49 | ->setDescription('Clean expired tokens') 50 | ->setHelp( 51 | <<%command.name% command will remove expired OAuth2 tokens. 53 | 54 | php %command.full_name% 55 | EOT 56 | ) 57 | ; 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | protected function execute(InputInterface $input, OutputInterface $output): int 64 | { 65 | foreach ([$this->accessTokenManager, $this->refreshTokenManager, $this->authCodeManager] as $service) { 66 | $result = $service->deleteExpired(); 67 | $output->writeln(sprintf('Removed %d items from %s storage.', $result, get_class($service))); 68 | } 69 | 70 | return 0; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Command/CreateClientCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Command; 15 | 16 | use FOS\OAuthServerBundle\Model\ClientManagerInterface; 17 | use Symfony\Component\Console\Command\Command; 18 | use Symfony\Component\Console\Input\InputInterface; 19 | use Symfony\Component\Console\Input\InputOption; 20 | use Symfony\Component\Console\Output\OutputInterface; 21 | use Symfony\Component\Console\Style\SymfonyStyle; 22 | 23 | class CreateClientCommand extends Command 24 | { 25 | /** 26 | * @var ClientManagerInterface 27 | */ 28 | private $clientManager; 29 | 30 | public function __construct(ClientManagerInterface $clientManager) 31 | { 32 | parent::__construct(); 33 | 34 | $this->clientManager = $clientManager; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | protected function configure(): void 41 | { 42 | parent::configure(); 43 | 44 | $this 45 | ->setName('fos:oauth-server:create-client') 46 | ->setDescription('Creates a new client') 47 | ->addOption( 48 | 'redirect-uri', 49 | null, 50 | InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 51 | 'Sets redirect uri for client. Use this option multiple times to set multiple redirect URIs.', 52 | null 53 | ) 54 | ->addOption( 55 | 'grant-type', 56 | null, 57 | InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 58 | 'Sets allowed grant type for client. Use this option multiple times to set multiple grant types..', 59 | null 60 | ) 61 | ->setHelp( 62 | <<%command.name% command creates a new client. 64 | 65 | php %command.full_name% [--redirect-uri=...] [--grant-type=...] 66 | 67 | EOT 68 | ) 69 | ; 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | protected function execute(InputInterface $input, OutputInterface $output): int 76 | { 77 | $io = new SymfonyStyle($input, $output); 78 | 79 | $io->title('Client Credentials'); 80 | 81 | // Create a new client 82 | $client = $this->clientManager->createClient(); 83 | 84 | $client->setRedirectUris($input->getOption('redirect-uri')); 85 | $client->setAllowedGrantTypes($input->getOption('grant-type')); 86 | 87 | // Save the client 88 | $this->clientManager->updateClient($client); 89 | 90 | // Give the credentials back to the user 91 | $headers = ['Client ID', 'Client Secret']; 92 | $rows = [ 93 | [$client->getPublicId(), $client->getSecret()], 94 | ]; 95 | 96 | $io->table($headers, $rows); 97 | 98 | return 0; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Controller/TokenController.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Controller; 15 | 16 | use OAuth2\OAuth2; 17 | use OAuth2\OAuth2ServerException; 18 | use Symfony\Component\HttpFoundation\Request; 19 | use Symfony\Component\HttpFoundation\Response; 20 | 21 | class TokenController 22 | { 23 | /** 24 | * @var OAuth2 25 | */ 26 | protected $server; 27 | 28 | public function __construct(OAuth2 $server) 29 | { 30 | $this->server = $server; 31 | } 32 | 33 | public function tokenAction(Request $request): Response 34 | { 35 | try { 36 | return $this->server->grantAccessToken($request); 37 | } catch (OAuth2ServerException $e) { 38 | return $e->getHttpResponse(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/GrantExtensionsCompilerPass.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\DependencyInjection\Compiler; 15 | 16 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 17 | use Symfony\Component\DependencyInjection\ContainerBuilder; 18 | use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; 19 | use Symfony\Component\DependencyInjection\Reference; 20 | 21 | /** 22 | * @author Adrien Brault 23 | */ 24 | class GrantExtensionsCompilerPass implements CompilerPassInterface 25 | { 26 | public function process(ContainerBuilder $container): void 27 | { 28 | $storageDefinition = $container->findDefinition('fos_oauth_server.storage'); 29 | $className = $container->getParameterBag()->resolveValue($storageDefinition->getClass()); 30 | $storageClass = new \ReflectionClass($className); 31 | if (!$storageClass->implementsInterface('FOS\OAuthServerBundle\Storage\GrantExtensionDispatcherInterface')) { 32 | return; 33 | } 34 | 35 | foreach ($container->findTaggedServiceIds('fos_oauth_server.grant_extension') as $id => $tags) { 36 | foreach ($tags as $tag) { 37 | if (empty($tag['uri'])) { 38 | throw new InvalidArgumentException(sprintf('Service "%s" must define the "uri" attribute on "fos_oauth_server.grant_extension" tags.', $id)); 39 | } 40 | 41 | $storageDefinition->addMethodCall('setGrantExtension', [$tag['uri'], new Reference($id)]); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/RequestStackCompilerPass.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\DependencyInjection\Compiler; 15 | 16 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 17 | use Symfony\Component\DependencyInjection\ContainerBuilder; 18 | use Symfony\Component\DependencyInjection\Reference; 19 | 20 | /** 21 | * @author Ener-Getick 22 | * 23 | * @internal 24 | */ 25 | final class RequestStackCompilerPass implements CompilerPassInterface 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function process(ContainerBuilder $container): void 31 | { 32 | if ($container->has('request_stack')) { 33 | return; 34 | } 35 | 36 | $definition = $container->getDefinition('fos_oauth_server.authorize.form.handler.default'); 37 | $definition->addMethodCall('setContainer', [new Reference('service_container')]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /DependencyInjection/Security/Factory/OAuthFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\DependencyInjection\Security\Factory; 15 | 16 | use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; 17 | use Symfony\Component\Config\Definition\Builder\NodeDefinition; 18 | use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; 19 | use Symfony\Component\DependencyInjection\ChildDefinition; 20 | use Symfony\Component\DependencyInjection\ContainerBuilder; 21 | use Symfony\Component\DependencyInjection\Reference; 22 | 23 | /** 24 | * OAuthFactory class. 25 | * 26 | * @author Arnaud Le Blanc 27 | */ 28 | class OAuthFactory implements AuthenticatorFactoryInterface 29 | { 30 | public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string 31 | { 32 | $authenticatorId = 'security.authenticator.oauth2.'.$firewallName; 33 | $firewallEventDispatcherId = 'security.event_dispatcher.'.$firewallName; 34 | 35 | // authenticator manager 36 | $firewallAuthenticationProviders = []; 37 | $authenticators = array_map(function ($firewallName) { 38 | return new Reference($firewallName); 39 | }, $firewallAuthenticationProviders); 40 | $container 41 | ->setDefinition($managerId = 'security.authenticator.oauth2.'.$firewallName, new ChildDefinition('fos_oauth_server.security.authenticator.manager')) 42 | // ->replaceArgument(0, $authenticators) 43 | // ->replaceArgument(2, new Reference($firewallEventDispatcherId)) 44 | ->addTag('monolog.logger', ['channel' => 'security']) 45 | ; 46 | 47 | $managerLocator = $container->getDefinition('security.authenticator.managers_locator'); 48 | $managerLocator->replaceArgument(0, array_merge($managerLocator->getArgument(0), [$firewallName => new ServiceClosureArgument(new Reference($managerId))])); 49 | 50 | // authenticator manager listener 51 | $container 52 | ->setDefinition('security.firewall.authenticator.'.$firewallName, new ChildDefinition('security.firewall.authenticator')) 53 | ->replaceArgument(0, new Reference($managerId)) 54 | ; 55 | 56 | // user checker listener 57 | $container 58 | ->setDefinition('security.listener.user_checker.'.$firewallName, new ChildDefinition('security.listener.user_checker')) 59 | ->replaceArgument(0, new Reference('security.user_checker.'.$firewallName)) 60 | ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]) 61 | ; 62 | 63 | // Add authenticators to the debug:firewall command 64 | if ($container->hasDefinition('security.command.debug_firewall')) { 65 | $debugCommand = $container->getDefinition('security.command.debug_firewall'); 66 | $debugCommand->replaceArgument(3, array_merge($debugCommand->getArgument(3), [$firewallName => $authenticators])); 67 | } 68 | 69 | return $authenticatorId; 70 | } 71 | 72 | public function getPriority(): int 73 | { 74 | return 0; 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function getKey(): string 81 | { 82 | return 'fos_oauth'; 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function addConfiguration(NodeDefinition $builder): void 89 | { 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Document/AccessToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Document; 15 | 16 | use FOS\OAuthServerBundle\Model\AccessToken as BaseAccessToken; 17 | 18 | class AccessToken extends BaseAccessToken 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Document/AccessTokenManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Document; 15 | 16 | use FOS\OAuthServerBundle\Model\AccessTokenManagerInterface; 17 | 18 | class AccessTokenManager extends TokenManager implements AccessTokenManagerInterface 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Document/AuthCode.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Document; 15 | 16 | use FOS\OAuthServerBundle\Model\AuthCode as BaseAuthCode; 17 | 18 | /** 19 | * @author Richard Fullmer 20 | */ 21 | class AuthCode extends BaseAuthCode 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /Document/AuthCodeManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Document; 15 | 16 | use Doctrine\ODM\MongoDB\DocumentManager; 17 | use Doctrine\ODM\MongoDB\Repository\DocumentRepository; 18 | use FOS\OAuthServerBundle\Model\AuthCodeInterface; 19 | use FOS\OAuthServerBundle\Model\AuthCodeManager as BaseAuthCodeManager; 20 | use MongoDB\DeleteResult; 21 | 22 | class AuthCodeManager extends BaseAuthCodeManager 23 | { 24 | /** 25 | * @var DocumentManager 26 | */ 27 | protected $dm; 28 | 29 | /** 30 | * @var DocumentRepository 31 | */ 32 | protected $repository; 33 | 34 | /** 35 | * @var string 36 | */ 37 | protected $class; 38 | 39 | public function __construct(DocumentManager $dm, $class) 40 | { 41 | // NOTE: bug in Doctrine, hinting DocumentRepository|ObjectRepository when only DocumentRepository is expected 42 | /** @var DocumentRepository $repository */ 43 | $repository = $dm->getRepository($class); 44 | 45 | $this->dm = $dm; 46 | $this->repository = $repository; 47 | $this->class = $class; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function getClass(): string 54 | { 55 | return $this->class; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function findAuthCodeBy(array $criteria): ?AuthCodeInterface 62 | { 63 | return $this->repository->findOneBy($criteria); 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function updateAuthCode(AuthCodeInterface $authCode) 70 | { 71 | $this->dm->persist($authCode); 72 | $this->dm->flush(); 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function deleteAuthCode(AuthCodeInterface $authCode) 79 | { 80 | $this->dm->remove($authCode); 81 | $this->dm->flush(); 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function deleteExpired(): int 88 | { 89 | $result = $this 90 | ->repository 91 | ->createQueryBuilder() 92 | ->remove() 93 | ->field('expiresAt')->lt(time()) 94 | ->getQuery(['safe' => true]) 95 | ->execute() 96 | ; 97 | 98 | return $result instanceof DeleteResult ? $result->getDeletedCount() : $result['n']; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Document/Client.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Document; 15 | 16 | use FOS\OAuthServerBundle\Model\Client as BaseClient; 17 | 18 | class Client extends BaseClient 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Document/ClientManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Document; 15 | 16 | use Doctrine\ODM\MongoDB\DocumentManager; 17 | use Doctrine\ODM\MongoDB\Repository\DocumentRepository; 18 | use FOS\OAuthServerBundle\Model\ClientInterface; 19 | use FOS\OAuthServerBundle\Model\ClientManager as BaseClientManager; 20 | 21 | class ClientManager extends BaseClientManager 22 | { 23 | /** 24 | * @var DocumentManager 25 | */ 26 | protected $dm; 27 | 28 | /** 29 | * @var DocumentRepository 30 | */ 31 | protected $repository; 32 | 33 | /** 34 | * @var string 35 | */ 36 | protected $class; 37 | 38 | public function __construct(DocumentManager $dm, $class) 39 | { 40 | // NOTE: bug in Doctrine, hinting DocumentRepository|ObjectRepository when only DocumentRepository is expected 41 | /** @var DocumentRepository $repository */ 42 | $repository = $dm->getRepository($class); 43 | 44 | $this->dm = $dm; 45 | $this->repository = $repository; 46 | $this->class = $class; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function getClass(): string 53 | { 54 | return $this->class; 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function findClientBy(array $criteria): ?ClientInterface 61 | { 62 | return $this->repository->findOneBy($criteria); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function updateClient(ClientInterface $client) 69 | { 70 | $this->dm->persist($client); 71 | $this->dm->flush(); 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function deleteClient(ClientInterface $client) 78 | { 79 | $this->dm->remove($client); 80 | $this->dm->flush(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Document/RefreshToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Document; 15 | 16 | use FOS\OAuthServerBundle\Model\RefreshToken as BaseRefreshToken; 17 | 18 | class RefreshToken extends BaseRefreshToken 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Document/RefreshTokenManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Document; 15 | 16 | use FOS\OAuthServerBundle\Model\RefreshTokenManagerInterface; 17 | 18 | class RefreshTokenManager extends TokenManager implements RefreshTokenManagerInterface 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Document/TokenManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Document; 15 | 16 | use Doctrine\ODM\MongoDB\DocumentManager; 17 | use Doctrine\ODM\MongoDB\Repository\DocumentRepository; 18 | use FOS\OAuthServerBundle\Model\TokenInterface; 19 | use FOS\OAuthServerBundle\Model\TokenManager as BaseTokenManager; 20 | use MongoDB\DeleteResult; 21 | 22 | class TokenManager extends BaseTokenManager 23 | { 24 | /** 25 | * @var DocumentManager 26 | */ 27 | protected $dm; 28 | 29 | /** 30 | * @var DocumentRepository 31 | */ 32 | protected $repository; 33 | 34 | /** 35 | * @var string 36 | */ 37 | protected $class; 38 | 39 | public function __construct(DocumentManager $dm, $class) 40 | { 41 | // NOTE: bug in Doctrine, hinting DocumentRepository|ObjectRepository when only DocumentRepository is expected 42 | /** @var DocumentRepository $repository */ 43 | $repository = $dm->getRepository($class); 44 | 45 | $this->dm = $dm; 46 | $this->repository = $repository; 47 | $this->class = $class; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function getClass(): string 54 | { 55 | return $this->class; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function findTokenBy(array $criteria): ?TokenInterface 62 | { 63 | return $this->repository->findOneBy($criteria); 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function updateToken(TokenInterface $token) 70 | { 71 | $this->dm->persist($token); 72 | $this->dm->flush(); 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function deleteToken(TokenInterface $token) 79 | { 80 | $this->dm->remove($token); 81 | $this->dm->flush(); 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function deleteExpired(): int 88 | { 89 | $result = $this 90 | ->repository 91 | ->createQueryBuilder() 92 | ->remove() 93 | ->field('expiresAt')->lt(time()) 94 | ->getQuery(['safe' => true]) 95 | ->execute() 96 | ; 97 | 98 | return $result instanceof DeleteResult ? $result->getDeletedCount() : $result['n']; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Entity/AccessToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Entity; 15 | 16 | use FOS\OAuthServerBundle\Model\AccessToken as BaseAccessToken; 17 | 18 | class AccessToken extends BaseAccessToken 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Entity/AccessTokenManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Entity; 15 | 16 | use FOS\OAuthServerBundle\Model\AccessTokenManagerInterface; 17 | 18 | class AccessTokenManager extends TokenManager implements AccessTokenManagerInterface 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Entity/AuthCode.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Entity; 15 | 16 | use FOS\OAuthServerBundle\Model\AuthCode as BaseAuthCode; 17 | 18 | class AuthCode extends BaseAuthCode 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Entity/AuthCodeManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Entity; 15 | 16 | use Doctrine\ORM\EntityManagerInterface; 17 | use FOS\OAuthServerBundle\Model\AuthCodeInterface; 18 | use FOS\OAuthServerBundle\Model\AuthCodeManager as BaseAuthCodeManager; 19 | 20 | class AuthCodeManager extends BaseAuthCodeManager 21 | { 22 | /** 23 | * @var EntityManagerInterface 24 | */ 25 | protected $em; 26 | 27 | /** 28 | * @var string 29 | */ 30 | protected $class; 31 | 32 | public function __construct(EntityManagerInterface $em, string $class) 33 | { 34 | $this->em = $em; 35 | $this->class = $class; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function getClass(): string 42 | { 43 | return $this->class; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function findAuthCodeBy(array $criteria): ?AuthCodeInterface 50 | { 51 | return $this->em->getRepository($this->class)->findOneBy($criteria); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function updateAuthCode(AuthCodeInterface $authCode) 58 | { 59 | $this->em->persist($authCode); 60 | $this->em->flush(); 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function deleteAuthCode(AuthCodeInterface $authCode) 67 | { 68 | $this->em->remove($authCode); 69 | $this->em->flush(); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function deleteExpired(): int 76 | { 77 | /** @var \Doctrine\ORM\EntityRepository $repository */ 78 | $repository = $this->em->getRepository($this->class); 79 | 80 | $qb = $repository->createQueryBuilder('a'); 81 | $qb 82 | ->delete() 83 | ->where('a.expiresAt < :time') 84 | ->setParameter('time', time()) 85 | ; 86 | 87 | return $qb->getQuery()->execute(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Entity/Client.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Entity; 15 | 16 | use FOS\OAuthServerBundle\Model\Client as BaseClient; 17 | 18 | class Client extends BaseClient 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Entity/ClientManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Entity; 15 | 16 | use Doctrine\ORM\EntityManagerInterface; 17 | use Doctrine\ORM\EntityRepository; 18 | use FOS\OAuthServerBundle\Model\ClientInterface; 19 | use FOS\OAuthServerBundle\Model\ClientManager as BaseClientManager; 20 | 21 | class ClientManager extends BaseClientManager 22 | { 23 | /** 24 | * @var EntityManagerInterface 25 | */ 26 | protected $em; 27 | 28 | /** 29 | * @var EntityRepository 30 | */ 31 | protected $repository; 32 | 33 | /** 34 | * @var string 35 | */ 36 | protected $class; 37 | 38 | public function __construct(EntityManagerInterface $em, $class) 39 | { 40 | // NOTE: bug in Doctrine, hinting EntityRepository|ObjectRepository when only EntityRepository is expected 41 | /** @var EntityRepository $repository */ 42 | $repository = $em->getRepository($class); 43 | 44 | $this->em = $em; 45 | $this->repository = $repository; 46 | $this->class = $class; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function getClass(): string 53 | { 54 | return $this->class; 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function findClientBy(array $criteria): ?ClientInterface 61 | { 62 | return $this->repository->findOneBy($criteria); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function updateClient(ClientInterface $client) 69 | { 70 | $this->em->persist($client); 71 | $this->em->flush(); 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function deleteClient(ClientInterface $client) 78 | { 79 | $this->em->remove($client); 80 | $this->em->flush(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Entity/RefreshToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Entity; 15 | 16 | use FOS\OAuthServerBundle\Model\RefreshToken as BaseRefreshToken; 17 | 18 | class RefreshToken extends BaseRefreshToken 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Entity/RefreshTokenManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Entity; 15 | 16 | use FOS\OAuthServerBundle\Model\RefreshTokenManagerInterface; 17 | 18 | class RefreshTokenManager extends TokenManager implements RefreshTokenManagerInterface 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Entity/TokenManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Entity; 15 | 16 | use Doctrine\ORM\EntityManagerInterface; 17 | use Doctrine\ORM\EntityRepository; 18 | use FOS\OAuthServerBundle\Model\TokenInterface; 19 | use FOS\OAuthServerBundle\Model\TokenManager as BaseTokenManager; 20 | 21 | class TokenManager extends BaseTokenManager 22 | { 23 | /** 24 | * @var EntityManagerInterface 25 | */ 26 | protected $em; 27 | 28 | /** 29 | * @var EntityRepository 30 | */ 31 | protected $repository; 32 | 33 | /** 34 | * @var string 35 | */ 36 | protected $class; 37 | 38 | public function __construct(EntityManagerInterface $em, $class) 39 | { 40 | // NOTE: bug in Doctrine, hinting EntityRepository|ObjectRepository when only EntityRepository is expected 41 | /** @var EntityRepository $repository */ 42 | $repository = $em->getRepository($class); 43 | 44 | $this->em = $em; 45 | $this->repository = $repository; 46 | $this->class = $class; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function getClass(): string 53 | { 54 | return $this->class; 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function findTokenBy(array $criteria): ?TokenInterface 61 | { 62 | return $this->repository->findOneBy($criteria); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function updateToken(TokenInterface $token) 69 | { 70 | $this->em->persist($token); 71 | $this->em->flush(); 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function deleteToken(TokenInterface $token) 78 | { 79 | $this->em->remove($token); 80 | $this->em->flush(); 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function deleteExpired(): int 87 | { 88 | $qb = $this->repository->createQueryBuilder('t'); 89 | $qb 90 | ->delete() 91 | ->where('t.expiresAt < :time') 92 | ->setParameter('time', time()) 93 | ; 94 | 95 | return $qb->getQuery()->execute(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Event/OAuthEvent.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Event; 15 | 16 | use FOS\OAuthServerBundle\Model\ClientInterface; 17 | use Symfony\Component\Security\Core\User\UserInterface; 18 | use Symfony\Contracts\EventDispatcher\Event; 19 | 20 | class OAuthEvent extends Event 21 | { 22 | public const PRE_AUTHORIZATION_PROCESS = 'fos_oauth_server.pre_authorization_process'; 23 | 24 | public const POST_AUTHORIZATION_PROCESS = 'fos_oauth_server.post_authorization_process'; 25 | 26 | public function __construct( 27 | private UserInterface $user, 28 | private ClientInterface $client, 29 | private bool $isAuthorizedClient = false) 30 | { 31 | } 32 | 33 | public function getUser(): UserInterface 34 | { 35 | return $this->user; 36 | } 37 | 38 | 39 | public function setAuthorizedClient(bool $isAuthorizedClient): void 40 | { 41 | $this->isAuthorizedClient = $isAuthorizedClient; 42 | } 43 | 44 | public function isAuthorizedClient(): bool 45 | { 46 | return $this->isAuthorizedClient; 47 | } 48 | 49 | public function getClient(): ClientInterface 50 | { 51 | return $this->client; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /FOSOAuthServerBundle.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle; 15 | 16 | use FOS\OAuthServerBundle\DependencyInjection\Compiler\GrantExtensionsCompilerPass; 17 | use FOS\OAuthServerBundle\DependencyInjection\Compiler\RequestStackCompilerPass; 18 | use FOS\OAuthServerBundle\DependencyInjection\FOSOAuthServerExtension; 19 | use FOS\OAuthServerBundle\DependencyInjection\Security\Factory\OAuthFactory; 20 | use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension; 21 | use Symfony\Component\DependencyInjection\ContainerBuilder; 22 | use Symfony\Component\HttpKernel\Bundle\Bundle; 23 | 24 | class FOSOAuthServerBundle extends Bundle 25 | { 26 | public function __construct() 27 | { 28 | $this->extension = new FOSOAuthServerExtension(); 29 | } 30 | 31 | public function build(ContainerBuilder $container): void 32 | { 33 | parent::build($container); 34 | 35 | /** @var SecurityExtension $extension */ 36 | $extension = $container->getExtension('security'); 37 | $extension->addAuthenticatorFactory(new OAuthFactory()); 38 | 39 | $container->addCompilerPass(new GrantExtensionsCompilerPass()); 40 | $container->addCompilerPass(new RequestStackCompilerPass()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Form/Handler/AuthorizeFormHandler.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Form\Handler; 15 | 16 | use FOS\OAuthServerBundle\Form\Model\Authorize; 17 | use Symfony\Component\DependencyInjection\ContainerInterface; 18 | use Symfony\Component\Form\FormInterface; 19 | use Symfony\Component\HttpFoundation\Request; 20 | use Symfony\Component\HttpFoundation\RequestStack; 21 | 22 | /** 23 | * @author Chris Jones 24 | */ 25 | class AuthorizeFormHandler 26 | { 27 | /** 28 | * @var FormInterface 29 | */ 30 | protected $form; 31 | 32 | /** 33 | * @var ContainerInterface 34 | */ 35 | protected $container; 36 | 37 | /** 38 | * @var RequestStack|Request|null 39 | */ 40 | private $requestStack; 41 | 42 | /** 43 | * @param Request|RequestStack $requestStack 44 | */ 45 | public function __construct(FormInterface $form, $requestStack = null) 46 | { 47 | if (null !== $requestStack && !$requestStack instanceof RequestStack && !$requestStack instanceof Request) { 48 | throw new \InvalidArgumentException(sprintf('Argument 2 of %s must be an instanceof RequestStack or Request', __CLASS__)); 49 | } 50 | 51 | $this->form = $form; 52 | $this->requestStack = $requestStack; 53 | } 54 | 55 | /** 56 | * Sets the container. 57 | * 58 | * @param ContainerInterface|null $container A ContainerInterface instance or null 59 | */ 60 | public function setContainer(?ContainerInterface $container = null) 61 | { 62 | $this->container = $container; 63 | } 64 | 65 | public function isAccepted() 66 | { 67 | return $this->form->getData()->accepted; 68 | } 69 | 70 | public function isRejected() 71 | { 72 | return !$this->form->getData()->accepted; 73 | } 74 | 75 | /** 76 | * @return bool 77 | */ 78 | public function process() 79 | { 80 | $request = $this->getCurrentRequest(); 81 | 82 | if (null === $request) { 83 | return false; 84 | } 85 | 86 | $this->form->setData(new Authorize( 87 | $request->request->has('accepted'), 88 | $request->query->all() 89 | )); 90 | 91 | if ('POST' !== $request->getMethod()) { 92 | return false; 93 | } 94 | 95 | $this->form->handleRequest($request); 96 | if ($this->form->isSubmitted() && !$this->form->isValid()) { 97 | return false; 98 | } 99 | 100 | $this->onSuccess(); 101 | 102 | return true; 103 | } 104 | 105 | public function getScope() 106 | { 107 | return $this->form->getData()->scope; 108 | } 109 | 110 | /** 111 | * Put form data in $_GET so that OAuth2 library will call Request::createFromGlobals(). 112 | * 113 | * @todo finishClientAuthorization() is a bit odd since it accepts $data 114 | * but then proceeds to ignore it and forces everything to be in $request->query 115 | */ 116 | protected function onSuccess() 117 | { 118 | $_GET = [ 119 | 'client_id' => $this->form->getData()->client_id, 120 | 'response_type' => $this->form->getData()->response_type, 121 | 'redirect_uri' => $this->form->getData()->redirect_uri, 122 | 'state' => $this->form->getData()->state, 123 | 'scope' => $this->form->getData()->scope, 124 | ]; 125 | } 126 | 127 | private function getCurrentRequest() 128 | { 129 | if (null === $this->requestStack) { 130 | return $this->container->get('request'); 131 | } 132 | 133 | if ($this->requestStack instanceof Request) { 134 | return $this->requestStack; 135 | } 136 | 137 | return $this->requestStack->getCurrentRequest(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Form/Model/Authorize.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Form\Model; 15 | 16 | /** 17 | * @author Chris Jones 18 | */ 19 | #[\AllowDynamicProperties] 20 | class Authorize 21 | { 22 | /** 23 | * @var bool 24 | */ 25 | public $accepted; 26 | 27 | /** 28 | * @var string 29 | */ 30 | public $client_id; 31 | 32 | /** 33 | * @var string 34 | */ 35 | public $response_type; 36 | 37 | /** 38 | * @var string 39 | */ 40 | public $redirect_uri; 41 | 42 | /** 43 | * @var string 44 | */ 45 | public $state; 46 | 47 | /** 48 | * @var string 49 | */ 50 | public $scope; 51 | 52 | public function __construct(bool $accepted, array $query = []) 53 | { 54 | foreach ($query as $key => $value) { 55 | $this->{$key} = $value; 56 | } 57 | 58 | $this->accepted = $accepted; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Form/Type/AuthorizeFormType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Form\Type; 15 | 16 | use FOS\OAuthServerBundle\Util\LegacyFormHelper; 17 | use Symfony\Component\Form\AbstractType; 18 | use Symfony\Component\Form\FormBuilderInterface; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | 21 | /** 22 | * @author Chris Jones 23 | */ 24 | class AuthorizeFormType extends AbstractType 25 | { 26 | public function buildForm(FormBuilderInterface $builder, array $options): void 27 | { 28 | $hiddenType = LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\HiddenType'); 29 | 30 | $builder->add('client_id', $hiddenType); 31 | $builder->add('response_type', $hiddenType); 32 | $builder->add('redirect_uri', $hiddenType); 33 | $builder->add('state', $hiddenType); 34 | $builder->add('scope', $hiddenType); 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function configureOptions(OptionsResolver $resolver): void 41 | { 42 | $resolver->setDefaults([ 43 | 'data_class' => 'FOS\OAuthServerBundle\Form\Model\Authorize', 44 | ]); 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function getBlockPrefix() 51 | { 52 | return 'fos_oauth_server_authorize'; 53 | } 54 | 55 | /** 56 | * @return string 57 | */ 58 | public function getName() 59 | { 60 | return $this->getBlockPrefix(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | QA_DOCKER_IMAGE=jakzal/phpqa:alpine 2 | QA_DOCKER_COMMAND=docker run -it --rm -v "$(shell pwd):/project" -w /project ${QA_DOCKER_IMAGE} 3 | 4 | dist: cs-full phpstan phpunit 5 | ci: cs-full-check phpstan phpunit-coverage 6 | lint: cs-full-check phpstan 7 | 8 | phpstan: 9 | sh -c "${QA_DOCKER_COMMAND} phpstan analyse --configuration phpstan.neon --level 6 ." 10 | 11 | cs: 12 | sh -c "${QA_DOCKER_COMMAND} php-cs-fixer fix -vvv --diff --config .php_cs-fixer.php" 13 | 14 | cs-full: 15 | sh -c "${QA_DOCKER_COMMAND} php-cs-fixer fix -vvv --using-cache=no --diff --config .php_cs-fixer.php" 16 | 17 | cs-full-check: 18 | sh -c "${QA_DOCKER_COMMAND} php-cs-fixer fix -vvv --using-cache=no --diff --dry-run --config .php_cs-fixer.php" 19 | 20 | composer-compat: 21 | composer config "platform.ext-mongo" "1.6.16" 22 | composer require alcaeus/mongo-php-adapter --no-update 23 | 24 | composer-config-beta: 25 | composer config "minimum-stability" "beta" 26 | 27 | composer-install: 28 | rm -f composer.lock && cp composer.json composer.json~ 29 | [[ -v SYMFONY_VERSION ]] && composer require "symfony/symfony:${SYMFONY_VERSION}" --no-update || true 30 | composer update --prefer-dist --no-interaction 31 | mv composer.json~ composer.json 32 | 33 | phpunit: 34 | vendor/bin/phpunit 35 | 36 | # TODO: output to COV 37 | phpunit-coverage: 38 | phpdbg -qrr vendor/bin/phpunit --coverage-text 39 | -------------------------------------------------------------------------------- /Model/AccessToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | class AccessToken extends Token implements AccessTokenInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /Model/AccessTokenInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | use OAuth2\Model\IOAuth2AccessToken; 17 | 18 | interface AccessTokenInterface extends TokenInterface, IOAuth2AccessToken 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Model/AccessTokenManagerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | interface AccessTokenManagerInterface extends TokenManagerInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /Model/AuthCode.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | /** 17 | * @author Richard Fullmer 18 | */ 19 | class AuthCode extends Token implements AuthCodeInterface 20 | { 21 | protected string $redirectUri; 22 | 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function setRedirectUri( string $redirectUri) 27 | { 28 | $this->redirectUri = $redirectUri; 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function getRedirectUri(): string 35 | { 36 | return $this->redirectUri; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Model/AuthCodeInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | use OAuth2\Model\IOAuth2AuthCode; 17 | 18 | /** 19 | * @author Richard Fullmer 20 | */ 21 | interface AuthCodeInterface extends TokenInterface, IOAuth2AuthCode 22 | { 23 | /** 24 | * @param string $redirectUri 25 | */ 26 | public function setRedirectUri( string $redirectUri ); 27 | } 28 | -------------------------------------------------------------------------------- /Model/AuthCodeManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | abstract class AuthCodeManager implements AuthCodeManagerInterface 17 | { 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function createAuthCode(): AuthCodeInterface 22 | { 23 | $class = $this->getClass(); 24 | 25 | return new $class(); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function findAuthCodeByToken(string $token): ?AuthCodeInterface 32 | { 33 | return $this->findAuthCodeBy(['token' => $token]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Model/AuthCodeManagerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | /** 17 | * @author Richard Fullmer 18 | */ 19 | interface AuthCodeManagerInterface 20 | { 21 | /** 22 | * Create a new auth code. 23 | */ 24 | public function createAuthCode(): AuthCodeInterface; 25 | 26 | /** 27 | * Return the class name. 28 | */ 29 | public function getClass(): string; 30 | 31 | /** 32 | * Retrieve an auth code using a set of criteria. 33 | */ 34 | public function findAuthCodeBy(array $criteria): ?AuthCodeInterface; 35 | 36 | /** 37 | * Retrieve an auth code by its token. 38 | */ 39 | public function findAuthCodeByToken(string $token): ?AuthCodeInterface; 40 | 41 | /** 42 | * Update a given auth code. 43 | */ 44 | public function updateAuthCode(AuthCodeInterface $authCode); 45 | 46 | /** 47 | * Delete a given auth code. 48 | */ 49 | public function deleteAuthCode(AuthCodeInterface $authCode); 50 | 51 | /** 52 | * Delete expired auth codes. 53 | */ 54 | public function deleteExpired(): int; 55 | } 56 | -------------------------------------------------------------------------------- /Model/Client.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | use FOS\OAuthServerBundle\Util\Random; 17 | use OAuth2\OAuth2; 18 | 19 | class Client implements ClientInterface 20 | { 21 | /** 22 | * @var int 23 | */ 24 | protected $id; 25 | 26 | protected ?string $randomId = null; 27 | 28 | protected ?string $secret = null; 29 | 30 | protected array $redirectUris = []; 31 | 32 | protected array $allowedGrantTypes = []; 33 | 34 | public function __construct() 35 | { 36 | $this->allowedGrantTypes[] = OAuth2::GRANT_TYPE_AUTH_CODE; 37 | 38 | $this->setRandomId(Random::generateToken()); 39 | $this->setSecret(Random::generateToken()); 40 | } 41 | 42 | public function getId() 43 | { 44 | return $this->id; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function setRandomId(string $random) 51 | { 52 | $this->randomId = $random; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function getRandomId(): ?string 59 | { 60 | return $this->randomId; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function getPublicId(): string 67 | { 68 | return sprintf('%s_%s', $this->getId(), $this->getRandomId()); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function setSecret($secret) 75 | { 76 | $this->secret = $secret; 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | public function getSecret(): ?string 83 | { 84 | return $this->secret; 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public function checkSecret(?string $secret): bool 91 | { 92 | return null === $this->secret || $secret === $this->secret; 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public function setRedirectUris(array $redirectUris) 99 | { 100 | $this->redirectUris = $redirectUris; 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function getRedirectUris(): array 107 | { 108 | return $this->redirectUris; 109 | } 110 | 111 | /** 112 | * {@inheritdoc} 113 | */ 114 | public function setAllowedGrantTypes(array $grantTypes) 115 | { 116 | $this->allowedGrantTypes = $grantTypes; 117 | } 118 | 119 | /** 120 | * {@inheritdoc} 121 | */ 122 | public function getAllowedGrantTypes(): array 123 | { 124 | return $this->allowedGrantTypes; 125 | } 126 | 127 | public function getRoles(): array 128 | { 129 | return ['ROLE_USER']; 130 | } 131 | 132 | public function getPassword(): string 133 | { 134 | if (null === $this->secret) { 135 | throw new \LogicException('The client has no secret.'); 136 | } 137 | return $this->getSecret(); 138 | } 139 | 140 | public function getSalt(): ?string 141 | { 142 | // Will use auto salt system 143 | return null; 144 | } 145 | 146 | public function eraseCredentials(): void 147 | { 148 | // nothind to erase 149 | } 150 | 151 | public function getUsername(): string 152 | { 153 | return $this->getRandomId(); 154 | } 155 | 156 | public function getUserIdentifier(): string 157 | { 158 | return $this->getRandomId(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Model/ClientInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | use OAuth2\Model\IOAuth2Client; 17 | 18 | interface ClientInterface extends IOAuth2Client 19 | { 20 | public function setRandomId(string $random); 21 | 22 | public function getRandomId(): ?string; 23 | 24 | public function setSecret(string $secret); 25 | 26 | public function checkSecret(?string $secret): bool; 27 | 28 | public function getSecret(): ?string; 29 | 30 | public function setRedirectUris(array $redirectUris); 31 | 32 | public function setAllowedGrantTypes(array $grantTypes); 33 | 34 | public function getAllowedGrantTypes(): ?array; 35 | } 36 | -------------------------------------------------------------------------------- /Model/ClientManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | abstract class ClientManager implements ClientManagerInterface 17 | { 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function createClient(): ClientInterface 22 | { 23 | $class = $this->getClass(); 24 | 25 | return new $class(); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function findClientByPublicId($publicId): ?ClientInterface 32 | { 33 | if (false === $pos = mb_strpos($publicId, '_')) { 34 | return null; 35 | } 36 | 37 | $id = mb_substr($publicId, 0, $pos); 38 | $randomId = mb_substr($publicId, $pos + 1); 39 | 40 | return $this->findClientBy([ 41 | 'id' => $id, 42 | 'randomId' => $randomId, 43 | ]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Model/ClientManagerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | interface ClientManagerInterface 17 | { 18 | public function createClient(): ClientInterface; 19 | 20 | public function getClass(): string; 21 | 22 | public function findClientBy(array $criteria): ?ClientInterface; 23 | 24 | /** 25 | * @param mixed $publicId 26 | */ 27 | public function findClientByPublicId($publicId): ?ClientInterface; 28 | 29 | public function updateClient(ClientInterface $client); 30 | 31 | public function deleteClient(ClientInterface $client); 32 | } 33 | -------------------------------------------------------------------------------- /Model/RefreshToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | class RefreshToken extends Token implements RefreshTokenInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /Model/RefreshTokenInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | use OAuth2\Model\IOAuth2RefreshToken; 17 | 18 | interface RefreshTokenInterface extends TokenInterface, IOAuth2RefreshToken 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Model/RefreshTokenManagerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | interface RefreshTokenManagerInterface extends TokenManagerInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /Model/Token.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | use Symfony\Component\Security\Core\User\UserInterface; 17 | 18 | class Token implements TokenInterface 19 | { 20 | /** 21 | * @var int 22 | */ 23 | protected $id; 24 | 25 | protected ClientInterface $client; 26 | 27 | protected string $token; 28 | 29 | protected ?int $expiresAt = null; 30 | 31 | protected ?string $scope = null; 32 | 33 | protected ?UserInterface $user = null; 34 | 35 | public function getId() 36 | { 37 | return $this->id; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function getClientId(): string 44 | { 45 | return $this->getClient()->getPublicId(); 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function setExpiresAt(?int $timestamp) 52 | { 53 | $this->expiresAt = $timestamp; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function getExpiresAt(): ?int 60 | { 61 | return $this->expiresAt; 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function getExpiresIn(): int 68 | { 69 | if ($this->expiresAt) { 70 | return $this->expiresAt - time(); 71 | } 72 | 73 | return PHP_INT_MAX; 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function hasExpired(): bool 80 | { 81 | if ($this->expiresAt) { 82 | return time() > $this->expiresAt; 83 | } 84 | 85 | return false; 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function setToken(string $token) 92 | { 93 | $this->token = $token; 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | public function getToken(): string 100 | { 101 | return $this->token; 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function setScope(?string $scope) 108 | { 109 | $this->scope = $scope; 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | public function getScope(): ?string 116 | { 117 | return $this->scope; 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | */ 123 | public function setUser(?UserInterface $user) 124 | { 125 | $this->user = $user; 126 | } 127 | 128 | /** 129 | * {@inheritdoc} 130 | */ 131 | public function getUser(): ?UserInterface 132 | { 133 | return $this->user; 134 | } 135 | 136 | /** 137 | * {@inheritdoc} 138 | * 139 | * @return mixed 140 | */ 141 | public function getData() 142 | { 143 | return $this->getUser(); 144 | } 145 | 146 | /** 147 | * {@inheritdoc} 148 | */ 149 | public function setClient(ClientInterface $client) 150 | { 151 | $this->client = $client; 152 | } 153 | 154 | /** 155 | * {@inheritdoc} 156 | */ 157 | public function getClient() 158 | { 159 | return $this->client; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Model/TokenInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | use OAuth2\Model\IOAuth2Token; 17 | use Symfony\Component\Security\Core\User\UserInterface; 18 | 19 | interface TokenInterface extends IOAuth2Token 20 | { 21 | public function setExpiresAt(?int $timestamp); 22 | 23 | public function getExpiresAt(): ?int; 24 | 25 | public function setToken(string $token); 26 | 27 | public function setScope(string $scope); 28 | 29 | public function setUser(UserInterface $user); 30 | 31 | public function getUser(): ?UserInterface; 32 | 33 | public function setClient(ClientInterface $client); 34 | } 35 | -------------------------------------------------------------------------------- /Model/TokenManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | abstract class TokenManager implements TokenManagerInterface 17 | { 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function createToken(): TokenInterface 22 | { 23 | $class = $this->getClass(); 24 | 25 | return new $class(); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function findTokenByToken($token): ?TokenInterface 32 | { 33 | return $this->findTokenBy(['token' => $token]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Model/TokenManagerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Model; 15 | 16 | interface TokenManagerInterface 17 | { 18 | /** 19 | * Create a new TokenInterface. 20 | */ 21 | public function createToken(): TokenInterface; 22 | 23 | /** 24 | * Return the class name of the Token. 25 | */ 26 | public function getClass(): string; 27 | 28 | /** 29 | * Retrieve a token using a set of criteria. 30 | */ 31 | public function findTokenBy(array $criteria): ?TokenInterface; 32 | 33 | /** 34 | * Retrieve a token (object) by its token string. 35 | */ 36 | public function findTokenByToken(string $token): ?TokenInterface; 37 | 38 | /** 39 | * Save or update a given token. 40 | */ 41 | public function updateToken(TokenInterface $token); 42 | 43 | /** 44 | * Delete a given token. 45 | */ 46 | public function deleteToken(TokenInterface $token); 47 | 48 | /** 49 | * Delete expired tokens. 50 | */ 51 | public function deleteExpired(): int; 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FOSOAuthServerBundle 2 | ==================== 3 | [![Tests](https://github.com/klapaudius/FOSOAuthServerBundle/actions/workflows/php.yml/badge.svg?branch=5.1)](https://github.com/klapaudius/FOSOAuthServerBundle/actions/workflows/php.yml) 4 | [![codecov](https://codecov.io/gh/klapaudius/FOSOAuthServerBundle/graph/badge.svg?token=UK39PJLNTB)](https://codecov.io/gh/klapaudius/FOSOAuthServerBundle) 5 | ![Packagist Downloads](https://img.shields.io/packagist/dt/klapaudius/oauth-server-bundle) 6 | 7 | ## Documentation 8 | 9 | The bulk of the documentation is stored in the `Resources/doc/index.md` file in this bundle: 10 | 11 | [Read the Documentation for master](https://github.com/klapaudius/FOSOAuthServerBundle/blob/master/Resources/doc/index.md) 12 | 13 | ## Repository history 14 | 15 | This repository has been initialy created to remove the deprecated warnings on Symfony 4.2 since several pull requests were not reviewed on FriendsOfSymfony/FOSOAuthServerBundle. 16 | It is actively maintained and works on Symfony 5 (branch 3.0), Symfony 6 (branch 4.0) and Symfony 7 (branch 5.0 / 5.1). 17 | 18 | Feel free to PR. 19 | 20 | ## License 21 | 22 | This bundle is under the MIT license. See the complete license in the bundle: 23 | 24 | Resources/meta/LICENSE 25 | 26 | 27 | ## Credits 28 | 29 | - Boris AUBE 30 | - Arnaud Le Blanc, and [all contributors](https://github.com/klapaudius/FOSOAuthServerBundle/contributors) 31 | - Inspired by [BazingaOAuthBundle](https://github.com/willdurand/BazingaOAuthServerBundle) and [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle) 32 | - Installation doc adapted from [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle) doc. 33 | -------------------------------------------------------------------------------- /Resources/config/authorize.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | %fos_oauth_server.authorize.form.name% 10 | %fos_oauth_server.authorize.form.type% 11 | null 12 | 13 | %fos_oauth_server.authorize.form.validation_groups% 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Resources/config/couchdb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | %fos_oauth_server.model.client.class% 11 | 12 | 13 | 14 | 15 | %fos_oauth_server.model.access_token.class% 16 | 17 | 18 | 19 | 20 | %fos_oauth_server.model.auth_code.class% 21 | 22 | 23 | 24 | 25 | %fos_oauth_server.model.refresh_token.class% 26 | 27 | 28 | 29 | %fos_oauth_server.model_manager_name% 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Resources/config/doctrine/AccessToken.couchdb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Resources/config/doctrine/AccessToken.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/config/doctrine/AccessToken.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/config/doctrine/AuthCode.couchdb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/config/doctrine/AuthCode.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Resources/config/doctrine/AuthCode.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Resources/config/doctrine/Client.couchdb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Resources/config/doctrine/Client.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Resources/config/doctrine/Client.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Resources/config/doctrine/RefreshToken.couchdb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Resources/config/doctrine/RefreshToken.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/config/doctrine/RefreshToken.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/config/mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | %fos_oauth_server.model.client.class% 11 | 12 | 13 | 14 | 15 | %fos_oauth_server.model.access_token.class% 16 | 17 | 18 | 19 | 20 | %fos_oauth_server.model.auth_code.class% 21 | 22 | 23 | 24 | 25 | %fos_oauth_server.model.refresh_token.class% 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Resources/config/oauth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | OAuth2\OAuth2 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | %fos_oauth_server.server.options% 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Resources/config/orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | %fos_oauth_server.model.client.class% 11 | 12 | 13 | 14 | 15 | %fos_oauth_server.model.access_token.class% 16 | 17 | 18 | 19 | 20 | %fos_oauth_server.model.refresh_token.class% 21 | 22 | 23 | 24 | 25 | %fos_oauth_server.model.auth_code.class% 26 | 27 | 28 | 29 | %fos_oauth_server.model_manager_name% 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Resources/config/routing/authorize.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | fos_oauth_server.controller.authorize::authorizeAction 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/config/routing/token.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | fos_oauth_server.controller.token::tokenAction 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/config/security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | FOS\OAuthServerBundle\Security\EntryPoint\OAuthEntryPoint 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Resources/config/validation.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Resources/doc/a_note_about_security.md: -------------------------------------------------------------------------------- 1 | A Note About Security 2 | ===================== 3 | 4 | ## OAuth Implementation with Flawed Session Management 5 | 6 | As described in this great article about [OAuth Authorization attacks](http://software-security.sans.org/blog/2011/03/07/oauth-authorization-attacks-secure-implementation), 7 | if you use the same firewall for the `authorization` page, and for the rest of your application, a malicious user with access to the unattended 8 | browser can use the user's session. 9 | 10 | To protect against that, the FOSOAuthServerBundle comes with a built-in solution. 11 | In the `loginAction()` of your `SecurityController`, just add few lines before to render the response: 12 | 13 | ``` php 14 | getSession(); 28 | 29 | // get the login error if there is one 30 | if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { 31 | $error = $request->attributes->get(Security::AUTHENTICATION_ERROR); 32 | } else { 33 | $error = $session->get(Security::AUTHENTICATION_ERROR); 34 | $session->remove(Security::AUTHENTICATION_ERROR); 35 | } 36 | 37 | // Add the following lines 38 | if ($session->has('_security.target_path')) { 39 | if (false !== strpos($session->get('_security.target_path'), $this->generateUrl('fos_oauth_server_authorize'))) { 40 | $session->set('_fos_oauth_server.ensure_logout', true); 41 | } 42 | } 43 | 44 | return $this->render('AcmeSecurityBundle:Security:login.html.twig', array( 45 | // last username entered by the user 46 | 'last_username' => $session->get(Security::LAST_USERNAME), 47 | 'error' => $error, 48 | )); 49 | } 50 | } 51 | ``` 52 | 53 | Now, when a user will login in order to access the Authorization page, he will be logged out just after his action. 54 | But, in the same time, if he is already logged, he won't be logged out. 55 | 56 | 57 | ## SSL 58 | 59 | Even if the FOSOAuthServerBundle doesn't enforce that, the use of TLS/SSL is the recommended approach. 60 | 61 | 62 | [Back to index](index.md) 63 | -------------------------------------------------------------------------------- /Resources/doc/adding_grant_extensions.md: -------------------------------------------------------------------------------- 1 | Adding Grant Extensions 2 | ======================= 3 | 4 | OAuth2 allows to use [grant extensions](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.5). 5 | It will let you define your own new *grant_types*, when legacy grant_types do not cover your requirements. 6 | 7 | Like classic grants, the grant extensions still requires valid client credentials, and the grant extension has to be added on the `allowedGrantTypes` property of the client. 8 | 9 | ## Creating the GrantExtension 10 | 11 | The GrantExtension class responsibility is to grant an access token, or not, based on the request parameters/headers. 12 | 13 | To illustrate the use of a new grant extension, we'll create one that grants access_token like the bingo game. This is of course not something to do :) 14 | 15 | ### Create your extension class: 16 | 17 | ``` php 18 | $userManager->findRandomUser() 54 | ); 55 | } 56 | 57 | if ($numberToGuess1 == $inputData['number_1'] || $numberToGuess2 == $inputData['number_2']) { 58 | // Only one of the numbers were guessed 59 | // We grant a simple access token 60 | 61 | return true; 62 | } 63 | 64 | return false; // No number guessed, the grant will fail 65 | } 66 | } 67 | ``` 68 | 69 | ### Register the extension 70 | 71 | You then have to register your class as a service, add the tag `fos_oauth_server.grant_extension` with the uri parameter (ie: the name of your grant type). 72 | 73 | ``` yaml 74 | services: 75 | acme.api.oauth.bingo_extension: 76 | class: Acme\ApiBundle\OAuth\BingoGrantExtension 77 | tags: 78 | - { name: fos_oauth_server.grant_extension, uri: 'http://acme.com/grants/bingo' } 79 | ``` 80 | 81 | ### Usage 82 | 83 | ``` 84 | $ curl -XGET "http://acme.com/oauth/v2/token? \ 85 | grant_type=http://acme.com/grants/bingo& \ 86 | client_id=1_1& \ 87 | client_secret=secret& \ 88 | number_1=42& \ 89 | number_2=66" 90 | ``` 91 | -------------------------------------------------------------------------------- /Resources/doc/configuration_reference.md: -------------------------------------------------------------------------------- 1 | FOSOAuthServerBundle Configuration Reference 2 | ============================================ 3 | 4 | All available configuration options are listed below with their default values. 5 | 6 | ``` yaml 7 | fos_oauth_server: 8 | db_driver: ~ # Required. Available: mongodb, orm 9 | client_class: ~ # Required 10 | access_token_class: ~ # Required 11 | refresh_token_class: ~ # Required 12 | auth_code_class: ~ # Required 13 | model_manager_name: ~ # change it to the name of your entity/document manager if you don't want to use the default one. 14 | authorize: 15 | form: 16 | type: fos_oauth_server_authorize 17 | handler: fos_oauth_server.authorize.form.handler.default 18 | name: fos_oauth_server_authorize_form 19 | validation_groups: 20 | 21 | # Defaults: 22 | - Authorize 23 | - Default 24 | service: 25 | storage: fos_oauth_server.storage.default 26 | user_provider: ~ 27 | client_manager: fos_oauth_server.client_manager.default 28 | access_token_manager: fos_oauth_server.access_token_manager.default 29 | refresh_token_manager: fos_oauth_server.refresh_token_manager.default 30 | auth_code_manager: fos_oauth_server.auth_code_manager.default 31 | options: 32 | # Prototype 33 | key: [] 34 | 35 | # Example 36 | # supported_scopes: string 37 | 38 | # Changing tokens and authcode lifetime 39 | #access_token_lifetime: 3600 40 | #refresh_token_lifetime: 1209600 41 | #auth_code_lifetime: 30 42 | 43 | # Token type to respond with. Currently only "Bearer" supported. 44 | #token_type: string 45 | 46 | #realm: 47 | 48 | # Enforce redirect_uri on input for both authorize and token steps. 49 | #enforce_redirect: true or false 50 | 51 | # Enforce state to be passed in authorization (see RFC 6749, section 10.12) 52 | #enforce_state: true or false 53 | ``` 54 | 55 | [Back to index](index.md) 56 | -------------------------------------------------------------------------------- /Resources/doc/custom_db_driver.md: -------------------------------------------------------------------------------- 1 | Custom db driver. 2 | ================= 3 | 4 | The bundle provides drivers for Doctrine ORM and Doctrine MongoDB libraries. 5 | Though sometimes you might want to use the bundle with a custom or in-house written storage. 6 | For that, the bundle has support for custom storage. 7 | Once set, setting manager options in fos_oauth_server.service section becomes mandatory. 8 | 9 | Here's an example of custom configuration: 10 | 11 | ```yaml 12 | # config/packages/fos_oauth_server.yaml 13 | 14 | fos_oauth_server: 15 | db_driver: custom 16 | service: 17 | user_provider: 'user_provider_manager_service_id' 18 | client_manager: 'client_provider_manager_service_id' 19 | access_token_manager: 'access_token_manager_service_id' 20 | refresh_token_manager: 'refresh_token_manager_service_id' 21 | auth_code_manager: 'auth_code_manager_service_id' 22 | 23 | ``` 24 | 25 | [Back to index](index.md) 26 | 27 | -------------------------------------------------------------------------------- /Resources/doc/dealing_with_scopes.md: -------------------------------------------------------------------------------- 1 | Dealing With Scopes 2 | =================== 3 | 4 | OAuth2 allows to use [access token scopes](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.3). 5 | Scopes are what you want, there is not real constraint except to list scopes as a list of strings separated by a space: 6 | 7 | scope1 scope2 8 | 9 | That's why the `scope` column in the model layer is a string, not an array for instance. 10 | 11 | 12 | ## Configuring scopes 13 | 14 | To configure allowed scopes in your application, you have to edit your `app/config/config.yml` file: 15 | 16 | ``` yaml 17 | # app/config/config.yml 18 | fos_oauth_server: 19 | service: 20 | options: 21 | supported_scopes: 22 | - scope1 23 | - scope2 24 | - scope3 25 | - scope4 26 | ``` 27 | 28 | If you have a short list of scopes, you can define them as a simple string: 29 | ``` yaml 30 | # app/config/config.yml 31 | fos_oauth_server: 32 | service: 33 | options: 34 | supported_scopes: scope1 scope2 35 | ``` 36 | 37 | 38 | Now, clients will be able to pass a `scope` parameter when they request an access token. 39 | 40 | 41 | ## Using scopes 42 | 43 | The default behavior of the FOSOAuthServerBundle is to use scopes as [roles](http://symfony.com/doc/master/book/security.html#roles). 44 | In the previous example, it would allow us to use the roles `ROLE_SCOPE1`, and `ROLE_SCOPE2` (scopes are automatically uppercased). 45 | 46 | That way, you can configure the `access_control` section of the security layer: 47 | 48 | ``` yaml 49 | # app/config/security.yml 50 | security: 51 | access_control: 52 | - { path: ^/api/super/secured, role: ROLE_SCOPE1 } 53 | ``` 54 | 55 | For more information, you can read the [Security documentation](http://symfony.com/doc/master/book/security.html#authorization). 56 | 57 | 58 | [Back to index](index.md) 59 | -------------------------------------------------------------------------------- /Resources/doc/extending_the_authorization_page.md: -------------------------------------------------------------------------------- 1 | Extending the Authorization page 2 | ================================ 3 | 4 | The "Authorization Page" is the page you will present to your users, in order to ask them to share their 5 | data with a third-party consumer. This is most of the time a page with two buttons _Deny_, and _Allow_. 6 | 7 | By default, the FOSOAuthServerBundle's authorization page is really basic, and it's probably a good idea to improve it. 8 | 9 | The first step is to copy the [`authorize_content.html.twig`](https://github.com/klapaudius/FOSOAuthServerBundle/blob/master/Resources/views/Authorize/authorize_content.html.twig) template to the `app/Resources/FOSOAuthServerBundle/views/Authorize/` directory. 10 | 11 | You're almost done, now you just have to customize it. By default a _client_ in the FOSOAuthServerBundle 12 | doesn't have any _name_ or _title_ because it depends on your application, and what you really need. 13 | But most of the time, you will give a name to each of your clients, this part is described in the [extending the model](extending_the_model.md) section. 14 | 15 | Now, assuming your clients have a nice _name_, it's a pretty nice idea to expose it to your 16 | users. That way, they will know which consumer asks for their data. If you take a look at the `AuthorizeController`, 17 | you will see a `client` variable which is passed to the templating engine. That means you can use it in your custom template: 18 | 19 | ``` html+jinja 20 |

The application "{{ client.name }}" would like to connect to your account

21 | 22 | {{ form_start(form, {'method': 'POST', 'action': path('fos_oauth_server_authorize'), 'label_attr': {'class': 'fos_oauth_server_authorize'} }) }} 23 | 24 | 25 | 26 | {{ form_row(form.client_id) }} 27 | {{ form_row(form.response_type) }} 28 | {{ form_row(form.redirect_uri) }} 29 | {{ form_row(form.state) }} 30 | {{ form_row(form.scope) }} 31 | {{ form_rest(form) }} 32 | 33 | ``` 34 | 35 | [Back to index](index.md) 36 | -------------------------------------------------------------------------------- /Resources/doc/extending_the_model.md: -------------------------------------------------------------------------------- 1 | Extending the Model 2 | =================== 3 | 4 | This bundle provides a complete solution to quickly setup an OAuth2 security layer for your APIs. 5 | But, as all applications are often different, the bundle has a basic database model. 6 | Thanks to an abstraction layer (see the `Model/` directory), it's really easy to deal with various 7 | database abstraction layers, but in the same time to extend each layer. In this chapter, you will see 8 | how to properly extend the model of the FOSOAuthServerBundle to add your business logic. 9 | 10 | [Back to index](index.md) 11 | -------------------------------------------------------------------------------- /Resources/doc/the_oauth_event_class.md: -------------------------------------------------------------------------------- 1 | The OAuthEvent class 2 | ==================== 3 | 4 | When a user accepts to share his data with a client, it's a nice idea to save this state. 5 | By default, the FOSOAuthServerBundle will always show the authorization page to the user 6 | when an access token is asked. As an access token has a lifetime, it can be annoying for your 7 | users to always accept a client. 8 | 9 | Thanks to the [Event Dispatcher](http://symfony.com/doc/current/components/event_dispatcher.html), 10 | you can listen before, and after the authorization form process. So, you can save the user's choice, 11 | and even bypass the authorization process. Let's look at an example. 12 | 13 | Assuming we have a _Many to Many_ relation between clients, and users. An `OAuthEvent` contains 14 | a `ClientInterface` instance, a `UserInterface` instance (coming from the [Security Component](http://symfony.com/doc/current/book/security.html)), 15 | and a flag to determine whether the client has been accepted, or not. 16 | 17 | ### Registering the listener 18 | 19 | ``` yaml 20 | services: 21 | oauth_event_listener: 22 | class: Acme\DemoBundle\EventListener\OAuthEventListener 23 | tags: 24 | - { name: kernel.event_listener, event: fos_oauth_server.pre_authorization_process, method: onPreAuthorizationProcess } 25 | - { name: kernel.event_listener, event: fos_oauth_server.post_authorization_process, method: onPostAuthorizationProcess } 26 | ``` 27 | 28 | 29 | ### Next? 30 | 31 | You can build a panel for your users displaying this list. If they remove an entry from this list, 32 | then the authorization page will be displayed to the user like the first time. And, if the user 33 | accepts the client, then the system will save this client to the user's list once again. 34 | 35 | 36 | [Back to index](index.md) 37 | -------------------------------------------------------------------------------- /Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012 FriendsOfSymfony 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. 20 | -------------------------------------------------------------------------------- /Resources/translations/FOSOAuthServerBundle.de.yml: -------------------------------------------------------------------------------- 1 | lang: "de" 2 | title: "Authentifizierung" 3 | authorize: 4 | accept: "Erlauben" 5 | reject: "Verweigern" 6 | -------------------------------------------------------------------------------- /Resources/translations/FOSOAuthServerBundle.en.yml: -------------------------------------------------------------------------------- 1 | lang: "en" 2 | title: "authentication" 3 | authorize: 4 | accept: "Allow" 5 | reject: "Deny" 6 | -------------------------------------------------------------------------------- /Resources/translations/FOSOAuthServerBundle.fr.yml: -------------------------------------------------------------------------------- 1 | lang: "fr" 2 | title: "Authentification" 3 | authorize: 4 | accept: "Accepter" 5 | reject: "Refuser" 6 | -------------------------------------------------------------------------------- /Resources/translations/FOSOAuthServerBundle.sl.yml: -------------------------------------------------------------------------------- 1 | lang: "sl" 2 | title: "Avtentikacija" 3 | authorize: 4 | accept: "Dovoli" 5 | reject: "Ne dovoli" 6 | -------------------------------------------------------------------------------- /Resources/views/Authorize/authorize.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "@FOSOAuthServer/layout.html.twig" %} 2 | 3 | {% block fos_oauth_server_content %} 4 | {% include "@FOSOAuthServer/Authorize/authorize_content.html.twig" %} 5 | {% endblock fos_oauth_server_content %} 6 | -------------------------------------------------------------------------------- /Resources/views/Authorize/authorize_content.html.twig: -------------------------------------------------------------------------------- 1 | {{ form_start(form, {'method': 'POST', 'action': path('fos_oauth_server_authorize'), 'label_attr': {'class': 'fos_oauth_server_authorize'} }) }} 2 | 3 | 4 | 5 | {{ form_row(form.client_id) }} 6 | {{ form_row(form.response_type) }} 7 | {{ form_row(form.redirect_uri) }} 8 | {{ form_row(form.state) }} 9 | {{ form_row(form.scope) }} 10 | {{ form_rest(form) }} 11 | 12 | -------------------------------------------------------------------------------- /Resources/views/form.html.twig: -------------------------------------------------------------------------------- 1 | 2 | {%- block field_label -%} 3 | 4 | {%- endblock field_label -%} 5 | -------------------------------------------------------------------------------- /Resources/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title %}{{ 'title'|trans }}{% endblock %} 5 | 6 | 7 | 8 |
9 | {% block fos_oauth_server_content %} 10 | {% endblock fos_oauth_server_content %} 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /Security/Authenticator/OAuth2Passport.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Security\Authenticator; 15 | 16 | use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; 17 | use Symfony\Component\Security\Http\Authenticator\Passport\PassportTrait; 18 | 19 | class OAuth2Passport implements PassportInterface 20 | { 21 | use PassportTrait; 22 | } 23 | -------------------------------------------------------------------------------- /Security/Authenticator/Oauth2Authenticator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Security\Authenticator; 15 | 16 | use FOS\OAuthServerBundle\Model\AccessToken; 17 | use FOS\OAuthServerBundle\Security\Authenticator\Token\OAuthToken; 18 | use FOS\OAuthServerBundle\Security\Authenticator\Passport\Badge\AccessTokenBadge; 19 | use OAuth2\OAuth2; 20 | use OAuth2\OAuth2AuthenticateException; 21 | use OAuth2\OAuth2ServerException; 22 | use Symfony\Component\HttpFoundation\JsonResponse; 23 | use Symfony\Component\HttpFoundation\Request; 24 | use Symfony\Component\HttpFoundation\Response; 25 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 26 | use Symfony\Component\Security\Core\Exception\AccountStatusException; 27 | use Symfony\Component\Security\Core\Exception\AuthenticationException; 28 | use Symfony\Component\Security\Core\User\UserCheckerInterface; 29 | use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; 30 | use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; 31 | use Symfony\Component\Security\Http\Authenticator\Passport\Passport; 32 | use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; 33 | use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; 34 | 35 | class Oauth2Authenticator extends AbstractAuthenticator 36 | { 37 | /** 38 | * @var UserCheckerInterface 39 | */ 40 | protected $userChecker; 41 | 42 | /** 43 | * @var OAuth2 44 | */ 45 | protected $serverService; 46 | 47 | public function __construct(OAuth2 $serverService, UserCheckerInterface $userChecker) 48 | { 49 | $this->serverService = $serverService; 50 | $this->userChecker = $userChecker; 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | public function supports(Request $request): ?bool 57 | { 58 | return null !== $this->serverService->getBearerToken($request); 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | public function authenticate(Request $request): Passport 65 | { 66 | try { 67 | $tokenString = $this->serverService->getBearerToken($request); 68 | if (null === $tokenString) { 69 | throw new AuthenticationException('OAuth2 authentication failed: missing access token.'); 70 | } 71 | 72 | /** @var AccessToken $accessToken */ 73 | $accessToken = $this->serverService->verifyAccessToken($tokenString); 74 | 75 | $user = $accessToken->getUser(); 76 | $client = $accessToken->getClient(); 77 | 78 | if (null !== $user) { 79 | try { 80 | $this->userChecker->checkPreAuth($user); 81 | } catch (AccountStatusException $e) { 82 | throw new OAuth2AuthenticateException(Response::HTTP_UNAUTHORIZED, OAuth2::TOKEN_TYPE_BEARER, $this->serverService->getVariable(OAuth2::CONFIG_WWW_REALM), 'access_denied', $e->getMessage()); 83 | } 84 | } 85 | 86 | $roles = (null !== $user) ? $user->getRoles() : []; 87 | $scope = $accessToken->getScope(); 88 | 89 | if (!empty($scope)) { 90 | foreach (explode(' ', $scope) as $role) { 91 | $roles[] = 'ROLE_'.mb_strtoupper($role); 92 | } 93 | } 94 | 95 | $roles = array_unique($roles, SORT_REGULAR); 96 | 97 | $accessTokenBadge = new AccessTokenBadge($accessToken, $roles); 98 | 99 | return new SelfValidatingPassport(new UserBadge($client->getUserIdentifier()), [$accessTokenBadge]); 100 | } catch (OAuth2ServerException $e) { 101 | throw new AuthenticationException('OAuth2 authentication failed', 0, $e); 102 | } 103 | } 104 | 105 | public function createToken(Passport $passport, string $firewallName): TokenInterface 106 | { 107 | /** @var AccessTokenBadge $accessTokenBadge */ 108 | $accessTokenBadge = $passport->getBadge(AccessTokenBadge::class); 109 | $token = new OAuthToken($accessTokenBadge->getRoles()); 110 | $token->setToken($accessTokenBadge->getAccessToken()->getToken()); 111 | if (!empty($user = $accessTokenBadge->getAccessToken()->getUser())) { 112 | $token->setUser($user); 113 | } 114 | 115 | return $token; 116 | } 117 | 118 | /** 119 | * {@inheritDoc} 120 | */ 121 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response 122 | { 123 | return null; 124 | } 125 | 126 | /** 127 | * {@inheritDoc} 128 | */ 129 | public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response 130 | { 131 | $data = [ 132 | 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()), 133 | ]; 134 | 135 | return new JsonResponse($data, Response::HTTP_UNAUTHORIZED); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Security/Authenticator/Passport/Badge/AccessTokenBadge.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Security\Authenticator\Passport\Badge; 15 | 16 | use FOS\OAuthServerBundle\Model\AccessToken; 17 | use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; 18 | 19 | class AccessTokenBadge implements BadgeInterface 20 | { 21 | /** 22 | * AccessTokenBadge constructor. 23 | */ 24 | public function __construct( 25 | private AccessToken $accessToken, 26 | private array $roles) 27 | { 28 | } 29 | 30 | /** 31 | * {@inheritDoc} 32 | */ 33 | public function isResolved(): bool 34 | { 35 | return !empty($this->roles); 36 | } 37 | 38 | public function getAccessToken(): AccessToken 39 | { 40 | return $this->accessToken; 41 | } 42 | 43 | public function getRoles(): array 44 | { 45 | return $this->roles; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Security/Authenticator/Token/OAuthToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Security\Authenticator\Token; 15 | 16 | use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; 17 | 18 | /** 19 | * OAuthToken class. 20 | * 21 | * @author Arnaud Le Blanc 22 | */ 23 | class OAuthToken extends AbstractToken 24 | { 25 | protected string $token; 26 | 27 | public function setToken(string $token) 28 | { 29 | $this->token = $token; 30 | } 31 | 32 | public function getToken(): string 33 | { 34 | return $this->token; 35 | } 36 | 37 | public function getCredentials(): string 38 | { 39 | return $this->token; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Security/EntryPoint/OAuthEntryPoint.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Security\EntryPoint; 15 | 16 | use OAuth2\OAuth2; 17 | use OAuth2\OAuth2AuthenticateException; 18 | use Symfony\Component\HttpFoundation\Request; 19 | use Symfony\Component\HttpFoundation\Response; 20 | use Symfony\Component\Security\Core\Exception\AuthenticationException; 21 | use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; 22 | 23 | class OAuthEntryPoint implements AuthenticationEntryPointInterface 24 | { 25 | protected $serverService; 26 | 27 | public function __construct(OAuth2 $serverService) 28 | { 29 | $this->serverService = $serverService; 30 | } 31 | 32 | public function start(Request $request, ?AuthenticationException $authException = null): Response 33 | { 34 | $exception = new OAuth2AuthenticateException( 35 | Response::HTTP_UNAUTHORIZED, 36 | OAuth2::TOKEN_TYPE_BEARER, 37 | $this->serverService->getVariable(OAuth2::CONFIG_WWW_REALM), 38 | 'access_denied', 39 | 'OAuth2 authentication required' 40 | ); 41 | 42 | return $exception->getHttpResponse(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Storage/GrantExtensionDispatcherInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Storage; 15 | 16 | /** 17 | * @author Adrien Brault 18 | */ 19 | interface GrantExtensionDispatcherInterface 20 | { 21 | public function setGrantExtension($uri, GrantExtensionInterface $grantExtension); 22 | } 23 | -------------------------------------------------------------------------------- /Storage/GrantExtensionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Storage; 15 | 16 | use OAuth2\Model\IOAuth2Client; 17 | 18 | /** 19 | * @author Adrien Brault 20 | */ 21 | interface GrantExtensionInterface 22 | { 23 | /** 24 | * @see OAuth2\IOAuth2GrantExtension::checkGrantExtension 25 | */ 26 | public function checkGrantExtension(IOAuth2Client $client, array $inputData, array $authHeaders); 27 | } 28 | -------------------------------------------------------------------------------- /Tests/Command/CleanCommandTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Command; 15 | 16 | use FOS\OAuthServerBundle\Command\CleanCommand; 17 | use FOS\OAuthServerBundle\Model\AuthCodeManagerInterface; 18 | use FOS\OAuthServerBundle\Model\TokenManagerInterface; 19 | use PHPUnit\Framework\MockObject\MockObject; 20 | use PHPUnit\Framework\TestCase; 21 | use Symfony\Component\Console\Application; 22 | use Symfony\Component\Console\Tester\CommandTester; 23 | 24 | class CleanCommandTest extends TestCase 25 | { 26 | private CleanCommand $command; 27 | private TokenManagerInterface|MockObject $accessTokenManager; 28 | private TokenManagerInterface|MockObject $refreshTokenManager; 29 | private AuthCodeManagerInterface|MockObject $authCodeManager; 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | protected function setUp(): void 35 | { 36 | $this->accessTokenManager = $this->getMockBuilder(TokenManagerInterface::class)->disableOriginalConstructor()->getMock(); 37 | $this->refreshTokenManager = $this->getMockBuilder(TokenManagerInterface::class)->disableOriginalConstructor()->getMock(); 38 | $this->authCodeManager = $this->getMockBuilder(AuthCodeManagerInterface::class)->disableOriginalConstructor()->getMock(); 39 | 40 | $command = new CleanCommand($this->accessTokenManager, $this->refreshTokenManager, $this->authCodeManager); 41 | 42 | $application = new Application(); 43 | $application->add($command); 44 | 45 | /** @var CleanCommand $command */ 46 | $command = $application->find($command->getName()); 47 | 48 | $this->command = $command; 49 | } 50 | 51 | /** 52 | * Delete expired tokens for provided classes. 53 | */ 54 | public function testItShouldRemoveExpiredToken() 55 | { 56 | $expiredAccessTokens = 5; 57 | $this->accessTokenManager 58 | ->expects($this->once()) 59 | ->method('deleteExpired') 60 | ->willReturn($expiredAccessTokens) 61 | ; 62 | 63 | $expiredRefreshTokens = 183; 64 | $this->refreshTokenManager 65 | ->expects($this->once()) 66 | ->method('deleteExpired') 67 | ->willReturn($expiredRefreshTokens) 68 | ; 69 | 70 | $expiredAuthCodes = 0; 71 | $this->authCodeManager 72 | ->expects($this->once()) 73 | ->method('deleteExpired') 74 | ->willReturn($expiredAuthCodes) 75 | ; 76 | 77 | $tester = new CommandTester($this->command); 78 | $tester->execute(['command' => $this->command->getName()]); 79 | 80 | $display = $tester->getDisplay(); 81 | 82 | $this->assertStringContainsString(sprintf('Removed %d items from %s storage.', $expiredAccessTokens, get_class($this->accessTokenManager)), $display); 83 | $this->assertStringContainsString(sprintf('Removed %d items from %s storage.', $expiredRefreshTokens, get_class($this->refreshTokenManager)), $display); 84 | $this->assertStringContainsString(sprintf('Removed %d items from %s storage.', $expiredAuthCodes, get_class($this->authCodeManager)), $display); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/Command/CreateClientCommandTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Command; 15 | 16 | use FOS\OAuthServerBundle\Command\CreateClientCommand; 17 | use FOS\OAuthServerBundle\Model\ClientManagerInterface; 18 | use PHPUnit\Framework\Attributes\DataProvider; 19 | use PHPUnit\Framework\MockObject\MockObject; 20 | use PHPUnit\Framework\TestCase; 21 | use Symfony\Component\Console\Application; 22 | use Symfony\Component\Console\Tester\CommandTester; 23 | 24 | class CreateClientCommandTest extends TestCase 25 | { 26 | /** 27 | * @var CreateClientCommand 28 | */ 29 | private $command; 30 | 31 | /** 32 | * @var MockObject|ClientManagerInterface 33 | */ 34 | private $clientManager; 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | protected function setUp(): void 40 | { 41 | $this->clientManager = $this->getMockBuilder(ClientManagerInterface::class)->disableOriginalConstructor()->getMock(); 42 | $command = new CreateClientCommand($this->clientManager); 43 | 44 | $application = new Application(); 45 | $application->add($command); 46 | 47 | /** @var CreateClientCommand $command */ 48 | $command = $application->find($command->getName()); 49 | 50 | $this->command = $command; 51 | } 52 | 53 | #[DataProvider('clientProvider')] 54 | public function testItShouldCreateClient($client): void 55 | { 56 | $this 57 | ->clientManager 58 | ->expects($this->any()) 59 | ->method('createClient') 60 | ->willReturn(new $client()) 61 | ; 62 | 63 | $commandTester = new CommandTester($this->command); 64 | 65 | $commandTester->execute([ 66 | 'command' => $this->command->getName(), 67 | '--redirect-uri' => ['https://www.example.com/oauth2/callback'], 68 | '--grant-type' => [ 69 | 'authorization_code', 70 | 'password', 71 | 'refresh_token', 72 | 'token', 73 | 'client_credentials', 74 | ], 75 | ]); 76 | 77 | $this->assertSame(0, $commandTester->getStatusCode()); 78 | 79 | $output = $commandTester->getDisplay(); 80 | 81 | $this->assertStringContainsString('Client ID', $output); 82 | $this->assertStringContainsString('Client Secret', $output); 83 | } 84 | 85 | /** 86 | * @return array 87 | */ 88 | public static function clientProvider() 89 | { 90 | return [ 91 | ['FOS\OAuthServerBundle\Document\Client'], 92 | ['FOS\OAuthServerBundle\Entity\Client'], 93 | ['FOS\OAuthServerBundle\Model\Client'], 94 | ]; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Tests/Controller/TokenControllerTest.php: -------------------------------------------------------------------------------- 1 | serverMock = $this->createMock(OAuth2::class); 22 | $this->tokenController = new TokenController($this->serverMock); 23 | } 24 | 25 | public function testTokenActionReturnsAccessTokenResponse(): void 26 | { 27 | $request = $this->createMock(Request::class); 28 | $response = $this->createMock(Response::class); 29 | 30 | $this->serverMock 31 | ->expects($this->once()) 32 | ->method('grantAccessToken') 33 | ->with($request) 34 | ->willReturn($response); 35 | 36 | $result = $this->tokenController->tokenAction($request); 37 | 38 | $this->assertSame($response, $result); 39 | } 40 | 41 | public function testTokenActionHandlesOAuth2ServerException(): void 42 | { 43 | $request = $this->createMock(Request::class); 44 | $exceptionResponse = $this->createMock(Response::class); 45 | $exception = $this->createMock(OAuth2ServerException::class); 46 | 47 | $exception 48 | ->expects($this->once()) 49 | ->method('getHttpResponse') 50 | ->willReturn($exceptionResponse); 51 | 52 | $this->serverMock 53 | ->expects($this->once()) 54 | ->method('grantAccessToken') 55 | ->with($request) 56 | ->willThrowException($exception); 57 | 58 | $result = $this->tokenController->tokenAction($request); 59 | 60 | $this->assertSame($exceptionResponse, $result); 61 | } 62 | } -------------------------------------------------------------------------------- /Tests/DependencyInjection/Compiler/RequestStackCompilerPassTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\DependencyInjection\Compiler; 15 | 16 | use FOS\OAuthServerBundle\DependencyInjection\Compiler\RequestStackCompilerPass; 17 | use Symfony\Component\DependencyInjection\ContainerBuilder; 18 | use Symfony\Component\DependencyInjection\Definition; 19 | use Symfony\Component\DependencyInjection\Reference; 20 | 21 | /** 22 | * Class RequestStackCompilerPassTest. 23 | * 24 | * @author Nikola Petkanski 25 | */ 26 | class RequestStackCompilerPassTest extends \PHPUnit\Framework\TestCase 27 | { 28 | /** 29 | * @var RequestStackCompilerPass 30 | */ 31 | protected $instance; 32 | 33 | /** 34 | * @var \PHPUnit_Framework_MockObject_MockObject|ContainerBuilder 35 | */ 36 | protected $container; 37 | 38 | public function setUp(): void 39 | { 40 | $this->container = $this->getMockBuilder(ContainerBuilder::class) 41 | ->disableOriginalConstructor() 42 | ->onlyMethods([ 43 | 'has', 44 | 'getDefinition', 45 | ]) 46 | ->getMock() 47 | ; 48 | 49 | $this->instance = new RequestStackCompilerPass(); 50 | 51 | parent::setUp(); 52 | } 53 | 54 | public function testProcessWithoutRequestStackDoesNothing(): void 55 | { 56 | $this->container 57 | ->expects($this->once()) 58 | ->method('has') 59 | ->with('request_stack') 60 | ->willReturn(true) 61 | ; 62 | 63 | $this->assertNull($this->instance->process($this->container)); 64 | } 65 | 66 | public function testProcess(): void 67 | { 68 | $this->container 69 | ->expects($this->once()) 70 | ->method('has') 71 | ->with('request_stack') 72 | ->willReturn(false) 73 | ; 74 | 75 | $definition = $this->getMockBuilder(Definition::class) 76 | ->disableOriginalConstructor() 77 | ->getMock() 78 | ; 79 | 80 | $this->container 81 | ->expects($this->once()) 82 | ->method('getDefinition') 83 | ->with('fos_oauth_server.authorize.form.handler.default') 84 | ->willReturn($definition) 85 | ; 86 | 87 | $definition 88 | ->expects($this->once()) 89 | ->method('addMethodCall') 90 | ->with( 91 | 'setContainer', 92 | [ 93 | new Reference('service_container'), 94 | ] 95 | ) 96 | ->willReturnSelf() 97 | ; 98 | 99 | $this->assertNull($this->instance->process($this->container)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Tests/Document/ClientManagerTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Document; 15 | 16 | use Doctrine\ODM\MongoDB\DocumentManager; 17 | use Doctrine\ODM\MongoDB\Repository\DocumentRepository; 18 | use FOS\OAuthServerBundle\Document\Client; 19 | use FOS\OAuthServerBundle\Document\ClientManager; 20 | use FOS\OAuthServerBundle\Model\ClientInterface; 21 | use PHPUnit\Framework\MockObject\MockObject; 22 | use PHPUnit\Framework\TestCase; 23 | 24 | /** 25 | * Class ClientManagerTest. 26 | * 27 | * @author Nikola Petkanski 28 | */ 29 | class ClientManagerTest extends TestCase 30 | { 31 | protected MockObject|DocumentManager $documentManager; 32 | protected string $className; 33 | protected MockObject|DocumentRepository $repository; 34 | protected ClientManager $instance; 35 | 36 | public function setUp(): void 37 | { 38 | if (!class_exists('\Doctrine\ODM\MongoDB\DocumentManager')) { 39 | $this->markTestSkipped('Doctrine MongoDB ODM has to be installed for this test to run.'); 40 | } 41 | 42 | $this->documentManager = $this->getMockBuilder(DocumentManager::class) 43 | ->disableOriginalConstructor() 44 | ->getMock(); 45 | $this->repository = $this->getMockBuilder(DocumentRepository::class) 46 | ->disableOriginalConstructor() 47 | ->getMock(); 48 | $this->className = 'RandomClassName' . \random_bytes(5); 49 | 50 | $this->documentManager 51 | ->expects($this->once()) 52 | ->method('getRepository') 53 | ->with($this->className) 54 | ->willReturn($this->repository); 55 | 56 | $this->instance = new ClientManager($this->documentManager, $this->className); 57 | 58 | parent::setUp(); 59 | } 60 | 61 | public function testGetClass(): void 62 | { 63 | $this->assertSame($this->className, $this->instance->getClass()); 64 | } 65 | 66 | public function testFindClientBy(): void 67 | { 68 | $randomResult = new Client(); 69 | $criteria = [ 70 | \random_bytes(5), 71 | ]; 72 | 73 | $this->repository 74 | ->expects($this->once()) 75 | ->method('findOneBy') 76 | ->with($criteria) 77 | ->willReturn($randomResult); 78 | 79 | $this->assertSame($randomResult, $this->instance->findClientBy($criteria)); 80 | } 81 | 82 | public function testUpdateClient(): void 83 | { 84 | $client = $this->getMockBuilder(ClientInterface::class) 85 | ->disableOriginalConstructor() 86 | ->getMock(); 87 | 88 | $this->documentManager 89 | ->expects($this->once()) 90 | ->method('persist') 91 | ->with($client); 92 | 93 | $this->documentManager 94 | ->expects($this->once()) 95 | ->method('flush') 96 | ->with(); 97 | 98 | $this->instance->updateClient($client); 99 | } 100 | 101 | public function testDeleteClient(): void 102 | { 103 | $client = $this->getMockBuilder(ClientInterface::class) 104 | ->disableOriginalConstructor() 105 | ->getMock(); 106 | 107 | $this->documentManager 108 | ->expects($this->once()) 109 | ->method('remove') 110 | ->with($client); 111 | 112 | $this->documentManager 113 | ->expects($this->once()) 114 | ->method('flush') 115 | ->with(); 116 | 117 | $this->instance->deleteClient($client); 118 | } 119 | 120 | public function testCreateClient() 121 | { 122 | $dm = $this->getMockBuilder(DocumentManager::class) 123 | ->disableOriginalConstructor() 124 | ->getMock(); 125 | $clientManager = new ClientManager($dm, Client::class);; 126 | 127 | $this->assertInstanceOf(Client::class, $clientManager->createClient()); 128 | } 129 | 130 | public function testFindClientByPublicIdWithValidPublicId () 131 | { 132 | $this->repository 133 | ->expects($this->once()) 134 | ->method('findOneBy') 135 | ->with(['id' => 'phpunit', 'randomId' => 'random']) 136 | ->willReturn($expected = new Client()); 137 | 138 | $actual = $this->instance->findClientByPublicId('phpunit_random'); 139 | 140 | $this->assertSame($expected, $actual); 141 | } 142 | 143 | public function testFindClientByPublicIdWithInvalidPublicId () 144 | { 145 | $this->repository 146 | ->expects($this->never()) 147 | ->method('findOneBy'); 148 | 149 | $actual = $this->instance->findClientByPublicId('phpunit'); 150 | 151 | $this->assertNull($actual); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Tests/Document/TokenManagerTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Document; 15 | 16 | use Doctrine\ODM\MongoDB\DocumentManager; 17 | use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; 18 | use Doctrine\ODM\MongoDB\Query\Builder; 19 | use Doctrine\ODM\MongoDB\Query\Query; 20 | use Doctrine\ODM\MongoDB\Repository\DocumentRepository; 21 | use FOS\OAuthServerBundle\Document\AccessToken; 22 | use FOS\OAuthServerBundle\Document\TokenManager; 23 | use FOS\OAuthServerBundle\Model\Token; 24 | use MongoDB\Collection; 25 | use MongoDB\DeleteResult; 26 | use PHPUnit\Framework\Attributes\Group; 27 | use PHPUnit\Framework\MockObject\MockObject; 28 | use PHPUnit\Framework\TestCase; 29 | 30 | /** 31 | * Class TokenManagerTest 32 | * 33 | * @author Nikola Petkanski 34 | */ 35 | #[Group('time-sensitive')] 36 | class TokenManagerTest extends TestCase 37 | { 38 | protected string $className; 39 | protected MockObject|DocumentManager $documentManager; 40 | protected MockObject|DocumentRepository $repository; 41 | protected TokenManager $instance; 42 | 43 | public function setUp(): void 44 | { 45 | if (!class_exists('\Doctrine\ODM\MongoDB\DocumentManager')) { 46 | $this->markTestSkipped('Doctrine MongoDB ODM has to be installed for this test to run.'); 47 | } 48 | 49 | $this->className = AccessToken::class; 50 | $this->repository = $this->getMockBuilder(DocumentRepository::class) 51 | ->disableOriginalConstructor() 52 | ->getMock() 53 | ; 54 | $this->documentManager = $this->getMockBuilder(DocumentManager::class) 55 | ->disableOriginalConstructor() 56 | ->getMock() 57 | ; 58 | 59 | $this->documentManager 60 | ->expects($this->once()) 61 | ->method('getRepository') 62 | ->with($this->className) 63 | ->willReturn($this->repository) 64 | ; 65 | 66 | $this->instance = new TokenManager($this->documentManager, $this->className); 67 | } 68 | 69 | public function testFindTokenByToken(): void 70 | { 71 | $randomToken = \random_bytes(5); 72 | $randomResult = new Token(); 73 | 74 | $this->repository 75 | ->expects($this->once()) 76 | ->method('findOneBy') 77 | ->with([ 78 | 'token' => $randomToken, 79 | ]) 80 | ->willReturn($randomResult) 81 | ; 82 | 83 | $this->assertSame($randomResult, $this->instance->findTokenByToken($randomToken)); 84 | } 85 | 86 | public function testUpdateTokenPersistsAndFlushes(): void 87 | { 88 | $token = $this->getMockBuilder(AccessToken::class) 89 | ->disableOriginalConstructor() 90 | ->getMock() 91 | ; 92 | 93 | $this->documentManager 94 | ->expects($this->once()) 95 | ->method('persist') 96 | ->with($token) 97 | ; 98 | 99 | $this->documentManager 100 | ->expects($this->once()) 101 | ->method('flush') 102 | ->with() 103 | ; 104 | 105 | $this->instance->updateToken($token); 106 | } 107 | 108 | public function testGetClass(): void 109 | { 110 | $this->assertSame($this->className, $this->instance->getClass()); 111 | } 112 | 113 | public function testDeleteToken(): void 114 | { 115 | $token = $this->getMockBuilder(AccessToken::class) 116 | ->disableOriginalConstructor() 117 | ->getMock() 118 | ; 119 | 120 | $this->documentManager 121 | ->expects($this->once()) 122 | ->method('remove') 123 | ->with($token) 124 | ; 125 | 126 | $this->documentManager 127 | ->expects($this->once()) 128 | ->method('flush') 129 | ->with() 130 | ; 131 | 132 | $this->instance->deleteToken($token); 133 | } 134 | 135 | public function testDeleteExpired(): void 136 | { 137 | $queryBuilder = $this->getMockBuilder(Builder::class) 138 | ->disableOriginalConstructor() 139 | ->getMock() 140 | ; 141 | 142 | $this->repository 143 | ->expects($this->once()) 144 | ->method('createQueryBuilder') 145 | ->with() 146 | ->willReturn($queryBuilder) 147 | ; 148 | 149 | $queryBuilder 150 | ->expects($this->once()) 151 | ->method('remove') 152 | ->with() 153 | ->willReturn($queryBuilder) 154 | ; 155 | 156 | $queryBuilder 157 | ->expects($this->once()) 158 | ->method('field') 159 | ->with('expiresAt') 160 | ->willReturn($queryBuilder) 161 | ; 162 | 163 | $queryBuilder 164 | ->expects($this->once()) 165 | ->method('lt') 166 | ->with(time()) 167 | ->willReturn($queryBuilder) 168 | ; 169 | 170 | $data = [ 171 | 'n' => \random_int(0, 5), 172 | ]; 173 | 174 | $deleteResult = $this->getMockBuilder(DeleteResult::class) 175 | ->disableOriginalConstructor() 176 | ->getMock(); 177 | $deleteResult->expects(self::once()) 178 | ->method('getDeletedCount') 179 | ->willReturn($data['n']); 180 | 181 | $collection = $this->createMock(Collection::class); 182 | $collection->expects(self::once()) 183 | ->method('deleteMany') 184 | ->willReturn($deleteResult) 185 | ; 186 | 187 | $query = new Query( 188 | $this->documentManager, 189 | $this->createMock(ClassMetadata::class), 190 | $collection, 191 | [ 192 | 'type' => Query::TYPE_REMOVE, 193 | 'query' => [], 194 | ], 195 | [], 196 | false 197 | ); 198 | 199 | $queryBuilder 200 | ->expects($this->once()) 201 | ->method('getQuery') 202 | ->with([ 203 | 'safe' => true, 204 | ]) 205 | ->willReturn($query) 206 | ; 207 | 208 | $this->assertSame($data['n'], $this->instance->deleteExpired()); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Tests/Entity/AuthCodeManagerTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Entity; 15 | 16 | use Doctrine\ORM\AbstractQuery; 17 | use Doctrine\ORM\EntityManagerInterface; 18 | use Doctrine\ORM\EntityRepository; 19 | use Doctrine\ORM\QueryBuilder; 20 | use Doctrine\Persistence\ObjectRepository; 21 | use FOS\OAuthServerBundle\Document\AuthCode; 22 | use FOS\OAuthServerBundle\Entity\AuthCodeManager; 23 | use FOS\OAuthServerBundle\Model\AuthCodeInterface; 24 | use PHPUnit\Framework\Attributes\Group; 25 | use PHPUnit\Framework\MockObject\MockObject; 26 | use PHPUnit\Framework\TestCase; 27 | 28 | /** 29 | * Class AuthCodeManagerTest 30 | * 31 | * @author Nikola Petkanski 32 | */ 33 | #[Group('time-sensitive')] 34 | class AuthCodeManagerTest extends TestCase 35 | { 36 | protected MockObject|EntityManagerInterface $entityManager; 37 | protected string $className; 38 | protected AuthCodeManager $instance; 39 | 40 | public function setUp(): void 41 | { 42 | $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class) 43 | ->disableOriginalConstructor() 44 | ->getMock() 45 | ; 46 | $this->className = 'TestClassName'.\random_bytes(5); 47 | 48 | $this->instance = new AuthCodeManager($this->entityManager, $this->className); 49 | 50 | parent::setUp(); 51 | } 52 | 53 | public function testGetClassWillReturnClassName(): void 54 | { 55 | $this->assertSame($this->className, $this->instance->getClass()); 56 | } 57 | 58 | public function testFindAuthCodeBy(): void 59 | { 60 | $repository = $this->getMockBuilder(ObjectRepository::class) 61 | ->disableOriginalConstructor() 62 | ->getMock() 63 | ; 64 | 65 | $this->entityManager 66 | ->expects($this->once()) 67 | ->method('getRepository') 68 | ->with($this->className) 69 | ->willReturn($repository) 70 | ; 71 | 72 | $criteria = [ 73 | \random_bytes(10), 74 | ]; 75 | $randomResult = new AuthCode(); 76 | 77 | $repository 78 | ->expects($this->once()) 79 | ->method('findOneBy') 80 | ->with($criteria) 81 | ->willReturn($randomResult) 82 | ; 83 | 84 | $this->assertSame($randomResult, $this->instance->findAuthCodeBy($criteria)); 85 | } 86 | 87 | public function testUpdateAuthCode(): void 88 | { 89 | $authCode = $this->getMockBuilder(AuthCodeInterface::class) 90 | ->disableOriginalConstructor() 91 | ->getMock() 92 | ; 93 | 94 | $this->entityManager 95 | ->expects($this->once()) 96 | ->method('persist') 97 | ->with($authCode) 98 | ->willReturn(null) 99 | ; 100 | 101 | $this->entityManager 102 | ->expects($this->once()) 103 | ->method('flush') 104 | ->with() 105 | ->willReturn(null) 106 | ; 107 | 108 | $this->instance->updateAuthCode($authCode); 109 | } 110 | 111 | public function testDeleteAuthCode(): void 112 | { 113 | $authCode = $this->getMockBuilder(AuthCodeInterface::class) 114 | ->disableOriginalConstructor() 115 | ->getMock() 116 | ; 117 | 118 | $this->entityManager 119 | ->expects($this->once()) 120 | ->method('remove') 121 | ->with($authCode) 122 | ->willReturn(null) 123 | ; 124 | 125 | $this->entityManager 126 | ->expects($this->once()) 127 | ->method('flush') 128 | ->with() 129 | ->willReturn(null) 130 | ; 131 | 132 | $this->instance->deleteAuthCode($authCode); 133 | } 134 | 135 | public function testDeleteExpired(): void 136 | { 137 | $randomResult = \random_int(0, 10); 138 | 139 | $queryBuilder = $this->getMockBuilder(QueryBuilder::class) 140 | ->disableOriginalConstructor() 141 | ->getMock() 142 | ; 143 | 144 | $repository = $this->getMockBuilder(EntityRepository::class) 145 | ->disableOriginalConstructor() 146 | ->getMock() 147 | ; 148 | 149 | $this->entityManager 150 | ->expects($this->once()) 151 | ->method('getRepository') 152 | ->with($this->className) 153 | ->willReturn($repository) 154 | ; 155 | 156 | $repository 157 | ->expects($this->once()) 158 | ->method('createQueryBuilder') 159 | ->with('a') 160 | ->willReturn($queryBuilder) 161 | ; 162 | 163 | $queryBuilder 164 | ->expects($this->once()) 165 | ->method('delete') 166 | ->with() 167 | ->willReturn($queryBuilder) 168 | ; 169 | 170 | $queryBuilder 171 | ->expects($this->once()) 172 | ->method('where') 173 | ->with('a.expiresAt < :time') 174 | ->willReturn($queryBuilder) 175 | ; 176 | 177 | $queryBuilder 178 | ->expects($this->once()) 179 | ->method('setParameter') 180 | ->with('time', time()) 181 | ->willReturn($queryBuilder) 182 | ; 183 | 184 | $query = $this->getMockBuilder(AbstractQuery::class) 185 | ->disableOriginalConstructor() 186 | ->getMock() 187 | ; 188 | 189 | $queryBuilder 190 | ->expects($this->once()) 191 | ->method('getQuery') 192 | ->with() 193 | ->willReturn($query) 194 | ; 195 | 196 | $query 197 | ->expects($this->once()) 198 | ->method('execute') 199 | ->with() 200 | ->willReturn($randomResult) 201 | ; 202 | 203 | $this->assertSame($randomResult, $this->instance->deleteExpired()); 204 | } 205 | } -------------------------------------------------------------------------------- /Tests/Entity/ClientManagerTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Entity; 15 | 16 | use Doctrine\ORM\EntityManagerInterface; 17 | use Doctrine\ORM\EntityRepository; 18 | use FOS\OAuthServerBundle\Document\Client; 19 | use FOS\OAuthServerBundle\Entity\ClientManager; 20 | use FOS\OAuthServerBundle\Model\ClientInterface; 21 | use PHPUnit\Framework\MockObject\MockObject; 22 | use PHPUnit\Framework\TestCase; 23 | 24 | /** 25 | * Class ClientManagerTest. 26 | * 27 | * @author Nikola Petkanski 28 | */ 29 | class ClientManagerTest extends TestCase 30 | { 31 | protected MockObject|EntityManagerInterface $entityManager; 32 | protected string $className; 33 | protected MockObject|EntityRepository $repository; 34 | protected ClientManager $instance; 35 | 36 | public function setUp(): void 37 | { 38 | $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class) 39 | ->disableOriginalConstructor() 40 | ->getMock() 41 | ; 42 | $this->repository = $this->getMockBuilder(EntityRepository::class) 43 | ->disableOriginalConstructor() 44 | ->getMock() 45 | ; 46 | $this->className = 'RandomClassName'.\random_bytes(5); 47 | 48 | $this->entityManager 49 | ->expects($this->once()) 50 | ->method('getRepository') 51 | ->with($this->className) 52 | ->willReturn($this->repository) 53 | ; 54 | 55 | $this->instance = new ClientManager($this->entityManager, $this->className); 56 | 57 | parent::setUp(); 58 | } 59 | 60 | public function testGetClass(): void 61 | { 62 | $this->assertSame($this->className, $this->instance->getClass()); 63 | } 64 | 65 | public function testFindClientBy(): void 66 | { 67 | $criteria = [ 68 | \random_bytes(5), 69 | ]; 70 | $randomResult = new Client(); 71 | 72 | $this->repository 73 | ->expects($this->once()) 74 | ->method('findOneBy') 75 | ->with($criteria) 76 | ->willReturn($randomResult) 77 | ; 78 | 79 | $this->assertSame($randomResult, $this->instance->findClientBy($criteria)); 80 | } 81 | 82 | public function testUpdateClient(): void 83 | { 84 | $client = $this->getMockBuilder(ClientInterface::class) 85 | ->disableOriginalConstructor() 86 | ->getMock() 87 | ; 88 | 89 | $this->entityManager 90 | ->expects($this->once()) 91 | ->method('persist') 92 | ->with($client) 93 | ->willReturn(null) 94 | ; 95 | 96 | $this->entityManager 97 | ->expects($this->once()) 98 | ->method('flush') 99 | ->with() 100 | ->willReturn(null) 101 | ; 102 | 103 | $this->assertNull($this->instance->updateClient($client)); 104 | } 105 | 106 | public function testDeleteClient(): void 107 | { 108 | $client = $this->getMockBuilder(ClientInterface::class) 109 | ->disableOriginalConstructor() 110 | ->getMock() 111 | ; 112 | 113 | $this->entityManager 114 | ->expects($this->once()) 115 | ->method('remove') 116 | ->with($client) 117 | ->willReturn(null) 118 | ; 119 | 120 | $this->entityManager 121 | ->expects($this->once()) 122 | ->method('flush') 123 | ->with() 124 | ->willReturn(null) 125 | ; 126 | 127 | $this->assertNull($this->instance->deleteClient($client)); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Tests/Entity/TokenManagerTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Entity; 15 | 16 | use Doctrine\ORM\AbstractQuery; 17 | use Doctrine\ORM\EntityManager; 18 | use Doctrine\ORM\EntityManagerInterface; 19 | use Doctrine\ORM\EntityRepository; 20 | use Doctrine\ORM\QueryBuilder; 21 | use FOS\OAuthServerBundle\Entity\AccessToken; 22 | use FOS\OAuthServerBundle\Entity\TokenManager; 23 | use FOS\OAuthServerBundle\Model\Token; 24 | use FOS\OAuthServerBundle\Model\TokenInterface; 25 | use PHPUnit\Framework\Attributes\Group; 26 | use PHPUnit\Framework\MockObject\MockObject; 27 | use PHPUnit\Framework\TestCase; 28 | 29 | /** 30 | * Class TokenManagerTest 31 | * 32 | * @author Nikola Petkanski 33 | */ 34 | #[Group('time-sensitive')] 35 | class TokenManagerTest extends TestCase 36 | { 37 | protected MockObject|EntityManagerInterface $entityManager; 38 | protected MockObject|EntityRepository $repository; 39 | protected string $className; 40 | protected TokenManager $instance; 41 | 42 | public function setUp(): void 43 | { 44 | $this->className = AccessToken::class; 45 | $this->repository = $this->getMockBuilder(EntityRepository::class) 46 | ->disableOriginalConstructor() 47 | ->getMock() 48 | ; 49 | $this->entityManager = $this->getMockBuilder(EntityManager::class) 50 | ->disableOriginalConstructor() 51 | ->getMock() 52 | ; 53 | 54 | $this->entityManager 55 | ->expects($this->once()) 56 | ->method('getRepository') 57 | ->with($this->className) 58 | ->willReturn($this->repository) 59 | ; 60 | 61 | $this->instance = new TokenManager($this->entityManager, $this->className); 62 | } 63 | 64 | public function testUpdateTokenPersistsAndFlushes(): void 65 | { 66 | $token = new AccessToken(); 67 | 68 | $this->entityManager 69 | ->expects($this->once()) 70 | ->method('persist') 71 | ->with($token) 72 | ; 73 | 74 | $this->entityManager 75 | ->expects($this->once()) 76 | ->method('flush') 77 | ->with() 78 | ; 79 | 80 | $this->instance->updateToken($token); 81 | } 82 | 83 | public function testGetClass(): void 84 | { 85 | $this->assertSame($this->className, $this->instance->getClass()); 86 | } 87 | 88 | public function testFindTokenBy(): void 89 | { 90 | $randomResult = new Token(); 91 | 92 | $criteria = [ 93 | \random_bytes(5), 94 | ]; 95 | 96 | $this->repository 97 | ->expects($this->once()) 98 | ->method('findOneBy') 99 | ->with($criteria) 100 | ->willReturn($randomResult) 101 | ; 102 | 103 | $this->assertSame($randomResult, $this->instance->findTokenBy($criteria)); 104 | } 105 | 106 | public function testUpdateToken(): void 107 | { 108 | $token = $this->getMockBuilder(TokenInterface::class) 109 | ->disableOriginalConstructor() 110 | ->getMock() 111 | ; 112 | 113 | $this->entityManager 114 | ->expects($this->once()) 115 | ->method('persist') 116 | ->with($token) 117 | ->willReturn(null) 118 | ; 119 | 120 | $this->entityManager 121 | ->expects($this->once()) 122 | ->method('flush') 123 | ->with() 124 | ->willReturn(null) 125 | ; 126 | 127 | $this->instance->updateToken($token); 128 | } 129 | 130 | public function testDeleteToken(): void 131 | { 132 | $token = $this->getMockBuilder(TokenInterface::class) 133 | ->disableOriginalConstructor() 134 | ->getMock() 135 | ; 136 | 137 | $this->entityManager 138 | ->expects($this->once()) 139 | ->method('remove') 140 | ->with($token) 141 | ; 142 | 143 | $this->entityManager 144 | ->expects($this->once()) 145 | ->method('flush') 146 | ->with() 147 | ; 148 | 149 | $this->instance->deleteToken($token); 150 | } 151 | 152 | public function testDeleteExpired(): void 153 | { 154 | $randomResult = \random_int(0,10); 155 | 156 | $queryBuilder = $this->getMockBuilder(QueryBuilder::class) 157 | ->disableOriginalConstructor() 158 | ->getMock() 159 | ; 160 | 161 | $this->repository 162 | ->expects($this->once()) 163 | ->method('createQueryBuilder') 164 | ->with('t') 165 | ->willReturn($queryBuilder) 166 | ; 167 | 168 | $queryBuilder 169 | ->expects($this->once()) 170 | ->method('delete') 171 | ->with() 172 | ->willReturn($queryBuilder) 173 | ; 174 | 175 | $queryBuilder 176 | ->expects($this->once()) 177 | ->method('where') 178 | ->with('t.expiresAt < :time') 179 | ->willReturn($queryBuilder) 180 | ; 181 | 182 | $queryBuilder 183 | ->expects($this->once()) 184 | ->method('setParameter') 185 | ->with('time',time()) 186 | ->willReturn($queryBuilder) 187 | ; 188 | 189 | $query = $this->getMockBuilder(AbstractQuery::class) 190 | ->disableOriginalConstructor() 191 | ->getMock() 192 | ; 193 | 194 | $queryBuilder 195 | ->expects($this->once()) 196 | ->method('getQuery') 197 | ->with() 198 | ->willReturn($query) 199 | ; 200 | 201 | $query 202 | ->expects($this->once()) 203 | ->method('execute') 204 | ->with() 205 | ->willReturn($randomResult) 206 | ; 207 | 208 | $this->assertSame($randomResult, $this->instance->deleteExpired()); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Tests/Event/OAuthEventTest.php: -------------------------------------------------------------------------------- 1 | createMock(UserInterface::class); 19 | $clientMock = $this->createMock(ClientInterface::class); 20 | $event = new OAuthEvent($userMock, $clientMock); 21 | 22 | // Act 23 | $result = $event->getUser(); 24 | 25 | // Assert 26 | $this->assertSame($userMock, $result); 27 | } 28 | 29 | /** 30 | * Test that setAuthorizedClient properly updates the isAuthorizedClient property to true. 31 | */ 32 | public function testSetAuthorizedClientUpdatesToTrue(): void 33 | { 34 | // Arrange 35 | $userMock = $this->createMock(UserInterface::class); 36 | $clientMock = $this->createMock(ClientInterface::class); 37 | $event = new OAuthEvent($userMock, $clientMock); 38 | 39 | // Act 40 | $event->setAuthorizedClient(true); 41 | 42 | // Assert 43 | $this->assertTrue($event->isAuthorizedClient()); 44 | } 45 | 46 | /** 47 | * Test that setAuthorizedClient properly updates the isAuthorizedClient property to false. 48 | */ 49 | public function testSetAuthorizedClientUpdatesToFalse(): void 50 | { 51 | // Arrange 52 | $userMock = $this->createMock(UserInterface::class); 53 | $clientMock = $this->createMock(ClientInterface::class); 54 | $event = new OAuthEvent($userMock, $clientMock); 55 | 56 | // Act 57 | $event->setAuthorizedClient(false); 58 | 59 | // Assert 60 | $this->assertFalse($event->isAuthorizedClient()); 61 | } 62 | 63 | /** 64 | * Test that getClient returns the correct ClientInterface instance. 65 | */ 66 | public function testGetClientReturnsCorrectClientInstance(): void 67 | { 68 | // Arrange 69 | $userMock = $this->createMock(UserInterface::class); 70 | $clientMock = $this->createMock(ClientInterface::class); 71 | $event = new OAuthEvent($userMock, $clientMock); 72 | 73 | // Act 74 | $result = $event->getClient(); 75 | 76 | // Assert 77 | $this->assertSame($clientMock, $result); 78 | } 79 | } -------------------------------------------------------------------------------- /Tests/FOSOAuthServerBundleTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests; 15 | 16 | use FOS\OAuthServerBundle\DependencyInjection\Compiler; 17 | use FOS\OAuthServerBundle\DependencyInjection\Security\Factory\OAuthFactory; 18 | use FOS\OAuthServerBundle\FOSOAuthServerBundle; 19 | use PHPUnit\Framework\MockObject\MockObject; 20 | use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension; 21 | use Symfony\Component\DependencyInjection\ContainerBuilder; 22 | 23 | class FOSOAuthServerBundleTest extends \PHPUnit\Framework\TestCase 24 | { 25 | protected function setUp(): void 26 | { 27 | parent::setUp(); 28 | } 29 | 30 | public function testConstruction(): void 31 | { 32 | $bundle = new FOSOAuthServerBundle(); 33 | 34 | /** @var ContainerBuilder|MockObject $containerBuilder */ 35 | $containerBuilder = $this->getMockBuilder(ContainerBuilder::class) 36 | ->disableOriginalConstructor() 37 | ->onlyMethods([ 38 | 'getExtension', 39 | 'addCompilerPass', 40 | ]) 41 | ->getMock() 42 | ; 43 | 44 | /** @var SecurityExtension|MockObject $securityExtension */ 45 | $securityExtension = $this->getMockBuilder(SecurityExtension::class) 46 | ->disableOriginalConstructor() 47 | ->getMock() 48 | ; 49 | 50 | $containerBuilder 51 | ->expects($this->any()) 52 | ->method('getExtension') 53 | ->with('security') 54 | ->willReturn($securityExtension) 55 | ; 56 | 57 | $securityExtension 58 | ->expects($this->any()) 59 | ->method('addAuthenticatorFactory') 60 | ->with(new OAuthFactory()) 61 | ; 62 | 63 | $invocations = [ 64 | new Compiler\GrantExtensionsCompilerPass(), 65 | new Compiler\RequestStackCompilerPass() 66 | ]; 67 | $matcher = $this->exactly(count($invocations)); 68 | $containerBuilder 69 | ->expects($matcher) 70 | ->method('addCompilerPass') 71 | ->with($this->callback(function ($param) use (&$invocations, $matcher) { 72 | $this->assertEquals( $param, $invocations[$matcher->numberOfInvocations()-1]); 73 | return true; 74 | })) 75 | ->willReturnOnConsecutiveCalls( 76 | $containerBuilder, 77 | $containerBuilder 78 | ) 79 | ; 80 | 81 | $bundle->build($containerBuilder); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Tests/Form/Type/AuthorizeFormTypeTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Form\Type; 15 | 16 | use FOS\OAuthServerBundle\Form\Model\Authorize; 17 | use FOS\OAuthServerBundle\Form\Type\AuthorizeFormType; 18 | use FOS\OAuthServerBundle\Util\LegacyFormHelper; 19 | use Symfony\Component\Form\FormBuilder; 20 | use Symfony\Component\Form\Forms; 21 | use Symfony\Component\Form\Test\TypeTestCase; 22 | use Symfony\Component\OptionsResolver\OptionsResolver; 23 | 24 | class AuthorizeFormTypeTest extends TypeTestCase 25 | { 26 | protected AuthorizeFormType $instance; 27 | 28 | protected function setUp(): void 29 | { 30 | parent::setUp(); 31 | 32 | $this->factory = Forms::createFormFactoryBuilder() 33 | ->addTypes($this->getTypes()) 34 | ->getFormFactory() 35 | ; 36 | 37 | $this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory); 38 | 39 | $this->instance = new AuthorizeFormType(); 40 | } 41 | 42 | public function testSubmit(): void 43 | { 44 | $accepted = true; 45 | $formData = [ 46 | 'client_id' => '1', 47 | 'response_type' => 'code', 48 | 'redirect_uri' => 'http:\\localhost\test.php', 49 | 'state' => 'testState', 50 | 'scope' => 'testScope', 51 | ]; 52 | 53 | $authorize = new Authorize($accepted, $formData); 54 | 55 | $form = $this->factory->create(LegacyFormHelper::getType('FOS\OAuthServerBundle\Form\Type\AuthorizeFormType'), $authorize); 56 | 57 | $form->submit($formData); 58 | 59 | $this->assertTrue($form->isSynchronized()); 60 | $this->assertSame($authorize, $form->getData()); 61 | $this->assertSame($accepted, $authorize->accepted); 62 | 63 | $view = $form->createView(); 64 | $children = $view->children; 65 | 66 | foreach (array_keys($formData) as $key) { 67 | $this->assertArrayHasKey($key, $children); 68 | } 69 | } 70 | 71 | public function testConfigureOptionsWillSetDefaultsOnTheOptionsResolver(): void 72 | { 73 | /** @var \PHPUnit_Framework_MockObject_MockObject|OptionsResolver $resolver */ 74 | $resolver = $this->getMockBuilder(OptionsResolver::class) 75 | ->disableOriginalConstructor() 76 | ->getMock() 77 | ; 78 | 79 | $resolver 80 | ->expects($this->once()) 81 | ->method('setDefaults') 82 | ->with([ 83 | 'data_class' => 'FOS\OAuthServerBundle\Form\Model\Authorize', 84 | ]) 85 | ->willReturn($resolver) 86 | ; 87 | 88 | $this->assertNull($this->instance->configureOptions($resolver)); 89 | } 90 | 91 | public function testGetName(): void 92 | { 93 | $this->assertSame('fos_oauth_server_authorize', $this->instance->getName()); 94 | } 95 | 96 | public function testGetBlockPrefix(): void 97 | { 98 | $this->assertSame('fos_oauth_server_authorize', $this->instance->getBlockPrefix()); 99 | } 100 | 101 | protected function getTypes() 102 | { 103 | return [ 104 | new AuthorizeFormType(), 105 | ]; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Tests/Functional/AppKernel.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Functional; 15 | 16 | use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; 17 | use FOS\OAuthServerBundle\FOSOAuthServerBundle; 18 | use FOS\OAuthServerBundle\Tests\Functional\TestBundle\TestBundle; 19 | use Symfony\Bundle\FrameworkBundle\FrameworkBundle; 20 | use Symfony\Bundle\SecurityBundle\SecurityBundle; 21 | use Symfony\Bundle\TwigBundle\TwigBundle; 22 | use Symfony\Component\Config\Loader\LoaderInterface; 23 | use Symfony\Component\HttpKernel\Kernel; 24 | 25 | class AppKernel extends Kernel 26 | { 27 | public function registerBundles(): array 28 | { 29 | $bundles = [ 30 | new FrameworkBundle(), 31 | new SecurityBundle(), 32 | new TwigBundle(), 33 | new FOSOAuthServerBundle(), 34 | 35 | new TestBundle(), 36 | ]; 37 | 38 | if ('orm' === $this->getEnvironment()) { 39 | $bundles[] = new DoctrineBundle(); 40 | } 41 | 42 | return $bundles; 43 | } 44 | 45 | public function getCacheDir(): string 46 | { 47 | return sys_get_temp_dir().'/FOSOAuthServerBundle/'; 48 | } 49 | 50 | public function registerContainerConfiguration(LoaderInterface $loader): void 51 | { 52 | $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/Functional/BootTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Functional; 15 | 16 | use PHPUnit\Framework\Attributes\DataProvider; 17 | use PHPUnit\Framework\Attributes\RunInSeparateProcess; 18 | 19 | class BootTest extends TestCase 20 | { 21 | 22 | #[DataProvider('getTestBootData')] 23 | #[RunInSeparateProcess] 24 | public function testBoot($env): void 25 | { 26 | try { 27 | $kernel = static::createKernel(['env' => $env]); 28 | $kernel->boot(); 29 | 30 | // no exceptions were thrown 31 | self::assertTrue(true); 32 | } catch (\Exception $exception) { 33 | $this->fail($exception->getMessage()); 34 | } 35 | } 36 | 37 | public static function getTestBootData(): array 38 | { 39 | return [ 40 | ['orm'], 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/Entity/AccessToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity; 15 | 16 | use Doctrine\ORM\Mapping as ORM; 17 | use FOS\OAuthServerBundle\Entity\AccessToken as BaseAccessToken; 18 | use FOS\OAuthServerBundle\Model\ClientInterface; 19 | use Symfony\Component\Security\Core\User\UserInterface; 20 | 21 | /** 22 | * @ORM\Entity 23 | * @ORM\Table(name="access_tokens") 24 | */ 25 | class AccessToken extends BaseAccessToken 26 | { 27 | /** 28 | * @ORM\Id 29 | * @ORM\Column(type="integer") 30 | * @ORM\GeneratedValue(strategy="AUTO") 31 | */ 32 | protected $id; 33 | 34 | /** 35 | * @ORM\ManyToOne(targetEntity="Client") 36 | * @ORM\JoinColumn(nullable=false) 37 | */ 38 | protected ClientInterface $client; 39 | 40 | /** 41 | * @ORM\ManyToOne(targetEntity="User") 42 | */ 43 | protected ?UserInterface $user; 44 | } 45 | -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/Entity/AuthCode.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity; 15 | 16 | use Doctrine\ORM\Mapping as ORM; 17 | use FOS\OAuthServerBundle\Entity\AuthCode as BaseAuthCode; 18 | use FOS\OAuthServerBundle\Model\ClientInterface; 19 | use Symfony\Component\Security\Core\User\UserInterface; 20 | 21 | /** 22 | * @ORM\Entity 23 | * @ORM\Table(name="auth_codes") 24 | */ 25 | class AuthCode extends BaseAuthCode 26 | { 27 | /** 28 | * @ORM\Id 29 | * @ORM\Column(type="integer") 30 | * @ORM\GeneratedValue(strategy="AUTO") 31 | */ 32 | protected $id; 33 | 34 | /** 35 | * @ORM\ManyToOne(targetEntity="Client") 36 | * @ORM\JoinColumn(nullable=false) 37 | */ 38 | protected ClientInterface $client; 39 | 40 | /** 41 | * @ORM\ManyToOne(targetEntity="User") 42 | */ 43 | protected ?UserInterface $user; 44 | } 45 | -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/Entity/Client.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity; 15 | 16 | use Doctrine\ORM\Mapping as ORM; 17 | use FOS\OAuthServerBundle\Entity\Client as BaseClient; 18 | 19 | /** 20 | * @ORM\Entity 21 | * @ORM\Table(name="clients") 22 | */ 23 | class Client extends BaseClient 24 | { 25 | /** 26 | * @ORM\Id 27 | * @ORM\Column(type="integer") 28 | * @ORM\GeneratedValue(strategy="AUTO") 29 | */ 30 | protected $id; 31 | } 32 | -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/Entity/RefreshToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity; 15 | 16 | use Doctrine\ORM\Mapping as ORM; 17 | use FOS\OAuthServerBundle\Entity\RefreshToken as BaseRefreshToken; 18 | use FOS\OAuthServerBundle\Model\ClientInterface; 19 | use Symfony\Component\Security\Core\User\UserInterface; 20 | 21 | /** 22 | * @ORM\Entity 23 | * @ORM\Table(name="refresh_tokens") 24 | */ 25 | class RefreshToken extends BaseRefreshToken 26 | { 27 | /** 28 | * @ORM\Id 29 | * @ORM\Column(type="integer") 30 | * @ORM\GeneratedValue(strategy="AUTO") 31 | */ 32 | protected $id; 33 | 34 | /** 35 | * @ORM\ManyToOne(targetEntity="Client") 36 | * @ORM\JoinColumn(nullable=false) 37 | */ 38 | protected ClientInterface $client; 39 | 40 | /** 41 | * @ORM\ManyToOne(targetEntity="User") 42 | */ 43 | protected ?UserInterface $user; 44 | } 45 | -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/Entity/User.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity; 15 | 16 | use Doctrine\ORM\Mapping as ORM; 17 | use Symfony\Component\Security\Core\User\UserInterface; 18 | 19 | /** 20 | * @ORM\Entity 21 | */ 22 | class User implements UserInterface 23 | { 24 | /** 25 | * @var int 26 | * 27 | * @ORM\Id 28 | * @ORM\Column(type="integer") 29 | * @ORM\GeneratedValue(strategy="AUTO") 30 | */ 31 | protected int $id; 32 | 33 | /** 34 | * @var string 35 | * 36 | * @ORM\Column(type="string") 37 | */ 38 | protected string $password; 39 | 40 | public function getId(): int 41 | { 42 | return $this->id; 43 | } 44 | 45 | public function getRoles(): array 46 | { 47 | return ['ROLE_USER']; 48 | } 49 | 50 | public function getPassword(): string 51 | { 52 | return $this->password; 53 | } 54 | 55 | public function setPassword($password): void 56 | { 57 | $this->password = $password; 58 | } 59 | 60 | public function getSalt(): ?string 61 | { 62 | return null; 63 | } 64 | 65 | public function getUsername(): string 66 | { 67 | return (string) $this->getId(); 68 | } 69 | 70 | public function getUserIdentifier(): string 71 | { 72 | return (string) $this->getId(); 73 | } 74 | 75 | public function eraseCredentials(): void 76 | { 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/TestBundle.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle; 15 | 16 | use Symfony\Component\HttpKernel\Bundle\Bundle; 17 | 18 | class TestBundle extends Bundle 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Tests/Functional/TestCase.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Functional; 15 | 16 | use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 17 | use Symfony\Component\Filesystem\Filesystem; 18 | use Symfony\Component\HttpKernel\KernelInterface; 19 | 20 | abstract class TestCase extends WebTestCase 21 | { 22 | /** 23 | * @var KernelInterface|null 24 | */ 25 | protected static ?KernelInterface $kernel; 26 | 27 | protected function setUp(): void 28 | { 29 | $fs = new Filesystem(); 30 | $fs->remove(sys_get_temp_dir().'/FOSOAuthServerBundle/'); 31 | } 32 | 33 | protected function tearDown(): void 34 | { 35 | static::$kernel = null; 36 | } 37 | 38 | protected static function createKernel(array $options = []): KernelInterface 39 | { 40 | $env = @$options['env'] ?: 'test'; 41 | 42 | return new AppKernel($env, true); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/Functional/config/config.yml: -------------------------------------------------------------------------------- 1 | framework: 2 | form: ~ 3 | secret: test 4 | router: 5 | resource: "%kernel.project_dir%/Tests/Functional/config/routing.yml" 6 | 7 | fos_oauth_server: 8 | 9 | security: 10 | role_hierarchy: 11 | ROLE_ADMIN: ROLE_USER 12 | ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] 13 | 14 | firewalls: 15 | oauth_token: 16 | pattern: ^/oauth/v2/token 17 | security: false 18 | 19 | oauth_authorize: 20 | pattern: ^/oauth/v2/auth 21 | security: false 22 | 23 | secured: 24 | pattern: ^/ 25 | fos_oauth: true 26 | stateless: true 27 | -------------------------------------------------------------------------------- /Tests/Functional/config/config_orm.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: config.yml } 3 | 4 | doctrine: 5 | dbal: 6 | driver: pdo_sqlite 7 | path: '%kernel.cache_dir%/data.sqlite' 8 | orm: 9 | entity_managers: 10 | default: 11 | mappings: 12 | TestBundle: ~ 13 | 14 | fos_oauth_server: 15 | db_driver: orm 16 | service: 17 | user_provider: security.user.provider.concrete.main 18 | 19 | client_class: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\Client 20 | access_token_class: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\AccessToken 21 | refresh_token_class: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\RefreshToken 22 | auth_code_class: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\AuthCode 23 | 24 | security: 25 | password_hashers: 26 | FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\User: plaintext 27 | 28 | providers: 29 | main: 30 | entity: { class: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\User, property: id } -------------------------------------------------------------------------------- /Tests/Functional/config/routing.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klapaudius/FOSOAuthServerBundle/db7fc3d80c929a505cebdc5729e66ce6de356364/Tests/Functional/config/routing.yml -------------------------------------------------------------------------------- /Tests/Model/ClientTest.php: -------------------------------------------------------------------------------- 1 | assertSame([], $client->getRedirectUris()); 19 | } 20 | 21 | /** 22 | * Test that getRedirectUris returns the same array that was set. 23 | */ 24 | public function testGetRedirectUrisReturnsSetArray(): void 25 | { 26 | $client = new Client(); 27 | $redirectUris = ['https://example.com/callback', 'https://another-example.com/callback']; 28 | 29 | $client->setRedirectUris($redirectUris); 30 | 31 | $this->assertSame($redirectUris, $client->getRedirectUris()); 32 | } 33 | 34 | /** 35 | * Test that getRedirectUris handles an empty array properly when set. 36 | */ 37 | public function testGetRedirectUrisHandlesEmptyArray(): void 38 | { 39 | $client = new Client(); 40 | $redirectUris = []; 41 | 42 | $client->setRedirectUris($redirectUris); 43 | 44 | $this->assertSame($redirectUris, $client->getRedirectUris()); 45 | } 46 | 47 | /** 48 | * Test that getSalt returns null. 49 | */ 50 | public function testGetSaltReturnsNull(): void 51 | { 52 | $client = new Client(); 53 | 54 | $this->assertNull($client->getSalt()); 55 | } 56 | 57 | /** 58 | * Test that getRoles returns the default role 'ROLE_USER'. 59 | */ 60 | public function testGetRolesReturnsDefaultRole(): void 61 | { 62 | $client = new Client(); 63 | $this->assertSame(['ROLE_USER'], $client->getRoles()); 64 | } 65 | 66 | /** 67 | * Test that getPassword returns the set secret. 68 | */ 69 | public function testGetPasswordReturnsExpectedValue(): void 70 | { 71 | $client = new Client(); 72 | $secret = 'my_secret_value'; 73 | 74 | $client->setSecret($secret); 75 | 76 | $this->assertSame($secret, $client->getPassword()); 77 | } 78 | 79 | /** 80 | * Test that getPassword returns null if no secret is set. 81 | */ 82 | public function testGetPasswordReturnsNullWithoutSecret(): void 83 | { 84 | $client = new Client(); 85 | 86 | $client->setSecret(null); 87 | 88 | $this->expectException(\LogicException::class); 89 | $this->expectExceptionMessage('The client has no secret.'); 90 | 91 | $this->assertNull($client->getPassword()); 92 | } 93 | 94 | /** 95 | * Test that getUsername returns the expected randomId value. 96 | */ 97 | public function testGetUsernameReturnsExpectedValue(): void 98 | { 99 | $client = new Client(); 100 | $randomId = 'test_random_id'; 101 | 102 | $client->setRandomId($randomId); 103 | 104 | $this->assertSame($randomId, $client->getUsername()); 105 | } 106 | 107 | /** 108 | * Test that getUsername returns null by default if randomId is not set. 109 | */ 110 | public function testGetUsernameReturnsRandomStringByDefault(): void 111 | { 112 | $client = new Client(); 113 | 114 | $this->assertNotNull($client->getUsername()); 115 | } 116 | 117 | /** 118 | * Test that getUserIdentifier returns the expected randomId value. 119 | */ 120 | public function testGetUserIdentifierReturnsExpectedValue(): void 121 | { 122 | $client = new Client(); 123 | $randomId = 'test_random_id'; 124 | 125 | $client->setRandomId($randomId); 126 | 127 | $this->assertSame($randomId, $client->getUserIdentifier()); 128 | } 129 | 130 | /** 131 | * Test that getUserIdentifier returns null by default if randomId is not set. 132 | */ 133 | public function testGetUserIdentifierReturnsRansmStringByDefault(): void 134 | { 135 | $client = new Client(); 136 | 137 | $this->assertNotNull($client->getUserIdentifier()); 138 | } 139 | } -------------------------------------------------------------------------------- /Tests/Model/TokenTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Model; 15 | 16 | use FOS\OAuthServerBundle\Model\Token; 17 | use PHPUnit\Framework\Attributes\DataProvider; 18 | use PHPUnit\Framework\Attributes\Group; 19 | use PHPUnit\Framework\TestCase; 20 | 21 | #[Group('time-sensitive')] 22 | class TokenTest extends TestCase 23 | { 24 | 25 | #[DataProvider('getTestHasExpiredData')] 26 | public function testHasExpired($expiresAt, $expect): void 27 | { 28 | $token = new Token(); 29 | $token->setExpiresAt($expiresAt); 30 | 31 | $this->assertSame($expect, $token->hasExpired()); 32 | } 33 | 34 | public static function getTestHasExpiredData(): array 35 | { 36 | return [ 37 | [time() + 60, false], 38 | [time() - 60, true], 39 | [null, false], 40 | ]; 41 | } 42 | 43 | public function testExpiresIn(): void 44 | { 45 | $token = new Token(); 46 | 47 | $this->assertSame(PHP_INT_MAX, $token->getExpiresIn()); 48 | } 49 | 50 | public function testExpiresInWithExpiresAt(): void 51 | { 52 | $token = new Token(); 53 | $token->setExpiresAt(time() + 60); 54 | 55 | $this->assertSame(60, $token->getExpiresIn()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/Security/Authenticator/Passport/Badge/AccessTokenBadgeTest.php: -------------------------------------------------------------------------------- 1 | createMock(AccessToken::class); 16 | $roles = ['ROLE_USER']; 17 | 18 | $badge = new AccessTokenBadge($accessToken, $roles); 19 | 20 | $this->assertTrue($badge->isResolved()); 21 | } 22 | 23 | public function testIsResolvedReturnsFalseWhenRolesAreEmpty(): void 24 | { 25 | $accessToken = $this->createMock(AccessToken::class); 26 | $roles = []; 27 | 28 | $badge = new AccessTokenBadge($accessToken, $roles); 29 | 30 | $this->assertFalse($badge->isResolved()); 31 | } 32 | 33 | public function testGetAccessToken(): void 34 | { 35 | $accessToken = $this->createMock(AccessToken::class); 36 | $roles = []; 37 | 38 | $badge = new AccessTokenBadge($accessToken, $roles); 39 | 40 | $this->assertSame($accessToken, $badge->getAccessToken()); 41 | } 42 | } -------------------------------------------------------------------------------- /Tests/Security/EntryPoint/OAuthEntryPointTest.php: -------------------------------------------------------------------------------- 1 | createMock(OAuth2::class); 19 | $mockOAuth2->method('getVariable') 20 | ->with(OAuth2::CONFIG_WWW_REALM) 21 | ->willReturn('example-realm'); 22 | 23 | $entryPoint = new OAuthEntryPoint($mockOAuth2); 24 | $request = $this->createMock(Request::class); 25 | $authException = $this->createMock(AuthenticationException::class); 26 | 27 | $response = $entryPoint->start($request, $authException); 28 | 29 | $this->assertEquals(Response::HTTP_UNAUTHORIZED, $response->getStatusCode()); 30 | $this->assertTrue($response->headers->has('WWW-Authenticate')); 31 | $this->assertStringContainsString('Bearer realm="example-realm"', $response->headers->get('WWW-Authenticate')); 32 | } 33 | } -------------------------------------------------------------------------------- /Tests/Util/RandomTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Tests\Util; 15 | 16 | use FOS\OAuthServerBundle\Util\Random; 17 | use phpmock\phpunit\PHPMock; 18 | use PHPUnit\Framework\Attributes\RunInSeparateProcess; 19 | use PHPUnit\Framework\TestCase; 20 | 21 | /** 22 | * Class RandomTest. 23 | * 24 | * @author Nikola Petkanski getFunctionMock('FOS\OAuthServerBundle\Util', 'random_bytes') 36 | ->expects($this->once()) 37 | ->with(32) 38 | ->willReturn($hashResult) 39 | ; 40 | 41 | $bin2hexResult = \bin2hex($hashResult); 42 | $this->getFunctionMock('FOS\OAuthServerBundle\Util', 'bin2hex') 43 | ->expects($this->once()) 44 | ->with($hashResult) 45 | ->willReturn($bin2hexResult) 46 | ; 47 | 48 | $baseConvertResult = \base_convert($bin2hexResult, 16, 36); 49 | $this->getFunctionMock('FOS\OAuthServerBundle\Util', 'base_convert') 50 | ->expects($this->once()) 51 | ->with($bin2hexResult, 16, 36) 52 | ->willReturn($baseConvertResult) 53 | ; 54 | 55 | $this->assertSame($baseConvertResult, Random::generateToken()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | use Symfony\Component\Dotenv\Dotenv; 15 | 16 | require dirname( __DIR__ ) . '/vendor/autoload.php'; 17 | 18 | if (file_exists(dirname(__DIR__).'/config/bootstrap.php')) { 19 | require dirname(__DIR__).'/config/bootstrap.php'; 20 | } elseif (method_exists(Dotenv::class, 'bootEnv')) { 21 | (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); 22 | } 23 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # UPGRADE TO 5.1 2 | 3 | ## BC BREAK: redirectUris and allowedGrantTypes are now json and not array anymore 4 | 5 | This change were made to be doctrine/dbal v4 compliant. 6 | Make sure to migrate you database schema. -------------------------------------------------------------------------------- /Util/LegacyFormHelper.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Util; 15 | 16 | /** 17 | * @internal 18 | * 19 | * @author Sanjay Pillai 20 | */ 21 | final class LegacyFormHelper 22 | { 23 | /** 24 | * @var string[] 25 | */ 26 | private static $map = [ 27 | 'Symfony\Component\Form\Extension\Core\Type\HiddenType' => 'hidden', 28 | 'FOS\OAuthServerBundle\Form\Type\AuthorizeFormType' => 'fos_oauth_server_authorize', 29 | ]; 30 | 31 | /** 32 | * No code needed here the main goal is to make the function private 33 | */ 34 | private function __construct() 35 | { 36 | } 37 | 38 | /** 39 | * No code needed here the main goal is to make the function private 40 | */ 41 | private function __clone() 42 | { 43 | } 44 | 45 | public static function getType(string $class): string 46 | { 47 | if (!self::isLegacy()) { 48 | return $class; 49 | } 50 | 51 | if (!isset(self::$map[$class])) { 52 | throw new \InvalidArgumentException(sprintf('Form type with class "%s" can not be found. Please check for typos or add it to the map in LegacyFormHelper', $class)); 53 | } 54 | 55 | return self::$map[$class]; 56 | } 57 | 58 | public static function isLegacy(): bool 59 | { 60 | return !method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Util/Random.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FOS\OAuthServerBundle\Util; 15 | 16 | /** 17 | * Class Random. 18 | * 19 | * @author Nikola Petkanski , string given.#' 17 | - '#Parameter \#1 \$tokenStorage of class FOS\\OAuthServerBundle\\Security\\Firewall\\OAuthListener constructor expects Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface, PHPUnit\\Framework\\MockObject\\MockObject given.#' -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ./ 18 | 19 | 20 | ./Resources 21 | ./Tests 22 | ./Tests-old 23 | ./vendor 24 | .php_cs-fixer.php 25 | 26 | 27 | 28 | 29 | ./Tests 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | --------------------------------------------------------------------------------