Skip to content

Commit eee6892

Browse files
committed
feat(authorization): Extend EffectiveRole for superuser checks
Add a new property to `EffectiveRole` that holds the superuser status, so that this can be checked efficiently. Extend `DbAuthorizationService` to set this flag correctly. Add more test cases for role assignments on the superuser level. Signed-off-by: Oliver Heger <[email protected]>
1 parent a5d1e9a commit eee6892

File tree

3 files changed

+60
-16
lines changed

3 files changed

+60
-16
lines changed

components/authorization/backend/src/main/kotlin/rights/EffectiveRole.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ interface EffectiveRole {
3434
*/
3535
val elementId: CompoundHierarchyId
3636

37+
/**
38+
* A flag indicating whether the associated user has superuser rights.
39+
*/
40+
val isSuperuser: Boolean
41+
3742
/**
3843
* Check whether this effective role grants the given [permission] on the organization level.
3944
*/

components/authorization/backend/src/main/kotlin/service/DbAuthorizationService.kt

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,20 @@ class DbAuthorizationService(
6767
userId: String,
6868
compoundHierarchyId: CompoundHierarchyId
6969
): EffectiveRole {
70+
val roleAssignments = loadAssignments(userId, compoundHierarchyId)
71+
val permissions = roleAssignments.map { it.toHierarchyPermissions() }
72+
.takeUnless { it.isEmpty() }?.reduce(::reducePermissions) ?: EMPTY_PERMISSIONS
73+
val isSuperuser = roleAssignments.any {
74+
it[RoleAssignmentsTable.organizationId] == null &&
75+
it[RoleAssignmentsTable.productId] == null &&
76+
it[RoleAssignmentsTable.repositoryId] == null &&
77+
it.extractRole() == OrganizationRole.ADMIN
78+
}
79+
7080
return EffectiveRoleImpl(
7181
elementId = compoundHierarchyId,
72-
permissions = loadAssignments(userId, compoundHierarchyId)
73-
.takeUnless { it.isEmpty() }?.reduce(::reducePermissions) ?: EMPTY_PERMISSIONS
82+
isSuperuser = isSuperuser,
83+
permissions = permissions
7484
)
7585
}
7686

@@ -85,6 +95,7 @@ class DbAuthorizationService(
8595

8696
EffectiveRoleImpl(
8797
elementId = compoundHierarchyId,
98+
isSuperuser = false,
8899
permissions = EMPTY_PERMISSIONS
89100
)
90101
} else {
@@ -200,15 +211,15 @@ class DbAuthorizationService(
200211
private suspend fun loadAssignments(
201212
userId: String,
202213
compoundHierarchyId: CompoundHierarchyId
203-
): List<HierarchyPermissions> = db.dbQuery {
214+
): List<ResultRow> = db.dbQuery {
204215
RoleAssignmentsTable.selectAll()
205216
.where {
206217
(RoleAssignmentsTable.userId eq userId) and (
207218
repositoryCondition(compoundHierarchyId) or
208219
productWildcardCondition(compoundHierarchyId) or
209220
organizationWildcardCondition()
210221
)
211-
}.map { it.toHierarchyPermissions() }
222+
}.toList()
212223
}
213224

214225
/**
@@ -265,6 +276,8 @@ private val EMPTY_PERMISSIONS = HierarchyPermissions(
265276
private class EffectiveRoleImpl(
266277
override val elementId: CompoundHierarchyId,
267278

279+
override val isSuperuser: Boolean,
280+
268281
/** The permissions granted on the different levels of the hierarchy. */
269282
private val permissions: HierarchyPermissions
270283
) : EffectiveRole {
@@ -330,11 +343,12 @@ private fun reducePermissions(
330343
* assignments on higher levels in the same hierarchy.
331344
*/
332345
private fun SqlExpressionBuilder.repositoryCondition(hierarchyId: CompoundHierarchyId): Op<Boolean> =
333-
(RoleAssignmentsTable.repositoryId eq hierarchyId.repositoryId?.value) or (
334-
(RoleAssignmentsTable.repositoryId eq null) and
346+
(
347+
(RoleAssignmentsTable.repositoryId eq hierarchyId.repositoryId?.value) or
348+
(RoleAssignmentsTable.repositoryId eq null)
349+
) and
335350
(RoleAssignmentsTable.productId eq hierarchyId.productId?.value) and
336351
(RoleAssignmentsTable.organizationId eq hierarchyId.organizationId?.value)
337-
)
338352

339353
/**
340354
* Generate the SQL condition to match role assignments for the given [hierarchyId] for which no product ID is

components/authorization/backend/src/test/kotlin/service/DbAuthorizationServiceTest.kt

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,26 @@ class DbAuthorizationServiceTest : WordSpec() {
278278

279279
val effectiveRole = service.getEffectiveRole(USER_ID, repositoryCompoundId)
280280

281-
checkPermissions(effectiveRole, OrganizationRole.ADMIN)
281+
checkPermissions(effectiveRole, OrganizationRole.ADMIN, expectedSuperuser = true)
282+
}
283+
284+
"allow querying super users only" {
285+
val normalUser = "normal-user"
286+
createAssignment(
287+
organizationRole = OrganizationRole.ADMIN
288+
)
289+
createAssignment(
290+
userId = normalUser,
291+
organizationId = dbExtension.fixtures.organization.id,
292+
organizationRole = OrganizationRole.READER
293+
)
294+
val service = createService()
295+
296+
val effectiveRoleNormal = service.getEffectiveRole(normalUser, CompoundHierarchyId.WILDCARD)
297+
val effectiveRoleSuper = service.getEffectiveRole(USER_ID, CompoundHierarchyId.WILDCARD)
298+
299+
checkPermissions(effectiveRoleNormal)
300+
checkPermissions(effectiveRoleSuper, OrganizationRole.ADMIN, expectedSuperuser = true)
282301
}
283302

284303
"not fail for invalid role names" {
@@ -332,7 +351,7 @@ class DbAuthorizationServiceTest : WordSpec() {
332351
checkPermissions(effectiveRole, ProductRole.WRITER)
333352

334353
val effectiveRoleOrg = service.getEffectiveRole(USER_ID, productCompoundId.parent!!)
335-
checkPermissions(effectiveRoleOrg, ProductRole.WRITER)
354+
checkPermissions(effectiveRoleOrg)
336355
}
337356

338357
"create a new role assignment on organization level" {
@@ -354,7 +373,7 @@ class DbAuthorizationServiceTest : WordSpec() {
354373
checkPermissions(effectiveRoleRepo, OrganizationRole.WRITER)
355374
}
356375

357-
"create a new role assignment for the WILDCARD ID" {
376+
"create a new superuser role assignment" {
358377
val service = createService()
359378

360379
service.assignRole(
@@ -364,7 +383,7 @@ class DbAuthorizationServiceTest : WordSpec() {
364383
)
365384

366385
val effectiveRole = service.getEffectiveRole(USER_ID, repositoryCompoundId())
367-
checkPermissions(effectiveRole, OrganizationRole.ADMIN)
386+
checkPermissions(effectiveRole, OrganizationRole.ADMIN, expectedSuperuser = true)
368387
}
369388

370389
"replace an already exiting assignment" {
@@ -710,13 +729,15 @@ private const val USER_ID = "test-user"
710729

711730
/**
712731
* Check that the given [effectiveRole] contains exactly the specified [expectedOrganizationPermissions],
713-
* [expectedProductPermissions], and [expectedRepositoryPermissions] on the different hierarchy levels.
732+
* [expectedProductPermissions], and [expectedRepositoryPermissions] on the different hierarchy levels. Also check the
733+
* [superuser][expectedSuperuser] flag.
714734
*/
715735
private fun checkPermissions(
716736
effectiveRole: EffectiveRole,
717737
expectedOrganizationPermissions: Set<OrganizationPermission> = emptySet(),
718738
expectedProductPermissions: Set<ProductPermission> = emptySet(),
719-
expectedRepositoryPermissions: Set<RepositoryPermission> = emptySet()
739+
expectedRepositoryPermissions: Set<RepositoryPermission> = emptySet(),
740+
expectedSuperuser: Boolean = false
720741
) {
721742
OrganizationPermission.entries.forAll {
722743
effectiveRole.hasOrganizationPermission(it) shouldBe (it in expectedOrganizationPermissions)
@@ -727,15 +748,19 @@ private fun checkPermissions(
727748
RepositoryPermission.entries.forAll {
728749
effectiveRole.hasRepositoryPermission(it) shouldBe (it in expectedRepositoryPermissions)
729750
}
751+
752+
effectiveRole.isSuperuser shouldBe expectedSuperuser
730753
}
731754

732755
/**
733-
* Check that the given [effectiveRole] contains exactly the permissions as defined by the given [expectedRole].
756+
* Check that the given [effectiveRole] contains exactly the permissions as defined by the given [expectedRole]. Also
757+
* check the [superuser][expectedSuperuser] flag.
734758
*/
735-
private fun checkPermissions(effectiveRole: EffectiveRole, expectedRole: Role) =
759+
private fun checkPermissions(effectiveRole: EffectiveRole, expectedRole: Role, expectedSuperuser: Boolean = false) =
736760
checkPermissions(
737761
effectiveRole,
738762
expectedRole.organizationPermissions,
739763
expectedRole.productPermissions,
740-
expectedRole.repositoryPermissions
764+
expectedRole.repositoryPermissions,
765+
expectedSuperuser
741766
)

0 commit comments

Comments
 (0)