Skip to content

Conversation

@oheger-bosch
Copy link
Contributor

This PR should contain everything that is needed to switch to the new authorization component:

  • the reworked routes
  • the migration to the new data structures
  • changes for the UI

This is still WIP.

@oheger-bosch oheger-bosch force-pushed the oheger-bosch/db_authorization_integration branch 5 times, most recently from b2449bd to 8463c1f Compare November 10, 2025 09:52
@mnonnenmacher
Copy link
Contributor

mnonnenmacher commented Nov 13, 2025

@heger I rebase this PR locally, built the core image, and deployed to my local Docker Compose to look into the required UI changes. It looks like it imported the roles correctly (both users are in the superusers group):
image

However, when I try to create an organization the request fails with this exception:

core-1  | time=2025-11-13T17:52:07.943 thread=DefaultDispatcher-worker-3 traceId=c0586844-e670-4bc1-b227-b539212fd17d component=core level=DEBUG logger=o.e.a.o.c.a.s.DbAuthorizationService message="Loading role assignments for user 'admin' on eleme
nt CompoundHierarchyId(organizationId=null, productId=null, repositoryId=null)..."
core-1  | java.lang.NullPointerException: asString(...) must not be null
core-1  |       at org.eclipse.apoapsis.ortserver.components.authorization.routes.AuthorizedRoutesKt.createAuthorizedPrincipal(AuthorizedRoutes.kt:109)
core-1  |       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
core-1  |       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
core-1  |       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)

I also see this exception in the logs:

core-1  | time=2025-11-13T17:57:03.404 thread=DefaultDispatcher-worker-5 traceId=b31b063f-b8a0-4877-8f96-b30f1e56939e component=core level=DEBUG logger=o.e.a.o.c.a.s.DbAuthorizationService message="Loading role assignments for user 'admin' on element CompoundHierarchyId(organizationId=null, productId=null, repositoryId=null)..."
core-1  | time=2025-11-13T17:57:03.407 thread=DefaultDispatcher-worker-5 traceId=b31b063f-b8a0-4877-8f96-b30f1e56939e component=core level=ERROR logger=StatusPages message="Internal Server Error"
core-1  | 	at org.eclipse.apoapsis.ortserver.components.authorization.routes.OrtServerPrincipal$Companion.create(OrtServerPrincipal.kt:72)
core-1  | 	at org.eclipse.apoapsis.ortserver.components.authorization.routes.AuthorizedRoutesKt$createAuthorizedPrincipal$1.invokeSuspend(AuthorizedRoutes.kt)
core-1  | 	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
core-1  | 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:829)
core-1  | 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)

Also listing the organizations fails, there is one organization that I should see as superuser, but I get an empty list because the query looks like this (WHERE TRUE AND FALSE):

core-1  | time=2025-11-13T17:58:12.303 thread=DefaultDispatcher-worker-4 traceId=2ea26e26-23ea-4501-91dd-5b181849e32a component=core level=TRACE logger=Exposed message="SELECT organizations.id, organizations.\"name\", organizations.description FROM organizations WHERE TRUE AND FALSE ORDER BY organizations.\"name\" ASC"

Do you have an idea what could be the problem?

@oheger-bosch
Copy link
Contributor Author

@mnonnenmacher, the stacktrace indicates that the username cannot be extracted from the access token. It is expected to be contained in the preferred_username claim. Is this set in the compose environment?

mnonnenmacher added a commit that referenced this pull request Nov 14, 2025
With the upcoming changes in [1] it will be required that the name is
not empty. Before, the admin user did not have any name set which lead
to the `name` claim missing from the JWT.

[1]: #3908

Signed-off-by: Martin Nonnenmacher <[email protected]>
@mnonnenmacher
Copy link
Contributor

@mnonnenmacher, the stacktrace indicates that the username cannot be extracted from the access token. It is expected to be contained in the preferred_username claim. Is this set in the compose environment?

preferred_username was present but it turned out that the problem was a missing name claim.

@oheger-bosch oheger-bosch force-pushed the oheger-bosch/db_authorization_integration branch from b2ac338 to d3f1157 Compare November 18, 2025 12:15
@mnonnenmacher mnonnenmacher force-pushed the oheger-bosch/db_authorization_integration branch 2 times, most recently from e2ceb33 to 4a62e9d Compare November 19, 2025 15:08
@oheger-bosch oheger-bosch force-pushed the oheger-bosch/db_authorization_integration branch from 4a62e9d to 42f61ec Compare November 26, 2025 11:45
@Etsija
Copy link
Contributor

Etsija commented Nov 28, 2025

Can I ask about the status of this PR? I'd need it to advance my search component.

@oheger-bosch
Copy link
Contributor Author

Can I ask about the status of this PR? I'd need it to advance my search component.

We have been doing tests this week, and so far, everything seems to be looking fine. So, I assume that it is going to be merged soon.

@mnonnenmacher mnonnenmacher force-pushed the oheger-bosch/db_authorization_integration branch 2 times, most recently from 2064a50 to 298803d Compare November 28, 2025 13:49
@oheger-bosch oheger-bosch marked this pull request as ready for review December 1, 2025 10:41
When installing the Keycloak extension, drop the `createRealmPerTest`
flag per default. The additional isolation is no longer needed for most
tests, since no roles or other data in Keycloak are manipulated during
test execution, and an initial setup of test users used by the test cases
is sufficient. For tests that do manipulate the state in Keycloak,
support enabling this feature on demand.

Avoiding the repeated setup of the realm saves a few seconds for every
test case, which has a notable effect on the total test execution times.

Signed-off-by: Oliver Heger <[email protected]>
The filter's `isWildcard` flag was always set to `true` for
superusers. This prevented the `containedIn` filter to be applied
correctly. Fix this by taking the presence of a `containedIn` filter into
account.

Signed-off-by: Oliver Heger <[email protected]>
In the endpoint to fetch the products of an organization, apply a
`HierarchyFilter`. Extend `OrganizationService` accordingly. This makes
sure that only products are listed that are visible to the user. If a
user has only been granted access to specific repositories, he or she
should only see the products these repositories belong to, even if there
is an implicit READ right on the organization.

Signed-off-by: Oliver Heger <[email protected]>
In the endpoint to fetch the repositories of a product, apply a
`HierarchyFilter`. Extend `ProductService` accordingly. This makes
sure that only repositories are listed that are visible to the user. By
having access to some repositories, the user gets implicit READ
permission on the owning products. However, in these products, not
automatically all repositories are visible.

Signed-off-by: Oliver Heger <[email protected]>
@mnonnenmacher mnonnenmacher force-pushed the oheger-bosch/db_authorization_integration branch 2 times, most recently from 6a53bfb to 3da4140 Compare December 1, 2025 17:31
oheger-bosch and others added 13 commits December 2, 2025 06:57
Create a new `RolesToDbMigration` class offering a function that can
create structures in the database that correspond to the roles and
permissions currently kept in Keycloak. The function is going to be
called on startup of ORT Server to perform a one-time migration.

Signed-off-by: Oliver Heger <[email protected]>
Instead of doing a synchronization of roles in Keycloak, trigger a
migration to new access rights structures on startup of the core
component.

Note that after starting ORT Server with this change, the new data
structures are considered the source of truth for authorization
information. To do a rollback, the `role_assignments` table should be
manually cleaned.

Signed-off-by: Oliver Heger <[email protected]>
This endpoint is going to be used by the UI to adapt itself according to
the status of the current user.

Make a function of AbstractAuthorizationTest public which can be reused
here to access the authorization infrastructure.

Signed-off-by: Oliver Heger <[email protected]>
The existing interface supported only checks whether a specific
permission is granted or not. For some use cases, however, the full set
of available permissions needs to be known. Therefore, extend the
interface to provide this information. Implement the methods that check
permissions as standard functions in the interface that delegate to the
new functions.

Signed-off-by: Oliver Heger <[email protected]>
This endpoint returns information about the current user and their
permissions on a specific hierarchy element. It is going to be consumed
by the UI which can use this information to adapt itself according to the
user's rights.

Signed-off-by: Oliver Heger <[email protected]>
For authorized routes, the `generateOpenApiSpec` task generated wrong
URLs, since the route selector was included. Rework the way the routes
are added to the routing graph to fix this problem.

Signed-off-by: Oliver Heger <[email protected]>
It is not guaranteed that the `name` claim in the access token is
present. Since this information is not relevant for the business logic,
make the `OrtServerPrincipal.fullName` property nullable and replace it
with an empty string when it should be displayed.

Signed-off-by: Oliver Heger <[email protected]>
Make sure that always all levels are processed when applying a
`HierarchyFilter`; otherwise, the generated filter condition may be
incomplete.

Signed-off-by: Oliver Heger <[email protected]>
Add a new `permissionGrantedOnLevel()` function that returns the level on
which permissions are granted. This can be used to distinguish between
rights that have been explicitly granted and rights that are inherited
through the hierarchy. This distinction is necessary for some use cases,
for instance, when displaying the users having access to a specific
element.

Signed-off-by: Oliver Heger <[email protected]>
Introduce a new `RoleInfo` class that stores additional information on
which hierarchy level a role has been assigned to a user. Change
`AuthorizationService.listUsers()` to return such `RoleInfo` objects.
This additional information can be used by the UI to distinguish between
users who have been explicitly assigned to a hierarchy element and those
with roles inherited from other elements.

For now, the REST API only uses this new functionality to filter out
users with inherited roles. This can be extended later to support more
advanced views about users in the UI.

Signed-off-by: Oliver Heger <[email protected]>
Include the `ort-server-client` client scope in the `ort-server-ui` and
`ort-server-ui-dev` clients by default. This ensures that the
`ort-server` client is included in the audience claim even when the user
has no roles from that client assigned. Otherwise, the backend will not
accept the JWT from such users.

Signed-off-by: Martin Nonnenmacher <[email protected]>
Use the previously introduced endpoint to get information about the
user's superuser status and permissions. The superuser status is made
available globally via the `useUser` hook while hierarchy related
permissions are made available via the router context.

As before, if the user has the superuser status, checking the specific
permissions is skipped. Also, the results of all queries are cached for
60s to reduce the amount of backend calls. This means that it can take
up to 60s for some permission changes to be reflected by the UI.

Signed-off-by: Martin Nonnenmacher <[email protected]>
@oheger-bosch oheger-bosch force-pushed the oheger-bosch/db_authorization_integration branch from 3da4140 to 1943894 Compare December 2, 2025 06:07
Copy link
Contributor

@Etsija Etsija left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI code LGTM, but I'd like @lamppu to review as well.

Question to PR authors: I assume you have tested that the UI works? Specifically:

  • that trying to access non-authed routes manually (by typing a forbidden route to the URL) leads to proper error
  • that it is possible to CRUD secrets
  • that it is possible to CRUD infrastructure services
  • that when creating infrastructure services, the username and password secret dropdowns are properly populated by existing secrets inherited from all hierarchy levels
  • that in the ORT run creation form, Analyzer / environment secret dropdown is populated likewise
  • and in the same form, infra services dropdown for Analyzer / environment config is populated likewise

@oheger-bosch
Copy link
Contributor Author

UI code LGTM, but I'd like @lamppu to review as well.

Question to PR authors: I assume you have tested that the UI works? Specifically:

* that trying to access non-authed routes manually (by typing a forbidden route to the URL) leads to proper error

* that it is possible to CRUD secrets

* that it is possible to CRUD infrastructure services

* that when creating infrastructure services, the username and password secret dropdowns are properly populated by existing secrets inherited from all hierarchy levels

* that in the ORT run creation form, Analyzer / environment secret dropdown is populated likewise

* and in the same form, infra services dropdown for Analyzer / environment config is populated likewise

@mnonnenmacher, can you comment what you have tested in the UI? I went through some use cases with a restricted user.

Since there are comprehensive integration tests for the API endpoints and the roles/permissions they require, I would expect that there are no behavior changes in this regard.

Copy link
Contributor

@lamppu lamppu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI code LGTM, but I'd like @lamppu to review as well.

LGTM too for the most part, the only small thing is that the hasRole function could be completely removed from the useUser hook.

@oheger-bosch oheger-bosch added this pull request to the merge queue Dec 2, 2025
Merged via the queue into eclipse-apoapsis:main with commit b0701a4 Dec 2, 2025
33 checks passed
@oheger-bosch oheger-bosch deleted the oheger-bosch/db_authorization_integration branch December 2, 2025 10:57
@mnonnenmacher
Copy link
Contributor

UI code LGTM, but I'd like @lamppu to review as well.

LGTM too for the most part, the only small thing is that the hasRole function could be completely removed from the useUser hook.

I will do that as a follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants