diff --git a/README.md b/README.md index a025472eb..111dcc215 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,19 @@ Nextcloud app to sign PDF documents ## Setup +### Java and JSignPDF + +Add the follow to Nextcloud PHP container Dockerfile + +```Dockerfile +# Install Java and JsignPDF +RUN mkdir -p /usr/share/man/man1 +RUN apt-get install -y default-jre +RUN curl -OL https://sourceforge.net/projects/jsignpdf/files/stable/JSignPdf%201.6.4/JSignPdf-1.6.4.zip \ + && unzip JSignPdf-1.6.4.zip -d /opt \ + && rm JSignPdf-1.6.4.zip +``` + ### With CFSS server Up a cfssl server using this code: diff --git a/appinfo/routes.php b/appinfo/routes.php index 047fe18fc..cf354732e 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -7,6 +7,7 @@ 'verb' => 'OPTIONS', 'requirements' => ['path' => '.+'], ], ['name' => 'webhook#register', 'url' => '/api/0.1/webhook/register', 'verb' => 'POST'], ['name' => 'libresign#sign', 'url' => '/api/0.1/sign', 'verb' => 'POST'], + ['name' => 'libresign#signUsingUuid', 'url' => '/api/0.1/sign/{uuid}', 'verb' => 'POST'], ['name' => 'account#createToSign', 'url' => '/api/0.1/account/create/{uuid}', 'verb' => 'POST'], ['name' => 'signature#generate', 'url' => '/api/0.1/signature/generate', 'verb' => 'POST'], ['name' => 'signature#hasRootCert', 'url' => '/api/0.1/signature/has-root-cert', 'verb' => 'GET'], diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php index ac70e9134..8545a3d12 100644 --- a/lib/Controller/AccountController.php +++ b/lib/Controller/AccountController.php @@ -2,9 +2,9 @@ namespace OCA\Libresign\Controller; +use OC\Files\Filesystem; use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Db\FileMapper; -use OCA\Libresign\Db\FileUser; use OCA\Libresign\Helper\JSActions; use OCA\Libresign\Service\AccountService; use OCP\AppFramework\ApiController; @@ -42,6 +42,7 @@ public function __construct( * @NoAdminRequired * @CORS * @NoCSRFRequired + * @PublicPage * @return JSONResponse */ public function createToSign(string $uuid, string $email, string $password, string $signPassword) { @@ -56,6 +57,7 @@ public function createToSign(string $uuid, string $email, string $password, stri $this->account->createToSign($uuid, $email, $password, $signPassword); $fileUser = $this->account->getFileUserByUuid($uuid); $fileData = $this->fileMapper->getById($fileUser->getLibresignFileId()); + Filesystem::initMountPoints($fileData->getUserId()); $fileToSign = $this->root->getById($fileData->getFileId()); if (count($fileToSign) < 1) { return new JSONResponse( @@ -67,6 +69,15 @@ public function createToSign(string $uuid, string $email, string $password, stri ); } $fileToSign = $fileToSign[0]; + $data = [ + 'message' => $this->l10n->t('Success'), + 'action' => JSActions::ACTION_SIGN, + 'pdf' => [ + 'base64' => base64_encode($fileToSign->getContent()) + ], + 'filename' => $fileData->getName(), + 'description' => $fileData->getDescription() + ]; } catch (\Throwable $th) { return new JSONResponse( [ @@ -77,15 +88,7 @@ public function createToSign(string $uuid, string $email, string $password, stri ); } return new JSONResponse( - [ - 'message' => $this->l10n->t('Success'), - 'action' => JSActions::ACTION_SIGN, - 'pdf' => [ - 'base64' => $fileToSign->getContent() - ], - 'filename' => $fileData->getName(), - 'description' => $fileData->getDescription() - ], + $data, Http::STATUS_OK ); } diff --git a/lib/Controller/LibresignController.php b/lib/Controller/LibresignController.php index 8fe36bbdc..8ff6cb10c 100644 --- a/lib/Controller/LibresignController.php +++ b/lib/Controller/LibresignController.php @@ -2,10 +2,20 @@ namespace OCA\Libresign\Controller; +use OC\Files\Filesystem; use OCA\Libresign\AppInfo\Application; +use OCA\Libresign\Db\FileMapper; +use OCA\Libresign\Db\FileUserMapper; +use OCA\Libresign\Exception\LibresignException; +use OCA\Libresign\Handler\JLibresignHandler; +use OCA\Libresign\Helper\JSActions; +use OCA\Libresign\Service\AccountService; use OCA\Libresign\Service\LibresignService; use OCP\AppFramework\Controller; -use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\JSONResponse; +use OCP\Files\IRootFolder; +use OCP\IL10N; use OCP\IRequest; class LibresignController extends Controller { @@ -15,16 +25,40 @@ class LibresignController extends Controller { /** @var LibresignService */ private $service; + /** @var FileUserMapper */ + private $fileUserMapper; + /** @var FileMapper */ + private $fileMapper; + /** @var IRootFolder */ + private $root; + /** @var IL10N */ + private $l10n; + /** @var AccountService */ + private $account; + /** @var JLibresignHandler */ + private $libresignHandler; /** @var string */ private $userId; public function __construct( IRequest $request, LibresignService $service, + FileUserMapper $fileUserMapper, + FileMapper $fileMapper, + IRootFolder $root, + IL10N $l10n, + AccountService $account, + JLibresignHandler $libresignHandler, $userId ) { parent::__construct(Application::APP_ID, $request); $this->service = $service; + $this->fileUserMapper = $fileUserMapper; + $this->fileMapper = $fileMapper; + $this->root = $root; + $this->l10n = $l10n; + $this->account = $account; + $this->libresignHandler = $libresignHandler; $this->userId = $userId; } @@ -39,7 +73,7 @@ public function sign( string $outputFolderPath = null, string $certificatePath = null, string $password = null - ): DataResponse { + ): JSONResponse { try { $this->checkParams([ 'inputFilePath' => $inputFilePath, @@ -50,9 +84,65 @@ public function sign( $fileSigned = $this->service->sign($inputFilePath, $outputFolderPath, $certificatePath, $password); - return new DataResponse(['fileSigned' => $fileSigned->getInternalPath()]); + return new JSONResponse( + ['fileSigned' => $fileSigned->getInternalPath()], + HTTP::STATUS_OK + ); } catch (\Exception $exception) { - return $this->handleErrors($exception); + return new JSONResponse( + [ + 'action' => JSActions::ACTION_DO_NOTHING, + 'errors' => [$this->l10n->t($exception->getMessage())] + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + */ + public function signUsingUuid(string $uuid, string $password): JSONResponse { + try { + $fileUser = $this->fileUserMapper->getByUuidAndUserId($uuid, $this->userId); + $fileData = $this->fileMapper->getById($fileUser->getLibresignFileId()); + Filesystem::initMountPoints($fileData->getuserId()); + $inputFile = $this->root->getById($fileData->getFileId()); + if (count($inputFile) < 1) { + throw new LibresignException($this->l10n->t('File not found')); + } + $inputFile = $inputFile[0]; + $signedFilePath = preg_replace( + '/' . $inputFile->getExtension() . '$/', + $this->l10n->t('signed').'.'.$inputFile->getExtension(), + $inputFile->getPath() + ); + if ($this->root->nodeExists($signedFilePath)) { + $signedFile = $this->root->get($signedFilePath); + $inputFile = $signedFilePath; + } + $certificatePath = $this->account->getPfx($fileUser->getUserId()); + list(, $signedContent) = $this->libresignHandler->signExistingFile($inputFile, $certificatePath, $password); + if (!$signedFile) { + $signedFile = $this->root->newFile($signedFilePath); + } + $signedFile->putContent($signedContent); + return new JSONResponse( + [ + 'action' => JSActions::ACTION_DO_NOTHING, + 'message' => $this->l10n->t('File signed') + ], + Http::STATUS_OK + ); + } catch (\Throwable $th) { + return new JSONResponse( + [ + 'action' => JSActions::ACTION_DO_NOTHING, + 'errors' => [$this->l10n->t('Invalid data to sign file')] + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); } } } diff --git a/lib/Db/File.php b/lib/Db/File.php index 3f6d4c26b..b8db60eee 100644 --- a/lib/Db/File.php +++ b/lib/Db/File.php @@ -11,6 +11,8 @@ * @method int getId() * @method void setFileId(int $fileId) * @method int getFileId() + * @method void setUserId(int $userId) + * @method int getUserId() * @method void setCreatedAt(string $createdAt) * @method string getCreatedAt() * @method void setDescription(string $description) diff --git a/lib/Db/FileUserMapper.php b/lib/Db/FileUserMapper.php index 15543573a..24f8d50f1 100644 --- a/lib/Db/FileUserMapper.php +++ b/lib/Db/FileUserMapper.php @@ -53,4 +53,19 @@ public function getByUuid(string $uuid) { return $this->findEntity($qb); } + + public function getByUuidAndUserId(string $uuid, string $userId) { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('uuid', $qb->createNamedParameter($uuid, IQueryBuilder::PARAM_STR)) + ) + ->andWhere( + $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) + ); + + return $this->findEntity($qb); + } } diff --git a/lib/Handler/JLibresignHandler.php b/lib/Handler/JLibresignHandler.php index ef220ac91..bb5c621a8 100644 --- a/lib/Handler/JLibresignHandler.php +++ b/lib/Handler/JLibresignHandler.php @@ -17,6 +17,8 @@ public function signExistingFile( ->setPdf($inputFile->getContent()) ->setPassword($password) ->setTempPath('/tmp/') + ->setIsUseJavaInstalled(true) + ->setjSignPdfJarPath('/opt/jsignpdf-1.6.4/JSignPdf.jar') ; $jSignPdf = new JSignPDF($param); diff --git a/lib/Service/AccountService.php b/lib/Service/AccountService.php index 5a94c45ef..4a2e6f798 100644 --- a/lib/Service/AccountService.php +++ b/lib/Service/AccountService.php @@ -22,8 +22,6 @@ class AccountService { private $fileUserMapper; /** @var IUserManager */ protected $userManager; - /** @var SignatureService */ - private $signature; /** @var FolderService */ private $folder; /** @var IConfig */ @@ -32,12 +30,13 @@ class AccountService { private $newUserMail; /** @var CfsslHandler */ private $cfsslHandler; + /** @var string */ + private $pdfFilename = 'signature.pfx'; public function __construct( IL10N $l10n, FileUserMapper $fileUserMapper, IUserManager $userManager, - SignatureService $signature, FolderService $folder, IConfig $config, NewUserMailHelper $newUserMail, @@ -46,7 +45,6 @@ public function __construct( $this->l10n = $l10n; $this->fileUserMapper = $fileUserMapper; $this->userManager = $userManager; - $this->signature = $signature; $this->folder = $folder; $this->config = $config; $this->newUserMail = $newUserMail; @@ -127,17 +125,25 @@ public function createToSign($uuid, $uid, $password, $signPassword) { private function savePfx($uid, $content) { Filesystem::initMountPoints($uid); $folder = $this->folder->getFolderForUser(); - $filename = 'signature.pfx'; - if ($folder->nodeExists($filename)) { - $node = $folder->get($filename); + if ($folder->nodeExists($this->pdfFilename)) { + $node = $folder->get($this->pdfFilename); if (!$node instanceof File) { - throw new LibresignException("path {$filename} already exists and is not a file!", 400); + throw new LibresignException("path {$this->pdfFilename} already exists and is not a file!", 400); } $node->putContent($content); return $node; } - $file = $folder->newFile($filename); + $file = $folder->newFile($this->pdfFilename); $file->putContent($content); } + + public function getPfx($uid) { + Filesystem::initMountPoints($uid); + $folder = $this->folder->getFolderForUser(); + if (!$folder->nodeExists($this->pdfFilename)) { + throw new LibresignException("Signature file not found!", 400); + } + return $folder->get($this->pdfFilename); + } } diff --git a/lib/Service/FolderService.php b/lib/Service/FolderService.php index 0a6f728a1..6deff1b8a 100644 --- a/lib/Service/FolderService.php +++ b/lib/Service/FolderService.php @@ -20,7 +20,7 @@ public function __construct( IRootFolder $root, IConfig $config, IL10N $l10n, - string $userId + ?string $userId ) { $this->root = $root; $this->config = $config; diff --git a/tests/Unit/Controller/AccountControllerTest.php b/tests/Unit/Controller/AccountControllerTest.php index a070ba900..cf92cdd80 100644 --- a/tests/Unit/Controller/AccountControllerTest.php +++ b/tests/Unit/Controller/AccountControllerTest.php @@ -3,22 +3,23 @@ namespace OCA\Libresign\Tests\Unit\Controller; use OCA\Libresign\Controller\AccountController; -use OCA\Libresign\Db\File; +use OCA\Libresign\Db\File as LibresignFile; use OCA\Libresign\Db\FileMapper; use OCA\Libresign\Db\FileUser; -use OCA\Libresign\Db\FileUserMapper; use OCA\Libresign\Helper\JSActions; use OCA\Libresign\Service\AccountService; +use OCA\Libresign\Tests\lib\User\Dummy; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; -use OCP\Files\File as FileNode; +use OCP\Files\File; use OCP\Files\IRootFolder; -use OCP\Files\Node; use OCP\IL10N; use OCP\IRequest; use PHPUnit\Framework\TestCase; final class AccountControllerTest extends TestCase { + /** @var AccountController */ + private $controller; /** @var IRequest */ private $request; /** @var IL10N */ @@ -34,6 +35,9 @@ public function setUp(): void { parent::setUp(); $this->request = $this->createMock(IRequest::class); $this->l10n = $this->createMock(IL10N::class); + $this->l10n + ->method('t') + ->will($this->returnArgument(0)); $this->account = $this->createMock(AccountService::class); $this->fileMapper = $this->createMock(FileMapper::class); $this->root = $this->createMock(IRootFolder::class); @@ -47,9 +51,6 @@ public function setUp(): void { } public function testCreateSuccess() { - $this->l10n - ->method('t') - ->will($this->returnArgument(0)); $fileUser = $this->createMock(FileUser::class); $fileUser ->method('__call') @@ -58,21 +59,34 @@ public function testCreateSuccess() { $this->account ->method('getFileUserByUuid') ->will($this->returnValue($fileUser)); - $fileData = $this->createMock(File::class); + + $fileData = $this->createMock(LibresignFile::class); $fileData ->method('__call') ->withConsecutive( + [$this->equalTo('getUserId'), $this->anything()], [$this->equalTo('getFileId'), $this->anything()], [$this->equalTo('getName'), $this->anything()] ) ->will($this->returnValueMap([ + ['getUserId', [], ''], ['getFileId', [], 1], ['getName', [], 'Filename'] ])); $this->fileMapper ->method('getById') ->will($this->returnValue($fileData)); - $node = $this->createMock(FileNode::class); + + $userDummyBackend = $this->createMock(Dummy::class); + $userDummyBackend + ->method('userExists') + ->will($this->returnValue(true)); + \OC::$server->getUserManager()->registerBackend($userDummyBackend); + \OC::$server->getSession()->set('user_id', 1); + + $node = $this->createMock(File::class); + $node->method('getContent') + ->will($this->returnvalue('PDF')); $this->root ->method('getById') ->will($this->returnValue([$node])); @@ -84,7 +98,7 @@ public function testCreateSuccess() { 'filename' => 'Filename', 'description' => null, 'pdf' => [ - 'base64' => null + 'base64' => 'UERG' ] ], Http::STATUS_OK); $this->assertEquals($expected, $actual); diff --git a/tests/Unit/Controller/LibresignControllerTest.php b/tests/Unit/Controller/LibresignControllerTest.php index 224614734..a3a060b4f 100644 --- a/tests/Unit/Controller/LibresignControllerTest.php +++ b/tests/Unit/Controller/LibresignControllerTest.php @@ -4,7 +4,13 @@ use OC\Files\Node\File; use OCA\Libresign\Controller\LibresignController; +use OCA\Libresign\Db\FileMapper; +use OCA\Libresign\Db\FileUserMapper; +use OCA\Libresign\Handler\JLibresignHandler; +use OCA\Libresign\Service\AccountService; use OCA\Libresign\Service\LibresignService; +use OCP\Files\IRootFolder; +use OCP\IL10N; use OCP\IRequest; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -19,6 +25,12 @@ public function testSignFile() { $userId = 'john'; $request = $this->prophesize(IRequest::class); $service = $this->prophesize(LibresignService::class); + $fileUserMapper = $this->prophesize(FileUserMapper::class); + $fileMapper = $this->prophesize(FileMapper::class); + $root = $this->prophesize(IRootFolder::class); + $l10n = $this->prophesize(IL10N::class); + $accountService = $this->createMock(AccountService::class); + $libresignHandler = $this->createMock(JLibresignHandler::class); $file = $this->prophesize(File::class); $file->getInternalPath()->willReturn("/path/to/someFileSigned"); @@ -35,6 +47,12 @@ public function testSignFile() { $controller = new LibresignController( $request->reveal(), $service->reveal(), + $fileUserMapper->reveal(), + $fileMapper->reveal(), + $root->reveal(), + $l10n->reveal(), + $accountService, + $libresignHandler, $userId ); @@ -68,6 +86,15 @@ public function testSignFileFailParameterMissing( $userId = 'john'; $request = $this->prophesize(IRequest::class); $service = $this->prophesize(LibresignService::class); + $fileUserMapper = $this->prophesize(FileUserMapper::class); + $fileMapper = $this->prophesize(FileMapper::class); + $root = $this->prophesize(IRootFolder::class); + $l10n = $this->createMock(IL10N::class); + $l10n + ->method('t') + ->will($this->returnArgument(0)); + $accountService = $this->createMock(AccountService::class); + $libresignHandler = $this->createMock(JLibresignHandler::class); $service->sign(\Prophecy\Argument::cetera()) ->shouldNotBeCalled(); @@ -75,12 +102,18 @@ public function testSignFileFailParameterMissing( $controller = new LibresignController( $request->reveal(), $service->reveal(), + $fileUserMapper->reveal(), + $fileMapper->reveal(), + $root->reveal(), + $l10n, + $accountService, + $libresignHandler, $userId ); $result = $controller->sign($inputFilePath, $outputFolderPath, $certificatePath, $password); - static::assertSame(['message' => "parameter '{$paramenterMissing}' is required!"], $result->getData()); - static::assertSame(400, $result->getStatus()); + static::assertSame(["parameter '{$paramenterMissing}' is required!"], $result->getData()['errors']); + static::assertSame(422, $result->getStatus()); } } diff --git a/tests/Unit/Service/AccountServiceTest.php b/tests/Unit/Service/AccountServiceTest.php index 6297b36c4..43fcfb87f 100644 --- a/tests/Unit/Service/AccountServiceTest.php +++ b/tests/Unit/Service/AccountServiceTest.php @@ -7,7 +7,6 @@ use OCA\Libresign\Handler\CfsslHandler; use OCA\Libresign\Service\AccountService; use OCA\Libresign\Service\FolderService; -use OCA\Libresign\Service\SignatureService; use OCA\Settings\Mailer\NewUserMailHelper; use OCP\IConfig; use OCP\IL10N; @@ -25,8 +24,6 @@ final class AccountServiceTest extends TestCase { private $fileUserMapper; /** @var IUserManager */ protected $userManager; - /** @var SignatureService */ - private $signatureService; /** @var FolderService */ private $folder; /** @var IConfig */ @@ -43,7 +40,6 @@ public function setUp(): void { ->will($this->returnArgument(0)); $this->fileUserMapper = $this->createMock(FileUserMapper::class); $this->userManager = $this->createMock(IUserManager::class); - $this->signature = $this->createMock(SignatureService::class); $this->folder = $this->createMock(FolderService::class); $this->config = $this->createMock(IConfig::class); $this->newUserMail = $this->createMock(NewUserMailHelper::class); @@ -52,7 +48,6 @@ public function setUp(): void { $this->l10n, $this->fileUserMapper, $this->userManager, - $this->signature, $this->folder, $this->config, $this->newUserMail, diff --git a/tests/lib/user/dummy.php b/tests/lib/user/dummy.php new file mode 100644 index 000000000..332639170 --- /dev/null +++ b/tests/lib/user/dummy.php @@ -0,0 +1,156 @@ +users[$uid])) { + return false; + } else { + $this->users[$uid] = $password; + return true; + } + } + + /** + * delete a user + * + * @param string $uid The username of the user to delete + * @return bool + * + * Deletes a user + */ + public function deleteUser($uid) { + if (isset($this->users[$uid])) { + unset($this->users[$uid]); + return true; + } else { + return false; + } + } + + /** + * Set password + * + * @param string $uid The username + * @param string $password The new password + * @return bool + * + * Change the password of a user + */ + public function setPassword($uid, $password) { + if (isset($this->users[$uid])) { + $this->users[$uid] = $password; + return true; + } else { + return false; + } + } + + /** + * Check if the password is correct + * + * @param string $uid The username + * @param string $password The password + * @return string|bool + * + * Check if the password is correct without logging in the user + * returns the user id or false + */ + public function checkPassword($uid, $password) { + if (isset($this->users[$uid]) && $this->users[$uid] === $password) { + return $uid; + } + + return false; + } + + public function loginName2UserName($loginName) { + if (isset($this->users[strtolower($loginName)])) { + return strtolower($loginName); + } + return false; + } + + /** + * Get a list of all users + * + * @param string $search + * @param null|int $limit + * @param null|int $offset + * @return string[] an array of all uids + */ + public function getUsers($search = '', $limit = null, $offset = null) { + if (empty($search)) { + return array_keys($this->users); + } + $result = []; + foreach (array_keys($this->users) as $user) { + if (stripos($user, $search) !== false) { + $result[] = $user; + } + } + return $result; + } + + /** + * check if a user exists + * + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + return isset($this->users[$uid]); + } + + /** + * @return bool + */ + public function hasUserListings() { + return true; + } + + /** + * counts the users in the database + * + * @return int|bool + */ + public function countUsers() { + return 0; + } + + public function setDisplayName($uid, $displayName) { + $this->displayNames[$uid] = $displayName; + return true; + } + + public function getDisplayName($uid) { + return isset($this->displayNames[$uid])? $this->displayNames[$uid]: $uid; + } + + /** + * Backend name to be shown in user management + * @return string the name of the backend to be shown + */ + public function getBackendName() { + return 'Dummy'; + } +}