Skip to content

Commit

Permalink
Restrict startsWith comparisons in CheckboxTree more (#1126)
Browse files Browse the repository at this point in the history
* Restrict startsWith comparisons in CheckboxTree more

To fix a bug where checking user.biometric would also check user.biometric_health

* Update changelog
  • Loading branch information
allisonking authored Sep 30, 2022
1 parent 3a929cf commit 0cd4d77
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The types of changes are:
### Fixed

* Fixed the "help" link in the UI header [#1078](https://github.com/ethyca/fides/pull/1078)
* Fixed a bug in Data Category Dropdowns where checking i.e. `user.biometric` would also check `user.biometric_health` [#1126](https://github.com/ethyca/fides/pull/1126)

### Security

Expand Down
13 changes: 13 additions & 0 deletions clients/ctl/admin-ui/__tests__/checkbox-tree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const MOCK_NODES = [
value: "grandparent.aunt",
children: [],
},
{
label: "aunt_second",
value: "grandparent.aunt_second",
children: [],
},
],
},
{
Expand Down Expand Up @@ -236,6 +241,7 @@ describe("Checkbox tree", () => {
"grandparent.parent.child",
"grandparent.parent.sibling",
"grandparent.aunt",
"grandparent.aunt_second",
].sort()
);
expect(
Expand All @@ -254,6 +260,13 @@ describe("Checkbox tree", () => {
(d) => d.value
)
).toEqual(["grandparent.parent.child"]);

// make sure aunt_second does not sneak in
expect(
getDescendantsAndCurrent(MOCK_NODES, "grandparent.aunt").map(
(d) => d.value
)
).toEqual(["grandparent.aunt"]);
});

it("can determine if an ancestor is selected", () => {
Expand Down
24 changes: 19 additions & 5 deletions clients/ctl/admin-ui/src/features/common/CheckboxTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ export const ancestorIsSelected = (selected: string[], nodeName: string) => {
return intersection.length > 0;
};

const matchNodeOrDescendant = (value: string, match: string) => {
if (value === match) {
return true;
}
if (value.startsWith(`${match}.`)) {
return true;
}
return false;
};

export const getDescendantsAndCurrent = (
nodes: TreeNode[],
nodeName: string,
Expand All @@ -46,7 +56,7 @@ export const getDescendantsAndCurrent = (
if (node.children) {
getDescendantsAndCurrent(node.children, nodeName, descendants);
}
if (node.value.startsWith(nodeName)) {
if (matchNodeOrDescendant(node.value, nodeName)) {
descendants.push(node);
}
});
Expand Down Expand Up @@ -151,8 +161,10 @@ const CheckboxTree = ({ nodes, selected, onSelected }: CheckboxTreeProps) => {
let newSelected: string[] = [];
if (checked.indexOf(node.value) >= 0) {
// take advantage of dot notation here for unchecking children
newChecked = checked.filter((s) => !s.startsWith(node.value));
newSelected = selected.filter((s) => !s.startsWith(node.value));
newChecked = checked.filter((s) => !matchNodeOrDescendant(s, node.value));
newSelected = selected.filter(
(s) => !matchNodeOrDescendant(s, node.value)
);
} else {
// we need to mark all descendants as checked, though these are not
// technically 'selected'
Expand All @@ -169,7 +181,9 @@ const CheckboxTree = ({ nodes, selected, onSelected }: CheckboxTreeProps) => {
const handleExpanded = (node: TreeNode) => {
if (expanded.indexOf(node.value) >= 0) {
// take advantage of dot notation here for unexpanding children
setExpanded(expanded.filter((c) => !c.startsWith(node.value)));
setExpanded(
expanded.filter((c) => !matchNodeOrDescendant(c, node.value))
);
} else {
setExpanded([...expanded, node.value]);
}
Expand All @@ -186,7 +200,7 @@ const CheckboxTree = ({ nodes, selected, onSelected }: CheckboxTreeProps) => {
const isIndeterminate =
isChecked &&
node.children.length > 0 &&
checked.filter((s) => s.startsWith(node.value)).length !==
checked.filter((s) => s.startsWith(`${node.value}.`)).length + 1 !==
thisDescendants.length;
const isDisabled = ancestorIsSelected(selected, node.value);

Expand Down

0 comments on commit 0cd4d77

Please sign in to comment.