How to handle CapabilitySets which include Capabilities which may not be deflined yet.

Spike Overview

JIRA ID: EUREKA-90 How to handle CapabilitySets which include Capabilities which may not be deflined yet.

Objective: EUREKA needs to manage a capability set that includes capabilities that do not yet exist on the platform.

Problem Statement

The UI module's descriptor includes a permission set with permissions from other backend modules. When the application is entitled, UI modules can be installed earlier than backend modules, as the process occurs asynchronously via an event from Kafka. In this scenario, EUREKA does not create a capability set and skips this event because it lacks sufficient information to create it and associate it with existing resources in Keycloak. OKAPI checks permissions at runtime and does not make any pre-installations.

Deliverables

 

Folio-Page-13.png

 

Option 1 Postpone the UI events in the database to process them later, after processing all backend modules' events.

The approach requires that capability events for UI modules are not sent until all backend modules have been processed, and a delay or confirmation event from mod-role-keycloak indicates that all backend modules have been installed.

Pros

This can be quickly implemented to resolve the issue for phase one, especially when we will have a couple of large applications installed on the EUREKA platform.

Cons

This approach does not handle proper permissions set when one of the permissions would be in another application and would be installed late.

Option 2 Rearrange the application descriptor so that UI module capability events are sent only after all backend module events have been sent:

The approach involves rearranging the processing of modules to send all UI modules only after all backend modules have already been processed.

Pros

This can be quickly implemented to resolve the issue for phase one, especially when we will have a couple of large applications installed on the EUREKA platform.

Cons

This approach does not handle proper permissions set when one of the permissions would be in another application and would be installed late.

Option 3 involves creating postponed jobs for the capability events that have not been processed, and triggering them again when a new application is installed.

If we encounter an issue while processing a capability event and cannot proceed correctly, we will create a postponed job from this event and store it in the database. The postponed jobs will attempt to rerun once the mod-roles-keycloak receives another event indicating that a new application has been installed or updated. It is essential to ensure that these jobs start only after the application installation has been completed.

Pros

This approach would handle all possible cases where the UI modules have permission sets with permissions from other backend modules, which could be defined in other applications.

Cons

The approach will require a long time to implement.

Option 4 involves creating a mapping table for capability sets to capabilities that do not yet exist.

If mod-role-keycloak receives a capability event but cannot create a capability set because some capabilities are missing, it will still create a capability set and add a record to the mapping table indicating that this capability set is associated with another capability, along with the capability name. When we receive another capability event, we will create it and check the table to see if there is a capability set associated with it. If there is, we will associate the capability set with it and remove that item from the mapping.

Pros

This approach would handle all possible cases where the UI modules have permission sets with permissions from other backend modules, which could be defined in other applications.

Cons

Implementation for option 4

To avoid disappointing users when they see a capability set in the system that doesn't actually work (due to missing capabilities and resources), we need to hide the dummy capability sets from them. Additionally, even if we have some partially created capabilities required for the capability set, I suggest not displaying them to the end user. This can prevent confusion and simplify the implementation.

Currently, each time we create a new CapabilitySet, we always create it from scratch and never update it. We resolve the capability ID by its name, and if we can't find it, we exclude it from the result and log a warning. If we can resolve at least one capability, we create the CapabilitySet in the system, and it appears to the end user.

private Optional<UUID> getCapabilityId(Map<String, UUID> existingCapabilityIdsMap, String capabilityName) { var value = existingCapabilityIdsMap.get(capabilityName); if (value == null) { log.warn("Capability id is not found by capability name: {}", capabilityName); return Optional.empty(); } return Optional.of(value); }

So, in this case, instead of creating a partially valid CapabilitySet or not creating it at all, we need to create a dummy CapabilitySet in a separate table with the following structure.

Folio-Page-14.png

Now we have all the necessary information to create the capability set in the future, except for the missing capability IDs that have not yet been created.

Next, we need to extend the logic to create new capabilities. Once a capability is created, we should update the capability ID for all entries that exist in the DummyCapabilityIdMapping.

update DummyCapabilityIdMapping set capabilityid=? where capabilityName=?;

which populates all capability IDs for the particular capability

Next, we need to check if any capabilitySet has all its missing capability IDs populated. If such a capabilitySet exists, we should create a real capabilitySet from the dummy data and then remove it from the dummy table.

SELECT d.capabilitysetid FROM DummyCapabilityIdMapping d GROUP BY d.capabilitysetid
having count(case when d.capabilityid IS NOT NULL then 1 end) = count(*)

The query below returns all dummy capability set IDs that need to be created as real capability sets. All necessary data already exists and can be retrieved from the dummy tables.

Then we need to perform a cascading delete on the dummy capability set table data.

Conclusion

I would choose option 4, as it can be easily implemented for phase one and would cover all possible cases where capabilities may be defined in other applications.

Spike Status: Complited