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.exceptions.PluginException;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.security.DeclareRoles;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.annotation.security.RunAs;
|
||||
|
|
@ -60,11 +59,6 @@ public class KSeFAPIService {
|
|||
@Inject
|
||||
DocumentService documentService;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method uploads an KSeF Invoice document (XML).
|
||||
* 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);
|
||||
|
||||
// ---------- 4) XML per AES-256-CBC verschlüsseln ----------
|
||||
javax.crypto.Cipher aesCipher;
|
||||
|
||||
aesCipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
|
||||
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);
|
||||
|
|
@ -198,6 +189,9 @@ public class KSeFAPIService {
|
|||
|
||||
} catch (Exception e) {
|
||||
logger.severe("├── ⚠️ Error parsing JSON response" + e.getMessage());
|
||||
|
||||
kseFAuthManager.deleteCurrentSession();
|
||||
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Error parsing JSON response: " + e.getMessage());
|
||||
|
||||
|
|
@ -220,6 +214,8 @@ public class KSeFAPIService {
|
|||
fileData.setAttribute("ksef.referenceNumber", textlist);
|
||||
|
||||
kseFAuthManager.closeSession();
|
||||
kseFAuthManager.deleteCurrentSession();
|
||||
|
||||
return referenceNumber;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ import java.security.spec.MGF1ParameterSpec;
|
|||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
|
@ -36,6 +38,7 @@ import jakarta.ejb.Lock;
|
|||
import jakarta.ejb.LockType;
|
||||
import jakarta.ejb.Singleton;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.json.JsonArray;
|
||||
import jakarta.json.JsonObject;
|
||||
import jakarta.json.bind.Jsonb;
|
||||
import jakarta.json.bind.JsonbBuilder;
|
||||
|
|
@ -195,14 +198,22 @@ public class KSeFAuthManager {
|
|||
logger.info("│ ├── open new interactive KSeF Session...");
|
||||
// open new session
|
||||
this.loadPublicKeyCertificates();
|
||||
|
||||
this.authChallenge();
|
||||
this.authKSeFToken();
|
||||
this.waitSomeTime(3000);
|
||||
|
||||
// Jetzt Status abwarten
|
||||
this.waitForAuthStatus();
|
||||
|
||||
waitSomeTime(5000);
|
||||
this.redeemToken();
|
||||
|
||||
this.openInteractiveSession();
|
||||
// Delete deprecated sessions
|
||||
this.deleteDeprecatedSessions();
|
||||
|
||||
this.checkTokenStatus();
|
||||
|
||||
this.redeemToken();
|
||||
this.openInteractiveSession();
|
||||
} else {
|
||||
logger.warning("│ ├── ! Session already exists - reuse existing KSeF Session...");
|
||||
}
|
||||
|
|
@ -262,10 +273,8 @@ public class KSeFAuthManager {
|
|||
|
||||
logger.info("├── ♻️ KSeF API load public-key-certificates...");
|
||||
String uri = baseURI + "/security/public-key-certificates";
|
||||
logger.info("│ ├── GET: " + uri);
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
}
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
.header("Accept", "application/json")
|
||||
|
|
@ -279,9 +288,9 @@ public class KSeFAuthManager {
|
|||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Unable to send request for KSeF certificates endpoint: " + e.getMessage());
|
||||
}
|
||||
|
||||
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||
if (debug) {
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
|
||||
logger.info("Response: " + response.body());
|
||||
}
|
||||
|
||||
|
|
@ -342,9 +351,7 @@ public class KSeFAuthManager {
|
|||
logger.info("├── 🛅 KSeF API Challenge...");
|
||||
String uri = baseURI + "/auth/challenge";
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
}
|
||||
logger.info("│ ├── POST: " + uri);
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri)) // nur Endpoint checken
|
||||
.header("Accept", "application/json")
|
||||
|
|
@ -354,8 +361,9 @@ public class KSeFAuthManager {
|
|||
HttpResponse<String> response;
|
||||
try {
|
||||
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||
if (debug) {
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
|
||||
logger.info("Response: " + response.body());
|
||||
}
|
||||
// Prüfen, dass wir eine API-Antwort bekommen, kein HTML
|
||||
|
|
@ -400,9 +408,8 @@ public class KSeFAuthManager {
|
|||
logger.info("├── 🛃 KSeF API auth ksef-token...");
|
||||
String uri = baseURI + "/auth/ksef-token";
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
}
|
||||
logger.info("│ ├── POST: " + uri);
|
||||
|
||||
HttpResponse<String> response = null;
|
||||
try {
|
||||
String jsonPayload = String.format(
|
||||
|
|
@ -429,8 +436,8 @@ public class KSeFAuthManager {
|
|||
"API Error: Unable to request ksef-token");
|
||||
}
|
||||
|
||||
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||
if (debug) {
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
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
|
||||
* sessionRefNumber which is mandatory to upload an invoice.
|
||||
|
|
@ -479,9 +593,8 @@ public class KSeFAuthManager {
|
|||
|
||||
logger.info("├── 🔐 KSeF API open interactive session...");
|
||||
String uri = baseURI + "/sessions/online";
|
||||
if (debug) {
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
}
|
||||
logger.info("│ ├── POST: " + uri);
|
||||
|
||||
HttpResponse<String> response = null;
|
||||
try {
|
||||
// --- AES Schlüssel erzeugen ---
|
||||
|
|
@ -529,8 +642,9 @@ public class KSeFAuthManager {
|
|||
|
||||
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||
if (debug) {
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
|
||||
logger.info("Response: " + response.body());
|
||||
}
|
||||
|
||||
|
|
@ -546,8 +660,8 @@ public class KSeFAuthManager {
|
|||
sessionRefNumber = jsonObject.getString("referenceNumber");
|
||||
sessionValidUntil = jsonObject.getString("validUntil");
|
||||
if (true) {
|
||||
logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber);
|
||||
logger.info("│ └── Session ValidUntil = " + sessionValidUntil);
|
||||
logger.info("│ ├── Session ReferenceNumber = " + sessionRefNumber);
|
||||
logger.info("│ └── Session ValidUntil = " + sessionValidUntil);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
logger.info("│ ├── Timestamp (ms): " + timestampMillis);
|
||||
|
||||
// 2. Format erstellen: token|timestamp
|
||||
String plaintext = ksefToken.get() + "|" + timestampMillis;
|
||||
|
||||
|
|
@ -634,71 +754,6 @@ public class KSeFAuthManager {
|
|||
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 {
|
||||
byte[] der = java.util.Base64.getDecoder().decode(base64Cert);
|
||||
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
|
||||
*
|
||||
* @return HTTP Response Code
|
||||
* @throws PluginException
|
||||
*/
|
||||
public void checkTokenStatus() throws PluginException {
|
||||
logger.info("├── 🌡️ KSeF API check auth status...");
|
||||
public int checkTokenStatus() throws PluginException {
|
||||
logger.info("├── ↩️ KSeF API check auth status...");
|
||||
String uri = baseURI + "/auth/" + authRefNumber;
|
||||
int httpResponse = -1;
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
}
|
||||
logger.info("│ ├── GET: " + uri);
|
||||
HttpResponse<String> response = null;
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
|
|
@ -737,10 +792,11 @@ public class KSeFAuthManager {
|
|||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"API Error: Unable to request auth status: " + e.getMessage());
|
||||
}
|
||||
if (true) {
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
logger.info("Response: " + response.body());
|
||||
}
|
||||
|
||||
httpResponse = response.statusCode();
|
||||
logger.info("│ ├── HTTP Response: " + httpResponse);
|
||||
|
||||
logger.info("│ ├── " + response.body());
|
||||
|
||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||
|
|
@ -767,6 +823,163 @@ public class KSeFAuthManager {
|
|||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"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
|
||||
String uri = baseURI + "/sessions/online/" + sessionRefNumber + "/close";
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── Endpoint: " + uri);
|
||||
}
|
||||
logger.info("│ ├── POST: " + uri);
|
||||
|
||||
if (sessionRefNumber == null || sessionRefNumber.isEmpty()) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
|
|
@ -809,7 +1020,7 @@ public class KSeFAuthManager {
|
|||
"API Error: Unable to request auth status: " + e.getMessage());
|
||||
}
|
||||
if (true) {
|
||||
logger.info("│ ├── Response Code = " + response.statusCode());
|
||||
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||
logger.info("Response: " + response.body());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import java.util.logging.Logger;
|
|||
import org.imixs.workflow.FileData;
|
||||
import org.imixs.workflow.ItemCollection;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
|
|
@ -24,7 +23,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||
|
||||
import com.alexanderlogistics.TestLoggerConfig;
|
||||
|
||||
@Disabled
|
||||
//@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class KSeFAPIServiceTest {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import javax.crypto.spec.IvParameterSpec;
|
|||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
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.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@Disabled
|
||||
import com.alexanderlogistics.TestLoggerConfig;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
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
|
||||
void setup() throws Exception {
|
||||
manager = new KSeFAuthManager();
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
TestLoggerConfig.setupTestLogger();
|
||||
manager = new KSeFAuthManager();
|
||||
|
||||
// Test config
|
||||
manager.setKsefToken(Params.TOKEN);
|
||||
manager.setKsefNip(Params.NIP);
|
||||
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
||||
manager.setDebug(false);
|
||||
// Test config
|
||||
manager.setKsefToken(Params.TOKEN);
|
||||
manager.setKsefNip(Params.NIP);
|
||||
manager.setKsefEndpoint("https://ksef-test.mf.gov.pl/api/v2");
|
||||
manager.setDebug(false);
|
||||
|
||||
// init() ausführen
|
||||
manager.init();
|
||||
// init() ausführen
|
||||
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();
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 1) Challenge abholen
|
||||
// ------------------------------------------------------------
|
||||
@Test
|
||||
@Order(1)
|
||||
@DisplayName("POST /api/v2/auth/token/redeem")
|
||||
void testRedeemToken() throws Exception {
|
||||
// ---------- 9) Call ausführen ----------
|
||||
var response = manager.getHttpClient().send(request,
|
||||
java.net.http.HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
manager.loadPublicKeyCertificates();
|
||||
manager.authChallenge();
|
||||
manager.authKSeFToken();
|
||||
manager.redeemToken();
|
||||
System.out.println("Upload Response:");
|
||||
System.out.println(response.statusCode());
|
||||
System.out.println(response.body());
|
||||
|
||||
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.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");
|
||||
}
|
||||
// ---------- 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