Skip to content

Commit 416b8f8

Browse files
Current Users or ALL Users getting deleted in an Update (#906)
* fixed: when updating a user permission by DELETING a user from a certain role, it drops ALL the users. Also if you add a user to a role it will still keep the role on previously added users * updated the code so that if datbase selected is empty we show feedback there is nothing to fetch and do not provide labels and also enable to save users to role even though db is empty --------- Co-authored-by: Niels de Jong <[email protected]>
1 parent 2a7105f commit 416b8f8

File tree

2 files changed

+96
-62
lines changed

2 files changed

+96
-62
lines changed

src/extensions/rbac/RBACManagementModal.tsx

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const RBACManagementModal = ({ open, handleClose, currentRole, createNoti
3232
const [allowCompleted, setAllowCompleted] = useState(false);
3333
const [usersCompleted, setUsersCompleted] = useState(false);
3434
const [failed, setFailed] = useState(false);
35+
const [isDatabaseEmpty, setIsDatabaseEmpty] = useState(false);
3536

3637
useEffect(() => {
3738
if (!open) {
@@ -75,35 +76,48 @@ export const RBACManagementModal = ({ open, handleClose, currentRole, createNoti
7576

7677
const handleDatabaseSelect = (selectedOption) => {
7778
setSelectedDatabase(selectedOption.value);
78-
retrieveLabelsList(driver, selectedOption.value, (records) => parseLabelsList(selectedOption.value, records));
79+
setLabels([]);
80+
setAllowList([]);
81+
setDenyList([]);
82+
retrieveLabelsList(driver, selectedOption.value, (records) => {
83+
if (records.length === 0) {
84+
setIsDatabaseEmpty(true);
85+
} else {
86+
parseLabelsList(selectedOption.value, records);
87+
setIsDatabaseEmpty(false);
88+
}
89+
});
7990
};
8091

8192
const handleSave = async () => {
8293
createNotification('Updating', `Access for role '${currentRole}' is being updated, please wait...`);
83-
console.log(selectedUsers);
84-
updateUsers(
85-
driver,
86-
currentRole,
87-
neo4jUsers,
88-
selectedUsers,
89-
() => setUsersCompleted(true),
90-
(failReason) => setFailed(`Operation 'ROLE-USER ASSIGNMENT' failed.\n Reason: ${failReason}`)
91-
);
92-
93-
if (selectedDatabase) {
94-
const nonFixedDenyList = denyList.filter((n) => !fixedDenyList.includes(n));
95-
const nonFixedAllowList = allowList.filter((n) => !fixedDenyList.includes(n));
96-
updatePrivileges(
94+
try {
95+
await updateUsers(
9796
driver,
98-
selectedDatabase,
9997
currentRole,
100-
labels,
101-
nonFixedDenyList,
102-
Operation.DENY,
103-
() => setDenyCompleted(true),
104-
(failReason) => setFailed(`Operation 'DENY LABEL ACCESS' failed.\n Reason: ${failReason}`)
105-
).then(() => {
106-
updatePrivileges(
98+
neo4jUsers,
99+
selectedUsers,
100+
() => setUsersCompleted(true),
101+
(failReason) => setFailed(`Operation 'ROLE-USER ASSIGNMENT' failed.\n Reason: ${failReason}`)
102+
);
103+
104+
if (selectedDatabase && labels.length > 0) {
105+
// Check if there are labels to update
106+
const nonFixedDenyList = denyList.filter((n) => !fixedDenyList.includes(n));
107+
const nonFixedAllowList = allowList.filter((n) => !fixedDenyList.includes(n));
108+
109+
await updatePrivileges(
110+
driver,
111+
selectedDatabase,
112+
currentRole,
113+
labels,
114+
nonFixedDenyList,
115+
Operation.DENY,
116+
() => setDenyCompleted(true),
117+
(failReason) => setFailed(`Operation 'DENY LABEL ACCESS' failed.\n Reason: ${failReason}`)
118+
);
119+
120+
await updatePrivileges(
107121
driver,
108122
selectedDatabase,
109123
currentRole,
@@ -113,14 +127,18 @@ export const RBACManagementModal = ({ open, handleClose, currentRole, createNoti
113127
() => setAllowCompleted(true),
114128
(failReason) => setFailed(`Operation 'ALLOW LABEL ACCESS' failed.\n Reason: ${failReason}`)
115129
);
116-
});
117-
} else {
118-
// Since there is no database selected, we don't run the DENY/ALLOW queries.
119-
// We just mark them as completed so the success message shows up.
120-
setDenyCompleted(true);
121-
setAllowCompleted(true);
130+
} else {
131+
// Since there is no database or labels selected, we don't run the DENY/ALLOW queries.
132+
// We just mark them as completed so the success message shows up.
133+
setDenyCompleted(true);
134+
setAllowCompleted(true);
135+
}
136+
} catch (error) {
137+
// Handle any errors that occur during the update process
138+
createNotification('error', `An error occurred: ${error.message}`);
139+
} finally {
140+
handleClose();
122141
}
123-
handleClose();
124142
};
125143

126144
return (
@@ -171,8 +189,13 @@ export const RBACManagementModal = ({ open, handleClose, currentRole, createNoti
171189
onChange: handleDatabaseSelect,
172190
}}
173191
/>
192+
{selectedDatabase && isDatabaseEmpty && (
193+
<p style={{ color: 'red' }}>
194+
This database is currently empty. Please select a different database or add labels to manage access.
195+
</p>
196+
)}
174197
</div>
175-
{selectedDatabase && loaded && (
198+
{selectedDatabase && !isDatabaseEmpty && loaded && (
176199
<>
177200
<br />
178201
<div style={{ display: 'flex', justifyContent: 'space-between' }}>

src/extensions/rbac/RBACUtils.ts

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -217,15 +217,24 @@ export const retrieveNeo4jUsers = (driver, currentRole, setNeo4jUsers, setRoleUs
217217
* @param setLabels callback to update the list of labels.
218218
*/
219219
export function retrieveLabelsList(driver, database: any, setLabels: (records: any) => void) {
220-
runCypherQuery(
221-
driver,
222-
database.value,
223-
'CALL db.labels()',
224-
{},
225-
1000,
226-
() => {},
227-
(records) => setLabels(records)
228-
);
220+
let labelsSet = false; // Flag to track if setLabels was called
221+
222+
// Wrapper around the original setLabels to set the flag when called
223+
const wrappedSetLabels = (records) => {
224+
labelsSet = true;
225+
setLabels(records);
226+
};
227+
228+
runCypherQuery(driver, database, 'CALL db.labels()', {}, 1000, () => {}, wrappedSetLabels)
229+
.then(() => {
230+
if (!labelsSet) {
231+
setLabels([]);
232+
}
233+
})
234+
.catch((error) => {
235+
console.error('Error retrieving labels:', error);
236+
setLabels([]);
237+
});
229238
}
230239

231240
/**
@@ -265,34 +274,36 @@ export async function updateUsers(driver, currentRole, allUsers, selectedUsers,
265274
`REVOKE ROLE ${currentRole} FROM ${escapedAllUsers}`,
266275
{},
267276
1000,
268-
(status) => {
277+
async (status) => {
269278
globalStatus = status;
279+
if (globalStatus == QueryStatus.NO_DATA || globalStatus == QueryStatus.COMPLETE) {
280+
// TODO: Neo4j is very slow in updating after the previous query, even though it is technically a finished query.
281+
// We build in an artificial delay... This must be improved the future.
282+
setTimeout(async () => {
283+
if (selectedUsers.length > 0) {
284+
const escapedSelectedUsers = selectedUsers.map((user) => `\`${user}\``).join(',');
285+
await runCypherQuery(
286+
driver,
287+
'system',
288+
`GRANT ROLE ${currentRole} TO ${escapedSelectedUsers};`,
289+
{},
290+
1000,
291+
(status) => {
292+
if (status == QueryStatus.NO_DATA || QueryStatus.COMPLETE) {
293+
onSuccess();
294+
}
295+
}
296+
);
297+
} else {
298+
onSuccess();
299+
}
300+
}, 2000);
301+
}
270302
},
271303
(records) => {
272304
if (records && records[0] && records[0].error) {
273305
onFail(records[0].error);
274306
}
275307
}
276308
);
277-
if (globalStatus == QueryStatus.NO_DATA || globalStatus == QueryStatus.COMPLETE) {
278-
// TODO: Neo4j is very slow in updating after the previous query, even though it is technically a finished query.
279-
// We build in an artificial delay...
280-
if (selectedUsers.length > 0) {
281-
const escapedSelectedUsers = selectedUsers.map((user) => `\`${user}\``).join(',');
282-
await runCypherQuery(
283-
driver,
284-
'system',
285-
`GRANT ROLE ${currentRole} TO ${escapedSelectedUsers}`,
286-
{},
287-
1000,
288-
(status) => {
289-
if (status == QueryStatus.NO_DATA || QueryStatus.COMPLETE) {
290-
onSuccess();
291-
}
292-
}
293-
);
294-
} else {
295-
onSuccess();
296-
}
297-
}
298309
}

0 commit comments

Comments
 (0)