diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ksef/KSeFAuthManager.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ksef/KSeFAuthManager.java index 3b76360..6a710b2 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ksef/KSeFAuthManager.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ksef/KSeFAuthManager.java @@ -7,13 +7,17 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.security.PublicKey; -import java.security.SecureRandom; +import java.security.spec.MGF1ParameterSpec; import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; import java.util.Base64; import java.util.Optional; import java.util.logging.Logger; import javax.crypto.Cipher; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.imixs.workflow.exceptions.InvalidAccessException; @@ -70,19 +74,11 @@ public class KSeFAuthManager { private String challenge = ""; private String refNumber = null; private String authToken = null; - private String accessToken = null; - private String refreshToken = null; - private String symmetricCertBase64; // der Zertifikat-String aus der API - private PublicKey symmetricPublicKey; // der extrahierte RSA Public Key - private String sessionRefNumber = null; - private String sessionValidUntil = null; - private String ksefTokenEncryptionCertBase64 = null; - private PublicKey ksefTokenEncryptionPublicKey; private String challengeTimestamp = null; - - private byte[] sessionAesKey; - - private byte[] sessionIv; + private String symmetricCertBase64; + private String ksefTokenCertBase64; + private PublicKey symmetricPublicKey; + private PublicKey ksefTokenPublicKey; @PostConstruct void init() { @@ -135,13 +131,12 @@ public class KSeFAuthManager { * * @return */ - // public String getEncodedToken() { + public String getEncodedToken() { - // String result = - // java.util.Base64.getEncoder().encodeToString(ksefToken.orElse("").getBytes()); + String result = java.util.Base64.getEncoder().encodeToString(ksefToken.orElse("").getBytes()); - // return result; - // } + return result; + } public String getBaseURI() { return baseURI; @@ -155,14 +150,6 @@ public class KSeFAuthManager { return authToken; } - public PublicKey getSymmetricPublicKey() { - return symmetricPublicKey; - } - - public String getSymmetricCertBase64() { - return symmetricCertBase64; - } - public String getChallengeValue() { return challenge; } @@ -174,7 +161,7 @@ public class KSeFAuthManager { */ // @Lock(LockType.READ) @Lock(LockType.WRITE) - public void getChallenge() throws PluginException { + public void authChallenge() throws PluginException { logger.info("├── KSeF API Challenge..."); String uri = baseURI + "/auth/challenge"; logger.info("│ ├── Endpoint: " + uri); @@ -230,30 +217,10 @@ public class KSeFAuthManager { * @throws Exception */ @Lock(LockType.WRITE) - public void generateAuthToken() throws Exception { + public void authKSeFToken() throws Exception { logger.info("├── KSeF API auth ksef-token..."); String uri = baseURI + "/auth/ksef-token"; logger.info("│ ├── Endpoint: " + uri); - - if (ksefTokenEncryptionPublicKey == null) { - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG, - "Missing public key for KSeF token encryption (KsefTokenEncryption)."); - } - - // token|timestamp (aus challenge) - String token = ksefToken.orElseThrow(() -> new PluginException( - KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG, "Missing KSeF API token")); - - String tokenWithTimestamp = token + "|" + challengeTimestamp; - - logger.info("│ ├── token|timestamp = " + tokenWithTimestamp); - - // Verschlüsseln mit RSA-OAEP SHA-256 - String encryptedToken = encryptWithPublicKey(tokenWithTimestamp, ksefTokenEncryptionPublicKey); - - logger.info("│ ├── Encrypted token (Base64): " + encryptedToken); - - // JSON payload zusammenbauen String jsonPayload = String.format( "{" + "\"challenge\": \"%s\"," + @@ -263,9 +230,7 @@ public class KSeFAuthManager { "}," + "\"encryptedToken\": \"%s\"" + "}", - challenge, - ksefNip.get(), - encryptedToken); + challenge, ksefNip.get(), getEncryptedToken()); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(uri)) @@ -275,84 +240,146 @@ public class KSeFAuthManager { .build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - logger.info("│ ├── Response Code = " + response.statusCode()); if (debug) { logger.info("Response: " + response.body()); } + // Prüfen, dass wir eine API-Antwort bekommen if (response.statusCode() >= 500) { throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG, "Unable to access KSeF API: response=" + response.statusCode()); } + // JSON parsen und Werte speichern if (response.statusCode() == 200 || response.statusCode() == 202) { try (Jsonb jsonb = JsonbBuilder.create()) { JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class); + // Reference Number speichern refNumber = jsonObject.getString("referenceNumber"); logger.info("Extracted referenceNumber: " + refNumber); + // Authentication Token speichern JsonObject authTokenObject = jsonObject.getJsonObject("authenticationToken"); authToken = authTokenObject.getString("token"); logger.info("Extracted authenticationToken: " + authToken); } catch (Exception e) { + logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage()); throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, "Error parsing JSON response: " + e.getMessage()); } } } + /** + * Verschlüsselt den KSeF-Token mit RSA-OAEP (SHA-256) für die Authentifizierung + * + * Format: token|timestamp_in_milliseconds + * + * @return Base64-kodierter verschlüsselter Token + * @throws Exception + */ + public String getEncryptedToken() throws Exception { + + if (ksefTokenPublicKey == null) { + throw new IllegalStateException("Public Key not loaded! Call loadPublicKeyCertificates() first."); + } + + if (challengeTimestamp == null || challengeTimestamp.isEmpty()) { + throw new IllegalStateException("Challenge timestamp is missing! Call authChallenge() first."); + } + + // 1. Timestamp parsen - KSeF liefert zu viele Dezimalstellen + // Format: "2025-11-14T14:18:35.606507+00:00" + // Flexibler Parser der verschiedene Dezimalstellen akzeptiert + DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + Instant instant = Instant.from(formatter.parse(challengeTimestamp)); + + long timestampMillis = instant.toEpochMilli(); + + logger.info("│ ├── Timestamp (ms): " + timestampMillis); + + // 2. Format erstellen: token|timestamp + String plaintext = ksefToken.get() + "|" + timestampMillis; + + if (debug) { + logger.info("│ ├── Plaintext format: [token]|[" + timestampMillis + "]"); + } + + // 3. RSA-OAEP Verschlüsselung mit SHA-256 + Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + OAEPParameterSpec oaepParams = new OAEPParameterSpec( + "SHA-256", + "MGF1", + MGF1ParameterSpec.SHA256, + PSource.PSpecified.DEFAULT); + cipher.init(Cipher.ENCRYPT_MODE, ksefTokenPublicKey, oaepParams); + + byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); + + // 4. Base64 kodieren + String result = Base64.getEncoder().encodeToString(encrypted); + + logger.info("│ ├── ✓ Token encrypted with RSA-OAEP"); + + return result; + } + + /** + * Redeems the KSeF token and returns a session token + * + * POST /auth/token/redeem + * + * @return JsonObject with referenceNumber and sessionToken + * @throws Exception + */ @Lock(LockType.WRITE) - public void redeemAuthToken() throws Exception { - logger.info("├── KSeF API redeem authentication token..."); + public JsonObject redeemToken() throws Exception { + + logger.info("├── KSeF API redeem token..."); String uri = baseURI + "/auth/token/redeem"; logger.info("│ ├── Endpoint: " + uri); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(uri)) .header("Accept", "application/json") - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + authToken) // authToken aus generateAuthToken() - .POST(HttpRequest.BodyPublishers.noBody()) // Payload leer, API erwartet nur Bearer im Header + .header("Authorization", "Bearer " + authToken) + .POST(HttpRequest.BodyPublishers.noBody()) .build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); logger.info("│ ├── Response Code = " + response.statusCode()); + if (debug) { - logger.info("Response: " + response.body()); + logger.info("│ └── Response: " + response.body()); } - // Prüfen auf Fehler - if (response.statusCode() >= 500) { - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_CONFIG, - "Unable to access KSeF API: response=" + response.statusCode()); + if (response.statusCode() >= 400) { + throw new PluginException(KSeFAuthManager.class.getSimpleName(), + ERROR_API, + "Token redeem failed: " + response.statusCode() + + " - " + response.body()); } - // JSON parsen und AccessToken + RefreshToken speichern - if (response.statusCode() == 200 || response.statusCode() == 201) { - try (Jsonb jsonb = JsonbBuilder.create()) { - JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class); + try (Jsonb jsonb = JsonbBuilder.create()) { + JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class); - JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken"); - accessToken = accessTokenObj.getString("token"); // finaler Bearer für alle weiteren API-Calls - logger.info("Extracted accessToken: " + accessToken); + // Access Token extrahieren und speichern + JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken"); + this.authToken = accessTokenObj.getString("token"); - JsonObject refreshTokenObj = jsonObject.getJsonObject("refreshToken"); - refreshToken = refreshTokenObj.getString("token"); // optional zum späteren Refresh - if (debug) { - logger.info("Extracted refreshToken: " + refreshToken); - } + logger.info("│ └── Access Token received"); - } catch (Exception e) { - logger.severe("├── ⚠️ Error parsing JSON response: " + e.getMessage()); - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, - "Error parsing JSON response: " + e.getMessage()); - } - } else { - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, - "Failed to redeem authentication token: " + response.statusCode()); + return jsonObject; + + } catch (Exception e) { + logger.severe("├── ⚠️ Error parsing JSON response: " + e.getMessage()); + throw new PluginException(KSeFAuthManager.class.getSimpleName(), + ERROR_API, + "Error parsing response: " + e.getMessage()); } } @@ -362,7 +389,7 @@ public class KSeFAuthManager { * @throws Exception */ @Lock(LockType.WRITE) - public void loadKsefPublicKeys() throws Exception { + public void loadPublicKeyCertificates() throws Exception { logger.info("├── KSeF API load public-key-certificates..."); String uri = baseURI + "/security/public-key-certificates"; @@ -383,81 +410,53 @@ public class KSeFAuthManager { if (response.statusCode() != 200) { throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, - "Unexpected response: " + response.statusCode()); + "Unexpected response from KSeF certificates endpoint: " + response.statusCode()); } + // JSON parsen try (Jsonb jsonb = JsonbBuilder.create()) { - // WICHTIG: JsonArray lesen – NICHT JsonObject[] - jakarta.json.JsonArray certArray = jsonb.fromJson(response.body(), jakarta.json.JsonArray.class); - - for (var entryValue : certArray) { - - JsonObject entry = entryValue.asJsonObject(); + var certArray = jsonb.fromJson(response.body(), JsonObject[].class); + for (JsonObject entry : certArray) { + // usage = ["KsefTokenEncryption"] oder ["SymmetricKeyEncryption"] var usages = entry.getJsonArray("usage"); - if (usages != null && - usages.stream().anyMatch(v -> v.toString().contains("KsefTokenEncryption"))) { + if (usages.stream().anyMatch(v -> v.toString().contains("KsefTokenEncryption"))) { + ksefTokenCertBase64 = entry.getString("certificate"); + logger.info("│ ├── KsefTokenEncryption certificate found."); + // Public Key extrahieren + ksefTokenPublicKey = extractPublicKeyFromCertificate(ksefTokenCertBase64); + logger.info("│ ├── ✓ RSA PublicKey successfully extracted."); - ksefTokenEncryptionCertBase64 = entry.getString("certificate"); - logger.info("│ ├── Found certificate with usage = KsefTokenEncryption"); + } - ksefTokenEncryptionPublicKey = extractPublicKeyFromCertificate(ksefTokenEncryptionCertBase64); + if (usages.stream().anyMatch(v -> v.toString().contains("SymmetricKeyEncryption"))) { + symmetricCertBase64 = entry.getString("certificate"); + logger.info("│ ├── SymmetricKeyEncryption certificate found."); + // Public Key extrahieren + symmetricPublicKey = extractPublicKeyFromCertificate(symmetricCertBase64); + logger.info("│ ├── ✓ RSA PublicKey successfully extracted."); - logger.info("│ ├── ✓ RSA PublicKey for KSeF Token Encryption extracted."); - return; } } - - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, - "No certificate with usage = KsefTokenEncryption found"); - + if (ksefTokenPublicKey == null) { + logger.warning("│ ├── ⚠️ No certificate with usage 'ksefTokenPublicKey' found!"); + } + if (symmetricPublicKey == null) { + logger.warning("│ ├── ⚠️ No certificate with usage 'symmetricPublicKey' found!"); + } } catch (Exception e) { throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, "Error parsing certificates JSON: " + e.getMessage()); } } - @Lock(LockType.WRITE) - public void loadSymmetricKeyPublicKey() throws Exception { - - logger.info("├── KSeF API load symmetric public key..."); - String uri = baseURI + "/security/public-key"; - logger.info("│ ├── Endpoint: " + uri); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(uri)) - .header("Accept", "application/json") - .GET() - .build(); - - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - - logger.info("│ ├── Response Code = " + response.statusCode()); - if (debug) { - logger.info("Response: " + response.body()); - } - - if (response.statusCode() != 200) { - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, - "Unexpected response: " + response.statusCode()); - } - - try (Jsonb jsonb = JsonbBuilder.create()) { - - JsonObject json = jsonb.fromJson(response.body(), JsonObject.class); - - symmetricCertBase64 = json.getString("certificate"); - logger.info("│ ├── Symmetric key certificate loaded"); - - symmetricPublicKey = extractPublicKeyFromCertificate(symmetricCertBase64); - - logger.info("│ ├── ✓ RSA PublicKey for session encryption extracted."); - - } catch (Exception e) { - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, - "Error parsing public key JSON: " + e.getMessage()); - } + private PublicKey extractPublicKeyFromCertificate(String base64Cert) throws Exception { + byte[] der = java.util.Base64.getDecoder().decode(base64Cert); + java.security.cert.CertificateFactory factory = java.security.cert.CertificateFactory.getInstance("X.509"); + var certificate = (java.security.cert.X509Certificate) factory + .generateCertificate(new java.io.ByteArrayInputStream(der)); + return certificate.getPublicKey(); } /** @@ -468,90 +467,11 @@ public class KSeFAuthManager { @Lock(LockType.WRITE) public void openInteractiveSession() throws Exception { - if (accessToken == null) { - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, - "Error - missing access token!"); - } - logger.info("├── KSeF API open interactive session..."); String uri = baseURI + "/sessions/online"; logger.info("│ ├── Endpoint: " + uri); - // --- AES Schlüssel erzeugen --- - SecureRandom rnd = new SecureRandom(); - byte[] aesKeyBytes = new byte[32]; // 256-bit AES key - rnd.nextBytes(aesKeyBytes); - byte[] ivBytes = new byte[16]; // 128-bit IV - rnd.nextBytes(ivBytes); - - // --- AES Key mit RSA encrypten --- - // Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); - Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); - rsa.init(Cipher.ENCRYPT_MODE, this.symmetricPublicKey); - byte[] encryptedAesKey = rsa.doFinal(aesKeyBytes); - - String jsonPayload = String.format( - "{" + - "\"formCode\": {" + - " \"systemCode\": \"FA (3)\"," + - " \"schemaVersion\": \"1-0E\"," + - " \"value\": \"FA\"" + - "}," + - "\"encryption\": {" + - " \"encryptedSymmetricKey\": \"%s\"," + - " \"initializationVector\": \"%s\"" + - "}" + - "}", - Base64.getEncoder().encodeToString(encryptedAesKey), - Base64.getEncoder().encodeToString(ivBytes)); - - if (debug) { - logger.info("│ ├── Payload: " + jsonPayload); - } - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(uri)) - // .header("Accept-Charset", "UTF-8") - .header("Accept", "application/json") - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) - .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) - .build(); - - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - - logger.info("│ ├── Response Code = " + response.statusCode()); - if (debug) { - logger.info("Response: " + response.body()); - } - - if (response.statusCode() != 201) { - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, - "Failed to open interactive session: " + response.statusCode()); - } - - // JSON parsen - // Session RefNumber aus Response extrahieren und speichern - - try (Jsonb jsonb = JsonbBuilder.create()) { - // Annahme: response.body() ist ein String mit JSON-Inhalt - JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class); - sessionRefNumber = jsonObject.getString("referenceNumber"); - sessionValidUntil = jsonObject.getString("validUntil"); - if (debug) { - logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber); - logger.info("│ └── Session ValidUntil = " + sessionValidUntil); - } - - // --- WICHTIG: AES Schlüssel speichern, wir brauchen ihn beim Upload --- - this.sessionAesKey = aesKeyBytes; - this.sessionIv = ivBytes; - } catch (Exception e) { - logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage()); - throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API, - "Error parsing JSON response: " + e.getMessage()); - - } + // ............ } @@ -568,9 +488,6 @@ public class KSeFAuthManager { logger.info("├── KSeF API check authentication status..."); - logger.info("Using public key algorithm: " + symmetricPublicKey.getAlgorithm()); - logger.info("Key format: " + symmetricPublicKey.getFormat()); - String uri = baseURI + "/auth/" + refNumber; logger.info("│ ├── Endpoint: " + uri); @@ -605,24 +522,4 @@ public class KSeFAuthManager { } } - private PublicKey extractPublicKeyFromCertificate(String base64Cert) throws Exception { - - byte[] der = java.util.Base64.getDecoder().decode(base64Cert); - - java.security.cert.CertificateFactory factory = java.security.cert.CertificateFactory.getInstance("X.509"); - - var certificate = (java.security.cert.X509Certificate) factory - .generateCertificate(new java.io.ByteArrayInputStream(der)); - - return certificate.getPublicKey(); - } - - private String encryptWithPublicKey(String data, PublicKey publicKey) throws Exception { - Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); - cipher.init(Cipher.ENCRYPT_MODE, publicKey); - byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); - return Base64.getEncoder().encodeToString(encryptedBytes); - - } - } diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/ksef/KSeFAuthManagerTest.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/ksef/KSeFAuthManagerTest.java index 7155ee5..571a823 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/ksef/KSeFAuthManagerTest.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/ksef/KSeFAuthManagerTest.java @@ -1,6 +1,5 @@ package com.alexanderlogistics.ksef; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.BeforeEach; @@ -37,88 +36,21 @@ public class KSeFAuthManagerTest { // ------------------------------------------------------------ @Test @Order(1) - @DisplayName("GET /auth/challenge should return a challenge") - void testChallenge() throws Exception { + @DisplayName("POST /api/v2/auth/token/redeem") + void testRedeemToken() throws Exception { - manager.getChallenge(); + manager.loadPublicKeyCertificates(); + manager.authChallenge(); + manager.authKSeFToken(); + manager.redeemToken(); assertNotNull( - manager.getChallengeValue(), - "Challenge darf nicht NULL sein"); + manager.getRefNumber(), + "RefNumber darf nicht NULL sein"); - assertFalse( - manager.getChallengeValue().isBlank(), - "Challenge darf nicht leer sein"); - - System.out.println("Challenge = " + manager.getChallengeValue()); - } - - // ------------------------------------------------------------ - // 2) Zertifikate holen (nur Public Keys) - // ------------------------------------------------------------ - @Test - @Order(2) - @DisplayName("GET /security/public-key-certificates should return symmetric key certificate") - void testPublicKeys() throws Exception { - - manager.getKsefPublicKeys(); - - assertNotNull(manager.getSymmetricPublicKey(), "Symmetric RSA PublicKey muss vorhanden sein"); - assertNotNull(manager.getSymmetricCertBase64(), "Base64 Zertifikat muss vorhanden sein"); - - System.out.println("Symmetric PublicKey OK"); - } - - // ------------------------------------------------------------ - // 3) KSeF Token Request - // ------------------------------------------------------------ - @Test - @Order(3) - @DisplayName("POST /auth/ksef-token should return a token") - void testKsefToken() throws Exception { - - // Challenge muss vorher geholt worden sein - manager.getChallenge(); - - // Token verschlüsseln - manager.generateAuthToken(); - - // Token-Call durchführen - String authToken = manager.getAuthToken(); - assertNotNull(authToken, "auth token darf nicht NULL sein"); - - String refNumber = manager.getRefNumber(); - assertNotNull(refNumber, "refNumber darf nicht NULL sein"); - - System.out.println("Received KSeF Token OK"); - } - - @Test - @Order(4) - @DisplayName("Upload Invoice XML (AES-256 + RSA)") - public void testOpenInteractiveSession() throws Exception { - - // ---------- 1) Vorbereitung ---------- - // API initialisieren - manager.loadKsefPublicKeys(); - manager.getChallenge(); - manager.generateAuthToken(); // erzeugt authToken + refNumber - manager.getAuthStatus(); - manager.redeemAuthToken(); - - assertNotNull(manager.getAuthToken(), "AccessToken fehlt"); - assertNotNull(manager.getRefNumber(), "ReferenceNumber fehlt"); - - manager.openInteractiveSession(); - } - - @Test - @Order(5) - @DisplayName("Upload Invoice XML (AES-256 + RSA)") - public void testUploadInvoice() throws Exception { - - // ---------- 1) Vorbereitung ---------- - // API initialisieren + assertNotNull( + manager.getAuthToken(), + "AuthToken darf nicht leer sein"); }