Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
ConsumerAdmin
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 15
600
0.00% covered (danger)
0.00%
0 / 1
 addInstanceForm
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 editInstance
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getClassService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCurrentClass
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getCurrentInstance
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 createNewInstanceForm
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 createExistingInstanceForm
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 enrichWithSecretField
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 enrichWithRegenerateSecretAction
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 enrichWithRegenerateSecretDisclaimer
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 handleNewInstanceSubmission
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 handleExistingInstanceSubmission
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 extractValues
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 updateCurrentInstance
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSecretKeyService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2019 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
19 */
20
21namespace oat\taoLti\controller;
22
23use common_exception_BadRequest as BadRequestException;
24use core_kernel_classes_Class as KernelClass;
25use core_kernel_classes_Resource as KernelResource;
26use oat\tao\model\controller\SignedFormInstance;
27use oat\tao\model\oauth\DataStore;
28use oat\taoLti\models\classes\ConsumerService;
29use oat\taoLti\models\classes\Security\Business\Contract\SecretKeyServiceInterface;
30use oat\taoLti\models\classes\Security\Business\Domain\Exception\SecretKeyGenerationException;
31use tao_actions_form_CreateInstance as CreateInstanceContainer;
32use tao_actions_SaSModule;
33use tao_helpers_form_Form as Form;
34use tao_helpers_form_FormFactory as FormFactory;
35use tao_helpers_Uri as UriHelper;
36use tao_models_classes_dataBinding_GenerisFormDataBinder as FormDataBinder;
37use tao_models_classes_dataBinding_GenerisFormDataBindingException as FormDataBindingException;
38
39/**
40 * This controller allows the additon and deletion
41 * of LTI Oauth Consumers
42 *
43 * @package taoLti
44 * @license GPLv2 http://www.opensource.org/licenses/gpl-2.0.php
45 */
46class ConsumerAdmin extends tao_actions_SaSModule
47{
48    private const EXCLUDED_FIELDS = [
49        'id',
50        DataStore::PROPERTY_OAUTH_SECRET,
51        DataStore::PROPERTY_OAUTH_CALLBACK,
52    ];
53
54    private const SECRET_REGENERATION_KEY = 'regenerate_secret';
55
56    /** @var KernelClass|null */
57    private $currentClass;
58
59    /** @var KernelResource|null */
60    private $currentInstance;
61
62    /**
63     * @inheritDoc
64     *
65     * @throws SecretKeyGenerationException
66     */
67    public function addInstanceForm(): void
68    {
69        if (!$this->isXmlHttpRequest()) {
70            throw new BadRequestException('wrong request mode');
71        }
72
73        $form = $this->createNewInstanceForm();
74
75        $this->handleNewInstanceSubmission($form);
76
77        $this->setData('formTitle', __('Create instance of ') . $this->getCurrentClass()->getLabel());
78        $this->setData('myForm', $form->render());
79
80        $this->setView('form.tpl', 'tao');
81    }
82
83    /**
84     * @inheritDoc
85     *
86     * @throws FormDataBindingException
87     */
88    public function editInstance(): void
89    {
90        $form = $this->createExistingInstanceForm();
91
92        $this->handleExistingInstanceSubmission($form);
93
94        $this->setData('formTitle', __('Edit Instance'));
95        $this->setData('myForm', $form->render());
96        $this->setView('form.tpl', 'tao');
97    }
98
99    /**
100     * @inheritDoc
101     */
102    protected function getClassService()
103    {
104        return $this->getServiceLocator()->get(ConsumerService::class);
105    }
106
107    /**
108     * @inheritDoc
109     */
110    protected function getCurrentClass(): KernelClass
111    {
112        if (null === $this->currentClass) {
113            $this->currentClass = parent::getCurrentClass();
114        }
115
116        return $this->currentClass;
117    }
118
119    /**
120     * @noinspection PhpDocMissingThrowsInspection
121     *
122     * @param string $parameterName
123     *
124     * @return KernelResource
125     */
126    protected function getCurrentInstance($parameterName = 'uri'): KernelResource
127    {
128        if (null === $this->currentInstance) {
129            /** @noinspection PhpUnhandledExceptionInspection */
130            $this->currentInstance = parent::getCurrentInstance($parameterName);
131        }
132
133        return $this->currentInstance;
134    }
135
136    private function createNewInstanceForm(): Form
137    {
138        $formContainer = new CreateInstanceContainer(
139            [$this->getCurrentClass()],
140            [
141                CreateInstanceContainer::CSRF_PROTECTION_OPTION => true,
142                'excludedProperties'                            => self::EXCLUDED_FIELDS,
143            ]
144        );
145
146        return $formContainer->getForm();
147    }
148
149    private function createExistingInstanceForm(): Form
150    {
151        $myFormContainer = new SignedFormInstance(
152            $this->getCurrentClass(),
153            $this->getCurrentInstance(),
154            [SignedFormInstance::CSRF_PROTECTION_OPTION => true]
155        );
156
157        $form = $myFormContainer->getForm();
158
159        $this->enrichWithSecretField($form);
160        $this->enrichWithRegenerateSecretAction($form);
161        $this->enrichWithRegenerateSecretDisclaimer($form);
162
163        return $form;
164    }
165
166    private function enrichWithSecretField(Form $form): void
167    {
168        $secret = $form->getElement(UriHelper::encode(DataStore::PROPERTY_OAUTH_SECRET));
169        $secret->addAttribute('readonly', 'readonly');
170        $secret->addClass('copy-to-clipboard');
171        $secret->addAttribute(
172            'data-copy-success-feedback',
173            __('Oauth consumer secret has been copied to the clipboard')
174        );
175
176        foreach (self::EXCLUDED_FIELDS as $excludedField) {
177            $form->removeElement(UriHelper::encode($excludedField));
178        }
179
180        $form->addElement($secret);
181    }
182
183    private function enrichWithRegenerateSecretAction(Form $form): void
184    {
185        /** @noinspection PhpUnhandledExceptionInspection */
186        $regenerateButton = FormFactory::getElement(self::SECRET_REGENERATION_KEY, 'Button');
187        $regenerateButton->addClass('small form-submitter');
188        $regenerateButton->addAttribute('style', 'width: 100%;');
189        $regenerateButton->setValue(__('Generate a new secret key'));
190
191        $form->addElement($regenerateButton);
192    }
193
194    private function enrichWithRegenerateSecretDisclaimer(Form $form): void
195    {
196        /** @noinspection PhpUnhandledExceptionInspection */
197        $regenerateDisclaimer = FormFactory::getElement('regenerate_disclaimer', 'Free');
198        $regenerateDisclaimer->setValue(
199            __('Please consider that generating a new key will replace the previously generated one.')
200        );
201
202        $form->addElement($regenerateDisclaimer);
203    }
204
205    /**
206     * @param Form $form
207     *
208     * @throws SecretKeyGenerationException
209     */
210    private function handleNewInstanceSubmission(Form $form): void
211    {
212        if ($form->isSubmited() && $form->isValid()) {
213            $values                                   = $this->extractValues($form);
214            $values[DataStore::PROPERTY_OAUTH_SECRET] = $this->getSecretKeyService()->generate();
215
216            $instance = $this->createInstance([$this->getCurrentClass()], $values);
217
218            $this->setData('message', __('%s created', $instance->getLabel()));
219            $this->setData('reload', true);
220        }
221    }
222
223    /**
224     * @param Form $form
225     *
226     * @throws FormDataBindingException
227     */
228    private function handleExistingInstanceSubmission(Form $form): void
229    {
230        if ($form->isSubmited() && $form->isValid()) {
231            $values = $this->extractValues($form);
232
233            $requestBody = $this->getPsrRequest()->getParsedBody();
234
235            if (!empty($requestBody[self::SECRET_REGENERATION_KEY])) {
236                $values[DataStore::PROPERTY_OAUTH_SECRET] = $this->getSecretKeyService()->generate();
237            }
238
239            $this->updateCurrentInstance($values);
240
241            $this->setData('message', __('Instance saved'));
242            $this->setData('reload', true);
243        }
244    }
245
246    private function extractValues(Form $form): array
247    {
248        $values = $form->getValues();
249        $values = array_diff_key($values, array_flip(self::EXCLUDED_FIELDS));
250
251        return $values;
252    }
253
254    /**
255     * @param array $values
256     *
257     * @throws FormDataBindingException
258     */
259    private function updateCurrentInstance(array $values): void
260    {
261        (new FormDataBinder($this->getCurrentInstance()))->bind($values);
262    }
263
264    private function getSecretKeyService(): SecretKeyServiceInterface
265    {
266        /** @noinspection PhpIncompatibleReturnTypeInspection */
267        return $this->getServiceLocator()->get(SecretKeyServiceInterface::class);
268    }
269}