Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1951,3 +1951,53 @@
| 🔧 zmienione | 32 |
| ➖ usunięte | 0 |


# Changelog zmian - `## 3.0.20 (2026-03-17)`- `API: 2.2.1`

## 1. ksef-client

### 1.1 system
- **FilesUtil.java**: 🔧 dla metody `splitAndEncryptZipStream` zmiana parametru `cryptographyService` na interfejs

### 1.2 api
- **DefaultKsefClient.java**: 🔧 do response z błędem dodano informacje z url i method, dodanie obsługi mapowań dla kodów http 401 i 403 dla formatu Problem Details `(application/problem+json)`
- **DefaultLighthouseKsefClient.java**: 🔧 do response z błędem dodano informacje z url i method

### 1.3 client
- **Headers.java**: 🔧 dodanie pola `String APPLICATION_PROBLEM_JSON = "application/problem+json"`

### 1.4 api.services
- **DefaultCryptographyService.java**: 🔧 dodanie metody `Exception getOfflineModeCause()` dającej informacje o powodzie przejścia w tryb offline

### 1.5 api.client.interfaces
- **CryptographyService.java**: 🔧 zmiany zgodnie z implementacja w `DefaultCryptographyService.java`

### 1.6 api.client.model
- **ApiException.java**: 🔧 dodanie pól `String url` i `String method`, zmiana na klasę abstrakcyjną
- **model/session/SchemaVersion.java**: 🔧 dodanie enuma `VERSION_1_1E("1-1E")`
- **UnauthorizedProblemDetails.java**: ➕ dodanie klasy
- **ForbiddenProblemDetails.java**: ➕ dodanie klasy
- **UnauthorizedApiException.java**: ➕ dodanie klasy, rozszerzającej `ApiException`
- **ForbiddenApiException.java**: ➕ dodanie klasy, rozszerzającej `ApiException`
- **KsefApiException.java**: ➕ dodanie klasy, rozszerzającej `ApiException`

## 2. demo-web-app

### 2.1 integrationTest
- **QrCodeOnlineIntegrationTest.java.java**: 🔧 drobne zmiany w asercji
- **RrInvoiceIntegrationTest.java.java**: 🔧 użycie nowej wersji schemy RR `SchemaVersion.VERSION_1_1E`
- **KsefTokenIntegrationTest.java.java**: 🔧 zmiany kosmetyczne w assercji
- **ExceptionsApiIntegrationTest.java.java**: ➕ dodanie scenariusza do obsługi kodów http 401 i 403 z API

### 2.1.1 integrationTest.resources
- **invoice-template-fa-rr-1.xml**: 🔧 aktualizacja pod nową wersję schemy RR

---
## 3. Podsumowanie

| Typ zmiany | Liczba plików |
|-------------|---------------|
| ➕ dodane | 6 |
| 🔧 zmienione | 12 |
| ➖ usunięte | 0 |

2 changes: 1 addition & 1 deletion demo-web-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

group = "pl.akmf.ksef"
version = "3.0.19"
version = "3.0.20"

java {
toolchain {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package pl.akmf.ksef.sdk;

import jakarta.xml.bind.JAXBException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import pl.akmf.ksef.sdk.api.builders.permission.entity.GrantEntityPermissionsRequestBuilder;
import pl.akmf.ksef.sdk.client.model.ApiException;
import pl.akmf.ksef.sdk.client.model.ForbiddenApiException;
import pl.akmf.ksef.sdk.client.model.ForbiddenProblemDetails;
import pl.akmf.ksef.sdk.client.model.UnauthorizedApiException;
import pl.akmf.ksef.sdk.client.model.UnauthorizedProblemDetails;
import pl.akmf.ksef.sdk.client.model.permission.OperationResponse;
import pl.akmf.ksef.sdk.client.model.permission.PermissionStatusInfo;
import pl.akmf.ksef.sdk.client.model.permission.entity.EntityPermission;
import pl.akmf.ksef.sdk.client.model.permission.entity.EntityPermissionType;
import pl.akmf.ksef.sdk.client.model.permission.entity.GrantEntityPermissionsRequest;
import pl.akmf.ksef.sdk.client.model.permission.entity.SubjectIdentifier;
import pl.akmf.ksef.sdk.configuration.BaseIntegrationTest;
import pl.akmf.ksef.sdk.util.IdentifierGeneratorUtils;

import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.Map;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static pl.akmf.ksef.sdk.api.Url.GRANT_INVOICE_SUBJECT_PERMISSION;

class ExceptionsApiIntegrationTest extends BaseIntegrationTest {

@Test
void forbiddenTest() throws JAXBException, IOException, ApiException {
String contextNip = IdentifierGeneratorUtils.generateRandomNIP();
String subjectNip = IdentifierGeneratorUtils.generateRandomNIP();
String thirdNip = IdentifierGeneratorUtils.generateRandomNIP();
String accessToken = authWithCustomNip(contextNip, contextNip).accessToken();

String grantReferenceNumber = grantPermission(subjectNip, accessToken);

await().pollDelay(Duration.ZERO)
.atMost(30, SECONDS)
.pollInterval(2, SECONDS)
.until(() -> isOperationFinish(grantReferenceNumber, accessToken));

String accessTokenSubject = authWithCustomNip(contextNip, subjectNip).accessToken();

ApiException apiException = assertThrows(ApiException.class, () ->
grantPermission(thirdNip, accessTokenSubject));
Assertions.assertEquals(403, apiException.getCode());
Assertions.assertEquals("POST", apiException.getMethod());
Assertions.assertTrue(apiException.getUrl().contains(GRANT_INVOICE_SUBJECT_PERMISSION.getUrl()));
ForbiddenApiException forbiddenApiException = (ForbiddenApiException) apiException;
Assertions.assertNotNull(forbiddenApiException);
ForbiddenProblemDetails forbiddenProblemDetails = forbiddenApiException.getForbiddenProblemDetails();
Assertions.assertNotNull(forbiddenProblemDetails);
Assertions.assertEquals("Forbidden", forbiddenProblemDetails.getTitle());
Assertions.assertEquals(403, forbiddenProblemDetails.getStatus());
Assertions.assertEquals("missing-permissions", forbiddenProblemDetails.getReasonCode());
Assertions.assertEquals("Brak wymaganych uprawnień do wykonania operacji w bieżącym kontekście.", forbiddenProblemDetails.getDetail());
Assertions.assertNotNull(forbiddenProblemDetails.getInstance());
Assertions.assertNotNull(forbiddenProblemDetails.getTraceId());
Map<String, Object> security = forbiddenProblemDetails.getSecurity();
Assertions.assertNotNull(security);
Assertions.assertTrue(security.get("requiredAnyOfPermissions").toString().contains("CredentialsManage"));
Assertions.assertTrue(security.get("presentPermissions").toString().contains("InvoiceRead"));
Assertions.assertTrue(security.get("presentPermissions").toString().contains("InvoiceWrite"));
}

@Test
void unauthorizedTest() {
String subjectNip = IdentifierGeneratorUtils.generateRandomNIP();

ApiException apiException = assertThrows(ApiException.class, () ->
grantPermission(subjectNip, "invalidToken"));
Assertions.assertEquals(401, apiException.getCode());
Assertions.assertEquals("POST", apiException.getMethod());
Assertions.assertTrue(apiException.getUrl().contains(GRANT_INVOICE_SUBJECT_PERMISSION.getUrl()));
UnauthorizedApiException unauthorizedApiException = (UnauthorizedApiException) apiException;
Assertions.assertNotNull(unauthorizedApiException);
UnauthorizedProblemDetails unauthorizedProblemDetails = unauthorizedApiException.getUnauthorizedProblemDetails();
Assertions.assertNotNull(unauthorizedProblemDetails);
Assertions.assertEquals("Unauthorized", unauthorizedProblemDetails.getTitle());
Assertions.assertEquals(401, unauthorizedProblemDetails.getStatus());
Assertions.assertEquals("Wymagane jest uwierzytelnienie.", unauthorizedProblemDetails.getDetail());
Assertions.assertNotNull(unauthorizedProblemDetails.getInstance());
Assertions.assertNotNull(unauthorizedProblemDetails.getTraceId());
}

private Boolean isOperationFinish(String referenceNumber, String accessToken) throws ApiException {
PermissionStatusInfo operations = ksefClient.permissionOperationStatus(referenceNumber, accessToken);
return operations != null && operations.getStatus().getCode() == 200;
}

private String grantPermission(String targetNip, String accessToken) throws ApiException {
GrantEntityPermissionsRequest request = new GrantEntityPermissionsRequestBuilder()
.withPermissions(List.of(
new EntityPermission(EntityPermissionType.INVOICE_READ, true),
new EntityPermission(EntityPermissionType.INVOICE_WRITE, false)))
.withDescription("description")
.withSubjectIdentifier(new SubjectIdentifier(SubjectIdentifier.IdentifierType.NIP, targetNip))
.withSubjectDetails(
new GrantEntityPermissionsRequest.PermissionsEntitySubjectDetails("Testowo")
)
.build();

OperationResponse response = ksefClient.grantsPermissionEntity(request, accessToken);
Assertions.assertNotNull(response);

return response.getReferenceNumber();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,13 @@ void checkGenerateTokenTest() throws IOException, ApiException, JAXBException {
Assertions.assertNotNull(token.getToken());
Assertions.assertNotNull(token.getReferenceNumber());

// step 2: wait for token to become ACTIVE
// step 2: wait for tokens to become ACTIVE
Awaitility.await().pollDelay(Duration.ZERO)
.atMost(10, SECONDS)
.pollInterval(1, SECONDS)
.until(() -> {
AuthenticationToken ksefToken = ksefClient.getKsefToken(token.getReferenceNumber(), accessToken);
return ksefToken != null && ksefToken.getStatus() == AuthenticationTokenStatus.ACTIVE;
});
.until(() -> isActiveToken(token, accessToken)
&& isActiveToken(token2, accessToken)
&& isActiveToken(token3, accessToken));

AuthenticationToken ksefToken = ksefClient.getKsefToken(token.getReferenceNumber(), accessToken);
Assertions.assertNotNull(ksefToken);
Expand Down Expand Up @@ -150,4 +149,9 @@ private Boolean isAuthStatusReady(String referenceNumber, String tempToken) thro
AuthStatus authStatus = ksefClient.getAuthStatus(referenceNumber, tempToken);
return authStatus != null && authStatus.getStatus().getCode() == 200;
}

private Boolean isActiveToken(GenerateTokenResponse token, String accessToken) throws ApiException {
AuthenticationToken ksefToken = ksefClient.getKsefToken(token.getReferenceNumber(), accessToken);
return ksefToken != null && ksefToken.getStatus() == AuthenticationTokenStatus.ACTIVE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void qrCodeOnlineE2ETest() throws JAXBException, IOException, ApiExceptio
.pollInterval(2, SECONDS)
.until(() -> isInvoicesInSessionAdded(sessionReferenceNumber, accessToken));

checkOnlineSessionStatus(sessionReferenceNumber, 100, accessToken);
Assertions.assertTrue(checkOnlineSessionStatus(sessionReferenceNumber, 100, accessToken));

//Zamknięcie sesji online
closeOnlineSession(sessionReferenceNumber, accessToken);
Expand Down Expand Up @@ -186,8 +186,7 @@ private boolean isInvoicesInSessionAdded(String sessionReferenceNumber, String a
private boolean checkOnlineSessionStatus(String sessionReferenceNumber, int expectedSessionStatus, String accessToken) throws ApiException {
SessionStatusResponse statusResponse = getSessionStatusResponse(sessionReferenceNumber, accessToken);
Assertions.assertNotNull(statusResponse);
Assertions.assertEquals(expectedSessionStatus, (int) statusResponse.getStatus().getCode());
return true;
return expectedSessionStatus == statusResponse.getStatus().getCode();
}

private SessionStatusResponse getSessionStatusResponse(String sessionReferenceNumber, String accessToken) throws ApiException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ void sendingFaRrInvoiceWithGrantPermission() throws JAXBException, IOException,
encryptionData = defaultCryptographyService.getEncryptionData();

// Otwarcie sesji online z kodem systemu FA_RR
String sessionReferenceNumber = openOnlineSession(encryptionData, SystemCode.FA_RR, SchemaVersion.VERSION_1_0E, SessionValue.RR, authorizedAccessToken);
String sessionReferenceNumber = openOnlineSession(encryptionData, SystemCode.FA_RR, SchemaVersion.VERSION_1_1E, SessionValue.RR, authorizedAccessToken);

// Wysłanie faktury FA-RR
sendRrInvoice(sessionReferenceNumber, encryptionData, grantorNip, authorizedNip, templateFileName, authorizedAccessToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<Faktura xmlns:etd="http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2022/01/05/eD/DefinicjeTypy/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://crd.gov.pl/wzor/2026/02/17/14164/">
xmlns="http://crd.gov.pl/wzor/2026/03/06/14189/">
<Naglowek>
<KodFormularza kodSystemowy="FA_RR(1)" wersjaSchemy="1-0E">FA_RR</KodFormularza>
<KodFormularza kodSystemowy="FA_RR (1)" wersjaSchemy="1-1E">FA_RR</KodFormularza>
<WariantFormularza>1</WariantFormularza>
<DataWytworzeniaFa>2026-02-23T10:37:00Z</DataWytworzeniaFa>
<SystemInfo>SamploFaktur</SystemInfo>
Expand Down
2 changes: 1 addition & 1 deletion ksef-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}


val appVersion = "3.0.19"
val appVersion = "3.0.20"
val artifactName = "ksef-client"

val githubRepositoryToken = "token"
Expand Down
Loading
Loading