Atlassian uses cookies to improve your browsing experience, perform analytics and research, and conduct advertising. Accept all cookies to indicate that you agree to our use of cookies on your device. Atlassian cookies and tracking notice, (opens new window)
/
Link management between Capabilities/CapabilitySets and Users/Roles
Link management between Capabilities/CapabilitySets and Users/Roles
This page describes algorithms that assign capabilities to a user and role, capability sets to a user, and role.
Capabilities and capability sets are assigned individually
Example:
There is a capability: foo.item.view [endpoints: GET /foo/item/{id}], foo.item.create [endpoints: POST /foo/item], foo.item.update [endpoints: PUT /foo/item/{id}]and capability set foo.item.manage uniting foo.item.view, foo.item.create and foo.item.update.
Usage Scenarios
Scenario #1 (role-capability and role-capabilitySet management)
User assigned capability set foo.item.manage to a role Foo management role (sampleRoleId). The following resources will be created:
Entities in mod-roles-keycloak database:
policy: [policyId, 'Policy for role: sampleRoleId']
Note that keycloak entities are not affected, because permission: Permission: [name: 'GET access for role: sampleRoleId to /foo/item/{id}', scope: 'GET', resource: '/foo/item/{id}', policy: policyId] already exists
Then a user removes assignment foo.item.manage from a role with a sampleRoleId. The following resources will be deleted:
Permission: [name: 'POST access for role: sampleRoleId to /foo/item', scope: 'POST', resource: '/foo/item', policy: policyId]
Permission: [PUT access for role: sampleRoleId to /foo/item/{id}, scope: 'GET', resource: '/foo/item/{id}', policy: policyId]
Not that Permission: [name: 'GET access for role: sampleRoleId to /foo/item/{id}', scope: 'GET', resource: '/foo/item/{id}', policy: policyId] is not deleted, because it was explicitly assigned to a role via relation roleCapability: [policyId, fooItemViewCapabilityId]
The same process works for user-capability, user-capabilitySet link management
Capability Relation Management
Role
Capability assignment process
safeCreate flag is used only internally, for outside requests a user must always assign a set of new capability ids, if not - an update operation allows the creation and delete capabilities in a single request
@startuml
!theme mono
!pragma useVerticalIf on
skinparam conditionStyle inside
skinparam defaultTextAlignment center
skinparam defaultFontSize 12
<style>
activityDiagram {
activity {
MaximumWidth 600
backgroundColor #f0f0f0
LineColor DimGrey
}
diamond {
HorizontalAlignment center
}
group {
LineColor LightGrey
FontSize 10
}
}
</style>
group RoleCapabilityServiceImpl {
:get role by id;<<input>>
:get ""roleCapabilities"" by ""roleId"";<<task>>
:extract existing ""capabilityIds"" from ""roleCapabilities"";<<task>>
if (not ""safeCreate"" AND ""capabilityIds"" not empty)) then (yes)
:throw EntityExistsException;<<output>>
end
else (no)
:get difference between new and existing ""capabilityIds"" sets as ""newCapabilityIds"";<<input>>
end if
group assignCapabilities {
:get assigned capabilities through capability sets as ""assignedCapabilityIds"";<<input>>
:store union of ""assignedCapabilityIds"" and ""newCapabilityIds"" as ""assignedCapabilityIds"";<<input>>
group #LightSteelBlue CapabilityEndpointService.getByCapabilityIds {
:get difference with ""newCapabilityIds"" and ""assignedCapabilityIds"" as ""changedIdentifiers"";<<input>>
:get changed capability endpoints by querying capabilities by ""changedIdentifiers""
and extracting distinct list of assigned endpoints and store it as ""changedCapabilityEndpoints"";<<task>>
:get assigned capability endpoints by querying capabilities by ""assignedIds""
and extracting distinct list of assigned endpoints and store it as ""assignedCapabilityEndpoints"";<<task>>
:return subtraction ""assignedCapabilityEndpoints"" from ""changedCapabilityEndpoints"";<<output>>
}
:rolePermissionsService.createPermissions for ""roleId"" and ""endpoints"";<<task>>
group #LightSteelBlue CapabilityEndpointService.getByCapabilityIds {
if (""endpoints"" are empty) then (yes)
:do nothing;<<output>>
else (no)
:get role by ""roleId"";<<input>>
:generate policy name for ""role"";<<input>>
:getOrCreate role policy by name
//Policy name template:// ""Policy for role: {{roleId}}"";<<task>>
:create permissions in Keycloak with role policy, list of endpoint and using permissionNameGenerator
//Permission name template:// ""{{httpMethod}} access for role '{{roleId}}' to '{{path}}'"";
end if
}
:generate RoleCapabilityEntity for ""newIds"" and store them as ""entities"";<<task>>
:upsert entities to table ""role_capability"" in database;<<output>>
:convert ""RoleCapabilityEntity"" to ""RoleCapability"" and return them as ""PageResult"";<<output>>
}
}
@enduml
Capability remove process
@startuml
!theme mono
!pragma useVerticalIf on
skinparam conditionStyle inside
skinparam defaultTextAlignment center
skinparam defaultFontSize 12
<style>
activityDiagram {
activity {
MaximumWidth 600
backgroundColor #f0f0f0
LineColor DimGrey
}
diamond {
HorizontalAlignment center
}
group {
LineColor LightGrey
FontSize 10
}
}
</style>
group RoleCapabilityServiceImpl {
:get existing ""roleCapabilities"" by ""roleId"";<<task>>
:extract existing ""capabilityIds"" from ""roleCapabilities"";<<task>>
if (""capabilityIds"" is empty) then (yes)
:do nothing;<<output>>
end
else (no)
:get intersection between ""capabilityIds"" and existing ""capabilityIds"" sets as ""deprecatedIds"";<<input>>
:get subtraction between ""assignedCapabilityIds"" and existing ""deprecatedIds"" sets as ""assignedIds"";<<input>>
end if
group removeCapabilities {
:get assigned capabilities through capability sets as ""assignedCapabilityIds"";<<input>>
:store union of ""assignedCapabilityIds"" and ""assignedIds"" as ""assignedCapabilityIds"";<<task>>
group #LightSteelBlue CapabilityEndpointService.getByCapabilityIds {
:get difference with ""deprecatedIds"" and ""assignedIds"" as ""changedIdentifiers"";<<input>>
:get changed capability endpoints by querying capabilities by ""changedIdentifiers""
and extracting distinct list of assigned endpoints and store it as ""changedCapabilityEndpoints"";<<task>>
:get assigned capability endpoints by querying capabilities by ""assignedIds""
and extracting distinct list of assigned endpoints and store it as ""assignedCapabilityEndpoints"";<<task>>
:return subtraction ""assignedCapabilityEndpoints"" from ""changedCapabilityEndpoints"";<<output>>
}
:rolePermissionsService.deletePermissions for ""roleId"" and ""endpoints"";<<task>>
group #LightSteelBlue CapabilityEndpointService.getByCapabilityIds {
if (""endpoints"" are empty) then (yes)
:do nothing;<<output>>
else (no)
:get role by ""roleId"";<<input>>
:generate policy name for ""role"";<<input>>
:get policy by ""name"" and type == ""ROLE"";<<task>>
:delete permissions in Keycloak with role policy, list of endpoint and using permissionNameGenerator
//Permission name template:// ""{{httpMethod}} access for role '{{roleId}}' to '{{path}}'"";
end if
}
:delete entities from table ""role_capability"" in database by ""roleId"" and ""deprecatedIds"";<<output>>
}
}
@enduml
Capability update process
A capability update process combines capability assignment and capability remove processes, where unmodified capabilities and corresponding keycloak permissions remain intact.
CapabilitySet assignment process
@startuml
!theme mono
!pragma useVerticalIf on
skinparam conditionStyle inside
skinparam defaultTextAlignment center
skinparam defaultFontSize 12
<style>
activityDiagram {
activity {
MaximumWidth 600
backgroundColor #f0f0f0
LineColor DimGrey
}
diamond {
HorizontalAlignment center
}
group {
LineColor LightGrey
FontSize 10
}
}
</style>
group RoleCapabilityServiceImpl {
:get role by id;<<input>>
:get ""roleCapabilitySets"" by ""roleId"";<<task>>
:extract existing ""capabilitySetIds"" from ""roleCapabilitySets"";<<task>>
if (not ""safeCreate"" AND ""capabilitySetIds"" not empty)) then (yes)
:throw EntityExistsException;<<output>>
end
else (no)
:get difference between new and existing ""capabilitySetIds"" sets as ""newSetIds"";<<input>>
end if
group #LightSteelBlue assignCapabilitySets {
group getChangedEndpoints {
:retrieve ""Capability"" entities by ""roleId"" as ""directlyAssignedCapabilities"";<<input>>
:extract endpoints from ""directlyAssignedCapabilities"" and store them as ""excludedEndpoints"";<<task>>
group capabilityEndpointService.getByCapabilitySetIds {
:retrieve ""Capability"" entities by ""newSetIds"" as ""changedCapabilities"";<<input>>
:retrieve ""Capability"" entities by ""assignedSetIds"" as ""assignedCapabilities"";<<input>>
:extract endpoints from ""changedCapabilities"" and store them as ""changedCapabilitySetEndpoints"";<<task>>
:extract endpoints from ""assignedCapabilities"" and store them as ""assignedCapabilitySetEndpoints"";<<task>>
:return subtraction ""assignedCapabilitySetEndpoints"" and ""excludedEndpoints"" from ""changedCapabilitySetEndpoints"" as ""List<Endpoint"";<<output>>
}
}
:create keycloak permissions in ""RolePermissionsService"" for ""roleId"" and ""endpoints"";<<task>>
:generate ""RoleCapabilitySetEntity"" for ""newSetIds"" and store them as ""entities"";<<task>>
:upsert entities to table ""role_capability_set"" in database;<<output>>
:convert ""RoleCapabilitySetEntity"" to ""RoleCapabilitySet"" and return them as ""PageResult"";<<output>>
}
}
@enduml
CapabilitySet removal process
@startuml
!theme mono
!pragma useVerticalIf on
skinparam conditionStyle inside
skinparam defaultTextAlignment center
skinparam defaultFontSize 12
<style>
activityDiagram {
activity {
MaximumWidth 600
backgroundColor #f0f0f0
LineColor DimGrey
}
diamond {
HorizontalAlignment center
}
group {
LineColor LightGrey
FontSize 10
}
}
</style>
group RoleCapabilityServiceImpl {
:get role by id;<<input>>
:get ""roleCapabilitySets"" by ""roleId"" as ""assignedRoleCapabilitySetEntities"";<<task>>
:extract existing ""assignedCapabilitySetIds"" from ""assignedRoleCapabilitySetEntities"";<<task>>
if (""assignedCapabilitySetIds"" is empty) then (yes)
:do nothing;<<output>>
end
else (no)
:get intersection between ""assignedCapabilitySetIds"" and given ""capabilitySetIds"" sets as ""deprecatedSetIds"";<<input>>
:get subtraction between ""assignedCapabilityIds"" and existing ""deprecatedSetIds"" sets as ""assignedSetIds"";<<input>>
end if
group removeCapabilitySets {
group #LightSteelBlue getChangedEndpoints {
:retrieve "Capability" entities by ""roleId"" as ""directlyAssignedCapabilities"";<<input>>
:extract endpoints from ""directlyAssignedCapabilities"" and store them as ""excludedEndpoints"";<<task>>
group capabilityEndpointService.getByCapabilitySetIds {
:retrieve "Capability" entities by ""deprecatedSetIds"" as ""changedCapabilities"";<<input>>
:extract endpoints from ""changedCapabilities"" and store them as ""changedCapabilitySetEndpoints"";<<task>>
:retrieve "Capability" entities by ""assignedSetIds"" as ""assignedCapabilities"";<<input>>
:extract endpoints from ""assignedCapabilities"" and store them as ""assignedCapabilitySetEndpoints"";<<task>>
:return subtraction ""assignedCapabilitySetEndpoints"" and ""excludedEndpoints"" from ""changedCapabilitySetEndpoints"" as ""List<Endpoint"";<<output>>
}
:remove keycloak permissions using ""RolePermissionsService"" for ""roleId"" and ""endpoints"";<<task>>
:delete entities from table ""user_capability"" by ""roleId"" and ""deprecatedIds"";<<output>>
}
}
}
@enduml
User
Capability assignment process
safeCreate flag is used only internally, for outside requests a user must always assign a set of new capability ids, if not - an update operation allows the creation and delete capabilities in a single request
@startuml
!theme mono
!pragma useVerticalIf on
skinparam conditionStyle inside
skinparam defaultTextAlignment center
skinparam defaultFontSize 12
<style>
activityDiagram {
activity {
MaximumWidth 600
backgroundColor #f0f0f0
LineColor DimGrey
}
diamond {
HorizontalAlignment center
}
group {
LineColor LightGrey
FontSize 10
}
}
</style>
group UserCapabilityServiceImpl {
:create keycloakUser If not exists by calling ""mod-users-keycloak"" API;<<task>>
:extract existing ""capabilityIds"" from ""userCapabilities"";<<task>>
if (not ""safeCreate"" AND ""capabilityIds"" not empty)) then (yes)
:throw EntityExistsException;<<output>>
end
else (no)
:get difference between new and existing ""capabilityIds"" sets as ""newCapabilityIds"";<<input>>
end if
group assignCapabilities {
:get assigned capabilities through capability sets as ""assignedCapabilityIds"";<<input>>
:store union of ""assignedCapabilityIds"" and ""newCapabilityIds"" as ""assignedCapabilityIds"";<<input>>
group #PaleGreen CapabilityEndpointService.getByCapabilityIds {
:get difference with ""newCapabilityIds"" and ""assignedCapabilityIds"" as ""changedIdentifiers"";<<input>>
:get changed capability endpoints by querying capabilities by ""changedIdentifiers""
and extracting distinct list of assigned endpoints and store it as ""changedCapabilityEndpoints"";<<task>>
:get assigned capability endpoints by querying capabilities by ""assignedIds""
and extracting distinct list of assigned endpoints and store it as ""assignedCapabilityEndpoints"";<<task>>
:return subtraction ""assignedCapabilityEndpoints"" from ""changedCapabilityEndpoints"";<<output>>
}
:userPermissionsService.createPermissions for ""userId"" and ""endpoints"";<<task>>
group #PaleGreen CapabilityEndpointService.getByCapabilityIds {
if (""endpoints"" are empty) then (yes)
:do nothing;<<output>>
else (no)
:get keycloak user by Folio ""userId"";<<input>>
:generate policy name for ""user"";<<input>>
:getOrCreate user policy by name
//Policy name template:// ""Policy for user: {{userId}}"";<<task>>
:create permissions in Keycloak with user policy, list of endpoint and using permissionNameGenerator
//Permission name template:// ""{{httpMethod}} access for user '{{userId}}' to '{{path}}'"";
end if
}
:generate UserCapabilityEntity for ""newIds"" and store them as ""entities"";<<task>>
:upsert entities to table ""user_capability"" in database;<<output>>
:convert ""UserCapabilityEntity"" to ""UserCapability"" and return them as ""PageResult"";<<output>>
}
}
@enduml
Capability removal process
@startuml
!theme mono
!pragma useVerticalIf on
skinparam conditionStyle inside
skinparam defaultTextAlignment center
skinparam defaultFontSize 12
<style>
activityDiagram {
activity {
MaximumWidth 600
backgroundColor #f0f0f0
LineColor DimGrey
}
diamond {
HorizontalAlignment center
}
group {
LineColor LightGrey
FontSize 10
}
}
</style>
group UserCapabilityServiceImpl {
:get existing ""userCapabilities"" by ""userId"";<<task>>
:extract existing ""capabilityIds"" from ""userCapabilities"";<<task>>
if (""capabilityIds"" is empty) then (yes)
:do nothing;<<output>>
end
else (no)
:get intersection between ""capabilityIds"" and existing ""capabilityIds"" sets as ""deprecatedIds"";<<input>>
:get subtraction between ""assignedCapabilityIds"" and existing ""deprecatedIds"" sets as ""assignedIds"";<<input>>
end if
group removeCapabilities {
:get assigned capabilities through capability sets as ""assignedCapabilityIds"";<<input>>
:store union of ""assignedCapabilityIds"" and ""assignedIds"" as ""assignedCapabilityIds"";<<task>>
group #PaleGreen CapabilityEndpointService.getByCapabilityIds {
:get difference with ""deprecatedIds"" and ""assignedIds"" as ""changedIdentifiers"";<<input>>
:get changed capability endpoints by querying capabilities by ""changedIdentifiers""
and extracting distinct list of assigned endpoints and store it as ""changedCapabilityEndpoints"";<<task>>
:get assigned capability endpoints by querying capabilities by ""assignedIds""
and extracting distinct list of assigned endpoints and store it as ""assignedCapabilityEndpoints"";<<task>>
:return subtraction ""assignedCapabilityEndpoints"" from ""changedCapabilityEndpoints"";<<output>>
}
:userPermissionsService.deletePermissions for ""userId"" and ""endpoints"";<<task>>
group #PaleGreen CapabilityEndpointService.getByCapabilityIds {
if (""endpoints"" are empty) then (yes)
:do nothing;<<output>>
else (no)
:get keycloak user by ""userId"";<<input>>
:generate policy name for ""user"";<<input>>
:get policy by ""name"" and type == ""USER"";<<task>>
:delete permissions in Keycloak with user policy, list of endpoint and using permissionNameGenerator
//Permission name template:// ""{{httpMethod}} access for user '{{userId}}' to '{{path}}'"";
end if
}
:delete entities from table ""user_capability"" in database by ""userId"" and ""deprecatedIds"";<<output>>
}
}
@enduml
Capability update process
A capability update process combines capability assignment and capability remove processes, where unmodified capabilities and corresponding keycloak permissions remain intact.
CapabilitySet assignment process
@startuml
!theme mono
!pragma useVerticalIf on
skinparam conditionStyle inside
skinparam defaultTextAlignment center
skinparam defaultFontSize 12
<style>
activityDiagram {
activity {
MaximumWidth 600
backgroundColor #f0f0f0
LineColor DimGrey
}
diamond {
HorizontalAlignment center
}
group {
LineColor LightGrey
FontSize 10
}
}
</style>
group UserCapabilityServiceImpl {
:create keycloakUser If not exists by calling ""mod-users-keycloak"" API;<<task>>
:get ""userCapabilitySets"" by ""userId"";<<task>>
:extract existing ""capabilitySetIds"" from ""userCapabilitySets"";<<task>>
if (not ""safeCreate"" AND ""capabilitySetIds"" not empty)) then (yes)
:throw EntityExistsException;<<output>>
end
else (no)
:get difference between new and existing ""capabilitySetIds"" sets as ""newSetIds"";<<input>>
end if
group #PaleGreen assignCapabilitySets {
group getChangedEndpoints {
:retrieve ""Capability"" entities by ""userId"" as ""directlyAssignedCapabilities"";<<input>>
:extract endpoints from ""directlyAssignedCapabilities"" and store them as ""excludedEndpoints"";<<task>>
group capabilityEndpointService.getByCapabilitySetIds {
:retrieve ""Capability"" entities by ""newSetIds"" as ""changedCapabilities"";<<input>>
:retrieve ""Capability"" entities by ""assignedSetIds"" as ""assignedCapabilities"";<<input>>
:extract endpoints from ""changedCapabilities"" and store them as ""changedCapabilitySetEndpoints"";<<task>>
:extract endpoints from ""assignedCapabilities"" and store them as ""assignedCapabilitySetEndpoints"";<<task>>
:return subtraction ""assignedCapabilitySetEndpoints"" and ""excludedEndpoints"" from ""changedCapabilitySetEndpoints"" as ""List<Endpoint"";<<output>>
}
}
:create keycloak permissions in ""UserPermissionsService"" for ""userId"" and ""endpoints"";<<task>>
:generate ""UserCapabilitySetEntity"" for ""newSetIds"" and store them as ""entities"";<<task>>
:upsert entities to table ""user_capability_set"" in database;<<output>>
:convert ""UserCapabilitySetEntity"" to ""UserCapabilitySet"" and return them as ""PageResult"";<<output>>
}
}
@enduml
CapabilitySet removal process
@startuml
!theme mono
!pragma useVerticalIf on
skinparam conditionStyle inside
skinparam defaultTextAlignment center
skinparam defaultFontSize 12
<style>
activityDiagram {
activity {
MaximumWidth 600
backgroundColor #f0f0f0
LineColor DimGrey
}
diamond {
HorizontalAlignment center
}
group {
LineColor LightGrey
FontSize 10
}
}
</style>
group UserCapabilityServiceImpl {
:get keycloak user by Folio ""userId"";<<input>>
:get ""userCapabilitySets"" by ""userId"" as ""assignedUserCapabilitySetEntities"";<<task>>
:extract existing ""assignedCapabilitySetIds"" from ""assignedUserCapabilitySetEntities"";<<task>>
if (""assignedCapabilitySetIds"" is empty) then (yes)
:do nothing;<<output>>
end
else (no)
:get intersection between ""assignedCapabilitySetIds"" and given ""capabilitySetIds"" sets as ""deprecatedSetIds"";<<input>>
:get subtraction between ""assignedCapabilityIds"" and existing ""deprecatedSetIds"" sets as ""assignedSetIds"";<<input>>
end if
group removeCapabilitySets {
group #PaleGreen getChangedEndpoints {
:retrieve "Capability" entities by ""userId"" as ""directlyAssignedCapabilities"";<<input>>
:extract endpoints from ""directlyAssignedCapabilities"" and store them as ""excludedEndpoints"";<<task>>
group capabilityEndpointService.getByCapabilitySetIds {
:retrieve "Capability" entities by ""deprecatedSetIds"" as ""changedCapabilities"";<<input>>
:extract endpoints from ""changedCapabilities"" and store them as ""changedCapabilitySetEndpoints"";<<task>>
:retrieve "Capability" entities by ""assignedSetIds"" as ""assignedCapabilities"";<<input>>
:extract endpoints from ""assignedCapabilities"" and store them as ""assignedCapabilitySetEndpoints"";<<task>>
:return subtraction ""assignedCapabilitySetEndpoints"" and ""excludedEndpoints"" from ""changedCapabilitySetEndpoints"" as ""List<Endpoint"";<<output>>
}
:remove keycloak permissions using ""UserPermissionsService"" for ""userId"" and ""endpoints"";<<task>>
:delete entities from table ""user_capability"" by ""userId"" and ""deprecatedIds"";<<output>>
}
}
}
@enduml