• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LibreSign / libresign / 21967842076

12 Feb 2026 11:09PM UTC coverage: 51.292%. First build
21967842076

Pull #6824

github

web-flow
Merge e1e14865d into 34f32c1fd
Pull Request #6824: fix: signature methods names

217 of 241 new or added lines in 21 files covered. (90.04%)

8989 of 17525 relevant lines covered (51.29%)

6.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.97
/lib/Service/IdentifyMethodService.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Service;
10

11
use OCA\Libresign\Db\IdentifyMethod;
12
use OCA\Libresign\Db\IdentifyMethodMapper;
13
use OCA\Libresign\Db\SignRequest;
14
use OCA\Libresign\Exception\LibresignException;
15
use OCA\Libresign\Service\IdentifyMethod\Account;
16
use OCA\Libresign\Service\IdentifyMethod\Email;
17
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
18
use OCA\Libresign\Service\IdentifyMethod\Signal;
19
use OCA\Libresign\Service\IdentifyMethod\Sms;
20
use OCA\Libresign\Service\IdentifyMethod\Telegram;
21
use OCA\Libresign\Service\IdentifyMethod\Whatsapp;
22
use OCA\Libresign\Service\IdentifyMethod\Xmpp;
23
use OCP\IL10N;
24
use OCP\IUserManager;
25

26
class IdentifyMethodService {
27
        public const IDENTIFY_ACCOUNT = 'account';
28
        public const IDENTIFY_EMAIL = 'email';
29
        public const IDENTIFY_SIGNAL = 'signal';
30
        public const IDENTIFY_TELEGRAM = 'telegram';
31
        public const IDENTIFY_SMS = 'sms';
32
        public const IDENTIFY_WHATSAPP = 'whatsapp';
33
        public const IDENTIFY_XMPP = 'xmpp';
34
        public const IDENTIFY_PASSWORD = 'password';
35
        public const IDENTIFY_CLICK_TO_SIGN = 'clickToSign';
36
        public const IDENTIFY_METHODS = [
37
                self::IDENTIFY_ACCOUNT,
38
                self::IDENTIFY_EMAIL,
39
                self::IDENTIFY_SIGNAL,
40
                self::IDENTIFY_TELEGRAM,
41
                self::IDENTIFY_SMS,
42
                self::IDENTIFY_WHATSAPP,
43
                self::IDENTIFY_XMPP,
44
                self::IDENTIFY_PASSWORD,
45
                self::IDENTIFY_CLICK_TO_SIGN,
46
        ];
47
        private bool $isRequest = true;
48
        private ?IdentifyMethod $currentIdentifyMethod = null;
49
        private array $identifyMethodsSettings = [];
50
        /**
51
         * @var array<string,array<IIdentifyMethod>>
52
         */
53
        private array $identifyMethods = [];
54

55
        public function __construct(
56
                private IdentifyMethodMapper $identifyMethodMapper,
57
                private IL10N $l10n,
58
                private IUserManager $userManager,
59
                private Account $account,
60
                private Email $email,
61
                private Signal $signal,
62
                private Sms $sms,
63
                private Telegram $telegram,
64
                private Whatsapp $Whatsapp,
65
                private Xmpp $xmpp,
66
                private SubjectAlternativeNameService $subjectAlternativeNameService,
67
        ) {
68
        }
75✔
69

70
        public function clearCache(): void {
71
                $this->identifyMethods = [];
13✔
72
                $this->currentIdentifyMethod = null;
13✔
73
        }
74

75
        public function setIsRequest(bool $isRequest): self {
76
                $this->isRequest = $isRequest;
2✔
77
                return $this;
2✔
78
        }
79

80
        public function getInstanceOfIdentifyMethod(string $name, ?string $identifyValue = null): IIdentifyMethod {
81
                if ($identifyValue && isset($this->identifyMethods[$name])) {
13✔
82
                        foreach ($this->identifyMethods[$name] as $identifyMethod) {
8✔
83
                                if ($identifyMethod->getEntity()->getIdentifierValue() === $identifyValue) {
8✔
84
                                        $identifyMethod = $this->mergeWithCurrentIdentifyMethod($identifyMethod);
8✔
85
                                        return $identifyMethod;
8✔
86
                                }
87
                        }
88
                }
89
                $identifyMethod = $this->getNewInstanceOfMethod($name);
13✔
90

91
                $entity = $identifyMethod->getEntity();
13✔
92
                if (!$entity->getId()) {
13✔
93
                        $entity->setIdentifierKey($name);
13✔
94
                        $entity->setIdentifierValue($identifyValue);
13✔
95
                        $entity->setMandatory($this->isMandatoryMethod($name) ? 1 : 0);
13✔
96
                }
97
                if ($identifyValue && $this->isRequest) {
13✔
98
                        $identifyMethod->validateToRequest();
13✔
99
                }
100

101
                $this->identifyMethods[$name][] = $identifyMethod;
13✔
102
                return $identifyMethod;
13✔
103
        }
104

105
        private function mergeWithCurrentIdentifyMethod(IIdentifyMethod $identifyMethod): IIdentifyMethod {
106
                if ($this->currentIdentifyMethod === null) {
8✔
107
                        return $identifyMethod;
×
108
                }
109
                if ($this->currentIdentifyMethod->getIdentifierKey() === $identifyMethod->getEntity()->getIdentifierKey()
8✔
110
                        && $this->currentIdentifyMethod->getIdentifierValue() === $identifyMethod->getEntity()->getIdentifierValue()
8✔
111
                ) {
112
                        $identifyMethod->setEntity($this->currentIdentifyMethod);
8✔
113
                }
114
                return $identifyMethod;
8✔
115
        }
116

117
        private function getNewInstanceOfMethod(string $name): IIdentifyMethod {
118
                $className = 'OCA\Libresign\Service\IdentifyMethod\\' . ucfirst($name);
13✔
119
                if (!class_exists($className)) {
13✔
120
                        $className = 'OCA\Libresign\Service\IdentifyMethod\\SignatureMethod\\' . ucfirst($name);
×
121
                        if (!class_exists($className)) {
×
122
                                // TRANSLATORS When is requested to a person to sign a file, is
123
                                // necessary identify what is the identification method. The
124
                                // identification method is used to define how will be the sign
125
                                // flow.
126
                                throw new LibresignException($this->l10n->t('Invalid identification method'));
×
127
                        }
128
                }
129
                /** @var IIdentifyMethod */
130
                $identifyMethod = clone \OCP\Server::get($className);
13✔
131
                if (empty($this->currentIdentifyMethod)) {
13✔
132
                        $identifyMethod->cleanEntity();
13✔
133
                } else {
134
                        $identifyMethod->setEntity($this->currentIdentifyMethod);
×
135
                }
136
                $identifyMethod->getSettings();
13✔
137
                return $identifyMethod;
13✔
138
        }
139

140
        private function setEntityData(string $method, string $identifyValue): void {
141
                // @todo Replace by enum when PHP 8.1 is the minimum version acceptable
142
                // at server. Check file lib/versioncheck.php of server repository
143
                if (!in_array($method, IdentifyMethodService::IDENTIFY_METHODS)) {
1✔
144
                        // TRANSLATORS When is requested to a person to sign a file, is
145
                        // necessary identify what is the identification method. The
146
                        // identification method is used to define how will be the sign
147
                        // flow.
148
                        throw new LibresignException($this->l10n->t('Invalid identification method'));
×
149
                }
150
                $identifyMethod = $this->getInstanceOfIdentifyMethod($method, $identifyValue);
1✔
151
                $identifyMethod->validateToRequest();
1✔
152
        }
153

154
        public function setAllEntityData(array $user): void {
155
                foreach ($user['identify'] as $method => $identifyValue) {
1✔
156
                        $this->setEntityData($method, $identifyValue);
1✔
157
                }
158
        }
159

160
        private function isMandatoryMethod(string $methodName): bool {
161
                $settings = $this->getIdentifyMethodsSettings();
13✔
162
                foreach ($settings as $setting) {
13✔
163
                        if ($setting['name'] === $methodName) {
13✔
164
                                return $setting['mandatory'];
13✔
165
                        }
166
                }
167
                return false;
×
168
        }
169

170
        /**
171
         * @return array<IIdentifyMethod>
172
         */
173
        public function getByUserData(array $data) {
174
                $return = [];
13✔
175
                foreach ($data as $method => $identifyValue) {
13✔
176
                        $this->setCurrentIdentifyMethod();
13✔
177
                        $return[] = $this->getInstanceOfIdentifyMethod($method, $identifyValue);
13✔
178
                }
179
                return $return;
13✔
180
        }
181

182
        public function setCurrentIdentifyMethod(?IdentifyMethod $entity = null): self {
183
                $this->currentIdentifyMethod = $entity;
13✔
184
                return $this;
13✔
185
        }
186

187
        /**
188
         * @return array<string,array<IIdentifyMethod>>
189
         */
190
        public function getIdentifyMethodsFromSignRequestId(int $signRequestId): array {
191
                $entities = $this->identifyMethodMapper->getIdentifyMethodsFromSignRequestId($signRequestId);
7✔
192
                foreach ($entities as $entity) {
7✔
193
                        $this->setCurrentIdentifyMethod($entity);
7✔
194
                        $this->getInstanceOfIdentifyMethod(
7✔
195
                                $entity->getIdentifierKey(),
7✔
196
                                $entity->getIdentifierValue(),
7✔
197
                        );
7✔
198
                }
199
                $return = [];
7✔
200
                foreach ($this->identifyMethods as $methodName => $list) {
7✔
201
                        foreach ($list as $method) {
7✔
202
                                if ($method->getEntity()->getSignRequestId() === $signRequestId) {
7✔
203
                                        $return[$methodName][] = $method;
7✔
204
                                }
205
                        }
206
                }
207
                return $return;
7✔
208
        }
209

210
        /**
211
         * @param int[] $signRequestIds
212
         * @return array<int, array<string,array<IIdentifyMethod>>>
213
         */
214
        public function getIdentifyMethodsFromSignRequestIds(array $signRequestIds): array {
215
                if (empty($signRequestIds)) {
3✔
216
                        return [];
×
217
                }
218

219
                $entitiesBySignRequest = $this->identifyMethodMapper->getIdentifyMethodsFromSignRequestIds($signRequestIds);
3✔
220

221
                foreach ($entitiesBySignRequest as $entities) {
3✔
222
                        foreach ($entities as $entity) {
3✔
223
                                $this->setCurrentIdentifyMethod($entity);
×
224
                                $this->getInstanceOfIdentifyMethod($entity->getIdentifierKey(), $entity->getIdentifierValue());
×
225
                        }
226
                }
227

228
                $results = [];
3✔
229
                foreach ($signRequestIds as $signRequestId) {
3✔
230
                        $results[$signRequestId] = [];
3✔
231
                        foreach ($this->identifyMethods as $methodName => $list) {
3✔
232
                                foreach ($list as $method) {
3✔
233
                                        if ($method->getEntity()->getSignRequestId() === $signRequestId) {
3✔
234
                                                $results[$signRequestId][$methodName][] = $method;
3✔
235
                                        }
236
                                }
237
                        }
238
                }
239

240
                return $results;
3✔
241
        }
242

243
        public function getIdentifiedMethod(int $signRequestId): IIdentifyMethod {
244
                $matrix = $this->getIdentifyMethodsFromSignRequestId($signRequestId);
1✔
245
                [$identifiedMethod, $firstMethod] = $this->findMethodsInMatrix($matrix);
1✔
246

247
                if ($identifiedMethod !== null) {
1✔
248
                        return $identifiedMethod;
1✔
249
                }
250

251
                if ($firstMethod !== null) {
×
252
                        return $firstMethod;
×
253
                }
254

255
                throw new LibresignException($this->l10n->t('Invalid identification method'), 1);
×
256
        }
257

258
        /**
259
         * @return array{?IIdentifyMethod, ?IIdentifyMethod} [identifiedMethod, firstMethod]
260
         */
261
        private function findMethodsInMatrix(array $matrix): array {
262
                $firstMethod = null;
14✔
263

264
                foreach ($matrix as $identifyMethods) {
14✔
265
                        foreach ($identifyMethods as $identifyMethod) {
11✔
266
                                $firstMethod ??= $identifyMethod;
11✔
267

268
                                if ($identifyMethod->getEntity()->getIdentifiedAtDate()) {
11✔
269
                                        return [$identifyMethod, $firstMethod];
7✔
270
                                }
271
                        }
272
                }
273

274
                return [null, $firstMethod];
7✔
275
        }
276

277
        public function getUserIdentifier(int $signRequestId): string {
278
                $identifyMethod = $this->getIdentifiedMethod($signRequestId);
×
279
                return $identifyMethod->getEntity()->getUniqueIdentifier();
×
280
        }
281

282
        public function deleteBySignRequestId(int $signRequestId): void {
283
                $this->identifyMethodMapper->deleteBySignRequestId($signRequestId);
3✔
284
                $this->clearCache();
3✔
285
        }
286

287
        public function getSignMethodsOfIdentifiedFactors(int $signRequestId): array {
288
                $matrix = $this->getIdentifyMethodsFromSignRequestId($signRequestId);
×
289
                $return = [];
×
290
                foreach ($matrix as $identifyMethods) {
×
291
                        foreach ($identifyMethods as $identifyMethod) {
×
292
                                $signatureMethods = $identifyMethod->getSignatureMethods();
×
293
                                foreach ($signatureMethods as $signatureMethod) {
×
294
                                        if (!$signatureMethod->isEnabled()) {
×
295
                                                continue;
×
296
                                        }
297
                                        $signatureMethod->setEntity($identifyMethod->getEntity());
×
298
                                        $return[$signatureMethod->getName()] = $signatureMethod->toArray();
×
299
                                }
300
                        }
301
                }
302
                return $return;
×
303
        }
304

305
        public function save(SignRequest $signRequest, bool $notify = true): void {
306
                foreach ($this->identifyMethods as $methods) {
×
307
                        foreach ($methods as $identifyMethod) {
×
308
                                $entity = $identifyMethod->getEntity();
×
309
                                $entity->setSignRequestId($signRequest->getId());
×
310
                                if ($entity->getId()) {
×
311
                                        $entity = $this->identifyMethodMapper->update($entity);
×
312
                                        if ($notify) {
×
313
                                                $identifyMethod->willNotifyUser(false);
×
314
                                                $identifyMethod->notify();
×
315
                                        }
316
                                } else {
317
                                        $entity = $this->identifyMethodMapper->insert($entity);
×
318
                                        if ($notify) {
×
319
                                                $identifyMethod->willNotifyUser(true);
×
320
                                                $identifyMethod->notify();
×
321
                                        }
322
                                }
323
                        }
324
                }
325
        }
326

327
        public function getIdentifyMethodsSettings(): array {
328
                if ($this->identifyMethodsSettings) {
17✔
329
                        return $this->identifyMethodsSettings;
2✔
330
                }
331
                $this->identifyMethodsSettings = [
17✔
332
                        $this->account->getSettings(),
17✔
333
                        $this->email->getSettings(),
17✔
334
                ];
17✔
335
                if ($this->signal->isTwofactorGatewayEnabled()) {
17✔
336
                        $this->identifyMethodsSettings[] = $this->signal->getSettings();
×
337
                }
338
                if ($this->sms->isTwofactorGatewayEnabled()) {
17✔
339
                        $this->identifyMethodsSettings[] = $this->sms->getSettings();
1✔
340
                }
341
                if ($this->telegram->isTwofactorGatewayEnabled()) {
17✔
342
                        $this->identifyMethodsSettings[] = $this->telegram->getSettings();
×
343
                }
344
                if ($this->Whatsapp->isTwofactorGatewayEnabled()) {
17✔
345
                        $this->identifyMethodsSettings[] = $this->Whatsapp->getSettings();
1✔
346
                }
347
                if ($this->xmpp->isTwofactorGatewayEnabled()) {
17✔
348
                        $this->identifyMethodsSettings[] = $this->xmpp->getSettings();
×
349
                }
350
                return $this->identifyMethodsSettings;
17✔
351
        }
352

353
        /**
354
         * Resolve UID from certificate chain data
355
         *
356
         * Extracts and resolves the identifier from certificate subject or extensions.
357
         * Supports fallbacks for older LibreSign versions and converts to standard
358
         * identifier format (account:uid or email:value).
359
         *
360
         * @param array $chainArr Certificate chain array with subject and extensions
361
         * @param string $host Host domain for email matching
362
         * @return string|null Resolved identifier in format "type:value" or null
363
         */
364
        public function resolveUid(array $chainArr, string $host): ?string {
365
                if (!empty($chainArr['subject']['UID'])) {
×
366
                        return $chainArr['subject']['UID'];
×
367
                }
368

NEW
369
                return $this->subjectAlternativeNameService->resolveUid($chainArr, $host);
×
370
        }
371
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc