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();
+
+ }
+
+ }
+
+}