logging
This commit is contained in:
parent
cfd974ecd2
commit
1bce7db64e
5 changed files with 586 additions and 259 deletions
|
|
@ -25,7 +25,6 @@ import org.imixs.workflow.ItemCollection;
|
||||||
import org.imixs.workflow.engine.DocumentService;
|
import org.imixs.workflow.engine.DocumentService;
|
||||||
import org.imixs.workflow.exceptions.PluginException;
|
import org.imixs.workflow.exceptions.PluginException;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.security.DeclareRoles;
|
import jakarta.annotation.security.DeclareRoles;
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
import jakarta.annotation.security.RunAs;
|
import jakarta.annotation.security.RunAs;
|
||||||
|
|
@ -60,11 +59,6 @@ public class KSeFAPIService {
|
||||||
@Inject
|
@Inject
|
||||||
DocumentService documentService;
|
DocumentService documentService;
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
void init() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method uploads an KSeF Invoice document (XML).
|
* This method uploads an KSeF Invoice document (XML).
|
||||||
* The response of the uplaod is a 'referenceNumber' which is stored into the
|
* The response of the uplaod is a 'referenceNumber' which is stored into the
|
||||||
|
|
@ -117,10 +111,7 @@ public class KSeFAPIService {
|
||||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||||
|
|
||||||
// ---------- 4) XML per AES-256-CBC verschlüsseln ----------
|
// ---------- 4) XML per AES-256-CBC verschlüsseln ----------
|
||||||
javax.crypto.Cipher aesCipher;
|
javax.crypto.Cipher aesCipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
|
||||||
aesCipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
|
|
||||||
aesCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, iv);
|
aesCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, iv);
|
||||||
|
|
||||||
byte[] encryptedInvoiceXml = aesCipher.doFinal(invoiceXml);
|
byte[] encryptedInvoiceXml = aesCipher.doFinal(invoiceXml);
|
||||||
|
|
@ -198,6 +189,9 @@ public class KSeFAPIService {
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage());
|
logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage());
|
||||||
|
|
||||||
|
kseFAuthManager.deleteCurrentSession();
|
||||||
|
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
"Error parsing JSON response: " + e.getMessage());
|
"Error parsing JSON response: " + e.getMessage());
|
||||||
|
|
||||||
|
|
@ -220,6 +214,8 @@ public class KSeFAPIService {
|
||||||
fileData.setAttribute("ksef.referenceNumber", textlist);
|
fileData.setAttribute("ksef.referenceNumber", textlist);
|
||||||
|
|
||||||
kseFAuthManager.closeSession();
|
kseFAuthManager.closeSession();
|
||||||
|
kseFAuthManager.deleteCurrentSession();
|
||||||
|
|
||||||
return referenceNumber;
|
return referenceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ import java.security.spec.MGF1ParameterSpec;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
@ -36,6 +38,7 @@ import jakarta.ejb.Lock;
|
||||||
import jakarta.ejb.LockType;
|
import jakarta.ejb.LockType;
|
||||||
import jakarta.ejb.Singleton;
|
import jakarta.ejb.Singleton;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.json.JsonArray;
|
||||||
import jakarta.json.JsonObject;
|
import jakarta.json.JsonObject;
|
||||||
import jakarta.json.bind.Jsonb;
|
import jakarta.json.bind.Jsonb;
|
||||||
import jakarta.json.bind.JsonbBuilder;
|
import jakarta.json.bind.JsonbBuilder;
|
||||||
|
|
@ -195,14 +198,22 @@ public class KSeFAuthManager {
|
||||||
logger.info("│ ├── open new interactive KSeF Session...");
|
logger.info("│ ├── open new interactive KSeF Session...");
|
||||||
// open new session
|
// open new session
|
||||||
this.loadPublicKeyCertificates();
|
this.loadPublicKeyCertificates();
|
||||||
|
|
||||||
this.authChallenge();
|
this.authChallenge();
|
||||||
this.authKSeFToken();
|
this.authKSeFToken();
|
||||||
|
this.waitSomeTime(3000);
|
||||||
|
|
||||||
|
// Jetzt Status abwarten
|
||||||
|
this.waitForAuthStatus();
|
||||||
|
|
||||||
|
waitSomeTime(5000);
|
||||||
|
this.redeemToken();
|
||||||
|
|
||||||
|
this.openInteractiveSession();
|
||||||
|
// Delete deprecated sessions
|
||||||
|
this.deleteDeprecatedSessions();
|
||||||
|
|
||||||
this.checkTokenStatus();
|
this.checkTokenStatus();
|
||||||
|
|
||||||
this.redeemToken();
|
|
||||||
this.openInteractiveSession();
|
|
||||||
} else {
|
} else {
|
||||||
logger.warning("│ ├── ! Session already exists - reuse existing KSeF Session...");
|
logger.warning("│ ├── ! Session already exists - reuse existing KSeF Session...");
|
||||||
}
|
}
|
||||||
|
|
@ -262,10 +273,8 @@ public class KSeFAuthManager {
|
||||||
|
|
||||||
logger.info("├── ♻️ KSeF API load public-key-certificates...");
|
logger.info("├── ♻️ KSeF API load public-key-certificates...");
|
||||||
String uri = baseURI + "/security/public-key-certificates";
|
String uri = baseURI + "/security/public-key-certificates";
|
||||||
|
logger.info("│ ├── GET: " + uri);
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
|
||||||
}
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create(uri))
|
.uri(URI.create(uri))
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
|
|
@ -279,9 +288,9 @@ public class KSeFAuthManager {
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
"Unable to send request for KSeF certificates endpoint: " + e.getMessage());
|
"Unable to send request for KSeF certificates endpoint: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
if (debug) {
|
if (debug) {
|
||||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
|
||||||
logger.info("Response: " + response.body());
|
logger.info("Response: " + response.body());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,9 +351,7 @@ public class KSeFAuthManager {
|
||||||
logger.info("├── 🛅 KSeF API Challenge...");
|
logger.info("├── 🛅 KSeF API Challenge...");
|
||||||
String uri = baseURI + "/auth/challenge";
|
String uri = baseURI + "/auth/challenge";
|
||||||
|
|
||||||
if (debug) {
|
logger.info("│ ├── POST: " + uri);
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
|
||||||
}
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create(uri)) // nur Endpoint checken
|
.uri(URI.create(uri)) // nur Endpoint checken
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
|
|
@ -354,8 +361,9 @@ public class KSeFAuthManager {
|
||||||
HttpResponse<String> response;
|
HttpResponse<String> response;
|
||||||
try {
|
try {
|
||||||
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
if (debug) {
|
if (debug) {
|
||||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
|
||||||
logger.info("Response: " + response.body());
|
logger.info("Response: " + response.body());
|
||||||
}
|
}
|
||||||
// Prüfen, dass wir eine API-Antwort bekommen, kein HTML
|
// Prüfen, dass wir eine API-Antwort bekommen, kein HTML
|
||||||
|
|
@ -400,9 +408,8 @@ public class KSeFAuthManager {
|
||||||
logger.info("├── 🛃 KSeF API auth ksef-token...");
|
logger.info("├── 🛃 KSeF API auth ksef-token...");
|
||||||
String uri = baseURI + "/auth/ksef-token";
|
String uri = baseURI + "/auth/ksef-token";
|
||||||
|
|
||||||
if (debug) {
|
logger.info("│ ├── POST: " + uri);
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
|
||||||
}
|
|
||||||
HttpResponse<String> response = null;
|
HttpResponse<String> response = null;
|
||||||
try {
|
try {
|
||||||
String jsonPayload = String.format(
|
String jsonPayload = String.format(
|
||||||
|
|
@ -429,8 +436,8 @@ public class KSeFAuthManager {
|
||||||
"API Error: Unable to request ksef-token");
|
"API Error: Unable to request ksef-token");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
if (debug) {
|
if (debug) {
|
||||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
|
||||||
logger.info("Response: " + response.body());
|
logger.info("Response: " + response.body());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -463,6 +470,113 @@ public class KSeFAuthManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wartet auf den Korrekten Auth Status...
|
||||||
|
*
|
||||||
|
* @throws PluginException
|
||||||
|
*/
|
||||||
|
public void waitForAuthStatus() throws PluginException {
|
||||||
|
logger.info("├── 🔜 Waiting for Auth status 200...");
|
||||||
|
|
||||||
|
// Now we need to wait until auth/{AuthRef} will return 200
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
long timeout = 10_000; // 10 Sekunden
|
||||||
|
|
||||||
|
while (System.currentTimeMillis() - start < timeout) {
|
||||||
|
if (this.checkTokenStatus() == 200) {
|
||||||
|
break; // Erfolg
|
||||||
|
}
|
||||||
|
// kleine Pause, um CPU zu schonen
|
||||||
|
waitSomeTime(200);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: prüfen, ob Timeout erreicht wurde
|
||||||
|
if (System.currentTimeMillis() - start >= timeout) {
|
||||||
|
logger.info("├── ⚠️ Timeout reached after 10 seconds!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* waits some time..
|
||||||
|
*/
|
||||||
|
private void waitSomeTime(long millis) {
|
||||||
|
logger.info("│ ├── waiting " + millis + "ms....");
|
||||||
|
try {
|
||||||
|
Thread.sleep(millis);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redeems the KSeF token and returns a session access token to be used to open
|
||||||
|
* an Interactive Session
|
||||||
|
*
|
||||||
|
* POST /auth/token/redeem
|
||||||
|
*
|
||||||
|
* @return JsonObject with referenceNumber and sessionToken
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Lock(LockType.WRITE)
|
||||||
|
public JsonObject redeemToken() throws PluginException {
|
||||||
|
|
||||||
|
logger.info("├── 🛂 KSeF API redeem token...");
|
||||||
|
String uri = baseURI + "/auth/token/redeem";
|
||||||
|
|
||||||
|
logger.info("│ ├── POST: " + uri);
|
||||||
|
|
||||||
|
HttpResponse<String> response = null;
|
||||||
|
try {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(uri))
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + authToken)
|
||||||
|
.POST(HttpRequest.BodyPublishers.noBody())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
response = httpClient.send(request,
|
||||||
|
HttpResponse.BodyHandlers.ofString());
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
|
"API Error - unable to redeemToken: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
|
if (debug) {
|
||||||
|
logger.info("│ ├── Response: " + response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode() >= 400) {
|
||||||
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(),
|
||||||
|
ERROR_API,
|
||||||
|
"Token redeem failed: " + response.statusCode()
|
||||||
|
+ " - " + response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||||
|
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||||
|
|
||||||
|
// Access Token extrahieren und speichern
|
||||||
|
JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken");
|
||||||
|
this.accessToken = accessTokenObj.getString("token");
|
||||||
|
String validUntil = accessTokenObj.getString("validUntil");
|
||||||
|
|
||||||
|
logger.info("│ ├── ✓ Access Token received");
|
||||||
|
logger.info("│ └── ✓ validUntil: " + validUntil);
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper Method to open an interactive session. The method fetches a
|
* Helper Method to open an interactive session. The method fetches a
|
||||||
* sessionRefNumber which is mandatory to upload an invoice.
|
* sessionRefNumber which is mandatory to upload an invoice.
|
||||||
|
|
@ -479,9 +593,8 @@ public class KSeFAuthManager {
|
||||||
|
|
||||||
logger.info("├── 🔐 KSeF API open interactive session...");
|
logger.info("├── 🔐 KSeF API open interactive session...");
|
||||||
String uri = baseURI + "/sessions/online";
|
String uri = baseURI + "/sessions/online";
|
||||||
if (debug) {
|
logger.info("│ ├── POST: " + uri);
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
|
||||||
}
|
|
||||||
HttpResponse<String> response = null;
|
HttpResponse<String> response = null;
|
||||||
try {
|
try {
|
||||||
// --- AES Schlüssel erzeugen ---
|
// --- AES Schlüssel erzeugen ---
|
||||||
|
|
@ -529,8 +642,9 @@ public class KSeFAuthManager {
|
||||||
|
|
||||||
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
if (debug) {
|
if (debug) {
|
||||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
|
||||||
logger.info("Response: " + response.body());
|
logger.info("Response: " + response.body());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -546,8 +660,8 @@ public class KSeFAuthManager {
|
||||||
sessionRefNumber = jsonObject.getString("referenceNumber");
|
sessionRefNumber = jsonObject.getString("referenceNumber");
|
||||||
sessionValidUntil = jsonObject.getString("validUntil");
|
sessionValidUntil = jsonObject.getString("validUntil");
|
||||||
if (true) {
|
if (true) {
|
||||||
logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber);
|
logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber);
|
||||||
logger.info("│ └── Session ValidUntil = " + sessionValidUntil);
|
logger.info("│ └── Session ValidUntil = " + sessionValidUntil);
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT: Store the AES key - this key is mandatory to upload a invoice
|
// IMPORTANT: Store the AES key - this key is mandatory to upload a invoice
|
||||||
|
|
@ -566,6 +680,14 @@ public class KSeFAuthManager {
|
||||||
"API Error openInteractiveSession: " + e.getMessage());
|
"API Error openInteractiveSession: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logger.info("│ ├── Sleep 5 sec...");
|
||||||
|
// try {
|
||||||
|
// Thread.sleep(5000);
|
||||||
|
// } catch (InterruptedException e) {
|
||||||
|
// Thread.currentThread().interrupt();
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -598,8 +720,6 @@ public class KSeFAuthManager {
|
||||||
|
|
||||||
long timestampMillis = instant.toEpochMilli();
|
long timestampMillis = instant.toEpochMilli();
|
||||||
|
|
||||||
logger.info("│ ├── Timestamp (ms): " + timestampMillis);
|
|
||||||
|
|
||||||
// 2. Format erstellen: token|timestamp
|
// 2. Format erstellen: token|timestamp
|
||||||
String plaintext = ksefToken.get() + "|" + timestampMillis;
|
String plaintext = ksefToken.get() + "|" + timestampMillis;
|
||||||
|
|
||||||
|
|
@ -634,71 +754,6 @@ public class KSeFAuthManager {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Redeems the KSeF token and returns a session access token to be used to open
|
|
||||||
* an Interactive Session
|
|
||||||
*
|
|
||||||
* POST /auth/token/redeem
|
|
||||||
*
|
|
||||||
* @return JsonObject with referenceNumber and sessionToken
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
@Lock(LockType.WRITE)
|
|
||||||
public JsonObject redeemToken() throws PluginException {
|
|
||||||
|
|
||||||
logger.info("├── 🛂 KSeF API redeem token...");
|
|
||||||
String uri = baseURI + "/auth/token/redeem";
|
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
|
||||||
}
|
|
||||||
HttpResponse<String> response = null;
|
|
||||||
try {
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(uri))
|
|
||||||
.header("Accept", "application/json")
|
|
||||||
.header("Authorization", "Bearer " + authToken)
|
|
||||||
.POST(HttpRequest.BodyPublishers.noBody())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
response = httpClient.send(request,
|
|
||||||
HttpResponse.BodyHandlers.ofString());
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
|
||||||
"API Error - unable to redeemToken: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
|
||||||
logger.info("│ ├── Response: " + response.body());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode() >= 400) {
|
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(),
|
|
||||||
ERROR_API,
|
|
||||||
"Token redeem failed: " + response.statusCode()
|
|
||||||
+ " - " + response.body());
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
|
||||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
|
||||||
|
|
||||||
// Access Token extrahieren und speichern
|
|
||||||
JsonObject accessTokenObj = jsonObject.getJsonObject("accessToken");
|
|
||||||
this.accessToken = accessTokenObj.getString("token");
|
|
||||||
|
|
||||||
logger.info("│ └── ☑️ Access Token received");
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PublicKey extractPublicKeyFromCertificate(String base64Cert) throws Exception {
|
private PublicKey extractPublicKeyFromCertificate(String base64Cert) throws Exception {
|
||||||
byte[] der = java.util.Base64.getDecoder().decode(base64Cert);
|
byte[] der = java.util.Base64.getDecoder().decode(base64Cert);
|
||||||
java.security.cert.CertificateFactory factory = java.security.cert.CertificateFactory.getInstance("X.509");
|
java.security.cert.CertificateFactory factory = java.security.cert.CertificateFactory.getInstance("X.509");
|
||||||
|
|
@ -714,15 +769,15 @@ public class KSeFAuthManager {
|
||||||
*
|
*
|
||||||
* https://ksef-test.mf.gov.pl/docs/v2/index.html#tag/Uzyskiwanie-dostepu/paths/~1api~1v2~1auth~1%7BreferenceNumber%7D/get
|
* https://ksef-test.mf.gov.pl/docs/v2/index.html#tag/Uzyskiwanie-dostepu/paths/~1api~1v2~1auth~1%7BreferenceNumber%7D/get
|
||||||
*
|
*
|
||||||
|
* @return HTTP Response Code
|
||||||
* @throws PluginException
|
* @throws PluginException
|
||||||
*/
|
*/
|
||||||
public void checkTokenStatus() throws PluginException {
|
public int checkTokenStatus() throws PluginException {
|
||||||
logger.info("├── 🌡️ KSeF API check auth status...");
|
logger.info("├── ↩️ KSeF API check auth status...");
|
||||||
String uri = baseURI + "/auth/" + authRefNumber;
|
String uri = baseURI + "/auth/" + authRefNumber;
|
||||||
|
int httpResponse = -1;
|
||||||
|
|
||||||
if (debug) {
|
logger.info("│ ├── GET: " + uri);
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
|
||||||
}
|
|
||||||
HttpResponse<String> response = null;
|
HttpResponse<String> response = null;
|
||||||
try {
|
try {
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
|
@ -737,10 +792,11 @@ public class KSeFAuthManager {
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
"API Error: Unable to request auth status: " + e.getMessage());
|
"API Error: Unable to request auth status: " + e.getMessage());
|
||||||
}
|
}
|
||||||
if (true) {
|
|
||||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
httpResponse = response.statusCode();
|
||||||
logger.info("Response: " + response.body());
|
logger.info("│ ├── HTTP Response: " + httpResponse);
|
||||||
}
|
|
||||||
|
logger.info("│ ├── " + response.body());
|
||||||
|
|
||||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||||
|
|
@ -767,6 +823,163 @@ public class KSeFAuthManager {
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
"Error parsing JSON response: " + e.getMessage());
|
"Error parsing JSON response: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return httpResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns all active sessions
|
||||||
|
*
|
||||||
|
* https://ksef-test.mf.gov.pl/docs/v2/index.html#tag/Aktywne-sesje/paths/~1api~1v2~1auth~1sessions/get
|
||||||
|
*
|
||||||
|
* @throws PluginException
|
||||||
|
*/
|
||||||
|
public JsonObject getActiveSessions() throws PluginException {
|
||||||
|
logger.info("├── 🚸 KSeF API active sessions...");
|
||||||
|
String uri = baseURI + "/auth/sessions";
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
logger.info("│ ├── GET: " + uri);
|
||||||
|
}
|
||||||
|
HttpResponse<String> response = null;
|
||||||
|
try {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(uri))
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
|
"API Error: Unable to request auth status: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true) {
|
||||||
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
|
// logger.info("Response: " + response.body());
|
||||||
|
}
|
||||||
|
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||||
|
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||||
|
return jsonObject;
|
||||||
|
|
||||||
|
} 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all old sessions
|
||||||
|
*
|
||||||
|
* The method first asks for old sessions and if we have more then 2 sessions we
|
||||||
|
* delete the deprecated ones.
|
||||||
|
*
|
||||||
|
* @param sessionData
|
||||||
|
* @throws PluginException
|
||||||
|
*/
|
||||||
|
public void deleteDeprecatedSessions() throws PluginException {
|
||||||
|
int maxIterations = 10; // Sicherheits-Begrenzung
|
||||||
|
|
||||||
|
for (int iteration = 0; iteration < maxIterations; iteration++) {
|
||||||
|
JsonObject currentSessions = this.getActiveSessions();
|
||||||
|
|
||||||
|
// Get list of refNumbers
|
||||||
|
List<String> referenceNumbers = new ArrayList<>();
|
||||||
|
|
||||||
|
JsonArray items = currentSessions.getJsonArray("items");
|
||||||
|
for (int i = 0; i < items.size(); i++) {
|
||||||
|
JsonObject item = items.getJsonObject(i);
|
||||||
|
referenceNumbers.add(item.getString("referenceNumber"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn nur noch eine Session da ist, abbrechen
|
||||||
|
if (referenceNumbers.size() <= 1) {
|
||||||
|
if (iteration == 0) {
|
||||||
|
logger.info("│ ├── Only one session found, no deprecated sessions");
|
||||||
|
} else {
|
||||||
|
logger.info("├── 🎉 All deprecated sessions deleted after " + iteration + " iterations");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete old sessions....
|
||||||
|
logger.info("├── 🚮 KSeF API delete deprecated sessions (iteration " + (iteration + 1) + ")...");
|
||||||
|
String uri = baseURI + "/auth/sessions/";
|
||||||
|
|
||||||
|
// Alle Einträge ab Index 1 (also ab dem zweiten Eintrag) durchgehen
|
||||||
|
for (int i = 1; i < referenceNumbers.size(); i++) {
|
||||||
|
String referenceNumber = referenceNumbers.get(i);
|
||||||
|
logger.info("│ ├── delete deprecated session: " + referenceNumber);
|
||||||
|
|
||||||
|
HttpResponse<String> response = null;
|
||||||
|
try {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(uri + referenceNumber))
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
|
.DELETE()
|
||||||
|
.build();
|
||||||
|
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
|
"API Error: Unable to request auth status: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("│ ├── Deleted " + (referenceNumbers.size() - 1) + " sessions in this iteration");
|
||||||
|
|
||||||
|
// Kurze Pause vor der nächsten Iteration
|
||||||
|
if (referenceNumbers.size() > 1 && iteration < maxIterations - 1) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn wir die maximale Iteration erreicht haben aber immer noch Sessions da
|
||||||
|
// sind
|
||||||
|
if (iteration == maxIterations - 1 && referenceNumbers.size() > 1) {
|
||||||
|
logger.warning("├── ⚠️ Maximum iterations reached, but " + (referenceNumbers.size() - 1)
|
||||||
|
+ " sessions still remain");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the current session
|
||||||
|
*
|
||||||
|
* @throws PluginException
|
||||||
|
*/
|
||||||
|
public void deleteCurrentSession() throws PluginException {
|
||||||
|
|
||||||
|
logger.info("├── 🚮 KSeF API delete current session...");
|
||||||
|
String uri = baseURI + "/auth/sessions/current";
|
||||||
|
|
||||||
|
HttpResponse<String> response = null;
|
||||||
|
try {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(uri))
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
|
.DELETE()
|
||||||
|
.build();
|
||||||
|
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
|
"API Error: Unable to request auth status: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -785,9 +998,7 @@ public class KSeFAuthManager {
|
||||||
// https://ksef-test.mf.gov.pl/api/v2/sessions/online/{referenceNumber}/close
|
// https://ksef-test.mf.gov.pl/api/v2/sessions/online/{referenceNumber}/close
|
||||||
String uri = baseURI + "/sessions/online/" + sessionRefNumber + "/close";
|
String uri = baseURI + "/sessions/online/" + sessionRefNumber + "/close";
|
||||||
|
|
||||||
if (debug) {
|
logger.info("│ ├── POST: " + uri);
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionRefNumber == null || sessionRefNumber.isEmpty()) {
|
if (sessionRefNumber == null || sessionRefNumber.isEmpty()) {
|
||||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||||
|
|
@ -809,7 +1020,7 @@ public class KSeFAuthManager {
|
||||||
"API Error: Unable to request auth status: " + e.getMessage());
|
"API Error: Unable to request auth status: " + e.getMessage());
|
||||||
}
|
}
|
||||||
if (true) {
|
if (true) {
|
||||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||||
logger.info("Response: " + response.body());
|
logger.info("Response: " + response.body());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import java.util.logging.Logger;
|
||||||
import org.imixs.workflow.FileData;
|
import org.imixs.workflow.FileData;
|
||||||
import org.imixs.workflow.ItemCollection;
|
import org.imixs.workflow.ItemCollection;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
import org.junit.jupiter.api.Order;
|
import org.junit.jupiter.api.Order;
|
||||||
|
|
@ -24,7 +23,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
import com.alexanderlogistics.TestLoggerConfig;
|
import com.alexanderlogistics.TestLoggerConfig;
|
||||||
|
|
||||||
@Disabled
|
//@Disabled
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class KSeFAPIServiceTest {
|
public class KSeFAPIServiceTest {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
import org.junit.jupiter.api.Order;
|
import org.junit.jupiter.api.Order;
|
||||||
|
|
@ -27,155 +26,160 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
@Disabled
|
import com.alexanderlogistics.TestLoggerConfig;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class KSeFAuthManagerTest {
|
public class KSeFAuthManagerTest {
|
||||||
|
|
||||||
private static Logger logger = Logger.getLogger(KSeFAuthManagerTest.class.getName());
|
private static Logger logger = Logger.getLogger(KSeFAuthManagerTest.class.getName());
|
||||||
|
|
||||||
private KSeFAuthManager manager;
|
private KSeFAuthManager manager;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setup() throws Exception {
|
void setup() throws Exception {
|
||||||
manager = new KSeFAuthManager();
|
TestLoggerConfig.setupTestLogger();
|
||||||
|
manager = new KSeFAuthManager();
|
||||||
|
|
||||||
// Test config
|
// Test config
|
||||||
manager.setKsefToken(Params.TOKEN);
|
manager.setKsefToken(Params.TOKEN);
|
||||||
manager.setKsefNip(Params.NIP);
|
manager.setKsefNip(Params.NIP);
|
||||||
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
||||||
manager.setDebug(false);
|
manager.setDebug(false);
|
||||||
|
|
||||||
// init() ausführen
|
// init() ausführen
|
||||||
manager.init();
|
manager.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// 1) Challenge abholen
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
@DisplayName("POST /api/v2/auth/token/redeem")
|
||||||
|
void testRedeemToken() throws Exception {
|
||||||
|
|
||||||
|
manager.loadPublicKeyCertificates();
|
||||||
|
manager.authChallenge();
|
||||||
|
manager.authKSeFToken();
|
||||||
|
manager.redeemToken();
|
||||||
|
|
||||||
|
assertNotNull(
|
||||||
|
manager.getAuthRefNumber(),
|
||||||
|
"RefNumber darf nicht NULL sein");
|
||||||
|
|
||||||
|
assertNotNull(
|
||||||
|
manager.getAuthToken(),
|
||||||
|
"AuthToken darf nicht leer sein");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
@DisplayName("Upload Invoice XML (AES-256 + RSA)")
|
||||||
|
public void testUploadInvoice() throws Exception {
|
||||||
|
|
||||||
|
// ---------- 1) Vorbereitung ----------
|
||||||
|
// API initialisieren
|
||||||
|
manager.loadPublicKeyCertificates();
|
||||||
|
manager.authChallenge();
|
||||||
|
manager.authKSeFToken();
|
||||||
|
|
||||||
|
manager.waitForAuthStatus();
|
||||||
|
|
||||||
|
manager.redeemToken();
|
||||||
|
|
||||||
|
manager.openInteractiveSession();
|
||||||
|
|
||||||
|
assertNotNull(manager.getAccessToken(), "AccessToken fehlt");
|
||||||
|
// assertNotNull(manager.getRefNumber(), "ReferenceNumber fehlt");
|
||||||
|
assertNotNull(manager.getSessionRefNumber(), "SessionReferenceNumber fehlt");
|
||||||
|
|
||||||
|
// ---------- 2) XML laden ----------
|
||||||
|
Path xmlPath = Paths.get("src/test/resources/ksef/example-invoice-01.xml");
|
||||||
|
byte[] invoiceXml = Files.readAllBytes(xmlPath);
|
||||||
|
|
||||||
|
// ---------- 3) AES Key generieren ----------
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
byte[] aesKeyBytes = new byte[32]; // 256-bit AES key
|
||||||
|
random.nextBytes(aesKeyBytes);
|
||||||
|
|
||||||
|
byte[] ivBytes = new byte[16]; // 128-bit IV
|
||||||
|
random.nextBytes(ivBytes);
|
||||||
|
|
||||||
|
SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
||||||
|
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||||
|
|
||||||
|
// ---------- 4) XML per AES-256-CBC verschlüsseln ----------
|
||||||
|
javax.crypto.Cipher aesCipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
aesCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, iv);
|
||||||
|
|
||||||
|
byte[] encryptedInvoiceXml = aesCipher.doFinal(invoiceXml);
|
||||||
|
|
||||||
|
// ---------- 5) AES Key per RSA verschlüsseln ----------
|
||||||
|
PublicKey ksefPublicKey = manager.getSymmetricPublicKey();
|
||||||
|
|
||||||
|
javax.crypto.Cipher rsaCipher = javax.crypto.Cipher
|
||||||
|
.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||||
|
rsaCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, ksefPublicKey);
|
||||||
|
|
||||||
|
byte[] encryptedAesKey = rsaCipher.doFinal(aesKeyBytes);
|
||||||
|
|
||||||
|
// ---------- 6) SHA-256 Hash des Klartext-XML berechnen ----------
|
||||||
|
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||||
|
|
||||||
|
// hash origin invoice
|
||||||
|
byte[] xmlHashBytes = sha256.digest(invoiceXml);
|
||||||
|
String invoiceHash = Base64.getEncoder().encodeToString(xmlHashBytes);
|
||||||
|
|
||||||
|
// hash encrypted invoice
|
||||||
|
xmlHashBytes = sha256.digest(encryptedInvoiceXml);
|
||||||
|
String encryptedInvoiceHash = Base64.getEncoder().encodeToString(xmlHashBytes);
|
||||||
|
|
||||||
|
// ---------- 7) Request-Body bauen ----------
|
||||||
|
// Struktur gemäß KSeF-Doku
|
||||||
|
String jsonBody = String.format("{ " +
|
||||||
|
"\"invoiceHash\": \"%s\", " +
|
||||||
|
"\"invoiceSize\": \"%s\", " +
|
||||||
|
"\"encryptedInvoiceHash\": \"%s\", " +
|
||||||
|
"\"encryptedInvoiceSize\": \"%s\", " +
|
||||||
|
"\"encryptedInvoiceContent\": \"%s\", " +
|
||||||
|
"\"offlineMode\": false " +
|
||||||
|
"}",
|
||||||
|
invoiceHash,
|
||||||
|
invoiceXml.length,
|
||||||
|
encryptedInvoiceHash,
|
||||||
|
encryptedInvoiceXml.length,
|
||||||
|
Base64.getEncoder().encodeToString(encryptedInvoiceXml));
|
||||||
|
|
||||||
|
// ---------- 8) HTTP POST vorbereiten ----------
|
||||||
|
// String uri = manager.getBaseURI() + "/sessions/online/" +
|
||||||
|
// manager.getRefNumber() + "/invoices";
|
||||||
|
String uri = manager.getBaseURI() + "/sessions/online/" + manager.getSessionRefNumber() + "/invoices";
|
||||||
|
logger.info("│ ├── Endpoint: " + uri);
|
||||||
|
|
||||||
|
if (manager.isDebug()) {
|
||||||
|
logger.info("Request: " + jsonBody);
|
||||||
}
|
}
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(uri))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + manager.getAccessToken())
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||||
|
.build();
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ---------- 9) Call ausführen ----------
|
||||||
// 1) Challenge abholen
|
var response = manager.getHttpClient().send(request,
|
||||||
// ------------------------------------------------------------
|
java.net.http.HttpResponse.BodyHandlers.ofString());
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
@DisplayName("POST /api/v2/auth/token/redeem")
|
|
||||||
void testRedeemToken() throws Exception {
|
|
||||||
|
|
||||||
manager.loadPublicKeyCertificates();
|
System.out.println("Upload Response:");
|
||||||
manager.authChallenge();
|
System.out.println(response.statusCode());
|
||||||
manager.authKSeFToken();
|
System.out.println(response.body());
|
||||||
manager.redeemToken();
|
|
||||||
|
|
||||||
assertNotNull(
|
// ---------- 10) Basic Assertions ----------
|
||||||
manager.getAuthRefNumber(),
|
assertTrue(response.statusCode() == 202,
|
||||||
"RefNumber darf nicht NULL sein");
|
"Upload muss erfolgreich sein");
|
||||||
|
}
|
||||||
assertNotNull(
|
|
||||||
manager.getAuthToken(),
|
|
||||||
"AuthToken darf nicht leer sein");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(4)
|
|
||||||
@DisplayName("Upload Invoice XML (AES-256 + RSA)")
|
|
||||||
public void testUploadInvoice() throws Exception {
|
|
||||||
|
|
||||||
// ---------- 1) Vorbereitung ----------
|
|
||||||
// API initialisieren
|
|
||||||
manager.loadPublicKeyCertificates();
|
|
||||||
manager.authChallenge();
|
|
||||||
manager.authKSeFToken();
|
|
||||||
manager.redeemToken();
|
|
||||||
|
|
||||||
manager.openInteractiveSession();
|
|
||||||
|
|
||||||
assertNotNull(manager.getAccessToken(), "AccessToken fehlt");
|
|
||||||
// assertNotNull(manager.getRefNumber(), "ReferenceNumber fehlt");
|
|
||||||
assertNotNull(manager.getSessionRefNumber(), "SessionReferenceNumber fehlt");
|
|
||||||
|
|
||||||
// ---------- 2) XML laden ----------
|
|
||||||
Path xmlPath = Paths.get("src/test/resources/ksef/example-invoice-01.xml");
|
|
||||||
byte[] invoiceXml = Files.readAllBytes(xmlPath);
|
|
||||||
|
|
||||||
// ---------- 3) AES Key generieren ----------
|
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
|
|
||||||
byte[] aesKeyBytes = new byte[32]; // 256-bit AES key
|
|
||||||
random.nextBytes(aesKeyBytes);
|
|
||||||
|
|
||||||
byte[] ivBytes = new byte[16]; // 128-bit IV
|
|
||||||
random.nextBytes(ivBytes);
|
|
||||||
|
|
||||||
SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
|
||||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
|
||||||
|
|
||||||
// ---------- 4) XML per AES-256-CBC verschlüsseln ----------
|
|
||||||
javax.crypto.Cipher aesCipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
aesCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, iv);
|
|
||||||
|
|
||||||
byte[] encryptedInvoiceXml = aesCipher.doFinal(invoiceXml);
|
|
||||||
|
|
||||||
// ---------- 5) AES Key per RSA verschlüsseln ----------
|
|
||||||
PublicKey ksefPublicKey = manager.getSymmetricPublicKey();
|
|
||||||
|
|
||||||
javax.crypto.Cipher rsaCipher = javax.crypto.Cipher
|
|
||||||
.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
|
||||||
rsaCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, ksefPublicKey);
|
|
||||||
|
|
||||||
byte[] encryptedAesKey = rsaCipher.doFinal(aesKeyBytes);
|
|
||||||
|
|
||||||
// ---------- 6) SHA-256 Hash des Klartext-XML berechnen ----------
|
|
||||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
|
||||||
|
|
||||||
// hash origin invoice
|
|
||||||
byte[] xmlHashBytes = sha256.digest(invoiceXml);
|
|
||||||
String invoiceHash = Base64.getEncoder().encodeToString(xmlHashBytes);
|
|
||||||
|
|
||||||
// hash encrypted invoice
|
|
||||||
xmlHashBytes = sha256.digest(encryptedInvoiceXml);
|
|
||||||
String encryptedInvoiceHash = Base64.getEncoder().encodeToString(xmlHashBytes);
|
|
||||||
|
|
||||||
// ---------- 7) Request-Body bauen ----------
|
|
||||||
// Struktur gemäß KSeF-Doku
|
|
||||||
String jsonBody = String.format("{ " +
|
|
||||||
"\"invoiceHash\": \"%s\", " +
|
|
||||||
"\"invoiceSize\": \"%s\", " +
|
|
||||||
"\"encryptedInvoiceHash\": \"%s\", " +
|
|
||||||
"\"encryptedInvoiceSize\": \"%s\", " +
|
|
||||||
"\"encryptedInvoiceContent\": \"%s\", " +
|
|
||||||
"\"offlineMode\": false " +
|
|
||||||
"}",
|
|
||||||
invoiceHash,
|
|
||||||
invoiceXml.length,
|
|
||||||
encryptedInvoiceHash,
|
|
||||||
encryptedInvoiceXml.length,
|
|
||||||
Base64.getEncoder().encodeToString(encryptedInvoiceXml));
|
|
||||||
|
|
||||||
// ---------- 8) HTTP POST vorbereiten ----------
|
|
||||||
// String uri = manager.getBaseURI() + "/sessions/online/" +
|
|
||||||
// manager.getRefNumber() + "/invoices";
|
|
||||||
String uri = manager.getBaseURI() + "/sessions/online/" + manager.getSessionRefNumber() + "/invoices";
|
|
||||||
logger.info("│ ├── Endpoint: " + uri);
|
|
||||||
|
|
||||||
if (manager.isDebug()) {
|
|
||||||
logger.info("Request: " + jsonBody);
|
|
||||||
}
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(uri))
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.header("Authorization", "Bearer " + manager.getAccessToken())
|
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// ---------- 9) Call ausführen ----------
|
|
||||||
var response = manager.getHttpClient().send(request,
|
|
||||||
java.net.http.HttpResponse.BodyHandlers.ofString());
|
|
||||||
|
|
||||||
System.out.println("Upload Response:");
|
|
||||||
System.out.println(response.statusCode());
|
|
||||||
System.out.println(response.body());
|
|
||||||
|
|
||||||
// ---------- 10) Basic Assertions ----------
|
|
||||||
assertTrue(response.statusCode() == 202,
|
|
||||||
"Upload muss erfolgreich sein");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue