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