Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.58% covered (warning)
89.58%
43 / 48
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
IdentifyController
89.58% covered (warning)
89.58%
43 / 48
50.00% covered (danger)
50.00%
2 / 4
14.22
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
 search
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
2
 resolveAllowedMethods
77.78% covered (warning)
77.78%
14 / 18
0.00% covered (danger)
0.00%
0 / 1
11.10
 registerPlugin
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Controller;
10
11use OCA\Libresign\AppInfo\Application;
12use OCA\Libresign\Collaboration\Collaborators\AccountPhonePlugin;
13use OCA\Libresign\Collaboration\Collaborators\ContactPhonePlugin;
14use OCA\Libresign\Collaboration\Collaborators\ManualPhonePlugin;
15use OCA\Libresign\Collaboration\Collaborators\SignerPlugin;
16use OCA\Libresign\Middleware\Attribute\RequireManager;
17use OCA\Libresign\Service\Identify\ResultEnricher;
18use OCA\Libresign\Service\Identify\ResultFilter;
19use OCA\Libresign\Service\Identify\ResultFormatter;
20use OCA\Libresign\Service\Identify\SearchNormalizer;
21use OCA\Libresign\Service\Identify\ShareTypeResolver;
22use OCA\Libresign\Service\Identify\SignerSearchContext;
23use OCA\Libresign\Service\Policy\PolicyService;
24use OCA\Libresign\Service\Policy\Provider\IdentifyMethods\IdentifyMethodsPolicy;
25use OCA\Libresign\Service\Policy\Provider\IdentifyMethods\IdentifyMethodsPolicyValue;
26use OCP\AppFramework\Http;
27use OCP\AppFramework\Http\Attribute\ApiRoute;
28use OCP\AppFramework\Http\Attribute\NoAdminRequired;
29use OCP\AppFramework\Http\DataResponse;
30use OCP\Collaboration\Collaborators\ISearch;
31use OCP\IRequest;
32
33/**
34 * @psalm-import-type LibresignIdentifyAccount from \OCA\Libresign\ResponseDefinitions
35 * @psalm-import-type LibresignIdentifyAccountsResponse from \OCA\Libresign\ResponseDefinitions
36 */
37class IdentifyController extends AEnvironmentAwareController {
38    public function __construct(
39        IRequest $request,
40        private ISearch $collaboratorSearch,
41        private ShareTypeResolver $shareTypeResolver,
42        private SearchNormalizer $searchNormalizer,
43        private SignerSearchContext $signerSearchContext,
44        private ResultFilter $resultFilter,
45        private ResultFormatter $resultFormatter,
46        private ResultEnricher $resultEnricher,
47        private PolicyService $policyService,
48    ) {
49        parent::__construct(Application::APP_ID, $request);
50    }
51
52    /**
53     * List possible signers
54     *
55     * Used to identify who can be invited to sign a document. The response follows Administration Settings > LibreSign > Identify method.
56     *
57     * @param string $search search params
58     * @param string $method filter by method (email, account, sms, signal, telegram, whatsapp, whatsappbusiness, xmpp)
59     * @param int $page the number of page to return. Default: 1
60     * @param int $limit Total of elements to return. Default: 25
61     * @return DataResponse<Http::STATUS_OK, LibresignIdentifyAccountsResponse, array{}>
62     *
63     * 200: Certificate saved with success
64     * 400: No file provided or other problem with provided file
65     */
66    #[NoAdminRequired]
67    #[RequireManager]
68    #[ApiRoute(verb: 'GET', url: '/api/{apiVersion}/identify-account/search', requirements: ['apiVersion' => '(v1)'])]
69    public function search(string $search = '', string $method = '', int $page = 1, int $limit = 25): DataResponse {
70        $rawSearch = $search;
71        $search = $this->searchNormalizer->normalize($search, $method);
72
73        // Only search for string larger than a minimum length
74        if (strlen($search) < 1) {
75            return new DataResponse([]);
76        }
77
78        $shareTypes = $this->shareTypeResolver->resolve($method);
79        $offset = $limit * ($page - 1);
80
81        $this->signerSearchContext->set($method, $search, $rawSearch);
82        $this->registerPlugin();
83        [$result] = $this->collaboratorSearch->search($search, $shareTypes, false, $limit, $offset);
84
85        // Process results through filters and formatters
86        $result['exact'] = $this->resultFilter->unify($result['exact']);
87        $result = $this->resultFilter->unify($result);
88        $result = $this->resultFilter->excludeEmpty($result);
89
90        $return = $this->resultFormatter->formatForNcSelect($result);
91        $return = $this->resultFormatter->replaceShareTypeWithMethod($return);
92        $return = $this->resultFilter->excludeNotAllowed($return, $this->resolveAllowedMethods($method));
93        $return = $this->resultEnricher->addHerselfAccount($return, $search, $method);
94        $return = $this->resultEnricher->addHerselfEmail($return, $search, $method);
95        $return = $this->resultEnricher->addEmailNotificationPreference($return);
96        $return = $this->resultFilter->excludeNotAllowed($return);
97        /** @var LibresignIdentifyAccountsResponse $return */
98        $return = array_values($return);
99
100        return new DataResponse($return);
101    }
102
103    /**
104     * @return list<string>
105     */
106    private function resolveAllowedMethods(string $requestedMethod): array {
107        $resolved = $this->policyService->resolve(IdentifyMethodsPolicy::KEY)->getEffectiveValue();
108        $settings = IdentifyMethodsPolicyValue::extractFactors(IdentifyMethodsPolicyValue::normalize($resolved));
109
110        $enabledMethods = [];
111        foreach ($settings as $setting) {
112            if (!is_array($setting)) {
113                continue;
114            }
115
116            $name = isset($setting['name']) && is_string($setting['name']) ? trim($setting['name']) : '';
117            $enabled = !empty($setting['enabled']);
118            if ($name === '' || !$enabled) {
119                continue;
120            }
121
122            $enabledMethods[] = $name;
123        }
124
125        $enabledMethods = array_values(array_unique($enabledMethods));
126        $requestedMethod = strtolower(trim($requestedMethod));
127        if ($requestedMethod === '' || $requestedMethod === 'all') {
128            return $enabledMethods;
129        }
130
131        if (in_array($requestedMethod, $enabledMethods, true)) {
132            return [$requestedMethod];
133        }
134
135        return [];
136    }
137
138    private function registerPlugin(): void {
139
140        $refObject = new \ReflectionObject($this->collaboratorSearch);
141        $refProperty = $refObject->getProperty('pluginList');
142
143        $plugins = $refProperty->getValue($this->collaboratorSearch);
144        $plugins[SignerPlugin::TYPE_SIGNER] = [SignerPlugin::class];
145        $plugins[AccountPhonePlugin::TYPE_SIGNER_ACCOUNT_PHONE] = [AccountPhonePlugin::class];
146        $plugins[ContactPhonePlugin::TYPE_SIGNER_CONTACT_PHONE] = [ContactPhonePlugin::class];
147        $plugins[ManualPhonePlugin::TYPE_SIGNER_MANUAL_PHONE] = [ManualPhonePlugin::class];
148
149        $refProperty->setValue($this->collaboratorSearch, $plugins);
150    }
151
152}