Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions lib/Enum/DocMdpLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,30 @@
use OCP\IL10N;

enum DocMdpLevel: int {
case NONE = 0;
case NO_CHANGES = 1;
case FORM_FILL = 2;
case FORM_FILL_AND_ANNOTATIONS = 3;
case NOT_CERTIFIED = 0;
case CERTIFIED_NO_CHANGES_ALLOWED = 1;
case CERTIFIED_FORM_FILLING = 2;
case CERTIFIED_FORM_FILLING_AND_ANNOTATIONS = 3;

public function isCertifying(): bool {
return $this !== self::NONE;
return $this !== self::NOT_CERTIFIED;
}

public function getLabel(IL10N $l10n): string {
return match($this) {
self::NONE => $l10n->t('No certification'),
self::NO_CHANGES => $l10n->t('No changes allowed'),
self::FORM_FILL => $l10n->t('Form filling and additional signatures'),
self::FORM_FILL_AND_ANNOTATIONS => $l10n->t('Form filling, annotations and additional signatures'),
self::NOT_CERTIFIED => $l10n->t('No certification'),
self::CERTIFIED_NO_CHANGES_ALLOWED => $l10n->t('No changes allowed'),
self::CERTIFIED_FORM_FILLING => $l10n->t('Form filling and additional signatures'),
self::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => $l10n->t('Form filling, annotations and additional signatures'),
};
}

public function getDescription(IL10N $l10n): string {
return match($this) {
self::NONE => $l10n->t('Document is not certified. No restrictions on modifications.'),
self::NO_CHANGES => $l10n->t('No changes allowed. Additional approval signatures are prohibited.'),
self::FORM_FILL => $l10n->t('Form filling allowed. Additional approval signatures are allowed.'),
self::FORM_FILL_AND_ANNOTATIONS => $l10n->t('Form filling and annotations allowed. Additional approval signatures are allowed.'),
self::NOT_CERTIFIED => $l10n->t('Document is not certified. No restrictions on modifications.'),
self::CERTIFIED_NO_CHANGES_ALLOWED => $l10n->t('No changes allowed. Additional approval signatures are prohibited.'),
self::CERTIFIED_FORM_FILLING => $l10n->t('Form filling allowed. Additional approval signatures are allowed.'),
self::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => $l10n->t('Form filling and annotations allowed. Additional approval signatures are allowed.'),
};
}
}
26 changes: 13 additions & 13 deletions lib/Handler/DocMdpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
class DocMdpHandler {
/** @var array<string, string[]> Allowed modification types per DocMDP level */
private const ALLOWED_MODIFICATIONS = [
'NO_CHANGES' => [],
'FORM_FILL' => ['form_field', 'template', 'signature'],
'FORM_FILL_AND_ANNOTATIONS' => ['form_field', 'template', 'annotation', 'signature'],
'CERTIFIED_NO_CHANGES_ALLOWED' => [],
'CERTIFIED_FORM_FILLING' => ['form_field', 'template', 'signature'],
'CERTIFIED_FORM_FILLING_AND_ANNOTATIONS' => ['form_field', 'template', 'annotation', 'signature'],
];

public function __construct(
Expand Down Expand Up @@ -70,15 +70,15 @@ private function extractDocMdpLevel($pdfResource): DocMdpLevel {
$content = stream_get_contents($pdfResource);

if (!$this->validateIsoCompliance($content)) {
return DocMdpLevel::NONE;
return DocMdpLevel::NOT_CERTIFIED;
}

$pValue = $this->extractPValue($content);
if ($pValue === null) {
return DocMdpLevel::NONE;
return DocMdpLevel::NOT_CERTIFIED;
}

return DocMdpLevel::tryFrom($pValue) ?? DocMdpLevel::NONE;
return DocMdpLevel::tryFrom($pValue) ?? DocMdpLevel::NOT_CERTIFIED;
}

/**
Expand Down Expand Up @@ -249,7 +249,7 @@ private function validateModifications(DocMdpLevel $docmdpLevel, array $modifica
);
}

if ($docmdpLevel === DocMdpLevel::NONE) {
if ($docmdpLevel === DocMdpLevel::NOT_CERTIFIED) {
return $this->buildValidationResult(
true,
File::MODIFICATION_ALLOWED,
Expand Down Expand Up @@ -307,9 +307,9 @@ private function buildValidationResult(bool $valid, int $status, string $message
*/
private function getAllowedModificationMessage(DocMdpLevel $level): string {
return match ($level) {
DocMdpLevel::NO_CHANGES => 'Invalid: Document was modified after signing (DocMDP violation - no changes allowed)',
DocMdpLevel::FORM_FILL => 'Document form fields were modified (allowed by DocMDP P=2)',
DocMdpLevel::FORM_FILL_AND_ANNOTATIONS => 'Document form fields or annotations were modified (allowed by DocMDP P=3)',
DocMdpLevel::CERTIFIED_NO_CHANGES_ALLOWED => 'Invalid: Document was modified after signing (DocMDP violation - no changes allowed)',
DocMdpLevel::CERTIFIED_FORM_FILLING => 'Document form fields were modified (allowed by DocMDP P=2)',
DocMdpLevel::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => 'Document form fields or annotations were modified (allowed by DocMDP P=3)',
default => 'Document was modified after signing',
};
}
Expand All @@ -322,9 +322,9 @@ private function getAllowedModificationMessage(DocMdpLevel $level): string {
*/
private function getViolationMessage(DocMdpLevel $level): string {
return match ($level) {
DocMdpLevel::NO_CHANGES => 'Invalid: Document was modified after signing (DocMDP violation - no changes allowed)',
DocMdpLevel::FORM_FILL => 'Invalid: Document was modified after signing (DocMDP P=2 only allows form field changes)',
DocMdpLevel::FORM_FILL_AND_ANNOTATIONS => 'Invalid: Document was modified after signing (DocMDP P=3 only allows form fields and annotations)',
DocMdpLevel::CERTIFIED_NO_CHANGES_ALLOWED => 'Invalid: Document was modified after signing (DocMDP violation - no changes allowed)',
DocMdpLevel::CERTIFIED_FORM_FILLING => 'Invalid: Document was modified after signing (DocMDP P=2 only allows form field changes)',
DocMdpLevel::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS => 'Invalid: Document was modified after signing (DocMDP P=3 only allows form fields and annotations)',
default => 'Invalid: Document was modified after signing (DocMDP violation)',
};
}
Expand Down
36 changes: 18 additions & 18 deletions tests/php/Unit/Handler/DocMdpHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function testUnsignedPdfIsDetectedAsLevelNone(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level']);
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level']);
}

public function testP0AllowsAnyModification(): void {
Expand Down Expand Up @@ -154,7 +154,7 @@ public function testExtractsDocMdpFromSignatureReferenceNotPerms(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::FORM_FILL->value, $result['docmdp']['level'], 'Must extract DocMDP from /Reference per ICP-Brasil recommendation (not /Perms)');
$this->assertSame(DocMdpLevel::CERTIFIED_FORM_FILLING->value, $result['docmdp']['level'], 'Must extract DocMDP from /Reference per ICP-Brasil recommendation (not /Perms)');
}

public function testExtractsDocMdpFromFirstCertifyingSignature(): void {
Expand All @@ -164,7 +164,7 @@ public function testExtractsDocMdpFromFirstCertifyingSignature(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NO_CHANGES->value, $result['docmdp']['level'], 'Must extract DocMDP from first CERTIFYING signature, not first signature in file');
$this->assertSame(DocMdpLevel::CERTIFIED_NO_CHANGES_ALLOWED->value, $result['docmdp']['level'], 'Must extract DocMDP from first CERTIFYING signature, not first signature in file');
}

public function testP2AllowsPageTemplateInstantiation(): void {
Expand Down Expand Up @@ -196,7 +196,7 @@ public function testExtractsDocMdpWithIndirectReferenceItiStyle(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::FORM_FILL->value, $result['docmdp']['level'], 'Must extract DocMDP from indirect references per ICP-Brasil example');
$this->assertSame(DocMdpLevel::CERTIFIED_FORM_FILLING->value, $result['docmdp']['level'], 'Must extract DocMDP from indirect references per ICP-Brasil example');
}

public function testValidatesTransformParamsVersion(): void {
Expand All @@ -206,7 +206,7 @@ public function testValidatesTransformParamsVersion(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::FORM_FILL->value, $result['docmdp']['level'], 'Must accept /V /1.2 in TransformParams per ICP-Brasil');
$this->assertSame(DocMdpLevel::CERTIFIED_FORM_FILLING->value, $result['docmdp']['level'], 'Must accept /V /1.2 in TransformParams per ICP-Brasil');
}

public function testRejectsDocMdpWithoutVersion(): void {
Expand All @@ -216,7 +216,7 @@ public function testRejectsDocMdpWithoutVersion(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level'], 'Must reject DocMDP without /V version per ICP-Brasil requirement');
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level'], 'Must reject DocMDP without /V version per ICP-Brasil requirement');
}

public function testRejectsDocMdpWithInvalidVersion(): void {
Expand All @@ -226,7 +226,7 @@ public function testRejectsDocMdpWithInvalidVersion(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level'], 'Must reject DocMDP with /V 1.0 (only /V /1.2 allowed per ICP-Brasil)');
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level'], 'Must reject DocMDP with /V 1.0 (only /V /1.2 allowed per ICP-Brasil)');
}

public function testRejectsDocMdpWithInvalidVersionIndirectRef(): void {
Expand All @@ -236,15 +236,15 @@ public function testRejectsDocMdpWithInvalidVersionIndirectRef(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level'], 'Must reject indirect DocMDP with /V 1.3 (only /V /1.2 allowed)');
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level'], 'Must reject indirect DocMDP with /V 1.3 (only /V /1.2 allowed)');
}

public static function docMdpLevelExtractionProvider(): array {
return [
'Level P=0 NONE' => [0, DocMdpLevel::NONE],
'Level P=1 NO_CHANGES' => [1, DocMdpLevel::NO_CHANGES],
'Level P=2 FORM_FILL' => [2, DocMdpLevel::FORM_FILL],
'Level P=3 FORM_FILL_AND_ANNOTATIONS' => [3, DocMdpLevel::FORM_FILL_AND_ANNOTATIONS],
'Level P=0 NOT_CERTIFIED' => [0, DocMdpLevel::NOT_CERTIFIED],
'Level P=1 CERTIFIED_NO_CHANGES_ALLOWED' => [1, DocMdpLevel::CERTIFIED_NO_CHANGES_ALLOWED],
'Level P=2 CERTIFIED_FORM_FILLING' => [2, DocMdpLevel::CERTIFIED_FORM_FILLING],
'Level P=3 CERTIFIED_FORM_FILLING_AND_ANNOTATIONS' => [3, DocMdpLevel::CERTIFIED_FORM_FILLING_AND_ANNOTATIONS],
];
}

Expand Down Expand Up @@ -726,7 +726,7 @@ public function testRejectsSignatureDictionaryWithoutTypeWhenPresent(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level'], 'ISO 32000-1 Table 252: if /Type present in signature dict, must be /Sig');
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level'], 'ISO 32000-1 Table 252: if /Type present in signature dict, must be /Sig');
}

public function testRejectsSignatureWithoutFilterEntry(): void {
Expand All @@ -744,7 +744,7 @@ public function testRejectsSignatureWithoutFilterEntry(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level'], 'ISO 32000-1 Table 252: /Filter is Required in signature dictionary');
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level'], 'ISO 32000-1 Table 252: /Filter is Required in signature dictionary');
}

public function testRejectsSignatureWithoutByteRange(): void {
Expand All @@ -762,7 +762,7 @@ public function testRejectsSignatureWithoutByteRange(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level'], 'ISO 32000-1: ByteRange required when DocMDP transform method is used');
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level'], 'ISO 32000-1: ByteRange required when DocMDP transform method is used');
}

public function testRejectsMultipleDocMdpSignatures(): void {
Expand Down Expand Up @@ -792,7 +792,7 @@ public function testRejectsMultipleDocMdpSignatures(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level'], 'ISO 32000-1 12.8.2.2.1: A document can contain only one signature field that contains a DocMDP transform method');
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level'], 'ISO 32000-1 12.8.2.2.1: A document can contain only one signature field that contains a DocMDP transform method');
}

public function testRejectsDocMdpNotFirstSignature(): void {
Expand Down Expand Up @@ -820,7 +820,7 @@ public function testRejectsDocMdpNotFirstSignature(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level'], 'ISO 32000-1 12.8.2.2.1: DocMDP signature shall be the first signed field in the document');
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level'], 'ISO 32000-1 12.8.2.2.1: DocMDP signature shall be the first signed field in the document');
}

public function testRejectsSigRefWithoutTransformMethod(): void {
Expand All @@ -839,6 +839,6 @@ public function testRejectsSigRefWithoutTransformMethod(): void {
$result = $this->handler->extractDocMdpData($resource);
fclose($resource);

$this->assertSame(DocMdpLevel::NONE->value, $result['docmdp']['level'], 'ISO 32000-1 Table 253: /TransformMethod is Required in signature reference dictionary');
$this->assertSame(DocMdpLevel::NOT_CERTIFIED->value, $result['docmdp']['level'], 'ISO 32000-1 Table 253: /TransformMethod is Required in signature reference dictionary');
}
}
Loading