move rbac.md to doc folder
This commit is contained in:
635
doc/rbac.md
Normal file
635
doc/rbac.md
Normal file
@ -0,0 +1,635 @@
|
||||
## *hsadmin-ng*'s Role-Based-Access-Management (RBAC)
|
||||
|
||||
The requirements of *hsadmin-ng* include table-m row- and column-level-security for read and write access to business-objects.
|
||||
More precisely, any access has to be controlled according to given rules depending on the accessing users, their roles and the accessed business-object.
|
||||
Further, roles and business-objects are hierarchical.
|
||||
|
||||
To avoid misunderstandings, we are using the term "business-object" what's usually called a "domain-object".
|
||||
But as we are in the context of a webhosting infrastructure provider, "domain" would have a double meaning.
|
||||
|
||||
Our implementation is based on Role-Based-Access-Management (RBAC) in conjunction with views and triggers on the business-objects.
|
||||
As far as possible, we are using the same terms as defined in the RBAC standard, for our function names though, we chose more expressive names.
|
||||
|
||||
In RBAC, subjects can be assigned to roles, roles can be hierarchical and eventually have assigned permissions.
|
||||
A permission allows a specific operation (e.g. view or edit) on a specific (business-) object.
|
||||
|
||||
You can find the entity structure as a UML class diagram as follows:
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
' left to right direction
|
||||
top to bottom direction
|
||||
|
||||
' hide the ugly E in a circle left to the entity name
|
||||
hide circle
|
||||
|
||||
' use right-angled line routing
|
||||
skinparam linetype ortho
|
||||
|
||||
package RBAC {
|
||||
|
||||
' forward declarations
|
||||
entity RbacUser
|
||||
|
||||
together {
|
||||
|
||||
entity RbacRole
|
||||
entity RbacPermission
|
||||
|
||||
|
||||
RbacUser -[hidden]> RbacRole
|
||||
RbacRole -[hidden]> RbacUser
|
||||
}
|
||||
|
||||
together {
|
||||
entity RbacGrant
|
||||
enum RbacReferenceType
|
||||
entity RbacReference
|
||||
}
|
||||
RbacReference -[hidden]> RbacReferenceType
|
||||
|
||||
entity RbacGrant {
|
||||
ascendantUuid: uuid(RbackReference)
|
||||
descendantUuid: uuid(RbackReference)
|
||||
auto
|
||||
}
|
||||
RbacGrant o-u-> RbacReference
|
||||
RbacGrant o-u-> RbacReference
|
||||
|
||||
enum RbacReferenceType {
|
||||
RbacUser
|
||||
RbacRole
|
||||
RbacPermission
|
||||
}
|
||||
RbacReferenceType ..> RbacUser
|
||||
RbacReferenceType ..> RbacRole
|
||||
RbacReferenceType ..> RbacPermission
|
||||
|
||||
entity RbacReference {
|
||||
*uuid : uuid <<generated>>
|
||||
--
|
||||
type : RbacReferenceType
|
||||
}
|
||||
RbacReference o--> RbacReferenceType
|
||||
entity RbacUser {
|
||||
*uuid : uuid <<generated>>
|
||||
--
|
||||
name : varchar
|
||||
}
|
||||
RbacUser o-- RbacReference
|
||||
|
||||
entity RbacRole {
|
||||
*uuid : uuid(RbacReference)
|
||||
--
|
||||
name : varchar
|
||||
}
|
||||
RbacRole o-- RbacReference
|
||||
|
||||
together {
|
||||
enum RbacOperation
|
||||
entity RbacObject
|
||||
}
|
||||
|
||||
entity RbacPermission {
|
||||
*uuid : uuid(RbacReference)
|
||||
--
|
||||
objectUuid: RbacObject
|
||||
op: RbacOperation
|
||||
}
|
||||
RbacPermission o-- RbacReference
|
||||
RbacPermission o-- RbacOperation
|
||||
RbacPermission *-- RbacObject
|
||||
|
||||
enum RbacOperation {
|
||||
add-package
|
||||
add-domain
|
||||
add-unixuser
|
||||
...
|
||||
view
|
||||
edit
|
||||
delete
|
||||
}
|
||||
|
||||
entity RbacObject {
|
||||
*uuid : uuid <<generated>>
|
||||
--
|
||||
objectTable: varchar
|
||||
}
|
||||
RbacObject o- "Business Objects"
|
||||
}
|
||||
|
||||
package "Business Objects" {
|
||||
|
||||
entity package
|
||||
package *--u- RbacObject
|
||||
|
||||
entity customer
|
||||
customer *--u- RbacObject
|
||||
|
||||
entity "..." as moreBusinessObjects
|
||||
moreBusinessObjects *-u- RbacObject
|
||||
}
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
### The RBAC Entity Types
|
||||
|
||||
#### RbacReference
|
||||
|
||||
An *RbacReference* is a generalization of all entity types which participate in the hierarchical role system, defined via *RbacGrant*.
|
||||
|
||||
The primary key of the *RbacReference* and its referred object is always identical.
|
||||
|
||||
#### RbacReferenceType
|
||||
|
||||
The enum *RbacReferenceType* describes the type of reference.
|
||||
It's only needed to make it easier to find the referred object in *RbacUser*, *RbacRole* or *RbacPermission*.
|
||||
|
||||
#### RbacUser
|
||||
|
||||
An *RbacUser* is a type of RBAC-subject which references a login account outside this system, identified by a name (usually an email-address).
|
||||
|
||||
*RbacUser*s can be assigned to multiple *RbacRole*s, through which they can get permissions to *RbacObject*s.
|
||||
|
||||
The primary key of the *RbacUser* is identical to its related *RbacReference*.
|
||||
|
||||
#### RbacRole
|
||||
|
||||
An *RbacRole* represents a collection of directly or indirectly assigned *RbacPermission*s.
|
||||
Each *RbacRole* can be assigned to *RbacUser*s or to another *RbacRole*.
|
||||
|
||||
Both kinds of assignments are represented via *RbacGrant*.
|
||||
|
||||
*RbacRole* entities can *RbacObject*s, or more precise
|
||||
|
||||
#### RbacPermission
|
||||
|
||||
An *RbacPermission* allows a specific *RbacOperation* on a specific *RbacObject*.
|
||||
|
||||
#### RbacOperation
|
||||
|
||||
An *RbacOperation* determines, <u>what</u> an *RbacPermission* allows to do.
|
||||
It can be one of:
|
||||
|
||||
- **'add-...'** - permits creating new instances of specific entity types underneath the object specified by the permission, e.g. "add-package"
|
||||
- **'view'** - permits reading the contents of the object specified by the permission
|
||||
- **'edit'** - change the contents of the object specified by the permission
|
||||
- **'delete'** - delete the object specified by the permission
|
||||
- **'\*'**
|
||||
|
||||
This list is extensible according to the needs of the access rule system.
|
||||
|
||||
Please notice, that there is no **create** operation to create new instances of unrelated business-object-types.
|
||||
For such a singleton business-object-type, e.g. *Organization" or "Hostsharing" has to be defined, and its single entity is referred in the permission.
|
||||
Only with this rule, the foreign key in *RbacPermission* can be defined as `NOT NULL`.
|
||||
|
||||
#### RbacGrant
|
||||
|
||||
The *RbacGrant* entities represent the access-rights structure from *RbacUser*s via hierarchical *RbacRoles* down to *RbacPermission*s.
|
||||
|
||||
The core SQL queries to determine access rights are all recursive queries on the *RbacGrant* table.
|
||||
|
||||
### Role naming
|
||||
|
||||
The naming pattern of a role is important to be able to address specific roles.
|
||||
E.g. if a new package is added, the admin-role of the related customer has to be addressed.
|
||||
|
||||
There can be global roles like 'administrators'.
|
||||
Most roles, though, are specific for certain business-objects and automatically generated as such:
|
||||
|
||||
business-object-table#business-object-name.relative-role
|
||||
|
||||
|
||||
Where *business-object-table* is the name of the SQL table of the business object (e.g *customer* or 'package'),
|
||||
*business-object-name* is generated from an immutable business key(e.g. a prefix like 'xyz' or 'xyz00')
|
||||
and the *relative-role*' describes the role relative to the referenced business-object as follows:
|
||||
|
||||
#### owner
|
||||
|
||||
The owner-role is granted to the subject which created the business object.
|
||||
E.g. for a new *customer* it would be granted to 'administrators' and for a new *package* to the 'customer#...admin'.
|
||||
|
||||
Whoever has the owner-role assigned can do everything with the related business-object, including deleting (or deactivating) it.
|
||||
|
||||
In most cases, the permissions to other operations than 'delete' are granted through the 'admin' role.
|
||||
By this, all roles ob sub-objects, which are assigned to the 'admin' role, are also granted to the 'owner'.
|
||||
|
||||
#### admin
|
||||
|
||||
The admin-role is granted to a role of those subjects who manage the business object.
|
||||
E.g. a 'package' is manged by the admin of the customer.
|
||||
|
||||
Whoever has the admin-role assigned, do everything with the related business-object, including deleting (or deactivating) it.
|
||||
|
||||
In most cases, the permissions to the 'view' operation is granted through the 'tenant' role.
|
||||
By this, all roles ob sub-objects, which are assigned to the 'tenent' role, are also granted to the 'admin'.
|
||||
|
||||
|
||||
#### tenant
|
||||
|
||||
The tenant-role is granted to everybody who needs to be able to view the business-object.
|
||||
Usually all owners, admins and tenants of sub-objects get this role granted.
|
||||
|
||||
Some business-objects only have very limited data directly in the main business-object and store more sensitive data in special sub-objects (e.g. 'customer-details') to which tenants of sub-objects of the main-object (e.g. package admins) do not get view permission.
|
||||
|
||||
|
||||
## Example Users, Roles, Permissions and Business-Objects
|
||||
|
||||
The following diagram shows how users, roles and permissions could be granted access to operations on business objects.
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
' left to right direction
|
||||
top to bottom direction
|
||||
|
||||
' hide the ugly E in a circle left to the entity name
|
||||
hide circle
|
||||
|
||||
' use right-angled line routing
|
||||
' skinparam linetype ortho
|
||||
|
||||
package RbacUsers {
|
||||
object UserMike
|
||||
object UserSuse
|
||||
object UserPaul
|
||||
}
|
||||
|
||||
package RbacRoles {
|
||||
object RoleAdministrators
|
||||
object RoleCustXyz_Owner
|
||||
object RoleCustXyz_Admin
|
||||
object RolePackXyz00_Owner
|
||||
}
|
||||
RbacUsers -[hidden]> RbacRoles
|
||||
|
||||
package RbacPermissions {
|
||||
object PermCustXyz_View
|
||||
object PermCustXyz_Edit
|
||||
object PermCustXyz_Delete
|
||||
object PermCustXyz_AddPackage
|
||||
object PermPackXyz00_View
|
||||
object PermPackXyz00_Edit
|
||||
object PermPackXyz00_Delete
|
||||
object PermPackXyz00_AddUser
|
||||
}
|
||||
RbacRoles -[hidden]> RbacPermissions
|
||||
|
||||
package BusinessObjects {
|
||||
object CustXyz
|
||||
object PackXyz00
|
||||
}
|
||||
RbacPermissions -[hidden]> BusinessObjects
|
||||
|
||||
UserMike o---> RoleAdministrators
|
||||
UserSuse o--> RoleCustXyz_Admin
|
||||
UserPaul o--> RolePackXyz00_Owner
|
||||
|
||||
RoleAdministrators o..> RoleCustXyz_Owner
|
||||
RoleCustXyz_Owner o-> RoleCustXyz_Admin
|
||||
RoleCustXyz_Admin o-> RolePackXyz00_Owner
|
||||
|
||||
RoleCustXyz_Owner o--> PermCustXyz_Edit
|
||||
RoleCustXyz_Owner o--> PermCustXyz_Delete
|
||||
RoleCustXyz_Admin o--> PermCustXyz_View
|
||||
RoleCustXyz_Admin o--> PermCustXyz_AddPackage
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_View
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_Edit
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_Delete
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_AddUser
|
||||
|
||||
PermCustXyz_View o--> CustXyz
|
||||
PermCustXyz_Edit o--> CustXyz
|
||||
PermCustXyz_Delete o--> CustXyz
|
||||
PermCustXyz_AddPackage o--> CustXyz
|
||||
PermPackXyz00_View o--> PackXyz00
|
||||
PermPackXyz00_Edit o--> PackXyz00
|
||||
PermPackXyz00_Delete o--> PackXyz00
|
||||
PermPackXyz00_AddUser o--> PackXyz00
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
## Business-Object-Tables, Triggers and Views
|
||||
|
||||
To support the RBAC system, for each business-object-table, some more artifacts are created in the database:
|
||||
|
||||
- a `BEFORE INSERT TRIGGER` which creates the related *RbacObject* instance,
|
||||
- an `AFTER INSERT TRIGGER` which creates the related *RbacRole*s, *RbacPermission*s together with their related *RbacReference*s as well as *RbacGrant*s,
|
||||
- a restricted view (e.g. *customer_rv*) through which restricted users can access the underlying data.
|
||||
|
||||
Not yet implemented, but planned are these actions:
|
||||
|
||||
- an `ON DELETE ... DO INSTEAD` rule to allow `SQL DELETE` if applicable for the business-object-table and the user has 'delete' permission,
|
||||
- an `ON UPDATE ... DO INSTEAD` rule to allow `SQL UPDATE` if the user has 'edit' right,
|
||||
- an `ON INSERT ... DO INSTEAD` rule to allow `SQL INSERT` if the user has 'add-..' right to the parent-business-object.
|
||||
|
||||
The restricted view takes the current user from a session property and applies the hierarchy of its roles all the way down to the permissions related to the respective business-object-table.
|
||||
This way, each user can only view the data they have 'view'-permission for, only create those they have 'add-...'-permission, only update those they have 'edit'- and only delete those they have 'delete'-permission to.
|
||||
|
||||
### Current User
|
||||
|
||||
The current use is taken from the session variable `hsadminng.currentUser` which contains the name of the user as stored in the
|
||||
*RbacUser*s table. Example:
|
||||
|
||||
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
|
||||
|
||||
That user is also used for historicization and audit log, but which is a different topic.
|
||||
|
||||
### Assuming Roles
|
||||
|
||||
If the session variable `hsadminng.assumedRoles` is set to a non-empty value, its content is interpreted as a list of semicolon-separated role names.
|
||||
Example:
|
||||
|
||||
SET LOCAL hsadminng.assumedRoles = 'customer#aab.admin;customer#aac.admin';
|
||||
|
||||
In this case, not the current user but the assumed roles are used as a starting point for any further queries.
|
||||
Roles which are not granted to the current user, directly or indirectly, cannot be assumed.
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
A full example is shown here:
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
SET SESSION SESSION AUTHORIZATION restricted;
|
||||
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
|
||||
SET LOCAL hsadminng.assumedRoles = 'customer#aab.admin;customer#aac.admin';
|
||||
|
||||
SELECT c.prefix, p.name as "package", ema.localPart || '@' || dom.name as "email-address"
|
||||
FROM emailaddress_rv ema
|
||||
JOIN domain_rv dom ON dom.uuid = ema.domainuuid
|
||||
JOIN unixuser_rv uu ON uu.uuid = dom.unixuseruuid
|
||||
JOIN package_rv p ON p.uuid = uu.packageuuid
|
||||
JOIN customer_rv c ON c.uuid = p.customeruuid;
|
||||
END TRANSACTION;
|
||||
|
||||
|
||||
|
||||
## Roles and Their Assignments for Certain Business Objects
|
||||
|
||||
To give you an overview of the business-object-types for the following role-examples,
|
||||
check this diagram:
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
left to right direction
|
||||
' top to bottom direction
|
||||
|
||||
' hide the ugly E in a circle left to the entity name
|
||||
hide circle
|
||||
|
||||
' use right-angled line routing
|
||||
' skinparam linetype ortho
|
||||
|
||||
entity EMailAddress
|
||||
|
||||
entity Domain
|
||||
Domain o-- "*" EMailAddress
|
||||
|
||||
entity UnixUser
|
||||
UnixUser o-- "*" Domain
|
||||
|
||||
entity Package
|
||||
Package o.. "*" UnixUser
|
||||
|
||||
entity Customer
|
||||
Customer o-- "*" Package
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
It's mostly an example hierarchy of business-object-types, but resembles a part of Hostsharing's actual hosting infrastructure.
|
||||
|
||||
The following diagrams show which roles are created for each business-object-type
|
||||
and how they relate to roles from other business-object-types.
|
||||
|
||||
### Customer Roles
|
||||
|
||||
The highest level of the business-object-type-hierarchy is the *Customer*.
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
' left to right direction
|
||||
top to bottom direction
|
||||
|
||||
' hide the ugly E in a circle left to the entity name
|
||||
hide circle
|
||||
|
||||
' use right-angled line routing
|
||||
' skinparam linetype ortho
|
||||
|
||||
' needs PlantUML 1.2021.14 as Markdown plugin
|
||||
allow_mixing
|
||||
|
||||
entity "BObj customer#xyz" as boCustXyz
|
||||
|
||||
together {
|
||||
entity "Perm customer#xyz *" as permCustomerXyzAll
|
||||
permCustomerXyzAll --> boCustXyz
|
||||
|
||||
entity "Perm customer#xyz add-package" as permCustomerXyzAddPack
|
||||
permCustomerXyzAddPack --> boCustXyz
|
||||
|
||||
entity "Perm customer#xyz view" as permCustomerXyzView
|
||||
permCustomerXyzView --> boCustXyz
|
||||
}
|
||||
|
||||
entity "Role customer#xyz.tenant" as roleCustXyzTenant
|
||||
roleCustXyzTenant --> permCustomerXyzView
|
||||
|
||||
entity "Role customer#xyz.admin" as roleCustXyzAdmin
|
||||
roleCustXyzAdmin --> roleCustXyzTenant
|
||||
roleCustXyzAdmin --> permCustomerXyzAddPack
|
||||
|
||||
entity "Role customer#xyz.owner" as roleCustXyzOwner
|
||||
roleCustXyzOwner ..> roleCustXyzAdmin
|
||||
roleCustXyzOwner --> permCustomerXyzAll
|
||||
|
||||
actor "Customer XYZ Admin" as actorCustXyzAdmin
|
||||
actorCustXyzAdmin --> roleCustXyzAdmin
|
||||
|
||||
entity "Role administrators" as roleAdmins
|
||||
roleAdmins --> roleCustXyzOwner
|
||||
|
||||
actor "Any Hostmaster" as actorHostmaster
|
||||
actorHostmaster --> roleAdmins
|
||||
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
As you can see, there something special:
|
||||
From the 'Role customer#xyz.owner' to the 'Role customer#xyz.admin' there is a dashed line, whereas all other lines are solid lines.
|
||||
Solid lines means, that one role is granted to another and followed in all queries to the restricted views.
|
||||
The dashed line means that one role is granted to another but not automatically followed in queries to the restricted views.
|
||||
|
||||
The reason here is that otherwise simply too many objects would be accessible to those with the 'administrators' role and all queries would be slowed down vastly.
|
||||
|
||||
Grants which are not followed are still valid grants for `hsadminng.assumedRoles`.
|
||||
Thus, if you want to access anything below a customer, assume its role first.
|
||||
|
||||
There is actually another speciality in the customer roles:
|
||||
For all others, a user defined by the customer gets the owner role assigned, just for the customer, the owner's role is assigned to the 'administrators' role.
|
||||
|
||||
|
||||
### Package Roles
|
||||
|
||||
One example of the business-object-type-level right below is the *Package*.
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
' left to right direction
|
||||
top to bottom direction
|
||||
|
||||
' hide the ugly E in a circle left to the entity name
|
||||
hide circle
|
||||
|
||||
' use right-angled line routing
|
||||
' skinparam linetype ortho
|
||||
|
||||
' needs PlantUML 1.2021.14 as Markdown plugin
|
||||
allow_mixing
|
||||
|
||||
entity "BObj package#xyz00" as boPacXyz00
|
||||
|
||||
together {
|
||||
entity "Perm package#xyz00 *" as permPackageXyzAll
|
||||
permPackageXyzAll --> boPacXyz00
|
||||
|
||||
entity "Perm package#xyz00 add-unixuser" as permPacXyz00AddUser
|
||||
permPacXyz00AddUser --> boPacXyz00
|
||||
|
||||
entity "Perm package#xyz00 edit" as permPacXyz00Edit
|
||||
permPacXyz00Edit --> boPacXyz00
|
||||
|
||||
entity "Perm package#xyz00 view" as permPacXyz00View
|
||||
permPacXyz00View --> boPacXyz00
|
||||
}
|
||||
|
||||
package {
|
||||
entity "Role customer#xyz.tenant" as roleCustXyzTenant
|
||||
entity "Role customer#xyz.admin" as roleCustXyzAdmin
|
||||
entity "Role customer#xyz.owner" as roleCustXyzOwner
|
||||
}
|
||||
|
||||
package {
|
||||
entity "Role package#xyz00.owner" as rolePacXyz00Owner
|
||||
entity "Role package#xyz00.admin" as rolePacXyz00Admin
|
||||
entity "Role package#xyz00.tenant" as rolePacXyz00Tenant
|
||||
}
|
||||
|
||||
rolePacXyz00Tenant --> permPacXyz00View
|
||||
rolePacXyz00Tenant --> roleCustXyzTenant
|
||||
|
||||
rolePacXyz00Owner --> rolePacXyz00Admin
|
||||
rolePacXyz00Owner --> permPackageXyzAll
|
||||
|
||||
roleCustXyzAdmin --> rolePacXyz00Owner
|
||||
roleCustXyzAdmin --> roleCustXyzTenant
|
||||
|
||||
roleCustXyzOwner ..> roleCustXyzAdmin
|
||||
|
||||
rolePacXyz00Admin --> rolePacXyz00Tenant
|
||||
rolePacXyz00Admin --> permPacXyz00AddUser
|
||||
rolePacXyz00Admin --> permPacXyz00Edit
|
||||
|
||||
actor "Package XYZ00 Admin" as actorPacXyzAdmin
|
||||
actorPacXyzAdmin -l-> rolePacXyz00Admin
|
||||
|
||||
actor "Customer XYZ Admin" as actorCustXyzAdmin
|
||||
actorCustXyzAdmin --> roleCustXyzAdmin
|
||||
|
||||
entity "Role administrators" as roleAdmins
|
||||
roleAdmins --> roleCustXyzOwner
|
||||
|
||||
actor "Any Hostmaster" as actorHostmaster
|
||||
actorHostmaster --> roleAdmins
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
Initially, the customer's admin role is assigned to the package owner role.
|
||||
They can use the package's admin role to hand over most management functionality to a third party.
|
||||
The 'administrators' can get access through an assumed customer's admin role or directly by assuming the package's owner or admin role.
|
||||
|
||||
## Performance
|
||||
|
||||
We did not define maximum response time in our requirements,
|
||||
but set a target of 7.000 customers, 15.000 packages, 150.000 Unix users, 100.000 domains and 500.000 email-addresses.
|
||||
|
||||
For such a dataset the response time for typical queries from a UI should be acceptable.
|
||||
Also, when adding data beyond these quantities, increase in response time should be roughly linear or below.
|
||||
For this, we increased the dataset by 14% and then by another 25%, ending up with 10.000 customers, almost 25.000 packages, over 174.000 unix users, over 120.000 domains and almost 750.000 email-addresses.
|
||||
|
||||
The performance test suite comprised 8 SELECT queries issued by an administrator, mostly with two assumed customer owner roles.
|
||||
The tests started with finding a specific customer and ended with listing all accessible email-addresses joined with their domains, unix-users, packages and customers.
|
||||
|
||||
Find the SQL script here: `28-hs-tests.sql`.
|
||||
|
||||
### Two View Query Variants
|
||||
|
||||
We have tested two variants of the query for the restricted view,
|
||||
both utilizing a PostgreSQL function like this:
|
||||
|
||||
FUNCTION queryAccessibleObjectUuidsOfSubjectIds(
|
||||
requiredOp RbacOp,
|
||||
forObjectTable varchar,
|
||||
subjectIds uuid[],
|
||||
maxObjects integer = 16000)
|
||||
RETURNS SETOF uuid
|
||||
|
||||
The function returns all object uuids for which the given subjectIds (user o assumed roles) have a permission or required operation.
|
||||
|
||||
Let's have a look at the two view queries:
|
||||
|
||||
#### Using WHERE ... IN
|
||||
|
||||
CREATE OR REPLACE VIEW customer_rv AS
|
||||
SELECT DISTINCT target.*
|
||||
FROM customer AS target
|
||||
WHERE target.uuid IN (
|
||||
SELECT uuid
|
||||
FROM queryAccessibleObjectUuidsOfSubjectIds(
|
||||
'view', 'customer', currentSubjectIds()));
|
||||
|
||||
This view should be automatically updatable.
|
||||
Where, for updates, we actually have to check for 'edit' instead of 'view' operation, which makes it a bit more complicated.
|
||||
|
||||
With the larger dataset, the test suite initially needed over 7 seconds with this view query.
|
||||
At this point the second variant was tried.
|
||||
|
||||
But after the initial query, the execution time was drastically reduced,
|
||||
even with different query values.
|
||||
Looks like the query optimizer needed some statistics to find the best path.
|
||||
|
||||
#### Using A JOIN
|
||||
|
||||
CREATE OR REPLACE VIEW customer_rv AS
|
||||
SELECT DISTINCT target.*
|
||||
FROM customer AS target
|
||||
JOIN queryAccessibleObjectUuidsOfSubjectIds(
|
||||
'view', 'customer', currentSubjectIds()) AS allowedObjId
|
||||
ON target.uuid = allowedObjId;
|
||||
|
||||
This view cannot is not updatable automatically,
|
||||
but it was quite fast from the beginning.
|
||||
|
||||
### Performance Results
|
||||
|
||||
The following table shows the average between the second and the third repeat of the test-suite:
|
||||
|
||||
| Dataset | using JOIN | using WHERE IN |
|
||||
|----------------:|-----------:|---------------:|
|
||||
| 7000 customers | 670ms | 1040ms |
|
||||
| 10000 customers | 1050ms | 1125ms |
|
||||
| +43% | +57% | +8% |
|
||||
|
||||
The JOIN-variant is still faster, but the growth in execution time exceeded the growth of the dataset.
|
||||
|
||||
The WHERE-IN-variant is about 50% slower on the smaller dataset, but almost keeps its performance on the larger dataset.
|
||||
|
||||
Both variants a viable option, depending on other needs, e.g. updatable views.
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user