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 direct access to the keycloak PolicyEnforcer or the AuthzClient #16972

Closed
joggeli34 opened this issue May 4, 2021 · 9 comments · Fixed by #17013
Closed

Allow direct access to the keycloak PolicyEnforcer or the AuthzClient #16972

joggeli34 opened this issue May 4, 2021 · 9 comments · Fixed by #17013

Comments

@joggeli34
Copy link
Contributor

Description

When using quarkus-keycloak-authorization, it would be nice when the AuthzClient or the PolicyEnforcer could be injected, or at least be retreived from the KeycloakPolicyEnforcerAuthorizer.

This way, it's not necessary to create a second AuthzClient for configuring the authorization from within the application. Sometimes its also necessary to get the user entitlements for collection-rest-endpoints that deliver all items a user has access to.

Implementation ideas

First Idea: Create the PolicyEnforcer as a bean and also inject it in the KeycloakPolicyEnforcerAuthorizer.
Second Idea: Just keep the PolicyEnforcer as a field on the KeycloakPolicyEnforcerAuthorizer with a public getter.

Regards, Adrian

@quarkus-bot
Copy link

quarkus-bot bot commented May 4, 2021

/cc @pedroigor

@sberyozkin
Copy link
Member

sberyozkin commented May 5, 2021

@joggeli34 I'm not sure this enhancement is needed, can you please be more specific why would you like to have it ?
If PolicyEnforcer will also be invoked from the custom endpoint then it will be invoked twice, once by quarkus-keyalocak-authorization and once by the endpoint.
Besides with a possible upcoming tenant support it will turn into the issue of injecting the named dynamically resolved enforcers etc.
IMHO it is not worth it.

What I think we can do though is to have
org.keycloak.AuthorizationContext injected - in case the endpoint needs to do some extra checks.
Would that work for you ?
thanks

@sberyozkin
Copy link
Member

AuthzClient is not related to quarkus-keycloak-authorization, correct ?

@joggeli34
Copy link
Contributor Author

My use case is the following:

Assume we have several resources like /items/1, /items/2 and so on. I now like to implement the rest call for /items which returns only the items the user has permissions on.

My plan was to disable the authorization for the path /items and then get the permissions from the AuthzClient by my self.
For example like this:

@Path("/items")
public class ItemResource {
    @Inject
    AuthzClient authzClient;

    @Inject
    JsonWebToken accessToken;

    @Path("")
    @NoCache
    public String getItems() {

        // create an authorization request
        AuthorizationRequest request = new AuthorizationRequest();

        // send the entitlement request to the server in order to
        // obtain an RPT with all permissions granted to the user
        AuthorizationResponse response = authzClient.authorization(accessToken.getRawToken()).authorize(request);
        String rpt = response.getToken();

        // introspect the token
        TokenIntrospectionResponse requestingPartyToken = authzClient.protection().introspectRequestingPartyToken(rpt);
        for (Permission granted : requestingPartyToken.getPermissions()) {
            // TODO get all the items belonging to the resources in the permissions
            var items = ...;
        }

        return items;
    }
}

The PolicyEnforcer uses an instance of the AuthzClient to accessing keycloak authorization. So when I could get access to the PolicyEnforcer I could use this instance as well.

I think on the org.keycloak.AuthorizationContext are only the permissions/resources specific to this path right? But I need all the resources of a certain type.

Or is there a other way to implement this which I haven't discovered yet?

Regards

@joggeli34
Copy link
Contributor Author

The second use case is, that when someone is adding a new item to our service, we like to automatically create a new keycloak-resource for it, which can also be done with the AuthzClient. Of course I can create one by my self, but as I use the same settings as used for the quarkus-keycloak-authorization, it will allmost be just a copy of the same code.

@pedroigor
Copy link
Contributor

@joggeli34 We support that when using Keycloak Adapters but not here. Adding it.

@sberyozkin
Copy link
Member

@joggeli34 thanks for the clarifications

@mbecker
Copy link

mbecker commented Jun 24, 2021

HI guys,

I'm having a similar use case with getting all permissions for all resources for a specifc path to check the access to the items.

Seeing the comments in this thread and checking the pull request I thougt to integrate the AutzClient is to us the quarkus version "2.0.0.Alpha3" and then accessing the AuthzClient by injecting it.

Bootstraping the project:

mvn io.quarkus:quarkus-maven-plugin:2.0.0.Alpha3:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=quarkus-keycloak-20alpha3 \
    -DclassName="org.acme.keycloak.EventResource" \
    -Dpath="/events" \
    -Dextensions="oidc,keycloak-authorization,resteasy,resteasy-jackson,resteasy-mutiny"

Having the following pom:

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.acme</groupId>
  <artifactId>quarkus-keycloak-20alpha3</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <properties>
    <compiler-plugin.version>3.8.1</compiler-plugin.version>
    <maven.compiler.parameters>true</maven.compiler.parameters>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <quarkus-plugin.version>2.0.0.Alpha3</quarkus-plugin.version>
    <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
    <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
    <quarkus.platform.version>2.0.0.Alpha3</quarkus.platform.version>
    <surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>${quarkus.platform.group-id}</groupId>
        <artifactId>${quarkus.platform.artifact-id}</artifactId>
        <version>${quarkus.platform.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jackson</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-mutiny</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-oidc</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-keycloak-authorization</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-arc</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-maven-plugin</artifactId>
        <version>${quarkus-plugin.version}</version>
        <extensions>true</extensions>
        <executions>
          <execution>
            <goals>
              <goal>build</goal>
              <goal>generate-code</goal>
              <goal>generate-code-tests</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${compiler-plugin.version}</version>
        <configuration>
          <parameters>${maven.compiler.parameters}</parameters>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${surefire-plugin.version}</version>
        <configuration>
          <systemPropertyVariables>
            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
            <maven.home>${maven.home}</maven.home>
          </systemPropertyVariables>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <profiles>
    <profile>
      <id>native</id>
      <activation>
        <property>
          <name>native</name>
        </property>
      </activation>
      <build>
        <plugins>
          <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>${surefire-plugin.version}</version>
            <executions>
              <execution>
                <goals>
                  <goal>integration-test</goal>
                  <goal>verify</goal>
                </goals>
                <configuration>
                  <systemPropertyVariables>
                    <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
                    <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                    <maven.home>${maven.home}</maven.home>
                  </systemPropertyVariables>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
      <properties>
        <quarkus.package.type>native</quarkus.package.type>
      </properties>
    </profile>
  </profiles>
</project>

Injecting the AuthzClient as follows:

package org.acme.keycloak;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.keycloak.authorization.client.AuthzClient;

import io.quarkus.security.identity.SecurityIdentity;


@Path("/events")
public class EventResource {

    @Inject
    SecurityIdentity identity;

    @Inject
    AuthzClient authzClient;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy";
    }
}

I'm getting the following error message:

2021-06-24 13:39:31,160 ERROR [io.qua.dep.dev.IsolatedDevModeMain] (vert.x-worker-thread-1) Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
	[error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.keycloak.authorization.client.AuthzClient and qualifiers [@Default]
	- java member: org.acme.keycloak.EventResource#authzClient
	- declared on CLASS bean [types=[org.acme.keycloak.EventResource, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.keycloak.EventResource]
	at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1098)
	at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:264)
	at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:129)
	at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:413)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:820)
	at io.quarkus.builder.BuildContext.run(BuildContext.java:277)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
	at java.base/java.lang.Thread.run(Thread.java:832)
	at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.keycloak.authorization.client.AuthzClient and qualifiers [@Default]
	- java member: org.acme.keycloak.EventResource#authzClient
	- declared on CLASS bean [types=[org.acme.keycloak.EventResource, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.keycloak.EventResource]
	at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:492)
	at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:460)
	at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:252)
	... 12 more

...

Any idea or tipps how to use the AuthzClient to get the permissions / resources?

Thanks and cheers.

@antsfiles
Copy link

antsfiles commented Nov 16, 2023

I think you need to set this to true to be able to inject the AuthzClient:
quarkus.keycloak.policy-enforcer.enable=true
cf here
But it does not work for me :(

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

Successfully merging a pull request may close this issue.

5 participants