Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ jobs:

services:
keycloak:
image: quay.io/keycloak/keycloak:10.0.0
image: quay.io/keycloak/keycloak:10.0.1
env:
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
Expand Down Expand Up @@ -506,7 +506,7 @@ jobs:
-server -Xms64m -Xmx512m -XX:MetaspaceSize=96M \
-XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djava.awt.headless=true \
-Dkeycloak.profile.feature.upload_scripts=enabled" \
-d quay.io/keycloak/keycloak:10.0.0
-d quay.io/keycloak/keycloak:10.0.1
if: matrix.keycloak
- uses: actions/checkout@v2
- name: Set up JDK 11
Expand Down
2 changes: 1 addition & 1 deletion bom/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@
<jna.version>5.3.1</jna.version><!-- should satisfy both testcontainers and mongodb -->
<antlr.version>4.7.2</antlr.version>
<quarkus-security.version>1.1.1.Final</quarkus-security.version>
<keycloak.version>10.0.0</keycloak.version>
<keycloak.version>10.0.1</keycloak.version>
<logstash-gelf.version>1.14.0</logstash-gelf.version>
<jsch.version>0.1.55</jsch.version>
<jzlib.version>1.1.1</jzlib.version>
Expand Down
2 changes: 1 addition & 1 deletion build-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@

<!-- The image to use for tests that run Keycloak -->
<!-- IMPORTANT: If this is changed you must also update .github/workflows/ci-actions.yml to match the version -->
<keycloak.docker.image>quay.io/keycloak/keycloak:10.0.0</keycloak.docker.image>
<keycloak.docker.image>quay.io/keycloak/keycloak:10.0.1</keycloak.docker.image>

<unboundid-ldap.version>4.0.13</unboundid-ldap.version>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public Uni<Boolean> apply(Permission permission) {
if (context != null) {
String scopes = permission.getActions();

if (scopes == null) {
if (scopes == null || "".equals(scopes)) {
return Uni.createFrom().item(context.hasResourcePermission(permission.getName()));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.it.keycloak;

import java.security.BasicPermission;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -13,6 +14,7 @@
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

Expand Down Expand Up @@ -43,6 +45,27 @@ public List<Permission> apply(Boolean granted) {
});
}

@GET
@Path("/scope")
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<Permission>> hasScopePermission(@QueryParam("scope") String scope) {
return identity.checkPermission(new BasicPermission("Scope Permission Resource") {
@Override
public String getActions() {
return scope;
}
}).onItem()
.apply(new Function<Boolean, List<Permission>>() {
@Override
public List<Permission> apply(Boolean granted) {
if (granted) {
return identity.getAttribute("permissions");
}
throw new ForbiddenException();
}
});
}

@Path("/claim-protected")
@GET
@Produces(MediaType.APPLICATION_JSON)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ quarkus.keycloak.policy-enforcer.paths.8.name=Public
quarkus.keycloak.policy-enforcer.paths.8.path=/hello
quarkus.keycloak.policy-enforcer.paths.8.enforcement-mode=DISABLED

quarkus.keycloak.policy-enforcer.paths.9.name=Scope Permission Resource
quarkus.keycloak.policy-enforcer.paths.9.path=/api/permission/scope

admin-url=${keycloak.url}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ private static ClientRepresentation createClient(String clientId) {
configureHttpResponseClaimBasedPermission(authorizationSettings);
configureBodyClaimBasedPermission(authorizationSettings);
configurePaths(authorizationSettings);
configureScopePermission(authorizationSettings);

client.setAuthorizationSettings(authorizationSettings);

Expand All @@ -108,6 +109,13 @@ private static void configurePermissionResourcePermission(ResourceServerRepresen
createPermission(settings, createResource(settings, "Permission Resource", "/api/permission"), policy);
}

private static void configureScopePermission(ResourceServerRepresentation settings) {
PolicyRepresentation policy = createJSPolicy("Grant Policy", "$evaluation.grant();", settings);
createScopePermission(settings,
createResource(settings, "Scope Permission Resource", "/api/permission/scope", "read", "write"), policy,
"read");
}

private static void configureClaimBasedPermission(ResourceServerRepresentation settings) {
PolicyRepresentation policy = createJSPolicy("Claim-Based Policy", "var context = $evaluation.getContext();\n"
+ "var attributes = context.getAttributes();\n"
Expand Down Expand Up @@ -166,10 +174,30 @@ private static void createPermission(ResourceServerRepresentation settings, Reso
settings.getPolicies().add(permission);
}

private static void createScopePermission(ResourceServerRepresentation settings, ResourceRepresentation resource,
PolicyRepresentation policy, String scope) {
PolicyRepresentation permission = new PolicyRepresentation();

permission.setName(resource.getName() + " Permission");
permission.setType("scope");
permission.setResources(new HashSet<>());
permission.getResources().add(resource.getName());
permission.setScopes(new HashSet<>());
permission.getScopes().add(scope);
permission.setPolicies(new HashSet<>());
permission.getPolicies().add(policy.getName());

settings.getPolicies().add(permission);
}

private static ResourceRepresentation createResource(ResourceServerRepresentation authorizationSettings, String name,
String uri) {
String uri, String... scopes) {
ResourceRepresentation resource = new ResourceRepresentation(name);

for (String scope : scopes) {
resource.addScope(scope);
}

if (uri != null) {
resource.setUris(Collections.singleton(uri));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ public void testUserHasRoleConfidential() {
.then()
.statusCode(200)
.and().body(Matchers.containsString("Permission Resource"));
RestAssured.given().auth().oauth2(getAccessToken("jdoe"))
.when().get("/api/permission/scope?scope=write")
.then()
.statusCode(403);
RestAssured.given().auth().oauth2(getAccessToken("jdoe"))
.when().get("/api/permission/scope?scope=read")
.then()
.statusCode(200)
.and().body(Matchers.containsString("read"));
;
RestAssured.given().auth().oauth2(getAccessToken("admin"))
.when().get("/api/permission")
Expand Down
4 changes: 0 additions & 4 deletions integration-tests/oidc-code-flow/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-admin-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# Configuration file
admin-url=${keycloak.url}

# Default tenant configuration
quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus
quarkus.oidc.client-id=quarkus-app
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,75 @@
package io.quarkus.it.keycloak;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.RolesRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.util.JsonSerialization;

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import io.restassured.RestAssured;

public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycleManager {

private static final String KEYCLOAK_SERVER_URL = System.getProperty("keycloak.url", "http://localhost:8180/auth");
static final String KEYCLOAK_REALM = "quarkus";

private Keycloak keycloak;
private static final String KEYCLOAK_REALM = "quarkus";
private List<RealmRepresentation> realms = new ArrayList<>();

@Override
public Map<String, String> start() {
keycloak = createKeycloakClient();

RealmRepresentation realm = createRealm(KEYCLOAK_REALM);
keycloak.realms().create(realm);
realms.add(realm);
try {

RealmRepresentation realm = createRealm(KEYCLOAK_REALM);
createRealmInKeycloak(realm);
realms.add(realm);

RealmRepresentation logoutRealm = createRealm("logout-realm");
// revoke refresh tokens so that they can only be used once
logoutRealm.setRevokeRefreshToken(true);
logoutRealm.setRefreshTokenMaxReuse(0);
logoutRealm.setSsoSessionMaxLifespan(15);
logoutRealm.setAccessTokenLifespan(5);
keycloak.realms().create(logoutRealm);
realms.add(logoutRealm);
RealmRepresentation logoutRealm = createRealm("logout-realm");
// revoke refresh tokens so that they can only be used once
logoutRealm.setRevokeRefreshToken(true);
logoutRealm.setRefreshTokenMaxReuse(0);
logoutRealm.setSsoSessionMaxLifespan(15);
logoutRealm.setAccessTokenLifespan(5);
createRealmInKeycloak(logoutRealm);
realms.add(logoutRealm);

} catch (IOException e) {
throw new RuntimeException(e);
}
return Collections.emptyMap();
}

private static Keycloak createKeycloakClient() {
return KeycloakBuilder.builder()
.serverUrl(KEYCLOAK_SERVER_URL)
.realm("master")
.clientId("admin-cli")
.username("admin")
.password("admin")
.build();
private static String getAdminAccessToken() {
return RestAssured
.given()
.param("grant_type", "password")
.param("username", "admin")
.param("password", "admin")
.param("client_id", "admin-cli")
.when()
.post(KEYCLOAK_SERVER_URL + "/realms/master/protocol/openid-connect/token")
.as(AccessTokenResponse.class).getToken();
}

private static void createRealmInKeycloak(RealmRepresentation realm) throws IOException {
RestAssured
.given()
.auth().oauth2(getAdminAccessToken())
.contentType("application/json")
.body(JsonSerialization.writeValueAsBytes(realm))
.when()
.post(KEYCLOAK_SERVER_URL + "/admin/realms").then()
.statusCode(201);
}

private static RealmRepresentation createRealm(String name) {
Expand All @@ -62,8 +79,8 @@ private static RealmRepresentation createRealm(String name) {
realm.setEnabled(true);
realm.setUsers(new ArrayList<>());
realm.setClients(new ArrayList<>());
realm.setSsoSessionMaxLifespan(3); // 3 seconds
realm.setAccessTokenLifespan(4); // 4 seconds
realm.setSsoSessionMaxLifespan(3); // sec
realm.setAccessTokenLifespan(4); // 3 seconds

RolesRepresentation roles = new RolesRepresentation();
List<RoleRepresentation> realmRoles = new ArrayList<>();
Expand All @@ -84,26 +101,26 @@ private static RealmRepresentation createRealm(String name) {
return realm;
}

private static ClientRepresentation createClient(String clientId) {
private static ClientRepresentation createClientJwt(String clientId) {
ClientRepresentation client = new ClientRepresentation();

client.setClientId(clientId);
client.setEnabled(true);
client.setRedirectUris(Arrays.asList("*"));
client.setClientAuthenticatorType("client-secret");
client.setSecret("secret");
client.setClientAuthenticatorType("client-secret-jwt");
client.setSecret("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow");

return client;
}

private static ClientRepresentation createClientJwt(String clientId) {
private static ClientRepresentation createClient(String clientId) {
ClientRepresentation client = new ClientRepresentation();

client.setClientId(clientId);
client.setEnabled(true);
client.setRedirectUris(Arrays.asList("*"));
client.setClientAuthenticatorType("client-secret-jwt");
client.setSecret("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow");
client.setClientAuthenticatorType("client-secret");
client.setSecret("secret");

return client;
}
Expand All @@ -130,11 +147,11 @@ private static UserRepresentation createUser(String username, String... realmRol
@Override
public void stop() {
for (RealmRepresentation realm : realms) {
try {
keycloak.realm(realm.getRealm()).remove();
} catch (Exception ignore) {

}
RestAssured
.given()
.auth().oauth2(getAdminAccessToken())
.when()
.delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + realm.getRealm()).thenReturn().prettyPrint();
}
}
}