How to handle permissions in UI

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.

Thoughts?

3 Likes

Thanks @Ondrej_Prazak !
I believe the flow should be something like this:

  1. Use entered the page and a loading/skeleton state should appear
  2. meanwhile an ajax call is being sent to the back-end to retrieve the page data.
  3. in case the back-end identifies that the user has no permissions, we should return a 404 error
  4. an error empty state is being shown on the UI.

adding here also @MariSvirik to verify if the UX flow is correct,

@Laviro 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

No access example

Flow seems fine to me (of course it may vary due to different permissions/context/actions).

1 Like

@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.

2 Likes
  1. User entered the page and a loading/skeleton state should appear
  2. meanwhile an ajax call is being sent to the back-end to retrieve the page data
  3. in case the back-end identifies that the user has no permissions, we should return a 404 error
  4. an error empty state is being shown on the UI.

@Laviro, 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” :slight_smile: 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.

Right… it can be a quite risky in some cases,

Btw in the index page template where the approach is data driven, the permissions remain on the backend,
And the ui only gets data to render a component, e.g delete action on table row: foreman/architectures_controller.rb at f009811a013010e143520da4f802f3732597e032 · theforeman/foreman · GitHub

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.