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,54 @@ 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+ isWildcard = permissions.isSuperuser()
229+ )
230+ }
231+
232+ /* *
233+ * Check if the given [containedInId] filter is contained in any of the includes in the given [permissions]. If so,
234+ * the user can access all elements under the [containedInId] ID, so return a map of includes with just this ID.
235+ * If this is not the case or [containedInId] is undefined, return *null*.
236+ */
237+ private fun includesDominatedByContainsFilter (
238+ permissions : HierarchyPermissions ,
239+ containedInId : CompoundHierarchyId ?
240+ ): IdsByLevel ? =
241+ containedInId?.let {
242+ val containedInLevel = it.level
243+ val isFilterCovered = permissions.includes().entries.any { (level, ids) ->
244+ level < containedInLevel && ids.any { id -> id.contains(containedInId) }
245+ }
246+
247+ if (isFilterCovered) {
248+ mapOf (containedInId.level to listOf (containedInId))
249+ } else {
250+ null
251+ }
252+ }
253+
202254 /* *
203255 * Retrieve the missing components to construct a [CompoundHierarchyId] from the given [hierarchyId]. Throw a
204256 * meaningful exception if this fails.
@@ -246,19 +298,20 @@ class DbAuthorizationService(
246298 /* *
247299 * Load all role assignments for the given [userId] in the hierarchy defined by [compoundHierarchyId]. Return a
248300 * 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.
301+ * the whole hierarchy below the organization referenced by [compoundHierarchyId] or all assignments of the given
302+ * [userId] if no ID is specified. This makes sure that all relevant assignments are found.
251303 */
252304 private suspend fun loadAssignments (
253305 userId : String ,
254- compoundHierarchyId : CompoundHierarchyId
306+ compoundHierarchyId : CompoundHierarchyId ?
255307 ): List <Pair <CompoundHierarchyId , Role >> = db.dbQuery {
256308 RoleAssignmentsTable .selectAll()
257309 .where {
258- (RoleAssignmentsTable .userId eq userId) and (
259- (RoleAssignmentsTable .organizationId eq compoundHierarchyId.organizationId?.value) or
260- (RoleAssignmentsTable .organizationId eq null )
261- )
310+ val hierarchyCondition = compoundHierarchyId?.let { id ->
311+ (RoleAssignmentsTable .organizationId eq id.organizationId?.value) or
312+ (RoleAssignmentsTable .organizationId eq null )
313+ } ? : Op .TRUE
314+ (RoleAssignmentsTable .userId eq userId) and hierarchyCondition
262315 }.mapNotNull { row ->
263316 row.extractRole()?.let { role ->
264317 row.extractHierarchyId() to role
@@ -277,15 +330,15 @@ class DbAuthorizationService(
277330 (RoleAssignmentsTable .productId eq compoundHierarchyId.productId?.value) and
278331 (RoleAssignmentsTable .repositoryId eq compoundHierarchyId.repositoryId?.value)
279332 } == 1
280- ).also {
281- if (it) {
282- logger.info(
283- " Removed role assignment for user '{}' on hierarchy element {}." ,
284- userId,
285- compoundHierarchyId
286- )
333+ ).also {
334+ if (it) {
335+ logger.info(
336+ " Removed role assignment for user '{}' on hierarchy element {}." ,
337+ userId,
338+ compoundHierarchyId
339+ )
340+ }
287341 }
288- }
289342}
290343
291344/* *
@@ -387,6 +440,21 @@ private fun SqlExpressionBuilder.roleCondition(role: Role): Op<Boolean> =
387440 is RepositoryRole -> RoleAssignmentsTable .repositoryRole eq role.name
388441 }
389442
443+ /* *
444+ * Filter the IDs in this [IdsByLevel] to only include those that are contained in the given [containedInId]. If
445+ * [containedInId] is *null*, return this object unmodified.
446+ */
447+ private fun IdsByLevel.filterContainedIn (
448+ containedInId : CompoundHierarchyId ?
449+ ): IdsByLevel =
450+ if (containedInId == null ) {
451+ this
452+ } else {
453+ this .mapValues { (_, ids) ->
454+ ids.filter { id -> id in containedInId }
455+ }.filterValues { it.isNotEmpty() }
456+ }
457+
390458/* *
391459 * Return a collection with the roles that are relevant on the hierarchy level defined by [hierarchyId].
392460 */
0 commit comments