@@ -21,8 +21,10 @@ package org.eclipse.apoapsis.ortserver.components.authorization.service
2121
2222import org.eclipse.apoapsis.ortserver.components.authorization.db.RoleAssignmentsTable
2323import org.eclipse.apoapsis.ortserver.components.authorization.rights.EffectiveRole
24+ import org.eclipse.apoapsis.ortserver.components.authorization.rights.HierarchyPermissions
2425import org.eclipse.apoapsis.ortserver.components.authorization.rights.OrganizationPermission
2526import org.eclipse.apoapsis.ortserver.components.authorization.rights.OrganizationRole
27+ import org.eclipse.apoapsis.ortserver.components.authorization.rights.PermissionChecker
2628import org.eclipse.apoapsis.ortserver.components.authorization.rights.ProductPermission
2729import org.eclipse.apoapsis.ortserver.components.authorization.rights.ProductRole
2830import org.eclipse.apoapsis.ortserver.components.authorization.rights.RepositoryPermission
@@ -63,24 +65,61 @@ class DbAuthorizationService(
6365 /* * The database to use. */
6466 private val db : Database
6567) : AuthorizationService {
68+ override suspend fun checkPermissions (
69+ userId : String ,
70+ compoundHierarchyId : CompoundHierarchyId ,
71+ checker : PermissionChecker
72+ ): EffectiveRole ? {
73+ val roleAssignments = loadAssignments(userId, compoundHierarchyId)
74+
75+ val permissions = HierarchyPermissions .create(roleAssignments, checker)
76+ return if (permissions.hasPermission(compoundHierarchyId)) {
77+ EffectiveRoleImpl (
78+ elementId = compoundHierarchyId,
79+ isSuperuser = permissions.isSuperuser(),
80+ permissions = checker
81+ )
82+ } else {
83+ null
84+ }
85+ }
86+
87+ override suspend fun checkPermissions (
88+ userId : String ,
89+ hierarchyId : HierarchyId ,
90+ checker : PermissionChecker
91+ ): EffectiveRole ? {
92+ val compoundHierarchyId = resolveCompoundId(hierarchyId)
93+
94+ return compoundHierarchyId.takeUnless { it.isInvalid() }?.let {
95+ checkPermissions(userId, it, checker)
96+ }
97+ }
98+
6699 override suspend fun getEffectiveRole (
67100 userId : String ,
68101 compoundHierarchyId : CompoundHierarchyId
69102 ): EffectiveRole {
70103 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-
80- return EffectiveRoleImpl (
104+ val roles = rolesForLevel(compoundHierarchyId).reversed().asSequence()
105+
106+ // Check for all roles on the current level, starting with the ADMIN role, whether all its permissions are
107+ // granted. This is then the effective role. This assumes that constants in the enums for roles are ordered
108+ // by the number of permissions they grant in ascending order. If this changes, there should be failing tests.
109+ return roles.mapNotNull { role ->
110+ val permissionChecker = HierarchyPermissions .permissions(role)
111+ HierarchyPermissions .create(roleAssignments, permissionChecker)
112+ .takeIf { it.hasPermission(compoundHierarchyId) }?.let {
113+ EffectiveRoleImpl (
114+ elementId = compoundHierarchyId,
115+ isSuperuser = it.isSuperuser(),
116+ permissions = permissionChecker
117+ )
118+ }
119+ }.firstOrNull() ? : EffectiveRoleImpl (
81120 elementId = compoundHierarchyId,
82- isSuperuser = isSuperuser ,
83- permissions = permissions
121+ isSuperuser = false ,
122+ permissions = PermissionChecker ()
84123 )
85124 }
86125
@@ -96,7 +135,7 @@ class DbAuthorizationService(
96135 EffectiveRoleImpl (
97136 elementId = compoundHierarchyId,
98137 isSuperuser = false ,
99- permissions = EMPTY_PERMISSIONS
138+ permissions = PermissionChecker ()
100139 )
101140 } else {
102141 getEffectiveRole(userId, compoundHierarchyId)
@@ -206,20 +245,25 @@ class DbAuthorizationService(
206245
207246 /* *
208247 * Load all role assignments for the given [userId] in the hierarchy defined by [compoundHierarchyId]. Return a
209- * list of [HierarchyPermissions] instances for the entities that were found.
248+ * list with pairs of IDs and assigned roles for the entities that were found. The function selects assignments in
249+ * the whole hierarchy below the organization referenced by [compoundHierarchyId]. This makes sure that all
250+ * relevant assignments are found.
210251 */
211252 private suspend fun loadAssignments (
212253 userId : String ,
213254 compoundHierarchyId : CompoundHierarchyId
214- ): List <ResultRow > = db.dbQuery {
255+ ): List <Pair < CompoundHierarchyId , Role > > = db.dbQuery {
215256 RoleAssignmentsTable .selectAll()
216257 .where {
217258 (RoleAssignmentsTable .userId eq userId) and (
218- repositoryCondition(compoundHierarchyId) or
219- productWildcardCondition(compoundHierarchyId) or
220- organizationWildcardCondition()
259+ (RoleAssignmentsTable .organizationId eq compoundHierarchyId.organizationId?.value) or
260+ (RoleAssignmentsTable .organizationId eq null )
221261 )
222- }.toList()
262+ }.mapNotNull { row ->
263+ row.extractRole()?.let { role ->
264+ row.extractHierarchyId() to role
265+ }
266+ }
223267 }
224268
225269 /* *
@@ -249,27 +293,6 @@ class DbAuthorizationService(
249293 */
250294private const val INVALID_ID = - 1L
251295
252- /* *
253- * An internally used data class to store the available permissions on all levels of the hierarchy for a user.
254- */
255- private data class HierarchyPermissions (
256- /* * The permissions granted on organization level. */
257- val organizationPermissions : Set <OrganizationPermission >,
258-
259- /* * The permissions granted on product level. */
260- val productPermissions : Set <ProductPermission >,
261-
262- /* * The permissions granted on repository level. */
263- val repositoryPermissions : Set <RepositoryPermission >
264- )
265-
266- /* * An instance of [HierarchyPermissions] with no permissions at all. */
267- private val EMPTY_PERMISSIONS = HierarchyPermissions (
268- organizationPermissions = emptySet(),
269- productPermissions = emptySet(),
270- repositoryPermissions = emptySet()
271- )
272-
273296/* *
274297 * An implementation of the [EffectiveRole] interface used by [DbAuthorizationService].
275298 */
@@ -279,7 +302,7 @@ private class EffectiveRoleImpl(
279302 override val isSuperuser : Boolean ,
280303
281304 /* * The permissions granted on the different levels of the hierarchy. */
282- private val permissions : HierarchyPermissions
305+ private val permissions : PermissionChecker
283306) : EffectiveRole {
284307 override fun hasOrganizationPermission (permission : OrganizationPermission ): Boolean =
285308 permission in permissions.organizationPermissions
@@ -313,42 +336,38 @@ private fun ResultRow.extractRole(): Role? = runCatching {
313336}.getOrNull()
314337
315338/* *
316- * Obtain the information about roles from this [ResultRow] and construct a [HierarchyPermissions] object from it .
339+ * Extract the [CompoundHierarchyId] on the correct level from this [ResultRow].
317340 */
318- private fun ResultRow.toHierarchyPermissions (): HierarchyPermissions =
319- extractRole()?.let { role ->
320- HierarchyPermissions (
321- organizationPermissions = role.organizationPermissions,
322- productPermissions = role.productPermissions,
323- repositoryPermissions = role.repositoryPermissions
324- )
325- } ? : EMPTY_PERMISSIONS
326-
327- /* *
328- * Combine two [HierarchyPermissions] instances [p1] and [p2] by constructing the union of their permissions on all
329- * levels.
330- */
331- private fun reducePermissions (
332- p1 : HierarchyPermissions ,
333- p2 : HierarchyPermissions
334- ): HierarchyPermissions =
335- HierarchyPermissions (
336- organizationPermissions = p1.organizationPermissions + p2.organizationPermissions,
337- productPermissions = p1.productPermissions + p2.productPermissions,
338- repositoryPermissions = p1.repositoryPermissions + p2.repositoryPermissions
339- )
341+ private fun ResultRow.extractHierarchyId (): CompoundHierarchyId {
342+ val orgId = this [RoleAssignmentsTable .organizationId]?.let { OrganizationId (it.value) }
343+ if (orgId == null ) {
344+ return CompoundHierarchyId .WILDCARD
345+ } else {
346+ val productId = this [RoleAssignmentsTable .productId]?.let { ProductId (it.value) }
347+ if (productId == null ) {
348+ return CompoundHierarchyId .forOrganization(orgId)
349+ } else {
350+ val repositoryId = this [RoleAssignmentsTable .repositoryId]?.let { RepositoryId (it.value) }
351+ return if (repositoryId == null ) {
352+ CompoundHierarchyId .forProduct(orgId, productId)
353+ } else {
354+ CompoundHierarchyId .forRepository(orgId, productId, repositoryId)
355+ }
356+ }
357+ }
358+ }
340359
341360/* *
342361 * Generate the SQL condition to match the repository part of this [hierarchyId]. The condition also has to select
343362 * assignments on higher levels in the same hierarchy.
344363 */
345364private fun SqlExpressionBuilder.repositoryCondition (hierarchyId : CompoundHierarchyId ): Op <Boolean > =
346365 (
347- (RoleAssignmentsTable .repositoryId eq hierarchyId.repositoryId?.value) or
348- (RoleAssignmentsTable .repositoryId eq null )
349- ) and
350- (RoleAssignmentsTable .productId eq hierarchyId.productId?.value) and
351- (RoleAssignmentsTable .organizationId eq hierarchyId.organizationId?.value)
366+ (RoleAssignmentsTable .repositoryId eq hierarchyId.repositoryId?.value) or
367+ (RoleAssignmentsTable .repositoryId eq null )
368+ ) and
369+ (RoleAssignmentsTable .productId eq hierarchyId.productId?.value) and
370+ (RoleAssignmentsTable .organizationId eq hierarchyId.organizationId?.value)
352371
353372/* *
354373 * Generate the SQL condition to match role assignments for the given [hierarchyId] for which no product ID is
@@ -358,12 +377,6 @@ private fun SqlExpressionBuilder.productWildcardCondition(hierarchyId: CompoundH
358377 (RoleAssignmentsTable .productId eq null ) and
359378 (RoleAssignmentsTable .organizationId eq hierarchyId.organizationId?.value)
360379
361- /* *
362- * Generate the SQL condition to match role assignments for which no organization ID is defined.
363- */
364- private fun SqlExpressionBuilder.organizationWildcardCondition (): Op <Boolean > =
365- RoleAssignmentsTable .organizationId eq null
366-
367380/* *
368381 * Generate the SQL condition to match role assignments for the given [role].
369382 */
@@ -373,3 +386,13 @@ private fun SqlExpressionBuilder.roleCondition(role: Role): Op<Boolean> =
373386 is ProductRole -> RoleAssignmentsTable .productRole eq role.name
374387 is RepositoryRole -> RoleAssignmentsTable .repositoryRole eq role.name
375388 }
389+
390+ /* *
391+ * Return a collection with the roles that are relevant on the hierarchy level defined by [hierarchyId].
392+ */
393+ private fun rolesForLevel (hierarchyId : CompoundHierarchyId ): Collection <Role > =
394+ when (hierarchyId.level) {
395+ CompoundHierarchyId .REPOSITORY_LEVEL -> RepositoryRole .entries
396+ CompoundHierarchyId .PRODUCT_LEVEL -> ProductRole .entries
397+ else -> OrganizationRole .entries
398+ }
0 commit comments