Skip to content

Commit 9c73a54

Browse files
authored
Tags api calls return 401 for non admin users (#1241)
* feat: tags module do not use proxy api * tests: Tags controller
1 parent 08262b9 commit 9c73a54

File tree

5 files changed

+231
-6
lines changed

5 files changed

+231
-6
lines changed

config/routes.php

+20
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,26 @@
275275
['controller' => 'Tags', 'action' => 'index'],
276276
['_name' => 'tags:index']
277277
);
278+
$routes->connect(
279+
'/tags/delete/{id}',
280+
['controller' => 'Tags', 'action' => 'delete'],
281+
['pass' => ['id'], '_name' => 'tags:delete']
282+
);
283+
$routes->connect(
284+
'/tags/create',
285+
['controller' => 'Tags', 'action' => 'create'],
286+
['_name' => 'tags:create']
287+
);
288+
$routes->connect(
289+
'/tags/patch/{id}',
290+
['controller' => 'Tags', 'action' => 'patch'],
291+
['pass' => ['id'], '_name' => 'tags:patch']
292+
);
293+
$routes->connect(
294+
'/tags/search',
295+
['controller' => 'Tags', 'action' => 'search'],
296+
['_name' => 'tags:search']
297+
);
278298

279299
// view resource by id / uname
280300
$routes->connect(

resources/js/app/components/tag-form/tag-form.vue

+5-5
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export default {
165165
headers,
166166
method: 'GET',
167167
};
168-
const response = await fetch(`${BEDITA.base}/api/model/tags?filter[name]=${this.name}`, options);
168+
const response = await fetch(`${BEDITA.base}/tags/search?filter[name]=${this.name}`, options);
169169
const responseJson = await response.json();
170170
171171
return responseJson?.data?.length > 0;
@@ -212,11 +212,11 @@ export default {
212212
payload.data.id = this.obj.id;
213213
options.body = JSON.stringify(payload);
214214
options.method = 'PATCH'
215-
response = await fetch(`${BEDITA.base}/api/model/tags/${this.obj.id}`, options);
215+
response = await fetch(`${BEDITA.base}/tags/patch/${this.obj.id}`, options);
216216
} else {
217217
options.body = JSON.stringify(payload);
218218
options.method = 'POST'
219-
response = await fetch(`${BEDITA.base}/api/model/tags`, options);
219+
response = await fetch(`${BEDITA.base}/tags/create`, options);
220220
}
221221
if (response.status === 200) {
222222
if (this.redir) {
@@ -253,14 +253,14 @@ export default {
253253
const prefix = t`Error on deleting tag`;
254254
try {
255255
const options = {
256-
method: 'DELETE',
256+
method: 'POST',
257257
credentials: 'same-origin',
258258
headers: {
259259
'accept': 'application/json',
260260
'X-CSRF-Token': BEDITA.csrfToken,
261261
},
262262
};
263-
const response = await fetch(`${BEDITA.base}/api/model/tags/${this.obj.id}`, options);
263+
const response = await fetch(`${BEDITA.base}/tags/delete/${this.obj.id}`, options);
264264
if (response.status === 200) {
265265
this.$el.remove(this.$el);
266266
this.dialog?.hide();

resources/js/app/components/tag-picker/tag-picker.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export default {
153153
return callback(null, []);
154154
}
155155
156-
const res = await fetch(`${BEDITA.base}/api/model/tags?filter[query]=${searchQuery}&filter[enabled]=1&page_size=30`, API_OPTIONS);
156+
const res = await fetch(`${BEDITA.base}/tags/search?filter[query]=${searchQuery}&filter[enabled]=1&page_size=30`, API_OPTIONS);
157157
const json = await res.json();
158158
const tags = [...(json.data || [])];
159159

src/Controller/TagsController.php

+107
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
namespace App\Controller;
55

66
use App\Controller\Model\TagsController as ModelTagsController;
7+
use BEdita\SDK\BEditaClientException;
78
use Cake\Event\EventInterface;
89
use Cake\Http\Response;
10+
use Cake\Utility\Hash;
911

1012
/**
1113
* Tags Controller
@@ -21,6 +23,7 @@ public function initialize(): void
2123
{
2224
parent::initialize();
2325
$this->loadComponent('ProjectConfiguration');
26+
$this->Security->setConfig('unlockedActions', ['create', 'patch', 'delete']);
2427
}
2528

2629
/**
@@ -37,6 +40,110 @@ public function index(): ?Response
3740
return null;
3841
}
3942

43+
/**
44+
* Create new tag (ajax).
45+
*
46+
* @return \Cake\Http\Response|null
47+
*/
48+
public function create(): ?Response
49+
{
50+
$this->getRequest()->allowMethod(['post']);
51+
$this->viewBuilder()->setClassName('Json');
52+
$response = $error = null;
53+
try {
54+
$body = (array)$this->getRequest()->getData();
55+
$response = $this->apiClient->post('/model/tags', json_encode($body));
56+
} catch (BEditaClientException $e) {
57+
$error = $e->getMessage();
58+
$this->log($error, 'error');
59+
$this->set('error', $error);
60+
}
61+
$this->set('response', $response);
62+
$this->set('error', $error);
63+
$this->setSerialize(['response', 'error']);
64+
65+
return null;
66+
}
67+
68+
/**
69+
* Delete single tag (ajax).
70+
*
71+
* @param string $id Tag ID.
72+
* @return \Cake\Http\Response|null
73+
*/
74+
public function delete(string $id): ?Response
75+
{
76+
$this->getRequest()->allowMethod(['post']);
77+
$this->viewBuilder()->setClassName('Json');
78+
$response = $error = null;
79+
try {
80+
$this->apiClient->delete(sprintf('/model/tags/%s', $id));
81+
$response = 'ok';
82+
} catch (BEditaClientException $e) {
83+
$error = $e->getMessage();
84+
$this->log($error, 'error');
85+
$this->set('error', $error);
86+
}
87+
$this->set('response', $response);
88+
$this->set('error', $error);
89+
$this->setSerialize(['response', 'error']);
90+
91+
return null;
92+
}
93+
94+
/**
95+
* Save tag (ajax).
96+
*
97+
* @param string $id Tag ID.
98+
* @return \Cake\Http\Response|null
99+
*/
100+
public function patch(string $id): ?Response
101+
{
102+
$this->getRequest()->allowMethod(['patch']);
103+
$this->viewBuilder()->setClassName('Json');
104+
$response = $error = null;
105+
try {
106+
$body = (array)$this->getRequest()->getData();
107+
$this->apiClient->patch(sprintf('/model/tags/%s', $id), json_encode($body));
108+
$response = 'ok';
109+
} catch (BEditaClientException $e) {
110+
$error = $e->getMessage();
111+
$this->log($error, 'error');
112+
$this->set('error', $error);
113+
}
114+
$this->set('response', $response);
115+
$this->set('error', $error);
116+
$this->setSerialize(['response', 'error']);
117+
118+
return null;
119+
}
120+
121+
/**
122+
* Search tags (ajax)
123+
*
124+
* @return \Cake\Http\Response|null
125+
*/
126+
public function search(): ?Response
127+
{
128+
$this->getRequest()->allowMethod(['get']);
129+
$this->viewBuilder()->setClassName('Json');
130+
$data = $error = null;
131+
try {
132+
$query = $this->getRequest()->getQueryParams();
133+
$response = $this->apiClient->get('/model/tags', $query);
134+
$data = (array)Hash::get($response, 'data');
135+
} catch (BEditaClientException $e) {
136+
$error = $e->getMessage();
137+
$this->log($error, 'error');
138+
$this->set('error', $error);
139+
}
140+
$this->set('data', $data);
141+
$this->set('error', $error);
142+
$this->setSerialize(['data', 'error']);
143+
144+
return null;
145+
}
146+
40147
/**
41148
* @inheritDoc
42149
*/

tests/TestCase/Controller/TagsControllerTest.php

+98
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use BEdita\WebTools\ApiClientProvider;
88
use Cake\Http\ServerRequest;
99
use Cake\TestSuite\TestCase;
10+
use Cake\Utility\Hash;
1011

1112
/**
1213
* {@see \App\Controller\TagsController} Test Case
@@ -110,4 +111,101 @@ public function testBeforeRender(): void
110111
$this->controller->dispatchEvent('Controller.beforeRender');
111112
static::assertSame(['_name' => 'tags:index'], $this->controller->viewBuilder()->getVar('moduleLink'));
112113
}
114+
115+
/**
116+
* Test `create`, `patch`, `delete`, `search` methods
117+
*
118+
* @return void
119+
* @covers ::create()
120+
* @covers ::patch()
121+
* @covers ::delete()
122+
* @covers ::search()
123+
*/
124+
public function testMulti(): void
125+
{
126+
// create with error
127+
$this->setupController(['environment' => ['REQUEST_METHOD' => 'POST']]);
128+
$this->controller->create();
129+
static::assertNotEmpty($this->controller->viewBuilder()->getVar('error'));
130+
131+
// create ok
132+
$data = [
133+
'type' => 'tags',
134+
'attributes' => [
135+
'name' => 'my-dummy-test-tag',
136+
'label' => 'My Dummy Test Tag',
137+
'labels' => [
138+
'default' => 'My Dummy Test Tag',
139+
],
140+
'enabled' => false,
141+
],
142+
];
143+
$request = $this->controller->getRequest()->withData('data', $data);
144+
$this->controller->setRequest($request);
145+
$this->controller->create();
146+
static::assertEmpty($this->controller->viewBuilder()->getVar('error'));
147+
$response = $this->controller->viewBuilder()->getVar('response');
148+
static::assertNotEmpty($response);
149+
$id = $response['data']['id'];
150+
151+
// patch with error
152+
$this->setupController(['environment' => ['REQUEST_METHOD' => 'PATCH']]);
153+
$this->controller->getRequest()->withData('name', 'test');
154+
$this->controller->patch('test');
155+
static::assertNotEmpty($this->controller->viewBuilder()->getVar('error'));
156+
static::assertNotEquals('ok', $this->controller->viewBuilder()->getVar('response'));
157+
158+
// patch ok
159+
$data = [
160+
'id' => $id,
161+
'type' => 'tags',
162+
'attributes' => [
163+
'name' => 'my-dummy-test-tag',
164+
'label' => 'My Dummy Test Tag',
165+
'labels' => [
166+
'default' => 'My Dummy Test Tag',
167+
],
168+
'enabled' => true,
169+
],
170+
];
171+
$request = $this->controller->getRequest()->withData('data', $data);
172+
$this->controller->setRequest($request);
173+
$this->controller->patch($id);
174+
static::assertEquals('ok', $this->controller->viewBuilder()->getVar('response'));
175+
static::assertEmpty($this->controller->viewBuilder()->getVar('error'));
176+
177+
// search with error
178+
$this->setupController(['environment' => ['REQUEST_METHOD' => 'GET']]);
179+
$request = $this->controller->getRequest()->withQueryParams(['filter' => 'wrong']);
180+
$this->controller->setRequest($request);
181+
$this->controller->search();
182+
static::assertNotEmpty($this->controller->viewBuilder()->getVar('error'));
183+
184+
// search ok
185+
$request = $this->controller->getRequest()->withQueryParams(['filter' => ['name' => 'my-dummy-test-tag']]);
186+
$this->controller->setRequest($request);
187+
$this->controller->search();
188+
static::assertEmpty($this->controller->viewBuilder()->getVar('error'));
189+
$response = $this->controller->viewBuilder()->getVar('data');
190+
static::assertNotEmpty($response);
191+
$actual = (array)Hash::get($response, '0');
192+
static::assertNotEmpty($actual);
193+
static::assertEquals('my-dummy-test-tag', $actual['attributes']['name']);
194+
static::assertEquals('My Dummy Test Tag', $actual['attributes']['label']);
195+
static::assertEquals('My Dummy Test Tag', $actual['attributes']['labels']['default']);
196+
static::assertEquals(true, $actual['attributes']['enabled']);
197+
static::assertEquals($id, $actual['id']);
198+
199+
// delete with error
200+
$this->setupController(['environment' => ['REQUEST_METHOD' => 'POST']]);
201+
$this->controller->delete('test');
202+
static::assertNotEmpty($this->controller->viewBuilder()->getVar('error'));
203+
static::assertNotEquals('ok', $this->controller->viewBuilder()->getVar('response'));
204+
205+
// delete ok
206+
$this->setupController(['environment' => ['REQUEST_METHOD' => 'POST']]);
207+
$this->controller->delete($id);
208+
static::assertEquals('ok', $this->controller->viewBuilder()->getVar('response'));
209+
static::assertEmpty($this->controller->viewBuilder()->getVar('error'));
210+
}
113211
}

0 commit comments

Comments
 (0)