diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/TestFrameworkException.java b/test-framework/core/src/main/java/org/keycloak/test/framework/TestFrameworkException.java new file mode 100644 index 00000000000..3f73f003839 --- /dev/null +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/TestFrameworkException.java @@ -0,0 +1,9 @@ +package org.keycloak.test.framework; + +public class TestFrameworkException extends RuntimeException { + + public TestFrameworkException(String message) { + super(message); + } + +} diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/admin/KeycloakAdminClientSupplier.java b/test-framework/core/src/main/java/org/keycloak/test/framework/admin/KeycloakAdminClientSupplier.java index bbbe0221ebc..9cbd355a932 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/admin/KeycloakAdminClientSupplier.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/admin/KeycloakAdminClientSupplier.java @@ -3,12 +3,18 @@ package org.keycloak.test.framework.admin; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.test.framework.TestFrameworkException; import org.keycloak.test.framework.annotations.InjectAdminClient; import org.keycloak.test.framework.config.Config; import org.keycloak.test.framework.injection.InstanceContext; import org.keycloak.test.framework.injection.LifeCycle; import org.keycloak.test.framework.injection.RequestedInstance; import org.keycloak.test.framework.injection.Supplier; +import org.keycloak.test.framework.realm.ManagedRealm; +import org.keycloak.test.framework.realm.ManagedUser; import org.keycloak.test.framework.server.KeycloakServer; public class KeycloakAdminClientSupplier implements Supplier { @@ -25,14 +31,46 @@ public class KeycloakAdminClientSupplier implements Supplier instanceContext) { + InjectAdminClient annotation = instanceContext.getAnnotation(); + + InjectAdminClient.Mode mode = annotation.mode(); + KeycloakServer server = instanceContext.getDependency(KeycloakServer.class); - return KeycloakBuilder.builder() + KeycloakBuilder clientBuilder = KeycloakBuilder.builder() .serverUrl(server.getBaseUrl()) - .realm("master") - .grantType(OAuth2Constants.CLIENT_CREDENTIALS) - .clientId(Config.getAdminClientId()) - .clientSecret(Config.getAdminClientSecret()) - .build(); + .grantType(OAuth2Constants.CLIENT_CREDENTIALS); + + if (mode.equals(InjectAdminClient.Mode.BOOTSTRAP)) { + clientBuilder.realm("master").clientId(Config.getAdminClientId()).clientSecret(Config.getAdminClientSecret()); + } else if (mode.equals(InjectAdminClient.Mode.MANAGED_REALM)) { + ManagedRealm managedRealm = instanceContext.getDependency(ManagedRealm.class); + clientBuilder.realm(managedRealm.getName()); + + String clientId = !annotation.client().isEmpty() ? annotation.client() : null; + String userId = !annotation.user().isEmpty() ? annotation.user() : null; + + if (clientId == null) { + throw new TestFrameworkException("Client is required when using managed realm mode"); + } + + RealmRepresentation realmRep = managedRealm.getCreatedRepresentation(); + ClientRepresentation clientRep = realmRep.getClients().stream() + .filter(c -> c.getClientId().equals(annotation.client())) + .findFirst().orElseThrow(() -> new TestFrameworkException("Client " + annotation.client() + " not found in managed realm")); + + clientBuilder.clientId(clientId).clientSecret(clientRep.getSecret()); + + if (userId != null) { + UserRepresentation userRep = realmRep.getUsers().stream() + .filter(u -> u.getUsername().equals(annotation.user())) + .findFirst().orElseThrow(() -> new TestFrameworkException("User " + annotation.user() + " not found in managed realm")); + String password = ManagedUser.getPassword(userRep); + clientBuilder.username(userRep.getUsername()).password(password); + clientBuilder.grantType(OAuth2Constants.PASSWORD); + } + } + + return clientBuilder.build(); } @Override diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/annotations/InjectAdminClient.java b/test-framework/core/src/main/java/org/keycloak/test/framework/annotations/InjectAdminClient.java index bccd431e502..27a41d498f3 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/annotations/InjectAdminClient.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/annotations/InjectAdminClient.java @@ -10,4 +10,19 @@ import java.lang.annotation.Target; @Target(ElementType.FIELD) public @interface InjectAdminClient { + String ref() default ""; + + Mode mode() default Mode.BOOTSTRAP; + + String client() default ""; + + String user() default ""; + + enum Mode { + + BOOTSTRAP, + MANAGED_REALM + + } + } diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/injection/DefaultAnnotationProxy.java b/test-framework/core/src/main/java/org/keycloak/test/framework/injection/DefaultAnnotationProxy.java index 43b433e55a6..22803503a91 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/injection/DefaultAnnotationProxy.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/injection/DefaultAnnotationProxy.java @@ -7,19 +7,23 @@ import java.lang.reflect.Proxy; public class DefaultAnnotationProxy implements InvocationHandler { private final Class annotationClass; + private final String ref; - public DefaultAnnotationProxy(Class annotationClass) { + public DefaultAnnotationProxy(Class annotationClass, String ref) { this.annotationClass = annotationClass; + this.ref = ref; } - public static S proxy(Class annotationClass) { - return (S) Proxy.newProxyInstance(DefaultAnnotationProxy.class.getClassLoader(), new Class[]{annotationClass}, new DefaultAnnotationProxy(annotationClass)); + public static S proxy(Class annotationClass, String ref) { + return (S) Proxy.newProxyInstance(DefaultAnnotationProxy.class.getClassLoader(), new Class[]{annotationClass}, new DefaultAnnotationProxy(annotationClass, ref)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("annotationType")) { return annotationClass; + } else if (method.getName().equals("ref")) { + return ref; } else { return annotationClass.getMethod(method.getName()).getDefaultValue(); } diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/injection/Registry.java b/test-framework/core/src/main/java/org/keycloak/test/framework/injection/Registry.java index a7bf2395471..176c22dd025 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/injection/Registry.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/injection/Registry.java @@ -100,7 +100,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource { private T getUnConfiguredDependency(Class typeClass, String ref, InstanceContext dependent) { InstanceContext dependency; Supplier supplier = extensions.findSupplierByType(typeClass); - Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass()); + Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass(), ref); dependency = new InstanceContext(-1, this, supplier, defaultAnnotation, typeClass); dependency.registerDependency(dependent); @@ -238,7 +238,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource { } else { Supplier supplier = extensions.findSupplierByType(valueType); if (supplier != null) { - Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass()); + Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass(), ""); return new RequestedInstance(supplier, defaultAnnotation, valueType); } } diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ManagedRealm.java b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ManagedRealm.java index 8477f98a4a6..ed485e6a461 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ManagedRealm.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ManagedRealm.java @@ -40,6 +40,10 @@ public class ManagedRealm extends ManagedTestResource { return realmResource; } + public RealmRepresentation getCreatedRepresentation() { + return createdRepresentation; + } + public void updateWithCleanup(RealmUpdate... updates) { RealmRepresentation rep = admin().toRepresentation(); cleanup().resetToOriginalRepresentation(rep); diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ManagedUser.java b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ManagedUser.java index 8ed6d7a0e0c..c57fe32a16a 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ManagedUser.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/ManagedUser.java @@ -26,12 +26,16 @@ public class ManagedUser { } public String getPassword() { - Optional password = createdRepresentation.getCredentials().stream().filter(c -> c.getType().equals(CredentialRepresentation.PASSWORD)).findFirst(); - return password.map(CredentialRepresentation::getValue).orElse(null); + return getPassword(createdRepresentation); } public UserResource admin() { return userResource; } + public static String getPassword(UserRepresentation userRepresentation) { + Optional password = userRepresentation.getCredentials().stream().filter(c -> c.getType().equals(CredentialRepresentation.PASSWORD)).findFirst(); + return password.map(CredentialRepresentation::getValue).orElse(null); + } + } diff --git a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/RealmSupplier.java b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/RealmSupplier.java index a54284b800f..26788a94bfb 100644 --- a/test-framework/core/src/main/java/org/keycloak/test/framework/realm/RealmSupplier.java +++ b/test-framework/core/src/main/java/org/keycloak/test/framework/realm/RealmSupplier.java @@ -30,7 +30,7 @@ public class RealmSupplier implements Supplier { @Override public ManagedRealm getValue(InstanceContext instanceContext) { KeycloakServer server = instanceContext.getDependency(KeycloakServer.class); - Keycloak adminClient = instanceContext.getDependency(Keycloak.class); + Keycloak adminClient = instanceContext.getDependency(Keycloak.class, "bootstrap-client"); RealmConfig config = SupplierHelpers.getInstance(instanceContext.getAnnotation().config()); diff --git a/test-framework/core/src/test/java/org/keycloak/test/framework/injection/DefaultAnnotationProxyTest.java b/test-framework/core/src/test/java/org/keycloak/test/framework/injection/DefaultAnnotationProxyTest.java index 5e940ab2b98..a0738933ba0 100644 --- a/test-framework/core/src/test/java/org/keycloak/test/framework/injection/DefaultAnnotationProxyTest.java +++ b/test-framework/core/src/test/java/org/keycloak/test/framework/injection/DefaultAnnotationProxyTest.java @@ -16,16 +16,22 @@ public class DefaultAnnotationProxyTest { @Test public void testGetField() { - MockAnnotation proxy = DefaultAnnotationProxy.proxy(MockAnnotation.class); + MockAnnotation proxy = DefaultAnnotationProxy.proxy(MockAnnotation.class, ""); Assertions.assertEquals(LifeCycle.CLASS, proxy.lifecycle()); Assertions.assertEquals(LinkedList.class, proxy.config()); Assertions.assertEquals("", proxy.ref()); Assertions.assertEquals("else", proxy.something()); } + @Test + public void testCustomRef() { + MockAnnotation proxy = DefaultAnnotationProxy.proxy(MockAnnotation.class, "myref"); + Assertions.assertEquals("myref", proxy.ref()); + } + @Test public void testAnnotationReflection() { - MockAnnotation proxy = DefaultAnnotationProxy.proxy(MockAnnotation.class); + MockAnnotation proxy = DefaultAnnotationProxy.proxy(MockAnnotation.class, ""); Assertions.assertEquals(LifeCycle.CLASS, SupplierHelpers.getAnnotationField(proxy, "lifecycle")); Assertions.assertEquals(LinkedList.class, SupplierHelpers.getAnnotationField(proxy, "config")); Assertions.assertEquals("", SupplierHelpers.getAnnotationField(proxy, "ref")); diff --git a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RealmWithClientAndUserTest.java b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RealmSpecificAdminClientTest.java similarity index 64% rename from test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RealmWithClientAndUserTest.java rename to test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RealmSpecificAdminClientTest.java index 88624816c17..340c548e47f 100644 --- a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RealmWithClientAndUserTest.java +++ b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RealmSpecificAdminClientTest.java @@ -2,12 +2,17 @@ package org.keycloak.test.examples; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.JWSInputException; import org.keycloak.models.AdminRoles; import org.keycloak.models.Constants; +import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.MappingsRepresentation; -import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.test.framework.annotations.InjectAdminClient; import org.keycloak.test.framework.annotations.InjectRealm; import org.keycloak.test.framework.annotations.KeycloakIntegrationTest; import org.keycloak.test.framework.realm.ManagedRealm; @@ -17,14 +22,31 @@ import org.keycloak.test.framework.realm.RealmConfigBuilder; import java.util.List; @KeycloakIntegrationTest -public class RealmWithClientAndUserTest { +public class RealmSpecificAdminClientTest { @InjectRealm(config = RealmWithClientAndUser.class) ManagedRealm realm; + @InjectAdminClient(ref = "bootstrap-client") + Keycloak bootstrapAdminClient; + + @InjectAdminClient(mode = InjectAdminClient.Mode.MANAGED_REALM, client = "myclient", user = "myadmin") + Keycloak realmAdminClient; + + @Test + public void testAdminClientIssuers() throws JWSInputException { + AccessToken bootstrapAccessToken = new JWSInput(bootstrapAdminClient.tokenManager().getAccessToken().getToken()).readJsonContent(AccessToken.class); + Assertions.assertTrue(bootstrapAccessToken.getIssuer().endsWith("/realms/master")); + + AccessToken realmAccessToken = new JWSInput(realmAdminClient.tokenManager().getAccessToken().getToken()).readJsonContent(AccessToken.class); + Assertions.assertTrue(realmAccessToken.getIssuer().endsWith("/realms/" + realm.getName())); + } + @Test public void testRealmWithClientAndUser() { - List clients = realm.admin().clients().findByClientId("myclient"); + RealmResource realmResource = realmAdminClient.realms().realm(realm.getName()); + + List clients = realmResource.clients().findByClientId("myclient"); Assertions.assertEquals(1, clients.size()); ClientRepresentation client = clients.get(0); @@ -42,7 +64,7 @@ public class RealmWithClientAndUserTest { Assertions.assertEquals("myadmin@localhost", user.getEmail()); Assertions.assertTrue(user.isEmailVerified()); - MappingsRepresentation roles = realm.admin().users().get(user.getId()).roles().getAll(); + MappingsRepresentation roles = realmResource.users().get(user.getId()).roles().getAll(); Assertions.assertEquals(1, roles.getClientMappings().get(Constants.REALM_MANAGEMENT_CLIENT_ID).getMappings().size()); }