1717 * License-Filename: LICENSE
1818 */
1919
20+ @file:Suppress(" TooManyFunctions" )
21+
2022package org.eclipse.apoapsis.ortserver.components.authorization.service
2123
2224import org.eclipse.apoapsis.ortserver.components.authorization.db.RoleAssignmentsTable
2325import org.eclipse.apoapsis.ortserver.components.authorization.rights.EffectiveRole
2426import org.eclipse.apoapsis.ortserver.components.authorization.rights.HierarchyPermissions
27+ import org.eclipse.apoapsis.ortserver.components.authorization.rights.IdsByLevel
2528import org.eclipse.apoapsis.ortserver.components.authorization.rights.OrganizationPermission
2629import org.eclipse.apoapsis.ortserver.components.authorization.rights.OrganizationRole
2730import org.eclipse.apoapsis.ortserver.components.authorization.rights.PermissionChecker
@@ -38,6 +41,7 @@ import org.eclipse.apoapsis.ortserver.model.HierarchyId
3841import org.eclipse.apoapsis.ortserver.model.OrganizationId
3942import org.eclipse.apoapsis.ortserver.model.ProductId
4043import org.eclipse.apoapsis.ortserver.model.RepositoryId
44+ import org.eclipse.apoapsis.ortserver.model.util.HierarchyFilter
4145
4246import org.jetbrains.exposed.sql.Database
4347import org.jetbrains.exposed.sql.JoinType
@@ -199,6 +203,55 @@ class DbAuthorizationService(
199203 .mapValues { it.value.filterNotNullTo(mutableSetOf ()) }
200204 }
201205
206+ override suspend fun filterHierarchyIds (
207+ userId : String ,
208+ organizationPermissions : Set <OrganizationPermission >,
209+ productPermissions : Set <ProductPermission >,
210+ repositoryPermissions : Set <RepositoryPermission >,
211+ containedIn : HierarchyId ?
212+ ): HierarchyFilter {
213+ val assignments = loadAssignments(userId, null )
214+ val checker = PermissionChecker (
215+ organizationPermissions,
216+ productPermissions,
217+ repositoryPermissions
218+ )
219+ val permissions = HierarchyPermissions .create(assignments, checker)
220+
221+ val containedInId = containedIn?.let { resolveCompoundId(it) }
222+ val includes = includesDominatedByContainsFilter(permissions, containedInId)
223+ ? : permissions.includes().filterContainedIn(containedInId)
224+
225+ return HierarchyFilter (
226+ transitiveIncludes = includes,
227+ nonTransitiveIncludes = permissions.implicitIncludes().filterContainedIn(containedInId),
228+ excludes = permissions.excludes().filterContainedIn(containedInId),
229+ isWildcard = permissions.isSuperuser()
230+ )
231+ }
232+
233+ /* *
234+ * Check if the given [containedInId] filter is contained in any of the includes in the given [permissions]. If so,
235+ * the user can access all elements under the [containedInId] ID, so return a map of includes with just this ID.
236+ * If this is not the case or [containedInId] is undefined, return *null*.
237+ */
238+ private fun includesDominatedByContainsFilter (
239+ permissions : HierarchyPermissions ,
240+ containedInId : CompoundHierarchyId ?
241+ ): IdsByLevel ? =
242+ containedInId?.let {
243+ val containedInLevel = it.level
244+ val isFilterCovered = permissions.includes().entries.any { (level, ids) ->
245+ level < containedInLevel && ids.any { id -> id.contains(containedInId) }
246+ }
247+
248+ if (isFilterCovered) {
249+ mapOf (containedInId.level to listOf (containedInId))
250+ } else {
251+ null
252+ }
253+ }
254+
202255 /* *
203256 * Retrieve the missing components to construct a [CompoundHierarchyId] from the given [hierarchyId]. Throw a
204257 * meaningful exception if this fails.
@@ -246,19 +299,20 @@ class DbAuthorizationService(
246299 /* *
247300 * Load all role assignments for the given [userId] in the hierarchy defined by [compoundHierarchyId]. Return a
248301 * 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.
302+ * the whole hierarchy below the organization referenced by [compoundHierarchyId] or all assignments of the given
303+ * [userId] if no ID is specified. This makes sure that all relevant assignments are found.
251304 */
252305 private suspend fun loadAssignments (
253306 userId : String ,
254- compoundHierarchyId : CompoundHierarchyId
307+ compoundHierarchyId : CompoundHierarchyId ?
255308 ): List <Pair <CompoundHierarchyId , Role >> = db.dbQuery {
256309 RoleAssignmentsTable .selectAll()
257310 .where {
258- (RoleAssignmentsTable .userId eq userId) and (
259- (RoleAssignmentsTable .organizationId eq compoundHierarchyId.organizationId?.value) or
260- (RoleAssignmentsTable .organizationId eq null )
261- )
311+ val hierarchyCondition = compoundHierarchyId?.let { id ->
312+ (RoleAssignmentsTable .organizationId eq id.organizationId?.value) or
313+ (RoleAssignmentsTable .organizationId eq null )
314+ } ? : Op .TRUE
315+ (RoleAssignmentsTable .userId eq userId) and hierarchyCondition
262316 }.mapNotNull { row ->
263317 row.extractRole()?.let { role ->
264318 row.extractHierarchyId() to role
@@ -277,15 +331,15 @@ class DbAuthorizationService(
277331 (RoleAssignmentsTable .productId eq compoundHierarchyId.productId?.value) and
278332 (RoleAssignmentsTable .repositoryId eq compoundHierarchyId.repositoryId?.value)
279333 } == 1
280- ).also {
281- if (it) {
282- logger.info(
283- " Removed role assignment for user '{}' on hierarchy element {}." ,
284- userId,
285- compoundHierarchyId
286- )
334+ ).also {
335+ if (it) {
336+ logger.info(
337+ " Removed role assignment for user '{}' on hierarchy element {}." ,
338+ userId,
339+ compoundHierarchyId
340+ )
341+ }
287342 }
288- }
289343}
290344
291345/* *
@@ -387,6 +441,21 @@ private fun SqlExpressionBuilder.roleCondition(role: Role): Op<Boolean> =
387441 is RepositoryRole -> RoleAssignmentsTable .repositoryRole eq role.name
388442 }
389443
444+ /* *
445+ * Filter the IDs in this [IdsByLevel] to only include those that are contained in the given [containedInId]. If
446+ * [containedInId] is *null*, return this object unmodified.
447+ */
448+ private fun IdsByLevel.filterContainedIn (
449+ containedInId : CompoundHierarchyId ?
450+ ): IdsByLevel =
451+ if (containedInId == null ) {
452+ this
453+ } else {
454+ this .mapValues { (_, ids) ->
455+ ids.filter { id -> id in containedInId }
456+ }.filterValues { it.isNotEmpty() }
457+ }
458+
390459/* *
391460 * Return a collection with the roles that are relevant on the hierarchy level defined by [hierarchyId].
392461 */
0 commit comments