Skip to content

Commit a1b1e83

Browse files
committed
feat(authorization): Introduce InvalidHierarchyIdException
This is a new exception class that is thrown by `DbAuthorizationService` when it cannot resolve a hierarchy ID. Throwing a special exception in this case allows handling this error condition differently from normal authorization errors. For instance, the REST API should return a 404 response if users provide non-existing IDs. Signed-off-by: Oliver Heger <[email protected]>
1 parent 0386cb8 commit a1b1e83

File tree

3 files changed

+69
-48
lines changed

3 files changed

+69
-48
lines changed

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

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,8 @@ class DbAuthorizationService(
9696
userId: String,
9797
hierarchyId: HierarchyId,
9898
checker: PermissionChecker
99-
): EffectiveRole? {
100-
val compoundHierarchyId = resolveCompoundId(hierarchyId)
101-
102-
return compoundHierarchyId.takeUnless { it.isInvalid() }?.let {
103-
checkPermissions(userId, it, checker)
104-
}
105-
}
99+
): EffectiveRole? =
100+
checkPermissions(userId, resolveCompoundId(hierarchyId), checker)
106101

107102
override suspend fun getEffectiveRole(
108103
userId: String,
@@ -129,21 +124,8 @@ class DbAuthorizationService(
129124
override suspend fun getEffectiveRole(
130125
userId: String,
131126
hierarchyId: HierarchyId
132-
): EffectiveRole {
133-
val compoundHierarchyId = resolveCompoundId(hierarchyId)
134-
135-
return if (compoundHierarchyId.isInvalid()) {
136-
logger.warn("Failed to resolve hierarchy ID $hierarchyId.")
137-
138-
EffectiveRoleImpl(
139-
elementId = compoundHierarchyId,
140-
isSuperuser = false,
141-
permissions = PermissionChecker()
142-
)
143-
} else {
144-
getEffectiveRole(userId, compoundHierarchyId)
145-
}
146-
}
127+
): EffectiveRole =
128+
getEffectiveRole(userId, resolveCompoundId(hierarchyId))
147129

148130
override suspend fun assignRole(
149131
userId: String,
@@ -273,6 +255,11 @@ class DbAuthorizationService(
273255
val (orgId, prodId) = resolveOrganizationAndProduct(hierarchyId)
274256
CompoundHierarchyId.forRepository(orgId, prodId, hierarchyId)
275257
}
258+
}.also {
259+
if (it.isInvalid()) {
260+
logger.warn("Failed to resolve hierarchy ID $hierarchyId.")
261+
throw InvalidHierarchyIdException(hierarchyId)
262+
}
276263
}
277264

278265
/**
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (C) 2025 The ORT Server Authors (See <https://github.com/eclipse-apoapsis/ort-server/blob/main/NOTICE>)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.eclipse.apoapsis.ortserver.components.authorization.service
21+
22+
import org.eclipse.apoapsis.ortserver.model.HierarchyId
23+
24+
/**
25+
* An exception class that is thrown by the [AuthorizationService] when it cannot resolve a [HierarchyId]. This
26+
* typically means that users have called the API with the ID of a non-existing hierarchy element. Thus, such exceptions
27+
* should lead to HTTP 404 responses.
28+
*/
29+
class InvalidHierarchyIdException(
30+
val hierarchyId: HierarchyId
31+
) : RuntimeException("Could not resolve hierarchy ID: $hierarchyId.")

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

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.eclipse.apoapsis.ortserver.components.authorization.service
2121

22+
import io.kotest.assertions.throwables.shouldThrow
2223
import io.kotest.core.spec.style.WordSpec
2324
import io.kotest.inspectors.forAll
2425
import io.kotest.matchers.collections.shouldBeSingleton
@@ -29,6 +30,7 @@ import io.kotest.matchers.nulls.beNull
2930
import io.kotest.matchers.nulls.shouldNotBeNull
3031
import io.kotest.matchers.should
3132
import io.kotest.matchers.shouldBe
33+
import io.kotest.matchers.string.shouldContain
3234

3335
import org.eclipse.apoapsis.ortserver.components.authorization.db.RoleAssignmentsTable
3436
import org.eclipse.apoapsis.ortserver.components.authorization.rights.EffectiveRole
@@ -105,36 +107,33 @@ class DbAuthorizationServiceTest : WordSpec() {
105107
}
106108
}
107109

108-
"return an object with no permissions if resolving the product ID fails" {
110+
"throw an InvalidHierarchyIdException if resolving the product ID fails" {
109111
val service = createService()
110-
111112
val missingProductId = ProductId(-1L)
112113

113-
val effectiveRole = service.getEffectiveRole(
114-
USER_ID,
115-
missingProductId
116-
)
114+
val exception = shouldThrow<InvalidHierarchyIdException> {
115+
service.getEffectiveRole(
116+
USER_ID,
117+
missingProductId
118+
)
119+
}
117120

118-
effectiveRole.elementId shouldBe CompoundHierarchyId.forProduct(OrganizationId(-1L), missingProductId)
119-
checkPermissions(effectiveRole)
121+
exception.hierarchyId shouldBe missingProductId
120122
}
121123

122-
"return an object with no permissions if resolving the repository ID fails" {
124+
"throw an IllegalHierarchyIdException if resolving the repository ID fails" {
123125
val service = createService()
124126

125127
val missingRepositoryId = RepositoryId(-1L)
126128

127-
val effectiveRole = service.getEffectiveRole(
128-
USER_ID,
129-
missingRepositoryId
130-
)
129+
val exception = shouldThrow<InvalidHierarchyIdException> {
130+
service.getEffectiveRole(
131+
USER_ID,
132+
missingRepositoryId
133+
)
134+
}
131135

132-
effectiveRole.elementId shouldBe CompoundHierarchyId.forRepository(
133-
OrganizationId(-1L),
134-
ProductId(-1L),
135-
missingRepositoryId
136-
)
137-
checkPermissions(effectiveRole)
136+
exception.hierarchyId shouldBe missingRepositoryId
138137
}
139138

140139
"return an object with no permissions for a user without role assignments" {
@@ -434,16 +433,20 @@ class DbAuthorizationServiceTest : WordSpec() {
434433
}
435434
}
436435

437-
"handle an invalid compound hierarchy ID gracefully" {
436+
"throw an InvalidHierarchyIdException for an invalid hierarchy ID" {
438437
val service = createService()
438+
val invalidId = ProductId(-1L)
439439

440-
val effectiveRole = service.checkPermissions(
441-
USER_ID,
442-
ProductId(-1L),
443-
HierarchyPermissions.permissions(ProductPermission.READ)
444-
)
440+
val exception = shouldThrow<InvalidHierarchyIdException> {
441+
service.checkPermissions(
442+
USER_ID,
443+
invalidId,
444+
HierarchyPermissions.permissions(ProductPermission.READ)
445+
)
446+
}
445447

446-
effectiveRole should beNull()
448+
exception.hierarchyId shouldBe invalidId
449+
exception.message shouldContain "Could not resolve hierarchy ID"
447450
}
448451
}
449452

0 commit comments

Comments
 (0)