Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow adding a mapping between OIDC user role (from ID token) and application user role #37989

Closed
jycr opened this issue Jan 2, 2024 · 27 comments
Labels
area/oidc kind/enhancement New feature or request

Comments

@jycr
Copy link
Contributor

jycr commented Jan 2, 2024

Description

Thanks to the Quarkus OIDC extension, I was able to interconnect my application with an identity provider (based on Keycloak).
I was able to retrieve my user's profiles provided by the identity provider via the idToken (in a specific user_profile claim)

Here is the subset of an example ID token received:

{
	"name": "john.doe@acme.com",
	"issuer": "https://idp.acme.com/auth/realms/ACME",
	"subject": "f:c9507fd1-12f3-487f-8f23-77eb06728749:john.doe@acme.com",
	"tokenId": "d0e14a43-8545-4b67-bb9d-7704c01901dc",
	"claims": {
		"sub": "f:c9507fd1-12f3-487f-8f23-77eb06728749:john.doe@acme.com",
		"typ": "ID",
		"user_profile": [
			"AP_DEV_UI_USER",
			"AP_DEV_UI_ADMIN",
			"AP_PROD_UI_USER"
		]
	}
}

And here a subset of my configuration:

quarkus.oidc.roles.source=idtoken
quarkus.oidc.roles.role-claim-path=user_profile

The list of profiles provided by the IdP are relatively "obscure".

I need to map profile to role according different environment

Environment Identity provider profile Application role
Development AP_DEV_UI_USER user
Development AP_DEV_UI_ADMIN admin
Production AP_PROD_UI_USER user
Production AP_PROD_UI_ADMIN admin

Is it possible to add functionality to “map” a profile to a role?

Thus, I will be able to protect my resources simply via the corresponding annotations such as for example:

@GET
@RolesAllowed("admin")
@Path("/restricted-area")
public String restrictedArea() {
    return "Restricted access granted";
}

Implementation ideas

We can execute the mapping in findClaimWithRoles method of OidcUtils.java#L203-L218 from configuration provided in OidcTenantConfig.Roles rolesConfig parameter:

    private static List<String> findClaimWithRoles(OidcTenantConfig.Roles rolesConfig, String claimPath,
            JsonObject json) {
        Object claimValue = findClaimValue(claimPath, json, splitClaimPath(claimPath), 0);
        final List<String> roles;
        if (claimValue instanceof JsonArray) {
            roles = convertJsonArrayToList((JsonArray) claimValue);
        } else if (claimValue != null) {
            String sep = rolesConfig.getRoleClaimSeparator().isPresent() ? rolesConfig.getRoleClaimSeparator().get() : " ";
            if (claimValue.toString().isBlank()) {
                roles = Collections.emptyList();
            } else {
                roles = Arrays.asList(claimValue.toString().split(sep));
            }
        } else {
            roles = Collections.emptyList();
        }

        if (!rolesConfig.getRoleMapping().isEmpty()) {
            final Map<String, String> mapping = rolesConfig.getRoleMapping();
            return roles.stream()
                    .map(mapping::get)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        } else {
            return roles;
        }
    }

Property to add in OidcTenantConfig.Roles config:

        /**
         * Mapping between OIDC user role and application user role
         */
        @ConfigDocMapKey("oidc-role-name")
        public Map<String, String> roleMapping = new HashMap<>();

        public Map<String, String> getRoleMapping() {
            return roleMapping;
        }

Here is an example of configuration to correctly respond to my use case described above:

quarkus.oidc.roles.source=idtoken
quarkus.oidc.roles.role-claim-path=user_profile
%dev.quarkus.oidc.roles.role-mapping.AP_DEV_UI_USER=user
%dev.quarkus.oidc.roles.role-mapping.AP_DEV_UI_ADMIN=admin
quarkus.oidc.roles.role-mapping.AP_PROD_UI_USER=user
quarkus.oidc.roles.role-mapping.AP_PROD_UI_ADMIN=admin
@jycr jycr added the kind/enhancement New feature or request label Jan 2, 2024
@quarkus-bot quarkus-bot bot added the area/oidc label Jan 2, 2024
@quarkus-bot
Copy link

quarkus-bot bot commented Jan 2, 2024

/cc @pedroigor (oidc), @sberyozkin (oidc)

@sberyozkin
Copy link
Member

Thanks @jycr for opening this enhancement request, it makes sense.

FYI, we already have a generic support for this kind of mapping, which is meant to work with all authentication mechanisms, with @michalvavrik completing #37275, will be available in 3.7.0.CR1, for example:
https://quarkus.io/version/main/guides/security-authorize-web-endpoints-reference#map-security-identity-roles

Since you'd like to use @RolesAllowed then having only something like

quarkus.http.auth.policy.identity.roles.AP_DEV_UI_USER=user
quarkus.http.auth.policy.identity.roles.AP_DEV_UI_ADMIN=admin

should do (CC @michalvavrik just in case)

Can you please try a snapshot and see if it works for you ?

@jycr
Copy link
Contributor Author

jycr commented Jan 3, 2024

Can you please try a snapshot and see if it works for you ?

Is there any Public Maven Repository can I use to test a Quarkus SNAPSHOT?

@sberyozkin
Copy link
Member

@jycr See https://github.com/quarkusio/quarkus/blob/main/CONTRIBUTING.md#using-snapshots

@jycr
Copy link
Contributor Author

jycr commented Jan 3, 2024

Just tested with:

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>io.quarkus.platform</groupId>
				<artifactId>quarkus-bom</artifactId>
				<version>999-SNAPSHOT</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

io.quarkus.platform:quarkus-bom:999-SNAPSHOT from https://s01.oss.sonatype.org/content/repositories/snapshots/io/quarkus/platform/quarkus-bom/999-SNAPSHOT/quarkus-bom-999-20240103.021525-126.pom

My project dependency tree
com.acme:poc:jar:1.0.0-SNAPSHOT
+- io.quarkus:quarkus-resteasy-reactive:jar:3.6.0:compile
|  +- io.quarkus:quarkus-resteasy-reactive-common:jar:3.6.0:compile
|  |  +- io.quarkus.resteasy.reactive:resteasy-reactive-common:jar:3.6.0:compile
|  |  |  +- io.quarkus.resteasy.reactive:resteasy-reactive-common-types:jar:3.6.0:compile
|  |  |  +- org.reactivestreams:reactive-streams:jar:1.0.4:compile
|  |  |  \- io.smallrye.reactive:mutiny-zero-flow-adapters:jar:1.0.0:compile
|  |  \- io.quarkus:quarkus-mutiny:jar:3.6.0:compile
|  |     +- io.quarkus:quarkus-smallrye-context-propagation:jar:3.6.0:compile
|  |     |  \- io.smallrye:smallrye-context-propagation:jar:2.1.0:compile
|  |     |     +- io.smallrye:smallrye-context-propagation-api:jar:2.1.0:compile
|  |     |     \- io.smallrye:smallrye-context-propagation-storage:jar:2.1.0:compile
|  |     \- io.smallrye.reactive:mutiny-smallrye-context-propagation:jar:2.5.1:compile
|  +- io.quarkus.resteasy.reactive:resteasy-reactive-vertx:jar:3.6.0:compile
|  |  +- io.vertx:vertx-web:jar:4.4.6:compile
|  |  |  +- io.vertx:vertx-web-common:jar:4.4.6:compile
|  |  |  +- io.vertx:vertx-auth-common:jar:4.4.6:compile
|  |  |  \- io.vertx:vertx-bridge-common:jar:4.4.6:compile
|  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-core:jar:3.7.2:compile
|  |  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-runtime:jar:3.7.2:compile
|  |  |  \- io.smallrye.reactive:vertx-mutiny-generator:jar:3.7.2:compile
|  |  |     \- io.vertx:vertx-codegen:jar:4.4.6:compile
|  |  +- io.quarkus.resteasy.reactive:resteasy-reactive:jar:3.6.0:compile
|  |  +- jakarta.enterprise:jakarta.enterprise.cdi-api:jar:4.0.1:compile
|  |  |  +- jakarta.enterprise:jakarta.enterprise.lang-model:jar:4.0.1:compile
|  |  |  \- jakarta.el:jakarta.el-api:jar:5.0.1:compile
|  |  +- jakarta.ws.rs:jakarta.ws.rs-api:jar:3.1.0:compile
|  |  +- org.jboss.logging:commons-logging-jboss-logging:jar:1.0.0.Final:compile
|  |  +- jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.1:compile
|  |  |  \- jakarta.activation:jakarta.activation-api:jar:2.1.2:compile
|  |  \- org.jboss.logging:jboss-logging:jar:3.5.3.Final:compile
|  +- io.quarkus:quarkus-vertx-http:jar:3.6.0:compile
|  |  +- io.quarkus:quarkus-security-runtime-spi:jar:3.6.0:compile
|  |  +- io.quarkus:quarkus-credentials:jar:3.6.0:compile
|  |  +- io.smallrye.common:smallrye-common-vertx-context:jar:2.1.2:compile
|  |  |  \- io.smallrye.common:smallrye-common-constraint:jar:2.1.2:compile
|  |  +- io.quarkus.security:quarkus-security:jar:2.0.2.Final:compile
|  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-web:jar:3.7.2:compile
|  |  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-web-common:jar:3.7.2:compile
|  |  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-auth-common:jar:3.7.2:compile
|  |  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-bridge-common:jar:3.7.2:compile
|  |  |  \- io.smallrye.reactive:smallrye-mutiny-vertx-uri-template:jar:3.7.2:compile
|  |  |     \- io.vertx:vertx-uri-template:jar:4.4.6:compile
|  |  \- io.github.crac:org-crac:jar:0.1.3:compile
|  +- io.quarkus:quarkus-jsonp:jar:3.6.0:compile
|  |  \- org.eclipse.parsson:parsson:jar:1.1.5:compile
|  |     \- jakarta.json:jakarta.json-api:jar:2.1.3:compile
|  \- io.quarkus:quarkus-virtual-threads:jar:3.6.0:compile
|     \- io.vertx:vertx-core:jar:4.4.6:compile
|        +- io.netty:netty-common:jar:4.1.100.Final:compile
|        +- io.netty:netty-buffer:jar:4.1.100.Final:compile
|        +- io.netty:netty-transport:jar:4.1.100.Final:compile
|        +- io.netty:netty-handler:jar:4.1.100.Final:compile
|        |  \- io.netty:netty-transport-native-unix-common:jar:4.1.100.Final:compile
|        +- io.netty:netty-handler-proxy:jar:4.1.100.Final:compile
|        |  \- io.netty:netty-codec-socks:jar:4.1.100.Final:compile
|        +- io.netty:netty-codec-http:jar:4.1.100.Final:compile
|        +- io.netty:netty-codec-http2:jar:4.1.100.Final:compile
|        +- io.netty:netty-resolver:jar:4.1.100.Final:compile
|        \- io.netty:netty-resolver-dns:jar:4.1.100.Final:compile
|           \- io.netty:netty-codec-dns:jar:4.1.100.Final:compile
+- io.quarkus:quarkus-oidc:jar:3.6.0:compile
|  +- io.quarkus:quarkus-core:jar:3.6.0:compile
|  |  +- jakarta.inject:jakarta.inject-api:jar:2.0.1:compile
|  |  +- io.smallrye.common:smallrye-common-os:jar:2.1.2:compile
|  |  +- io.quarkus:quarkus-ide-launcher:jar:3.6.0:compile
|  |  +- io.quarkus:quarkus-development-mode-spi:jar:3.6.0:compile
|  |  +- io.smallrye.config:smallrye-config:jar:3.4.4:compile
|  |  |  \- io.smallrye.config:smallrye-config-core:jar:3.4.4:compile
|  |  |     +- io.smallrye.common:smallrye-common-classloader:jar:2.1.2:compile
|  |  |     \- io.smallrye.config:smallrye-config-common:jar:3.4.4:compile
|  |  +- org.jboss.logmanager:jboss-logmanager:jar:3.0.2.Final:compile
|  |  |  +- io.smallrye.common:smallrye-common-cpu:jar:2.1.2:compile
|  |  |  +- io.smallrye.common:smallrye-common-expression:jar:2.1.2:compile
|  |  |  |  \- io.smallrye.common:smallrye-common-function:jar:2.1.2:compile
|  |  |  +- io.smallrye.common:smallrye-common-net:jar:2.1.2:compile
|  |  |  \- io.smallrye.common:smallrye-common-ref:jar:2.1.2:compile
|  |  +- org.jboss.logging:jboss-logging-annotations:jar:2.2.1.Final:compile
|  |  +- org.jboss.threads:jboss-threads:jar:3.5.1.Final:compile
|  |  +- org.slf4j:slf4j-api:jar:2.0.6:compile
|  |  +- org.jboss.slf4j:slf4j-jboss-logmanager:jar:2.0.0.Final:compile
|  |  +- org.wildfly.common:wildfly-common:jar:1.7.0.Final:compile
|  |  +- io.quarkus:quarkus-bootstrap-runner:jar:3.6.0:compile
|  |  \- io.quarkus:quarkus-fs-util:jar:0.0.9:compile
|  +- io.quarkus:quarkus-vertx:jar:3.6.0:compile
|  |  +- io.quarkus:quarkus-netty:jar:3.6.0:compile
|  |  |  +- io.netty:netty-codec:jar:4.1.100.Final:compile
|  |  |  \- com.aayushatharva.brotli4j:brotli4j:jar:1.12.0:compile
|  |  |     +- com.aayushatharva.brotli4j:service:jar:1.12.0:compile
|  |  |     \- com.aayushatharva.brotli4j:native-osx-aarch64:jar:1.12.0:compile
|  |  +- io.netty:netty-codec-haproxy:jar:4.1.100.Final:compile
|  |  +- io.smallrye.common:smallrye-common-annotation:jar:2.1.2:compile
|  |  +- io.quarkus:quarkus-vertx-latebound-mdc-provider:jar:3.6.0:compile
|  |  \- io.smallrye:smallrye-fault-tolerance-vertx:jar:6.2.6:compile
|  +- io.quarkus:quarkus-security:jar:3.6.0:compile
|  |  \- jakarta.interceptor:jakarta.interceptor-api:jar:2.1.0:compile
|  +- io.quarkus:quarkus-oidc-common:jar:3.6.0:compile
|  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-web-client:jar:3.7.2:compile
|  |  |  \- io.vertx:vertx-web-client:jar:4.4.6:compile
|  |  \- io.quarkus:quarkus-smallrye-jwt-build:jar:3.6.0:compile
|  |     \- io.smallrye:smallrye-jwt-build:jar:4.4.0:compile
|  +- io.smallrye:smallrye-jwt:jar:4.4.0:compile
|  |  +- org.eclipse.microprofile.config:microprofile-config-api:jar:3.0.3:compile
|  |  +- org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:jar:2.1:compile
|  |  +- org.bitbucket.b_c:jose4j:jar:0.9.3:compile
|  |  \- io.smallrye:smallrye-jwt-common:jar:4.4.0:compile
|  \- jakarta.annotation:jakarta.annotation-api:jar:2.1.1:compile
+- io.quarkus:quarkus-arc:jar:3.6.0:compile
|  +- io.quarkus.arc:arc:jar:3.6.0:compile
|  |  +- jakarta.transaction:jakarta.transaction-api:jar:2.0.1:compile
|  |  \- io.smallrye.reactive:mutiny:jar:2.5.1:compile
|  \- org.eclipse.microprofile.context-propagation:microprofile-context-propagation-api:jar:1.3:compile
+- io.quarkus:quarkus-smallrye-openapi:jar:3.6.0:compile
|  +- io.smallrye:smallrye-open-api-core:jar:3.7.0:compile
|  |  +- org.eclipse.microprofile.openapi:microprofile-openapi-api:jar:3.1.1:compile
|  |  +- com.fasterxml.jackson.core:jackson-core:jar:2.15.3:compile
|  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.15.3:compile
|  |  |  \- com.fasterxml.jackson.core:jackson-annotations:jar:2.15.3:compile
|  |  +- com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.15.3:compile
|  |  |  \- org.yaml:snakeyaml:jar:2.2:compile
|  |  \- io.smallrye:jandex:jar:3.1.5:compile
|  \- io.quarkus:quarkus-swagger-ui:jar:3.6.0:compile
+- io.quarkus:quarkus-resteasy-reactive-jackson:jar:3.6.0:compile
|  \- io.quarkus:quarkus-resteasy-reactive-jackson-common:jar:3.6.0:compile
|     +- io.quarkus.resteasy.reactive:resteasy-reactive-jackson:jar:3.6.0:compile
|     \- io.quarkus:quarkus-jackson:jar:3.6.0:compile
|        +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.15.3:compile
|        +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.15.3:compile
|        \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.15.3:compile
\- io.quarkus:quarkus-junit5:jar:3.6.0:test
   +- io.quarkus:quarkus-bootstrap-core:jar:3.6.0:test
   |  +- io.quarkus:quarkus-bootstrap-app-model:jar:3.6.0:test
   |  \- io.smallrye.common:smallrye-common-io:jar:2.1.2:compile
   +- org.eclipse.sisu:org.eclipse.sisu.inject:jar:0.3.5:test
   +- io.quarkus:quarkus-test-common:jar:3.6.0:test
   |  +- io.quarkus:quarkus-core-deployment:jar:3.6.0:test
   |  |  +- org.aesh:readline:jar:2.4:test
   |  |  |  \- org.fusesource.jansi:jansi:jar:2.4.0:test
   |  |  +- org.aesh:aesh:jar:2.7:test
   |  |  +- org.apache.commons:commons-lang3:jar:3.13.0:test
   |  |  +- io.quarkus.gizmo:gizmo:jar:1.7.0:test
   |  |  |  \- org.ow2.asm:asm-util:jar:9.6:test
   |  |  |     \- org.ow2.asm:asm-analysis:jar:9.6:test
   |  |  +- org.ow2.asm:asm:jar:9.6:test
   |  |  +- org.ow2.asm:asm-commons:jar:9.6:test
   |  |  |  \- org.ow2.asm:asm-tree:jar:9.6:test
   |  |  +- io.quarkus:quarkus-class-change-agent:jar:3.6.0:test
   |  |  +- io.quarkus:quarkus-devtools-utilities:jar:3.6.0:test
   |  |  +- io.quarkus:quarkus-builder:jar:3.6.0:test
   |  |  +- org.graalvm.sdk:graal-sdk:jar:23.0.1:test
   |  |  \- org.junit.platform:junit-platform-launcher:jar:1.10.0:test
   |  +- io.quarkus:quarkus-bootstrap-maven-resolver:jar:3.6.0:test
   |  |  +- io.smallrye.beanbag:smallrye-beanbag-maven:jar:1.3.2:test
   |  |  |  +- io.smallrye.beanbag:smallrye-beanbag-sisu:jar:1.3.2:test
   |  |  |  |  \- io.smallrye.beanbag:smallrye-beanbag:jar:1.3.2:test
   |  |  |  +- commons-codec:commons-codec:jar:1.16.0:test
   |  |  |  +- javax.inject:javax.inject:jar:1:test
   |  |  |  +- org.apache.httpcomponents:httpclient:jar:4.5.14:test
   |  |  |  +- org.apache.httpcomponents:httpcore:jar:4.4.16:test
   |  |  |  +- org.apache.maven:maven-artifact:jar:3.9.5:test
   |  |  |  +- org.apache.maven:maven-builder-support:jar:3.9.5:test
   |  |  |  +- org.apache.maven:maven-model:jar:3.9.5:test
   |  |  |  +- org.apache.maven:maven-model-builder:jar:3.9.5:test
   |  |  |  +- org.apache.maven:maven-repository-metadata:jar:3.9.5:test
   |  |  |  +- org.apache.maven:maven-settings:jar:3.9.5:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-api:jar:1.9.13:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-impl:jar:1.9.13:test
   |  |  |  |  \- org.apache.maven.resolver:maven-resolver-named-locks:jar:1.9.13:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-spi:jar:1.9.13:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-util:jar:1.9.13:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-transport-http:jar:1.9.10:test
   |  |  |  +- org.apache.maven.wagon:wagon-provider-api:jar:3.5.3:test
   |  |  |  +- org.apache.maven.wagon:wagon-http-shared:jar:3.5.3:test
   |  |  |  +- org.codehaus.plexus:plexus-interpolation:jar:1.26:test
   |  |  |  +- org.codehaus.plexus:plexus-utils:jar:3.5.1:test
   |  |  |  +- org.codehaus.plexus:plexus-xml:jar:4.0.0:test
   |  |  |  |  \- org.apache.maven:maven-xml-impl:jar:4.0.0-alpha-5:test
   |  |  |  |     \- org.apache.maven:maven-api-xml:jar:4.0.0-alpha-5:test
   |  |  |  |        \- org.apache.maven:maven-api-meta:jar:4.0.0-alpha-5:test
   |  |  |  +- org.codehaus.plexus:plexus-cipher:jar:2.0:test
   |  |  |  \- org.codehaus.plexus:plexus-sec-dispatcher:jar:2.0:test
   |  |  +- org.apache.maven:maven-embedder:jar:3.9.5:test
   |  |  |  +- org.apache.maven:maven-core:jar:3.9.5:test
   |  |  |  |  \- org.codehaus.plexus:plexus-component-annotations:jar:2.1.0:test
   |  |  |  +- org.apache.maven:maven-plugin-api:jar:3.9.5:test
   |  |  |  +- org.apache.maven.shared:maven-shared-utils:jar:3.3.4:test
   |  |  |  +- com.google.inject:guice:jar:5.1.0:test
   |  |  |  |  \- aopalliance:aopalliance:jar:1.0:test
   |  |  |  +- com.google.guava:guava:jar:32.1.3-jre:test
   |  |  |  +- com.google.guava:failureaccess:jar:1.0.1:test
   |  |  |  +- javax.annotation:javax.annotation-api:jar:1.3.2:test
   |  |  |  +- org.codehaus.plexus:plexus-classworlds:jar:2.6.0:test
   |  |  |  \- commons-cli:commons-cli:jar:1.5.0:test
   |  |  +- org.eclipse.sisu:org.eclipse.sisu.plexus:jar:0.3.5:test
   |  |  +- org.apache.maven:maven-settings-builder:jar:3.9.5:test
   |  |  +- org.apache.maven:maven-resolver-provider:jar:3.9.5:test
   |  |  +- org.apache.maven.resolver:maven-resolver-connector-basic:jar:1.9.13:test
   |  |  +- org.apache.maven.resolver:maven-resolver-transport-wagon:jar:1.9.13:test
   |  |  +- org.apache.maven.wagon:wagon-http:jar:3.5.3:test
   |  |  \- org.apache.maven.wagon:wagon-file:jar:3.5.3:test
   |  +- io.quarkus:quarkus-bootstrap-gradle-resolver:jar:3.6.0:test
   |  \- commons-io:commons-io:jar:2.15.0:test
   +- io.quarkus:quarkus-junit5-properties:jar:3.6.0:test
   +- org.junit.jupiter:junit-jupiter:jar:5.10.0:test
   |  +- org.junit.jupiter:junit-jupiter-api:jar:5.10.0:test
   |  |  +- org.opentest4j:opentest4j:jar:1.3.0:test
   |  |  +- org.junit.platform:junit-platform-commons:jar:1.10.0:test
   |  |  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
   |  +- org.junit.jupiter:junit-jupiter-params:jar:5.10.0:test
   |  \- org.junit.jupiter:junit-jupiter-engine:jar:5.10.0:test
   |     \- org.junit.platform:junit-platform-engine:jar:1.10.0:test
   \- com.thoughtworks.xstream:xstream:jar:1.4.20:test
      \- io.github.x-stream:mxparser:jar:1.2.2:test
         \- xmlpull:xmlpull:jar:1.1.3.1:test

-> It doesn't seem to work :'(

but isn't it strange that the Quarkus BOM 999-SNAPSHOT references 3.6.0 as the version for Quarkus modules and not version 999-SNAPSHOT?

@sberyozkin
Copy link
Member

@jycr
Copy link
Contributor Author

jycr commented Jan 3, 2024

@jycr I believe you should use quarkus-bom directly, for example: https://s01.oss.sonatype.org/content/repositories/snapshots/io/quarkus/quarkus-bom/999-SNAPSHOT/quarkus-bom-999-20240103.021116-987.pom

@sberyozkin : Just tested with:

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>io.quarkus</groupId>
				<artifactId>quarkus-bom</artifactId>
				<version>999-SNAPSHOT</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

NB: io.quarkus:quarkus-bom instead of io.quarkus.platform:quarkus-bom

According article Platforms and Streams: a new way to discover Quarkus extensions:

io.quarkus.platform:quarkus-bom:2.1.0.Final - an equivalent of the io.quarkus:quarkus-bom:2.1.0.Final

My project dependency tree
gca.caps.team.dev.fun:fun-with-idp:jar:1.0.0-SNAPSHOT
+- io.quarkus:quarkus-resteasy-reactive:jar:999-SNAPSHOT:compile
|  +- io.quarkus:quarkus-resteasy-reactive-common:jar:999-SNAPSHOT:compile
|  |  +- io.quarkus.resteasy.reactive:resteasy-reactive-common:jar:999-SNAPSHOT:compile
|  |  |  +- io.quarkus.resteasy.reactive:resteasy-reactive-common-types:jar:999-SNAPSHOT:compile
|  |  |  +- org.reactivestreams:reactive-streams:jar:1.0.4:compile
|  |  |  \- io.smallrye.reactive:mutiny-zero-flow-adapters:jar:1.0.0:compile
|  |  \- io.quarkus:quarkus-mutiny:jar:999-SNAPSHOT:compile
|  |     +- io.quarkus:quarkus-smallrye-context-propagation:jar:999-SNAPSHOT:compile
|  |     |  \- io.smallrye:smallrye-context-propagation:jar:2.1.0:compile
|  |     |     +- io.smallrye:smallrye-context-propagation-api:jar:2.1.0:compile
|  |     |     \- io.smallrye:smallrye-context-propagation-storage:jar:2.1.0:compile
|  |     \- io.smallrye.reactive:mutiny-smallrye-context-propagation:jar:2.5.3:compile
|  +- io.quarkus.resteasy.reactive:resteasy-reactive-vertx:jar:999-SNAPSHOT:compile
|  |  +- io.vertx:vertx-web:jar:4.4.6:compile
|  |  |  +- io.vertx:vertx-web-common:jar:4.4.6:compile
|  |  |  +- io.vertx:vertx-auth-common:jar:4.4.6:compile
|  |  |  \- io.vertx:vertx-bridge-common:jar:4.4.6:compile
|  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-core:jar:3.7.2:compile
|  |  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-runtime:jar:3.7.2:compile
|  |  |  \- io.smallrye.reactive:vertx-mutiny-generator:jar:3.7.2:compile
|  |  |     \- io.vertx:vertx-codegen:jar:4.4.6:compile
|  |  +- io.quarkus.resteasy.reactive:resteasy-reactive:jar:999-SNAPSHOT:compile
|  |  +- jakarta.enterprise:jakarta.enterprise.cdi-api:jar:4.0.1:compile
|  |  |  +- jakarta.enterprise:jakarta.enterprise.lang-model:jar:4.0.1:compile
|  |  |  \- jakarta.el:jakarta.el-api:jar:5.0.1:compile
|  |  +- jakarta.ws.rs:jakarta.ws.rs-api:jar:3.1.0:compile
|  |  +- org.jboss.logging:commons-logging-jboss-logging:jar:1.0.0.Final:compile
|  |  +- jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.1:compile
|  |  |  \- jakarta.activation:jakarta.activation-api:jar:2.1.2:compile
|  |  \- org.jboss.logging:jboss-logging:jar:3.5.3.Final:compile
|  +- io.quarkus:quarkus-vertx-http:jar:999-SNAPSHOT:compile
|  |  +- io.quarkus:quarkus-security-runtime-spi:jar:999-SNAPSHOT:compile
|  |  +- io.quarkus:quarkus-credentials:jar:999-SNAPSHOT:compile
|  |  +- io.smallrye.common:smallrye-common-vertx-context:jar:2.1.2:compile
|  |  |  \- io.smallrye.common:smallrye-common-constraint:jar:2.1.2:compile
|  |  +- io.quarkus.security:quarkus-security:jar:2.0.2.Final:compile
|  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-web:jar:3.7.2:compile
|  |  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-web-common:jar:3.7.2:compile
|  |  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-auth-common:jar:3.7.2:compile
|  |  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-bridge-common:jar:3.7.2:compile
|  |  |  \- io.smallrye.reactive:smallrye-mutiny-vertx-uri-template:jar:3.7.2:compile
|  |  |     \- io.vertx:vertx-uri-template:jar:4.4.6:compile
|  |  \- io.github.crac:org-crac:jar:0.1.3:compile
|  +- io.quarkus:quarkus-jsonp:jar:999-SNAPSHOT:compile
|  |  \- org.eclipse.parsson:parsson:jar:1.1.5:compile
|  |     \- jakarta.json:jakarta.json-api:jar:2.1.3:compile
|  \- io.quarkus:quarkus-virtual-threads:jar:999-SNAPSHOT:compile
|     \- io.vertx:vertx-core:jar:4.4.6:compile
|        +- io.netty:netty-common:jar:4.1.100.Final:compile
|        +- io.netty:netty-buffer:jar:4.1.100.Final:compile
|        +- io.netty:netty-transport:jar:4.1.100.Final:compile
|        +- io.netty:netty-handler:jar:4.1.100.Final:compile
|        |  \- io.netty:netty-transport-native-unix-common:jar:4.1.100.Final:compile
|        +- io.netty:netty-handler-proxy:jar:4.1.100.Final:compile
|        |  \- io.netty:netty-codec-socks:jar:4.1.100.Final:compile
|        +- io.netty:netty-codec-http:jar:4.1.100.Final:compile
|        +- io.netty:netty-codec-http2:jar:4.1.100.Final:compile
|        +- io.netty:netty-resolver:jar:4.1.100.Final:compile
|        \- io.netty:netty-resolver-dns:jar:4.1.100.Final:compile
|           \- io.netty:netty-codec-dns:jar:4.1.100.Final:compile
+- io.quarkus:quarkus-oidc:jar:999-SNAPSHOT:compile
|  +- io.quarkus:quarkus-core:jar:999-SNAPSHOT:compile
|  |  +- jakarta.inject:jakarta.inject-api:jar:2.0.1:compile
|  |  +- io.smallrye.common:smallrye-common-os:jar:2.1.2:compile
|  |  +- io.quarkus:quarkus-ide-launcher:jar:999-SNAPSHOT:compile
|  |  +- io.quarkus:quarkus-development-mode-spi:jar:999-SNAPSHOT:compile
|  |  +- io.smallrye.config:smallrye-config:jar:3.4.4:compile
|  |  |  \- io.smallrye.config:smallrye-config-core:jar:3.4.4:compile
|  |  |     +- io.smallrye.common:smallrye-common-classloader:jar:2.1.2:compile
|  |  |     \- io.smallrye.config:smallrye-config-common:jar:3.4.4:compile
|  |  +- org.jboss.logmanager:jboss-logmanager:jar:3.0.4.Final:compile
|  |  |  +- io.smallrye.common:smallrye-common-cpu:jar:2.1.2:compile
|  |  |  +- io.smallrye.common:smallrye-common-expression:jar:2.1.2:compile
|  |  |  |  \- io.smallrye.common:smallrye-common-function:jar:2.1.2:compile
|  |  |  +- io.smallrye.common:smallrye-common-net:jar:2.1.2:compile
|  |  |  \- io.smallrye.common:smallrye-common-ref:jar:2.1.2:compile
|  |  +- org.jboss.logging:jboss-logging-annotations:jar:2.2.1.Final:compile
|  |  +- org.jboss.threads:jboss-threads:jar:3.5.1.Final:compile
|  |  +- org.slf4j:slf4j-api:jar:2.0.6:compile
|  |  +- org.jboss.slf4j:slf4j-jboss-logmanager:jar:2.0.0.Final:compile
|  |  +- org.wildfly.common:wildfly-common:jar:1.7.0.Final:compile
|  |  +- io.quarkus:quarkus-bootstrap-runner:jar:999-SNAPSHOT:compile
|  |  \- io.quarkus:quarkus-fs-util:jar:0.0.9:compile
|  +- io.quarkus:quarkus-vertx:jar:999-SNAPSHOT:compile
|  |  +- io.quarkus:quarkus-netty:jar:999-SNAPSHOT:compile
|  |  |  +- io.netty:netty-codec:jar:4.1.100.Final:compile
|  |  |  \- com.aayushatharva.brotli4j:brotli4j:jar:1.12.0:compile
|  |  |     +- com.aayushatharva.brotli4j:service:jar:1.12.0:compile
|  |  |     \- com.aayushatharva.brotli4j:native-osx-aarch64:jar:1.12.0:compile
|  |  +- io.netty:netty-codec-haproxy:jar:4.1.100.Final:compile
|  |  +- io.smallrye.common:smallrye-common-annotation:jar:2.1.2:compile
|  |  +- io.quarkus:quarkus-vertx-latebound-mdc-provider:jar:999-SNAPSHOT:compile
|  |  \- io.smallrye:smallrye-fault-tolerance-vertx:jar:6.2.6:compile
|  +- io.quarkus:quarkus-security:jar:999-SNAPSHOT:compile
|  |  \- jakarta.interceptor:jakarta.interceptor-api:jar:2.1.0:compile
|  +- io.quarkus:quarkus-oidc-common:jar:999-SNAPSHOT:compile
|  |  +- io.smallrye.reactive:smallrye-mutiny-vertx-web-client:jar:3.7.2:compile
|  |  |  \- io.vertx:vertx-web-client:jar:4.4.6:compile
|  |  \- io.quarkus:quarkus-smallrye-jwt-build:jar:999-SNAPSHOT:compile
|  |     \- io.smallrye:smallrye-jwt-build:jar:4.4.0:compile
|  +- io.smallrye:smallrye-jwt:jar:4.4.0:compile
|  |  +- org.eclipse.microprofile.config:microprofile-config-api:jar:3.0.3:compile
|  |  +- org.eclipse.microprofile.jwt:microprofile-jwt-auth-api:jar:2.1:compile
|  |  +- org.bitbucket.b_c:jose4j:jar:0.9.3:compile
|  |  \- io.smallrye:smallrye-jwt-common:jar:4.4.0:compile
|  \- jakarta.annotation:jakarta.annotation-api:jar:2.1.1:compile
+- io.quarkus:quarkus-arc:jar:999-SNAPSHOT:compile
|  +- io.quarkus.arc:arc:jar:999-SNAPSHOT:compile
|  |  +- jakarta.transaction:jakarta.transaction-api:jar:2.0.1:compile
|  |  \- io.smallrye.reactive:mutiny:jar:2.5.3:compile
|  \- org.eclipse.microprofile.context-propagation:microprofile-context-propagation-api:jar:1.3:compile
+- io.quarkus:quarkus-smallrye-openapi:jar:999-SNAPSHOT:compile
|  +- io.smallrye:smallrye-open-api-core:jar:3.7.0:compile
|  |  +- org.eclipse.microprofile.openapi:microprofile-openapi-api:jar:3.1.1:compile
|  |  +- com.fasterxml.jackson.core:jackson-core:jar:2.16.0:compile
|  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.16.0:compile
|  |  |  \- com.fasterxml.jackson.core:jackson-annotations:jar:2.16.0:compile
|  |  +- com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.16.0:compile
|  |  |  \- org.yaml:snakeyaml:jar:2.2:compile
|  |  \- io.smallrye:jandex:jar:3.1.6:compile
|  \- io.quarkus:quarkus-swagger-ui:jar:999-SNAPSHOT:compile
+- io.quarkus:quarkus-resteasy-reactive-jackson:jar:999-SNAPSHOT:compile
|  \- io.quarkus:quarkus-resteasy-reactive-jackson-common:jar:999-SNAPSHOT:compile
|     +- io.quarkus.resteasy.reactive:resteasy-reactive-jackson:jar:999-SNAPSHOT:compile
|     \- io.quarkus:quarkus-jackson:jar:999-SNAPSHOT:compile
|        +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.16.0:compile
|        +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.16.0:compile
|        \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.16.0:compile
\- io.quarkus:quarkus-junit5:jar:999-SNAPSHOT:test
   +- io.quarkus:quarkus-bootstrap-core:jar:999-SNAPSHOT:test
   |  +- io.quarkus:quarkus-bootstrap-app-model:jar:999-SNAPSHOT:test
   |  \- io.smallrye.common:smallrye-common-io:jar:2.1.2:compile
   +- org.eclipse.sisu:org.eclipse.sisu.inject:jar:0.9.0.M2:test
   +- io.quarkus:quarkus-test-common:jar:999-SNAPSHOT:test
   |  +- io.quarkus:quarkus-core-deployment:jar:999-SNAPSHOT:test
   |  |  +- org.aesh:readline:jar:2.4:test
   |  |  |  \- org.fusesource.jansi:jansi:jar:2.4.0:test
   |  |  +- org.aesh:aesh:jar:2.7:test
   |  |  +- org.apache.commons:commons-lang3:jar:3.14.0:test
   |  |  +- io.quarkus.gizmo:gizmo:jar:1.7.0:test
   |  |  |  \- org.ow2.asm:asm-util:jar:9.6:test
   |  |  |     \- org.ow2.asm:asm-analysis:jar:9.6:test
   |  |  +- org.ow2.asm:asm:jar:9.6:test
   |  |  +- org.ow2.asm:asm-commons:jar:9.6:test
   |  |  |  \- org.ow2.asm:asm-tree:jar:9.6:test
   |  |  +- io.quarkus:quarkus-class-change-agent:jar:999-SNAPSHOT:test
   |  |  +- io.quarkus:quarkus-devtools-utilities:jar:999-SNAPSHOT:test
   |  |  +- io.quarkus:quarkus-builder:jar:999-SNAPSHOT:test
   |  |  +- org.graalvm.sdk:graal-sdk:jar:23.0.1:test
   |  |  \- org.junit.platform:junit-platform-launcher:jar:1.10.1:test
   |  +- io.quarkus:quarkus-bootstrap-maven-resolver:jar:999-SNAPSHOT:test
   |  |  +- io.smallrye.beanbag:smallrye-beanbag-maven:jar:1.3.2:test
   |  |  |  +- io.smallrye.beanbag:smallrye-beanbag-sisu:jar:1.3.2:test
   |  |  |  |  \- io.smallrye.beanbag:smallrye-beanbag:jar:1.3.2:test
   |  |  |  +- commons-codec:commons-codec:jar:1.16.0:test
   |  |  |  +- javax.inject:javax.inject:jar:1:test
   |  |  |  +- org.apache.httpcomponents:httpclient:jar:4.5.14:test
   |  |  |  +- org.apache.httpcomponents:httpcore:jar:4.4.16:test
   |  |  |  +- org.apache.maven:maven-artifact:jar:3.9.6:test
   |  |  |  +- org.apache.maven:maven-builder-support:jar:3.9.6:test
   |  |  |  +- org.apache.maven:maven-model:jar:3.9.6:test
   |  |  |  +- org.apache.maven:maven-model-builder:jar:3.9.6:test
   |  |  |  +- org.apache.maven:maven-repository-metadata:jar:3.9.6:test
   |  |  |  +- org.apache.maven:maven-settings:jar:3.9.6:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-api:jar:1.9.18:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-impl:jar:1.9.18:test
   |  |  |  |  \- org.apache.maven.resolver:maven-resolver-named-locks:jar:1.9.18:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-spi:jar:1.9.18:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-util:jar:1.9.18:test
   |  |  |  +- org.apache.maven.resolver:maven-resolver-transport-http:jar:1.9.10:test
   |  |  |  +- org.apache.maven.wagon:wagon-provider-api:jar:3.5.3:test
   |  |  |  +- org.apache.maven.wagon:wagon-http-shared:jar:3.5.3:test
   |  |  |  +- org.codehaus.plexus:plexus-interpolation:jar:1.26:test
   |  |  |  +- org.codehaus.plexus:plexus-utils:jar:3.5.1:test
   |  |  |  +- org.codehaus.plexus:plexus-xml:jar:4.0.0:test
   |  |  |  |  \- org.apache.maven:maven-xml-impl:jar:4.0.0-alpha-5:test
   |  |  |  |     \- org.apache.maven:maven-api-xml:jar:4.0.0-alpha-5:test
   |  |  |  |        \- org.apache.maven:maven-api-meta:jar:4.0.0-alpha-5:test
   |  |  |  +- org.codehaus.plexus:plexus-cipher:jar:2.0:test
   |  |  |  \- org.codehaus.plexus:plexus-sec-dispatcher:jar:2.0:test
   |  |  +- org.apache.maven:maven-embedder:jar:3.9.6:test
   |  |  |  +- org.apache.maven:maven-core:jar:3.9.6:test
   |  |  |  |  \- org.codehaus.plexus:plexus-component-annotations:jar:2.1.0:test
   |  |  |  +- org.apache.maven:maven-plugin-api:jar:3.9.6:test
   |  |  |  +- org.apache.maven.shared:maven-shared-utils:jar:3.3.4:test
   |  |  |  +- com.google.inject:guice:jar:5.1.0:test
   |  |  |  |  \- aopalliance:aopalliance:jar:1.0:test
   |  |  |  +- com.google.guava:guava:jar:33.0.0-jre:test
   |  |  |  +- com.google.guava:failureaccess:jar:1.0.1:test
   |  |  |  +- javax.annotation:javax.annotation-api:jar:1.3.2:test
   |  |  |  +- org.codehaus.plexus:plexus-classworlds:jar:2.6.0:test
   |  |  |  \- commons-cli:commons-cli:jar:1.5.0:test
   |  |  +- org.eclipse.sisu:org.eclipse.sisu.plexus:jar:0.9.0.M2:test
   |  |  +- org.apache.maven:maven-settings-builder:jar:3.9.6:test
   |  |  +- org.apache.maven:maven-resolver-provider:jar:3.9.6:test
   |  |  +- org.apache.maven.resolver:maven-resolver-connector-basic:jar:1.9.18:test
   |  |  +- org.apache.maven.resolver:maven-resolver-transport-wagon:jar:1.9.18:test
   |  |  +- org.apache.maven.wagon:wagon-http:jar:3.5.3:test
   |  |  \- org.apache.maven.wagon:wagon-file:jar:3.5.3:test
   |  +- io.quarkus:quarkus-bootstrap-gradle-resolver:jar:999-SNAPSHOT:test
   |  \- commons-io:commons-io:jar:2.15.1:test
   +- io.quarkus:quarkus-junit5-properties:jar:999-SNAPSHOT:test
   +- org.junit.jupiter:junit-jupiter:jar:5.10.1:test
   |  +- org.junit.jupiter:junit-jupiter-api:jar:5.10.1:test
   |  |  +- org.opentest4j:opentest4j:jar:1.3.0:test
   |  |  +- org.junit.platform:junit-platform-commons:jar:1.10.1:test
   |  |  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
   |  +- org.junit.jupiter:junit-jupiter-params:jar:5.10.1:test
   |  \- org.junit.jupiter:junit-jupiter-engine:jar:5.10.1:test
   |     \- org.junit.platform:junit-platform-engine:jar:1.10.1:test
   \- com.thoughtworks.xstream:xstream:jar:1.4.20:test
      \- io.github.x-stream:mxparser:jar:1.2.2:test
         \- xmlpull:xmlpull:jar:1.1.3.1:test

NB: There are so many differences between:

-> But it doesn't seem to work :'(

@sberyozkin
Copy link
Member

sberyozkin commented Jan 3, 2024

@michalvavrik

Can you clarify please, can we have only

quarkus.http.auth.policy.identity.roles.AP_DEV_UI_USER=user
quarkus.http.auth.policy.identity.roles.AP_DEV_UI_ADMIN=admin

and then @RolesAllowed("user") without having to tie the identity roles to a path specific permission policy:

quarkus.http.auth.permission.roles1.paths=/*
quarkus.http.auth.permission.roles1.policy=identity

? It is not easy to spot from the PR. If it is not possible yet then it would be great if one can have @RolesAllowed("user") with only quarkus.http.auth.policy.identity.roles.AP_DEV_UI_USER=user, what do you think ?

@jycr FYI, you can implement this kind of mapping easily with a custom SecurityIdentityAugmentor right now, you can inject a custom mapping map property and augment the identity: https://quarkus.io/guides/security-customization#security-identity-customization

Hopefully we can make it simpler, but the augmentor approach is always available

@jycr
Copy link
Contributor Author

jycr commented Jan 3, 2024

I confirm that It works when I add following properties:

quarkus.http.auth.permission.roles1.paths=/*
quarkus.http.auth.permission.roles1.policy=identity

@michalvavrik
Copy link
Contributor

and then @RolesAllowed("user") without having to tie the identity roles to a path specific permission policy:

we are talking the path matching policy, hence if there is no path, there is no match

It is not easy to spot from the PR.

The fact that policy is only applied on matching path wasn't changed by my PR, it's here from the beginning. My PR just says: if policy is applied, identity augmentation will happen

If it is not possible yet then it would be great if one can have @RolesAllowed("user") with only quarkus.http.auth.policy.identity.roles.AP_DEV_UI_USER=user, what do you think ?

I think we can add add default permission for policies without matching HTTP permissions, but it smells like a lot of magic. I think we have already such complexity with shared policies and repeated wildcards, it's much better to understand for user what they are doing - if they want the policy to apply, they need to specify winning-the most specific path.

FYI, you can implement this kind of mapping easily with a custom SecurityIdentityAugmentor right now, you can inject a custom mapping map property and augment the identity: https://quarkus.io/guides/security-customization#security-identity-customization

IMHO it is unnecessary, documentation is quite clear: here https://quarkus.io/version/main/guides/security-authorize-web-endpoints-reference#map-security-identity-roles I copied and pasted

quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 
quarkus.http.auth.permission.roles1.paths=/*
quarkus.http.auth.permission.roles1.policy=admin-policy1

I confirm that It works when I add following properties:

+1

@michalvavrik
Copy link
Contributor

michalvavrik commented Jan 3, 2024

@sberyozkin maybe docs tweak is needed to make it clear for users that read docs and are not sure what is needed like you? I can't tell, lgtm sorry, let me know.

@sberyozkin
Copy link
Member

sberyozkin commented Jan 3, 2024

@michalvavrik Thanks, the problem is, that

quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 
quarkus.http.auth.permission.roles1.paths=/*
quarkus.http.auth.permission.roles1.policy=admin-policy1

duplicates the idea of @RolesAllowed("Admin1") as @RolesAllowed is an alternative to having to specify the path with quarkus.http.auth.permission.roles1.paths=/*.

IMHO the concept of the role mapping is independent to how these roles are enforced. But right now, after mapping quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 for @RolesAllowed("Admin1") on the path /a to work (which users already do with @Path("a")), users have to duplicate it with 2 extra lines of config:

quarkus.http.auth.permission.roles1.paths=/a
quarkus.http.auth.permission.roles1.policy=admin-policy1

I'm not sure now what you meant in the docs that it works for RolesAllowed given that HTTP permissions are applied in any case. In other words, using @RolesAllowed has no effect since the HTTP permission has been applied ? Sorry if I missing something.

In any case, I can certainly fix it at the OIDC level only with the roles.mapping property as proposed by @jycr, but I wonder if we can have @RolesAllowed checking only the mapping config like quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 - the problem is that this mapping is done within the individual policy. I think now this is a wrong place for the role mapping, as now it allows quarkus.http.auth.policy.admin-policy2.roles.admin=Admin2 and it does not make a lot of sense.

IMHO the mapping should be done at the higher level, for example at quarkus.http.auth, quarkus.http.auth.role-mapping.admin=Admin2, and then either HTTP polices or RolesAllowed would benefit without the latter having to depend on the former

@sberyozkin
Copy link
Member

sberyozkin commented Jan 3, 2024

@michalvavrik I'm sorry I did not provide all the above feedback during the earlier review of #37275, it was very thoroughly done as always, but IMHO it needs some follow up work. I'm more concerned now about the mapping being done at the policy level, and as such it has to be repeated many times, for every policy, while it really should be done only once. The problem is, I'm off from this Fri afternoon till 15th Jan, so even if you will have time and agree to do something else I won't have time to review, may be on 15th/16th Jan only, though Stuart, David may help. If you won't have time to update it then may be we should revert it as it is still only on main and target for 3.9

@michalvavrik
Copy link
Contributor

@michalvavrik I'm sorry I did not provide all the above feedback during the earlier review of #37275, it was very thoroughly done as always, but IMHO it needs some follow up work. I'm more concerned now about the mapping being done at the policy level, and as such it has to be repeated many times, for every policy, while it really should be done only once. The problem is, I'm off from this Fri afternoon till 15th Jan, so even if you will have time and agree to do something else I won't have time to review, may be on 15th/16th Jan only, though Stuart, David may help. If you won't have time to update it then may be we should revert it as it is still only on main and target for 3.9

@sberyozkin I'm out till 18.1., so I can't address it now anyway, I already spent more time responding to other issues (and tickets not related to Quarkus then I have). But I can tell you what you suggest now is in my eyes completely different and it is definitely not a bug, it's more like enhancement feature.

I also disagree, but please do what is necessary from your POV. np!

  • most importantly - it works the same way as the Securityidentity role to permission mapping does, so the behavior can't be surprising

duplicates the idea of @RolesAllowed("Admin1") as @RolesAllowed is an alternative to having to specify the path with quarkus.http.auth.permission.roles1.paths=/*.

The policy you mentioned enforces only authentication. Only authenticated requests can ever pass @RolesAllowed and only non-anonymous SecurityIdentity can have roles, therefore I can't see any duplication. Can you elaborate please?

IMHO the concept of the role mapping is independent to how these roles are enforced.

As said above, there is no role enforcement I'm aware of in your example configuration properties.

But right now, after mapping quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 for @RolesAllowed("Admin1") on the path /a to work (which users already do with @path("a")), users have to duplicate it with 2 extra lines of config:

I don't know what you suggest, if you map role x to y, than of course SecurityIdentity will have x. No change regarding policies can change it.

I'm not sure now what you meant in the docs that it works for RolesAllowed given that HTTP permissions are applied in any case

They are not, are you sure you are not confusing quarkus.http.auth.policy."role-policy".roles-allowed with quarkus.http.auth.policy."role-policy".roles ?

In any case, I can certainly fix it at the OIDC level only with the roles.mapping property as proposed by @jycr, but I wonder if we can have @RolesAllowed checking only the mapping config like quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1

I'm honestly sorry, but I don't see anything to fix.

the problem is that this mapping is done within the individual policy

Why is that problem? Are you sure you realized that only non-anonymous SecurityIdentity can have roles, therefore having policy that maps roles without having it authenticated won't work?

I think now this is a wrong place for the role mapping, as now it allows quarkus.http.auth.policy.admin-policy2.roles.admin=Admin2 and it does not make a lot of sense.

I complete disagree, but I recognize it is your responsibility as guardian of Quarkus Security, so please go ahead and revert the PR, I can't provide any alterations anytime soon. Absolutely fine, np!

IMHO the mapping should be done at the higher level, for example at quarkus.http.auth, quarkus.http.auth.role-mapping.admin=Admin2, and then either HTTP polices or RolesAllowed would benefit without the latter having to depend on the former

This suggestion will result in same behavior because if you want to map roles, you need authenticated policy. You can use shared policies to define this mapping for path /*. One difference I can see is that you will not require authentication, but in that case, the identity will never pass @RolesAllowed, so I don't think that is useful.

@jycr
Copy link
Contributor Author

jycr commented Jan 3, 2024

But I can tell you what you suggest now is in my eyes completely different and it is definitely not a bug, it's more like enhancement feature.

I confirm that this ticket is not a bug, but a suggestion for improvement.

The solutions described above (based on the SNAPSHOT version of Quarkus) seem to work for me.
A huge thank you to you for your help, your time, and your explanations.

I think we can close this issue?
(I will request the reopening of this ticket if the implementation poses a problem for us.)

@michalvavrik
Copy link
Contributor

thank you @jycr , let's wait for Sergey and close / not close the issue based on his answer, I'm not sure if this is not confusion between the 2 config properties I mentioned above, but if not and Sergey is convinced about his suggestion, we can enhance role mapping.

@sberyozkin
Copy link
Member

@jycr

The solutions described above (based on the SNAPSHOT version of Quarkus) seem to work for me.

Thanks, but your enhancement request gave an opportunity to myself and Michal to discuss the solution in place again. We are still in the SNAPSHOT in this regard so we have a chance to tune things. So lets keep this issue open for now.

@michalvavrik You made my day with referring to me as a Guardian of Quarkus Security :-), that would be Stuart I guess, I'd have an apprentice tag any time though :-). In any case, thanks, let me comment.

IMHO the fact that one can do

quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 
quarkus.http.auth.policy.admin-policy1.roles-allowed=admin,Admin1 
quarkus.http.auth.permission.roles1.paths=/a
quarkus.http.auth.permission.roles1.policy=admin-policy1

quarkus.http.auth.policy.admin-policy3.roles.admin=Admin2 
quarkus.http.auth.policy.admin-policy3.roles-allowed=admin,Admin2 
quarkus.http.auth.permission.roles3.paths=/c
quarkus.http.auth.permission.roles3.policy=admin-policy3

shows the mapping is done at a too low level. HTTP Security policy like .roles-allowed is about enforcing RBAC, not about coming up with varying mappings for the same input roles or repeating the same mapping. No matter how many polices exist there can be only 1 reasonable mapping from admin to a deployment specific role. Allowing admin->Admin1, admin->Admin2 does not change that both Admin1 and Admin2 are an admin and makes no practical sense IMHO.

The solution proposed by @jycr is correct because it is done at a higher level, as you can see - it is only a mapping, nothing else, and will work for @RolesAllowed and HTTP policy. The only problem is that it would become an OIDC specific solution. This is why IMHO we should push the mapping at a higher level in http.auth space too, such that it can be used by whatever role enforcing mechanism that users want to use.

it works the same way as the Securityidentity role to permission mapping does, so the behavior can't be surprising

It can be deprecated at some point and done similarly at the higher level too.

The policy you mentioned enforces only authentication....

I did not pay the attention, the 2 policies above are correct. Now that you mentioned it, I believe https://quarkus.io/version/main/guides/security-authorize-web-endpoints-reference#map-security-identity-roles needs an update - if this is an authentication policy, why do the mapping, who cares what roles are there ? Do you mean you can complement that with @RolesAllowed ? But we don't ask users who work with @RolesAllowed to complement it with HTTP authentication policies in general, right ?

This suggestion will result in same behavior because if you want to map roles, you need authenticated policy

No it won't. If we have it done outside of HTTP policy, for example, http.auth.roles-mapping.admin=Admin1 then either

quarkus.http.auth.policy.admin-policy1.roles-allowed=admin,Admin1 
quarkus.http.auth.permission.roles1.paths=/a
quarkus.http.auth.permission.roles1.policy=admin-policy1

works or

@RolesAllowed({"admin", "Admin1"}) @Path("/a")

works without having to do

quarkus.http.auth.permission.roles1.paths=/a
quarkus.http.auth.permission.roles1.policy=admin-policy1

IMHO we should revert it for now and tune a little bit

@sberyozkin
Copy link
Member

@stuartwdouglas Stuart, would you like to comment ?

@michalvavrik
Copy link
Contributor

IMHO we should revert it for now and tune a little bit

go ahead (no need to involve me, I'll know about the revert as you mentioned it here), I respect your opinions. Just note, you will need to revert shared policies PR as well, as the tests will fail and documentation will become incorrect, or you need to rewrite it.

I'll provide my view on your latest comments but I strongly disagree with your comments. IMHO you are incorrectly describing what is done by current implementation:

shows the mapping is done at a too low level. HTTP Security policy like .roles-allowed is about enforcing RBAC,

Default .roles-allowed value is ** which is authenticated policy. It does not require any role and it only makes sense to define roles mapping for authenticated user.

not about coming up with varying mappings for the same input roles or repeating the same mapping.

It was your suggestion to use policies for roles to permissions mapping, so I'm not sure why is it any worse for roles to roles.

No matter how many polices exist there can be only 1 reasonable mapping from admin to a deployment specific role.

That's just your opinion and I don't think you can support it with any data or references. If I want different roles mapping for different paths, why will you take this from me? It is possible now with path matching policies.

Allowing admin->Admin1, admin->Admin2 does not change that both Admin1 and Admin2 are an admin and makes no practical sense IMHO.

Your example

quarkus.http.auth.policy.admin-policy1.roles-allowed=admin,Admin1 
quarkus.http.auth.permission.roles1.paths=/a
quarkus.http.auth.permission.roles1.policy=admin-policy1

enforces admin or Admin1, therefore @RolesAllowed({"admin", "Admin1"}) @Path("/a") equivalent and such duplication does not make sense. Instead I think what you want to suggest is:

quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 
quarkus.http.auth.permission.roles1.paths=/a
quarkus.http.auth.permission.roles1.policy=admin-policy1

and annotations @RolesAllowed({"Admin1"}) @Path("/a"). What happens there? The HTTP Security policy roles1 requires authentication on path /a. And @RolesAllowed requires Admin1 role. Please note that @RolesAllowed can not ever be successful if user is not authenticated, therefore there is no duplication.

The solution proposed by @jycr is correct because it is done at a higher level, as you can see - it is only a mapping, nothing else, and will work for @RolesAllowed and HTTP policy.

mapping for non-authenticated request for anonymous Securityidentiy does not makes sense. And that is only difference to your proposal.

It can be deprecated at some point and done similarly at the higher level too.

Alright, acceptable, but I think might be interesting for you to know that what you suggest now is what I suggested for permissions and you preferred using policies. And I think you were right. Your new suggestion will give less flexibility and I can't see pros.

needs an update - if this is an authentication policy, why do the mapping, who cares what roles are there ?

Roles allowed policy is ** is de facto authentication. I care, it gives me capability to map role a to role b without requiring role a or role b.

Do you mean you can complement that with @RolesAllowed ? But we don't ask users who work with @RolesAllowed to complement it with HTTP authentication policies in general, right ?

I don't asks users to complement HTTP security policies and standard security annotations. I say: you can use authorization using configuration to map roles. There is no complementation, there is only logic: if you want to map roles, you need authenticated identity.

works without having to do

quarkus.http.auth.permission.roles1.paths=/a
quarkus.http.auth.permission.roles1.policy=admin-policy1

So your suggestion will take away from users option to apply mapping based on paths and for them it is "better" because they don't need to write 2 lines of code.

I think I understand you now, but please note that I don't agree, IMHO you should revert it in few days and think about it for a while.

@sberyozkin
Copy link
Member

@michalvavrik Can you please give me at least one good reason why, with the same application, we'd want to let users
provide an admin->Admin1 mapping on path /a and admin->Admin2 on path /b ? I'm all for making things more flexible but what this kind of flexibility can give users ?

@sberyozkin
Copy link
Member

sberyozkin commented Jan 3, 2024

@michalvavrik Please think about till 15th, it is not that bad as you are feeling right now, I'll ping Stuart directly and ask for his feedback, if you can convince yourself and agree that users won't lose anything with pushing the config a little bit up, then we can quickly revert both of these commits, IMHO, if you will have time to experiment, you will be able to prove things will become simpler. We don't have to rush, even though I appreciate users like @jycr already need it, the easy SecurityIdentityAugmentor workaround is always possible for now - I can type the custom augmentor here to help @jycr if the final solution will get postponed a bit.

@michalvavrik
Copy link
Contributor

@michalvavrik Can you please give me at least one good reason why, with the same application, we'd want to let users provide an admin->Admin1 mapping on path /a and admin->Admin2 on path /b ?

An application with 2 resources: AdministrationResource requires administrator, CustomerResource requires customer.

But I think what you meant to ask is that on pah /b you must not have role Admin1, right? I don't think that will happen if an application is designed and written from scratch, no legacy code.

@michalvavrik Please think about till 15th, it is not that bad as you are feeling right now, I'll ping Stuart directly and ask for his feedback

Your suggestion will work, please go ahead. I can't be available from now till 18th, so just go ahead please whenever it suits you. Maybe I won't be available after that due to personal reasons.

if you can convince yourself and agree that users won't lose anything with pushing the config a little bit up, then we can quickly revert both of these commits, IMHO, if you will have time to experiment, you will be able to prove things will become simpler.

Sure. I'd not do it for 2 lines, IMO if these 2 lines are so bad, we can just add default HTTP permission. But it's ok.

We don't have to rush, even though I appreciate users like @jycr already need it, the easy SecurityIdentityAugmentor workaround is always possible for now.

IMHO @jycr use case is solved by what is currently in place, no augmentor is needed. What you suggestion is simplification.

@sberyozkin
Copy link
Member

sberyozkin commented Jan 3, 2024

@michalvavrik

An application with 2 resources: AdministrationResource requires administrator, CustomerResource requires customer.
But I think what you meant to ask is that on pah /b you must not have role Admin1, right? I don't think that will happen if an application is designed and written from scratch, no legacy code.

No. You are concerned about losing the flexibility if the configuration is pushed higher and I'm trying to clarify that no flexibility will be lost at the practical level. The only flexibility that we can lose is mapping the same input role to different deployment specific roles which is possible now simply because each path specific policy can do its own mapping but I honestly can't see it being necessary - which is what I asked about - what can we lose if an input admin can be mapped only once to a deployment role like Admin - I don't see what. Your example will work fine with pushing the config up:

quarkus.http.auth.roles-mapping.admin=administrator
quarkus.http.auth.roles-mapping.user=customer

quarkus.http.auth.policy.policy1.roles-allowed=administrator
quarkus.http.auth.permission.roles1.paths=/admin
quarkus.http.auth.permission.roles1.policy=policy1

quarkus.http.auth.policy.policy2.roles-allowed=customer
quarkus.http.auth.permission.roles1.paths=/customer
quarkus.http.auth.permission.roles1.policy=policy2

What you suggestion is simplification.

Yeah, I agree with that - I suppose we all try to simplify the dev experience. Let me offer you an example and we can all think if it is an oversimplification or not, may be it is, and the whole revert process is not worth it.

So, here is a resource A and all I want is to enforce RBAC with @RolesAllowed, no mapping, the role is coming directly from the token:

@Path("/")
class A {
    @GET
    @RolesAllowed("admin")
    @Path("/admin")
    public String getAdmin() {}

    @GET
    @RolesAllowed("user")
    @Path("/user")
    public String getUser() {}
}

This is not an over complication, basic resource. Now, I need to map some token which has administrator and customer instead of the expected admin and user.

Current option:

quarkus.http.auth.policy.policy1.roles.administrator=admin
quarkus.http.auth.permission.roles1.paths=/admin
quarkus.http.auth.permission.roles1.policy=policy1

quarkus.http.auth.policy.policy2.roles.customer=user
quarkus.http.auth.permission.roles1.paths=/user
quarkus.http.auth.permission.roles1.policy=policy2

Proposed option:

quarkus.http.auth.roles-mapping.administrator=admin
quarkus.http.auth.roles-mapping.customer=user

With HTTP permissions the proposed solution would only be marginally simpler though.

Here you go. Maybe, now that I've typed it all, we can add, in 3.9.

quarkus.http.auth.roles-mapping.administrator=admin
quarkus.http.auth.roles-mapping.customer=user

in addition to what we already have, this mappings will add to whatever policy specific mappings exist ? I agree, that this optimization won't save much when only HTTP security policies are used. I'm not comfortable that for the mapping to work with @RolesAllowed, one need to create, in a very simple example above, 4 extra lines of properties.

So, what about this compromise, add quarkus.http.auth.roles-mapping in 3.9 ?

@sberyozkin
Copy link
Member

So, what about this compromise, add quarkus.http.auth.roles-mapping in 3.9 ?

We can discuss it later with Michal, Stuart if there will be an interest.

Thanks @jycr @michalvavrik

@sberyozkin
Copy link
Member

@michalvavrik I'll open an enhancement request and we can continue there in a few weeks, cheers

@michalvavrik
Copy link
Contributor

michalvavrik commented Jan 3, 2024

Your example and arguments are good @sberyozkin .

@sberyozkin
Copy link
Member

@michalvavrik Hope I haven't affected your quiet New Year break Michal :-), thanks for another invigorating debate :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/oidc kind/enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants