diff --git a/lib/Controller/AdminController.php b/lib/Controller/AdminController.php index 51c3b56023..997673fb4c 100644 --- a/lib/Controller/AdminController.php +++ b/lib/Controller/AdminController.php @@ -14,6 +14,7 @@ use OCA\Libresign\Handler\CertificateEngine\IEngineHandler; use OCA\Libresign\Helper\ConfigureCheckHelper; use OCA\Libresign\ResponseDefinitions; +use OCA\Libresign\Service\CertificatePolicyService; use OCA\Libresign\Service\Install\ConfigureCheckService; use OCA\Libresign\Service\Install\InstallService; use OCA\Libresign\Service\SignatureBackgroundService; @@ -31,6 +32,7 @@ use OCP\IL10N; use OCP\IRequest; use OCP\ISession; +use UnexpectedValueException; /** * @psalm-import-type LibresignEngineHandler from ResponseDefinitions @@ -51,6 +53,7 @@ public function __construct( private IL10N $l10n, protected ISession $session, private SignatureBackgroundService $signatureBackgroundService, + private CertificatePolicyService $certificatePolicyService, ) { parent::__construct(Application::APP_ID, $request); $this->eventSource = $this->eventSourceFactory->create(); @@ -525,4 +528,101 @@ public function signerName( ); } } + + /** + * Update certificate policy of this instance + * + * @return DataResponse|DataResponse + * + * 200: OK + * 422: Not found + */ + #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/admin/certificate-policy', requirements: ['apiVersion' => '(v1)'])] + public function saveCertificatePolicy(): DataResponse { + $pdf = $this->request->getUploadedFile('pdf'); + $phpFileUploadErrors = [ + UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'), + UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'), + UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'), + UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'), + UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'), + UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'), + UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'), + UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'), + ]; + if (empty($pdf)) { + $error = $this->l10n->t('No file uploaded'); + } elseif (!empty($pdf) && array_key_exists('error', $pdf) && $pdf['error'] !== UPLOAD_ERR_OK) { + $error = $phpFileUploadErrors[$pdf['error']]; + } + if ($error !== null) { + return new DataResponse( + [ + 'message' => $error, + 'status' => 'failure', + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + try { + $cps = $this->certificatePolicyService->updateFile($pdf['tmp_name']); + } catch (UnexpectedValueException $e) { + return new DataResponse( + [ + 'message' => $e->getMessage(), + 'status' => 'failure', + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + return new DataResponse( + [ + 'CPS' => $cps, + 'status' => 'success', + ] + ); + } + + /** + * Delete certificate policy of this instance + * + * @return DataResponse + * + * 200: OK + * 404: Not found + */ + #[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/admin/certificate-policy', requirements: ['apiVersion' => '(v1)'])] + public function deleteCertificatePolicy(): DataResponse { + $this->certificatePolicyService->deleteFile(); + return new DataResponse(); + } + + /** + * Update OID + * + * @param string $oid OID is a unique numeric identifier for certificate policies in digital certificates. + * @return DataResponse|DataResponse + * + * 200: OK + * 422: Validation error + */ + #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/admin/certificate-policy/oid', requirements: ['apiVersion' => '(v1)'])] + public function updateOID(string $oid): DataResponse { + try { + $this->certificatePolicyService->updateOid($oid); + return new DataResponse( + [ + 'status' => 'success', + ] + ); + } catch (\Exception $e) { + return new DataResponse( + [ + 'message' => $e->getMessage(), + 'status' => 'failure', + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + } } diff --git a/lib/Controller/CertificatePolicyController.php b/lib/Controller/CertificatePolicyController.php new file mode 100644 index 0000000000..dc21afa659 --- /dev/null +++ b/lib/Controller/CertificatePolicyController.php @@ -0,0 +1,55 @@ +|DataResponse, array{}> + * + * 200: OK + * 404: Not found + */ + #[PublicPage] + #[NoCSRFRequired] + #[AnonRateLimit(limit: 10, period: 60)] + #[FrontpageRoute(verb: 'GET', url: '/certificate-policy.pdf')] + public function getCertificatePolicy(): FileDisplayResponse|DataResponse { + try { + $file = $this->certificatePolicyService->getFile(); + return new FileDisplayResponse($file, Http::STATUS_OK, [ + 'Content-Disposition' => 'inline; filename="certificate-policy.pdf"', + 'Content-Type' => 'application/pdf', + ]); + } catch (NotFoundException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + } +} diff --git a/lib/Handler/CertificateEngine/AEngineHandler.php b/lib/Handler/CertificateEngine/AEngineHandler.php index 85eb75d932..035fc58aee 100644 --- a/lib/Handler/CertificateEngine/AEngineHandler.php +++ b/lib/Handler/CertificateEngine/AEngineHandler.php @@ -13,6 +13,7 @@ use OCA\Libresign\Exception\InvalidPasswordException; use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Helper\MagicGetterSetterTrait; +use OCA\Libresign\Service\CertificatePolicyService; use OCP\Files\AppData\IAppDataFactory; use OCP\Files\IAppData; use OCP\Files\SimpleFS\ISimpleFolder; @@ -71,6 +72,7 @@ public function __construct( protected IAppDataFactory $appDataFactory, protected IDateTimeFormatter $dateTimeFormatter, protected ITempManager $tempManager, + protected CertificatePolicyService $certificatePolicyService, ) { $this->appData = $appDataFactory->get('libresign'); } @@ -291,6 +293,12 @@ public function setConfigPath(string $configPath): IEngineHandler { if (!$configPath) { $this->appConfig->deleteKey(Application::APP_ID, 'config_path'); } else { + if (!is_dir($configPath)) { + mkdir( + directory: $configPath, + recursive: true, + ); + } $this->appConfig->setValueString(Application::APP_ID, 'config_path', $configPath); } $this->configPath = $configPath; @@ -341,6 +349,19 @@ public function configureCheck(): array { throw new \Exception('Necessary to implement configureCheck method'); } + private function getCertificatePolicy(): array { + $return = ['policySection' => []]; + $oid = $this->certificatePolicyService->getOid(); + $cps = $this->certificatePolicyService->getCps(); + if ($oid && $cps) { + $return['policySection'][] = [ + 'OID' => $oid, + 'CPS' => $cps, + ]; + } + return $return; + } + public function toArray(): array { $return = [ 'configPath' => $this->getConfigPath(), @@ -350,6 +371,10 @@ public function toArray(): array { 'names' => [], ], ]; + $return = array_merge( + $return, + $this->getCertificatePolicy(), + ); $names = $this->getNames(); foreach ($names as $name => $value) { $return['rootCert']['names'][] = [ diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index fd7937b0f7..5b929d3a84 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -16,6 +16,7 @@ use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Handler\CfsslServerHandler; use OCA\Libresign\Helper\ConfigureCheckHelper; +use OCA\Libresign\Service\CertificatePolicyService; use OCA\Libresign\Service\Install\InstallService; use OCP\Files\AppData\IAppDataFactory; use OCP\IAppConfig; @@ -37,7 +38,6 @@ class CfsslHandler extends AEngineHandler implements IEngineHandler { protected $client; protected $cfsslUri; private string $binary = ''; - private CfsslServerHandler $cfsslServerHandler; public function __construct( protected IConfig $config, @@ -46,10 +46,11 @@ public function __construct( protected IAppDataFactory $appDataFactory, protected IDateTimeFormatter $dateTimeFormatter, protected ITempManager $tempManager, + protected CfsslServerHandler $cfsslServerHandler, + protected CertificatePolicyService $certificatePolicyService, ) { - parent::__construct($config, $appConfig, $appDataFactory, $dateTimeFormatter, $tempManager); + parent::__construct($config, $appConfig, $appDataFactory, $dateTimeFormatter, $tempManager, $certificatePolicyService); - $this->cfsslServerHandler = new CfsslServerHandler(); $this->cfsslServerHandler->configCallback(fn () => $this->getConfigPath()); } diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 4b4288876f..7a7c82eef4 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -10,6 +10,7 @@ use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Helper\ConfigureCheckHelper; +use OCA\Libresign\Service\CertificatePolicyService; use OCP\Files\AppData\IAppDataFactory; use OCP\IAppConfig; use OCP\IConfig; @@ -30,8 +31,9 @@ public function __construct( protected IAppDataFactory $appDataFactory, protected IDateTimeFormatter $dateTimeFormatter, protected ITempManager $tempManager, + protected CertificatePolicyService $certificatePolicyService, ) { - parent::__construct($config, $appConfig, $appDataFactory, $dateTimeFormatter, $tempManager); + parent::__construct($config, $appConfig, $appDataFactory, $dateTimeFormatter, $tempManager, $certificatePolicyService); } public function generateRootCert( @@ -44,7 +46,8 @@ public function generateRootCert( ]); $csr = openssl_csr_new($this->getCsrNames(), $privateKey, ['digest_alg' => 'sha256']); - $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, ['digest_alg' => 'sha256']); + $options = $this->getRootCertOptions(); + $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, $options); openssl_csr_export($csr, $csrout); openssl_x509_export($x509, $certout); @@ -57,6 +60,18 @@ public function generateRootCert( return $pkeyout; } + private function getRootCertOptions(): array { + $this->generateRootCertConfig(); + $configPath = $this->getConfigPath(); + + $options = [ + 'digest_alg' => 'sha256', + 'config' => $configPath . DIRECTORY_SEPARATOR . 'openssl.cnf', + 'x509_extensions' => 'v3_ca', + ]; + return $options; + } + public function generateCertificate(): string { $configPath = $this->getConfigPath(); $rootCertificate = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); @@ -105,10 +120,17 @@ private function getFilenameToLeafCert(): string { 'subjectAltName' => $this->getSubjectAltNames(), 'authorityKeyIdentifier' => 'keyid', 'subjectKeyIdentifier' => 'hash', - // @todo Implement a feature to define this PDF at Administration Settings - // 'certificatePolicies' => ' CPS: http://url/with/policy/informations.pdf', - ] + ], ]; + $oid = $this->certificatePolicyService->getOid(); + $cps = $this->certificatePolicyService->getCps(); + if ($oid && $cps) { + $config['v3_req']['certificatePolicies'] = '@policy_section'; + $config['policy_section'] = [ + 'policyIdentifier' => $oid, + 'CPS.1' => $cps, + ]; + } if (empty($config['v3_req']['subjectAltName'])) { unset($config['v3_req']['subjectAltName']); } @@ -117,6 +139,34 @@ private function getFilenameToLeafCert(): string { return $temporaryFile; } + private function generateRootCertConfig(): void { + // More information about x509v3: https://www.openssl.org/docs/manmaster/man5/x509v3_config.html + $config = [ + 'v3_ca' => [ + 'basicConstraints' => 'critical, CA:TRUE', + 'keyUsage' => 'critical, digitalSignature, keyCertSign', + 'extendedKeyUsage' => 'clientAuth, emailProtection', + 'subjectAltName' => $this->getSubjectAltNames(), + 'authorityKeyIdentifier' => 'keyid', + 'subjectKeyIdentifier' => 'hash', + ], + ]; + $oid = $this->certificatePolicyService->getOid(); + $cps = $this->certificatePolicyService->getCps(); + if ($oid && $cps) { + $config['v3_ca']['certificatePolicies'] = '@policy_section'; + $config['policy_section'] = [ + 'policyIdentifier' => $oid, + 'CPS.1' => $cps, + ]; + } + if (empty($config['v3_ca']['subjectAltName'])) { + unset($config['v3_ca']['subjectAltName']); + } + $iniContent = $this->arrayToIni($config); + $this->saveFile('openssl.cnf', $iniContent); + } + private function arrayToIni(array $config) { $fileContent = ''; foreach ($config as $i => $v) { diff --git a/lib/Handler/CfsslServerHandler.php b/lib/Handler/CfsslServerHandler.php index e4dbe2fe6c..1be9899cd7 100644 --- a/lib/Handler/CfsslServerHandler.php +++ b/lib/Handler/CfsslServerHandler.php @@ -10,6 +10,7 @@ use Closure; use OCA\Libresign\Exception\LibresignException; +use OCA\Libresign\Service\CertificatePolicyService; class CfsslServerHandler { private string $csrServerFile = ''; @@ -17,6 +18,11 @@ class CfsslServerHandler { private string $configServerFileHash = ''; private Closure $getConfigPath; + public function __construct( + private CertificatePolicyService $certificatePolicyService, + ) { + } + /** * Create a callback to get config path not at the constructor because * getting at constructor, every time that the class is instantiated, will @@ -98,6 +104,19 @@ private function saveNewConfig(string $key, int $expirity): void { ], ], ]; + $oid = $this->certificatePolicyService->getOid(); + $cps = $this->certificatePolicyService->getCps(); + if ($oid && $cps) { + $config['signing']['profiles']['CA']['policies'][] = [ + 'id' => $oid, + 'qualifiers' => [ + [ + 'type' => 'id-qt-cps', + 'value' => $cps, + ], + ], + ]; + } $this->saveConfig($config); } diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 2a610b4df7..be08829590 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -81,9 +81,14 @@ * commonName: string, * names: LibresignRootCertificateName[], * } + * @psalm-type LibresignPolicySection = array{ + * OID: string, + * CPS: string, + * } * @psalm-type LibresignEngineHandler = array{ * configPath: string, * cfsslUri?: string, + * policySection: LibresignPolicySection[], * rootCert: LibresignRootCertificate, * } * @psalm-type LibresignCetificateDataGenerated = LibresignEngineHandler&array{ diff --git a/lib/Service/CertificatePolicyService.php b/lib/Service/CertificatePolicyService.php new file mode 100644 index 0000000000..dd0feda6ac --- /dev/null +++ b/lib/Service/CertificatePolicyService.php @@ -0,0 +1,88 @@ +appData->getFolder('/'); + try { + $rootFolder->newFile('certificate-policy.pdf', $blob); + } catch (NotFoundException $e) { + $file = $rootFolder->getFile('certificate-policy.pdf'); + $file->putContent($blob); + } + return $this->urlGenerator->linkToRouteAbsolute('libresign.CertificatePolicy.getCertificatePolicy'); + } + + public function getFile(): ISimpleFile { + return $this->appData->getFolder('/')->getFile('certificate-policy.pdf'); + } + + public function deleteFile(): void { + try { + $this->appData->getFolder('/')->getFile('certificate-policy.pdf')->delete(); + } catch (NotFoundException $e) { + } + } + + public function updateOid(string $oid): string { + if (empty($oid)) { + $this->appConfig->deleteKey(Application::APP_ID, 'certificate_policies_oid'); + return ''; + } + $regex = '(0|1|2)(\.\d+)+'; + preg_match('/^' . $regex . '$/', $oid, $matches); + if (empty($matches)) { + // TRANSLATORS This message appears when an invalid Object + // Identifier (OID) is entered. It informs the admin that the input + // must follow a specific numeric pattern used in digital + // certificate policies. + throw new LibresignException($this->l10n->t('Invalid OID format. Expected pattern: %s', [$regex])); + } + $this->appConfig->setValueString(Application::APP_ID, 'certificate_policies_oid', $oid); + return $oid; + } + + public function getOid(): string { + return $this->appConfig->getValueString(Application::APP_ID, 'certificate_policies_oid', ''); + } + + public function getCps(): string { + try { + $this->getFile(); + } catch (NotFoundException $e) { + return ''; + } + return $this->urlGenerator->linkToRouteAbsolute('libresign.CertificatePolicy.getCertificatePolicy'); + } +} diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 1465473126..648f6c10bc 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -11,6 +11,7 @@ use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory; +use OCA\Libresign\Service\CertificatePolicyService; use OCA\Libresign\Service\IdentifyMethodService; use OCA\Libresign\Service\SignatureBackgroundService; use OCA\Libresign\Service\SignatureTextService; @@ -25,6 +26,7 @@ public function __construct( private IInitialState $initialState, private IdentifyMethodService $identifyMethodService, private CertificateEngineFactory $certificateEngineFactory, + private CertificatePolicyService $certificatePolicyService, private IAppConfig $appConfig, private SignatureTextService $signatureTextService, private SignatureBackgroundService $signatureBackgroundService, @@ -40,6 +42,8 @@ public function getForm(): TemplateResponse { $this->initialState->provideInitialState('signature_text_template_error', $e->getMessage()); } $this->initialState->provideInitialState('certificate_engine', $this->certificateEngineFactory->getEngine()->getName()); + $this->initialState->provideInitialState('certificate_policies_oid', $this->certificatePolicyService->getOid()); + $this->initialState->provideInitialState('certificate_policies_cps', $this->certificatePolicyService->getCps()); $this->initialState->provideInitialState('config_path', $this->appConfig->getValueString(Application::APP_ID, 'config_path')); $this->initialState->provideInitialState('default_signature_font_size', SignatureTextService::SIGNATURE_DEFAULT_FONT_SIZE); $this->initialState->provideInitialState('default_signature_height', SignatureTextService::DEFAULT_SIGNATURE_HEIGHT); diff --git a/openapi-administration.json b/openapi-administration.json index 8fa25f8719..9c947225b5 100644 --- a/openapi-administration.json +++ b/openapi-administration.json @@ -131,6 +131,7 @@ "type": "object", "required": [ "configPath", + "policySection", "rootCert" ], "properties": { @@ -140,6 +141,12 @@ "cfsslUri": { "type": "string" }, + "policySection": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PolicySection" + } + }, "rootCert": { "$ref": "#/components/schemas/RootCertificate" } @@ -169,6 +176,21 @@ } } }, + "PolicySection": { + "type": "object", + "required": [ + "OID", + "CPS" + ], + "properties": { + "OID": { + "type": "string" + }, + "CPS": { + "type": "string" + } + } + }, "PublicCapabilities": { "type": "object", "properties": { @@ -1737,6 +1759,361 @@ } } }, + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy": { + "post": { + "operationId": "admin-save-certificate-policy", + "summary": "Update certificate policy of this instance", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "CPS" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "success" + ] + }, + "CPS": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "message" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "failure" + ] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "admin-delete-certificate-policy", + "summary": "Delete certificate policy of this instance", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy/oid": { + "post": { + "operationId": "admin-updateoid", + "summary": "Update OID", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "oid" + ], + "properties": { + "oid": { + "type": "string", + "description": "OID is a unique numeric identifier for certificate policies in digital certificates." + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "success" + ] + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "message" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "failure" + ] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/libresign/api/{apiVersion}/setting/has-root-cert": { "get": { "operationId": "setting-has-root-cert", diff --git a/openapi-full.json b/openapi-full.json index b633460629..c0b81ec1f3 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -237,6 +237,7 @@ "type": "object", "required": [ "configPath", + "policySection", "rootCert" ], "properties": { @@ -246,6 +247,12 @@ "cfsslUri": { "type": "string" }, + "policySection": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PolicySection" + } + }, "rootCert": { "$ref": "#/components/schemas/RootCertificate" } @@ -609,6 +616,21 @@ } } }, + "PolicySection": { + "type": "object", + "required": [ + "OID", + "CPS" + ], + "properties": { + "OID": { + "type": "string" + }, + "CPS": { + "type": "string" + } + } + }, "PublicCapabilities": { "type": "object", "properties": { @@ -1057,6 +1079,55 @@ } }, "paths": { + "/index.php/apps/libresign/certificate-policy.pdf": { + "get": { + "operationId": "certificate_policy-get-certificate-policy", + "summary": "Certificate policy of this instance", + "tags": [ + "certificate_policy" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Content-Disposition": { + "schema": { + "type": "string", + "enum": [ + "inline; filename=\"certificate-policy.pdf\"" + ] + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, "/index.php/apps/libresign/develop/pdf": { "get": { "operationId": "develop-pdf", @@ -9473,6 +9544,361 @@ } } }, + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy": { + "post": { + "operationId": "admin-save-certificate-policy", + "summary": "Update certificate policy of this instance", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "CPS" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "success" + ] + }, + "CPS": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "message" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "failure" + ] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "admin-delete-certificate-policy", + "summary": "Delete certificate policy of this instance", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy/oid": { + "post": { + "operationId": "admin-updateoid", + "summary": "Update OID", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "oid" + ], + "properties": { + "oid": { + "type": "string", + "description": "OID is a unique numeric identifier for certificate policies in digital certificates." + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "success" + ] + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "message" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "failure" + ] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/libresign/api/{apiVersion}/setting/has-root-cert": { "get": { "operationId": "setting-has-root-cert", diff --git a/openapi.json b/openapi.json index 27cf6934a1..57c35fc92d 100644 --- a/openapi.json +++ b/openapi.json @@ -961,6 +961,55 @@ } }, "paths": { + "/index.php/apps/libresign/certificate-policy.pdf": { + "get": { + "operationId": "certificate_policy-get-certificate-policy", + "summary": "Certificate policy of this instance", + "tags": [ + "certificate_policy" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Content-Disposition": { + "schema": { + "type": "string", + "enum": [ + "inline; filename=\"certificate-policy.pdf\"" + ] + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, "/index.php/apps/libresign/develop/pdf": { "get": { "operationId": "develop-pdf", diff --git a/src/types/openapi/openapi-administration.ts b/src/types/openapi/openapi-administration.ts index 46aa6a517c..75dbbacd4c 100644 --- a/src/types/openapi/openapi-administration.ts +++ b/src/types/openapi/openapi-administration.ts @@ -203,6 +203,50 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update certificate policy of this instance + * @description This endpoint requires admin access + */ + post: operations["admin-save-certificate-policy"]; + /** + * Delete certificate policy of this instance + * @description This endpoint requires admin access + */ + delete: operations["admin-delete-certificate-policy"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy/oid": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update OID + * @description This endpoint requires admin access + */ + post: operations["admin-updateoid"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/libresign/api/{apiVersion}/setting/has-root-cert": { parameters: { query?: never; @@ -258,6 +302,7 @@ export type components = { EngineHandler: { configPath: string; cfsslUri?: string; + policySection: components["schemas"]["PolicySection"][]; rootCert: components["schemas"]["RootCertificate"]; }; OCSMeta: { @@ -267,6 +312,10 @@ export type components = { totalitems?: string; itemsperpage?: string; }; + PolicySection: { + OID: string; + CPS: string; + }; PublicCapabilities: { libresign?: components["schemas"]["Capabilities"]; }; @@ -905,6 +954,146 @@ export interface operations { }; }; }; + "admin-save-certificate-policy": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "success"; + CPS: string; + }; + }; + }; + }; + }; + /** @description Not found */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "failure"; + message: string; + }; + }; + }; + }; + }; + }; + }; + "admin-delete-certificate-policy": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: Record; + }; + }; + }; + }; + }; + }; + "admin-updateoid": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description OID is a unique numeric identifier for certificate policies in digital certificates. */ + oid: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "success"; + }; + }; + }; + }; + }; + /** @description Validation error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "failure"; + message: string; + }; + }; + }; + }; + }; + }; + }; "setting-has-root-cert": { parameters: { query?: never; diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index c2a22e3ede..72a2e4c3df 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -4,6 +4,23 @@ */ export type paths = { + "/index.php/apps/libresign/certificate-policy.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Certificate policy of this instance */ + get: operations["certificate_policy-get-certificate-policy"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/index.php/apps/libresign/develop/pdf": { parameters: { query?: never; @@ -1113,6 +1130,50 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update certificate policy of this instance + * @description This endpoint requires admin access + */ + post: operations["admin-save-certificate-policy"]; + /** + * Delete certificate policy of this instance + * @description This endpoint requires admin access + */ + delete: operations["admin-delete-certificate-policy"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy/oid": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update OID + * @description This endpoint requires admin access + */ + post: operations["admin-updateoid"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/libresign/api/{apiVersion}/setting/has-root-cert": { parameters: { query?: never; @@ -1203,6 +1264,7 @@ export type components = { EngineHandler: { configPath: string; cfsslUri?: string; + policySection: components["schemas"]["PolicySection"][]; rootCert: components["schemas"]["RootCertificate"]; }; File: { @@ -1307,6 +1369,10 @@ export type components = { last: string | null; first: string | null; }; + PolicySection: { + OID: string; + CPS: string; + }; PublicCapabilities: { libresign?: components["schemas"]["Capabilities"]; }; @@ -1442,6 +1508,36 @@ export type components = { }; export type $defs = Record; export interface operations { + "certificate_policy-get-certificate-policy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "Content-Disposition"?: "inline; filename=\"certificate-policy.pdf\""; + [name: string]: unknown; + }; + content: { + "application/pdf": string; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; "develop-pdf": { parameters: { query?: never; @@ -4903,6 +4999,146 @@ export interface operations { }; }; }; + "admin-save-certificate-policy": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "success"; + CPS: string; + }; + }; + }; + }; + }; + /** @description Not found */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "failure"; + message: string; + }; + }; + }; + }; + }; + }; + }; + "admin-delete-certificate-policy": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: Record; + }; + }; + }; + }; + }; + }; + "admin-updateoid": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description OID is a unique numeric identifier for certificate policies in digital certificates. */ + oid: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "success"; + }; + }; + }; + }; + }; + /** @description Validation error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "failure"; + message: string; + }; + }; + }; + }; + }; + }; + }; "setting-has-root-cert": { parameters: { query?: never; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 423666b382..8192c1a674 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -4,6 +4,23 @@ */ export type paths = { + "/index.php/apps/libresign/certificate-policy.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Certificate policy of this instance */ + get: operations["certificate_policy-get-certificate-policy"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/index.php/apps/libresign/develop/pdf": { parameters: { query?: never; @@ -1200,6 +1217,36 @@ export type components = { }; export type $defs = Record; export interface operations { + "certificate_policy-get-certificate-policy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "Content-Disposition"?: "inline; filename=\"certificate-policy.pdf\""; + [name: string]: unknown; + }; + content: { + "application/pdf": string; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; "develop-pdf": { parameters: { query?: never; diff --git a/src/views/Settings/CertificatePolicy.vue b/src/views/Settings/CertificatePolicy.vue new file mode 100644 index 0000000000..bc11180ce4 --- /dev/null +++ b/src/views/Settings/CertificatePolicy.vue @@ -0,0 +1,199 @@ + + + + + + + diff --git a/src/views/Settings/RootCertificateCfssl.vue b/src/views/Settings/RootCertificateCfssl.vue index df47798eed..49d417a71d 100644 --- a/src/views/Settings/RootCertificateCfssl.vue +++ b/src/views/Settings/RootCertificateCfssl.vue @@ -22,6 +22,14 @@ {{ t('libresign', 'CFSSL API URI') }} {{ certificate.cfsslUri }} + + OID + {{ OID }} + + + CPS + {{ CPS }} + {{ t('libresign', 'Config path') }} {{ certificate.configPath }} @@ -61,7 +69,17 @@
- + {{ t('libresign', 'Include certificate policy') }} + +
+ +
+ {{ t('libresign', 'Define custom values to use {engine}', {engine: 'CFSSL'}) }} @@ -83,7 +101,7 @@ :placeholder="t('libresign', 'Not mandatory, don\'t fill to use default value.')" :disabled="formDisabled" />
- {{ submitLabel }} @@ -106,6 +124,7 @@ import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection' import NcTextField from '@nextcloud/vue/components/NcTextField' import CertificateCustonOptions from './CertificateCustonOptions.vue' +import CertificatePolicy from './CertificatePolicy.vue' import { selectCustonOption } from '../../helpers/certification.js' import logger from '../../logger.js' @@ -120,12 +139,15 @@ export default { NcButton, NcTextField, CertificateCustonOptions, + CertificatePolicy, }, setup() { const configureCheckStore = useConfigureCheckStore() return { configureCheckStore } }, data() { + const OID = loadState('libresign', 'certificate_policies_oid') + const CPS = loadState('libresign', 'certificate_policies_cps') return { isThisEngine: loadState('libresign', 'certificate_engine') === 'cfssl', modal: false, @@ -144,9 +166,25 @@ export default { description: t('libresign', 'To generate new signatures, you must first generate the root certificate.'), submitLabel: t('libresign', 'Generate root certificate'), formDisabled: false, + OID, + CPS, + toggleCertificatePolicy: !!(OID || CPS), + certificatePolicyValid: !!OID && !!CPS, } }, computed: { + includeCertificatePolicy() { + return this.toggleCertificatePolicy || this.CPS || this.OID + }, + canSave() { + if (this.formDisabled) { + return false + } + if (!this.toggleCertificatePolicy) { + return true + } + return this.certificatePolicyValid + }, configureOk() { return this.configureCheckStore.isConfigureOk('cfssl') }, @@ -168,6 +206,9 @@ export default { }, methods: { + handleCertificatePolicyValid(isValid) { + this.certificatePolicyValid = isValid + }, updateNames(names) { this.certificate.rootCert.names = names }, diff --git a/src/views/Settings/RootCertificateOpenSsl.vue b/src/views/Settings/RootCertificateOpenSsl.vue index 6e095de687..a55d7be583 100644 --- a/src/views/Settings/RootCertificateOpenSsl.vue +++ b/src/views/Settings/RootCertificateOpenSsl.vue @@ -18,6 +18,14 @@ {{ getLabelFromId(customName.id) }} ({{ customName.id }}) {{ customName.value }} + + OID + {{ OID }} + + + CPS + {{ CPS }} + {{ t('libresign', 'Config path') }} {{ certificate.configPath }} @@ -57,7 +65,17 @@
- + {{ t('libresign', 'Include certificate policy') }} + +
+ +
+ {{ t('libresign', 'Define custom values to use {engine}', {engine: 'OpenSSL'}) }} @@ -71,7 +89,7 @@ :placeholder="t('libresign', 'Not mandatory, don\'t fill to use default value.')" :disabled="formDisabled" />
- {{ submitLabel }} @@ -95,6 +113,7 @@ import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection' import NcTextField from '@nextcloud/vue/components/NcTextField' import CertificateCustonOptions from './CertificateCustonOptions.vue' +import CertificatePolicy from './CertificatePolicy.vue' import { selectCustonOption } from '../../helpers/certification.js' import logger from '../../logger.js' @@ -109,12 +128,15 @@ export default { NcButton, NcTextField, CertificateCustonOptions, + CertificatePolicy, }, setup() { const configureCheckStore = useConfigureCheckStore() return { configureCheckStore } }, data() { + const OID = loadState('libresign', 'certificate_policies_oid') + const CPS = loadState('libresign', 'certificate_policies_cps') return { isThisEngine: loadState('libresign', 'certificate_engine') === 'openssl', modal: false, @@ -131,9 +153,25 @@ export default { description: t('libresign', 'To generate new signatures, you must first generate the root certificate.'), submitLabel: t('libresign', 'Generate root certificate'), formDisabled: false, + OID, + CPS, + toggleCertificatePolicy: !!(OID || CPS), + certificatePolicyValid: !!OID && !!CPS, } }, computed: { + includeCertificatePolicy() { + return this.toggleCertificatePolicy || this.CPS || this.OID + }, + canSave() { + if (this.formDisabled) { + return false + } + if (!this.toggleCertificatePolicy) { + return true + } + return this.certificatePolicyValid + }, configureOk() { return this.configureCheckStore.isConfigureOk('openssl') }, @@ -154,6 +192,9 @@ export default { unsubscribe('libresign:update:certificateToSave') }, methods: { + handleCertificatePolicyValid(isValid) { + this.certificatePolicyValid = isValid + }, updateNames(names) { this.certificate.rootCert.names = names }, diff --git a/src/views/Settings/Settings.vue b/src/views/Settings/Settings.vue index 2fa17d4bab..f1e65a7ac1 100644 --- a/src/views/Settings/Settings.vue +++ b/src/views/Settings/Settings.vue @@ -45,22 +45,22 @@ import Validation from './Validation.vue' export default { name: 'Settings', components: { - NcSettingsSection, + AllowedGroups, CertificateEngine, - DownloadBinaries, + CollectMetadata, ConfigureCheck, - RootCertificateCfssl, - RootCertificateOpenSsl, - IdentificationFactors, + DefaultUserFolder, + DownloadBinaries, ExpirationRules, - Validation, - AllowedGroups, - LegalInformation, IdentificationDocuments, - CollectMetadata, - SignatureStamp, + IdentificationFactors, + LegalInformation, + NcSettingsSection, + RootCertificateCfssl, + RootCertificateOpenSsl, SignatureHashAlgorithm, - DefaultUserFolder, + SignatureStamp, + Validation, }, data() { return { diff --git a/tests/Unit/Handler/CertificateEngine/OpenSslHandlerTest.php b/tests/Unit/Handler/CertificateEngine/OpenSslHandlerTest.php index fcb2f4b21b..658ec822d4 100644 --- a/tests/Unit/Handler/CertificateEngine/OpenSslHandlerTest.php +++ b/tests/Unit/Handler/CertificateEngine/OpenSslHandlerTest.php @@ -6,10 +6,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -use bovigo\vfs\vfsStream; use OCA\Libresign\Exception\EmptyCertificateException; use OCA\Libresign\Exception\InvalidPasswordException; use OCA\Libresign\Handler\CertificateEngine\OpenSslHandler; +use OCA\Libresign\Service\CertificatePolicyService; use OCP\Files\AppData\IAppDataFactory; use OCP\IAppConfig; use OCP\IConfig; @@ -23,16 +23,16 @@ final class OpenSslHandlerTest extends \OCA\Libresign\Tests\Unit\TestCase { private IDateTimeFormatter $dateTimeFormatter; private ITempManager $tempManager; private OpenSslHandler $openSslHandler; + protected CertificatePolicyService $certificatePolicyService; + private string $tempDir; public function setUp(): void { $this->config = \OCP\Server::get(IConfig::class); $this->appConfig = \OCP\Server::get(IAppConfig::class); $this->appDataFactory = \OCP\Server::get(IAppDataFactory::class); $this->dateTimeFormatter = \OCP\Server::get(IDateTimeFormatter::class); $this->tempManager = \OCP\Server::get(ITempManager::class); - - // The storage can't be modified when create a new instance to - // don't lost the root cert - vfsStream::setup('certificate'); + $this->certificatePolicyService = \OCP\Server::get(certificatePolicyService::class); + $this->tempDir = $this->tempManager->getTemporaryFolder('certificate'); } private function getInstance(): OpenSslHandler { @@ -42,8 +42,9 @@ private function getInstance(): OpenSslHandler { $this->appDataFactory, $this->dateTimeFormatter, $this->tempManager, + $this->certificatePolicyService, ); - $this->openSslHandler->setConfigPath('vfs://certificate/'); + $this->openSslHandler->setConfigPath($this->tempDir); return $this->openSslHandler; } diff --git a/tests/Unit/Handler/SignEngine/JSignPdfHandlerTest.php b/tests/Unit/Handler/SignEngine/JSignPdfHandlerTest.php index 995c71d8b9..03c6603eba 100644 --- a/tests/Unit/Handler/SignEngine/JSignPdfHandlerTest.php +++ b/tests/Unit/Handler/SignEngine/JSignPdfHandlerTest.php @@ -8,7 +8,6 @@ namespace OCA\Libresign\Tests\Unit\Service; -use bovigo\vfs\vfsStream; use Jeidison\JSignPDF\JSignPDF; use Jeidison\JSignPDF\Sign\JSignParam; use OCA\Libresign\AppInfo\Application; @@ -42,14 +41,11 @@ public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); self::$certificateEngineFactory = \OCP\Server::get(CertificateEngineFactory::class); - // The storage can't be modified when create a new instance to - // don't lost the root cert - vfsStream::setup('certificate'); $appConfig = self::getMockAppConfig(); $appConfig->setValueString(Application::APP_ID, 'certificate_engine', 'openssl'); $certificateEngine = self::$certificateEngineFactory->getEngine(); $certificateEngine - ->setConfigPath('vfs://certificate/') + ->setConfigPath(\OCP\Server::get(ITempManager::class)->getTemporaryFolder('certificate')) ->generateRootCert('', []); self::$certificateContent = $certificateEngine diff --git a/tests/Unit/Service/CertificatePolicyServiceTest.php b/tests/Unit/Service/CertificatePolicyServiceTest.php new file mode 100644 index 0000000000..9ce7120ebf --- /dev/null +++ b/tests/Unit/Service/CertificatePolicyServiceTest.php @@ -0,0 +1,178 @@ +appData = $this->createMock(IAppData::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->appConfig = $this->getMockAppConfig(); + $this->l10n = \OCP\Server::get(IL10NFactory::class)->get(Application::APP_ID); + } + + private function getService(): CertificatePolicyService { + return new CertificatePolicyService( + $this->appData, + $this->urlGenerator, + $this->appConfig, + $this->l10n + ); + } + + #[DataProvider('providerUpdateOidWithValidValue')] + public function testUpdateOidWithValidValue(string $oid): void { + $result = $this->getService()->updateOid($oid); + $this->assertEquals($oid, $result); + } + + public static function providerUpdateOidWithValidValue(): array { + return [ + ['1.2.3.4'], + ['2.5.4.10'], + ['0.9.2342.19200300.100.1.1'], + [''], + ]; + } + + #[DataProvider('providerUpdateOidWithInvalidValue')] + public function testUpdateOidWithInvalidValue(string $oid): void { + $this->expectException(LibresignException::class); + $this->getService()->updateOid($oid); + } + + public static function providerUpdateOidWithInvalidValue(): array { + return [ + ['1.2..3'], + ['3.2.1'], + ['1'], + ]; + } + + public function testUpdateOid(): void { + $service = $this->getService(); + + $result = $service->updateOid('1.2.3'); + $this->assertEquals('1.2.3', $result); + $current = $this->appConfig->getValueString('libresign', 'certificate_policies_oid'); + $this->assertEquals('1.2.3', $current); + $this->assertEquals('1.2.3', $service->getOid()); + + $result = $service->updateOid(''); + $this->assertEquals('', $result); + $this->assertEquals('', $service->getOid()); + + $condition = $this->appConfig->hasKey('libresign', 'certificate_policies_oid'); + $this->assertFalse($condition); + } + + #[DataProvider('providerGetCps')] + public function testGetCps(bool $fileExists, string $expected): void { + $folder = $this->createMock(ISimpleFolder::class); + + if ($fileExists) { + $file = $this->createMock(ISimpleFile::class); + $folder->method('getFile')->willReturn($file); + $this->urlGenerator->method('linkToRouteAbsolute')->willReturn($expected); + } else { + $folder->method('getFile')->willThrowException(new NotFoundException()); + } + + $this->appData->method('getFolder')->willReturn($folder); + $service = $this->getService(); + $this->assertSame($expected, $service->getCps()); + } + + public static function providerGetCps(): array { + return [ + 'file exists' => [true, 'https://example.coop/cps'], + 'file not found' => [false, ''], + ]; + } + + #[DataProvider('providerGetFile')] + public function testGetFile(bool $exists): void { + $folder = $this->createMock(ISimpleFolder::class); + $this->appData->method('getFolder')->willReturn($folder); + $service = $this->getService(); + + if ($exists) { + $file = $this->createMock(ISimpleFile::class); + $folder->method('getFile')->with('certificate-policy.pdf')->willReturn($file); + $this->assertSame($file, $service->getFile()); + } else { + $folder->method('getFile')->willThrowException(new NotFoundException()); + $this->expectException(NotFoundException::class); + $service->getFile(); + } + } + + public static function providerGetFile(): array { + return [ + 'success' => [true], + 'not found' => [false], + ]; + } + + #[DataProvider('providerUpdateFileWithValidPdf')] + public function testUpdateFileWithValidPdf(string $pdfContent): void { + vfsStream::setup('uploaded'); + $pdfPath = 'vfs://uploaded/test.pdf'; + file_put_contents($pdfPath, $pdfContent); + + $this->urlGenerator->method('linkToRouteAbsolute')->willReturn('https://example.coop/cps'); + + $service = $this->getService(); + $result = $service->updateFile($pdfPath); + $this->assertSame('https://example.coop/cps', $result); + } + + public static function providerUpdateFileWithValidPdf(): array { + return [ + ['%PDF-1.0' . "\n" . '%LibreSign Test File' . "\n", '1.0'], + ['%PDF-1.1' . "\n" . '%LibreSign Test File' . "\n", '1.1'], + ['%PDF-1.2' . "\n" . '%LibreSign Test File' . "\n", '1.2'], + ['%PDF-1.3' . "\n" . '%LibreSign Test File' . "\n", '1.3'], + ['%PDF-1.4' . "\n" . '%LibreSign Test File' . "\n", '1.4'], + ['%PDF-1.5' . "\n" . '%LibreSign Test File' . "\n", '1.5'], + ['%PDF-1.6' . "\n" . '%LibreSign Test File' . "\n", '1.6'], + ['%PDF-1.7' . "\n" . '%LibreSign Test File' . "\n", '1.7'], + ['%PDF-2.0' . "\n" . '%LibreSign Test File' . "\n", '2.0'], + ]; + } + + public function testUpdateFileWithInvalidType(): void { + $tmpFile = tempnam(sys_get_temp_dir(), 'txt'); + file_put_contents($tmpFile, 'just text'); + + $service = $this->getService(); + $this->expectException(\Exception::class); + $service->updateFile($tmpFile); + } +} diff --git a/tests/Unit/Settings/AdminTest.php b/tests/Unit/Settings/AdminTest.php index 86595c0df3..9b3121042f 100644 --- a/tests/Unit/Settings/AdminTest.php +++ b/tests/Unit/Settings/AdminTest.php @@ -10,6 +10,7 @@ use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory; +use OCA\Libresign\Service\CertificatePolicyService; use OCA\Libresign\Service\IdentifyMethodService; use OCA\Libresign\Service\SignatureBackgroundService; use OCA\Libresign\Service\SignatureTextService; @@ -26,6 +27,7 @@ final class AdminTest extends \OCA\Libresign\Tests\Unit\TestCase { private IInitialState&MockObject $initialState; private IdentifyMethodService&MockObject $identifyMethodService; private CertificateEngineFactory&MockObject $certificateEngineFactory; + private CertificatePolicyService&MockObject $certificatePolicyService; private IAppConfig&MockObject $appConfig; private SignatureTextService&MockObject $signatureTextService; private SignatureBackgroundService&MockObject $signatureBackgroundService; @@ -33,6 +35,7 @@ public function setUp(): void { $this->initialState = $this->createMock(IInitialState::class); $this->identifyMethodService = $this->createMock(IdentifyMethodService::class); $this->certificateEngineFactory = $this->createMock(CertificateEngineFactory::class); + $this->certificatePolicyService = $this->createMock(CertificatePolicyService::class); $this->appConfig = $this->createMock(IAppConfig::class); $this->signatureTextService = $this->createMock(SignatureTextService::class); $this->signatureBackgroundService = $this->createMock(SignatureBackgroundService::class); @@ -40,6 +43,7 @@ public function setUp(): void { $this->initialState, $this->identifyMethodService, $this->certificateEngineFactory, + $this->certificatePolicyService, $this->appConfig, $this->signatureTextService, $this->signatureBackgroundService, diff --git a/tests/lib/AppConfigOverwrite.php b/tests/lib/AppConfigOverwrite.php index ed917b5c50..c7c5673c02 100644 --- a/tests/lib/AppConfigOverwrite.php +++ b/tests/lib/AppConfigOverwrite.php @@ -94,4 +94,8 @@ public function setValueString(string $app, string $key, string $value, bool $la $this->overWrite[$app][$key] = $value; return true; } + + public function deleteKey(string $app, string $key): void { + unset($this->overWrite[$app][$key]); + } }