keycloak-memoizeit

KEYCLOAK-6807 Refactor TestHelper

9/6/2018 8:51:29 AM

Details

diff --git a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/FluentTestsHelper.java b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/FluentTestsHelper.java
new file mode 100644
index 0000000..b35be56
--- /dev/null
+++ b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/FluentTestsHelper.java
@@ -0,0 +1,420 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2017, Red Hat, Inc. and/or its affiliates, and individual
+ * contributors by the @authors tag. See the copyright.txt in the
+ * distribution for a full listing of individual contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.test;
+
+import static org.keycloak.test.builders.ClientBuilder.AccessType.PUBLIC;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.RoleResource;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistration;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+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.UserRepresentation;
+import org.keycloak.test.builders.ClientBuilder;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * A helper class that makes creating tests a bit easier.
+ *
+ * <p>
+ *    Usage example:
+ *    <pre>{@code
+ *    new TestsHelper()
+ *        .init()
+ *        .createDirectGrantClient("direct-grant-client")
+ *        .deleteClient("direct-grant-client")
+ *        .createTestUser("seb", "seb")
+ *        .assignRoleWithUser("seb", "user")
+ *        .deleteTestUser("seb")
+ *        .deleteRole("user");
+ *    }</pre>
+ * </p>
+ */
+public class FluentTestsHelper {
+
+    protected static class ClientData {
+        private final ClientRepresentation clientRepresentation;
+        private final String registrationCode;
+
+        public ClientData(ClientRepresentation clientRepresentation, String registrationCode) {
+            this.clientRepresentation = clientRepresentation;
+            this.registrationCode = registrationCode;
+        }
+
+        public ClientRepresentation getClientRepresentation() {
+            return clientRepresentation;
+        }
+
+        public String getRegistrationCode() {
+            return registrationCode;
+        }
+    }
+
+    public static final String DEFAULT_KEYCLOAK_URL = "http://localhost:8080/auth";
+    public static final String DEFAULT_ADMIN_USERNAME = "admin";
+    public static final String DEFAULT_ADMIN_PASSWORD = "admin";
+    public static final String DEFAULT_ADMIN_REALM = "master";
+    public static final String DEFAULT_ADMIN_CLIENT = "admin-cli";
+    public static final String DEFAULT_TEST_REALM = DEFAULT_ADMIN_REALM;
+    public static final String DEFAULT_USER_ROLE = "user";
+
+    protected final String keycloakBaseUrl;
+    protected final String adminUserName;
+    protected final String adminPassword;
+    protected final String adminClient;
+    protected final String adminRealm;
+
+    protected String testRealm;
+    protected Keycloak keycloak;
+    protected String accessToken;
+    protected volatile boolean isInitialized;
+    protected Map<String, ClientData> createdClients = new HashMap<>();
+
+    /**
+     * Creates a new helper instance.
+     */
+    public FluentTestsHelper() {
+        this(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD);
+    }
+
+    /**
+     * Creates a new helper instance.
+     *
+     * @param adminUserName Admin username.
+     * @param adminPassword Admin password.
+     */
+    public FluentTestsHelper(String adminUserName, String adminPassword) {
+        this(DEFAULT_KEYCLOAK_URL, adminUserName, adminPassword, DEFAULT_ADMIN_REALM, DEFAULT_ADMIN_CLIENT, DEFAULT_TEST_REALM);
+    }
+
+    /**
+     * Creates a new helper instance.
+     *
+     * @param keycloakBaseUrl Full keycloak URL.
+     * @param adminUserName Admin username.
+     * @param adminPassword Admin password.
+     * @param adminRealm Master realm name.
+     * @param adminClient Admin Client name.
+     * @param testRealm new instance.
+     */
+    public FluentTestsHelper(String keycloakBaseUrl, String adminUserName, String adminPassword, String adminRealm, String adminClient, String testRealm) {
+        this.keycloakBaseUrl = keycloakBaseUrl;
+        this.testRealm = testRealm;
+        this.adminUserName = adminUserName;
+        this.adminPassword = adminPassword;
+        this.adminRealm = adminRealm;
+        this.adminClient = adminClient;
+    }
+
+    /**
+     * Initialization method.
+     *
+     * @return <code>this</code>
+     */
+    public FluentTestsHelper init() {
+        keycloak = getKeycloakInstance(DEFAULT_KEYCLOAK_URL, adminRealm, adminUserName, adminPassword, adminClient);
+        accessToken = generateInitialAccessToken();
+        isInitialized = true;
+        return this;
+    }
+
+    /**
+     * @return Returns <code>true</code> if this helper has been initialized.
+     */
+    public boolean isInitialized() {
+        return isInitialized;
+    }
+
+    protected Keycloak getKeycloakInstance(String keycloakBaseUrl, String realm, String username, String password, String clientId) {
+        return Keycloak.getInstance(keycloakBaseUrl, realm, username, password, clientId);
+    }
+
+    protected String generateInitialAccessToken() {
+        ClientInitialAccessCreatePresentation rep = new ClientInitialAccessCreatePresentation();
+        rep.setCount(2);
+        rep.setExpiration(180);
+        ClientInitialAccessPresentation initialAccess = keycloak.realms().realm(testRealm).clientInitialAccess().create(rep);
+        return initialAccess.getToken();
+    }
+
+    /**
+     * Creates a new client based on its representation.
+     *
+     * @param clientRepresentation Client data.
+     * @return <code>this</code>
+     */
+    public FluentTestsHelper createClient(ClientRepresentation clientRepresentation) throws ClientRegistrationException, JsonProcessingException {
+        assert isInitialized;
+        ClientRegistration reg = ClientRegistration.create()
+                .url(keycloakBaseUrl, testRealm)
+                .build();
+        reg.auth(Auth.token(accessToken));
+        clientRepresentation = reg.create(clientRepresentation);
+        String registrationAccessCode = clientRepresentation.getRegistrationAccessToken();
+        reg.auth(Auth.token(registrationAccessCode));
+        createdClients.put(clientRepresentation.getClientId(), new ClientData(clientRepresentation, registrationAccessCode));
+        return this;
+    }
+
+    /**
+     * Creates a direct grant client.
+     *
+     * @see {@link #createClient(ClientRepresentation)}
+     */
+    public FluentTestsHelper createDirectGrantClient(String clientId) throws ClientRegistrationException, JsonProcessingException {
+        assert isInitialized;
+        createClient(ClientBuilder.create(clientId).accessType(PUBLIC));
+        return this;
+    }
+
+    /**
+     * Deletes a client previously created by this helper. This will throw an error if you try to delete an
+     * arbitrary client.
+     *
+     * @param clientId Client id to be deleted.
+     * @return <code>this</code>
+     * @throws ClientRegistrationException Thrown when client registration error occurs.
+     */
+    public FluentTestsHelper deleteClient(String clientId) throws ClientRegistrationException {
+        assert isInitialized;
+        ClientData clientData = createdClients.get(clientId);
+        if (clientData == null) {
+            throw new ClientRegistrationException("This client wasn't created by this helper!");
+        }
+        ClientRegistration reg = ClientRegistration.create()
+                .url(keycloakBaseUrl, testRealm)
+                .build();
+        reg.auth(Auth.token(clientData.getRegistrationCode()));
+        reg.delete(clientId);
+        return this;
+    }
+
+    /**
+     * @see #importTestRealm(InputStream)
+     */
+    public FluentTestsHelper importTestRealm(String realmJsonPath) throws IOException {
+        return importTestRealm(FluentTestsHelper.class.getResourceAsStream(realmJsonPath));
+    }
+
+    /**
+     * @see #importTestRealm(InputStream)
+     */
+    public FluentTestsHelper importTestRealm(File realmJsonPath) throws IOException {
+        try (FileInputStream fis = new FileInputStream(realmJsonPath)) {
+            return importTestRealm(fis);
+        }
+    }
+
+    /**
+     * Import a test realm.
+     *
+     * @param stream A stream representing a JSON file with an exported realm.
+     * @return <code>this</code>
+     * @throws IOException Thrown in case of parsing error.
+     */
+    public FluentTestsHelper importTestRealm(InputStream stream) throws IOException {
+        ObjectMapper mapper = new ObjectMapper();
+        RealmRepresentation realmRepresentation = mapper.readValue(stream, RealmRepresentation.class);
+        keycloak.realms().create(realmRepresentation);
+        testRealm = realmRepresentation.getRealm();
+        accessToken = generateInitialAccessToken();
+        return this;
+    }
+
+    /**
+     * Deletes a realm.
+     *
+     * @param realmName Realm to be deleted.
+     * @return <code>this</code>
+     */
+    public FluentTestsHelper deleteRealm(String realmName) {
+        keycloak.realms().realm(realmName).remove();
+        return this;
+    }
+
+    /**
+     * Creates a test user.
+     *
+     * @param username A username to be created.
+     * @param password A password for a user.
+     * @return <code>this</code>
+     */
+    public FluentTestsHelper createTestUser(String username, String password) {
+        UserRepresentation userRepresentation = new UserRepresentation();
+        userRepresentation.setUsername(username);
+        userRepresentation.setEnabled(true);
+        Response response = keycloak.realms().realm(testRealm).users().create(userRepresentation);
+        String userId = getCreatedId(response);
+        response.close();
+        CredentialRepresentation rep = new CredentialRepresentation();
+        rep.setType(CredentialRepresentation.PASSWORD);
+        rep.setValue(password);
+        rep.setTemporary(false);
+        keycloak.realms().realm(testRealm).users().get(userId).resetPassword(rep);
+        return this;
+    }
+
+    /**
+     * Associates a user with a role. This method also creates a role if that is missing.
+     *
+     * @param userName A username to be associated with a role.
+     * @param roleName A role to be associated with a user name.
+     * @return <code>this</code>
+     */
+    public FluentTestsHelper assignRoleWithUser(String userName, String roleName) {
+        if (keycloak.realms().realm(testRealm).roles().get(roleName) == null) {
+            RoleRepresentation representation = new RoleRepresentation();
+            representation.setName(roleName);
+            keycloak.realms().realm(testRealm).roles().create(representation);
+        }
+        UserRepresentation userRepresentation = keycloak.realms().realm(testRealm).users().search(userName).get(0);
+        RoleRepresentation realmRole =  keycloak.realms().realm(testRealm).roles().get(roleName).toRepresentation();
+        keycloak.realms().realm(testRealm).users().get(userRepresentation.getId()).roles().realmLevel().add(Arrays.asList(realmRole));
+        return this;
+    }
+
+    /**
+     * Deletes a role.
+     *
+     * @param roleName A Role name to be deleted.
+     * @return <code>this</code>
+     */
+    public FluentTestsHelper deleteRole(String roleName) {
+        RoleResource role = keycloak.realms().realm(testRealm).roles().get(roleName);
+        if (role != null) {
+            keycloak.realms().realm(testRealm).roles().deleteRole(roleName);
+        }
+        return this;
+    }
+
+    /**
+     * Deletes a user.
+     *
+     * @param userName A Username to be deleted.
+     * @return <code>this</code>
+     */
+    public FluentTestsHelper deleteTestUser(String userName) {
+        UserRepresentation userInKeycloak = keycloak.realms().realm(testRealm).users().search(userName).get(0);
+        if (userInKeycloak != null) {
+            keycloak.realms().realm(testRealm).users().delete(userInKeycloak.getId());
+        }
+        return this;
+    }
+
+    protected String getCreatedId(Response response) {
+        URI location = response.getLocation();
+        if (!response.getStatusInfo().equals(Response.Status.CREATED)) {
+            Response.StatusType statusInfo = response.getStatusInfo();
+            throw new WebApplicationException("Create method returned status "
+                    + statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)", response);
+        }
+        if (location == null) {
+            return null;
+        }
+        String path = location.getPath();
+        return path.substring(path.lastIndexOf('/') + 1);
+    }
+
+    /**
+     * Checks if given endpoint returns successfully with supplied token.
+     *
+     * @param endpoint Endpoint to be evaluated,
+     * @param token Token that will be passed into the <code>Authorization</code> header.
+     * @return <code>true</code> if the endpoint returns forbidden.
+     * @throws IOException Thrown by the underlying HTTP Client implementation
+     */
+    public boolean testGetWithAuth(String endpoint, String token) throws IOException {
+        CloseableHttpClient client = HttpClientBuilder.create().build();
+
+        try {
+            HttpGet get = new HttpGet(keycloakBaseUrl + endpoint);
+            get.addHeader("Authorization", "Bearer " + token);
+
+            HttpResponse response = client.execute(get);
+            if (response.getStatusLine().getStatusCode() != 200) {
+                return false;
+            }
+            HttpEntity entity = response.getEntity();
+            InputStream is = entity.getContent();
+            try {
+                return true;
+            } finally {
+                is.close();
+            }
+
+        } finally {
+            client.close();
+        }
+    }
+
+    /**
+     * Checks if a given endpoint returns Forbidden HTTP Code.
+     *
+     * @param endpoint Endpoint to be evaluated,
+     * @return <code>true</code> if the endpoint returns forbidden.
+     * @throws IOException Thrown by the underlying HTTP Client implementation
+     */
+    public boolean returnsForbidden(String endpoint) throws IOException {
+        CloseableHttpClient client = HttpClientBuilder.create().build();
+        try {
+            HttpGet get = new HttpGet(keycloakBaseUrl + endpoint);
+            HttpResponse response = client.execute(get);
+            if (response.getStatusLine().getStatusCode() == 403 || response.getStatusLine().getStatusCode() == 401) {
+                return true;
+            } else {
+                return false;
+            }
+
+        } finally {
+            client.close();
+        }
+    }
+
+    /**
+     * @return Returns an Access Token.
+     */
+    public String getToken() {
+        assert isInitialized;
+        return keycloak.tokenManager().getAccessTokenString();
+    }
+
+}
diff --git a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/TestsHelper.java b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/TestsHelper.java
index 27bf7de..0740661 100644
--- a/misc/keycloak-test-helper/src/main/java/org/keycloak/test/TestsHelper.java
+++ b/misc/keycloak-test-helper/src/main/java/org/keycloak/test/TestsHelper.java
@@ -45,6 +45,10 @@ import java.util.Arrays;
 
 import static org.keycloak.test.builders.ClientBuilder.AccessType.PUBLIC;
 
+/**
+ * @deprecated This class will be removed in the next major release. Please migrate to {@link FluentTestsHelper}.
+ */
+@Deprecated
 public class TestsHelper {
 
     public static String baseUrl;