From 638b220beb227df2677c16f91bff8ae9e6e29805 Mon Sep 17 00:00:00 2001 From: Makar Date: Mon, 4 Mar 2024 16:22:46 +0200 Subject: [PATCH 1/5] feat: publish to two solar tenants (#398) * feat: publish to two solar tenants --------- Co-authored-by: Makar Sichevoi --- .../PlatformKeyChainRepository.conf.php | 4 +- .../Version202402271423013774_taoLti.php | 34 ++++ .../Exception/PlatformKeyChainException.php | 27 ++++ .../Service/CachedKeyChainGenerator.php | 22 ++- .../Service/KeyChainGeneratorInterface.php | 2 +- .../Service/OpenSslKeyChainGenerator.php | 15 +- .../CachedPlatformKeyChainRepository.php | 6 +- .../Repository/PlatformKeyChainRepository.php | 147 ++++++++++++------ scripts/tools/GenerateKeys.php | 128 +++++++++++++++ .../Service/CachedKeyChainGeneratorTest.php | 2 +- .../Service/KeyChainGeneratorTest.php | 17 ++ .../CachedPlatformKeyChainRepositoryTest.php | 8 +- .../Repository/PlatformJwksRepositoryTest.php | 2 +- .../PlatformKeyChainRepositoryTest.php | 80 +++++++--- 14 files changed, 404 insertions(+), 90 deletions(-) create mode 100644 migrations/Version202402271423013774_taoLti.php create mode 100644 models/classes/Exception/PlatformKeyChainException.php create mode 100644 scripts/tools/GenerateKeys.php diff --git a/config/default/PlatformKeyChainRepository.conf.php b/config/default/PlatformKeyChainRepository.conf.php index 25c5156d..c09b5c73 100644 --- a/config/default/PlatformKeyChainRepository.conf.php +++ b/config/default/PlatformKeyChainRepository.conf.php @@ -2,11 +2,11 @@ use oat\taoLti\models\classes\Security\DataAccess\Repository\PlatformKeyChainRepository; -return new PlatformKeyChainRepository( +return new PlatformKeyChainRepository([ [ PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID => 'defaultPlatformKeyId', PlatformKeyChainRepository::OPTION_DEFAULT_KEY_NAME => 'defaultPlatformKeyName', PlatformKeyChainRepository::OPTION_DEFAULT_PUBLIC_KEY_PATH => '/platform/default/public.key', PlatformKeyChainRepository::OPTION_DEFAULT_PRIVATE_KEY_PATH => '/platform/default/private.key', ] -); +]); diff --git a/migrations/Version202402271423013774_taoLti.php b/migrations/Version202402271423013774_taoLti.php new file mode 100644 index 00000000..a52b0ca6 --- /dev/null +++ b/migrations/Version202402271423013774_taoLti.php @@ -0,0 +1,34 @@ +getServiceLocator()->get(PlatformKeyChainRepository::SERVICE_ID); + $options = $platformKeyChainRepository->getOptions(); + $platformKeyChainRepository->setOptions([$options]); + $this->getServiceLocator()->register(PlatformKeyChainRepository::SERVICE_ID, $platformKeyChainRepository); + } + + public function down(Schema $schema): void + { + $platformKeyChainRepository = $this->getServiceLocator()->get(PlatformKeyChainRepository::SERVICE_ID); + $options = $platformKeyChainRepository->getOptions(); + $platformKeyChainRepository->setOptions(reset($options)); + $this->getServiceLocator()->register(PlatformKeyChainRepository::SERVICE_ID, $platformKeyChainRepository); + } +} diff --git a/models/classes/Exception/PlatformKeyChainException.php b/models/classes/Exception/PlatformKeyChainException.php new file mode 100644 index 00000000..aa1832ad --- /dev/null +++ b/models/classes/Exception/PlatformKeyChainException.php @@ -0,0 +1,27 @@ +getKeyChainGenerator()->generate($id, $name, $passPhrase); + $this->save($keyChain); + + return $keyChain; + } + + private function save(KeyChainInterface $keyChain): bool { - $keyChain = $this->getKeyChainGenerator()->generate(); - $this->getKeyChainRepository()->save($keyChain); + $this->getKeyChainRepository()->saveKeyChain($keyChain); $this->invalidateKeyChain($keyChain); $this->invalidateJwks(); - return $keyChain; + return true; } private function invalidateKeyChain(KeyChainInterface $keyChain): void @@ -65,7 +75,7 @@ private function getKeyChainGenerator(): KeyChainGeneratorInterface return $this->getServiceLocator()->get(OpenSslKeyChainGenerator::class); } - private function getKeyChainRepository(): KeyChainRepositoryInterface + private function getKeyChainRepository(): PlatformKeyChainRepository { return $this->getServiceLocator()->get(PlatformKeyChainRepository::class); } diff --git a/models/classes/Platform/Service/KeyChainGeneratorInterface.php b/models/classes/Platform/Service/KeyChainGeneratorInterface.php index 0610d5a3..5824eccc 100644 --- a/models/classes/Platform/Service/KeyChainGeneratorInterface.php +++ b/models/classes/Platform/Service/KeyChainGeneratorInterface.php @@ -28,5 +28,5 @@ interface KeyChainGeneratorInterface { public const OPTION_DATA_STORE = 'sslConfig'; - public function generate(): KeyChainInterface; + public function generate(string $id, string $name, ?string $passPhrase): KeyChainInterface; } diff --git a/models/classes/Platform/Service/OpenSslKeyChainGenerator.php b/models/classes/Platform/Service/OpenSslKeyChainGenerator.php index 4cb938e6..0e86d723 100644 --- a/models/classes/Platform/Service/OpenSslKeyChainGenerator.php +++ b/models/classes/Platform/Service/OpenSslKeyChainGenerator.php @@ -30,17 +30,20 @@ class OpenSslKeyChainGenerator extends ConfigurableService implements KeyChainGeneratorInterface { - public function generate(): KeyChainInterface - { + public function generate( + string $id = PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID, + string $name = PlatformKeyChainRepository::OPTION_DEFAULT_KEY_NAME, + ?string $passPhrase = null + ): KeyChainInterface { $resource = openssl_pkey_new($this->getOption(self::OPTION_DATA_STORE)); - openssl_pkey_export($resource, $privateKey); + openssl_pkey_export($resource, $privateKey, $passPhrase); $publicKey = openssl_pkey_get_details($resource); return new KeyChain( - PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID, - PlatformKeyChainRepository::OPTION_DEFAULT_KEY_NAME, + $id, + $name, new Key($publicKey['key']), - new Key($privateKey) + new Key($privateKey, $passPhrase) ); } } diff --git a/models/classes/Security/DataAccess/Repository/CachedPlatformKeyChainRepository.php b/models/classes/Security/DataAccess/Repository/CachedPlatformKeyChainRepository.php index 0b80f8fd..e8aa586e 100644 --- a/models/classes/Security/DataAccess/Repository/CachedPlatformKeyChainRepository.php +++ b/models/classes/Security/DataAccess/Repository/CachedPlatformKeyChainRepository.php @@ -46,20 +46,19 @@ class CachedPlatformKeyChainRepository extends ConfigurableService implements Ke * @throws InvalidArgumentException * @throws ErrorException */ - public function save(KeyChainInterface $keyChain): void + public function saveDefaultKeyChain(KeyChainInterface $keyChain): void { $this->setKeys( $keyChain, $keyChain->getIdentifier() ); - $this->getPlatformKeyChainRepository()->save($keyChain); + $this->getPlatformKeyChainRepository()->saveDefaultKeyChain($keyChain); } public function find(string $identifier): ?KeyChainInterface { if ($this->exists($identifier)) { - //TODO: Needs to be refactor if we have multiple key chains $rawKeys = $this->getCacheService()->getMultiple( [ sprintf(self::PRIVATE_PATTERN, $identifier), @@ -93,7 +92,6 @@ public function findAll(KeyChainQuery $query): KeyChainCollection } if ($this->exists($query->getIdentifier())) { - //TODO: Needs to be refactor if we have multiple key chains $rawKeys = $this->getCacheService()->getMultiple( [ sprintf(self::PRIVATE_PATTERN, $query->getIdentifier()), diff --git a/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepository.php b/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepository.php index 5ce33091..2fa37b00 100644 --- a/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepository.php +++ b/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepository.php @@ -23,7 +23,6 @@ namespace oat\taoLti\models\classes\Security\DataAccess\Repository; use common_exception_NoImplementation; -use ErrorException; use League\Flysystem\FilesystemInterface; use OAT\Library\Lti1p3Core\Security\Key\Key; use OAT\Library\Lti1p3Core\Security\Key\KeyChain; @@ -31,94 +30,132 @@ use OAT\Library\Lti1p3Core\Security\Key\KeyChainRepositoryInterface; use oat\oatbox\filesystem\FileSystemService; use oat\oatbox\service\ConfigurableService; -use oat\tao\model\security\Business\Domain\Key\KeyChainCollection; -use oat\tao\model\security\Business\Domain\Key\KeyChainQuery; use oat\tao\model\security\Business\Domain\Key\Key as TaoKey; use oat\tao\model\security\Business\Domain\Key\KeyChain as TaoKeyChain; +use oat\tao\model\security\Business\Domain\Key\KeyChainCollection; +use oat\tao\model\security\Business\Domain\Key\KeyChainQuery; +use oat\taoLti\models\classes\Exception\PlatformKeyChainException; class PlatformKeyChainRepository extends ConfigurableService implements KeyChainRepositoryInterface { public const SERVICE_ID = 'taoLti/PlatformKeyChainRepository'; public const OPTION_DEFAULT_KEY_ID = 'defaultKeyId'; + public const OPTION_DEFAULT_KEY_ID_VALUE = 'defaultPlatformKeyId'; public const OPTION_DEFAULT_KEY_NAME = 'defaultKeyName'; + public const OPTION_DEFAULT_KEY_NAME_VALUE = 'defaultPlatformKeyName'; public const OPTION_DEFAULT_PUBLIC_KEY_PATH = 'defaultPublicKeyPath'; public const OPTION_DEFAULT_PRIVATE_KEY_PATH = 'defaultPrivateKeyPath'; + public const OPTION_DEFAULT_PRIVATE_KEY_PASSPHRASE = 'defaultPrivateKeyPassphrase'; public const FILE_SYSTEM_ID = 'ltiKeyChain'; - /** - * @throws ErrorException - */ - public function save(KeyChainInterface $keyChain): void + + public function saveDefaultKeyChain(KeyChainInterface $keyChain): void + { + $this->save($keyChain, $this->getDefaultKeyId()); + } + + public function saveKeyChain(KeyChainInterface $keyChain): void { - $isPublicKeySaved = $this->getFileSystem() - ->put( - ltrim($this->getOption(self::OPTION_DEFAULT_PUBLIC_KEY_PATH), DIRECTORY_SEPARATOR), - $keyChain->getPublicKey()->getContent() - ); - - $isPrivateKeySaved = $this->getFileSystem() - ->put( - ltrim($this->getOption(self::OPTION_DEFAULT_PRIVATE_KEY_PATH), DIRECTORY_SEPARATOR), - $keyChain->getPrivateKey()->getContent() - ); + $this->save($keyChain, $keyChain->getIdentifier()); + } + + protected function save(KeyChainInterface $keyChain, string $identifier): void + { + $configs = $this->findConfiguration($identifier); + + if (empty($configs)) { + throw new PlatformKeyChainException('Impossible to write LTI keys. Configuration not found'); + } + + $publicKeyPath = $configs[self::OPTION_DEFAULT_PUBLIC_KEY_PATH] ?? null; + $privateKeyPath = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PATH] ?? null; + $isPublicKeySaved = null; + $isPrivateKeySaved = null; + + if ($publicKeyPath !== null && $privateKeyPath !== null) { + $isPublicKeySaved = $this->getFileSystem() + ->put( + ltrim($publicKeyPath, DIRECTORY_SEPARATOR), + $keyChain->getPublicKey()->getContent() + ); + + $isPrivateKeySaved = $this->getFileSystem() + ->put( + ltrim($privateKeyPath, DIRECTORY_SEPARATOR), + $keyChain->getPrivateKey()->getContent() + ); + } if (!$isPublicKeySaved || !$isPrivateKeySaved) { - throw new ErrorException('Impossible to write LTI keys'); + throw new PlatformKeyChainException('Impossible to write LTI keys'); } } public function getDefaultKeyId(): string { - return $this->getOption(PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID, ''); + $options = $this->getOptions(); + return reset($options)[self::OPTION_DEFAULT_KEY_ID] ?? ''; } - /** - * @throws common_exception_NoImplementation - */ public function find(string $identifier): ?KeyChainInterface { - if ($identifier !== $this->getDefaultKeyId()) { + $configs = $this->findConfiguration($identifier); + + if (empty($configs)) { return null; } - $publicKey = $this->getFileSystem() - ->read($this->getOption(self::OPTION_DEFAULT_PUBLIC_KEY_PATH)); + $publicKeyPath = $configs[self::OPTION_DEFAULT_PUBLIC_KEY_PATH] ?? null; + $privateKeyPath = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PATH] ?? null; + $privateKeyPassword = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PASSPHRASE] ?? null; + + if (!$publicKeyPath || !$privateKeyPath) { + throw new PlatformKeyChainException('The key path is not defined'); + } - $privateKey = $this->getFileSystem() - ->read($this->getOption(self::OPTION_DEFAULT_PRIVATE_KEY_PATH)); + $publicKey = $this->getFileSystem()->read($publicKeyPath); + $privateKey = $this->getFileSystem()->read($privateKeyPath); if ($publicKey === false || $privateKey === false) { - throw new ErrorException('Impossible to read LTI keys'); + throw new PlatformKeyChainException('Impossible to read LTI keys'); } return new KeyChain( - $this->getDefaultKeyId(), - $this->getOption(self::OPTION_DEFAULT_KEY_NAME), + $configs[self::OPTION_DEFAULT_KEY_ID] ?? null, + $configs[self::OPTION_DEFAULT_KEY_NAME] ?? null, new Key($publicKey), - new Key($privateKey) + new Key($privateKey, $privateKeyPassword) ); } public function findAll(KeyChainQuery $query): KeyChainCollection { - $publicKey = $this->getFileSystem() - ->read($this->getOption(self::OPTION_DEFAULT_PUBLIC_KEY_PATH)); - - $privateKey = $this->getFileSystem() - ->read($this->getOption(self::OPTION_DEFAULT_PRIVATE_KEY_PATH)); - - if ($publicKey === false || $privateKey === false) { - throw new ErrorException('Impossible to read LTI keys'); + $options = $this->getOptions(); + foreach ($options as $configs) { + $defaultKeyId = $configs[self::OPTION_DEFAULT_KEY_ID] ?? null; + $defaultKeyName = $configs[self::OPTION_DEFAULT_KEY_NAME] ?? null; + $publicKeyPath = $configs[self::OPTION_DEFAULT_PUBLIC_KEY_PATH] ?? null; + $privateKeyPath = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PATH] ?? null; + $privateKeyPassword = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PASSPHRASE] ?? null; + + if ($defaultKeyId && $publicKeyPath && $privateKeyPath) { + $publicKey = $this->getFileSystem()->read($publicKeyPath); + $privateKey = $this->getFileSystem()->read($privateKeyPath); + + $keyChains[] = new TaoKeyChain( + $defaultKeyId, + $defaultKeyName, + new TaoKey($publicKey), + new TaoKey($privateKey, $privateKeyPassword) + ); + } } - $keyChain = new TaoKeyChain( - $this->getOption(self::OPTION_DEFAULT_KEY_ID), - $this->getOption(self::OPTION_DEFAULT_KEY_NAME), - new TaoKey($publicKey), - new TaoKey($privateKey) - ); + if (empty($keyChains)) { + throw new PlatformKeyChainException('Impossible to read LTI keys'); + } - return new KeyChainCollection($keyChain); + return new KeyChainCollection(...$keyChains); } @@ -138,4 +175,20 @@ private function getFileSystem(): FilesystemInterface return $fileSystemService->getFileSystem(self::FILE_SYSTEM_ID); } + + /** + * @param string $identifier + * @return array|null + */ + protected function findConfiguration(string $identifier): ?array + { + $options = $this->getOptions(); + foreach ($options as $configs) { + if ($configs[self::OPTION_DEFAULT_KEY_ID] === $identifier) { + return $configs; + } + } + + return null; + } } diff --git a/scripts/tools/GenerateKeys.php b/scripts/tools/GenerateKeys.php new file mode 100644 index 00000000..abaf4a85 --- /dev/null +++ b/scripts/tools/GenerateKeys.php @@ -0,0 +1,128 @@ + [ + 'prefix' => 'id', + 'longPrefix' => 'key_id', + 'description' => 'Lti Platform key chain id', + 'required' => true, + 'cast' => 'string' + ], + 'key_name' => [ + 'prefix' => 'kn', + 'longPrefix' => 'key_name', + 'description' => 'Lti Platform key chain name', + 'required' => true, + 'cast' => 'string' + ], + 'public_key_path' => [ + 'prefix' => 'pkp', + 'longPrefix' => 'public_key_path', + 'description' => 'Lti Platform public key path', + 'required' => true, + 'cast' => 'string' + ], + 'private_key_path' => [ + 'prefix' => 'kp', + 'longPrefix' => 'private_key_path', + 'description' => 'Lti Platform private key path', + 'required' => true, + 'cast' => 'string' + ], + 'private_key_passphrase' => [ + 'prefix' => 'kpp', + 'longPrefix' => 'private_key_passphrase', + 'description' => 'Lti Platform private key passphrase', + 'required' => false, + 'cast' => 'string' + ], + ]; + } + + protected function provideDescription() + { + return 'Script to create a LTI Platform Key Chain'; + } + + protected function provideUsage() + { + return [ + 'prefix' => 'h', + 'longPrefix' => 'help', + 'description' => 'Prints the help.' + ]; + } + + protected function run() + { + $keyId = $this->getOption('key_id'); + $keyName = $this->getOption('key_name'); + $publicKeyPath = $this->getOption('public_key_path'); + $privateKeyPath = $this->getOption('private_key_path'); + $privateKeyPassphrase = $this->getOption('private_key_passphrase'); + + if (empty($keyId) || empty($keyName) || empty($publicKeyPath) || empty($privateKeyPath)) { + return Report::createError( + 'Not all required arguments were provided. Try to run the script with -h option' + ); + } + $platformKeyChainRepository = $this->getServiceLocator()->get(PlatformKeyChainRepository::SERVICE_ID); + $options = $platformKeyChainRepository->getOptions(); + $option = [ + PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID => $keyId, + PlatformKeyChainRepository::OPTION_DEFAULT_KEY_NAME => $keyName, + PlatformKeyChainRepository::OPTION_DEFAULT_PUBLIC_KEY_PATH => $publicKeyPath, + PlatformKeyChainRepository::OPTION_DEFAULT_PRIVATE_KEY_PATH => $privateKeyPath, + ]; + + if (!empty($privateKeyPassphrase)) { + $option[PlatformKeyChainRepository::OPTION_DEFAULT_PRIVATE_KEY_PASSPHRASE] = $privateKeyPassphrase; + } + + $options[] = $option; + $platformKeyChainRepository->setOptions($options); + $this->getServiceLocator()->register(PlatformKeyChainRepository::SERVICE_ID, $platformKeyChainRepository); + + /** @var CachedKeyChainGenerator $cachedKeyChainGenerator */ + $cachedKeyChainGenerator = $this->getServiceLocator()->get(CachedKeyChainGenerator::class); + $cachedKeyChainGenerator->generate($keyId, $keyName, $privateKeyPassphrase); + + return Report::createSuccess('LTI Platform Key Chain generated successfully!'); + } +} diff --git a/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php b/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php index c2251735..b1cea4d2 100644 --- a/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php +++ b/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php @@ -69,7 +69,7 @@ public function testGenerate(): void $this->platformKeyChainRepositoryMock ->expects($this->once()) - ->method('save'); + ->method('saveDefaultKeyChain'); $this->simpleCacheMock ->expects($this->exactly(3)) diff --git a/test/unit/models/classes/Platform/Service/KeyChainGeneratorTest.php b/test/unit/models/classes/Platform/Service/KeyChainGeneratorTest.php index 7e4a0d4a..ec56d85b 100644 --- a/test/unit/models/classes/Platform/Service/KeyChainGeneratorTest.php +++ b/test/unit/models/classes/Platform/Service/KeyChainGeneratorTest.php @@ -24,6 +24,7 @@ use oat\generis\test\TestCase; use oat\taoLti\models\classes\Platform\Service\OpenSslKeyChainGenerator; +use oat\taoLti\models\classes\Security\DataAccess\Repository\PlatformKeyChainRepository; class KeyChainGeneratorTest extends TestCase { @@ -44,4 +45,20 @@ public function testGenerate(): void $this->assertStringContainsString('-----BEGIN PUBLIC KEY-----', $result->getPublicKey()->getContent()); $this->assertStringContainsString('-----END PUBLIC KEY-----', $result->getPublicKey()->getContent()); } + + public function testGenerateWithPassphrase(): void + { + $result = $this->subject->generate( + PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID, + PlatformKeyChainRepository::OPTION_DEFAULT_KEY_NAME, + 'pass' + ); + + $this->assertEquals('pass', $result->getPrivateKey()->getPassPhrase()); + $this->assertEmpty($result->getPublicKey()->getPassPhrase()); + $this->assertStringContainsString('-----BEGIN ENCRYPTED PRIVATE KEY-----', $result->getPrivateKey()->getContent()); + $this->assertStringContainsString('-----END ENCRYPTED PRIVATE KEY-----', $result->getPrivateKey()->getContent()); + $this->assertStringContainsString('-----BEGIN PUBLIC KEY-----', $result->getPublicKey()->getContent()); + $this->assertStringContainsString('-----END PUBLIC KEY-----', $result->getPublicKey()->getContent()); + } } diff --git a/test/unit/models/classes/Security/DataAccess/Repository/CachedPlatformKeyChainRepositoryTest.php b/test/unit/models/classes/Security/DataAccess/Repository/CachedPlatformKeyChainRepositoryTest.php index 586a4b12..7b6ae2fc 100644 --- a/test/unit/models/classes/Security/DataAccess/Repository/CachedPlatformKeyChainRepositoryTest.php +++ b/test/unit/models/classes/Security/DataAccess/Repository/CachedPlatformKeyChainRepositoryTest.php @@ -100,9 +100,9 @@ public function testSave(): void $this->platformKeyChainRepository ->expects($this->once()) - ->method('save'); + ->method('saveDefaultKeyChain'); - $this->subject->save($keyChain); + $this->subject->saveDefaultKeyChain($keyChain); } public function testFindWhenCacheEmpty(): void @@ -143,7 +143,9 @@ public function testFindWhenCacheEmpty(): void $this->assertSame(self::KEY_CHAIN_ID, $keyChain->getIdentifier()); $this->assertSame('privateKey', $keyChain->getPrivateKey()->getContent()); + $this->assertSame('pass', $keyChain->getPrivateKey()->getPassPhrase()); $this->assertSame('publicKey', $keyChain->getPublicKey()->getContent()); + $this->assertNull($keyChain->getPublicKey()->getPassPhrase()); } public function testFind(): void @@ -187,7 +189,7 @@ private function getKeyChain(): KeyChainInterface self::KEY_CHAIN_ID, self::KEY_CHAIN_NAME, new Key('publicKey'), - new Key('privateKey') + new Key('privateKey', 'pass') ); } } diff --git a/test/unit/models/classes/Security/DataAccess/Repository/PlatformJwksRepositoryTest.php b/test/unit/models/classes/Security/DataAccess/Repository/PlatformJwksRepositoryTest.php index a2e58854..898d7cad 100644 --- a/test/unit/models/classes/Security/DataAccess/Repository/PlatformJwksRepositoryTest.php +++ b/test/unit/models/classes/Security/DataAccess/Repository/PlatformJwksRepositoryTest.php @@ -63,7 +63,7 @@ public function setUp(): void public function testFind(): void { $keyChain = new KeyChain('id', 'name', new Key('123456'), new Key('654321')); - $collection = new KeyChainCollection(...[$keyChain]); + $collection = new KeyChainCollection($keyChain); $this->keyChainRepository ->method('findAll') diff --git a/test/unit/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepositoryTest.php b/test/unit/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepositoryTest.php index 6e79e8e7..e16b5988 100644 --- a/test/unit/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepositoryTest.php +++ b/test/unit/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepositoryTest.php @@ -22,16 +22,19 @@ namespace oat\taoLti\test\unit\models\classes\Security\DataAccess\Repository; -use ErrorException; use oat\generis\test\ServiceManagerMockTrait; use OAT\Library\Lti1p3Core\Security\Key\Key; use OAT\Library\Lti1p3Core\Security\Key\KeyChain; use OAT\Library\Lti1p3Core\Security\Key\KeyChainInterface; +use OAT\Library\Lti1p3Core\Security\Key\KeyInterface; use oat\oatbox\filesystem\FileSystem; use oat\oatbox\filesystem\FileSystemService; +use oat\tao\model\security\Business\Domain\Key\KeyChainQuery; +use oat\taoLti\models\classes\Exception\PlatformKeyChainException; use oat\taoLti\models\classes\Security\DataAccess\Repository\PlatformKeyChainRepository; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use oat\tao\model\security\Business\Domain\Key\Key as TaoKey; class PlatformKeyChainRepositoryTest extends TestCase { @@ -51,14 +54,26 @@ public function setUp(): void $fileSystem->method('getFileSystem') ->willReturn($this->fileSystem); - $this->subject = new PlatformKeyChainRepository( + $this->subject = new PlatformKeyChainRepository([ [ PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID => 'keyId', PlatformKeyChainRepository::OPTION_DEFAULT_KEY_NAME => 'keyName', + PlatformKeyChainRepository::OPTION_DEFAULT_PUBLIC_KEY_PATH => 'publicPath', + PlatformKeyChainRepository::OPTION_DEFAULT_PRIVATE_KEY_PATH => 'privatePath', + ], + [ + PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID => 'keyId2', + PlatformKeyChainRepository::OPTION_DEFAULT_KEY_NAME => 'keyName2', + PlatformKeyChainRepository::OPTION_DEFAULT_PUBLIC_KEY_PATH => 'publicPath', + PlatformKeyChainRepository::OPTION_DEFAULT_PRIVATE_KEY_PATH => 'privatePath', + ], + [ + PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID => 'keyId3', + PlatformKeyChainRepository::OPTION_DEFAULT_KEY_NAME => 'keyName3', PlatformKeyChainRepository::OPTION_DEFAULT_PUBLIC_KEY_PATH => '', PlatformKeyChainRepository::OPTION_DEFAULT_PRIVATE_KEY_PATH => '', ] - ); + ]); $this->subject->setServiceLocator( $this->getServiceManagerMock( [ @@ -77,18 +92,37 @@ public function testFind(): void 'privateKey' ); - $keyChain = $this->subject->find('keyId'); + $keyChain = $this->subject->find('keyId2'); $this->assertInstanceOf(KeyChainInterface::class, $keyChain); - $this->assertEquals( - $keyChain = new KeyChain( - 'keyId', - 'keyName', - new Key('publicKey'), - new Key('privateKey') - ), - $keyChain - ); + $this->assertEquals('keyId2', $keyChain->getIdentifier()); + $this->assertEquals('keyName2', $keyChain->getKeySetName()); + $this->assertInstanceOf(KeyInterface::class, $keyChain->getPublicKey()); + $this->assertInstanceOf(KeyInterface::class, $keyChain->getPrivateKey()); + } + + public function testFindAll(): void + { + $this->fileSystem + ->method('read') + ->willReturnOnConsecutiveCalls( + 'publicKey', + 'privateKey', + 'publicKey', + 'privateKey', + '', + '' + ); + + $keyChains = $this->subject->findAll(new KeyChainQuery())->getKeyChains(); + + $this->assertIsArray($keyChains); + $this->assertCount(2, $keyChains); + $keyChain = $keyChains[1]; + $this->assertEquals('keyId2', $keyChain->getIdentifier()); + $this->assertEquals('keyName2', $keyChain->getName()); + $this->assertInstanceOf(TaoKey::class, $keyChain->getPublicKey()); + $this->assertInstanceOf(TaoKey::class, $keyChain->getPrivateKey()); } public function testFindFails(): void @@ -102,29 +136,37 @@ public function testFindFails(): void $this->assertNull($keyChain); } - public function testSave(): void + public function testFindWithEmptyPathFails(): void + { + $this->expectException(PlatformKeyChainException::class); + $this->expectExceptionMessage('The key path is not defined'); + + $this->subject->find('keyId3'); + } + + public function testSaveDefaultKeyChain(): void { $this->fileSystem ->method('put') ->willReturn(true); - $this->subject->save( - new KeyChain('', '', new Key(''), new Key('')) + $this->subject->saveDefaultKeyChain( + new KeyChain('keyId', '', new Key(''), new Key('')) ); $this->expectNotToPerformAssertions(); } - public function testSaveFails(): void + public function testSaveDefaultKeyChainFails(): void { $this->fileSystem ->method('put') ->willReturn(false); - $this->expectException(ErrorException::class); + $this->expectException(PlatformKeyChainException::class); $this->expectExceptionMessage('Impossible to write LTI keys'); - $this->subject->save(new KeyChain('', '', new Key(''), new Key(''))); + $this->subject->saveDefaultKeyChain(new KeyChain('', '', new Key(''), new Key(''))); } public function testGetDefaultKeyId(): void From 9065045cc8422822c8f141c5bedf4eb82fe908ba Mon Sep 17 00:00:00 2001 From: Makar Sichevoi Date: Mon, 4 Mar 2024 16:33:12 +0200 Subject: [PATCH 2/5] feat: publish to two solar tenants --- .../classes/Platform/Service/CachedKeyChainGeneratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php b/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php index b1cea4d2..c2251735 100644 --- a/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php +++ b/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php @@ -69,7 +69,7 @@ public function testGenerate(): void $this->platformKeyChainRepositoryMock ->expects($this->once()) - ->method('saveDefaultKeyChain'); + ->method('save'); $this->simpleCacheMock ->expects($this->exactly(3)) From 65470e734c7813aab4704454f8e53fc4a447f13d Mon Sep 17 00:00:00 2001 From: Makar Sichevoi Date: Wed, 6 Mar 2024 12:30:00 +0200 Subject: [PATCH 3/5] feat: publish to two solar tenants --- .../Repository/PlatformKeyChainRepository.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepository.php b/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepository.php index 2fa37b00..43d759cf 100644 --- a/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepository.php +++ b/models/classes/Security/DataAccess/Repository/PlatformKeyChainRepository.php @@ -69,8 +69,8 @@ protected function save(KeyChainInterface $keyChain, string $identifier): void $publicKeyPath = $configs[self::OPTION_DEFAULT_PUBLIC_KEY_PATH] ?? null; $privateKeyPath = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PATH] ?? null; - $isPublicKeySaved = null; - $isPrivateKeySaved = null; + $isPublicKeySaved = false; + $isPrivateKeySaved = false; if ($publicKeyPath !== null && $privateKeyPath !== null) { $isPublicKeySaved = $this->getFileSystem() @@ -105,9 +105,10 @@ public function find(string $identifier): ?KeyChainInterface return null; } + $keyName = $configs[self::OPTION_DEFAULT_KEY_NAME] ?? ''; $publicKeyPath = $configs[self::OPTION_DEFAULT_PUBLIC_KEY_PATH] ?? null; $privateKeyPath = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PATH] ?? null; - $privateKeyPassword = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PASSPHRASE] ?? null; + $privateKeyPassphrase = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PASSPHRASE] ?? null; if (!$publicKeyPath || !$privateKeyPath) { throw new PlatformKeyChainException('The key path is not defined'); @@ -121,10 +122,10 @@ public function find(string $identifier): ?KeyChainInterface } return new KeyChain( - $configs[self::OPTION_DEFAULT_KEY_ID] ?? null, - $configs[self::OPTION_DEFAULT_KEY_NAME] ?? null, + $identifier, + $keyName, new Key($publicKey), - new Key($privateKey, $privateKeyPassword) + new Key($privateKey, $privateKeyPassphrase) ); } @@ -133,10 +134,10 @@ public function findAll(KeyChainQuery $query): KeyChainCollection $options = $this->getOptions(); foreach ($options as $configs) { $defaultKeyId = $configs[self::OPTION_DEFAULT_KEY_ID] ?? null; - $defaultKeyName = $configs[self::OPTION_DEFAULT_KEY_NAME] ?? null; + $defaultKeyName = $configs[self::OPTION_DEFAULT_KEY_NAME] ?? ''; $publicKeyPath = $configs[self::OPTION_DEFAULT_PUBLIC_KEY_PATH] ?? null; $privateKeyPath = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PATH] ?? null; - $privateKeyPassword = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PASSPHRASE] ?? null; + $privateKeyPassphrase = $configs[self::OPTION_DEFAULT_PRIVATE_KEY_PASSPHRASE] ?? null; if ($defaultKeyId && $publicKeyPath && $privateKeyPath) { $publicKey = $this->getFileSystem()->read($publicKeyPath); @@ -146,7 +147,7 @@ public function findAll(KeyChainQuery $query): KeyChainCollection $defaultKeyId, $defaultKeyName, new TaoKey($publicKey), - new TaoKey($privateKey, $privateKeyPassword) + new TaoKey($privateKey, $privateKeyPassphrase) ); } } From fb4dfee49bf148f98b67a6e0a5fdf9ff84e4fda7 Mon Sep 17 00:00:00 2001 From: Makar Sichevoi Date: Wed, 6 Mar 2024 12:37:09 +0200 Subject: [PATCH 4/5] feat: publish to two solar tenants --- .../classes/Platform/Service/CachedKeyChainGeneratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php b/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php index c2251735..5a3f531c 100644 --- a/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php +++ b/test/unit/models/classes/Platform/Service/CachedKeyChainGeneratorTest.php @@ -69,7 +69,7 @@ public function testGenerate(): void $this->platformKeyChainRepositoryMock ->expects($this->once()) - ->method('save'); + ->method('saveKeyChain'); $this->simpleCacheMock ->expects($this->exactly(3)) From 3af469b8a3fdc5e16a6a602938d4aed627eb9f45 Mon Sep 17 00:00:00 2001 From: Makar Sichevoi Date: Wed, 6 Mar 2024 12:56:23 +0200 Subject: [PATCH 5/5] feat: publish to two solar tenants --- .../Platform/Service/CachedKeyChainGenerator.php | 1 - .../classes/Platform/Service/KeyChainGeneratorTest.php | 10 ++++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/models/classes/Platform/Service/CachedKeyChainGenerator.php b/models/classes/Platform/Service/CachedKeyChainGenerator.php index 48364b98..23956151 100644 --- a/models/classes/Platform/Service/CachedKeyChainGenerator.php +++ b/models/classes/Platform/Service/CachedKeyChainGenerator.php @@ -32,7 +32,6 @@ class CachedKeyChainGenerator extends ConfigurableService implements KeyChainGeneratorInterface { - public function generate( string $id = PlatformKeyChainRepository::OPTION_DEFAULT_KEY_ID_VALUE, string $name = PlatformKeyChainRepository::OPTION_DEFAULT_KEY_NAME_VALUE, diff --git a/test/unit/models/classes/Platform/Service/KeyChainGeneratorTest.php b/test/unit/models/classes/Platform/Service/KeyChainGeneratorTest.php index ec56d85b..5388e472 100644 --- a/test/unit/models/classes/Platform/Service/KeyChainGeneratorTest.php +++ b/test/unit/models/classes/Platform/Service/KeyChainGeneratorTest.php @@ -56,8 +56,14 @@ public function testGenerateWithPassphrase(): void $this->assertEquals('pass', $result->getPrivateKey()->getPassPhrase()); $this->assertEmpty($result->getPublicKey()->getPassPhrase()); - $this->assertStringContainsString('-----BEGIN ENCRYPTED PRIVATE KEY-----', $result->getPrivateKey()->getContent()); - $this->assertStringContainsString('-----END ENCRYPTED PRIVATE KEY-----', $result->getPrivateKey()->getContent()); + $this->assertStringContainsString( + '-----BEGIN ENCRYPTED PRIVATE KEY-----', + $result->getPrivateKey()->getContent() + ); + $this->assertStringContainsString( + '-----END ENCRYPTED PRIVATE KEY-----', + $result->getPrivateKey()->getContent() + ); $this->assertStringContainsString('-----BEGIN PUBLIC KEY-----', $result->getPublicKey()->getContent()); $this->assertStringContainsString('-----END PUBLIC KEY-----', $result->getPublicKey()->getContent()); }