Skip to content

Commit bfbd85a

Browse files
Merge pull request #4183 from greenbone/add-enhance-error-feed-for-missing-detials
Enhance error message feed for missing details
2 parents a0d5ec2 + f4393fb commit bfbd85a

File tree

10 files changed

+345
-17
lines changed

10 files changed

+345
-17
lines changed

allowedSnakeCase.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ module.exports = [
176176
'family_list',
177177
'feed_event',
178178
'feed_type',
179+
'feed_owner_set',
180+
'feed_resources_access',
179181
'field_value',
180182
'filtered_count',
181183
'filter_func',

public/locales/gsa-de.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"About GSA": "Über GSA",
4949
"Access Complexity": "Zugangskomplexität",
5050
"Access Vector": "Zugangsvektor",
51+
"Access to the feed resources is currently restricted.": "Der Zugriff auf die Feed-Ressourcen ist derzeit eingeschränkt.",
5152
"Actions": "Aktionen",
5253
"Activate the \"attach\" option to allow changes here.": "Aktivieren Sie sie \"Anhängen\"-Option, um hier Änderungen vorzunehmen.",
5354
"Activate the \"include\" option to make changes here.": "Aktivieren Sie die \"Einfügen\"-Option, um hier Änderungen vorzunehmen.",
@@ -1690,6 +1691,7 @@
16901691
"The families selection is STATIC. New families will NOT automatically be added and considered.": "Die Familien-Auswahl ist STATISCH. Neue Familien werden NICHT automatisch hinzugefügt und berücksichtigt.",
16911692
"The family selection is DYNAMIC. New families will automatically be added and considered.": "Die Familien-Auswahl ist DYNAMISCH. Neue Familien werden automatisch hinzugefügt und berücksichtigt.",
16921693
"The family selection is STATIC. New families will NOT automatically be added and considered.": "Die Familien-Auswahl ist STATISCH. Neue Familien werden NICHT automatisch hinzugefügt und berücksichtigt.",
1694+
"The feed owner is currently not set.": "Der Feed-Besitzer ist derzeit nicht festgelegt.",
16931695
"The following filter is currently applied: ": "Angewandter Filter: ",
16941696
"The last": "Jeden letzten",
16951697
"The last {{weekday}} every month": "Jeden letzten {{weekday}} jeden Monat",
@@ -1721,6 +1723,7 @@
17211723
"This form received invalid values. Please check the inputs and submit again.": "Dieses Formular erhielt ungültige Werte. Bitte überprüfen Sie Ihre Eingaben und senden sie erneut ab.",
17221724
"This is an Alterable Audit. Reports may not relate to current Policy or Target!": "Dies ist ein änderbares Audit. Berichte könnten sich nicht auf die aktuelle Richtlinie oder das aktuelle Ziel beziehen!",
17231725
"This is an Alterable Task. Reports may not relate to current Scan Config or Target!": "Dies ist eine änderbare Aufgabe. Berichte könnten sich nicht auf die aktuelle Scan-Konfiguration oder das aktuelle Ziel beziehen!",
1726+
"This issue may be due to the feed not having completed its synchronization.\nPlease try again shortly.": "Dieses Problem könnte daran liegen, dass der Feed seine Synchronisation noch nicht abgeschlossen hat.\nBitte versuchen Sie es in Kürze erneut.",
17241727
"This setting is not alterable once task has been run at least once.": "Diese Einstellung ist nicht änderbar sobald die Aufgabe mindestens einmal ausgeführt wurde.",
17251728
"This setting is not alterable once the audit has been run at least once.": "Diese Einstellung ist nicht änderbar sobald das audit mindestens einmal ausgeführt wurde.",
17261729
"This web application uses cookies to store session information. The cookies are not stored on the server side hard disk and not submitted anywhere. They are lost when the session is closed or expired. The cookies are stored temporarily in your browser as well where you can examine the content.": "Diese Web-Anwendung nutzt Cookies, um Sitzungsinformationen zu speichern. Die Cookies werden nicht auf der serverseitigen Festplatte gespeichert und nirgendwohin übermittelt. Sie gehen verloren, wenn die Sitzung beendet wird oder ausläuft. Die Cookies werden außerdem temporär in Ihrem Browser gespeichert, wo Sie den Inhalt einsehen können.",

src/gmp/commands/__tests__/feedstatus.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import {describe, test, expect} from '@gsa/testing';
6+
import {describe, test, expect, testing} from '@gsa/testing';
77

88
import {createResponse, createHttp} from '../testing';
99

@@ -113,4 +113,45 @@ describe('FeedStatusCommand tests', () => {
113113

114114
await expect(cmd.checkFeedSync()).rejects.toThrow('Network error');
115115
});
116+
117+
describe('checkFeedOwnerAndPermissions', () => {
118+
test('should return feed owner and permissions', async () => {
119+
const response = createResponse({
120+
get_feeds: {
121+
get_feeds_response: {
122+
feed_owner_set: 0,
123+
feed_resources_access: 1,
124+
},
125+
},
126+
});
127+
128+
const fakeHttp = createHttp(response);
129+
const cmd = new FeedStatus(fakeHttp);
130+
131+
const result = await cmd.checkFeedOwnerAndPermissions();
132+
133+
expect(fakeHttp.request).toHaveBeenCalledWith('get', {
134+
args: {
135+
cmd: 'get_feeds',
136+
},
137+
});
138+
139+
expect(result.isFeedOwner).toBe(false);
140+
expect(result.isFeedResourcesAccess).toBe(true);
141+
});
142+
143+
test('should log an error when checkFeedSync fails', async () => {
144+
const fakeHttp = createHttp(Promise.reject(new Error('Network error')));
145+
const cmd = new FeedStatus(fakeHttp);
146+
147+
console.error = testing.fn();
148+
149+
await expect(cmd.checkFeedSync()).rejects.toThrow('Network error');
150+
151+
expect(console.error).toHaveBeenCalledWith(
152+
'Error checking if feed is syncing:',
153+
expect.any(Error),
154+
);
155+
});
156+
});
116157
});

src/gmp/commands/feedstatus.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,42 @@ export class FeedStatus extends HttpCommand {
8080
throw error;
8181
}
8282
}
83+
84+
/**
85+
* Checks if the current user is the owner of the feed and if they have access to feed resources.
86+
*
87+
* @async
88+
* @function checkFeedOwnerAndPermissions
89+
* @returns {Promise<Object>} An object containing two boolean properties:
90+
* - `isFeedOwner`: Indicates if the user is the owner of the feed.
91+
* - `isFeedResourcesAccess`: Indicates if the user has access to feed resources.
92+
* @throws Will throw an error if the HTTP request fails.
93+
*/
94+
async checkFeedOwnerAndPermissions() {
95+
try {
96+
const {
97+
data: {
98+
get_feeds: {
99+
get_feeds_response: {
100+
feed_owner_set: feedOwner,
101+
feed_resources_access: feedResourcesAccess,
102+
},
103+
},
104+
},
105+
} = await this.httpGet();
106+
107+
const feedOwnerBoolean = Boolean(feedOwner);
108+
const feedResourcesAccessBoolean = Boolean(feedResourcesAccess);
109+
110+
return {
111+
isFeedOwner: feedOwnerBoolean,
112+
isFeedResourcesAccess: feedResourcesAccessBoolean,
113+
};
114+
} catch (error) {
115+
console.error('Error checking feed owner and permissions:', error);
116+
throw error;
117+
}
118+
}
83119
}
84120

85121
registerCommand('feedstatus', FeedStatus);

src/gmp/http/__tests__/http.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/* SPDX-FileCopyrightText: 2024 Greenbone AG
2+
*
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import {describe, test, expect, testing, beforeEach} from '@gsa/testing';
7+
8+
import Http from 'gmp/http/http';
9+
import Rejection from '../rejection';
10+
import {vi} from 'vitest';
11+
12+
const mockGetFeedAccessStatusMessage = testing.fn();
13+
const mockFindActionInXMLString = testing.fn();
14+
15+
vi.mock('gmp/http/utils', async () => {
16+
return {
17+
getFeedAccessStatusMessage: () => mockGetFeedAccessStatusMessage(),
18+
findActionInXMLString: () => mockFindActionInXMLString(),
19+
};
20+
});
21+
22+
global.XMLHttpRequest = testing.fn(() => ({
23+
open: testing.fn(),
24+
send: testing.fn(),
25+
setRequestHeader: testing.fn(),
26+
status: 0,
27+
responseText: '',
28+
onreadystatechange: null,
29+
readyState: 0,
30+
}));
31+
32+
describe('Http', () => {
33+
describe('handleResponseError', () => {
34+
let instance;
35+
let reject;
36+
let resolve;
37+
let xhr;
38+
let options;
39+
40+
beforeEach(() => {
41+
instance = new Http();
42+
resolve = testing.fn();
43+
reject = testing.fn();
44+
xhr = {status: 500};
45+
options = {};
46+
testing.clearAllMocks();
47+
});
48+
test('should handle response error without error handlers', async () => {
49+
await instance.handleResponseError(xhr, reject, resolve, options);
50+
expect(reject).toHaveBeenCalledWith(expect.any(Rejection));
51+
});
52+
53+
test('401 error should call error handler', async () => {
54+
xhr.status = 401;
55+
await instance.handleResponseError(resolve, reject, xhr, options);
56+
expect(reject).toHaveBeenCalledWith(expect.any(Rejection));
57+
expect(reject.mock.calls[0][0].reason).toBe(
58+
Rejection.REASON_UNAUTHORIZED,
59+
);
60+
});
61+
62+
test('404 error should append additional message', async () => {
63+
xhr.status = 404;
64+
const additionalMessage = 'Additional feed access status message';
65+
mockGetFeedAccessStatusMessage.mockResolvedValue(additionalMessage);
66+
mockFindActionInXMLString.mockReturnValue(true);
67+
68+
await instance.handleResponseError(resolve, reject, xhr, options);
69+
expect(mockGetFeedAccessStatusMessage).toHaveBeenCalled();
70+
71+
expect(reject).toHaveBeenCalledWith(expect.any(Rejection));
72+
const rejectedResponse = reject.mock.calls[0][0];
73+
expect(rejectedResponse.message).toContain(additionalMessage);
74+
});
75+
76+
test('404 error should not append additional message', async () => {
77+
xhr.status = 404;
78+
mockFindActionInXMLString.mockReturnValue(false);
79+
80+
await instance.handleResponseError(resolve, reject, xhr, options);
81+
expect(mockGetFeedAccessStatusMessage).not.toHaveBeenCalled();
82+
83+
expect(reject).toHaveBeenCalledWith(expect.any(Rejection));
84+
const rejectedResponse = reject.mock.calls[0][0];
85+
expect(rejectedResponse.message).toContain('Unknown Error');
86+
});
87+
});
88+
});

src/gmp/http/__tests__/utils.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/* SPDX-FileCopyrightText: 2024 Greenbone AG
2+
*
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import {describe, test, expect} from '@gsa/testing';
7+
8+
import {createResponse, createHttp} from 'gmp/commands/testing';
9+
10+
import {
11+
getFeedAccessStatusMessage,
12+
findActionInXMLString,
13+
} from 'gmp/http/utils';
14+
import {FeedStatus} from 'gmp/commands/feedstatus';
15+
16+
describe('Http', () => {
17+
describe('getFeedAccessStatusMessage', () => {
18+
const setupTest = async (feedOwnerSet, feedResourcesAccess) => {
19+
const response = createResponse({
20+
get_feeds: {
21+
get_feeds_response: {
22+
feed_owner_set: feedOwnerSet,
23+
feed_resources_access: feedResourcesAccess,
24+
},
25+
},
26+
});
27+
const fakeHttp = createHttp(response);
28+
const feedCmd = new FeedStatus(fakeHttp);
29+
await feedCmd.checkFeedOwnerAndPermissions();
30+
return getFeedAccessStatusMessage(fakeHttp);
31+
};
32+
33+
test.each([
34+
[
35+
0,
36+
1,
37+
'The feed owner is currently not set. This issue may be due to the feed not having completed its synchronization.\nPlease try again shortly.',
38+
],
39+
[
40+
1,
41+
0,
42+
'Access to the feed resources is currently restricted. This issue may be due to the feed not having completed its synchronization.\nPlease try again shortly.',
43+
],
44+
[1, 1, ''],
45+
])(
46+
'should return correct message for feedOwnerSet: %i, feedResourcesAccess: %i',
47+
async (feedOwnerSet, feedResourcesAccess, expectedMessage) => {
48+
const message = await setupTest(feedOwnerSet, feedResourcesAccess);
49+
expect(message).toBe(expectedMessage);
50+
},
51+
);
52+
});
53+
54+
describe('findActionInXMLString', () => {
55+
test.each([
56+
{
57+
description:
58+
'should return true if an action is found in the XML string',
59+
xmlString: `
60+
<response>
61+
<action>Run Wizard</action>
62+
</response>
63+
`,
64+
actions: ['Run Wizard', 'Create Task', 'Save Task'],
65+
expected: true,
66+
},
67+
{
68+
description:
69+
'should return false if no action is found in the XML string',
70+
xmlString: `
71+
<response>
72+
<action>Delete Task</action>
73+
</response>
74+
`,
75+
actions: ['Run Wizard', 'Create Task', 'Save Task'],
76+
expected: false,
77+
},
78+
{
79+
description: 'should return false if the XML string is empty',
80+
xmlString: '',
81+
actions: ['Run Wizard', 'Create Task', 'Save Task'],
82+
expected: false,
83+
},
84+
{
85+
description: 'should return false if the actions array is empty',
86+
xmlString: `
87+
<response>
88+
<action>Run Wizard</action>
89+
</response>
90+
`,
91+
actions: [],
92+
expected: false,
93+
},
94+
])('$description', ({xmlString, actions, expected}) => {
95+
expect(findActionInXMLString(xmlString, actions)).toBe(expected);
96+
});
97+
});
98+
});

src/gmp/http/http.js

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import Response from './response';
1414

1515
import DefaultTransform from './transform/default';
1616

17-
import {buildUrlParams} from './utils';
17+
import {
18+
buildUrlParams,
19+
getFeedAccessStatusMessage,
20+
findActionInXMLString,
21+
} from './utils';
1822

1923
const log = logger.getLogger('gmp.http');
2024

@@ -157,26 +161,50 @@ class Http {
157161
}
158162
}
159163

160-
handleResponseError(resolve, reject, xhr, options) {
161-
let promise = Promise.reject(xhr);
164+
async handleResponseError(_resolve, reject, xhr, options) {
165+
try {
166+
let request = xhr;
162167

163-
for (const interceptor of this.errorHandlers) {
164-
promise = promise.catch(interceptor);
165-
}
168+
for (const interceptor of this.errorHandlers) {
169+
try {
170+
await interceptor(request);
171+
} catch (err) {
172+
request = err;
173+
}
174+
}
166175

167-
promise.catch(request => {
168176
const {status} = request;
169177
const rej = new Rejection(
170178
request,
171179
status === 401 ? Rejection.REASON_UNAUTHORIZED : Rejection.REASON_ERROR,
172180
);
173-
try {
174-
reject(this.transformRejection(rej, options));
175-
} catch (error) {
176-
log.error('Could not transform rejection', error, rej);
177-
reject(rej);
181+
182+
let rejectedResponse = await this.transformRejection(rej, options);
183+
184+
const actionsRequiringFeedAccess = [
185+
'Run Wizard',
186+
'Create Task',
187+
'Save Task',
188+
'Create Target',
189+
'Save Target',
190+
];
191+
192+
if (
193+
rej.status === 404 &&
194+
findActionInXMLString(request.response, actionsRequiringFeedAccess)
195+
) {
196+
const additionalMessage = await getFeedAccessStatusMessage(this);
197+
198+
if (additionalMessage) {
199+
rejectedResponse.message = `${rejectedResponse.message}\n${additionalMessage}`;
200+
}
178201
}
179-
});
202+
203+
reject(rejectedResponse);
204+
} catch (error) {
205+
log.error('Could not handle response error', error);
206+
reject(error);
207+
}
180208
}
181209

182210
handleRequestError(resolve, reject, xhr, options) {

0 commit comments

Comments
 (0)