Hi,
with an increasing portion of our UI moving to React, we need to think about how to handle access permissions.
For those not familiar with permission system details in Foreman - handling permissions in Foreman has an additional layer of complexity due to permission filters. Essentially, knowledge that a user called Bob has a :edit_hosts permission is not enough do decide whether Bob is allowed to edit a particular host as that permission can be limited by a search filter using scoped search syntax.
Since filters are tied to scoped search, authorization information needs to be supplied to UI from backend after the filters are applied. For view, records that user is not allowed to see are filtered out and not displayed. For edit and destroy, our API resolves permissions for each record and GraphQL follows that approach.
The knowledge about create permissions is useful on its own in the context of UI - if Bob does not have create permission, he should not be allowed to access create form/page and see links that navigate to it. If Bob has create permission, it means he is allowed to create at least some resources and therefore he is allowed to access create form.
The problem is: how do we make UI aware of create permissions? It is possible to get the information from API when we fetch a list of records, but that assumes a certain succession of actions. If users try to access the create page directly, we currently do not have a way to decide whether they are authorized to see that page. This is already an existing issue for Job Invocation Wizard.
A possible approach is to add those permissions into context.
@Ron_Lavi if a user has no permissions we should display a âNo accessâ empty state informing a user about what happened. See PF4
Examples from crhc
Specific permissions needed
@MariSvirik in case a user does not have permissions for a specific action, should the action button/link be displayed and disabled or should it be hidden?
IMO it should be hidden.If there is nothing users could do to enable that action for them, there is no need to show it to them. (Otherwise they would get just confused)
If we want to notify a user about their permissions e.g. âYou are in a view only modeâ, we can use dismissable inline notification.
User entered the page and a loading/skeleton state should appear
meanwhile an ajax call is being sent to the back-end to retrieve the page data
in case the back-end identifies that the user has no permissions, we should return a 404 error
an error empty state is being shown on the UI.
@Ron_Lavi, I agree with the flow and there is one case that I would like to cover.
Imagine we would have /architectures/new in React. Then step 2. would mean fetching operating systems which can be associated. But how do I know that current user is actually allowed to create an architecture? Should we have API call for that as well?
We can do an initial API call for that,
but maybe we can even get all of the user create permissions when the app loads and store them in context / redux? as that shouldnât change I believe.
If Iâm not mistaken, the API already includes create permissions?
IMHO this is not a use case we need to support. Users shouldnât see links to pages they arenât allowed to use. If they donât see a link to that page but rather try to go to it directly - itâs their problem if itâs broken, and in any case the server will prevent them from performing the specific action.
Currently we donât have a way of updating the context without a full page reload (due to needing to have it available to components that arenât under the main react application). Otherwise - I would agree that the context is a good place to store all the permissions and add them as the user browses the application, but all permissions should be stored together (ideally with an easy to use interface) and not split between different places.
A different approach would be to include any needed permissions in the response for a specific UI page - we can have JSON responses also in the UI controllers, the UI doesnât have to use the public API. With GraphQL it should even be simpler as the page could request the specific permissions it cares about as part of the query.
Another question is regarding handling specific permissions that arenât part of the basic CRUD actions - for example power_hosts, lock_provisioning_templates etc.
Also important to keep in mind that permission checks for non-admin users can be very slow in certain cases, so we should try and optimize on using a single authorizor instance for any request rather than calculating the permissions multiple times.
Iâm afraid this is not possible. Even create permissions can contain filters. E.g. user can create hosts where fqdn ~ *.example.com. Perhaps thatâs higher granularity that you had in mind and perhaps such granular verification would only be done in time when user submits the form and then itâs fine and we could store permissions. However I think it would be super confusing for admins trying to tweak their roles, asking a colleague âdid this change help?â âno, still canât see itâ if there a way to invalidate such cache on the client side when userâs role/usergroups/filters changes, that would be cool.
The can_edit on looks a bit misleading on the index. You have a basically have an API thatâs user.has_perm(permission, object).
In REST you can sort of model this for basic actions by calling OPTIONS /api/$resource. If thereâs POST, you can create. If thereâs PUT, you can modify. However, if you have to call OPTIONS for every resource, itâll quickly become very expensive and thus very slow.
Iâm really not sure how you would best model this in a way thatâs fast and easy to use. Perhaps this is something that you could model with graphql: ask for resources and a list of permissions on each of them. Adding it to the existing REST API may not end well.
But I think it would work really well becsuse itâs a template, as for a custom page Imho itâs OK to do an ajax call while visiting a page, snd display its content / unauthorized empty state based on the response.