new impl authManager
This commit is contained in:
parent
6c4e72fe82
commit
1f2d3b4cd5
5 changed files with 216 additions and 76 deletions
|
|
@ -67,6 +67,7 @@ public class KSeFAPIService {
|
|||
|
||||
// First open an interactive session (reuses existing if valid)
|
||||
kseFAuthManager.openSession();
|
||||
|
||||
logger.info("├── 📤 Upload Invoice...");
|
||||
|
||||
// Validate session
|
||||
|
|
@ -177,8 +178,10 @@ public class KSeFAPIService {
|
|||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"Invoice upload failed: " + e.getMessage());
|
||||
} finally {
|
||||
kseFAuthManager.closeSession();
|
||||
kseFAuthManager.deleteCurrentSession();
|
||||
|
||||
// finally we close the session
|
||||
kseFAuthManager.closeInteractiveSession();
|
||||
kseFAuthManager.deleteCurrentAuthSession();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -185,12 +185,15 @@ public class KSeFAuthManager {
|
|||
public void openSession() throws PluginException {
|
||||
logger.info("├── open Session...");
|
||||
if (!hasValidSession()) {
|
||||
|
||||
// first we make sure that we do not have any old sessions!
|
||||
// this.deleteAllAuthSessions();
|
||||
|
||||
logger.info("│ ├── open new interactive KSeF Session...");
|
||||
// open new session
|
||||
this.loadPublicKeyCertificates();
|
||||
this.authChallenge();
|
||||
this.authKSeFToken();
|
||||
// this.waitSomeTime(3000);
|
||||
|
||||
// Jetzt Status abwarten
|
||||
this.waitForAuthStatus();
|
||||
|
|
@ -199,10 +202,8 @@ public class KSeFAuthManager {
|
|||
this.redeemToken();
|
||||
|
||||
this.openInteractiveSession();
|
||||
// Delete deprecated sessions
|
||||
this.deleteDeprecatedSessions();
|
||||
|
||||
this.checkTokenStatus();
|
||||
this.waitForSessionStatus();
|
||||
|
||||
} else {
|
||||
logger.warning("│ ├── ! Session already exists - reuse existing KSeF Session...");
|
||||
|
|
@ -461,7 +462,8 @@ public class KSeFAuthManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Wartet auf den Korrekten Auth Status...
|
||||
* Waits for the auth status code 200 after a new authentication process was
|
||||
* initiated
|
||||
*
|
||||
* @throws PluginException
|
||||
*/
|
||||
|
|
@ -473,11 +475,11 @@ public class KSeFAuthManager {
|
|||
long timeout = 10_000; // 10 Sekunden
|
||||
|
||||
while (System.currentTimeMillis() - start < timeout) {
|
||||
if (this.checkTokenStatus() == 200) {
|
||||
break; // Erfolg
|
||||
if (this.checkAuthStatus() >= 200) {
|
||||
break; // ok or borken
|
||||
}
|
||||
// kleine Pause, um CPU zu schonen
|
||||
waitSomeTime(200);
|
||||
waitSomeTime(500);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -488,6 +490,48 @@ public class KSeFAuthManager {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the session status code 200 after a new interactive session was
|
||||
* initiated
|
||||
*
|
||||
*
|
||||
* @throws PluginException
|
||||
*/
|
||||
public void waitForSessionStatus() throws PluginException {
|
||||
logger.info("├── 🔜 Waiting for Session status 200...");
|
||||
|
||||
// Now we need to wait until auth/{AuthRef} will return 200
|
||||
long start = System.currentTimeMillis();
|
||||
long timeout = 120_000; // 120 Sekunden
|
||||
|
||||
while (System.currentTimeMillis() - start < timeout) {
|
||||
int status = this.checkSessionStatus();
|
||||
|
||||
// Status 100 = Session opened and ready for uploads!
|
||||
if (status == 100 || status == 200) {
|
||||
logger.info("│ └── ✓ Session is ready (status " + status + ")");
|
||||
return; // Session is ready!
|
||||
}
|
||||
|
||||
// Error statuses (400+)
|
||||
if (status >= 400) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"API Error - invalid session status: " + status);
|
||||
}
|
||||
|
||||
// Wait and retry
|
||||
waitSomeTime(500);
|
||||
|
||||
}
|
||||
|
||||
// Optional: prüfen, ob Timeout erreicht wurde
|
||||
if (System.currentTimeMillis() - start >= timeout) {
|
||||
throw new PluginException(KSeFAuthManager.class.getSimpleName(), ERROR_API,
|
||||
"API Error - failed to init new interactive session - ⚠️ Timeout reached after 120 seconds!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* waits some time..
|
||||
*/
|
||||
|
|
@ -738,10 +782,10 @@ 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
|
||||
* @return Auth Status Code
|
||||
* @throws PluginException
|
||||
*/
|
||||
public int checkTokenStatus() throws PluginException {
|
||||
public int checkAuthStatus() throws PluginException {
|
||||
logger.info("├── ↩️ KSeF API check auth status...");
|
||||
String uri = baseURI + "/auth/" + authRefNumber;
|
||||
int httpResponse = -1;
|
||||
|
|
@ -808,19 +852,77 @@ public class KSeFAuthManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* This method returns all active sessions
|
||||
* This method verifies the actual status of the status of an interactive
|
||||
* session
|
||||
*
|
||||
* The method returns the aut status code (not the HTTP Response code!)
|
||||
*
|
||||
* https://ksef-test.mf.gov.pl/docs/v2/index.html#tag/Status-wysylki-i-UPO/paths/~1api~1v2~1sessions~1%7BreferenceNumber%7D/get
|
||||
*
|
||||
* @return Session Status Code
|
||||
* @throws PluginException
|
||||
*/
|
||||
public int checkSessionStatus() throws PluginException {
|
||||
logger.info("├── ↩️ KSeF API check session status...");
|
||||
String uri = baseURI + "/sessions/" + this.sessionRefNumber;
|
||||
int httpResponse = -1;
|
||||
int statusCode = -1;
|
||||
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 " + this.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());
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Status Code aus dem status-Objekt extrahieren
|
||||
if (jsonObject.containsKey("status")) {
|
||||
JsonObject statusObject = jsonObject.getJsonObject("status");
|
||||
if (statusObject.containsKey("code")) {
|
||||
statusCode = statusObject.getInt("code");
|
||||
logger.info("│ ├── Status Code: " + statusCode);
|
||||
logger.info("│ ├── Status Description: " + statusObject.getString("description"));
|
||||
}
|
||||
}
|
||||
|
||||
} 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());
|
||||
}
|
||||
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns all active auth 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 {
|
||||
public JsonObject getActiveAuthSessions() throws PluginException {
|
||||
logger.info("├── 🚸 KSeF API active sessions...");
|
||||
String uri = baseURI + "/auth/sessions";
|
||||
|
||||
if (debug) {
|
||||
logger.info("│ ├── GET: " + uri);
|
||||
}
|
||||
logger.info("│ ├── GET: " + uri);
|
||||
|
||||
HttpResponse<String> response = null;
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
|
|
@ -838,7 +940,7 @@ public class KSeFAuthManager {
|
|||
|
||||
if (true) {
|
||||
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||
// logger.info("Response: " + response.body());
|
||||
logger.info("│ ├── Response: " + response.body());
|
||||
}
|
||||
try (Jsonb jsonb = JsonbBuilder.create()) {
|
||||
JsonObject jsonObject = jsonb.fromJson(response.body(), JsonObject.class);
|
||||
|
|
@ -853,19 +955,52 @@ public class KSeFAuthManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Deletes all old sessions
|
||||
* Deletes the current auth session (authRefNumber)
|
||||
*
|
||||
* The method first asks for old sessions and if we have more then 2 sessions we
|
||||
* delete the deprecated ones.
|
||||
* The method should be called wenn all operations are completed
|
||||
*
|
||||
* @param sessionData
|
||||
* @throws PluginException
|
||||
*/
|
||||
public void deleteDeprecatedSessions() throws PluginException {
|
||||
public void deleteCurrentAuthSession() throws PluginException {
|
||||
|
||||
// delete old sessions....
|
||||
logger.info("├── 🚮 KSeF API delete current auth sessions...");
|
||||
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());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all auth sessions
|
||||
*
|
||||
* The method should be called before openInteractiveSession() to make sure no
|
||||
* old sessions are still active
|
||||
*
|
||||
* @param sessionData
|
||||
* @throws PluginException
|
||||
*/
|
||||
public void deleteAllAuthSessions() throws PluginException {
|
||||
int maxIterations = 10; // Sicherheits-Begrenzung
|
||||
|
||||
for (int iteration = 0; iteration < maxIterations; iteration++) {
|
||||
JsonObject currentSessions = this.getActiveSessions();
|
||||
JsonObject currentSessions = this.getActiveAuthSessions();
|
||||
|
||||
// Get list of refNumbers
|
||||
List<String> referenceNumbers = new ArrayList<>();
|
||||
|
|
@ -877,21 +1012,15 @@ public class KSeFAuthManager {
|
|||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
if (referenceNumbers.size() == 0) {
|
||||
logger.info("├── 🎉 No deprecated sessions found");
|
||||
break;
|
||||
}
|
||||
|
||||
// delete old sessions....
|
||||
logger.info("├── 🚮 KSeF API delete deprecated sessions (iteration " + (iteration + 1) + ")...");
|
||||
logger.info("├── 🚮 KSeF API delete deprecated sessions...");
|
||||
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++) {
|
||||
for (int i = 0; i < referenceNumbers.size(); i++) {
|
||||
String referenceNumber = referenceNumbers.get(i);
|
||||
logger.info("│ ├── delete deprecated session: " + referenceNumber);
|
||||
|
||||
|
|
@ -913,7 +1042,7 @@ public class KSeFAuthManager {
|
|||
logger.info("│ ├── HTTP Response: " + response.statusCode());
|
||||
}
|
||||
|
||||
logger.info("│ ├── Deleted " + (referenceNumbers.size() - 1) + " sessions in this iteration");
|
||||
logger.info("│ ├── Deleted " + referenceNumbers.size() + " sessions in this iteration");
|
||||
|
||||
// Kurze Pause vor der nächsten Iteration
|
||||
if (referenceNumbers.size() > 1 && iteration < maxIterations - 1) {
|
||||
|
|
@ -934,34 +1063,6 @@ public class KSeFAuthManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method closes the current session
|
||||
*
|
||||
|
|
@ -973,7 +1074,7 @@ public class KSeFAuthManager {
|
|||
*
|
||||
* @throws PluginException
|
||||
*/
|
||||
public void closeSession() throws PluginException {
|
||||
public void closeInteractiveSession() throws PluginException {
|
||||
logger.info("├── 📦 KSeF API close session...");
|
||||
// https://ksef-test.mf.gov.pl/api/v2/sessions/online/{referenceNumber}/close
|
||||
String uri = baseURI + "/sessions/online/" + sessionRefNumber + "/close";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.alexanderlogistics.ksef.api;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
|
|
@ -12,6 +14,8 @@ import javax.crypto.IllegalBlockSizeException;
|
|||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.OAEPParameterSpec;
|
||||
import javax.crypto.spec.PSource;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
|
|
@ -21,7 +25,8 @@ import javax.crypto.spec.SecretKeySpec;
|
|||
public class KSeFEncryptionHelper {
|
||||
|
||||
private static final String AES_ALGORITHM = "AES";
|
||||
private static final String RSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
|
||||
private static final String xxxRSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
|
||||
private static final String RSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
|
||||
private static final int AES_KEY_SIZE = 256;
|
||||
private static final int IV_SIZE = 16;
|
||||
|
||||
|
|
@ -61,6 +66,26 @@ public class KSeFEncryptionHelper {
|
|||
* @throws BadPaddingException if padding is incorrect
|
||||
*/
|
||||
public static String encryptAesKey(SecretKey aesKey, PublicKey ksefPublicKey)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException,
|
||||
InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
|
||||
InvalidAlgorithmParameterException { // <- Exception hinzufügen!
|
||||
|
||||
Cipher rsaCipher = Cipher.getInstance(RSA_TRANSFORMATION);
|
||||
|
||||
// WICHTIG: Explizite OAEP-Parameter setzen (wie bei Token-Verschlüsselung!)
|
||||
OAEPParameterSpec oaepParams = new OAEPParameterSpec(
|
||||
"SHA-256",
|
||||
"MGF1",
|
||||
MGF1ParameterSpec.SHA256,
|
||||
PSource.PSpecified.DEFAULT);
|
||||
|
||||
rsaCipher.init(Cipher.ENCRYPT_MODE, ksefPublicKey, oaepParams);
|
||||
|
||||
byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded());
|
||||
return Base64.getEncoder().encodeToString(encryptedKey);
|
||||
}
|
||||
|
||||
private static String OLDencryptAesKey(SecretKey aesKey, PublicKey ksefPublicKey)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException,
|
||||
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||
|
||||
|
|
@ -107,15 +132,16 @@ public class KSeFEncryptionHelper {
|
|||
*
|
||||
* @param ksefPublicKey KSeF's RSA public key
|
||||
* @return SessionEncryption containing all encryption data
|
||||
* @throws NoSuchAlgorithmException if algorithm is not available
|
||||
* @throws NoSuchPaddingException if padding scheme is not available
|
||||
* @throws InvalidKeyException if the public key is invalid
|
||||
* @throws IllegalBlockSizeException if the key size is invalid
|
||||
* @throws BadPaddingException if padding is incorrect
|
||||
* @throws NoSuchAlgorithmException if algorithm is not available
|
||||
* @throws NoSuchPaddingException if padding scheme is not available
|
||||
* @throws InvalidKeyException if the public key is invalid
|
||||
* @throws IllegalBlockSizeException if the key size is invalid
|
||||
* @throws BadPaddingException if padding is incorrect
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
*/
|
||||
public static SessionEncryption createSessionEncryption(PublicKey ksefPublicKey)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException,
|
||||
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||
InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
||||
|
||||
// Generate AES key and IV
|
||||
SecretKey aesKey = generateAesKey();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.alexanderlogistics.ksef.api;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
|
@ -12,6 +13,7 @@ import java.util.logging.Logger;
|
|||
|
||||
import org.imixs.workflow.FileData;
|
||||
import org.imixs.workflow.ItemCollection;
|
||||
import org.imixs.workflow.exceptions.PluginException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -52,15 +54,20 @@ public class KSeFAPIServiceTest {
|
|||
|
||||
@Test
|
||||
@DisplayName("Upload Invoice XML (AES-256 + RSA)")
|
||||
public void testUploadInvoice() throws Exception {
|
||||
public void testUploadInvoice() {
|
||||
logger.info("==> Test: Upload Invoice XML");
|
||||
|
||||
ItemCollection workitem = createWorkitem();
|
||||
|
||||
// Execute upload
|
||||
|
||||
String referenceNumber = apiService.uploadInvoice(workitem, "example-invoice-01.xml");
|
||||
ItemCollection workitem = null;
|
||||
String referenceNumber = null;
|
||||
try {
|
||||
workitem = createWorkitem();
|
||||
|
||||
// Execute upload
|
||||
referenceNumber = apiService.uploadInvoice(workitem, "example-invoice-01.xml");
|
||||
} catch (IOException | PluginException e) {
|
||||
logger.info("⚠️ Upload failed: " + e.getMessage());
|
||||
fail(e);
|
||||
}
|
||||
// Verify result
|
||||
assertNotNull(referenceNumber, "Reference number should not be null");
|
||||
assertFalse(referenceNumber.isEmpty(), "Reference number should not be empty");
|
||||
|
|
@ -70,6 +77,7 @@ public class KSeFAPIServiceTest {
|
|||
FileData file = workitem.getFileData("example-invoice-01.xml");
|
||||
ItemCollection fileAttributes = new ItemCollection(file.getAttributes());
|
||||
assertEquals(referenceNumber, fileAttributes.getItemValueString("ksef.referenceNumber"));
|
||||
|
||||
}
|
||||
|
||||
private ItemCollection createWorkitem() throws IOException {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ public class KSeFAuthManagerTest {
|
|||
|
||||
manager.openInteractiveSession();
|
||||
|
||||
manager.waitForSessionStatus();
|
||||
|
||||
assertNotNull(manager.getAccessToken(), "AccessToken fehlt");
|
||||
// assertNotNull(manager.getRefNumber(), "ReferenceNumber fehlt");
|
||||
assertNotNull(manager.getSessionRefNumber(), "SessionReferenceNumber fehlt");
|
||||
|
|
|
|||
Loading…
Reference in a new issue