Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.75% covered (success)
98.75%
79 / 80
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Cleanup
98.75% covered (success)
98.75%
79 / 80
66.67% covered (warning)
66.67%
2 / 3
10
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 configure
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 execute
98.41% covered (success)
98.41%
62 / 63
0.00% covered (danger)
0.00%
0 / 1
8
1<?php
2
3declare(strict_types=1);
4
5/**
6 * SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
7 * SPDX-License-Identifier: AGPL-3.0-or-later
8 */
9
10namespace OCA\Libresign\Command\Crl;
11
12use DateTime;
13use OCA\Libresign\Service\Crl\CrlService;
14use Symfony\Component\Console\Command\Command;
15use Symfony\Component\Console\Input\InputInterface;
16use Symfony\Component\Console\Input\InputOption;
17use Symfony\Component\Console\Output\OutputInterface;
18use Symfony\Component\Console\Style\SymfonyStyle;
19
20class Cleanup extends Command {
21    public function __construct(
22        private CrlService $crlService,
23    ) {
24        parent::__construct();
25    }
26
27    #[\Override]
28    protected function configure(): void {
29        $this
30            ->setName('libresign:crl:cleanup')
31            ->setDescription('Clean up expired certificates from the CRL database')
32            ->addOption(
33                'period',
34                'p',
35                InputOption::VALUE_REQUIRED,
36                'Clean up expired certificates older than specified period (e.g., "1 year", "6 months")',
37                '1 year'
38            )
39            ->addOption(
40                'dry-run',
41                null,
42                InputOption::VALUE_NONE,
43                'Show what would be cleaned without making changes'
44            );
45    }
46
47    #[\Override]
48    protected function execute(InputInterface $input, OutputInterface $output): int {
49        $io = new SymfonyStyle($input, $output);
50        $isDryRun = $input->getOption('dry-run');
51        $period = $input->getOption('period');
52
53        if ($isDryRun) {
54            $io->note('Running in DRY-RUN mode - no changes will be made');
55        }
56
57        $io->title('LibreSign CRL Cleanup');
58
59        try {
60            $cleanupDate = new DateTime();
61            $result = @$cleanupDate->modify("-{$period}");
62            if ($result === false) {
63                throw new \InvalidArgumentException("Invalid period format: {$period}");
64            }
65            $now = new DateTime();
66            if ($cleanupDate > $now) {
67                throw new \InvalidArgumentException('Period must result in a past date for cleanup');
68            }
69        } catch (\Exception $e) {
70            $io->error("Invalid period format: {$period}. Use formats like '1 year', '6 months', '30 days'");
71            return Command::FAILURE;
72        }
73
74        $io->section('Cleanup Configuration');
75        $io->text("Period: {$period}");
76        $io->text('Cleanup date: ' . $cleanupDate->format('Y-m-d H:i:s'));
77
78        try {
79            $stats = $this->crlService->getStatistics();
80
81            $totalCertificates = array_sum($stats);
82            $validCertificates = $stats['issued'] ?? 0;
83            $revokedCertificates = $stats['revoked'] ?? 0;
84            $expiredCertificates = $stats['expired'] ?? 0;
85
86            $io->section('Current Statistics');
87            $io->table(
88                ['Metric', 'Count'],
89                [
90                    ['Total Certificates', $totalCertificates],
91                    ['Valid Certificates', $validCertificates],
92                    ['Revoked Certificates', $revokedCertificates],
93                    ['Expired Certificates', $expiredCertificates],
94                ]
95            );
96
97            if ($isDryRun) {
98                $io->section('Dry Run Results');
99                $io->text('Would clean up expired certificates older than ' . $cleanupDate->format('Y-m-d H:i:s'));
100                $io->warning('Use --dry-run=false or remove --dry-run to perform actual cleanup');
101                return Command::SUCCESS;
102            }
103
104            $io->section('Performing Cleanup');
105            $deletedCount = $this->crlService->cleanupExpiredCertificates($cleanupDate);
106
107            if ($deletedCount > 0) {
108                $io->success("Successfully cleaned up {$deletedCount} expired certificate(s)");
109            } else {
110                $io->info('No expired certificates found for cleanup');
111            }
112
113            $newStats = $this->crlService->getStatistics();
114
115            $newTotalCertificates = array_sum($newStats);
116            $newValidCertificates = $newStats['issued'] ?? 0;
117            $newRevokedCertificates = $newStats['revoked'] ?? 0;
118            $newExpiredCertificates = $newStats['expired'] ?? 0;
119
120            $io->section('Updated Statistics');
121            $io->table(
122                ['Metric', 'Before', 'After', 'Change'],
123                [
124                    ['Total Certificates', $totalCertificates, $newTotalCertificates, $newTotalCertificates - $totalCertificates],
125                    ['Valid Certificates', $validCertificates, $newValidCertificates, $newValidCertificates - $validCertificates],
126                    ['Revoked Certificates', $revokedCertificates, $newRevokedCertificates, $newRevokedCertificates - $revokedCertificates],
127                    ['Expired Certificates', $expiredCertificates, $newExpiredCertificates, $newExpiredCertificates - $expiredCertificates],
128                ]
129            );
130
131        } catch (\Exception $e) {
132            $io->error('Error during cleanup: ' . $e->getMessage());
133            return Command::FAILURE;
134        }
135
136        return Command::SUCCESS;
137    }
138}