diff --git a/office-alexander-logistics-app/pom.xml b/office-alexander-logistics-app/pom.xml index c353190..e50f971 100644 --- a/office-alexander-logistics-app/pom.xml +++ b/office-alexander-logistics-app/pom.xml @@ -134,6 +134,7 @@ ${org.imixs.office.version} + @@ -142,7 +143,7 @@ true - oidc/ + src/oidc/ **/* @@ -457,5 +458,13 @@ test + + + org.keycloak + keycloak-authz-client + 26.0.4 + test + + \ No newline at end of file diff --git a/office-alexander-logistics-app/src/oidc/WEB-INF/oidc copy.1 b/office-alexander-logistics-app/src/oidc/WEB-INF/oidc copy.1 new file mode 100644 index 0000000..c7ac9d1 --- /dev/null +++ b/office-alexander-logistics-app/src/oidc/WEB-INF/oidc copy.1 @@ -0,0 +1,9 @@ +{ + "client-id" : "imixs", + "provider-url" : "https://sso.alexander-logistics.office-workflow.de/realms/imixs-office-workflow", + "ssl-required" : "EXTERNAL", + "bearer-only" : "true", + "credentials" : { + "secret" : "O8YjWcoW2nxb7jIgBePBKMvoDLUruZ3V" + } +} \ No newline at end of file diff --git a/office-alexander-logistics-app/src/oidc/WEB-INF/oidc.json b/office-alexander-logistics-app/src/oidc/WEB-INF/oidc.json new file mode 100644 index 0000000..960fecb --- /dev/null +++ b/office-alexander-logistics-app/src/oidc/WEB-INF/oidc.json @@ -0,0 +1,8 @@ +{ + "client-id" : "imixs", + "provider-url" : "https://sso.alexander-logistics.office-workflow.de/realms/imixs-office-workflow", + "bearer-only" : "true", + "credentials" : { + "secret" : "O8YjWcoW2nxb7jIgBePBKMvoDLUruZ3V" + } +} \ No newline at end of file diff --git a/office-alexander-logistics-app/src/oidc/WEB-INF/web.xml b/office-alexander-logistics-app/src/oidc/WEB-INF/web.xml new file mode 100644 index 0000000..5d3b359 --- /dev/null +++ b/office-alexander-logistics-app/src/oidc/WEB-INF/web.xml @@ -0,0 +1,143 @@ + + + imixs-office-workflow + + index.xhtml + + + + + jakarta.faces.STATE_SAVING_METHOD + server + + + jakarta.faces.CONFIG_FILES + + /WEB-INF/faces-config-office.xml, + /WEB-INF/faces-config-custom.xml + + + + + + + jakarta.faces.PROJECT_STAGE + Production + + + + + 60 + + + + + + Faces Servlet + jakarta.faces.webapp.FacesServlet + 0 + + /tmp + + 2097152 + + 20848820 + 1048576 + + + + Faces Servlet + *.jsf + + + Faces Servlet + *.xhtml + + + + + + 401 + /loginerror.jsf + + + 403 + /authorizationerror.jsf + + + 404 + /error.jsf + + + 500 + /error.jsf + + + + + + + + + Access Manager Security Constraint + + AUTHENTICATED_RESOURCE + /pages/* + /api/workflow/* + /api/model/* + /api/documents/* + /api/report/* + /api/snapshot/* + /api/adminp/* + /api/eventlog/* + /api/logout + + + + org.imixs.ACCESSLEVEL.READERACCESS + org.imixs.ACCESSLEVEL.AUTHORACCESS + org.imixs.ACCESSLEVEL.EDITORACCESS + org.imixs.ACCESSLEVEL.MANAGERACCESS + + + + + AUTHENTICATED_ADMIN_RESOURCE + /pages/admin/* + + + org.imixs.ACCESSLEVEL.MANAGERACCESS + + + + org.imixs.ACCESSLEVEL.NOACCESS + + + org.imixs.ACCESSLEVEL.READERACCESS + + + org.imixs.ACCESSLEVEL.AUTHORACCESS + + + org.imixs.ACCESSLEVEL.EDITORACCESS + + + org.imixs.ACCESSLEVEL.MANAGERACCESS + + \ No newline at end of file diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/KeycloakAuthenticator.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/KeycloakAuthenticator.java new file mode 100644 index 0000000..a77a6c7 --- /dev/null +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/KeycloakAuthenticator.java @@ -0,0 +1,105 @@ +package com.alexanderlogistics.rest; + +import java.io.IOException; +import java.util.logging.Logger; + +import org.imixs.melman.RestAPIException; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.JWSInputException; +import org.keycloak.representations.AccessToken; + +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.HttpHeaders; + +/** + * This RequestFilter performs a keycloak authentication. + * + * + * + * The client configuration is defined in a keycloak.json file as follows: + * + * { + * "realm": "imixs-office-workflow", + * "auth-server-url" : "https://my.keycloak.host/", + * "resource" : "the clientid", + * "credentials": { + * "secret": "xxx" + * } + * } + * + * + * @author rsoika + * + */ +public class KeycloakAuthenticator implements ClientRequestFilter { + + private String token = null; + + public static final String KEYCLOAK_ENDPOINT = "keycloak.endpoint"; + public static final String KEYCLOAK_REALM = "keycloak.realm"; + public static final String KEYCLOAK_CLIENTID = "keycloak.clientid"; + public static final String KEYCLOAK_SECRET = "keycloak.secret"; + + private final static Logger logger = Logger.getLogger(KeycloakAuthenticator.class.getName()); + + // Keycloak + String keycloakServer; + String keycloakRealm; + String keycloakClientId; + String keycloakSecret; + + /** + * Creates a new FormAuthenticator based on a baseUri and a username, password. + * The constructor post the user credentials to the endpoint /j_security_check + * to receive a JSESSIONID. + * + * @param _baseUri + * @param username + * @param password + * @throws RestAPIException + */ + public KeycloakAuthenticator(String token) throws RestAPIException { + + this.token = token; + + logger.info("token=" + token); + + AccessToken accessToken; + try { + accessToken = new JWSInput(token).readJsonContent(AccessToken.class); + logger.info("---email=" + accessToken.getEmail()); + + } catch (JWSInputException cause) { + throw new IllegalArgumentException("Failed to deserialize token", cause); + } + + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + /** + * This filter method is called for each request. The method adds teh bearer + * token into the header. + * + */ + public void filter(ClientRequestContext requestContext) throws IOException { + if (token != null && !"".equals(token)) { + logger.info(" add authroization header....v2"); + // requestContext.getHeaders().add("Bearer", getToken()); + + // Authorization: bearer $TOKEN + // requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, getToken()); + + requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + getToken()); + + } + } + +} \ No newline at end of file diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/TestKeycloakClient.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/TestKeycloakClient.java new file mode 100644 index 0000000..b922a93 --- /dev/null +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/TestKeycloakClient.java @@ -0,0 +1,166 @@ +package com.alexanderlogistics.rest; + +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +import org.apache.http.impl.client.HttpClients; +import org.imixs.melman.RestAPIException; +import org.imixs.melman.WorkflowClient; +import org.imixs.workflow.ItemCollection; +import org.imixs.workflow.exceptions.PluginException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.authorization.client.AuthzClient; +import org.keycloak.authorization.client.Configuration; + +import com.oracle.truffle.api.library.ExportMessage.Ignore; + +import jakarta.ws.rs.client.Invocation; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Response; + +/** + * Test to request data via keycloak authentication.... + * + * + * Curl Beispiel: + * https://stackoverflow.com/questions/50256433/obtaining-an-access-token-via-curl + * + * + * Beispiel: + * + * curl \ + * -d "client_id=imixs" \ + * -d "client_secret=O8YjWcoW2nxb7jIgBePBKMvoDLUruZ3V" \ + * -d "grant_type=client_credentials" \ + * "https://sso.alexander-logistics.office-workflow.de/realms/imixs-office-workflow/protocol/openid-connect/token" + * + * + * + * curl -X GET + * "https://test.alexander-logistics.office-workflow.de/api/documents/xxxxxx" \ + * -H "Authorization: Bearer + * eyJhbGciOiJSUzI1NiIsInR5cCIxxxxxxxxxxxxxlvPDoPf4l5w" + * + * + * + * + * curl -c cookies.txt -b cookies.txt -v -H "Authorization: Bearer + * eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJQT0g5Q0RPOUJjdGxEZFBVc3RlSzR4blVCM0VQNThKOENqaXdJb2hIUFRFIn0.eyJleHAiOjE3NDE2ODAwNDEsImlhdCI6MTc0MTY3OTc0MSwianRpIjoiMTJhZWMzZmItMDZlYy00ODY1LWI2MTAtYzBhNDczNjk1MWViIiwiaXNzIjoiaHR0cHM6Ly9zc28uYWxleGFuZGVyLWxvZ2lzdGljcy5vZmZpY2Utd29ya2Zsb3cuZGUvcmVhbG1zL2ltaXhzLW9mZmljZS13b3JrZmxvdyIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIyYjAyMTJiMy0wMTE4LTQ1ZmMtYjJiOS05ZjM4MmUyODlkMDgiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJpbWl4cyIsInNlc3Npb25fc3RhdGUiOiI2MWVlZWEwYy01ODRiLTRhMTctYjg3ZC0zNGE0NzY5Mzg3YmYiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHBzOi8vdGVzdC5hbGV4YW5kZXItbG9naXN0aWNzLm9mZmljZS13b3JrZmxvdy5kZSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1pbWl4cy1vZmZpY2Utd29ya2Zsb3ciLCJvZmZsaW5lX2FjY2VzcyIsIm9yZy5pbWl4cy5BQ0NFU1NMRVZFTC5BVVRIT1JBQ0NFU1MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6IjYxZWVlYTBjLTU4NGItNGExNy1iODdkLTM0YTQ3NjkzODdiZiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiQW5uYSBNdXN0ZXJtYW5uIiwiZ3JvdXBzIjpbImRlZmF1bHQtcm9sZXMtaW1peHMtb2ZmaWNlLXdvcmtmbG93Iiwib2ZmbGluZV9hY2Nlc3MiLCJvcmcuaW1peHMuQUNDRVNTTEVWRUwuQVVUSE9SQUNDRVNTIiwidW1hX2F1dGhvcml6YXRpb24iXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYW5uYSIsImdpdmVuX25hbWUiOiJBbm5hIiwiZmFtaWx5X25hbWUiOiJNdXN0ZXJtYW5uIiwiZW1haWwiOiJhbm5hQGltaXhzLmNvbSJ9.oitC9iBQSBEtZl3b65nWOSCwJ3MQjS0ES_YYJ4faL4IKZ3hN2XLFOL1kjW_guDF7Rg1k7dYm2WRAyI-QH1oXit7ANW-Z4DBpC0lbwepOsE5a5trjospYFtiLX5STO0WZuosa3b8XV7-F97Ru8Atox2eOqbJ4M-E_swnBjVxHvwLYIJXUBkQM_VXiF0acmwmcIjgolOl-mTC0NnnBClsZzbPfWxz0Ds2lc2lsQZQVjEaJ0mrMw3XCmf7rNC1LmSu6oJ8yiZ2UPMkkXDIRq0KV-hQ2sBqdfZdr4yFdbszXHLL0bDJixeS8xkBRkB7c0JSAxAgDmML1Y8Iuc7NuV6mg8Q" + * "https://test.alexander-logistics.office-workflow.de/api/documents/4eea4e54-be93-4297-9574-3a61e826b4f4" + * + * + * + * + * + * curl POST \ + * -H "Content-Type: application/x-www-form-urlencoded" \ + * -d "grant_type=password" \ + * -d "client_id=imixs" \ + * -d "client_secret=O8YjWcoW2nxb7jIgBePBKMvoDLUruZ3V" \ + * -d "username=anna" \ + * -d "password=anna" \ + * "https://sso.alexander-logistics.office-workflow.de/realms/imixs-office-workflow/protocol/openid-connect/token" + * + * + * curl -d 'client_id=imixs' -d 'username=anna' -d 'password=anna' -d + * 'grant_type=password' -d 'client_secret=O8YjWcoW2nxb7jIgBePBKMvoDLUruZ3V' \ + * 'https://sso.alexander-logistics.office-workflow.de/realms/imixs-office-workflow/protocol/openid-connect/token' + * | \ + * python -m json.tool + */ +@Ignore +public class TestKeycloakClient { + + private static Logger logger = Logger.getLogger(TestKeycloakClient.class.getName()); + + private static final String KEYCLOAK_TOKEN_URL = "https://sso.alexander-logistics.office-workflow.de/auth/realms/imixs-office-workflow/protocol/openid-connect/token"; + private static final String CLIENT_ID = "imixs"; + private static final String CLIENT_SECRET = "O8YjWcoW2nxb7jIgBePBKMvoDLUruZ3V"; + private static final String USERNAME = "anna"; + private static final String PASSWORD = "anna"; + + private String token = null; + + @Before + public void setup() throws PluginException { + try { + final Configuration configuration = new Configuration( + "https://sso.alexander-logistics.office-workflow.de/", + "imixs-office-workflow", // keycloak realm + "imixs", // keycloak client + Collections.singletonMap("secret", "O8YjWcoW2nxb7jIgBePBKMvoDLUruZ3V"), + HttpClients.createDefault()); + + token = AuthzClient.create(configuration).obtainAccessToken("anna", "anna").getToken(); + } catch (Exception e) { + System.out.println("Error: " + e.getMessage()); + + // "Could not obtain configuration from server + // [https://sso.alexander-logistics.office-workflow.de/auth/realms/imixs-office-workflow/.well-known/uma2-configuration]." + + // https://sso.alexander-logistics.office-workflow.de/realms/imixs-office-workflow/.well-known/openid-configuration + + throw new IllegalArgumentException("Token can't be obtained", e); + } + } + + /** + * Test token.... + * + */ + @Test + public void testToken() { + + System.out.println("Token = " + token); + + String instanceEndpoint = "https://test.alexander-logistics.office-workflow.de/api/"; + + String requestUri = "documents/6df8a656-9188-48a3-a0e0-c3a6274a586a"; + + WorkflowClient documentClient = new WorkflowClient(instanceEndpoint); + + KeycloakAuthenticator keycloakAuth; + try { + keycloakAuth = new KeycloakAuthenticator(token); + + documentClient.registerClientRequestFilter(keycloakAuth); + + WebTarget target = documentClient.getWebTarget(requestUri); + + // Erstelle eine Invocation von der WebTarget + Invocation.Builder invocationBuilder = target.request(); + Invocation invocation = invocationBuilder.buildGet(); + + // Sende die Anfrage und erhalte die Antwort + Response response = invocation.invoke(); + + // Überprüfe den Status der Antwort + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + // Extrahiere den Inhalt der Antwort als String + String responseBody = response.readEntity(String.class); + + // Gib den Inhalt aus (z.B. auf der Konsole) + System.out.println("Response Body: " + responseBody); + } else { + System.err.println("Fehler bei der Anfrage: " + response.getStatus()); + } + + // Schließe die Response, um Ressourcen freizugeben + response.close(); + + List result = documentClient.getCustomResource(requestUri); + + logger.info(" result size = " + result.size()); + } catch (RestAPIException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + Assert.fail(); + + } + + } + +}