Skip to content

Add 'Permissions' main page - #1145

Open
duzda wants to merge 2 commits into
freeipa:mainfrom
duzda:permissions
Open

Add 'Permissions' main page#1145
duzda wants to merge 2 commits into
freeipa:mainfrom
duzda:permissions

Conversation

@duzda

@duzda duzda commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Add the 'Permissions' page under the 'Role-based access control' section, allowing users to list, search, add, and delete permissions.

Changes:

  • Create Permissions main page with table, search, and pagination
  • Add AddPermissionModal and DeletePermissionsModal components
  • Add permissions navigation route and menu item
  • Add rpcPermissions service with find/show/add/delete/search endpoints
  • Add Permission data type and related utilities
  • Add reusable SelectMultiTypeaheadCheckbox form component
  • Extend SimpleSelector to support returning a custom property
  • Add findGroups query to rpcUserGroups service

Assisted-by: Cursor cursoragent@cursor.com

Summary by Sourcery

Introduce a new Permissions management page under role-based access control, backed by RPC services and utilities for listing, searching, adding, and deleting permissions.

New Features:

  • Add a Permissions main page with table view, bulk selection, search, and pagination controls.
  • Add modals for creating permissions and confirming bulk deletion of selected permissions.
  • Add a reusable SelectMultiTypeaheadCheckbox form control for multi-select, typeahead, and optional option creation.

Enhancements:

  • Extend SimpleSelector to support returning a custom property from selected options.
  • Add Permission data type definitions and conversion utilities for handling API objects.
  • Add a findGroups query to the user groups RPC service for populating group selections in the permissions UI.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 6 issues, and left some high level feedback:

  • In SelectMultiTypeaheadCheckbox, avoid calling setLocalOptions directly during render when props change and remove the console.log statements; instead sync localOptions from options via a dedicated useEffect to prevent unnecessary re-renders and potential state update warnings.
  • The getPermissionsFullData and searchPermissionsEntries endpoints in rpcPermissions share almost identical two-step find/show logic; consider extracting this into a shared helper to reduce duplication and make future changes to the query behavior easier to maintain.
  • In AddPermissionModal and permissionsUtils, the Permission fields extratargetfilter and memberof are treated as arrays in some places and as strings in the Permission type; align the Permission interface and helpers with their actual usage (arrays vs. strings) to avoid type confusion and potential runtime issues.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In SelectMultiTypeaheadCheckbox, avoid calling setLocalOptions directly during render when props change and remove the console.log statements; instead sync localOptions from options via a dedicated useEffect to prevent unnecessary re-renders and potential state update warnings.
- The getPermissionsFullData and searchPermissionsEntries endpoints in rpcPermissions share almost identical two-step find/show logic; consider extracting this into a shared helper to reduce duplication and make future changes to the query behavior easier to maintain.
- In AddPermissionModal and permissionsUtils, the Permission fields extratargetfilter and memberof are treated as arrays in some places and as strings in the Permission type; align the Permission interface and helpers with their actual usage (arrays vs. strings) to avoid type confusion and potential runtime issues.

## Individual Comments

### Comment 1
<location path="src/utils/datatypes/globalDataTypes.ts" line_range="233-242" />
<code_context>
   description: string;
 }

+export interface Permission {
+  cn: string;
+  dn: string;
+  ipapermright: string[];
+  ipapermbindruletype: string;
+  attrs: string[];
+  ipapermlocation: string;
+  extratargetfilter: string;
+  ipapermtargetfilter: string;
+  type: string;
+  memberof: string;
+  targetgroup: string;
+  memberof_privilege: string[];
+  ipapermissiontype: string[];
+}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Permission interface field types are inconsistent with usage, leading to type/runtime mismatches.

Several fields in `Permission` don’t match how they’re used elsewhere:
- `cn` is typed as `string` but accessed as `permission.cn[0]`, and `cnType` indicates it should be an array.
- `extratargetfilter` and `memberof` are typed as `string`, but `AddPermissionModal`/`rpcPermissions`/`PermissionAddPayload` use `string[]`.
- `attrs` is required `string[]`, but callers pass `undefined` when empty and `apiToPermission` treats it as optional.
These mismatches will lead to `any` casts or runtime errors. Please update the interface to match usage (e.g. `cn: string[]`, `extratargetfilter?: string[]`, `memberof?: string[]`, `attrs?: string[]`) and adjust helpers like `createEmptyPermission` accordingly.
</issue_to_address>

### Comment 2
<location path="src/components/layouts/SimpleSelector.tsx" line_range="37-39" />
<code_context>
   const onSelect = (
     _event: React.MouseEvent<Element, MouseEvent> | undefined,
-    value: string | number | undefined
+    value: SelectOptionProps
   ) => {
-    props.onSelectedChange(value as string);
-    setSelected(value as string);
+    props.onSelectedChange(value[props.returnedProperty ?? "value"]);
+    setSelected(value.value);
     setIsOpen(false);
</code_context>
<issue_to_address>
**issue (bug_risk):** SimpleSelector now passes/receives full option objects but still uses a string `selected` value, which is likely incompatible with the underlying Select component.

Previously `SelectOption` used a primitive `value` (`string | number`), and `SimpleSelector.selected` matched that. Now `SelectOption` passes the full option object as `value`, but `SimpleSelector.selected` is still a `string`. Since PatternFly `Select` expects `selected` to match the option `value` (now `SelectOptionProps`), this mismatch can break selection highlighting and controlled behavior.

`onSelectedChange` also returns `value[returnedProperty]` (e.g. `key`), while internal state uses `value.value` (the label), so the component stores one representation and emits another, which can cause desync between displayed selection and the actual selected key.

Please either keep `value` as a primitive (`option.value`) with `onSelect(value: string | number)`, or make `selected` consistently use `SelectOptionProps` (or the same type as `value`) and update callers accordingly.
</issue_to_address>

### Comment 3
<location path="src/pages/Permissions/Permissions.tsx" line_range="251-260" />
<code_context>
+    if (isSelected) {
+      newSelectedPermissions = JSON.parse(JSON.stringify(selectedPermissions));
+      for (let i = 0; i < permissions.length; i++) {
+        if (selectedPermissions.find((s) => s.cn[0] === permissions[i].cn[0])) {
+          continue;
+        }
+        newSelectedPermissions.push(permissions[i]);
+      }
+    } else {
+      for (let i = 0; i < selectedPermissions.length; i++) {
+        let found = false;
+        for (let ii = 0; ii < permissions.length; ii++) {
+          if (selectedPermissions[i].cn[0] === permissions[ii].cn[0]) {
+            found = true;
+            break;
</code_context>
<issue_to_address>
**issue (bug_risk):** Selection logic assumes `Permission.cn` is indexable (`cn[0]`), which contradicts the interface and can break equality checks.

`updateSelectedPermissions` compares `s.cn[0]` to `permissions[i].cn[0]`, while `Permission.cn` is typed and used elsewhere as a `string` (e.g. `permission.cn` in `deletePermissions`). This mismatch means you’re either only comparing the first character, or the type definition is wrong. Please align usage and types: either compare full strings (`s.cn === permissions[i].cn`) if `cn` is a string, or change `Permission` (and related helpers) to use `cn: string[]` and update the deletion/selection logic accordingly.
</issue_to_address>

### Comment 4
<location path="src/pages/Permissions/Permissions.tsx" line_range="188-197" />
<code_context>
+    searchPermissions({
</code_context>
<issue_to_address>
**issue (bug_risk):** Search mutation only re-enables the search UI when `data` is present, so errors can leave the search disabled indefinitely.

In `submitSearchValue`, `setSearchIsDisabled(true)` is called before the mutation, but `setSearchIsDisabled(false)` only runs when `"data" in result`. If the mutation fails or returns an error-only shape, the handler never re-enables the search, leaving `isSearchActive` true and the UI effectively locked. Please handle the error path explicitly (e.g., `"error" in result` or a `.catch`) and move re-enabling the input into a `.finally` so it always runs.
</issue_to_address>

### Comment 5
<location path="src/components/Form/SelectMultiTypeaheadCheckbox.tsx" line_range="35-44" />
<code_context>
+  allowCreation,
+}: SelectMultiTypeaheadCheckboxProps) => {
+  // This is to allow mutations to options property
+  const [localOptions, setLocalOptions] =
+    useState<SelectOptionProps[]>(options);
+  console.log("LOCAL", localOptions);
+  console.log("OPTIONS", options);
+  // This one is actually shown, it can contain Create New options and no results
+  const [availableOptions, setAvailableOptions] =
+    useState<SelectOptionProps[]>(localOptions);
+  const [isOpen, setIsOpen] = useState(false);
+  const [inputValue, setInputValue] = useState<string>("");
+  const [focusedItemIndex, setFocusedItemIndex] = useState<number | null>(null);
+  const [activeItemId, setActiveItemId] = useState<string | null>(null);
+  const textInputRef = useRef<HTMLInputElement>(undefined);
+
+  if (options !== localOptions) {
+    setLocalOptions(options);
+  }
</code_context>
<issue_to_address>
**issue (bug_risk):** Directly calling `setLocalOptions` in render based on `options !== localOptions` risks unnecessary re-renders or loops.

Because this check runs in the render path, `options !== localOptions` will be true whenever a new array instance is passed, even if its contents are unchanged. Calling `setLocalOptions` during render can lead to repeated renders or render loops and makes state harder to reason about. Instead, mirror `options` into state in an effect:
```ts
useEffect(() => {
  setLocalOptions(options);
}, [options]);
```
so updates are applied after render.
</issue_to_address>

### Comment 6
<location path="src/utils/permissionsUtils.tsx" line_range="20-29" />
<code_context>
+  return { ipaObject, recordOnChange };
+};
+
+const simpleValues = new Set([
+  "cn",
+  "dn",
+  "ipapermbindruletype",
+  "ipapermlocation",
+  "extratargetfilter",
+  "ipapermtargetfilter",
+  "type",
+  "memberof",
+  "targetgroup",
+]);
+const dateValues = new Set([]);
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Conversion helpers mark `extratargetfilter` and `memberof` as simple scalar values, but other code uses them as arrays.

Because `simpleValues` includes `extratargetfilter` and `memberof`, `convertApiObj` will treat them as scalars, while `PermissionAddPayload`, `AddPermissionModal`, and `apiToPermission` all treat them as `string[]`. This inconsistency can lead to objects flipping between string and array shapes depending on which helper ran last. Please either:
- Remove these keys from `simpleValues` and treat them as arrays in `convertApiObj`, or
- Normalize them to a single agreed shape in `apiToPermission`/`createEmptyPermission` and adjust the UI code accordingly.

Suggested implementation:

```typescript
const simpleValues = new Set([
  "cn",
  "dn",
  "ipapermbindruletype",
  "ipapermlocation",
  "ipapermtargetfilter",
  "type",
  "targetgroup",
]);

```

If there is any remaining code that assumes `extratargetfilter` or `memberof` are plain strings (e.g. direct string operations without indexing into an array), those usages should be updated to work with `string[]` instead. Based on your comment, `PermissionAddPayload`, `AddPermissionModal`, and `createEmptyPermission`/`apiToPermission` are already aligned to `string[]`, so no further changes should be needed there.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/utils/datatypes/globalDataTypes.ts Outdated
Comment thread src/components/layouts/SimpleSelector.tsx
Comment thread src/pages/Permissions/Permissions.tsx Outdated
Comment thread src/pages/Permissions/Permissions.tsx Outdated
Comment thread src/components/TypeAheadWithCheckbox.tsx
Comment thread src/utils/permissionsUtils.tsx
@duzda

duzda commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

I've considered reusing some of the already in use typeaheads, however decided not to, as I would end up breaking some stuff and I need checkboxes.

@duzda duzda added the needs-review This PR is waiting on a review label Jul 30, 2026

@veronnicka veronnicka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi, i have some comments just from checking the UI, I will review the code next.

Behaviour:
When adding a new permission:
1.1. I select Custom in the Type attribute
1.2. I write some non-xistent subtree in the Subtree field
1.3. I switch the Type attribute to Automount
1.4. The text in the subtree field remains there and blocks the creation of the permission

Expected: The text is removed upon changing the Type

See:

Screencast.From.2026-07-30.12-38-05.mp4

Another one:
Issue: Unclickable “Create new” option in the Attributes field

Actual behavior:
When typing a value in the Attributes field that does not match any existing attribute, a “Create new” option appears. However, the option is not clickable.

Expected behavior:
Either the “Create new” option should be clickable and create a new attribute, or it should not be displayed if creating new attributes is not supported.

See:

Screencast.From.2026-07-30.12-34-17.mp4

Another one is, please add rules to the Input field in the Permission name ( the very first one). Otherwise, its failing on submission.

See:
Screenshot From 2026-07-30 13-14-56

@veronnicka

Copy link
Copy Markdown
Contributor

Another suggestions I have for the add modal:

When all attribute is clicked in the Granted Rights, it could automatically click all the checks
Screenshot From 2026-07-30 13-12-23

Some fields are failing upon submission, for example the Target group and wrong Subtree. Much cleaner approach would be to let the user know when filling out the fields.

@veronnicka veronnicka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my other comments as well, some are connected to the UI behaviour I already mentioned

Comment thread src/components/modals/PermissionModals/AddPermissionModal.tsx Outdated
Comment thread src/components/modals/PermissionModals/AddPermissionModal.tsx Outdated
Comment thread src/components/modals/PermissionModals/AddPermissionModal.tsx Outdated
Comment thread src/components/modals/PermissionModals/AddPermissionModal.tsx Outdated
@duzda

duzda commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

As

When all attribute is clicked in the Granted Rights, it could automatically click all the checks

I understand where you're coming from, but I think this should be done server-side if at all, otherwise we will show different data than the server does, which would be confusing. You may add all for testing purposes and remove it later, without you having to go and write down all the previous options. Yes, it's confusing, that you can have all and others at the same time, but I really don't want to show different data than what is provided through backend.

@duzda

duzda commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

@veronnicka, please re-visit, the tests are failing, but those are the common flaky ones.

@veronnicka

Copy link
Copy Markdown
Contributor

As

When all attribute is clicked in the Granted Rights, it could automatically click all the checks

I understand where you're coming from, but I think this should be done server-side if at all, otherwise we will show different data than the server does, which would be confusing. You may add all for testing purposes and remove it later, without you having to go and write down all the previous options. Yes, it's confusing, that you can have all and others at the same time, but I really don't want to show different data than what is provided through backend.

okay, thanks for explaining

@veronnicka veronnicka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks for the changes, I checked the UI as well, works as expected.

One thing that is still unclear to me, why create the create new attribute, when upon creation, I cannot add the new permission because of it. See:

Image

Is it a typing problem? From the user perspective there is not enough information for what Im doing wrong.

Also, are we waiting for the search fix before merging or just merge it as-is and deal with it in the #1139 PR?

Other than this, LGTM.

@duzda

duzda commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

This one should get merged first, but please ignore broken search.

@duzda

duzda commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Hi @veronnicka, the add option is there, because LDAP schema may contain whatever and may differ from what FreeIPA provides us as the information. Say you expand your schema, don't write your own plugin but still want to add a permission there, that is exactly the use-case where you'd use this.

You can try this out, in my case I've expanded the schema by location attribute:

  1. Create location.ldif file with the following contents:
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 2.25.1234567890.2.1 NAME 'location'
  DESC 'Custom privilege location'
  EQUALITY caseIgnoreMatchV
  SUBSTR caseIgnoreSubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'custom' )
-
add: objectClasses
objectClasses: ( 2.25.1234567890.2.2 NAME 'customPrivilegeLocation'
  DESC 'Custom privilege location class'
  SUP top AUXILIARY
  MAY ( location )
  X-ORIGIN 'custom' )
  1. Modify ldap schema
ldapmodify -D "cn=Directory Manager" -W -f location.ldif
  1. Run ipactl restart

Now you're able to create location attribute to anything, but others will fail.

image image image

(don't worry about privilege in the example, it works for all)

@carma12 carma12 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check my comments below. I'm still wrapping my head around this approach: it is functional, but probably there are some things that need to be reviewed further due to the missing parameters. I took the reference from the 'API browser' utility in old WebUI, but please double check this as it may be some inconsistencies.

Comment thread src/utils/datatypes/globalDataTypes.ts Outdated
ipapermbindruletype: string;
attrs: string[];
ipapermlocation: string;
extratargetfilter: string;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one and some other parameters (ipapermtargetfilter, and memberof) should be string[].

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edited the params

description: string;
}

export interface Permission {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing parameters: ipapermincludedattr (string[]), ipapermexcludedattr (string[]), ipapermdefaultattr (string[]), ipapermtargetto (string), ipapermtargetfrom (string), targetgroup (string), ipapermlocation (string, I think).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list differs between mod and add functions..., but yes, for some reason it's completely wrong, without me raising an eyebrow...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edited the params

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still a lot of missing parameters from the list, but not sure if it would make sense to still add them in case those are needed in the future? Personally, I find this lack of consistency (and documentation) quite difficult to handle: there is no way to determine which parameters are really needed and which not.

Comment on lines +115 to +122
for (let i = 0; i < permissionsListSize; i++) {
permissions.push(permissionsListResult[i].result);
}

return {
elementsList: permissions,
totalCount: batchResponse.result.totalCount,
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a missing step here: ideally, the data should be parsed using the apiToPermission function. This will take the data received from the API call and will parse each parameter according to their corresponding types (e.g. if xyz parameter can be received as string[] but treated in the code as string or number).

Related to this, apiToPermission is not used anywhere, but for some reason knip is not detecting this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would explain, as knip goes, this is in exceptions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carma12 carma12 Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The apiToPermission also needs to be added in the search, as it currently still pushes raw API results directly without conversion. I.e.:

permissions.push(apiToPermission(permissionsListResult[i].result));

<Select
data-cy={`${dataCy}-multi-typeahead-checkbox-select`}
role="menu"
id="multi-typeahead-checkbox-select"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hardcoded. Maybe we can be ${dataCy}-multi-typeahead-checkbox-select as in data-cy?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id should be passed, will change

@carma12 carma12 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some other details that I found in another round of review.

Comment thread src/pages/Permissions/Permissions.tsx Outdated
if (isSelected) {
newSelectedPermissions = JSON.parse(JSON.stringify(selectedPermissions));
for (let i = 0; i < permissions.length; i++) {
if (selectedPermissions.find((s) => s.cn[0] === permissions[i].cn[0])) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

permission.cn is typed as string and apiToPermission converts it to a plain string via simpleValues/convertToString. So I think permission.cn[0] would access the first character of the string, not the first element of an array. This needs to be fixed.

ipapermbindruletype: string;
type?: string;
ipapermlocation?: string;
extratargetfilter?: string[];

@carma12 carma12 Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extratargetfilter should be string.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other way around, it should be an array in the other places.

image

Comment on lines +116 to +118
const [extraTargetFilter, setExtraTargetFilter] = React.useState<string[]>(
[]
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should be string only.

{...(activeItemId && { "aria-activedescendant": activeItemId })}
role="combobox"
isExpanded={isOpen}
aria-controls="select-multi-typeahead-checkbox-listbox"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one should match the id format (i.e.: id={${id}-select-multi-typeahead-checkbox-listbox}), so it should be:

aria-controls={`${id}-select-multi-typeahead-checkbox-listbox`}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed.

Comment on lines +339 to +350
<TypeAheadWithCheckbox
id="modal-form-memberof"
dataCy="modal-select-memberof"
options={(
groupsQuery.data?.result.result as unknown as Record<
string,
unknown[]
>[]
)?.map((group) => ({
children: group.cn[0] as string,
value: group.cn[0] as string,
"data-cy": `modal-select-attrs-${group.cn[0]}`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If groupsQuery.data is undefined (during loading or on error), the optional chaining ?.map(...) will return undefined as well. Please add a fallback: options={... || []} or guard inside TypeAheadWithCheckbox with useState(options ?? []).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, should be fixed now

@veronnicka

Copy link
Copy Markdown
Contributor

Hi @veronnicka, the add option is there, because LDAP schema may contain whatever and may differ from what FreeIPA provides us as the information. Say you expand your schema, don't write your own plugin but still want to add a permission there, that is exactly the use-case where you'd use this.

You can try this out, in my case I've expanded the schema by location attribute:

  1. Create location.ldif file with the following contents:
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 2.25.1234567890.2.1 NAME 'location'
  DESC 'Custom privilege location'
  EQUALITY caseIgnoreMatchV
  SUBSTR caseIgnoreSubstringsMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'custom' )
-
add: objectClasses
objectClasses: ( 2.25.1234567890.2.2 NAME 'customPrivilegeLocation'
  DESC 'Custom privilege location class'
  SUP top AUXILIARY
  MAY ( location )
  X-ORIGIN 'custom' )
  1. Modify ldap schema
ldapmodify -D "cn=Directory Manager" -W -f location.ldif
  1. Run ipactl restart

Now you're able to create location attribute to anything, but others will fail.

image image image
(don't worry about privilege in the example, it works for all)

Thank you for this explanation. Then I think it makes sense how the UI works now.

duzda added 2 commits August 10, 2026 11:11
Add the 'Permissions' page under the 'Role-based access control'
section, allowing users to list, search, add, and delete
permissions.

Changes:
- Create `Permissions` main page with table, search, and pagination
- Add `AddPermissionModal` and `DeletePermissionsModal` components
- Add permissions navigation route and menu item
- Add `rpcPermissions` service with find/show/add/delete/search endpoints
- Add `Permission` data type and related utilities
- Add reusable `SelectMultiTypeaheadCheckbox` form component
- Extend `SimpleSelector` to support returning a custom property
- Add `findGroups` query to `rpcUserGroups` service

Assisted-by: Cursor <cursoragent@cursor.com>
Signed-off-by: David Hanina <dhanina@redhat.com>
Not having this would throw, to not throw we prepend the id we pass.

Signed-off-by: David Hanina <dhanina@redhat.com>
@duzda

duzda commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Hi, @veronnicka, @carma12 I've updated the PR. Apart from the mentioned changes I've also rebased the PR on top of current main, which simplifies the code a bit.


const hasType = type.trim() !== "";
const hasTarget =
hasType || subtree.trim() !== "" || extraTargetFilter.length > 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasTarget is a bit off vs what the server actually checks. subtree on its own isn't enough, if you pick Custom and only fill Subtree, Add is enabled but permission_add fails asking for a target. On the flip side, target DN / memberof / attrs alone leave Add disabled even though that should be fine (basedn is used by default). Can we treat type, memberof, target DN/targetgroup, filter and attrs as the real targets, and not count subtree by itself?

{
id: "valid-chars",
message:
"May only contain letters, numbers, -, _, ., :, /, and space",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On create, FreeIPA doesn’t use the general permission cn pattern. permission_add replaces it with a stricter one via _disallow_colon, so names can’t include : or / — only letters, numbers, -, _, ., and space. The modal still allows : and /, so it looks fine here and fails on submit. Can we use the add rule instead?
Ref: https://github.com/freeipa/freeipa/blob/master/ipaserver/plugins/permission.py (_disallow_colon, and permission_add.get_args)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, it would be much nicer to simply grab these patterns from the backend response, but that is kind of refactoring I'm not ready for today.

@veronnicka veronnicka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no other comments. If the checks pass, Im fine with merging

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review This PR is waiting on a review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants