Skip to content

Commit

Permalink
custom admin realm resource provider
Browse files Browse the repository at this point in the history
  • Loading branch information
dasniko committed Oct 12, 2023
1 parent a236bda commit 23f9396
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 7 deletions.
16 changes: 16 additions & 0 deletions .run/Remote Debugger REST Endpoints.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Remote Debugger REST Endpoints" type="Remote">
<module name="rest-endpoint" />
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" />
<option name="HOST" value="localhost" />
<option name="PORT" value="8787" />
<option name="AUTO_RESTART" value="false" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="8787" />
<option name="LOCAL" value="false" />
</RunnerSettings>
<method v="2" />
</configuration>
</component>
10 changes: 4 additions & 6 deletions rest-endpoint/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,15 @@
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<exclusions>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dasniko.keycloak.resource;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.RequiredArgsConstructor;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.UserPermissionEvaluator;

import java.util.List;
import java.util.Map;


@RequiredArgsConstructor
public class MyAdminRealmResourceProvider implements AdminRealmResourceProvider {

private final KeycloakSession session;

private RealmModel realm;
private AdminPermissionEvaluator auth;

@Override
public Object getResource(KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.realm = realm;
this.auth = auth;
return this;
}

@Override
public void close() {
}

@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getListOfUsers() {
// do the authorization with the existing admin permissions (e.g. realm management roles)
final UserPermissionEvaluator userPermissionEvaluator = auth.users();
userPermissionEvaluator.requireQuery();

// collect/manipulate data accordingly to your requirements
List<Map<String, String>> userList = session.users()
.searchForUserStream(realm, Map.of(UserModel.SEARCH, "*"))
.filter(userModel -> userModel.getServiceAccountClientLink() == null)
.map(userModel -> Map.of("username", userModel.getUsername()))
.toList();

// then return the desired result
return Response.ok(userList).build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package dasniko.keycloak.resource;

import com.google.auto.service.AutoService;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider;
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory;

@AutoService(AdminRealmResourceProviderFactory.class)
public class MyAdminRealmResourceProviderFactory implements AdminRealmResourceProviderFactory, EnvironmentDependentProviderFactory {

public static final String PROVIDER_ID = "my-admin-rest-resource";

@Override
public AdminRealmResourceProvider create(KeycloakSession session) {
return new MyAdminRealmResourceProvider(session);
}

@Override
public void init(Config.Scope config) {
}

@Override
public void postInit(KeycloakSessionFactory factory) {
}

@Override
public void close() {
}

@Override
public String getId() {
return PROVIDER_ID;
}

@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.ADMIN2);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dasniko.keycloak.resource;

import com.google.auto.service.AutoService;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
Expand All @@ -9,6 +10,7 @@
/**
* @author Niko Köbler, https://www.n-k.de, @dasniko
*/
@AutoService(RealmResourceProviderFactory.class)
public class MyResourceProviderFactory implements RealmResourceProviderFactory {

public static final String PROVIDER_ID = "my-rest-resource";
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dasniko.keycloak.resource;

import dasniko.testcontainers.keycloak.KeycloakContainer;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.AccessTokenResponse;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;

@Testcontainers
public class MyAdminRealmResourceProviderTest {

@Container
private static final KeycloakContainer keycloak =
new KeycloakContainer().withProviderClassesFrom("target/classes");

@Test
public void testEndpoint() {
Keycloak keycloakClient = keycloak.getKeycloakAdminClient();
AccessTokenResponse accessTokenResponse = keycloakClient.tokenManager().getAccessToken();

given().baseUri(keycloak.getAuthServerUrl())
.basePath("/admin/realms/master/" + MyAdminRealmResourceProviderFactory.PROVIDER_ID)
.auth().oauth2(accessTokenResponse.getToken())
.when().get()
.then().statusCode(200)
.body("size()", is(1));
}

}

0 comments on commit 23f9396

Please sign in to comment.