%PDF- %PDF-
Direktori : /www/varak.net/nextcloud.varak.net/apps_old/apps/passwords/lib/Services/ |
Current File : //www/varak.net/nextcloud.varak.net/apps_old/apps/passwords/lib/Services/ValidationService.php |
<?php /** * This file is part of the Passwords App * created by Marius David Wieschollek * and licensed under the AGPL. */ namespace OCA\Passwords\Services; use Exception; use OCA\Passwords\Db\FolderRevision; use OCA\Passwords\Db\PasswordRevision; use OCA\Passwords\Db\RevisionInterface; use OCA\Passwords\Db\TagRevision; use OCA\Passwords\Exception\ApiException; use OCA\Passwords\Helper\Settings\UserSettingsHelper; use OCA\Passwords\Services\Object\FolderRevisionService; use OCA\Passwords\Services\Object\FolderService; use OCA\Passwords\Services\Object\PasswordService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Http; use OCP\AppFramework\IAppContainer; use Throwable; /** * Class ValidationService * * @package OCA\Passwords\Services */ class ValidationService { /** * @var IAppContainer */ protected IAppContainer $container; /** * ValidationService constructor. * * @param IAppContainer $container */ public function __construct(IAppContainer $container) { $this->container = $container; } /** * @param PasswordRevision $password * * @return PasswordRevision * @throws ApiException */ public function validatePassword(PasswordRevision $password): PasswordRevision { $this->validateEncryptionSettings($password); if(empty($password->getLabel())) { throw new ApiException('Field "label" can not be empty', Http::STATUS_BAD_REQUEST); } if(empty($password->getPassword())) { throw new ApiException('Field "password" can not be empty', Http::STATUS_BAD_REQUEST); } $this->checkHash($password); if(empty($password->getEdited()) || $password->getEdited() > strtotime('+2 hour')) { $password->setEdited(time()); } $password->setFolder( $this->validateFolderRelation($password->getFolder(), $password->isHidden()) ); return $password; } /** * @param FolderRevision $folder * * @return FolderRevision * @throws ApiException */ public function validateFolder(FolderRevision $folder): FolderRevision { $this->validateEncryptionSettings($folder); if(empty($folder->getLabel())) { throw new ApiException('Field "label" can not be empty', Http::STATUS_BAD_REQUEST); } if(empty($folder->getEdited()) || $folder->getEdited() > strtotime('+1 hour')) { $folder->setEdited(time()); } if($folder->getModel() === $folder->getParent()) { $folder->setParent(FolderService::BASE_FOLDER_UUID); } else { $folder->setParent( $this->validateFolderRelation($folder->getParent(), $folder->isHidden()) ); } return $folder; } /** * @param TagRevision $tag * * @return TagRevision * @throws ApiException */ public function validateTag(TagRevision $tag): TagRevision { $this->validateEncryptionSettings($tag); if(empty($tag->getLabel())) { throw new ApiException('Field "label" can not be empty', Http::STATUS_BAD_REQUEST); } if(empty($tag->getColor())) { throw new ApiException('Field "color" can not be empty', Http::STATUS_BAD_REQUEST); } if(empty($tag->getEdited()) || $tag->getEdited() > strtotime('+1 hour')) { $tag->setEdited(time()); } return $tag; } /** * @param RevisionInterface $object * * @return RevisionInterface * @throws ApiException * @throws Exception */ public function validateObject(RevisionInterface $object): RevisionInterface { switch(get_class($object)) { case PasswordRevision::class: /** @var $object PasswordRevision */ return $this->validatePassword($object); case FolderRevision::class: /** @var $object FolderRevision */ return $this->validateFolder($object); case TagRevision::class: /** @var $object TagRevision */ return $this->validateTag($object); } throw new Exception('Unknown object type'); } /** * @param string $domain * * @return bool */ public function isValidDomain(string $domain): bool { if(!preg_match("/^(([\w_-]+\.)+[\w_-]+)(:[0-9]+)?$/", idn_to_ascii($domain), $matches)) return false; if(!checkdnsrr($matches[1], 'A')) return false; return true; } /** * @param string $uuid * * @return bool */ public function isValidUuid(string $uuid): bool { return preg_match("/^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$/", $uuid) === 1; } /** * @param string $folderUuid * @param bool $isHidden * * @return string */ protected function validateFolderRelation(string $folderUuid, bool $isHidden): string { if($folderUuid !== FolderService::BASE_FOLDER_UUID) { if(!$this->isValidUuid($folderUuid)) { return FolderService::BASE_FOLDER_UUID; } else { try { $folderRevisionService = $this->container->get(FolderRevisionService::class); /** @var FolderRevision $folderRevision */ $folderRevision = $folderRevisionService->findCurrentRevisionByModel($folderUuid, false); if($folderRevision->isHidden() && !$isHidden) { return FolderService::BASE_FOLDER_UUID; } } catch(Throwable $e) { return FolderService::BASE_FOLDER_UUID; } } } return $folderUuid; } /** * @param RevisionInterface $revision * * @throws ApiException */ protected function validateEncryptionSettings(RevisionInterface $revision): void { if(empty($revision->getSseType())) { $revision->setSseType(EncryptionService::DEFAULT_SSE_ENCRYPTION); } if(empty($revision->getCseType())) { $revision->setCseType(EncryptionService::DEFAULT_CSE_ENCRYPTION); } $validSSE = [EncryptionService::SSE_ENCRYPTION_NONE, EncryptionService::SSE_ENCRYPTION_V1R1, EncryptionService::SSE_ENCRYPTION_V1R2, EncryptionService::SSE_ENCRYPTION_V2R1, EncryptionService::SSE_ENCRYPTION_V3R1]; if(!in_array($revision->getSseType(), $validSSE)) { throw new ApiException('Invalid server side encryption type', Http::STATUS_BAD_REQUEST); } $validCSE = [EncryptionService::CSE_ENCRYPTION_NONE, EncryptionService::CSE_ENCRYPTION_V1R1]; if(!in_array($revision->getCseType(), $validCSE)) { throw new ApiException('Invalid client side encryption type', Http::STATUS_BAD_REQUEST); } if($revision->getCseType() === EncryptionService::CSE_ENCRYPTION_NONE && !empty($revision->getCseKey())) { throw new ApiException('Invalid client side encryption type', Http::STATUS_BAD_REQUEST); } if($revision->getCseType() !== EncryptionService::CSE_ENCRYPTION_NONE) { /** @var UserChallengeService $challengeService */ $challengeService = $this->container->get(UserChallengeService::class); if(!$challengeService->hasChallenge()) { throw new ApiException('Invalid client side encryption type', Http::STATUS_BAD_REQUEST); } } if($revision->getCseType() !== EncryptionService::CSE_ENCRYPTION_NONE && empty($revision->getCseKey())) { throw new ApiException('Client side encryption key missing', Http::STATUS_BAD_REQUEST); } if($revision->getCseType() === EncryptionService::CSE_ENCRYPTION_NONE && $revision->getSseType() === EncryptionService::SSE_ENCRYPTION_NONE) { throw new ApiException('No encryption specified', Http::STATUS_BAD_REQUEST); } } /** * @param PasswordRevision $password * * @throws ApiException * @throws DoesNotExistException * @throws MultipleObjectsReturnedException */ protected function checkHash(PasswordRevision $password): void { if(preg_match("/^[0-9a-z]{40}$/", $password->getHash())) { return; } else { $settingsHelper = $this->container->get(UserSettingsHelper::class); $length = $settingsHelper->get('password.security.hash'); if($password->getId() === null) { if($length === 0 && empty($password->getHash())) { return; } if(preg_match("/^[0-9a-z]{{$length}}$/", $password->getHash()) === 1) { return; } } else { /** @var PasswordService $passwordService */ $passwordService = $this->container->get(PasswordService::class); $model = $passwordService->findByUuid($password->getModel()); if($model->isEditable() && empty($model->getShareId())) { if($length === 0 && empty($password->getHash())) { return; } if(preg_match("/^[0-9a-z]{{$length}}$/", $password->getHash()) === 1) { return; } } else { if(empty($password->getHash()) || preg_match("/^[0-9a-z]{20}$/", $password->getHash()) === 1 || preg_match("/^[0-9a-z]{30}$/", $password->getHash()) === 1) { return; } } } } throw new ApiException('Field "hash" must contain a valid sha1 hash', Http::STATUS_BAD_REQUEST); } }